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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
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 > 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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,17 +75,17 @@ public class MapEntityStorage(MetadataContext context) : MetadataStorage<string,
regionSkills.Add(regionSkill);
break;
case SpawnPointPC playerSpawn:
playerSpawns[playerSpawn.Id] = playerSpawn;
playerSpawns[playerSpawn.SpawnPointId] = playerSpawn;
break;
case SpawnPointNPC npcSpawn:
if (npcSpawn is EventSpawnPointNPC eventNpcSpawn) {
eventNpcSpawns.Add(eventNpcSpawn.Id, eventNpcSpawn);
eventNpcSpawns.Add(eventNpcSpawn.SpawnPointId, eventNpcSpawn);
} else {
npcSpawns.Add(npcSpawn);
}
break;
case EventSpawnPointItem eventItemSpawn:
eventItemSpawns.Add(eventItemSpawn.Id, eventItemSpawn);
eventItemSpawns.Add(eventItemSpawn.SpawnPointId, eventItemSpawn);
break;
case TaxiStation taxiStation:
Debug.Assert(taxi == null, $"Multiple taxi stations found in xblock:{xblock}");
Expand Down
15 changes: 15 additions & 0 deletions Maple2.Database/Storage/Metadata/QuestMetadataStorage.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Maple2.Database.Context;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Microsoft.EntityFrameworkCore;

Expand DownExpand Up@@ -47,6 +48,20 @@ public IEnumerable<QuestMetadata> GetQuestsByNpc(int npcId) {
}
}

public IEnumerable<QuestMetadata> GetQuestsByType(QuestType type) {
lock (Context) {
if (type == QuestType.EpicQuest) {
// Type is not saved because EpicQuest value is 0.
return Context.QuestMetadata
.FromSqlRaw($@"SELECT * FROM `quest` WHERE JSON_UNQUOTE(JSON_EXTRACT(Basic, '$.Type')) IS NULL")
.ToList();
}
return Context.QuestMetadata
.FromSqlRaw($"SELECT * FROM `quest` WHERE JSON_EXTRACT(Basic, '$.Type')={(int) type}")
.ToList();
}
}

