From b78c709f76a941dd34f400ccb68bc081c33e8063 Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Sun, 24 May 2026 08:27:28 +0900 Subject: [PATCH] VirCaptureLevel_SavePos VirCaptureLevel_SaveCapture VirCaptureLevel_ChangeFlag --- .../VirCapture/VirCaptureLevel_ChangeFlag.cs | 40 +++++ .../VirCapture/VirCaptureLevel_SaveCapture.cs | 44 +++++ .../VirCapture/VirCaptureLevel_SavePos.cs | 48 ++++++ .../VirCapture/VirCaptureStateHelper.cs | 157 ++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_ChangeFlag.cs create mode 100644 GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs create mode 100644 GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SavePos.cs create mode 100644 GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureStateHelper.cs diff --git a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_ChangeFlag.cs b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_ChangeFlag.cs new file mode 100644 index 0000000..98ea278 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_ChangeFlag.cs @@ -0,0 +1,40 @@ +using MikuSB.Database; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.VirCapture; + +[CallGSApi("VirCaptureLevel_ChangeFlag")] +public class VirCaptureLevel_ChangeFlag : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null || req.LevelId == 0 || req.RegionId == 0) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_ChangeFlag", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + VirCaptureStateHelper.SetPointState(player, (uint)req.LevelId, (uint)req.RegionId, req.Clean ? 0u : 1u, sync); + + DatabaseHelper.SaveDatabaseType(player.Data); + var rsp = $"{{\"nLevelID\":{req.LevelId},\"nRegionId\":{req.RegionId},\"bClean\":{req.Clean.ToString().ToLowerInvariant()}}}"; + await CallGSRouter.SendScript(connection, "VirCaptureLevel_ChangeFlag", rsp, sync); + } +} + +internal sealed class VirCaptureChangeFlagParam +{ + [JsonPropertyName("nLevelID")] + public int LevelId { get; set; } + + [JsonPropertyName("nRegionId")] + public int RegionId { get; set; } + + [JsonPropertyName("bClean")] + public bool Clean { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs new file mode 100644 index 0000000..d812c44 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs @@ -0,0 +1,44 @@ +using MikuSB.Database; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.VirCapture; + +[CallGSApi("VirCaptureLevel_SaveCapture")] +public class VirCaptureLevel_SaveCapture : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null || req.LevelId == 0 || req.RegionId == 0) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_SaveCapture", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + VirCaptureStateHelper.SetPointState(player, (uint)req.LevelId, (uint)req.RegionId, 2u, sync); + + DatabaseHelper.SaveDatabaseType(player.Data); + + var response = new JsonObject + { + ["nLevelID"] = req.LevelId, + ["nRegionId"] = req.RegionId + }; + + await CallGSRouter.SendScript(connection, "VirCaptureLevel_SaveCapture", response.ToJsonString(), sync); + } +} + +internal sealed class VirCaptureSaveCaptureParam +{ + [JsonPropertyName("nLevelID")] + public int LevelId { get; set; } + + [JsonPropertyName("nRegionId")] + public int RegionId { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SavePos.cs b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SavePos.cs new file mode 100644 index 0000000..a080b0e --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SavePos.cs @@ -0,0 +1,48 @@ +using MikuSB.Database; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.VirCapture; + +[CallGSApi("VirCaptureLevel_SavePos")] +public class VirCaptureLevel_SavePos : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null || req.LevelId == 0) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_SavePos", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + VirCaptureStateHelper.SetSignedMapOffset(player, (uint)req.LevelId, VirCaptureStateHelper.OffPosX, req.PosX, sync); + VirCaptureStateHelper.SetSignedMapOffset(player, (uint)req.LevelId, VirCaptureStateHelper.OffPosY, req.PosY, sync); + VirCaptureStateHelper.SetSignedMapOffset(player, (uint)req.LevelId, VirCaptureStateHelper.OffPosZ, req.PosZ, sync); + VirCaptureStateHelper.SetSignedMapOffset(player, (uint)req.LevelId, VirCaptureStateHelper.OffToward, req.Toward, sync); + + DatabaseHelper.SaveDatabaseType(player.Data); + await CallGSRouter.SendScript(connection, "VirCaptureLevel_SavePos", "{}", sync); + } +} + +internal sealed class VirCaptureSavePosParam +{ + [JsonPropertyName("nLevelID")] + public int LevelId { get; set; } + + [JsonPropertyName("nPosX")] + public int PosX { get; set; } + + [JsonPropertyName("nPosY")] + public int PosY { get; set; } + + [JsonPropertyName("nPosZ")] + public int PosZ { get; set; } + + [JsonPropertyName("nToward")] + public int Toward { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureStateHelper.cs b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureStateHelper.cs new file mode 100644 index 0000000..371cc40 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureStateHelper.cs @@ -0,0 +1,157 @@ +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.VirCapture; + +internal static class VirCaptureStateHelper +{ + public const uint GroupId = 128; + public const uint MapDataStart = 10000; + public const uint MapDataEnd = 19000; + public const uint MaxMapCount = 3; + public const uint MaxMapDataLen = 3000; + public const uint MaxPatrolPoint = 500; + public const uint MaxOtherPoint = 2500; + public const uint MinMaterialId = 50000; + public const uint MaxMaterialId = 51500; + + public const uint OffMapId = 1; + public const uint OffTurnNum = 2; + public const uint OffPosX = 3; + public const uint OffPosY = 4; + public const uint OffPosZ = 5; + public const uint OffToward = 6; + public const uint OffDayNight = 7; + public const uint OffMapLevel = 8; + public const uint OffPatrolStart = 51; + public const uint OffPatrolEnd = 1000; + public const uint OffOtherStart = 1001; + public const uint OffOtherEnd = 1500; + public const uint OffMaterialStart = 1501; + public const uint OffMaterialEnd = 3000; + + public static uint FindOrAllocateMapSlot(PlayerInstance player, uint levelId) + { + uint? emptySlot = null; + for (uint i = 0; i < MaxMapCount; i++) + { + var slotStart = MapDataStart + (i * MaxMapDataLen); + var mapIdAttr = player.Data.Attrs.FirstOrDefault(x => x.Gid == GroupId && x.Sid == slotStart + OffMapId); + if (mapIdAttr?.Val == levelId) + return slotStart; + + if (emptySlot == null && (mapIdAttr == null || mapIdAttr.Val == 0)) + emptySlot = slotStart; + } + + return emptySlot ?? 0; + } + + public static void EnsureBaseMapState(PlayerInstance player, uint levelId, NtfSyncPlayer sync) + { + var slotStart = FindOrAllocateMapSlot(player, levelId); + if (slotStart == 0) + return; + + EnsureUnsignedAttr(player, slotStart + OffMapId, levelId, sync); + EnsureUnsignedAttr(player, slotStart + OffDayNight, 1, sync); + EnsureUnsignedAttr(player, slotStart + OffMapLevel, 1, sync); + } + + public static void SetSignedMapOffset(PlayerInstance player, uint levelId, uint offset, int value, NtfSyncPlayer sync) + { + var slotStart = FindOrAllocateMapSlot(player, levelId); + if (slotStart == 0) + return; + + EnsureBaseMapState(player, levelId, sync); + SetUnsignedAttr(player, slotStart + offset, unchecked((uint)value), sync); + } + + public static void SetPointState(PlayerInstance player, uint levelId, uint pointId, uint value, NtfSyncPlayer sync) + { + var slotStart = FindOrAllocateMapSlot(player, levelId); + if (slotStart == 0 || pointId == 0) + return; + + EnsureBaseMapState(player, levelId, sync); + + if (pointId <= MaxPatrolPoint) + { + var sid = slotStart + (OffPatrolStart - 1) + pointId; + SetUnsignedAttr(player, sid, value, sync); + return; + } + + if (pointId <= MaxOtherPoint) + { + var relative = pointId - MaxPatrolPoint; + var sid = slotStart + (uint)Math.Floor(relative / 30d) + OffOtherStart; + if (sid > slotStart + OffOtherEnd) + return; + + var bit = (int)(relative % 30); + var attr = GetOrCreateAttr(player, sid); + var next = value > 0 + ? attr.Val | (1u << bit) + : attr.Val & ~(1u << bit); + if (next != attr.Val) + { + attr.Val = next; + SyncAttr(player, sync, sid, next); + } + return; + } + + if (pointId > MinMaterialId && pointId <= MaxMaterialId) + { + var sid = slotStart + (OffMaterialStart - 1) + (pointId - MinMaterialId); + if (sid >= slotStart + OffMaterialEnd) + return; + + SetUnsignedAttr(player, sid, value, sync); + } + } + + public static void EnsureUnsignedAttr(PlayerInstance player, uint sid, uint minValue, NtfSyncPlayer sync) + { + var attr = GetOrCreateAttr(player, sid); + if (attr.Val < minValue) + { + attr.Val = minValue; + SyncAttr(player, sync, sid, attr.Val); + } + } + + public static void SetUnsignedAttr(PlayerInstance player, uint sid, uint value, NtfSyncPlayer sync) + { + var attr = GetOrCreateAttr(player, sid); + if (attr.Val != value) + { + attr.Val = value; + SyncAttr(player, sync, sid, value); + } + } + + private static PlayerAttr GetOrCreateAttr(PlayerInstance player, uint sid) + { + var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == GroupId && x.Sid == sid); + if (attr != null) + return attr; + + attr = new PlayerAttr + { + Gid = GroupId, + Sid = sid + }; + player.Data.Attrs.Add(attr); + return attr; + } + + private static void SyncAttr(PlayerInstance player, NtfSyncPlayer sync, uint sid, uint value) + { + sync.Custom[player.ToPackedAttrKey(GroupId, sid)] = value; + sync.Custom[player.ToShiftedAttrKey(GroupId, sid)] = value; + } +}