Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Networking/RespawnManager.cs
2 using FarseerPhysics;
3 using FarseerPhysics.Dynamics;
4 using FarseerPhysics.Dynamics.Contacts;
5 using Microsoft.Xna.Framework;
6 using System;
7 using System.Collections.Generic;
8 using System.Linq;
9 
10 namespace Barotrauma.Networking
11 {
13  {
17  public static float SkillLossPercentageOnDeath => GameMain.NetworkMember?.ServerSettings?.SkillLossPercentageOnDeath ?? 20.0f;
18 
24  public static float SkillLossPercentageOnImmediateRespawn => GameMain.NetworkMember?.ServerSettings?.SkillLossPercentageOnImmediateRespawn ?? 10.0f;
25 
26  public static RespawnMode RespawnMode => GameMain.NetworkMember?.ServerSettings?.RespawnMode ?? RespawnMode.MidRound;
27 
28  public static bool UseDeathPrompt
29  {
30  get
31  {
32  return GameMain.GameSession?.GameMode is CampaignMode && Level.Loaded != null;
33  }
34  }
35 
36  public enum State
37  {
38  Waiting,
39  Transporting,
40  Returning
41  }
42 
43  private readonly NetworkMember networkMember;
44  private readonly Steering shuttleSteering;
45  private readonly List<Door> shuttleDoors;
46  private readonly ItemContainer respawnContainer;
47 
48  //items created during respawn
49  //any respawn items left in the shuttle are removed when the shuttle despawns
50  private readonly List<Item> respawnItems = new List<Item>();
51 
52  //characters who spawned during the last respawn
53  private readonly List<Character> respawnedCharacters = new List<Character>();
54 
55  public bool UsingShuttle
56  {
57  get { return RespawnShuttle != null; }
58  }
59 
63  public DateTime RespawnTime { get; private set; }
64 
68  public DateTime ReturnTime { get; private set; }
69 
71  {
72  get;
73  private set;
74  }
75 
77  {
78  get;
79  private set;
80  }
81 
82  public State CurrentState { get; private set; }
83 
84  private float maxTransportTime;
85 
86  private float updateReturnTimer;
87 
88  public bool CanRespawnAgain =>
89  /*can never respawn again if we're currently transporting and transport time is set to be infinite*/
90  !(CurrentState == State.Transporting && maxTransportTime <= 0.0f);
91 
92  public Submarine RespawnShuttle { get; private set; }
93 
94  public RespawnManager(NetworkMember networkMember, SubmarineInfo shuttleInfo)
95  : base(null, Entity.RespawnManagerID)
96  {
97  this.networkMember = networkMember;
98 
99  if (shuttleInfo != null && networkMember.ServerSettings is not { RespawnMode: RespawnMode.Permadeath })
100  {
101  RespawnShuttle = new Submarine(shuttleInfo, true);
102  RespawnShuttle.PhysicsBody.FarseerBody.OnCollision += OnShuttleCollision;
103  //set crush depth slightly deeper than the main sub's
105 
106  //prevent wifi components from communicating between the respawn shuttle and other subs
107  List<WifiComponent> wifiComponents = new List<WifiComponent>();
108  foreach (Item item in Item.ItemList)
109  {
110  if (item.Submarine == RespawnShuttle) { wifiComponents.AddRange(item.GetComponents<WifiComponent>()); }
111  }
112  foreach (WifiComponent wifiComponent in wifiComponents)
113  {
114  wifiComponent.TeamID = CharacterTeamType.FriendlyNPC;
115  }
116 
117  ResetShuttle();
118 
119  shuttleDoors = new List<Door>();
120  foreach (Item item in Item.ItemList)
121  {
122  if (item.Submarine != RespawnShuttle) { continue; }
123 
124  if (item.HasTag(Tags.RespawnContainer))
125  {
126  respawnContainer = item.GetComponent<ItemContainer>();
127  }
128 
129  var steering = item.GetComponent<Steering>();
130  if (steering != null) { shuttleSteering = steering; }
131 
132  var door = item.GetComponent<Door>();
133  if (door != null) { shuttleDoors.Add(door); }
134 
135  //lock all wires to prevent the players from messing up the electronics
136  var connectionPanel = item.GetComponent<ConnectionPanel>();
137  if (connectionPanel != null)
138  {
139  foreach (Connection connection in connectionPanel.Connections)
140  {
141  foreach (Wire wire in connection.Wires)
142  {
143 #if SERVER
144  if (GameMain.LuaCs.Game.overrideRespawnSub == false)
145  {
146  if (wire != null) wire.Locked = true;
147  }
148 #endif
149  }
150  }
151  }
152  }
153  }
154  else
155  {
156  RespawnShuttle = null;
157  }
158 
159 #if SERVER
160  if (networkMember is GameServer server)
161  {
162  maxTransportTime = server.ServerSettings.MaxTransportTime;
163  }
164 #endif
165  }
166 
167  private bool OnShuttleCollision(Fixture sender, Fixture other, Contact contact)
168  {
169  //ignore collisions with the top barrier when returning
170  return CurrentState != State.Returning || other?.Body != Level.Loaded?.TopBarrier;
171  }
172 
173  public void Update(float deltaTime)
174  {
175  var result = GameMain.LuaCs.Hook.Call<bool?>("respawnManager.update");
176  if (result != null && result.Value) return;
177 
178  if (RespawnShuttle == null)
179  {
180  if (CurrentState != State.Waiting)
181  {
182  CurrentState = State.Waiting;
183  }
184  }
185 
186  switch (CurrentState)
187  {
188  case State.Waiting:
189  UpdateWaiting(deltaTime);
190  break;
191  case State.Transporting:
192  UpdateTransporting(deltaTime);
193  break;
194  case State.Returning:
195  UpdateReturning(deltaTime);
196  break;
197  }
198  }
199 
200  partial void UpdateWaiting(float deltaTime);
201 
202  private void UpdateTransporting(float deltaTime)
203  {
204  //infinite transport time -> shuttle wont return
205  if (maxTransportTime <= 0.0f) return;
206  UpdateTransportingProjSpecific(deltaTime);
207  }
208 
209  partial void UpdateTransportingProjSpecific(float deltaTime);
210 
211  public void ForceRespawn()
212  {
213  ResetShuttle();
215  RespawnTime = DateTime.Now;
216  CurrentState = State.Waiting;
217  }
218 
219  private void UpdateReturning(float deltaTime)
220  {
221  updateReturnTimer += deltaTime;
222  if (updateReturnTimer > 1.0f)
223  {
224  updateReturnTimer = 0.0f;
225  shuttleSteering?.SetDestinationLevelStart();
226  UpdateReturningProjSpecific(deltaTime);
227  }
228  }
229 
230  partial void UpdateReturningProjSpecific(float deltaTime);
231 
232  private IEnumerable<CoroutineStatus> ForceShuttleToPos(Vector2 position, float speed)
233  {
234 
235 #if SERVER
236  if (GameMain.LuaCs.Game.overrideRespawnSub)
237  {
238  yield return CoroutineStatus.Success;
239  }
240 #endif
241 
242  if (RespawnShuttle == null)
243  {
244  yield return CoroutineStatus.Success;
245  }
246 
247  while (Math.Abs(position.Y - RespawnShuttle.WorldPosition.Y) > 100.0f)
248  {
249  Vector2 diff = position - RespawnShuttle.WorldPosition;
250  if (diff.LengthSquared() > 0.01f)
251  {
252  Vector2 displayVel = Vector2.Normalize(diff) * speed;
253  RespawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
254  }
255  yield return CoroutineStatus.Running;
256 
257  if (RespawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
258  }
259 
260  yield return CoroutineStatus.Success;
261  }
262 
263  private void ResetShuttle()
264  {
265  ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
266 
267 #if SERVER
268  despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
269 #endif
270 
271  if (RespawnShuttle == null) { return; }
272 
273  foreach (Item item in Item.ItemList)
274  {
275  if (item.Submarine != RespawnShuttle) { continue; }
276 
277  //remove respawn items that have been left in the shuttle
278  if (respawnItems.Contains(item) || respawnContainer?.Item != null && item.IsOwnedBy(respawnContainer.Item))
279  {
281  continue;
282  }
283 
284 #if CLIENT
285  foreach (var itemComponent in item.Components)
286  {
287  itemComponent.StopLoopingSound();
288  }
289 #endif
290 
291  //restore other items to full condition and recharge batteries
292  item.Condition = item.MaxCondition;
293  item.GetComponent<Repairable>()?.ResetDeterioration();
294  var powerContainer = item.GetComponent<PowerContainer>();
295  if (powerContainer != null)
296  {
297  powerContainer.Charge = powerContainer.GetCapacity();
298  }
299 
300  var door = item.GetComponent<Door>();
301  if (door != null) { door.Stuck = 0.0f; }
302 
303  var steering = item.GetComponent<Steering>();
304  if (steering != null)
305  {
306  steering.MaintainPos = true;
307  steering.AutoPilot = true;
308 #if SERVER
309  steering.UnsentChanges = true;
310 #endif
311  }
312  }
313  respawnItems.Clear();
314 
315  foreach (Structure wall in Structure.WallList)
316  {
317  if (wall.Submarine != RespawnShuttle) { continue; }
318  for (int i = 0; i < wall.SectionCount; i++)
319  {
320  wall.AddDamage(i, -100000.0f);
321  }
322  }
323 
324  foreach (Hull hull in Hull.HullList)
325  {
326  if (hull.Submarine != RespawnShuttle) { continue; }
327  hull.OxygenPercentage = 100.0f;
328  hull.WaterVolume = 0.0f;
329  hull.BallastFlora?.Remove();
330  }
331 
332  Dictionary<Character, Vector2> characterPositions = new Dictionary<Character, Vector2>();
333  foreach (Character c in Character.CharacterList)
334  {
335  if (c.Submarine != RespawnShuttle) { continue; }
336  if (!respawnedCharacters.Contains(c))
337  {
338  characterPositions.Add(c, c.WorldPosition);
339  continue;
340  }
341 #if CLIENT
342  if (Character.Controlled == c) { Character.Controlled = null; }
343 #endif
344  c.Kill(CauseOfDeathType.Unknown, null, true);
345  c.Enabled = false;
346 
348  if (c.Inventory != null)
349  {
350  foreach (Item item in c.Inventory.AllItems)
351  {
353  }
354  }
355  }
356 
357  RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height));
358  RespawnShuttle.Velocity = Vector2.Zero;
359 
360  foreach (var characterPosition in characterPositions)
361  {
362  characterPosition.Key.TeleportTo(characterPosition.Value);
363  }
364  }
365 
366  public static float GetReducedSkill(CharacterInfo characterInfo, Skill skill, float skillLossPercentage, float? currentSkillLevel = null)
367  {
368  var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier);
369  float currentLevel = currentSkillLevel ?? skill.Level;
370  if (skillPrefab == null || currentLevel < skillPrefab.LevelRange.End) { return currentLevel; }
371  return MathHelper.Lerp(currentLevel, skillPrefab.LevelRange.End, skillLossPercentage / 100.0f);
372  }
373 
374  partial void RespawnCharactersProjSpecific(Vector2? shuttlePos);
375  public void RespawnCharacters(Vector2? shuttlePos)
376  {
377  RespawnCharactersProjSpecific(shuttlePos);
378  }
379 
381  {
382  return AfflictionPrefab.Prefabs.First(a => a.AfflictionType == "respawnpenalty");
383  }
384 
386  {
388  }
389 
390  public static void GiveRespawnPenaltyAffliction(Character character)
391  {
392  var respawnPenaltyAffliction = GetRespawnPenaltyAffliction();
393  if (respawnPenaltyAffliction != null)
394  {
395  character.CharacterHealth.ApplyAffliction(targetLimb: null, respawnPenaltyAffliction);
396  }
397  }
398 
399  public Vector2 FindSpawnPos()
400  {
401  if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
402 
403  Rectangle dockedBorders = RespawnShuttle.GetDockedBorders();
404  Vector2 diffFromDockedBorders =
405  new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
406  - new Vector2(RespawnShuttle.Borders.Center.X, RespawnShuttle.Borders.Y - RespawnShuttle.Borders.Height / 2);
407 
408  int minWidth = Math.Max(dockedBorders.Width, 1000);
409  int minHeight = Math.Max(dockedBorders.Height, 1000);
410 
411  List<Level.InterestingPosition> potentialSpawnPositions = new List<Level.InterestingPosition>();
412  foreach (Level.InterestingPosition potentialSpawnPos in Level.Loaded.PositionsOfInterest.Where(p => p.PositionType == Level.PositionType.MainPath))
413  {
414  bool invalid = false;
415  //make sure the shuttle won't overlap with any ruins
416  foreach (var ruin in Level.Loaded.Ruins)
417  {
418  if (Math.Abs(ruin.Area.Center.X - potentialSpawnPos.Position.X) < (minWidth + ruin.Area.Width) / 2) { invalid = true; break; }
419  if (Math.Abs(ruin.Area.Center.Y - potentialSpawnPos.Position.Y) < (minHeight + ruin.Area.Height) / 2) { invalid = true; break; }
420  }
421  if (invalid) { continue; }
422 
423  //make sure there aren't any walls too close
424  var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight));
425  if (tooCloseCells.Any()) { continue; }
426 
427  //make sure the spawnpoint is far enough from other subs
428  foreach (Submarine sub in Submarine.Loaded)
429  {
430  if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; }
431  float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f);
432  if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist)
433  {
434  invalid = true;
435  break;
436  }
437  }
438  if (invalid) { continue; }
439 
440  foreach (Character character in Character.CharacterList)
441  {
442  if (character.IsDead)
443  {
444  //cannot spawn directly over dead bodies
445  if (Math.Abs(character.WorldPosition.X - potentialSpawnPos.Position.X) < minWidth) { invalid = true; break; }
446  if (Math.Abs(character.WorldPosition.Y - potentialSpawnPos.Position.Y) < minHeight) { invalid = true; break; }
447  }
448  else
449  {
450  //cannot spawn near alive characters (to prevent other players from seeing the shuttle
451  //appear out of nowhere, or monsters from immediatelly wrecking the shuttle)
452  if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < 5000.0f * 5000.0f)
453  {
454  invalid = true;
455  break;
456  }
457  }
458  }
459  if (invalid) { continue; }
460 
461  potentialSpawnPositions.Add(potentialSpawnPos);
462  }
463  Vector2 bestSpawnPos = new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height);
464  float bestSpawnPosValue = 0.0f;
465  foreach (var potentialSpawnPos in potentialSpawnPositions)
466  {
467  //the closer the spawnpos is to the main sub, the better
468  float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), Submarine.MainSub.WorldPosition), 1.0f);
469 
470  //prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards)
471  if (potentialSpawnPos.Position.X > Submarine.MainSub.WorldPosition.X)
472  {
473  spawnPosValue *= 0.1f;
474  }
475 
476  if (spawnPosValue > bestSpawnPosValue)
477  {
478  bestSpawnPos = potentialSpawnPos.Position.ToVector2();
479  bestSpawnPosValue = spawnPosValue;
480  }
481  }
482 
483  return bestSpawnPos;
484  }
485  }
486 }
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
Affliction Instantiate(float strength, Character source=null)
static readonly PrefabCollection< AfflictionPrefab > Prefabs
void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking=true, bool ignoreUnkillability=false)
Stores information about the Character that is needed between rounds in the menu etc....
static EntitySpawner Spawner
Definition: Entity.cs:31
virtual Vector2 WorldPosition
Definition: Entity.cs:49
const ushort RespawnManagerID
Definition: Entity.cs:16
Submarine Submarine
Definition: Entity.cs:53
static GameSession?? GameSession
Definition: GameMain.cs:88
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
static readonly List< Item > ItemList
JobPrefab Prefab
Definition: Job.cs:18
object Call(string name, params object[] args)
bool overrideRespawnSub
Definition: LuaGame.cs:144
static float SkillLossPercentageOnDeath
How much skills drop towards the job's default skill levels when dying
DateTime RespawnTime
When will the shuttle be dispatched with respawned characters
static float GetReducedSkill(CharacterInfo characterInfo, Skill skill, float skillLossPercentage, float? currentSkillLevel=null)
DateTime ReturnTime
When will the sub start heading back out of the level
RespawnManager(NetworkMember networkMember, SubmarineInfo shuttleInfo)
static float SkillLossPercentageOnImmediateRespawn
How much more the skills drop towards the job's default skill levels when dying, in addition to Skill...
readonly Identifier Identifier
Definition: Skill.cs:7
float Level
Definition: Skill.cs:19
void SetCrushDepth(float realWorldCrushDepth)
Normally crush depth is determined by the crush depths of the walls and upgrades applied on them....
Rectangle? Borders
Extents of the solid items/structures (ones with a physics body) and hulls
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
void SetPosition(Vector2 position, List< Submarine > checkd=null, bool forceUndockFromStaticSubmarines=true)
Interface for entities that the server can send events to the clients