public List<QuestMetadata> Search(string name) {
lock (Context) {
return Context.QuestMetadata
Expand Down
4 changes: 2 additions & 2 deletions Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,13 +102,13 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
switch (npcSpawn) {
case IEventSpawnPointNPC eventNpcSpawn:
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new EventSpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
Block = new EventSpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, (int) eventNpcSpawn.LifeTime, eventNpcSpawn.SpawnAnimation)
};
continue;
default:
string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null;
yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) {
Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
Block = new SpawnPointNPC(npcSpawn.EntityId, npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, npcList, (int) npcSpawn.RegenCheckTime, patrolData)
};
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Enum/Admin.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,15 +65,17 @@ public enum PetReportFlag {

[Flags]
public enum AdminPermissions {
Alert,
Ban,
SpawnItem,
SpawnNpc,
Debug,
Quest,
Warp,
Find,
PlayerCommands,
Alert = 1,
StringBoard = 2,
EventManagement = 4,
Ban = 8,
SpawnItem = 16,
SpawnNpc = 32,
Debug = 64,
Quest = 128,
Warp = 256,
Find = 512,
PlayerCommands = 1024,

Spawn = SpawnNpc | SpawnItem,
Admin = int.MaxValue,
Expand Down
20 changes: 11 additions & 9 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,20 +4,21 @@

namespace Maple2.Model.Metadata;

public abstract record SpawnPoint(int Id, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;
public abstract record SpawnPoint(int SpawnPointId, Vector3 Position, Vector3 Rotation, bool Visible) : MapBlock;

public record SpawnPointPC(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
bool Enable
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record SpawnPointNPCListEntry(int NpcId, int Count);

public record SpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -26,10 +27,11 @@ public record SpawnPointNPC(
IList<SpawnPointNPCListEntry> NpcList,
int RegenCheckTime,
string? PatrolData
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);

public record EventSpawnPointNPC(
int Id,
string EntityId,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
bool Visible,
Expand All@@ -39,15 +41,15 @@ public record EventSpawnPointNPC(
int RegenCheckTime,
int LifeTime,
string SpawnAnimation
) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);
) : SpawnPointNPC(EntityId, SpawnPointId, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcList, RegenCheckTime, String.Empty);

public record EventSpawnPointItem(
int Id,
int SpawnPointId,
Vector3 Position,
Vector3 Rotation,
float Lifetime,
int IndividualDropBoxId,
int GlobalDropBoxId,
int GlobalDropLevel,
bool Visible
) : SpawnPoint(Id, Position, Rotation, Visible);
) : SpawnPoint(SpawnPointId, Position, Rotation, Visible);
62 changes: 56 additions & 6 deletions Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.CommandLine.IO;
using Maple2.Database.Storage;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;

Expand All@@ -22,25 +23,74 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
var id = new Argument<int>("id", "Id of buff to activate.");
var level = new Option<int>(["--level", "-l"], () => 1, "Buff level.");
var stack = new Option<int>(["--stack", "-s"], () => 1, "Amount of stacks on the buff.");
var duration = new Option<int>(["--duration", "-d"], () => -1, "Duration of the buff in seconds.");
var all = new Option<bool>(["--all", "-a"], () => false, "Apply to all players in the field.");
var target = new Option<string>(["--target", "-t"], () => string.Empty, "Target player by name.");
var remove = new Option<bool>(["--remove", "-r"], () => false, "Remove buff from target.");

AddArgument(id);
AddOption(level);
AddOption(stack);
this.SetHandler<InvocationContext, int, int, int>(Handle, id, level, stack);
AddOption(duration);
AddOption(all);
AddOption(target);
AddOption(remove);
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

private void Handle(InvocationContext ctx, int buffId, int level, int stack) {
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? _)) {
ctx.Console.Error.WriteLine($"Invalid buff: {buffId}, level: {level}");
return;
}

session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
if (duration > 0) {
duration *= 1000; // convert to milliseconds
}

if (all) {
foreach (FieldPlayer player in session.Field.Players.Values) {
if (remove) {
player.Buffs.Remove(buffId);
} else {
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
}

}
} else if (!string.IsNullOrEmpty(target)) {
FieldPlayer? player = session.Field.GetPlayers().Values
.FirstOrDefault(player => string.Equals(player.Value.Character.Name, target, StringComparison.OrdinalIgnoreCase));
if (player is null) {
ctx.Console.Error.WriteLine($"Player {target} not found.");
return;
}

if (remove) {
player.Buffs.Remove(buffId);
return;
}
player.Buffs.AddBuff(session.Player, player, buffId, (short) level, duration);
if (stack > 1) {
player.Buffs.Buffs[buffId].Stack(stack);
session.Field.Broadcast(BuffPacket.Update(player.Buffs.Buffs[buffId]));
}
} else {
if (remove) {
session.Player.Buffs.Remove(buffId);
return;
}
session.Player.Buffs.AddBuff(session.Player, session.Player, buffId, (short) level, duration);
if (stack > 1) {
session.Player.Buffs.Buffs[buffId].Stack(stack);
session.Field?.Broadcast(BuffPacket.Update(session.Player.Buffs.Buffs[buffId]));
}
}

ctx.ExitCode = 0;
} catch (SystemException ex) {
ctx.Console.Error.WriteLine(ex.Message);
Expand Down
133 changes: 133 additions & 0 deletions Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model;
using Maple2.Server.Game.Model.Skill;
using Maple2.Server.Game.Packets;
using Maple2.Server.Game.Session;
using Maple2.Tools.Collision;

namespace Maple2.Server.Game.Commands;

public class KillCommand : Command {
private const string NAME = "kill";
private const string DESCRIPTION = "Kill NPCs and/or Mobs.";

public KillCommand(GameSession session) : base(NAME, DESCRIPTION) {
AddCommand(new KillAllNpcCommand(session));
AddCommand(new KillNearNpcCommand(session));
}

private class KillAllNpcCommand : Command {
private readonly GameSession session;

public KillAllNpcCommand(GameSession session) : base("all", "Kill all NPCs in map.") {
this.session = session;

var npcOnly = new Option<bool>("npc", () => false, "Kill only npcs.");
var mobOnly = new Option<bool>("mob", () => false, "Kill only mobs.");

AddOption(npcOnly);
AddOption(mobOnly);
this.SetHandler<InvocationContext, bool, bool>(Handle, npcOnly, mobOnly);
}

private void Handle(InvocationContext ctx, bool npcOnly, bool mobOnly) {
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}

if (npcOnly == mobOnly) {
// Consider both to be true
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);

}
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs and Mobs removed.");
return;
}

if (npcOnly) {
foreach (FieldNpc npc in session.Field.Npcs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All NPCs removed.");
} else {
foreach (FieldNpc npc in session.Field.Mobs.Values) {
Kill(session, npc, skill);
}
ctx.Console.Out.WriteLine("All Mobs removed.");
}
}
}

