Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Events/Missions/EndMission.cs
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
11  partial class EndMission : Mission
12  {
13  enum MissionPhase
14  {
15  Initial,
16  NoItemsDestroyed,
17  SomeItemsDestroyed,
18  AllItemsDestroyed,
19  BossKilled
20  }
21 
22  private readonly CharacterPrefab bossPrefab;
23  private readonly CharacterPrefab minionPrefab;
24 
25  private readonly Identifier spawnPointTag;
26  private WayPoint bossSpawnPoint;
27  private readonly Identifier destructibleItemTag;
28 
29  private readonly string endCinematicSound;
30 
31  private ImmutableArray<Character> minions;
32  private readonly int minionCount;
33  private readonly float minionScatter;
34 
35  private Character boss;
36 
37  private readonly ItemPrefab projectilePrefab;
38 
39  private float projectileTimer = 30.0f;
40 
41  private readonly float startCinematicDistance = 30.0f;
42 
43  private float endCinematicTimer;
44 
45  private readonly List<Item> destructibleItems = new List<Item>();
46 
47  protected readonly float wakeUpCinematicDelay = 5.0f;
48  protected readonly float bossWakeUpDelay = 7.0f;
49  protected readonly float cameraWaitDuration = 7.0f;
50 
51  public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
52  {
53  get { return destructibleItems.Where(it => it.Condition > 0.0f).Select(it => (Prefab.SonarLabel, it.WorldPosition)); }
54  }
55 
56  public override int State
57  {
58  get { return base.State; }
59  set
60  {
61 
62  if (state != value)
63  {
64  base.State = value;
65  OnStateChangedProjSpecific();
66  if (Phase == MissionPhase.AllItemsDestroyed)
67  {
68  CoroutineManager.Invoke(() =>
69  {
70  if (boss != null && !boss.Removed)
71  {
72  Vector2 prevPos = boss.AnimController.Collider.SimPosition;
73  boss.AnimController.ColliderIndex = 1;
74  if (bossSpawnPoint != null)
75  {
76  //ensure the new collider stays in the same position (the 2nd one has a different shape than the 1st one)
77  boss.AnimController.Collider.SetTransform(prevPos, 0.0f);
78  }
79  }
80  }, delay: wakeUpCinematicDelay + bossWakeUpDelay + 2);
81  }
82  }
83  }
84  }
85 
86  private MissionPhase Phase
87  {
88  get
89  {
90  //state 0: nothing happens yet, play a cinematic and skip to the next state when close enough to the boss
91  //state 1: start cinematic played
92  //state 2: first destructibleItems destroyed
93  //state 3: 2nd destructibleItems destroyed
94  //state 4: all destructibleItems destroyed
95  //state 5: boss killed
96  if (state == 0) { return MissionPhase.Initial; }
97  if (state == 1) { return MissionPhase.NoItemsDestroyed; }
98  if (state < destructibleItems.Count + 1) { return MissionPhase.SomeItemsDestroyed; }
99  if (state < destructibleItems.Count + 2) { return MissionPhase.AllItemsDestroyed; }
100  return MissionPhase.BossKilled;
101  }
102  }
103 
104  public EndMission(MissionPrefab prefab, Location[] locations, Submarine sub)
105  : base(prefab, locations, sub)
106  {
107  Identifier speciesName = prefab.ConfigElement.GetAttributeIdentifier("bossfile", Identifier.Empty);
108  if (!speciesName.IsEmpty)
109  {
110  bossPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
111  if (bossPrefab == null)
112  {
113  DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
114  contentPackage: Prefab.ContentPackage);
115  }
116  }
117  else
118  {
119  DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Monster file not set.",
120  contentPackage: Prefab.ContentPackage);
121  }
122 
123  Identifier minionName = prefab.ConfigElement.GetAttributeIdentifier("minionfile", Identifier.Empty);
124  if (!minionName.IsEmpty)
125  {
126  minionPrefab = CharacterPrefab.FindBySpeciesName(minionName);
127  if (minionPrefab == null)
128  {
129  DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
130  contentPackage: Prefab.ContentPackage);
131  }
132  }
133 
134  minionCount = Math.Min(prefab.ConfigElement.GetAttributeInt(nameof(minionCount), 0), 255);
135  minionScatter = Math.Min(prefab.ConfigElement.GetAttributeFloat(nameof(minionScatter), 0), 10000);
136 
137  Identifier projectileId = prefab.ConfigElement.GetAttributeIdentifier("projectile", Identifier.Empty);
138  if (!projectileId.IsEmpty)
139  {
140  projectilePrefab = MapEntityPrefab.FindByIdentifier(projectileId) as ItemPrefab;
141  if (projectilePrefab == null)
142  {
143  DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find an item prefab with the name \"{projectileId}\".",
144  contentPackage: Prefab.ContentPackage);
145  }
146  }
147 
148  spawnPointTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(spawnPointTag), Identifier.Empty);
149  destructibleItemTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(destructibleItemTag), Identifier.Empty);
150  endCinematicSound = prefab.ConfigElement.GetAttributeString(nameof(endCinematicSound), string.Empty);
151  startCinematicDistance = prefab.ConfigElement.GetAttributeFloat(nameof(startCinematicDistance), 0);
152  }
153 
154  protected override void StartMissionSpecific(Level level)
155  {
156  bossSpawnPoint = WayPoint.WayPointList.FirstOrDefault(wp => wp.Tags.Contains(spawnPointTag));
157  if (bossSpawnPoint == null)
158  {
159  DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\".",
160  contentPackage: Prefab.ContentPackage);
161  return;
162  }
163  if (!IsClient)
164  {
165  boss = Character.Create(bossPrefab.Identifier, bossSpawnPoint.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
166  var minionList = new List<Character>();
167  float angle = 0;
168  float angleStep = MathHelper.TwoPi / Math.Max(minionCount, 1);
169  for (int i = 0; i < minionCount; i++)
170  {
171  minionList.Add(Character.Create(minionPrefab.Identifier, MathUtils.GetPointOnCircumference(bossSpawnPoint.WorldPosition, minionScatter, angle), ToolBox.RandomSeed(8), createNetworkEvent: false));
172  angle += angleStep;
173  }
174  SwarmBehavior.CreateSwarm(minionList.Cast<AICharacter>());
175  minions = minionList.ToImmutableArray();
176  }
177  if (destructibleItemTag.IsEmpty)
178  {
179  DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Destructible item tag not set.",
180  contentPackage: Prefab.ContentPackage);
181  return;
182  }
183  destructibleItems.Clear();
184  destructibleItems.AddRange(Item.ItemList.FindAll(it => it.HasTag(destructibleItemTag)));
185  if (destructibleItems.None())
186  {
187  DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".",
188  contentPackage: Prefab.ContentPackage);
189  return;
190  }
191  }
192 
193  protected override void UpdateMissionSpecific(float deltaTime)
194  {
195  UpdateProjSpecific();
196 
197  if (state == 0)
198  {
199  if (startCinematicDistance <= 0.0f ||
200  boss == null || Submarine.MainSub == null ||
201  Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, boss.WorldPosition) <= startCinematicDistance * startCinematicDistance)
202  {
203  State = 1;
204  }
205  return;
206  }
207 
208  if (!IsClient && State > 0)
209  {
210  State = Math.Max(State, destructibleItems.Count(it => it.Condition <= 0.0f) + 1);
211  }
212 
213  if (Phase == MissionPhase.AllItemsDestroyed)
214  {
215  if (projectilePrefab != null && boss != null && !boss.IsDead && !boss.Removed)
216  {
217  projectileTimer -= deltaTime;
218  if (projectileTimer <= 0.0f)
219  {
220  float dist = Vector2.Distance(Submarine.MainSub.WorldPosition, boss.WorldPosition);
221  float distanceFactor = Math.Min(dist / 10000.0f, 1.0f);
222  int projectileAmount = Rand.Range(3, 6);
223  //more concentrated shots the further the sub is
224  float spread = MathHelper.ToRadians(Rand.Range(20.0f, 180.0f)) * Math.Max(1.0f - distanceFactor, 0.2f);
225  for (int i = 0; i < projectileAmount; i++)
226  {
227  int index = i;
228  Entity.Spawner.AddItemToSpawnQueue(projectilePrefab, boss.WorldPosition, onSpawned: it =>
229  {
230  var projectile = it.GetComponent<Projectile>();
231  float angle = MathUtils.VectorToAngle(Submarine.MainSub.WorldPosition - boss.WorldPosition);
232  if (projectileAmount > 1)
233  {
234  angle += (index / (float)(projectileAmount - 1) - 0.5f) * spread;
235  }
236  it.body.SetTransform(it.SimPosition, angle);
237  it.UpdateTransform();
238  //faster launch velocity the further the sub is
239  projectile.Use(launchImpulseModifier: MathHelper.Lerp(0, 5, distanceFactor));
240  });
241  }
242 
243  //the closer the sub is, more likely it is to shoot frequently
244  float shortIntervalProbability = MathHelper.Lerp(0.9f, 0.05f, distanceFactor);
245  if (Rand.Range(0.0f, 1.0f) < shortIntervalProbability)
246  {
247  projectileTimer = Rand.Range(3.0f, 5.0f);
248  }
249  else
250  {
251  projectileTimer = Rand.Range(15f, 30f);
252  }
253  }
254  }
255  else
256  {
257  State = Math.Max(destructibleItems.Count + 2, State);
258  }
259  }
260  else if (Phase == MissionPhase.BossKilled)
261  {
262  const float EndCinematicDuration = 20.0f;
263 
264  endCinematicTimer += deltaTime;
265 #if CLIENT
266  Screen.Selected.Cam.Shake = MathHelper.Clamp(MathF.Pow(endCinematicTimer, 3), 5.0f, 200.0f);
267 
268 
270  Math.Max((endCinematicTimer - 5.0f) * 0.05f, 0.0f)
271  + (PerlinNoise.GetPerlin(endCinematicTimer * 0.1f, endCinematicTimer * 0.05f) - 0.5f) * 0.5f * (endCinematicTimer / EndCinematicDuration);
272  if (Rand.Range(0.0f, 100.0f) < endCinematicTimer)
273  {
275  }
276  Level.Loaded.Renderer.ChromaticAberrationStrength = endCinematicTimer * 5;
277  Level.Loaded.Renderer.CollapseEffectOrigin = boss.WorldPosition;
278  Level.Loaded.Renderer.CollapseEffectStrength = endCinematicTimer / EndCinematicDuration;
279 #endif
280  if (endCinematicTimer > 5 && !IsClient)
281  {
282  foreach (Character c in Character.CharacterList)
283  {
284  if (c.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior == null)
285  {
286  c.SetAllDamage(200.0f, 0.0f, 0.0f);
287  }
288  }
289  }
290 
291  if (endCinematicTimer > EndCinematicDuration && !IsClient)
292  {
293  //endCinematicTimer = 0;
294  GameMain.GameSession.Campaign?.LoadNewLevel();
295  }
296  }
297 
298  }
299 
300  partial void UpdateProjSpecific();
301 
302  partial void OnStateChangedProjSpecific();
303 
304  protected override bool DetermineCompleted()
305  {
306  return Phase == MissionPhase.BossKilled;
307  }
308  }
309 }
float Rotation
Definition: Camera.cs:99
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
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)
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
EndMission(MissionPrefab prefab, Location[] locations, Submarine sub)
static EntitySpawner Spawner
Definition: Entity.cs:31
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition=null, int? quality=null, Action< Item > onSpawned=null)
static readonly List< Item > ItemList
Defines a point in the event that GoTo actions can jump to.
Definition: Label.cs:7
static MapEntityPrefab FindByIdentifier(Identifier identifier)
ContentPackage? ContentPackage
Definition: Prefab.cs:37
static void CreateSwarm(IEnumerable< AICharacter > swarm)