EntityEvents and EntitySpawner used to work independently of each other, with separate IDs, and there was no guarantee that spawning and events would happen in the correct order. For example, a client could fail to read events during midround syncing because the entity has been removed, or read an event for an incorrect entity because the entity has been removed and the ID taken by some other entity.
526 lines
19 KiB
C#
526 lines
19 KiB
C#
using Barotrauma.Items.Components;
|
|
using FarseerPhysics;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Barotrauma.Networking
|
|
{
|
|
class RespawnManager : Entity, IServerSerializable
|
|
{
|
|
private readonly float respawnInterval;
|
|
private float maxTransportTime;
|
|
|
|
public enum State
|
|
{
|
|
Waiting,
|
|
Transporting,
|
|
Returning
|
|
}
|
|
|
|
private NetworkMember networkMember;
|
|
|
|
private State state;
|
|
|
|
private Submarine respawnShuttle;
|
|
private Steering shuttleSteering;
|
|
private List<Door> shuttleDoors;
|
|
|
|
/// <summary>
|
|
/// How long until the shuttle is dispatched with respawned characters
|
|
/// </summary>
|
|
public float RespawnTimer
|
|
{
|
|
get { return respawnTimer; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// how long until the shuttle starts heading back out of the level
|
|
/// </summary>
|
|
public float TransportTimer
|
|
{
|
|
get { return shuttleTransportTimer; }
|
|
}
|
|
|
|
public bool CountdownStarted
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public State CurrentState
|
|
{
|
|
get { return state; }
|
|
}
|
|
|
|
private float respawnTimer, shuttleReturnTimer, shuttleTransportTimer;
|
|
|
|
private float updateReturnTimer;
|
|
|
|
public RespawnManager(NetworkMember networkMember, Submarine shuttle)
|
|
: base(shuttle)
|
|
{
|
|
this.networkMember = networkMember;
|
|
|
|
respawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true);
|
|
respawnShuttle.Load(false);
|
|
|
|
ResetShuttle();
|
|
|
|
//respawnShuttle.GodMode = true;
|
|
|
|
shuttleDoors = new List<Door>();
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.Submarine != respawnShuttle) continue;
|
|
|
|
var steering = item.GetComponent<Steering>();
|
|
if (steering != null) shuttleSteering = steering;
|
|
|
|
var door = item.GetComponent<Door>();
|
|
if (door != null) shuttleDoors.Add(door);
|
|
|
|
//lock all wires to prevent the players from messing up the electronics
|
|
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
|
if (connectionPanel != null)
|
|
{
|
|
foreach (Connection connection in connectionPanel.Connections)
|
|
{
|
|
Array.ForEach(connection.Wires, w => { if (w != null) w.Locked = true; });
|
|
}
|
|
}
|
|
}
|
|
|
|
var server = networkMember as GameServer;
|
|
if (server != null)
|
|
{
|
|
respawnInterval = server.RespawnInterval;
|
|
maxTransportTime = server.MaxTransportTime;
|
|
}
|
|
|
|
respawnTimer = respawnInterval;
|
|
}
|
|
|
|
private List<Client> GetClientsToRespawn()
|
|
{
|
|
return networkMember.ConnectedClients.FindAll(c => c.inGame && (c.Character == null || c.Character.IsDead));
|
|
}
|
|
|
|
public void Update(float deltaTime)
|
|
{
|
|
switch (state)
|
|
{
|
|
case State.Waiting:
|
|
UpdateWaiting(deltaTime);
|
|
break;
|
|
case State.Transporting:
|
|
UpdateTransporting(deltaTime);
|
|
break;
|
|
case State.Returning:
|
|
UpdateReturning(deltaTime);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UpdateWaiting(float deltaTime)
|
|
{
|
|
var server = networkMember as GameServer;
|
|
if (server == null)
|
|
{
|
|
if (CountdownStarted)
|
|
{
|
|
respawnTimer = Math.Max(0.0f, respawnTimer - deltaTime);
|
|
}
|
|
return;
|
|
}
|
|
|
|
respawnShuttle.Velocity = Vector2.Zero;
|
|
|
|
shuttleSteering.AutoPilot = false;
|
|
shuttleSteering.MaintainPos = false;
|
|
|
|
int characterToRespawnCount = GetClientsToRespawn().Count;
|
|
int totalCharacterCount = server.ConnectedClients.Count;
|
|
if (server.Character != null)
|
|
{
|
|
totalCharacterCount++;
|
|
if (server.Character.IsDead) characterToRespawnCount++;
|
|
}
|
|
bool startCountdown = (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * server.MinRespawnRatio, 1.0f);
|
|
|
|
if (startCountdown)
|
|
{
|
|
if (!CountdownStarted)
|
|
{
|
|
CountdownStarted = true;
|
|
server.CreateEntityEvent(this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CountdownStarted = false;
|
|
}
|
|
|
|
if (!CountdownStarted) return;
|
|
|
|
respawnTimer -= deltaTime;
|
|
if (respawnTimer <= 0.0f)
|
|
{
|
|
respawnTimer = respawnInterval;
|
|
|
|
DispatchShuttle();
|
|
}
|
|
}
|
|
|
|
private void UpdateTransporting(float deltaTime)
|
|
{
|
|
//infinite transport time -> shuttle wont return
|
|
if (maxTransportTime <= 0.0f) return;
|
|
|
|
shuttleTransportTimer -= deltaTime;
|
|
|
|
if (shuttleTransportTimer + deltaTime > 15.0f && shuttleTransportTimer <= 15.0f &&
|
|
networkMember.Character != null &&
|
|
networkMember.Character.Submarine == respawnShuttle)
|
|
{
|
|
networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server);
|
|
}
|
|
|
|
|
|
var server = networkMember as GameServer;
|
|
if (server == null) return;
|
|
|
|
//if there are no living chracters inside, transporting can be stopped immediately
|
|
if (!Character.CharacterList.Any(c => c.Submarine == respawnShuttle && !c.IsDead))
|
|
{
|
|
shuttleTransportTimer = 0.0f;
|
|
}
|
|
|
|
if (shuttleTransportTimer <= 0.0f)
|
|
{
|
|
state = State.Returning;
|
|
|
|
server.CreateEntityEvent(this);
|
|
|
|
CountdownStarted = false;
|
|
shuttleReturnTimer = maxTransportTime;
|
|
shuttleTransportTimer = maxTransportTime;
|
|
}
|
|
}
|
|
|
|
private void UpdateReturning(float deltaTime)
|
|
{
|
|
//if (shuttleReturnTimer == maxTransportTime &&
|
|
// networkMember.Character != null &&
|
|
// networkMember.Character.Submarine == respawnShuttle)
|
|
//{
|
|
// networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server);
|
|
//}
|
|
|
|
shuttleReturnTimer -= deltaTime;
|
|
|
|
updateReturnTimer += deltaTime;
|
|
|
|
if (updateReturnTimer > 1.0f)
|
|
{
|
|
updateReturnTimer = 0.0f;
|
|
|
|
respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.ShaftBody);
|
|
|
|
shuttleSteering.SetDestinationLevelStart();
|
|
|
|
foreach (Door door in shuttleDoors)
|
|
{
|
|
if (door.IsOpen) door.SetState(false,false,true);
|
|
}
|
|
|
|
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null);
|
|
shuttleGaps.ForEach(g => g.Remove());
|
|
|
|
var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == respawnShuttle && i.GetComponent<DockingPort>() != null);
|
|
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
|
|
|
var server = networkMember as GameServer;
|
|
if (server == null) return;
|
|
|
|
//shuttle has returned if the path has been traversed or the shuttle is close enough to the exit
|
|
|
|
if (!CoroutineManager.IsCoroutineRunning("forcepos"))
|
|
{
|
|
if (shuttleSteering.SteeringPath != null && shuttleSteering.SteeringPath.Finished
|
|
|| (respawnShuttle.WorldPosition.Y + respawnShuttle.Borders.Y > Level.Loaded.StartPosition.Y - Level.ShaftHeight &&
|
|
Math.Abs(Level.Loaded.StartPosition.X - respawnShuttle.WorldPosition.X) < 1000.0f))
|
|
{
|
|
CoroutineManager.StopCoroutines("forcepos");
|
|
CoroutineManager.StartCoroutine(
|
|
ForceShuttleToPos(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + 1000.0f), 100.0f), "forcepos");
|
|
|
|
}
|
|
}
|
|
|
|
if (respawnShuttle.WorldPosition.Y > Level.Loaded.Size.Y || shuttleReturnTimer <= 0.0f)
|
|
{
|
|
CoroutineManager.StopCoroutines("forcepos");
|
|
|
|
ResetShuttle();
|
|
|
|
state = State.Waiting;
|
|
server.CreateEntityEvent(this);
|
|
|
|
respawnTimer = respawnInterval;
|
|
CountdownStarted = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DispatchShuttle()
|
|
{
|
|
var server = networkMember as GameServer;
|
|
if (server == null) return;
|
|
|
|
state = State.Transporting;
|
|
server.CreateEntityEvent(this);
|
|
|
|
ResetShuttle();
|
|
|
|
shuttleSteering.TargetVelocity = Vector2.Zero;
|
|
|
|
RespawnCharacters();
|
|
|
|
CoroutineManager.StopCoroutines("forcepos");
|
|
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
|
}
|
|
|
|
private IEnumerable<object> ForceShuttleToPos(Vector2 position, float speed)
|
|
{
|
|
respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.ShaftBody);
|
|
|
|
while (Math.Abs(position.Y - respawnShuttle.WorldPosition.Y) > 100.0f)
|
|
{
|
|
Vector2 displayVel = Vector2.Normalize(position - respawnShuttle.WorldPosition) * speed;
|
|
respawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
|
|
yield return CoroutineStatus.Running;
|
|
|
|
if (respawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.ShaftBody);
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
private void ResetShuttle()
|
|
{
|
|
shuttleTransportTimer = maxTransportTime;
|
|
shuttleReturnTimer = maxTransportTime;
|
|
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.Submarine != respawnShuttle) continue;
|
|
|
|
if (item.body != null && item.body.Enabled && item.ParentInventory == null)
|
|
{
|
|
Entity.Spawner.AddToRemoveQueue(item);
|
|
continue;
|
|
}
|
|
|
|
item.Condition = 100.0f;
|
|
|
|
var powerContainer = item.GetComponent<PowerContainer>();
|
|
if (powerContainer != null)
|
|
{
|
|
powerContainer.Charge = powerContainer.Capacity;
|
|
}
|
|
}
|
|
|
|
foreach (Structure wall in Structure.WallList)
|
|
{
|
|
if (wall.Submarine != respawnShuttle) continue;
|
|
|
|
for (int i = 0; i < wall.SectionCount; i++)
|
|
{
|
|
wall.AddDamage(i, -100000.0f);
|
|
}
|
|
}
|
|
|
|
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null);
|
|
shuttleGaps.ForEach(g => g.Remove());
|
|
|
|
foreach (Hull hull in Hull.hullList)
|
|
{
|
|
if (hull.Submarine != respawnShuttle) continue;
|
|
|
|
hull.OxygenPercentage = 100.0f;
|
|
hull.Volume = 0.0f;
|
|
}
|
|
|
|
foreach (Character c in Character.CharacterList)
|
|
{
|
|
if (c.Submarine == respawnShuttle)
|
|
{
|
|
if (Character.Controlled == c) Character.Controlled = null;
|
|
c.Enabled = false;
|
|
|
|
if (c.Inventory != null)
|
|
{
|
|
foreach (Item item in c.Inventory.Items)
|
|
{
|
|
if (item == null) continue;
|
|
Entity.Spawner.AddToRemoveQueue(item);
|
|
}
|
|
}
|
|
|
|
c.Kill(CauseOfDeath.Damage, true);
|
|
}
|
|
}
|
|
|
|
respawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + respawnShuttle.Borders.Height));
|
|
|
|
respawnShuttle.Velocity = Vector2.Zero;
|
|
|
|
respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.ShaftBody);
|
|
|
|
}
|
|
|
|
public void RespawnCharacters()
|
|
{
|
|
var server = networkMember as GameServer;
|
|
if (server == null) return;
|
|
|
|
List<Item> spawnedItems = new List<Item>();
|
|
List<Character> spawnedCharacters = new List<Character>();
|
|
|
|
var clients = GetClientsToRespawn();
|
|
|
|
foreach (Client c in clients)
|
|
{
|
|
if (c.characterInfo == null) c.characterInfo = new CharacterInfo(Character.HumanConfigFile, c.name);
|
|
}
|
|
|
|
List<CharacterInfo> characterInfos = clients.Select(c => c.characterInfo).ToList();
|
|
if (server.Character != null && server.Character.IsDead) characterInfos.Add(server.CharacterInfo);
|
|
|
|
server.AssignJobs(clients, server.Character != null && server.Character.IsDead);
|
|
foreach (Client c in clients)
|
|
{
|
|
c.characterInfo.Job = new Job(c.assignedJob);
|
|
}
|
|
|
|
//the spawnpoints where the characters will spawn
|
|
var shuttleSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, respawnShuttle);
|
|
//the spawnpoints where they would spawn if they were spawned inside the main sub
|
|
//(in order to give them appropriate ID card tags)
|
|
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
|
|
|
|
ItemPrefab divingSuitPrefab = ItemPrefab.list.Find(ip => ip.Name == "Diving Suit") as ItemPrefab;
|
|
ItemPrefab oxyPrefab = ItemPrefab.list.Find(ip => ip.Name == "Oxygen Tank") as ItemPrefab;
|
|
ItemPrefab scooterPrefab = ItemPrefab.list.Find(ip => ip.Name == "Underwater Scooter") as ItemPrefab;
|
|
ItemPrefab batteryPrefab = ItemPrefab.list.Find(ip => ip.Name == "Battery Cell") as ItemPrefab;
|
|
|
|
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnShuttle && wp.SpawnType == SpawnType.Cargo);
|
|
|
|
for (int i = 0; i < characterInfos.Count; i++)
|
|
{
|
|
bool myCharacter = i >= clients.Count;
|
|
|
|
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, !myCharacter, false);
|
|
|
|
character.TeamID = 1;
|
|
|
|
if (myCharacter)
|
|
{
|
|
server.Character = character;
|
|
Character.Controlled = character;
|
|
GameMain.LightManager.LosEnabled = true;
|
|
}
|
|
else
|
|
{
|
|
clients[i].Character = character;
|
|
}
|
|
|
|
Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position;
|
|
|
|
if (divingSuitPrefab != null && oxyPrefab != null)
|
|
{
|
|
var divingSuit = new Item(divingSuitPrefab, pos, respawnShuttle);
|
|
Entity.Spawner.CreateNetworkEvent(divingSuit, false);
|
|
|
|
var oxyTank = new Item(oxyPrefab, pos, respawnShuttle);
|
|
Entity.Spawner.CreateNetworkEvent(oxyTank, false);
|
|
divingSuit.Combine(oxyTank);
|
|
}
|
|
|
|
if (scooterPrefab != null && batteryPrefab != null)
|
|
{
|
|
var scooter = new Item(scooterPrefab, pos, respawnShuttle);
|
|
Entity.Spawner.CreateNetworkEvent(scooter, false);
|
|
|
|
var battery = new Item(batteryPrefab, pos, respawnShuttle);
|
|
Entity.Spawner.CreateNetworkEvent(battery, false);
|
|
|
|
scooter.Combine(battery);
|
|
}
|
|
|
|
character.GiveJobItems(mainSubSpawnPoints[i]);
|
|
GameMain.GameSession.CrewManager.characters.Add(character);
|
|
}
|
|
|
|
}
|
|
|
|
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
|
{
|
|
msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)state);
|
|
|
|
switch (state)
|
|
{
|
|
case State.Transporting:
|
|
msg.Write(TransportTimer);
|
|
break;
|
|
case State.Waiting:
|
|
msg.Write(CountdownStarted);
|
|
msg.Write(respawnTimer);
|
|
break;
|
|
case State.Returning:
|
|
break;
|
|
}
|
|
|
|
msg.WritePadBits();
|
|
}
|
|
|
|
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
|
{
|
|
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
|
|
|
|
switch (newState)
|
|
{
|
|
case State.Transporting:
|
|
maxTransportTime = msg.ReadSingle();
|
|
shuttleTransportTimer = maxTransportTime;
|
|
CountdownStarted = false;
|
|
|
|
if (state != newState)
|
|
{
|
|
CoroutineManager.StopCoroutines("forcepos");
|
|
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
|
}
|
|
break;
|
|
case State.Waiting:
|
|
CountdownStarted = msg.ReadBoolean();
|
|
ResetShuttle();
|
|
respawnTimer = msg.ReadSingle();
|
|
break;
|
|
case State.Returning:
|
|
CountdownStarted = false;
|
|
break;
|
|
}
|
|
state = newState;
|
|
|
|
msg.ReadPadBits();
|
|
}
|
|
}
|
|
}
|