using System.Linq; using Content.Shared.Storage; using Robust.Shared.Random; namespace Content.Server.Worldgen.Tools; /// /// A faster version of EntitySpawnCollection that requires caching to work. /// public sealed class EntitySpawnCollectionCache { [ViewVariables] private readonly Dictionary _orGroups = new(); public EntitySpawnCollectionCache(IEnumerable entries) { // collect groups together, create singular items that pass probability foreach (var entry in entries) { if (!_orGroups.TryGetValue(entry.GroupId ?? string.Empty, out var orGroup)) { orGroup = new OrGroup(); _orGroups.Add(entry.GroupId ?? string.Empty, orGroup); } orGroup.Entries.Add(entry); orGroup.CumulativeProbability += entry.SpawnProbability; } } /// /// Using a collection of entity spawn entries, picks a random list of entity prototypes to spawn from that collection. /// /// /// This does not spawn the entities. The caller is responsible for doing so, since it may want to do something /// special to those entities (offset them, insert them into storage, etc) /// /// Resolve param. /// List that spawned entities are inserted into. /// A list of entity prototypes that should be spawned. /// This is primarily useful if you're calling it many times over, as it lets you reuse the list repeatedly. public void GetSpawns(IRobustRandom random, ref List spawned) { // handle orgroup spawns foreach (var spawnValue in _orGroups.Values) { //HACK: This doesn't seem to work without this if there's only a single orgroup entry. Not sure how to fix the original math properly, but it works in every other case. if (spawnValue.Entries.Count == 1) { var entry = spawnValue.Entries.First(); var amount = entry.Amount; if (entry.MaxAmount > amount) amount = random.Next(amount, entry.MaxAmount); for (var index = 0; index < amount; index++) { spawned.Add(entry.PrototypeId); } continue; } // For each group use the added cumulative probability to roll a double in that range var diceRoll = random.NextDouble() * spawnValue.CumulativeProbability; // Add the entry's spawn probability to this value, if equals or lower, spawn item, otherwise continue to next item. var cumulative = 0.0; foreach (var entry in spawnValue.Entries) { cumulative += entry.SpawnProbability; if (diceRoll > cumulative) continue; // Dice roll succeeded, add item and break loop var amount = entry.Amount; if (entry.MaxAmount > amount) amount = random.Next(amount, entry.MaxAmount); for (var index = 0; index < amount; index++) { spawned.Add(entry.PrototypeId); } break; } } } private sealed class OrGroup { [ViewVariables] public List Entries { get; } = new(); [ViewVariables] public float CumulativeProbability { get; set; } } }