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 IEnumerable<Item> RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer, float skipItemProbability = 0.0f)
61  {
62  return 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 IEnumerable<Item> 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 Enumerable.Empty<Item>();
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  return itemsToSpawn;
253 
254  void SpawnItems(ItemPrefab itemPrefab, float skipItemProbability = 0.0f)
255  {
256  if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < skipItemProbability) { return; }
257  if (itemPrefab == null)
258  {
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);
262  return;
263  }
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)
268  {
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())
276  {
277  validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: false);
278  }
279  foreach (var validContainer in validContainers)
280  {
281  var newItems = CreateItems(itemPrefab, containers, validContainer);
282  if (newItems.Any())
283  {
284  itemsToSpawn.AddRange(newItems);
285  }
286  }
287  }
288  }
289  }
290 
291  private static Dictionary<ItemContainer, PreferredContainer> GetValidContainers(PreferredContainer preferredContainer, IEnumerable<ItemContainer> allContainers, Dictionary<ItemContainer, PreferredContainer> validContainers, bool primary)
292  {
293  validContainers.Clear();
294  foreach (ItemContainer container in allContainers)
295  {
296  if (!container.AutoFill) { continue; }
297  if (primary)
298  {
299  if (!ItemPrefab.IsContainerPreferred(preferredContainer.Primary, container)) { continue; }
300  }
301  else
302  {
303  if (!ItemPrefab.IsContainerPreferred(preferredContainer.Secondary, container)) { continue; }
304  }
305  if (!validContainers.ContainsKey(container))
306  {
307  validContainers.Add(container, preferredContainer);
308  }
309  }
310  return validContainers;
311  }
312 
313  private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
314  {
315  List<Item> newItems = new List<Item>();
316  if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability) { return newItems; }
317  // Don't add dangerously reactive materials in thalamus wrecks
318  if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
319  {
320  return newItems;
321  }
322  int amount = validContainer.Value.Amount;
323  if (amount == 0)
324  {
325  amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
326  }
327  for (int i = 0; i < amount; i++)
328  {
329  if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount: true))
330  {
331  containers.Remove(validContainer.Key);
332  break;
333  }
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)
338  {
339  SpawnedInCurrentOutpost = validContainer.Key.Item.SpawnedInCurrentOutpost,
340  AllowStealing = validContainer.Key.Item.AllowStealing || validContainer.Key.Item.Prefab.AllowStealingContainedItems,
341  Quality = quality,
342  OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
343  OriginalContainerIndex =
344  Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item)
345  };
346  foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
347  {
348  wifiComponent.TeamID = validContainer.Key.Item.Submarine.TeamID;
349  }
350  newItems.Add(item);
351  validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false);
352  containers.AddRange(item.GetComponents<ItemContainer>());
353  }
354  return newItems;
355  }
356  }
357 }
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)