Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs
1 using Microsoft.Xna.Framework;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System;
6 
7 namespace Barotrauma
8 {
9  partial class MonsterMission : Mission
10  {
11  private readonly HashSet<(CharacterPrefab character, Point amountRange)> monsterPrefabs = new HashSet<(CharacterPrefab character, Point amountRange)>();
12  private readonly List<Character> monsters = new List<Character>();
13  private readonly List<Vector2> sonarPositions = new List<Vector2>();
14  private readonly List<Vector2> tempSonarPositions = new List<Vector2>();
15  private readonly float maxSonarMarkerDistance = 10000.0f;
16  private readonly Level.PositionType spawnPosType;
17  private Vector2? spawnPos = null;
18 
19  public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
20  {
21  get
22  {
23  if (State > 0)
24  {
25  yield break;
26  }
27  else
28  {
29  foreach (Vector2 sonarPos in sonarPositions)
30  {
31  yield return (Prefab.SonarLabel, sonarPos);
32  }
33  }
34  }
35  }
36 
37  public MonsterMission(MissionPrefab prefab, Location[] locations, Submarine sub)
38  : base(prefab, locations, sub)
39  {
40  Identifier speciesName = prefab.ConfigElement.GetAttributeIdentifier("monsterfile", Identifier.Empty);
41  if (!speciesName.IsEmpty)
42  {
43  var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
44  if (characterPrefab != null)
45  {
46  int monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
47  monsterPrefabs.Add((characterPrefab, new Point(monsterCount)));
48  }
49  else
50  {
51  DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
52  contentPackage: prefab.ContentPackage);
53  }
54  }
55 
56  maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f);
57 
58  var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
59  if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
60  !Enum.TryParse(spawnPosTypeStr, true, out spawnPosType))
61  {
62  spawnPosType = Level.PositionType.MainPath | Level.PositionType.SidePath;
63  }
64 
65  foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
66  {
67  speciesName = monsterElement.GetAttributeIdentifier("character", Identifier.Empty);
68  int defaultCount = monsterElement.GetAttributeInt("count", -1);
69  if (defaultCount < 0)
70  {
71  defaultCount = monsterElement.GetAttributeInt("amount", 1);
72  }
73  int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
74  int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
75  var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
76  if (characterPrefab != null)
77  {
78  monsterPrefabs.Add((characterPrefab, new Point(min, max)));
79  }
80  else
81  {
82  DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
83  contentPackage: prefab.ContentPackage);
84  }
85  }
86 
87  if (monsterPrefabs.Any())
88  {
89  var characterParams = new CharacterParams(monsterPrefabs.First().character.ContentFile as CharacterFile);
90  description = description.Replace("[monster]",
91  TextManager.Get("character." + characterParams.SpeciesTranslationOverride).Fallback(
92  TextManager.Get("character." + characterParams.SpeciesName)));
93  }
94  }
95 
96  protected override void StartMissionSpecific(Level level)
97  {
98  if (monsters.Count > 0)
99  {
100 #if DEBUG
101  throw new Exception($"monsters.Count > 0 ({monsters.Count})");
102 #else
103  DebugConsole.AddWarning("Monster list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
104  monsters.Clear();
105 #endif
106  }
107 
108  if (tempSonarPositions.Count > 0)
109  {
110 #if DEBUG
111  throw new Exception($"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})");
112 #else
113  DebugConsole.AddWarning("Sonar position list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
114  tempSonarPositions.Clear();
115 #endif
116  }
117 
118  if (!IsClient)
119  {
120  float minDistBetweenMonsterMissions = 10000;
121  float mindDistFromSub = Level.Loaded.Size.X * 0.3f;
122  var monsterMissions = GameMain.GameSession.Missions.Select(e => e as MonsterMission).Where(m => m != null && m != this && m.spawnPos.HasValue);
123  if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Level.InterestingPosition spawnPos,
124  filter: p => monsterMissions.None(m => Vector2.DistanceSquared(p.Position.ToVector2(), m.spawnPos.Value) < minDistBetweenMonsterMissions * minDistBetweenMonsterMissions),
125  suppressWarning: true))
126  {
127  Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out spawnPos);
128  }
129  this.spawnPos = spawnPos.Position.ToVector2();
130  foreach (var (character, amountRange) in monsterPrefabs)
131  {
132  int amount = Rand.Range(amountRange.X, amountRange.Y + 1);
133  for (int i = 0; i < amount; i++)
134  {
135  monsters.Add(Character.Create(character.Identifier, this.spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false));
136  }
137  }
138  InitializeMonsters(monsters);
139  }
140  }
141 
142  private void InitializeMonsters(IEnumerable<Character> monsters)
143  {
144  foreach (var monster in monsters)
145  {
146  monster.Enabled = false;
147  if (monster.Params.AI != null && monster.Params.AI.EnforceAggressiveBehaviorForMissions)
148  {
149  monster.Params.AI.FleeHealthThreshold = 0;
150  foreach (var targetParam in monster.Params.AI.Targets)
151  {
152  if (targetParam.Tag == "engine") { continue; }
153  switch (targetParam.State)
154  {
155  case AIState.Avoid:
156  case AIState.Escape:
157  case AIState.Flee:
158  case AIState.PassiveAggressive:
159  targetParam.State = AIState.Attack;
160  break;
161  }
162  }
163  }
164  }
165  SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
166  foreach (Character monster in monsters)
167  {
168  tempSonarPositions.Add(monster.WorldPosition + Rand.Vector(maxSonarMarkerDistance));
169  }
170  if (monsters.Count() != tempSonarPositions.Count)
171  {
172  throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count()} != {tempSonarPositions.Count})");
173  }
174  }
175 
176  protected override void UpdateMissionSpecific(float deltaTime)
177  {
178  switch (State)
179  {
180  case 0:
181  //keep sonar markers within maxSonarMarkerDistance from the monster(s)
182  for (int i = 0; i < tempSonarPositions.Count; i++)
183  {
184  if (monsters.Count != tempSonarPositions.Count)
185  {
186  throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count} != {tempSonarPositions.Count})");
187  }
188 
189  if (i < 0 || i >= monsters.Count)
190  {
191  throw new Exception($"Index {i} outside of bounds 0-{monsters.Count} ({tempSonarPositions.Count})");
192  }
193 
194  if (monsters[i].Removed || monsters[i].IsDead) { continue; }
195  Vector2 diff = tempSonarPositions[i] - monsters[i].WorldPosition;
196 
197  float maxDist = maxSonarMarkerDistance;
199  if (refSub != null)
200  {
201  Vector2 refPos = refSub == null ? Vector2.Zero : refSub.WorldPosition;
202  float subDist = Vector2.Distance(refPos, tempSonarPositions[i]) / maxDist;
203 
204  maxDist = Math.Min(subDist * subDist * maxDist, maxDist);
205  maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].WorldPosition), maxDist);
206  }
207 
208  if (diff.LengthSquared() > maxDist * maxDist)
209  {
210  tempSonarPositions[i] = monsters[i].WorldPosition + Vector2.Normalize(diff) * maxDist;
211  }
212  }
213 
214  sonarPositions.Clear();
215  for (int i = 0; i < monsters.Count; i++)
216  {
217  if (monsters[i].Removed || monsters[i].IsDead) { continue; }
218  //don't add another label if there's another monster roughly at the same spot
219  if (sonarPositions.All(p => Vector2.DistanceSquared(p, tempSonarPositions[i]) > 1000.0f * 1000.0f))
220  {
221  sonarPositions.Add(tempSonarPositions[i]);
222  }
223  }
224  if (!IsClient && monsters.All(m => IsEliminated(m)))
225  {
226  State = 1;
227  }
228  break;
229  }
230  }
231 
232  protected override bool DetermineCompleted()
233  {
234  return state > 0;
235  }
236 
237  protected override void EndMissionSpecific(bool completed)
238  {
239  tempSonarPositions.Clear();
240  monsters.Clear();
241  if (completed)
242  {
243  if (level?.LevelData != null && Prefab.Tags.Contains("huntinggrounds"))
244  {
246  }
247  }
248  }
249 
250  public static bool IsEliminated(Character enemy) =>
251  enemy == null ||
252  enemy.Removed ||
253  enemy.IsDead ||
254  enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
255  }
256 }
static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Create a new character
Contains character data that should be editable in the character editor.
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
int GetAttributeInt(string key, int def)
Identifier GetAttributeIdentifier(string key, string def)
Submarine Submarine
Definition: Entity.cs:53
static GameSession?? GameSession
Definition: GameMain.cs:88
Defines a point in the event that GoTo actions can jump to.
Definition: Label.cs:7
bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Func< InterestingPosition, bool > filter=null, bool suppressWarning=false)
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
static bool IsEliminated(Character enemy)
MonsterMission(MissionPrefab prefab, Location[] locations, Submarine sub)
ContentPackage? ContentPackage
Definition: Prefab.cs:37