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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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