using System.Text.Json; namespace Yuanshen.Gameserver.Game; internal readonly record struct DropEntry(uint ItemId, uint MinCount, uint MaxCount, double Chance, int Rolls, int Pool); internal sealed class DropTable { private readonly Dictionary> _monsters = new(); private readonly Dictionary> _drops = new(); private readonly Dictionary _itemTypes = new(); private readonly Dictionary> _subfields = new(); public DropTable() { Load("monster_drops.json"); Load("gadget_drops.json"); } public uint SubfieldDropId(uint subfieldId, uint level) { if (!_subfields.TryGetValue(subfieldId, out List<(uint MaxLevel, uint DropId)>? bands) || bands.Count == 0) return 0; foreach ((uint maxLevel, uint dropId) in bands.OrderBy(band => band.MaxLevel)) { if (level <= maxLevel) return dropId; } return bands[^1].DropId; } public bool IsEmpty => _drops.Count == 0; public int MonsterCount => _monsters.Count; public string TypeOf(uint itemId) => _itemTypes.GetValueOrDefault(itemId, "material"); public uint DropIdFor(uint monsterId, uint level) { if (!_monsters.TryGetValue(monsterId, out Dictionary? byLevel) || byLevel.Count == 0) return 0; if (byLevel.TryGetValue(level, out uint exact)) return exact; uint best = 0; uint bestLevel = 0; foreach ((uint candidate, uint dropId) in byLevel) { if (candidate <= level && candidate >= bestLevel) { bestLevel = candidate; best = dropId; } } return best != 0 ? best : byLevel.OrderBy(pair => pair.Key).First().Value; } public Dictionary Roll(uint dropId, Random random) { Dictionary result = new(); if (!_drops.TryGetValue(dropId, out List? entries)) return result; foreach (DropEntry entry in entries.Where(entry => entry.Pool == 0)) { for (int attempt = 0; attempt < entry.Rolls; attempt++) { if (random.NextDouble() < entry.Chance) Add(result, entry, random); } } foreach (IGrouping group in entries.Where(entry => entry.Pool != 0).GroupBy(entry => entry.Pool)) { List members = group.ToList(); for (int attempt = 0; attempt < members[0].Rolls; attempt++) { double roll = random.NextDouble(); double accumulated = 0; foreach (DropEntry member in members) { accumulated += member.Chance; if (roll >= accumulated) continue; Add(result, member, random); break; } } } return result; } private static void Add(Dictionary result, DropEntry entry, Random random) { uint count = entry.MaxCount > entry.MinCount ? (uint)random.Next((int)entry.MinCount, (int)entry.MaxCount + 1) : entry.MinCount; if (count != 0) result[entry.ItemId] = result.GetValueOrDefault(entry.ItemId) + count; } private void Load(string fileName) { string path = Path.Combine(AppContext.BaseDirectory, "assets", "drops", fileName); if (!File.Exists(path)) return; using JsonDocument document = JsonDocument.Parse(File.ReadAllText(path)); JsonElement root = document.RootElement; if (root.TryGetProperty("subfield", out JsonElement subfields)) { foreach (JsonProperty subfield in subfields.EnumerateObject()) { if (!uint.TryParse(subfield.Name, out uint subfieldId)) continue; List<(uint, uint)> bands = new(); foreach (JsonElement band in subfield.Value.EnumerateArray()) bands.Add((band.GetProperty("maxLevel").GetUInt32(), band.GetProperty("dropId").GetUInt32())); if (bands.Count != 0) _subfields[subfieldId] = bands; } } foreach (JsonProperty monster in root.TryGetProperty("monsters", out JsonElement monsterRoot) ? monsterRoot.EnumerateObject() : default) { if (!uint.TryParse(monster.Name, out uint monsterId)) continue; Dictionary byLevel = new(); foreach (JsonProperty level in monster.Value.EnumerateObject()) { if (uint.TryParse(level.Name, out uint parsedLevel)) byLevel[parsedLevel] = level.Value.GetUInt32(); } if (byLevel.Count != 0) _monsters[monsterId] = byLevel; } foreach (JsonProperty drop in root.TryGetProperty("drops", out JsonElement dropRoot) ? dropRoot.EnumerateObject() : default) { if (!uint.TryParse(drop.Name, out uint dropId)) continue; List entries = new(); foreach (JsonElement entry in drop.Value.EnumerateArray()) { entries.Add(new DropEntry( entry.GetProperty("itemId").GetUInt32(), entry.GetProperty("minCount").GetUInt32(), entry.GetProperty("maxCount").GetUInt32(), entry.GetProperty("chance").GetDouble(), entry.TryGetProperty("rolls", out JsonElement rolls) ? Math.Max(rolls.GetInt32(), 1) : 1, entry.TryGetProperty("pool", out JsonElement pool) ? pool.GetInt32() : 0)); } _drops[dropId] = entries; } foreach (JsonProperty item in root.TryGetProperty("items", out JsonElement itemRoot) ? itemRoot.EnumerateObject() : default) { if (uint.TryParse(item.Name, out uint itemId)) _itemTypes[itemId] = item.Value.GetProperty("type").GetString() ?? "material"; } } }