From 2f1b6d35da9810fbd16bf678f7862731f0fdd05f Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Mon, 25 May 2026 10:20:19 +0900 Subject: [PATCH] VirCapture fix EXP --- Common/Data/Excel/MonsterCardExcel.cs | 1 + Common/Data/Excel/VirCaptureLevelListExcel.cs | 2 + Common/Data/Excel/VirCaptureTimeExcel.cs | 1 + .../VirCapture/VirCaptureLevel_SaveCapture.cs | 131 +++++++++++++++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/Common/Data/Excel/MonsterCardExcel.cs b/Common/Data/Excel/MonsterCardExcel.cs index f25b257..6861cf5 100644 --- a/Common/Data/Excel/MonsterCardExcel.cs +++ b/Common/Data/Excel/MonsterCardExcel.cs @@ -12,6 +12,7 @@ public class MonsterCardExcel : ExcelResource [JsonProperty("Color")] public uint Color { get; set; } [JsonProperty("RikiId")] public uint RikiId { get; set; } [JsonProperty("CostValue")] public uint CostValue { get; set; } + [JsonProperty("Exp")] public uint Exp { get; set; } [JsonIgnore] public ulong TemplateId => GameResourceTemplateId.FromGdpl(Genre, Detail, Particular, Level); diff --git a/Common/Data/Excel/VirCaptureLevelListExcel.cs b/Common/Data/Excel/VirCaptureLevelListExcel.cs index 00dc16a..4a37d2c 100644 --- a/Common/Data/Excel/VirCaptureLevelListExcel.cs +++ b/Common/Data/Excel/VirCaptureLevelListExcel.cs @@ -6,8 +6,10 @@ namespace MikuSB.Data.Excel; public class VirCaptureLevelListExcel : ExcelResource { [JsonProperty("Level")] public uint Level { get; set; } + [JsonProperty("Exp")] public uint Exp { get; set; } [JsonProperty("Num")] public uint Num { get; set; } [JsonProperty("MaxCost")] public uint MaxCost { get; set; } + [JsonProperty("ExpUp")] public double ExpUp { get; set; } public override uint GetId() => Level; diff --git a/Common/Data/Excel/VirCaptureTimeExcel.cs b/Common/Data/Excel/VirCaptureTimeExcel.cs index 680e2cc..9cf04c3 100644 --- a/Common/Data/Excel/VirCaptureTimeExcel.cs +++ b/Common/Data/Excel/VirCaptureTimeExcel.cs @@ -9,6 +9,7 @@ public class VirCaptureTimeExcel : ExcelResource [JsonProperty("StartTime")] public string StartTime { get; set; } = ""; [JsonProperty("EndTime")] public string EndTime { get; set; } = ""; [JsonProperty("CaptureRegionId")] public List CaptureRegionId { get; set; } = []; + [JsonProperty("MaxExp")] public uint MaxExp { get; set; } public override uint GetId() => Id; diff --git a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs index 7661fdb..c7854dc 100644 --- a/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs +++ b/GameServer/Server/CallGS/Handlers/VirCapture/VirCaptureLevel_SaveCapture.cs @@ -11,6 +11,14 @@ namespace MikuSB.GameServer.Server.CallGS.Handlers.VirCapture; [CallGSApi("VirCaptureLevel_SaveCapture")] public class VirCaptureLevel_SaveCapture : ICallGSHandler { + private const uint VirCaptureGroupId = 128; + private const uint CurExpSid = 2; + private const uint CurLevelSid = 3; + private const uint BagNumSid = 5; + private const uint DailyExpSid = 8; + private const uint ColorMaxStartSid = 11; + private const uint RikiGroupId = 135; + public async Task Handle(Connection connection, string param, ushort seqNo) { var req = JsonSerializer.Deserialize(param); @@ -50,6 +58,7 @@ public class VirCaptureLevel_SaveCapture : ICallGSHandler sync.Items.Add(grantedItem.ToProto()); SyncVirCaptureCounters(player, grantedItem.TemplateId, sync); + ApplyCaptureExp(player, grantedItem.TemplateId, sync); DatabaseHelper.SaveDatabaseType(player.Data); DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData); @@ -68,17 +77,22 @@ public class VirCaptureLevel_SaveCapture : ICallGSHandler private static void SyncVirCaptureCounters(MikuSB.GameServer.Game.Player.PlayerInstance player, ulong templateId, NtfSyncPlayer sync) { var bagCount = (uint)player.InventoryManager.InventoryData.Items.Values.Count(x => x.ItemType == ItemTypeEnum.TYPE_MONSTER_CARD); - VirCaptureStateHelper.SetUnsignedAttr(player, 5, bagCount, sync); + VirCaptureStateHelper.SetUnsignedAttr(player, BagNumSid, bagCount, sync); if (!GameData.MonsterCardData.TryGetValue(templateId, out var monsterCard) || monsterCard.RikiId == 0) return; - var rikiAttr = player.Data.Attrs.FirstOrDefault(x => x.Gid == 135 && x.Sid == monsterCard.RikiId); + var colorSid = ColorMaxStartSid + Math.Max(0u, monsterCard.Color - 1u); + var colorAttr = player.Data.Attrs.FirstOrDefault(x => x.Gid == VirCaptureGroupId && x.Sid == colorSid); + var nextColorValue = (colorAttr?.Val ?? 0) + 1; + VirCaptureStateHelper.SetUnsignedAttr(player, colorSid, nextColorValue, sync); + + var rikiAttr = player.Data.Attrs.FirstOrDefault(x => x.Gid == RikiGroupId && x.Sid == monsterCard.RikiId); if (rikiAttr == null) { rikiAttr = new Database.Player.PlayerAttr { - Gid = 135, + Gid = RikiGroupId, Sid = monsterCard.RikiId, Val = 0 }; @@ -86,8 +100,115 @@ public class VirCaptureLevel_SaveCapture : ICallGSHandler } rikiAttr.Val += 1; - sync.Custom[player.ToPackedAttrKey(135, monsterCard.RikiId)] = rikiAttr.Val; - sync.Custom[player.ToShiftedAttrKey(135, monsterCard.RikiId)] = rikiAttr.Val; + sync.Custom[player.ToPackedAttrKey(RikiGroupId, monsterCard.RikiId)] = rikiAttr.Val; + sync.Custom[player.ToShiftedAttrKey(RikiGroupId, monsterCard.RikiId)] = rikiAttr.Val; + } + + private static void ApplyCaptureExp(MikuSB.GameServer.Game.Player.PlayerInstance player, ulong templateId, NtfSyncPlayer sync) + { + if (!GameData.MonsterCardData.TryGetValue(templateId, out var monsterCard) || monsterCard.Exp == 0) + return; + + var curLevelAttr = GetOrCreateVirCaptureAttr(player, CurLevelSid); + var curExpAttr = GetOrCreateVirCaptureAttr(player, CurExpSid); + var dailyExpAttr = GetOrCreateVirCaptureAttr(player, DailyExpSid); + + var maxLevel = GameData.VirCaptureLevelListData.Count == 0 ? 1u : GameData.VirCaptureLevelListData.Keys.Max(); + var curLevel = Math.Max(1u, curLevelAttr.Val); + if (curLevel >= maxLevel) + return; + + var baseExp = monsterCard.Exp; + if (GameData.VirCaptureLevelListData.TryGetValue(curLevel, out var currentLevelCfg) && currentLevelCfg.ExpUp > 1d) + baseExp = (uint)Math.Floor(baseExp * currentLevelCfg.ExpUp); + + var maxDailyExp = ResolveCurrentAct(player)?.MaxExp ?? 0u; + if (maxDailyExp > 0 && dailyExpAttr.Val >= maxDailyExp) + return; + + var gainExp = baseExp; + if (maxDailyExp > 0) + gainExp = Math.Min(gainExp, maxDailyExp - dailyExpAttr.Val); + + if (gainExp == 0) + return; + + dailyExpAttr.Val += gainExp; + SyncVirCaptureAttr(player, DailyExpSid, dailyExpAttr.Val, sync); + + var pendingExp = curExpAttr.Val + gainExp; + while (GameData.VirCaptureLevelListData.TryGetValue(curLevel, out var levelCfg) && curLevel < maxLevel) + { + if (pendingExp < levelCfg.Exp) + break; + + pendingExp -= levelCfg.Exp; + curLevel++; + } + + curLevelAttr.Val = curLevel; + curExpAttr.Val = curLevel >= maxLevel + ? GameData.VirCaptureLevelListData.GetValueOrDefault(maxLevel)?.Exp ?? pendingExp + : pendingExp; + + SyncVirCaptureAttr(player, CurLevelSid, curLevelAttr.Val, sync); + SyncVirCaptureAttr(player, CurExpSid, curExpAttr.Val, sync); + } + + private static Database.Player.PlayerAttr GetOrCreateVirCaptureAttr(MikuSB.GameServer.Game.Player.PlayerInstance player, uint sid) + { + var attr = player.Data.Attrs.FirstOrDefault(x => x.Gid == VirCaptureGroupId && x.Sid == sid); + if (attr != null) + return attr; + + attr = new Database.Player.PlayerAttr + { + Gid = VirCaptureGroupId, + Sid = sid, + Val = 0 + }; + player.Data.Attrs.Add(attr); + return attr; + } + + private static void SyncVirCaptureAttr(MikuSB.GameServer.Game.Player.PlayerInstance player, uint sid, uint value, NtfSyncPlayer sync) + { + sync.Custom[player.ToPackedAttrKey(VirCaptureGroupId, sid)] = value; + sync.Custom[player.ToShiftedAttrKey(VirCaptureGroupId, sid)] = value; + } + + private static MikuSB.Data.Excel.VirCaptureTimeExcel? ResolveCurrentAct(MikuSB.GameServer.Game.Player.PlayerInstance player) + { + var actId = player.Data.Attrs.FirstOrDefault(x => x.Gid == VirCaptureGroupId && x.Sid == 1)?.Val ?? 0; + if (actId > 0 && GameData.VirCaptureTimeData.TryGetValue(actId, out var act)) + return act; + + var now = DateTime.Now; + return GameData.VirCaptureTimeData.Values + .Select(x => new { Config = x, Start = ParseConfigTime(x.StartTime), End = ParseConfigTime(x.EndTime) }) + .Where(x => x.Start.HasValue && x.End.HasValue && x.Start <= now && now < x.End) + .OrderBy(x => x.Start) + .Select(x => x.Config) + .FirstOrDefault(); + } + + 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", + System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.None, + out var value) + ? value + : null; } }