Client LuaCsForBarotrauma
AutoItemPlacer.cs
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
7 namespace Barotrauma
8 {
9  static class AutoItemPlacer
10  {
11  public static bool OutputDebugInfo = false;
12 
13  public static void SpawnItems(Identifier? startItemSet = null)
14  {
15  if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
16 
17  //player has more than one sub = we must have given the start items already
18  bool startItemsGiven = GameMain.GameSession?.OwnedSubmarines != null && GameMain.GameSession.OwnedSubmarines.Count > 1;
19  if (!startItemsGiven)
20  {
21  for (int i = 0; i < Submarine.MainSubs.Length; i++)
22  {
23  var sub = Submarine.MainSubs[i];
24  if (sub == null || sub.Info.InitialSuppliesSpawned || sub.Info.IsManuallyOutfitted || !sub.Info.IsPlayer) { continue; }
25  //1st pass: items defined in the start item set, only spawned in the main sub (not drones/shuttles or other linked subs)
26  SpawnStartItems(sub, startItemSet);
27  //2nd pass: items defined using preferred containers, spawned in the main sub and all the linked subs (drones, shuttles etc)
28  var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
29  CreateAndPlace(subs);
30  subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
31  sub.CheckFuel();
32  }
33  }
34 
35  //spawn items in wrecks, beacon stations and pirate subs
36  foreach (var sub in Submarine.Loaded)
37  {
38  if (sub.Info.Type is SubmarineType.Player or SubmarineType.Outpost or SubmarineType.OutpostModule) { continue; }
39  if (sub.Info.InitialSuppliesSpawned) { continue; }
40  CreateAndPlace(sub.ToEnumerable());
41  sub.Info.InitialSuppliesSpawned = true;
42  }
43 
44  if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
45  {
46  var sub = Level.Loaded.StartOutpost;
47  if (!sub.Info.InitialSuppliesSpawned)
48  {
49  Rand.SetSyncedSeed(ToolBox.StringToInt(sub.Info.Name));
50  CreateAndPlace(sub.ToEnumerable());
51  sub.Info.InitialSuppliesSpawned = true;
52  }
53  }
54  }
55 
60  public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer, float skipItemProbability = 0.0f)
61  {
62  CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer, skipItemProbability);
63  }
64 
65  public static Identifier DefaultStartItemSet = new Identifier("normal");
66 
70  private static void SpawnStartItems(Submarine sub, Identifier? startItemSet)
71  {
72  Identifier setIdentifier = startItemSet ?? DefaultStartItemSet;
73  if (!StartItemSet.Sets.TryGet(setIdentifier, out StartItemSet itemSet))
74  {
75  DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{setIdentifier}\"!");
76  if (!StartItemSet.Sets.TryGet(DefaultStartItemSet, out StartItemSet defaultSet))
77  {
78  DebugConsole.ThrowError($"Couldn't find the default start item set \"{DefaultStartItemSet}\"!");
79  return;
80  }
81  itemSet = defaultSet;
82  }
83  WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub);
84  ISpatialEntity initialSpawnPos;
85  if (wp?.CurrentHull == null)
86  {
87  var spawnHull = Hull.HullList.Where(h => h.Submarine == sub && !h.IsWetRoom).GetRandomUnsynced();
88  if (spawnHull == null)
89  {
90  DebugConsole.AddWarning($"Failed to spawn start items in the sub. No cargo waypoint or dry hulls found to spawn the items in.");
91  return;
92  }
93  initialSpawnPos = spawnHull;
94  }
95  else
96  {
97  initialSpawnPos = wp;
98  }
99  var newItems = new List<Item>();
100  foreach (var startItem in itemSet.Items)
101  {
102  if (!ItemPrefab.Prefabs.TryGet(startItem.Item, out ItemPrefab itemPrefab))
103  {
104  DebugConsole.AddWarning($"Cannot find a start item with with the identifier \"{startItem.Item}\"");
105  continue;
106  }
107  if (startItem.MultiPlayerOnly && GameMain.GameSession?.GameMode is { IsSinglePlayer: true }) { continue; }
108  for (int i = 0; i < startItem.Amount; i++)
109  {
110  var item = new Item(itemPrefab, initialSpawnPos.Position, sub, callOnItemLoaded: false);
111  // Is this necessary?
112  foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
113  {
114  wifiComponent.TeamID = sub.TeamID;
115  }
116  newItems.Add(item);
117  }
118  }
119  var cargoContainers = new List<ItemContainer>();
120  foreach (var item in newItems)
121  {
122 #if SERVER
123  Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
124 #endif
125  foreach (ItemComponent ic in item.Components)
126  {
127  ic.OnItemLoaded();
128  }
129  var container = sub.FindContainerFor(item, onlyPrimary: true);
130  if (container == null)
131  {
132  var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, initialSpawnPos, ref cargoContainers);
133  container = cargoContainer?.Item;
134  }
135  container?.OwnInventory.TryPutItem(item, user: null);
136  }
137  }
138 
139  private static void CreateAndPlace(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float skipItemProbability = 0.0f)
140  {
141  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
142  {
143  DebugConsole.ThrowError("Clients are not allowed to use AutoItemPlacer.\n" + Environment.StackTrace.CleanupStackTrace());
144  return;
145  }
146 
147  List<Item> itemsToSpawn = new List<Item>(100);
148 
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>();
154 
155  // generate loot only for a specific container if defined
156  if (regeneratedContainer != null)
157  {
158  containers.Add(regeneratedContainer);
159  }
160  else
161  {
162  foreach (Item item in Item.ItemList)
163  {
164  if (!subs.Contains(item.Submarine)) { continue; }
165  if (item.GetRootInventoryOwner() is Character) { continue; }
166  if (item.NonInteractable) { continue; }
167  containers.AddRange(item.GetComponents<ItemContainer>());
168  }
169  containers.Shuffle(Rand.RandSync.ServerAndClient);
170  }
171 
172  var itemPrefabs = ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier);
173  foreach (ItemPrefab ip in itemPrefabs)
174  {
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)))
177  {
178  prefabsItemsCanSpawnIn.Add(ip);
179  }
180  else
181  {
182  singlePrefabs.Add(ip);
183  }
184  }
185 
186  bool CanSpawnIn(ItemPrefab item, ItemPrefab container)
187  {
188  foreach (var preferredContainer in item.PreferredContainers)
189  {
190  if (ItemPrefab.IsContainerPreferred(preferredContainer.Primary, container.Identifier.ToEnumerable().Union(container.Tags))) { return true; }
191  }
192  return false;
193  }
194 
195  var validContainers = new Dictionary<ItemContainer, PreferredContainer>();
196  prefabsItemsCanSpawnIn.Shuffle(Rand.RandSync.ServerAndClient);
197  // Spawn items that other items can spawn in first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc)
198  for (int i = 0; i < prefabsItemsCanSpawnIn.Count; i++)
199  {
200  var itemPrefab = prefabsItemsCanSpawnIn[i];
201  if (itemPrefab == null) { continue; }
202  SpawnItems(itemPrefab);
203  }
204 
205  // Spawn items that nothing can spawn in last
206  singlePrefabs.Shuffle(Rand.RandSync.ServerAndClient);
207  singlePrefabs.ForEach(i => SpawnItems(i));
208 
209  if (OutputDebugInfo)
210  {
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())
214  {
215  DebugConsole.NewMessage(" - " + itemName + " x" + itemsToSpawn.Count(it => it.Name == itemName));
216  }
217  }
218 
219  if (GameMain.GameSession?.Level != null &&
220  GameMain.GameSession.Level.Type == LevelData.LevelType.Outpost &&
221  GameMain.GameSession.StartLocation?.TakenItems != null)
222  {
223  foreach (Location.TakenItem takenItem in GameMain.GameSession.StartLocation.TakenItems)
224  {
225  var matchingItem = itemsToSpawn.Find(it => takenItem.Matches(it));
226  if (matchingItem == null) { continue; }
227  if (OutputDebugInfo)
228  {
229  DebugConsole.NewMessage($"Removing the stolen item: {matchingItem.Prefab.Identifier} ({matchingItem.ID})");
230  }
231  var containedItems = itemsToSpawn.FindAll(it => it.ParentInventory?.Owner == matchingItem);
232  matchingItem.Remove();
233  itemsToSpawn.Remove(matchingItem);
234  foreach (Item containedItem in containedItems)
235  {
236  containedItem.Remove();
237  itemsToSpawn.Remove(containedItem);
238  }
239  }
240  }
241  foreach (Item item in itemsToSpawn)
242  {
243 #if SERVER
244  Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
245 #endif
246  foreach (ItemComponent ic in item.Components)
247  {
248  ic.OnItemLoaded();
249  }
250  }
251 
252  void SpawnItems(ItemPrefab itemPrefab, float skipItemProbability = 0.0f)
253  {
254  if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < skipItemProbability) { return; }
255  if (itemPrefab == null)
256  {
257  string errorMsg = "Error in AutoItemPlacer.SpawnItems - itemPrefab was null.\n" + Environment.StackTrace.CleanupStackTrace();
258  DebugConsole.ThrowError(errorMsg);
259  GameAnalyticsManager.AddErrorEventOnce("AutoItemPlacer.SpawnItems:ItemNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
260  return;
261  }
262  bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode;
263  float levelDifficulty = Level.Loaded?.Difficulty ?? 0.0f;
264  foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers)
265  {
266  if (preferredContainer.CampaignOnly && !isCampaign) { continue; }
267  if (preferredContainer.NotCampaign && isCampaign) { continue; }
268  if (levelDifficulty < preferredContainer.MinLevelDifficulty || levelDifficulty > preferredContainer.MaxLevelDifficulty) { continue; }
269  if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) { continue; }
270  validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true);
271  if (validContainers.None())
272  {
273  validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: false);
274  }
275  foreach (var validContainer in validContainers)
276  {
277  var newItems = CreateItems(itemPrefab, containers, validContainer);
278  if (newItems.Any())
279  {
280  itemsToSpawn.AddRange(newItems);
281  }
282  }
283  }
284  }
285  }
286 
287  private static Dictionary<ItemContainer, PreferredContainer> GetValidContainers(PreferredContainer preferredContainer, IEnumerable<ItemContainer> allContainers, Dictionary<ItemContainer, PreferredContainer> validContainers, bool primary)
288  {
289  validContainers.Clear();
290  foreach (ItemContainer container in allContainers)
291  {
292  if (!container.AutoFill) { continue; }
293  if (primary)
294  {
295  if (!ItemPrefab.IsContainerPreferred(preferredContainer.Primary, container)) { continue; }
296  }
297  else
298  {
299  if (!ItemPrefab.IsContainerPreferred(preferredContainer.Secondary, container)) { continue; }
300  }
301  if (!validContainers.ContainsKey(container))
302  {
303  validContainers.Add(container, preferredContainer);
304  }
305  }
306  return validContainers;
307  }
308 
309  private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
310  {
311  List<Item> newItems = new List<Item>();
312  if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability) { return newItems; }
313  // Don't add dangerously reactive materials in thalamus wrecks
314  if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
315  {
316  return newItems;
317  }
318  int amount = validContainer.Value.Amount;
319  if (amount == 0)
320  {
321  amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
322  }
323  for (int i = 0; i < amount; i++)
324  {
325  if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount: true))
326  {
327  containers.Remove(validContainer.Key);
328  break;
329  }
330  var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab);
331  int quality = existingItem?.Quality ?? Quality.GetSpawnedItemQuality(validContainer.Key.Item.Submarine, Level.Loaded, Rand.RandSync.ServerAndClient);
332  if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
333  var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false)
334  {
335  SpawnedInCurrentOutpost = validContainer.Key.Item.SpawnedInCurrentOutpost,
336  AllowStealing = validContainer.Key.Item.AllowStealing || validContainer.Key.Item.Prefab.AllowStealingContainedItems,
337  Quality = quality,
338  OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
339  OriginalContainerIndex =
340  Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item)
341  };
342  foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
343  {
344  wifiComponent.TeamID = validContainer.Key.Item.Submarine.TeamID;
345  }
346  newItems.Add(item);
347  validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false);
348  containers.AddRange(item.GetComponents<ItemContainer>());
349  }
350  return newItems;
351  }
352  }
353 }
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)