4 using System.Collections.Generic;
9 static class AutoItemPlacer
11 public static bool OutputDebugInfo =
false;
13 public static void SpawnItems(Identifier? startItemSet =
null)
15 if (GameMain.NetworkMember !=
null && !GameMain.NetworkMember.IsServer) {
return; }
21 for (
int i = 0; i <
Submarine.MainSubs.Length; i++)
24 if (sub ==
null || sub.Info.InitialSuppliesSpawned || sub.Info.IsManuallyOutfitted || !sub.Info.IsPlayer) {
continue; }
26 SpawnStartItems(sub, startItemSet);
28 var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
30 subs.ForEach(s => s.Info.InitialSuppliesSpawned =
true);
39 if (sub.Info.InitialSuppliesSpawned) {
continue; }
40 CreateAndPlace(sub.ToEnumerable());
41 sub.Info.InitialSuppliesSpawned =
true;
44 if (Level.Loaded?.StartOutpost !=
null && Level.Loaded.Type == LevelData.LevelType.Outpost)
46 var sub = Level.Loaded.StartOutpost;
47 if (!sub.Info.InitialSuppliesSpawned)
49 Rand.SetSyncedSeed(ToolBox.StringToInt(sub.Info.Name));
50 CreateAndPlace(sub.ToEnumerable());
51 sub.Info.InitialSuppliesSpawned =
true;
60 public static IEnumerable<Item> RegenerateLoot(Submarine sub,
ItemContainer regeneratedContainer,
float skipItemProbability = 0.0f)
62 return CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer, skipItemProbability);
65 public static Identifier DefaultStartItemSet =
new Identifier(
"normal");
70 private static void SpawnStartItems(Submarine sub, Identifier? startItemSet)
72 Identifier setIdentifier = startItemSet ?? DefaultStartItemSet;
73 if (!StartItemSet.Sets.TryGet(setIdentifier, out StartItemSet itemSet))
75 DebugConsole.AddWarning($
"Couldn't find a start item set matching the identifier \"{setIdentifier}\"!");
76 if (!StartItemSet.Sets.TryGet(DefaultStartItemSet, out StartItemSet defaultSet))
78 DebugConsole.ThrowError($
"Couldn't find the default start item set \"{DefaultStartItemSet}\"!");
83 WayPoint wp = WayPoint.GetRandom(
SpawnType.Cargo,
null, sub);
84 ISpatialEntity initialSpawnPos;
85 if (wp?.CurrentHull ==
null)
87 var spawnHull = Hull.HullList.Where(h => h.Submarine == sub && !h.IsWetRoom).GetRandomUnsynced();
88 if (spawnHull ==
null)
90 DebugConsole.AddWarning($
"Failed to spawn start items in the sub. No cargo waypoint or dry hulls found to spawn the items in.");
93 initialSpawnPos = spawnHull;
99 var newItems =
new List<Item>();
100 foreach (var startItem
in itemSet.Items)
102 if (!ItemPrefab.Prefabs.TryGet(startItem.Item, out ItemPrefab itemPrefab))
104 DebugConsole.AddWarning($
"Cannot find a start item with with the identifier \"{startItem.Item}\"");
107 if (startItem.MultiPlayerOnly && GameMain.GameSession?.GameMode is { IsSinglePlayer: true }) {
continue; }
108 for (
int i = 0; i < startItem.Amount; i++)
110 var item =
new Item(itemPrefab, initialSpawnPos.Position, sub, callOnItemLoaded:
false);
114 wifiComponent.
TeamID = sub.TeamID;
119 var cargoContainers =
new List<ItemContainer>();
120 foreach (var item
in newItems)
123 Entity.Spawner.CreateNetworkEvent(
new EntitySpawner.SpawnEntity(item));
129 var container = sub.FindContainerFor(item, onlyPrimary:
true);
130 if (container ==
null)
132 var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, initialSpawnPos, ref cargoContainers);
133 container = cargoContainer?.Item;
135 container?.OwnInventory.TryPutItem(item, user:
null);
139 private static IEnumerable<Item> CreateAndPlace(IEnumerable<Submarine> subs,
ItemContainer regeneratedContainer =
null,
float skipItemProbability = 0.0f)
141 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient)
143 DebugConsole.ThrowError(
"Clients are not allowed to use AutoItemPlacer.\n" + Environment.StackTrace.CleanupStackTrace());
144 return Enumerable.Empty<
Item>();
147 List<Item> itemsToSpawn =
new List<Item>(100);
149 int itemCountApprox = MapEntityPrefab.List.Count() / 3;
150 var containers =
new List<ItemContainer>(70 + 30 * subs.Count());
151 var prefabsItemsCanSpawnIn =
new List<ItemPrefab>(itemCountApprox / 3);
152 var singlePrefabs =
new List<ItemPrefab>(itemCountApprox);
153 var removals =
new List<ItemPrefab>();
156 if (regeneratedContainer !=
null)
158 containers.Add(regeneratedContainer);
162 foreach (Item item
in Item.ItemList)
164 if (!subs.Contains(item.Submarine)) {
continue; }
165 if (item.GetRootInventoryOwner() is Character) {
continue; }
166 if (item.NonInteractable) {
continue; }
169 containers.Shuffle(Rand.RandSync.ServerAndClient);
172 var itemPrefabs = ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier);
173 foreach (ItemPrefab ip
in itemPrefabs)
175 if (ip.PreferredContainers.None()) {
continue; }
176 if (ip.ConfigElement.Elements().Any(e =>
string.Equals(e.Name.ToString(), typeof(
ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) && itemPrefabs.Any(ip2 => CanSpawnIn(ip2, ip)))
178 prefabsItemsCanSpawnIn.Add(ip);
182 singlePrefabs.Add(ip);
186 bool CanSpawnIn(ItemPrefab item, ItemPrefab container)
188 foreach (var preferredContainer
in item.PreferredContainers)
190 if (ItemPrefab.IsContainerPreferred(preferredContainer.Primary, container.Identifier.ToEnumerable().Union(container.Tags))) {
return true; }
195 var validContainers =
new Dictionary<ItemContainer, PreferredContainer>();
196 prefabsItemsCanSpawnIn.Shuffle(Rand.RandSync.ServerAndClient);
198 for (
int i = 0; i < prefabsItemsCanSpawnIn.Count; i++)
200 var itemPrefab = prefabsItemsCanSpawnIn[i];
201 if (itemPrefab ==
null) {
continue; }
202 SpawnItems(itemPrefab);
206 singlePrefabs.Shuffle(Rand.RandSync.ServerAndClient);
207 singlePrefabs.ForEach(i => SpawnItems(i));
211 var subNames = subs.Select(s => s.Info.Name).ToList();
212 DebugConsole.NewMessage($
"Automatically placed items in { string.Join(",
", subNames) }:");
213 foreach (
string itemName
in itemsToSpawn.Select(it => it.Name).Distinct())
215 DebugConsole.NewMessage(
" - " + itemName +
" x" + itemsToSpawn.Count(it => it.Name == itemName));
219 if (GameMain.GameSession?.Level !=
null &&
220 GameMain.GameSession.Level.Type == LevelData.LevelType.Outpost &&
221 GameMain.GameSession.StartLocation?.TakenItems !=
null)
223 foreach (Location.TakenItem takenItem in GameMain.GameSession.StartLocation.TakenItems)
225 var matchingItem = itemsToSpawn.Find(it => takenItem.Matches(it));
226 if (matchingItem ==
null) {
continue; }
229 DebugConsole.NewMessage($
"Removing the stolen item: {matchingItem.Prefab.Identifier} ({matchingItem.ID})");
231 var containedItems = itemsToSpawn.FindAll(it => it.ParentInventory?.Owner == matchingItem);
232 matchingItem.Remove();
233 itemsToSpawn.Remove(matchingItem);
234 foreach (Item containedItem
in containedItems)
236 containedItem.Remove();
237 itemsToSpawn.Remove(containedItem);
241 foreach (Item item
in itemsToSpawn)
244 Entity.Spawner.CreateNetworkEvent(
new EntitySpawner.SpawnEntity(item));
254 void SpawnItems(ItemPrefab itemPrefab,
float skipItemProbability = 0.0f)
256 if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < skipItemProbability) {
return; }
257 if (itemPrefab ==
null)
259 string errorMsg =
"Error in AutoItemPlacer.SpawnItems - itemPrefab was null.\n" + Environment.StackTrace.CleanupStackTrace();
260 DebugConsole.ThrowError(errorMsg);
261 GameAnalyticsManager.AddErrorEventOnce(
"AutoItemPlacer.SpawnItems:ItemNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
264 bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode;
265 bool isPvP = GameMain.GameSession?.GameMode is PvPMode;
266 float levelDifficulty = Level.Loaded?.Difficulty ?? 0.0f;
267 foreach (PreferredContainer preferredContainer
in itemPrefab.PreferredContainers)
269 if (preferredContainer.CampaignOnly && !isCampaign) {
continue; }
270 if (preferredContainer.NotCampaign && isCampaign) {
continue; }
271 if (preferredContainer.NotPvP && isPvP) {
continue; }
272 if (levelDifficulty < preferredContainer.MinLevelDifficulty || levelDifficulty > preferredContainer.MaxLevelDifficulty) {
continue; }
273 if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) {
continue; }
274 validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary:
true);
275 if (validContainers.None())
277 validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary:
false);
279 foreach (var validContainer
in validContainers)
281 var newItems = CreateItems(itemPrefab, containers, validContainer);
284 itemsToSpawn.AddRange(newItems);
291 private static Dictionary<ItemContainer, PreferredContainer> GetValidContainers(PreferredContainer preferredContainer, IEnumerable<ItemContainer> allContainers, Dictionary<ItemContainer, PreferredContainer> validContainers,
bool primary)
293 validContainers.Clear();
296 if (!container.
AutoFill) {
continue; }
299 if (!ItemPrefab.IsContainerPreferred(preferredContainer.Primary, container)) {
continue; }
303 if (!ItemPrefab.IsContainerPreferred(preferredContainer.Secondary, container)) {
continue; }
305 if (!validContainers.ContainsKey(container))
307 validContainers.Add(container, preferredContainer);
310 return validContainers;
313 private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
315 List<Item> newItems =
new List<Item>();
316 if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability) {
return newItems; }
318 if (validContainer.Key.Item.Submarine.WreckAI !=
null && itemPrefab.Tags.Contains(
"explodesinwater"))
322 int amount = validContainer.Value.Amount;
325 amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
327 for (
int i = 0; i < amount; i++)
329 if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount:
true))
331 containers.Remove(validContainer.Key);
334 var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab);
335 int quality = existingItem?.Quality ??
Quality.
GetSpawnedItemQuality(validContainer.Key.Item.Submarine, Level.Loaded, Rand.RandSync.ServerAndClient);
336 if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) {
break; }
337 var item =
new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded:
false)
339 SpawnedInCurrentOutpost = validContainer.Key.Item.SpawnedInCurrentOutpost,
340 AllowStealing = validContainer.Key.Item.AllowStealing || validContainer.Key.Item.Prefab.AllowStealingContainedItems,
343 OriginalContainerIndex =
344 Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item)
348 wifiComponent.
TeamID = validContainer.Key.Item.Submarine.TeamID;
351 validContainer.Key.Inventory.TryPutItem(item,
null, createNetworkEvent:
false);
List< SubmarineInfo > OwnedSubmarines
The base class for components holding the different functionalities of the item
virtual void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
static int GetSpawnedItemQuality(Submarine submarine, Level level, Rand.RandSync randSync=Rand.RandSync.ServerAndClient)
Get a random quality for an item spawning in some sub, taking into account the type of the submarine ...
int OriginalModuleIndex
The index of the outpost module this entity originally spawned in (-1 if not an outpost item)