private class KillNearNpcCommand : Command {
private readonly GameSession session;

public KillNearNpcCommand(GameSession session) : base("near", "Kill mobs/npcs within a given range.") {
this.session = session;

var distance = new Option<int>(["--distance", "-d"], () => 150, "Radius to kill mobs/npcs.");

AddOption(distance);
this.SetHandler<InvocationContext, int>(Handle, distance);

}

private void Handle(InvocationContext ctx, int distance) {
var rec = new Rectangle(new Vector2(session.Player.Position.X, session.Player.Position.Y), distance, distance, 0);
var prism = new Prism(rec, session.Player.Position.Z, distance * 2);
if (!session.SkillMetadata.TryGet(10000001, 1, out SkillMetadata? skill)) {
ctx.Console.Out.WriteLine("Skill not found.");
return;
}
foreach (FieldNpc npc in session.Field.Npcs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}

foreach (FieldNpc npc in session.Field.Mobs.Values) {
if (prism.Intersects(npc.Shape)) {
ctx.Console.Out.WriteLine($"Killing {npc.Value.Metadata.Name} - ObjectId: ({npc.ObjectId})");
Kill(session, npc, skill);
}
}
}
}

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,
TargetUid = ((long) Random.Shared.Next(int.MinValue, int.MaxValue) << 32) | (uint) Random.Shared.Next(int.MinValue, int.MaxValue),
OwnerId = session.Player.ObjectId,
SkillId = skill.Id,
Level = skill.Level,
AttackPoint = 0,
MotionPoint = 0,
Position = npc.Position,
Direction = session.Player.Rotation,
};
var target = new DamageRecordTarget() {
ObjectId = npc.ObjectId,
Position = session.Player.Position,
Direction = session.Player.Rotation,
};

long damage = npc.Stats.Values[BasicAttribute.Health].Current;
target.AddDamage(DamageType.Normal, damage);
damageRecord.Targets.Add(target);
npc.Stats.Values[BasicAttribute.Health].Add(-damage);
session.Field.Broadcast(StatsPacket.Update(npc, BasicAttribute.Health));
Comment thread
Zintixx marked this conversation as resolved.

session.Field.Broadcast(SkillDamagePacket.Damage(damageRecord));
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Commands/WarpCommand.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,7 +79,7 @@ public MapCommand(GameSession session, MapMetadataStorage mapStorage) : base("ma

AddArgument(mapName);
AddOption(mapIndex);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName);
this.SetHandler<InvocationContext, string[], int>(Handle, mapName, mapIndex);
}

private void Handle(InvocationContext ctx, string[] mapName, int mapIndex) {
Expand Down
Loading