Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Linq;
8 using PositionType = Barotrauma.Level.PositionType;
9 
10 namespace Barotrauma
11 {
12  partial class MineralMission : Mission
13  {
14  private readonly Dictionary<Identifier, int> resourceAmounts = new Dictionary<Identifier, int>();
15  private readonly Dictionary<Identifier, List<Item>> spawnedResources = new Dictionary<Identifier, List<Item>>();
16  private readonly Dictionary<Identifier, Item[]> relevantLevelResources = new Dictionary<Identifier, Item[]>();
17  private readonly List<(Identifier Identifier, Vector2 Position)> missionClusterPositions = new List<(Identifier Identifier, Vector2 Position)>();
18 
19  private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
20 
21  private readonly PositionType positionType = PositionType.Cave;
27  public static readonly ImmutableArray<PositionType> ValidPositionTypes = new PositionType[]
28  {
29  PositionType.Cave,
30  PositionType.SidePath,
31  PositionType.MainPath,
32  PositionType.AbyssCave,
33  }.ToImmutableArray();
34 
38  private readonly float resourceHandoverAmount;
39 
40  public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
41  {
42  get
43  {
44  return missionClusterPositions
45  .Where(p => spawnedResources.ContainsKey(p.Identifier) && AnyAreUncollected(spawnedResources[p.Identifier]))
46  .Select(p => (ModifyMessage(Prefab.SonarLabel, color: false), p.Position));
47  }
48  }
49 
50  public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage);
51  public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage);
53  public override LocalizedString Name => ModifyMessage(base.Name, false);
54 
55  public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
56  {
57  var positionType = prefab.ConfigElement.GetAttributeEnum("PositionType", in this.positionType);
58  if (ValidPositionTypes.Contains(positionType))
59  {
60  this.positionType = positionType;
61  }
62 
63  float handoverAmount = prefab.ConfigElement.GetAttributeFloat("ResourceHandoverAmount", 0.0f);
64  resourceHandoverAmount = Math.Clamp(handoverAmount, 0.0f, 1.0f);
65 
66  var configElement = prefab.ConfigElement.GetChildElement("Items");
67  foreach (var c in configElement.GetChildElements("Item"))
68  {
69  var identifier = c.GetAttributeIdentifier("identifier", Identifier.Empty);
70  if (identifier.IsEmpty) { continue; }
71  if (resourceAmounts.ContainsKey(identifier))
72  {
73  resourceAmounts[identifier]++;
74  }
75  else
76  {
77  resourceAmounts.Add(identifier, 1);
78  }
79  }
80  }
81 
82  protected override void StartMissionSpecific(Level level)
83  {
84  if (spawnedResources.Any())
85  {
86 #if DEBUG
87  throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})");
88 #else
89  DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
90  spawnedResources.Clear();
91 #endif
92  }
93 
94  if (relevantLevelResources.Any())
95  {
96 #if DEBUG
97  throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})");
98 #else
99  DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
100  relevantLevelResources.Clear();
101 #endif
102  }
103 
104  if (missionClusterPositions.Any())
105  {
106 #if DEBUG
107  throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})");
108 #else
109  DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
110  missionClusterPositions.Clear();
111 #endif
112  }
113 
114  caves.Clear();
115 
116  if (IsClient) { return; }
117 
118  foreach ((Identifier identifier, int amount) in resourceAmounts)
119  {
120  if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab)
121  {
122  DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")",
123  contentPackage: Prefab.ContentPackage);
124  continue;
125  }
126 
127  var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves);
128  if (spawnedResources.Count < amount)
129  {
130  DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}",
131  contentPackage: Prefab.ContentPackage);
132  }
133 
134  if (spawnedResources.None()) { continue; }
135 
136  this.spawnedResources.Add(identifier, spawnedResources);
137 
138  foreach (var cave in Level.Loaded.Caves)
139  {
140  foreach (var spawnedResource in spawnedResources)
141  {
142  if (cave.Area.Contains(spawnedResource.WorldPosition))
143  {
144  cave.MissionsToDisplayOnSonar.Add(this);
145  caves.Add(cave);
146  break;
147  }
148  }
149  }
150  }
151 
152  CalculateMissionClusterPositions();
153  FindRelevantLevelResources();
154  }
155 
156  protected override void UpdateMissionSpecific(float deltaTime)
157  {
158  if (IsClient) { return; }
159  switch (State)
160  {
161  case 0:
162  if (!EnoughHaveBeenCollected()) { return; }
163  State = 1;
164  break;
165  case 1:
166  if (!Submarine.MainSub.AtEitherExit) { return; }
167  State = 2;
168  break;
169  }
170  }
171 
172  protected override bool DetermineCompleted()
173  {
174  return EnoughHaveBeenCollected();
175  }
176 
177  protected override void EndMissionSpecific(bool completed)
178  {
179  failed = !completed && state > 0;
180  if (completed)
181  {
182  if (!IsClient)
183  {
184  // When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission)
185  var handoverResources = new List<Item>();
186  foreach (Identifier identifier in resourceAmounts.Keys)
187  {
188  if (relevantLevelResources.TryGetValue(identifier, out var availableResources))
189  {
190  var collectedResources = availableResources.Where(HasBeenCollected);
191  if (!collectedResources.Any()) { continue; }
192  int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count());
193  for (int i = 0; i < handoverCount; i++)
194  {
195  handoverResources.Add(collectedResources.ElementAt(i));
196  }
197  }
198  }
199  foreach (var resource in handoverResources)
200  {
201  resource.Remove();
202  }
203  }
204  }
205  foreach (var kvp in spawnedResources)
206  {
207  foreach (var i in kvp.Value)
208  {
209  if (i != null && !i.Removed && !HasBeenCollected(i))
210  {
211  i.Remove();
212  }
213  }
214  }
215  spawnedResources.Clear();
216  relevantLevelResources.Clear();
217  missionClusterPositions.Clear();
218  }
219 
220  private void FindRelevantLevelResources()
221  {
222  relevantLevelResources.Clear();
223  foreach (var identifier in resourceAmounts.Keys)
224  {
225  var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
226  i.Submarine == null && i.ParentInventory == null &&
227  (i.GetComponent<Holdable>() is not Holdable h || (h.Attachable && h.Attached)))
228  .ToArray();
229  relevantLevelResources.Add(identifier, items);
230  }
231  }
232 
233  private bool EnoughHaveBeenCollected()
234  {
235  foreach (var kvp in resourceAmounts)
236  {
237  if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
238  {
239  var collected = availableResources.Count(HasBeenCollected);
240  var needed = kvp.Value;
241  if (collected < needed) { return false; }
242  }
243  else
244  {
245  return false;
246  }
247  }
248  return true;
249  }
250 
251  private bool HasBeenCollected(Item item)
252  {
253  if (item == null) { return false; }
254  if (item.Removed) { return false; }
255  var owner = item.GetRootInventoryOwner();
256  if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player)
257  {
258  return true;
259  }
260  else if (owner is Character c)
261  {
262  return c.Info != null && GameMain.GameSession.CrewManager.GetCharacterInfos().Contains(c.Info);
263  }
264  return false;
265  }
266 
267  private bool AnyAreUncollected(IEnumerable<Item> items)
268  => items.Any(i => !HasBeenCollected(i));
269 
270  private void CalculateMissionClusterPositions()
271  {
272  missionClusterPositions.Clear();
273  foreach (var kvp in spawnedResources)
274  {
275  if (kvp.Value.None()) { continue; }
276  var pos = Vector2.Zero;
277  var itemCount = 0;
278  foreach (var i in kvp.Value.Where(i => i != null && !i.Removed))
279  {
280  pos += i.WorldPosition;
281  itemCount++;
282  }
283  pos /= itemCount;
284  missionClusterPositions.Add((kvp.Key, pos));
285  }
286  }
287 
288  protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true)
289  {
290  int i = 1;
291  foreach ((Identifier identifier, int amount) in resourceAmounts)
292  {
293  Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? "");
294  Replace($"[resourcequantity{i}]", amount.ToString());
295  i++;
296  }
297  Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount));
298  return message;
299 
300  void Replace(string find, string replace)
301  {
302  if (color)
303  {
304  replace = $"‖color:gui.orange‖{replace}‖end‖";
305  }
306  message = message.Replace(find, replace);
307  }
308  }
309  }
310 }
float GetAttributeFloat(string key, float def)
ContentXElement? GetChildElement(string name)
Identifier GetAttributeIdentifier(string key, string def)
static readonly List< Item > ItemList
Defines a point in the event that GoTo actions can jump to.
Definition: Label.cs:7
Cave(CaveGenerationParams caveGenerationParams, Rectangle area, Point startPos, Point endPos)
List< Item > GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, IEnumerable< Cave > targetCaves=null)
Used by clients to set the rotation for the resources
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
static MapEntityPrefab FindByIdentifier(Identifier identifier)
static readonly ImmutableArray< PositionType > ValidPositionTypes
override LocalizedString ModifyMessage(LocalizedString message, bool color=true)
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub)
ContentPackage? ContentPackage
Definition: Prefab.cs:37