using System; using System.Collections.Generic; using System.Linq; namespace ET.Server { [FriendOf(typeof(GachaComponent))] public static class GachaComponentSystem { public static List Draw(this GachaComponent self, int poolId, GachaDrawType drawType, BagComponent bag) { List weights = GetPoolWeights(poolId); if (weights.Count == 0) { return new List(); } int times = drawType == GachaDrawType.Single ? 1 : 10; List costs = GetDrawCost(poolId, drawType); if (!bag.HasItems(costs)) { throw new RpcException(ErrorCode.ERR_GACHA_COST_NOT_ENOUGH, "Gacha cost not enough"); } bool removed = bag.RemoveItems(costs); if (!removed) { throw new RpcException(ErrorCode.ERR_GACHA_COST_NOT_ENOUGH, "Gacha cost remove failed"); } List rewards = new(times); for (int i = 0; i < times; i++) { GachaPoolWeight pick = PickByWeight(weights); rewards.Add(new RewardEntry { ItemId = pick.ItemId, Count = pick.Count }); self.UpdateGuarantee(poolId, pick.Rare, 90); } if (drawType == GachaDrawType.Multi10 && !rewards.Any(r => GetRareByItemId(r.ItemId) >= 4)) { self.ReplaceWithGuaranteed(rewards, weights, 4); } bag.AddItems(rewards); LogExtensions.Audit( "Gacha", "Draw", $"{{\"poolId\":{poolId},\"drawType\":{(int)drawType},\"rewardsCount\":{rewards.Count},\"guaranteeRemain\":{self.GetGuaranteeRemain(poolId)}}}"); return rewards; } public static void UpdateGuarantee(this GachaComponent self, int poolId, int rare, int guaranteeCount) { if (!self.PoolStats.TryGetValue(poolId, out GachaPoolState state)) { state = new GachaPoolState { GuaranteeRemain = guaranteeCount }; self.PoolStats[poolId] = state; } state.TotalCount += 1; state.LastDrawMs = TimeInfo.Instance.ServerNow(); if (rare >= 5) { state.GuaranteeRemain = guaranteeCount; return; } state.GuaranteeRemain = Math.Max(0, state.GuaranteeRemain - 1); if (state.GuaranteeRemain == 0) { state.GuaranteeRemain = guaranteeCount; } } public static void ReplaceWithGuaranteed(this GachaComponent self, List rewards, List weights, int minRare) { GachaPoolWeight guaranteed = weights.FirstOrDefault(x => x.Rare >= minRare); if (guaranteed == null || rewards.Count == 0) { return; } int index = rewards.Count - 1; rewards[index] = new RewardEntry { ItemId = guaranteed.ItemId, Count = guaranteed.Count }; } public static int GetGuaranteeRemain(this GachaComponent self, int poolId) { if (!self.PoolStats.TryGetValue(poolId, out GachaPoolState state)) { return 90; } return state.GuaranteeRemain; } private static GachaPoolWeight PickByWeight(List weights) { int total = weights.Sum(x => x.Weight); int point = CryptoRandomGenerator.Next(0, Math.Max(total, 1)); int sum = 0; foreach (GachaPoolWeight w in weights) { sum += w.Weight; if (point < sum) { return w; } } return weights[weights.Count - 1]; } private static List GetDrawCost(int poolId, GachaDrawType drawType) { // TODO: 由 Luban TbGachaPool 驱动。当前使用固定测试配置。 return drawType == GachaDrawType.Single ? new List { new() { ItemId = poolId == 1 ? 1 : 2, Count = 1 } } : new List { new() { ItemId = poolId == 1 ? 1 : 2, Count = 9 } }; } private static int GetRareByItemId(int itemId) { if (itemId >= 10000) return 5; if (itemId >= 1000) return 4; return 3; } private static List GetPoolWeights(int poolId) { // TODO: 由 Luban TbGachaWeight 读取。当前提供固定测试池。 if (poolId == 2) { return new List { new() { ItemId = 2, Count = 10, Weight = 7000, Rare = 3 }, new() { ItemId = 1001, Count = 5, Weight = 2500, Rare = 4 }, new() { ItemId = 10001, Count = 1, Weight = 500, Rare = 5 }, }; } return new List { new() { ItemId = 1, Count = 100, Weight = 7000, Rare = 3 }, new() { ItemId = 1001, Count = 1, Weight = 2500, Rare = 4 }, new() { ItemId = 10001, Count = 1, Weight = 500, Rare = 5 }, }; } } }