150 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace ET.Server
{
[FriendOf(typeof(GachaComponent))]
public static class GachaComponentSystem
{
public static List<RewardEntry> Draw(this GachaComponent self, int poolId, GachaDrawType drawType, BagComponent bag)
{
List<GachaPoolWeight> weights = GetPoolWeights(poolId);
if (weights.Count == 0)
{
return new List<RewardEntry>();
}
int times = drawType == GachaDrawType.Single ? 1 : 10;
List<CostEntry> 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<RewardEntry> 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<RewardEntry> rewards, List<GachaPoolWeight> 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<GachaPoolWeight> 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<CostEntry> GetDrawCost(int poolId, GachaDrawType drawType)
{
// TODO: 由 Luban TbGachaPool 驱动。当前使用固定测试配置。
return drawType == GachaDrawType.Single
? new List<CostEntry> { new() { ItemId = poolId == 1 ? 1 : 2, Count = 1 } }
: new List<CostEntry> { 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<GachaPoolWeight> GetPoolWeights(int poolId)
{
// TODO: 由 Luban TbGachaWeight 读取。当前提供固定测试池。
if (poolId == 2)
{
return new List<GachaPoolWeight>
{
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<GachaPoolWeight>
{
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 },
};
}
}
}