From 12094f6dd1f0c85b63a559f86d61ca61f1a2c06a Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Sun, 24 May 2026 08:18:41 +0900 Subject: [PATCH] VirCaptureLevel_EnterLevel --- .../Excel/VirCaptureCaptureRegionExcel.cs | 19 ++ Common/Data/Excel/VirCaptureTimeExcel.cs | 1 + Common/Data/GameData.cs | 1 + .../VirCapture/VirCaptureLevel_EnterLevel.cs | 171 ++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 Common/Data/Excel/VirCaptureCaptureRegionExcel.cs create mode 100644 GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_EnterLevel.cs diff --git a/Common/Data/Excel/VirCaptureCaptureRegionExcel.cs b/Common/Data/Excel/VirCaptureCaptureRegionExcel.cs new file mode 100644 index 0000000..9e7f2bf --- /dev/null +++ b/Common/Data/Excel/VirCaptureCaptureRegionExcel.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("dlc/vircapture/captureregion.json")] +public class VirCaptureCaptureRegionExcel : ExcelResource +{ + [JsonProperty("Id")] public uint Id { get; set; } + [JsonProperty("StartTime")] public string StartTime { get; set; } = ""; + [JsonProperty("EndTime")] public string EndTime { get; set; } = ""; + [JsonProperty("MapId")] public uint MapId { get; set; } + + public override uint GetId() => Id; + + public override void Loaded() + { + GameData.VirCaptureCaptureRegionData[Id] = this; + } +} diff --git a/Common/Data/Excel/VirCaptureTimeExcel.cs b/Common/Data/Excel/VirCaptureTimeExcel.cs index 2195228..680e2cc 100644 --- a/Common/Data/Excel/VirCaptureTimeExcel.cs +++ b/Common/Data/Excel/VirCaptureTimeExcel.cs @@ -8,6 +8,7 @@ public class VirCaptureTimeExcel : ExcelResource [JsonProperty("Id")] public uint Id { get; set; } [JsonProperty("StartTime")] public string StartTime { get; set; } = ""; [JsonProperty("EndTime")] public string EndTime { get; set; } = ""; + [JsonProperty("CaptureRegionId")] public List CaptureRegionId { get; set; } = []; public override uint GetId() => Id; diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 2e526ee..ffa628d 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -53,6 +53,7 @@ public static class GameData public static Dictionary VirCaptureTimeData { get; private set; } = []; public static Dictionary VirCaptureSeasonData { get; private set; } = []; public static Dictionary VirCaptureTrialTimeData { get; private set; } = []; + public static Dictionary VirCaptureCaptureRegionData { get; private set; } = []; } public static class GameResourceTemplateId diff --git a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_EnterLevel.cs b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_EnterLevel.cs new file mode 100644 index 0000000..f5933ef --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_EnterLevel.cs @@ -0,0 +1,171 @@ +using MikuSB.Data; +using MikuSB.Data.Excel; +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.VirCapture; + +[CallGSApi("VirCaptureLevel_EnterLevel")] +public class VirCaptureLevel_EnterLevel : ICallGSHandler +{ + private const uint GroupId = 128; + private const uint MapDataStart = 10000; + private const uint MaxMapCount = 3; + private const uint MaxMapDataLen = 3000; + private const uint OffMapId = 1; + private const uint OffDayNight = 7; + private const uint OffMapLevel = 8; + private static readonly Random Random = new(); + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null || req.LevelId == 0 || req.TeamId <= 0) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_EnterLevel", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var now = DateTime.Now; + var act = ResolveCurrent(GameData.VirCaptureTimeData.Values, now); + if (act == null || !act.CaptureRegionId.Contains((uint)req.LevelId)) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_EnterLevel", "{\"sErr\":\"ui.TxtNotOpen\"}"); + return; + } + + if (!GameData.VirCaptureCaptureRegionData.TryGetValue((uint)req.LevelId, out var region)) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_EnterLevel", "{\"sErr\":\"error.BadParam\"}"); + return; + } + + var regionStart = ParseConfigTime(region.StartTime); + var regionEnd = ParseConfigTime(region.EndTime); + if (!regionStart.HasValue || !regionEnd.HasValue || now < regionStart.Value || now >= regionEnd.Value) + { + await CallGSRouter.SendScript(connection, "VirCaptureLevel_EnterLevel", "{\"sErr\":\"ui.TxtNotOpen\"}"); + return; + } + + var player = connection.Player!; + var sync = new NtfSyncPlayer(); + EnsureMapState(player, (uint)req.LevelId, sync); + + var rsp = $"{{\"nSeed\":{Random.Next(1, 1_000_000_000)}}}"; + await CallGSRouter.SendScript(connection, "VirCaptureLevel_EnterLevel", rsp, sync); + } + + private static void EnsureMapState(PlayerInstance player, uint levelId, NtfSyncPlayer sync) + { + var slotStart = FindOrAllocateMapSlot(player, levelId); + if (slotStart == 0) + return; + + EnsureMapAttr(player, slotStart + OffMapId, levelId, sync); + EnsureMapAttr(player, slotStart + OffDayNight, 1, sync); + EnsureMapAttr(player, slotStart + OffMapLevel, 1, sync); + } + + private 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; + } + + private static void EnsureMapAttr(PlayerInstance player, uint sid, uint minValue, NtfSyncPlayer sync) + { + var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == GroupId && x.Sid == sid); + if (attr == null) + { + attr = new PlayerAttr + { + Gid = GroupId, + Sid = sid, + Val = minValue + }; + player.Data.Attrs.Add(attr); + SyncAttr(player, sync, sid, minValue); + return; + } + + if (attr.Val < minValue) + { + attr.Val = minValue; + SyncAttr(player, sync, sid, attr.Val); + } + } + + 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; + } + + private static VirCaptureTimeExcel? ResolveCurrent(IEnumerable configs, DateTime now) + { + var parsed = configs + .Select(x => new + { + Config = x, + Start = ParseConfigTime(x.StartTime), + End = ParseConfigTime(x.EndTime) + }) + .Where(x => x.Start.HasValue && x.End.HasValue) + .OrderBy(x => x.Start) + .ToList(); + + var current = parsed.FirstOrDefault(x => x.Start <= now && now < x.End); + if (current != null) + return current.Config; + + var latestStarted = parsed.LastOrDefault(x => x.Start <= now); + if (latestStarted != null && latestStarted.End > latestStarted.Start) + return latestStarted.Config; + + return null; + } + + private static DateTime? ParseConfigTime(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + return null; + + var normalized = raw.Trim().Trim('[', ']'); + if (normalized.Length != 12) + return null; + + return DateTime.TryParseExact( + normalized, + "yyyyMMddHHmm", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var value) + ? value + : null; + } +} + +internal sealed class VirCaptureEnterLevelParam +{ + [JsonPropertyName("nLevelID")] + public int LevelId { get; set; } + + [JsonPropertyName("nTeamID")] + public int TeamId { get; set; } +}