using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using RainOpsMini.Helpers; using RainOpsMini.Models; using RainOpsMini.Models; using SqlSugar; namespace RainOpsMini.Services { public class CdkService { public static async Task> GenerateCdksAsync(string type, int duration, int count, string reason, string creator) { var list = new List(); using var db = DbHelper.GetInstance(); string batchId = Guid.NewGuid().ToString("N"); for (int i = 0; i < count; i++) { var cdk = new CdkInfo { Code = "CDK" + GenerateRandomCode(13), // Total 16 chars Type = type, DurationDays = duration, Reason = reason, Creator = creator, Status = "Unused", CreatedAt = DateTime.UtcNow, BatchId = batchId }; list.Add(cdk); } await db.Insertable(list).ExecuteCommandAsync(); return list; } public static async Task<(bool success, string message)> RedeemCdkAsync(string code, string steamId) { using var db = DbHelper.GetInstance(); // 1. Find CDK var cdk = await db.Queryable().FirstAsync(c => c.Code == code); if (cdk == null) return (false, "无效的兑换码"); if (cdk.Status != "Unused") return (false, "该兑换码已被使用"); // 2. Validate SteamID (Simple check) if (string.IsNullOrWhiteSpace(steamId) || steamId.Length < 17) return (false, "无效的 SteamID"); // 3. Check Batch Limit (One redemption per batch per user) if (!string.IsNullOrEmpty(cdk.BatchId)) { var alreadyRedeemed = await db.Queryable() .Where(c => c.BatchId == cdk.BatchId && c.RedeemedBy == steamId) .AnyAsync(); if (alreadyRedeemed) { return (false, "您已领取过该批次的兑换码,无法重复领取"); } } // 4. Update CDK status cdk.Status = "Used"; cdk.RedeemedBy = steamId; cdk.RedeemedAt = DateTime.UtcNow; await db.Updateable(cdk).ExecuteCommandAsync(); // 5. Grant VIP/SVIP // Map type to internal group name bool isRedeemingSvip = cdk.Type == "SVIP"; var existingVip = VipStorage.GetBySteamId(steamId); // Initializing/Migrating Expiries DateTime? currentVipExpiry = null; DateTime? currentSvipExpiry = null; string finalGroup = ""; DateTime finalExpiry = DateTime.MinValue; if (existingVip != null) { // Migrate legacy data if needed if (existingVip.VipExpiryDate == null && existingVip.SvipExpiryDate == null) { if (existingVip.Group == "SVIP_RainOpsMini") { existingVip.SvipExpiryDate = existingVip.ExpiryDate; // SVIP usually implies VIP privileges, but let's keep them somewhat distinct or // initialize VIP expiry to same as SVIP if we consider SVIP > VIP. // For independent tracking requested by user, if they were SVIP, we only set SVIP expiry. // But if they downgrade, they might have lost VIP. // Let's assume SVIP covers VIP, so we might set VIP expiry too? // User said: "我是SVIP身份但是我兑换VIP的CDK也会延长SVIP的时长,要分开独立计算" // This implies they want to accumulate VIP time even while SVIP. // So if we are migrating an SVIP user, we probably should set VIP expiry to same date? // Or maybe leave VIP as null (expired/inactive)? // If we leave VIP null, and they redeem VIP, they get VIP time. // If their SVIP expires, they fall back to VIP. // Let's set SvipExpiryDate = ExpiryDate. VipExpiryDate = null (or maybe ExpiryDate if we want to be generous). // Let's be strict: SVIP implies SVIP expiry. } else if (existingVip.Group == "VIP_RainOpsMini") { existingVip.VipExpiryDate = existingVip.ExpiryDate; } } currentVipExpiry = existingVip.VipExpiryDate; currentSvipExpiry = existingVip.SvipExpiryDate; } // Calculate new expiry if (isRedeemingSvip) { // Update SVIP Expiry DateTime baseTime = (currentSvipExpiry.HasValue && currentSvipExpiry.Value > DateTime.Now) ? currentSvipExpiry.Value : DateTime.Now; currentSvipExpiry = baseTime.AddDays(cdk.DurationDays); } else { // Update VIP Expiry DateTime baseTime = (currentVipExpiry.HasValue && currentVipExpiry.Value > DateTime.Now) ? currentVipExpiry.Value : DateTime.Now; currentVipExpiry = baseTime.AddDays(cdk.DurationDays); } // Determine Effective Group // Rule: SVIP > VIP > None bool hasActiveSvip = currentSvipExpiry.HasValue && currentSvipExpiry.Value > DateTime.Now; bool hasActiveVip = currentVipExpiry.HasValue && currentVipExpiry.Value > DateTime.Now; if (hasActiveSvip) { finalGroup = "SVIP_RainOpsMini"; finalExpiry = currentSvipExpiry.Value; } else if (hasActiveVip) { finalGroup = "VIP_RainOpsMini"; finalExpiry = currentVipExpiry.Value; } else { // Both expired? Or just newly added but expired (unlikely with duration > 0) // If redeeming valid CDK, at least one should be active. // Fallback finalGroup = isRedeemingSvip ? "SVIP_RainOpsMini" : "VIP_RainOpsMini"; finalExpiry = DateTime.Now.AddDays(cdk.DurationDays); // Should not happen } if (existingVip != null) { existingVip.Group = finalGroup; existingVip.ExpiryDate = finalExpiry; existingVip.VipExpiryDate = currentVipExpiry; existingVip.SvipExpiryDate = currentSvipExpiry; existingVip.Remark = $"CDK: {cdk.Reason}"; VipStorage.Save(); } else { // New User var newVip = new VipMember { SteamId = steamId, Name = "Redeemed User", Group = finalGroup, ExpiryDate = finalExpiry, VipExpiryDate = currentVipExpiry, SvipExpiryDate = currentSvipExpiry, Remark = $"CDK: {cdk.Reason}", CreatedAt = DateTime.Now }; try { var players = Program.RconCache?.Players?.ToList(); if (players != null) { var player = players.FirstOrDefault(p => p.SteamId == steamId); if (player != null) newVip.Name = player.Name; } } catch {} VipStorage.Add(newVip); } // Sync to config try { string cfgPath = System.IO.Path.Combine(Environment.CurrentDirectory, "SquadGame", "ServerConfig", "Admins.cfg"); VipStorage.SyncToAdminConfig(cfgPath); } catch (Exception ex) { RainOpsLog.Log($"[CDK] Sync Admin Config Failed: {ex.Message}"); } string svipMsg = hasActiveSvip ? $"{currentSvipExpiry:yyyy-MM-dd HH:mm}" : (currentSvipExpiry.HasValue ? "已过期" : "未开通"); string vipMsg = hasActiveVip ? $"{currentVipExpiry:yyyy-MM-dd HH:mm}" : (currentVipExpiry.HasValue ? "已过期" : "未开通"); string groupDisplay = finalGroup == "SVIP_RainOpsMini" ? "SVIP" : "VIP"; return (true, $"兑换成功!\n当前生效:{groupDisplay}\nSVIP到期:{svipMsg}\nVIP 到期:{vipMsg}"); } private static string GenerateRandomCode(int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var random = new Random(); return new string(Enumerable.Repeat(chars, length) .Select(s => s[random.Next(s.Length)]).ToArray()); } } }