Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
7 namespace Barotrauma
8 {
9  partial class CargoMission : Mission
10  {
11  private readonly ContentXElement itemConfig;
12 
13  private readonly List<Item> items = new List<Item>();
14  private readonly Dictionary<Item, UInt16> parentInventoryIDs = new Dictionary<Item, UInt16>();
15  private readonly Dictionary<Item, int> inventorySlotIndices = new Dictionary<Item, int>();
16  private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
17 
21  private float requiredDeliveryAmount;
22 
23  private readonly List<(ContentXElement element, ItemContainer container)> itemsToSpawn = new List<(ContentXElement element, ItemContainer container)>();
24  private int? rewardPerCrate;
25  private int calculatedReward;
26  private int maxItemCount;
27 
28  private Submarine currentSub;
29  private SubmarineInfo nextRoundSubInfo;
30 
31  private readonly List<CargoMission> previouslySelectedMissions = new List<CargoMission>();
32 
33  public override LocalizedString Description
34  {
35  get
36  {
38  {
39  string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(Submarine.MainSub))}‖end‖";
40  if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
41  }
42  return description;
43  }
44  }
45 
46  public CargoMission(MissionPrefab prefab, Location[] locations, Submarine sub)
47  : base(prefab, locations, sub)
48  {
49  this.currentSub = sub;
50  this.nextRoundSubInfo = sub?.Info;
51  itemConfig = prefab.ConfigElement.GetChildElement("Items");
52  requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
53  //this can get called between rounds when the client receives a campaign save
54  //don't attempt to determine cargo if the sub hasn't been fully loaded
55  if (sub == null || sub.Loading || sub.Removed || Submarine.Unloading || !Submarine.Loaded.Contains(sub))
56  {
57  return;
58  }
59  DetermineCargo();
60  }
61 
62  private void DetermineCargo()
63  {
64  if (this.currentSub == null || itemConfig == null)
65  {
66  calculatedReward = Prefab.Reward;
67  return;
68  }
69 
70  itemsToSpawn.Clear();
71 
72  maxItemCount = 0;
73  foreach (var subElement in itemConfig.Elements())
74  {
75  int maxCount = subElement.GetAttributeInt("maxcount", 10);
76  maxItemCount += maxCount;
77  }
78 
79  var pendingSubInfo = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
80  if (pendingSubInfo != null && pendingSubInfo != currentSub.Info)
81  {
82  //if we've got a submarine switch pending, calculate the amount of cargo based on it's cargo capacity
83  //TODO: this isn't guaranteed to be accurate, because we don't take existing items in the new sub's cargo containers
84  //or items that might get transferred in them into account
85  maxItemCount = Math.Min(maxItemCount, pendingSubInfo.CargoCapacity);
86  previouslySelectedMissions.Clear();
87  if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
88  {
89  bool isPriorMission = true;
90  foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
91  {
92  if (mission is not CargoMission otherMission) { continue; }
93  if (mission == this) { isPriorMission = false; }
94  previouslySelectedMissions.Add(otherMission);
95  if (!isPriorMission) { continue; }
96  maxItemCount -= otherMission.itemsToSpawn.Count;
97  }
98  }
99  for (int i = 0; i < maxItemCount; i++)
100  {
101  foreach (var subElement in itemConfig.Elements())
102  {
103  int maxCount = subElement.GetAttributeInt("maxcount", 10);
104  if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
105  // For logging purposes
106  FindItemPrefab(subElement);
107  while (itemsToSpawn.Count < maxItemCount)
108  {
109  itemsToSpawn.Add((subElement, null));
110  if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
111  }
112  }
113  }
114  maxItemCount = Math.Max(0, maxItemCount);
115  nextRoundSubInfo = pendingSubInfo;
116  }
117  else
118  {
119  List<(ItemContainer container, int freeSlots)> containers = currentSub.GetCargoContainers();
120  containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); });
121 
122  previouslySelectedMissions.Clear();
123  if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
124  {
125  bool isPriorMission = true;
126  foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
127  {
128  if (mission is not CargoMission otherMission) { continue; }
129  if (mission == this) { isPriorMission = false; }
130  previouslySelectedMissions.Add(otherMission);
131  if (!isPriorMission) { continue; }
132  foreach (var (element, container) in otherMission.itemsToSpawn)
133  {
134  for (int i = 0; i < containers.Count; i++)
135  {
136  if (containers[i].container == container)
137  {
138  containers[i] = (containers[i].container, containers[i].freeSlots - 1);
139  break;
140  }
141  }
142  }
143  }
144  }
145 
146  for (int i = 0; i < containers.Count; i++)
147  {
148  foreach (var subElement in itemConfig.Elements())
149  {
150  int maxCount = subElement.GetAttributeInt("maxcount", 10);
151  if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
152  ItemPrefab itemPrefab = FindItemPrefab(subElement);
153  while (containers[i].freeSlots > 0 && containers[i].container.Inventory.CanBePut(itemPrefab))
154  {
155  containers[i] = (containers[i].container, containers[i].freeSlots - 1);
156  itemsToSpawn.Add((subElement, containers[i].container));
157  if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
158  }
159  }
160  }
161  }
162 
163  if (!itemsToSpawn.Any())
164  {
165  itemsToSpawn.Add((itemConfig.Elements().First(), null));
166  }
167 
168  // Calculate the current total reward, since it might differ from the
169  // prefab total reward depending on the current actual crate count.
170  calculatedReward = 0;
171  bool crateValuesUniform = true;
172  int? prevCrateReward = null;
173  foreach (var (element, container) in itemsToSpawn)
174  {
175  int currentCrateReward = element.GetAttributeInt("reward", 0);
176  calculatedReward += currentCrateReward;
177 
178  // Apparently crates can have varying values, so we need to check
179  // here if that is the case, stopping checks on the first discrepancy
180  if (crateValuesUniform)
181  {
182  if (prevCrateReward.HasValue)
183  {
184  if (prevCrateReward.Value != currentCrateReward)
185  {
186  crateValuesUniform = false;
187  }
188  }
189  prevCrateReward = currentCrateReward;
190  }
191  }
192 
193  if (crateValuesUniform)
194  {
195  // If rewardPerCrate is set, it will be displayed in the client UI as eg. "123 mk x 5"
196  rewardPerCrate = calculatedReward / itemsToSpawn.Count;
197  }
198  else
199  {
200  // If rewardPerCrate is null, the client UI will display just the total reward
201  rewardPerCrate = null;
202  }
203 
204  // Apply the mission reward campaign setting multiplier to the per-crate price, too
205  if (GameMain.GameSession?.Campaign is CampaignMode campaign && rewardPerCrate is int confirmedRewardPerCrate)
206  {
207  rewardPerCrate = (int)Math.Round(confirmedRewardPerCrate * campaign.Settings.MissionRewardMultiplier);
208  }
209 
210  string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(currentSub))}‖end‖";
211  if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
212  }
213 
214  public override int GetBaseReward(Submarine sub)
215  {
216  // If we are not at the location of the mission, skip the calculation of the reward
218  {
219  return calculatedReward;
220  }
221 
222  bool missionsChanged = false;
224  {
225  List<Mission> currentMissions = GameMain.GameSession.StartLocation.SelectedMissions.Where(m => m is CargoMission).ToList();
226  if (currentMissions.Count != previouslySelectedMissions.Count)
227  {
228  missionsChanged = true;
229  }
230  else
231  {
232  for (int i = 0; i < previouslySelectedMissions.Count; i++)
233  {
234  if (previouslySelectedMissions[i] != currentMissions[i])
235  {
236  missionsChanged = true;
237  break;
238  }
239  }
240  }
241  }
242 
243  var pendingSubInfo = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
244  if (pendingSubInfo != null && nextRoundSubInfo != pendingSubInfo)
245  {
246  this.nextRoundSubInfo = pendingSubInfo;
247  DetermineCargo();
248  }
249  else if (sub != this.currentSub || missionsChanged)
250  {
251  this.currentSub = sub;
252  this.nextRoundSubInfo = sub?.Info;
253  DetermineCargo();
254  }
255 
256  return calculatedReward;
257  }
258 
259  private void InitItems()
260  {
261  this.currentSub = Submarine.MainSub;
262  DetermineCargo();
263 
264  items.Clear();
265  parentInventoryIDs.Clear();
266  parentItemContainerIndices.Clear();
267  inventorySlotIndices.Clear();
268 
269  if (itemConfig == null)
270  {
271  DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)",
272  contentPackage: Prefab.ContentPackage);
273  return;
274  }
275 
276  foreach (var (element, container) in itemsToSpawn)
277  {
278  LoadItemAsChild(element, container?.Item);
279  }
280 
281  if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; }
282  }
283 
284  private void LoadItemAsChild(ContentXElement element, Item parent)
285  {
286  ItemPrefab itemPrefab = FindItemPrefab(element);
287 
288  Vector2? position = GetCargoSpawnPosition(itemPrefab, out Submarine cargoRoomSub);
289  if (!position.HasValue) { return; }
290 
291  var item = new Item(itemPrefab, position.Value, cargoRoomSub)
292  {
293  SpawnedInCurrentOutpost = true,
294  AllowStealing = false
295  };
296  item.AddTag(Tags.CargoMissionItem);
297  item.AddTag(Prefab.Identifier);
298  foreach (var tag in Prefab.Tags)
299  {
300  item.AddTag(tag);
301  }
302  item.FindHull();
303  items.Add(item);
304 
305  if (parent?.GetComponent<ItemContainer>() != null)
306  {
307  parentInventoryIDs.Add(item, parent.ID);
308  parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(parent.GetComponent<ItemContainer>()));
309  parent.Combine(item, user: null);
310  inventorySlotIndices.Add(item, item.ParentInventory?.FindIndex(item) ?? -1);
311  }
312 
313  foreach (var subElement in element.Elements())
314  {
315  int amount = subElement.GetAttributeInt("amount", 1);
316  for (int i = 0; i < amount; i++)
317  {
318  LoadItemAsChild(subElement, item);
319  }
320  }
321  }
322 
323  protected override void StartMissionSpecific(Level level)
324  {
325  items.Clear();
326  parentInventoryIDs.Clear();
327 
328  if (!IsClient)
329  {
330  InitItems();
331  }
332  }
333 
334  protected override bool DetermineCompleted()
335  {
337  {
338  int deliveredItemCount = items.Count(it => IsItemDelivered(it));
339  if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount)
340  {
341  return true;
342  }
343  }
344  return false;
345  }
346 
347  protected override void EndMissionSpecific(bool completed)
348  {
349  foreach (Item item in items)
350  {
351  if (!item.Removed) { item.Remove(); }
352  }
353  items.Clear();
354  failed = !completed;
355  }
356 
357  private static bool IsItemDelivered(Item item)
358  {
359  if (item.Removed || item.Condition <= 0.0f || Submarine.MainSub == null) { return false; }
360  var submarine = item.Submarine ?? item.RootContainer?.Submarine;
361  return submarine == Submarine.MainSub || Submarine.MainSub.GetConnectedSubs().Contains(submarine);
362  }
363  }
364 }
CargoMission(MissionPrefab prefab, Location[] locations, Submarine sub)
override int GetBaseReward(Submarine sub)
Calculates the base reward, can be overridden for different mission types
float GetAttributeFloat(string key, float def)
ContentXElement? GetChildElement(string name)
Submarine Submarine
Definition: Entity.cs:53
static GameSession?? GameSession
Definition: GameMain.cs:88
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
IEnumerable< Mission > SelectedMissions
Definition: Location.cs:442
Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
Vector2? GetCargoSpawnPosition(ItemPrefab itemPrefab, out Submarine cargoRoomSub)
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
List<(ItemContainer container, int freeSlots)> GetCargoContainers()