diff --git a/Subsurface/Source/Characters/AI/EnemyAIController.cs b/Subsurface/Source/Characters/AI/EnemyAIController.cs index 3e32edbc0..6daac9237 100644 --- a/Subsurface/Source/Characters/AI/EnemyAIController.cs +++ b/Subsurface/Source/Characters/AI/EnemyAIController.cs @@ -435,6 +435,11 @@ namespace Barotrauma foreach (AITarget target in AITarget.List) { + if (Level.Loaded != null && target.WorldPosition.Y > Level.Loaded.Size.Y) + { + continue; + } + float valueModifier = 0.0f; float dist = 0.0f; diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index c934717a6..e1d0a1448 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -19,8 +19,6 @@ namespace Barotrauma { public static List CharacterList = new List(); - public static Queue NewCharacterQueue = new Queue(); - public static bool DisableControls; //the Character that the player is currently controlling @@ -37,6 +35,8 @@ namespace Barotrauma } } + public bool SpawnedMidRound; + private bool enabled; public bool Enabled @@ -161,6 +161,12 @@ namespace Barotrauma get { return inventory; } } + public WayPoint SpawnPoint + { + get; + set; + } + private Color speechBubbleColor; private float speechBubbleTimer; @@ -783,7 +789,7 @@ namespace Barotrauma foreach (Character c in CharacterList) { - if (c == this) continue; + if (c == this || !c.enabled) continue; if (Vector2.Distance(SimPosition, c.SimPosition) > maxDist) continue; diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index f15c411e0..a934069db 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -261,7 +261,11 @@ namespace Barotrauma spawnedCharacter = Character.Create("Content/Characters/" + commands[1] + "/" + commands[1] + ".xml", spawnPosition); } - if (spawnedCharacter != null && GameMain.Server != null) GameMain.Server.SendCharacterSpawnMessage(spawnedCharacter); + if (spawnedCharacter != null && GameMain.Server != null) + { + spawnedCharacter.SpawnedMidRound = true; + GameMain.Server.SendCharacterSpawnMessage(spawnedCharacter); + } break; case "disablecrewai": diff --git a/Subsurface/Source/Items/ItemSpawner.cs b/Subsurface/Source/Items/ItemSpawner.cs index 7899ba652..793b5fdb2 100644 --- a/Subsurface/Source/Items/ItemSpawner.cs +++ b/Subsurface/Source/Items/ItemSpawner.cs @@ -8,6 +8,10 @@ namespace Barotrauma { private readonly Queue> spawnQueue; + + public List spawnItems = new List(); + + public ItemSpawner() { spawnQueue = new Queue>(); @@ -67,6 +71,7 @@ namespace Barotrauma if (itemInfo.Second is Inventory) { var item = new Item(itemInfo.First, Vector2.Zero, null); + spawnItems.Add(item); var inventory = (Inventory)itemInfo.Second; inventory.TryPutItem(item, null, false); @@ -88,7 +93,7 @@ namespace Barotrauma message.Write(items[i].Prefab.Name); message.Write(items[i].ID); - message.Write((inventories[i]==null || inventories[i].Owner == null) ? (ushort)0 : inventories[i].Owner.ID); + message.Write((inventories == null || inventories[i] == null || inventories[i].Owner == null) ? (ushort)0 : inventories[i].Owner.ID); } } @@ -128,17 +133,25 @@ namespace Barotrauma } } - var item = new Item(itemPrefab, Vector2.Zero, null); + var item = new Item(itemPrefab, Vector2.Zero, null); item.ID = itemId; if (inventory != null) inventory.TryPutItem(item, null, false); } } + + public void Clear() + { + spawnQueue.Clear(); + spawnItems.Clear(); + } } class ItemRemover { private readonly Queue removeQueue; + + public List removedItems = new List(); public ItemRemover() { @@ -165,6 +178,7 @@ namespace Barotrauma while (removeQueue.Count > 0) { var item = removeQueue.Dequeue(); + removedItems.Add(item); item.Remove(); @@ -191,10 +205,16 @@ namespace Barotrauma ushort itemId = message.ReadUInt16(); var item = MapEntity.FindEntityByID(itemId); - if (item == null || item is Item) continue; + if (item == null || !(item is Item)) continue; item.Remove(); } } + + public void Clear() + { + removeQueue.Clear(); + removedItems.Clear(); + } } } diff --git a/Subsurface/Source/Map/TransitionCinematic.cs b/Subsurface/Source/Map/TransitionCinematic.cs index 476255cff..6d3449f6c 100644 --- a/Subsurface/Source/Map/TransitionCinematic.cs +++ b/Subsurface/Source/Map/TransitionCinematic.cs @@ -87,10 +87,10 @@ namespace Barotrauma foreach (Submarine sub in subs) { - sub.ApplyForce((Vector2.Normalize(sub.Position - targetPos) * targetSpeed - sub.Velocity) * 500.0f); + if (sub.Position == targetPos) continue; + sub.ApplyForce((Vector2.Normalize(targetPos - sub.Position) * targetSpeed - sub.Velocity) * 500.0f); } - - + timer += CoroutineManager.UnscaledDeltaTime; yield return CoroutineStatus.Running; diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 89ec3471f..730a7047e 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -247,7 +247,7 @@ namespace Barotrauma.Networking { new GUIMessageBox("Please wait", (allowSpectating) ? - "A round is already running, but you can spectate the game while waiting for a new one to start." : + "A round is already running, but you can spectate the game while waiting for a respawn shuttle or a new round." : "A round is already running and the admin has disabled spectating. You will have to wait for a new round to start."); } @@ -255,7 +255,7 @@ namespace Barotrauma.Networking { GameMain.NetLobbyScreen.Select(); - new GUIMessageBox("Connection timed out", "You were disconnected for too long and your Character was deleted. Please wait for another round to start."); + new GUIMessageBox("Connection timed out", "You were disconnected for too long and your character was deleted. Please wait for another round to start."); } GameMain.NetLobbyScreen.ClearPlayers(); @@ -667,24 +667,65 @@ namespace Barotrauma.Networking List crew = new List(); - int count = inc.ReadByte(); - for (int n = 0; n < count; n++) + byte characterCount = inc.ReadByte(); + for (int i = 0; i < characterCount; i++) { - byte id = inc.ReadByte(); - Character newCharacter = ReadCharacterData(inc, id == myID); + bool isAiCharacter = inc.ReadBoolean(); + ushort id = inc.ReadUInt16(); - if (id != myID) + if (isAiCharacter) { - var characterOwner = otherClients.Find(c => c.ID == id); - if (characterOwner != null) characterOwner.Character = newCharacter; + string configPath = inc.ReadString(); + + Vector2 position = new Vector2(inc.ReadFloat(), inc.ReadFloat()); + + var existingEntity = Entity.FindEntityByID(id); + if (existingEntity is AICharacter && existingEntity.ID == id) + { + continue; + } + + var character = Character.Create(configPath, position, null, true); + if (character != null) character.ID = id; + } + else + { + bool hasOwner = inc.ReadBoolean(); + int ownerId = -1; + if (hasOwner) + { + ownerId = inc.ReadByte(); + } + + Character newCharacter = ReadCharacterData(inc, ownerId == myID); + + if (id != myID) + { + var characterOwner = otherClients.Find(c => c.ID == ownerId); + if (characterOwner != null) characterOwner.Character = newCharacter; + } + + crew.Add(newCharacter); } - crew.Add(newCharacter); + //int count = inc.ReadByte(); + //for (int n = 0; n < count; n++) + //{ + // byte id = inc.ReadByte(); + // Character newCharacter = ReadCharacterData(inc, id == myID); - yield return CoroutineStatus.Running; + // if (id != myID) + // { + // var characterOwner = otherClients.Find(c => c.ID == id); + // if (characterOwner != null) characterOwner.Character = newCharacter; + // } + + // crew.Add(newCharacter); + + // yield return CoroutineStatus.Running; + //} } - - gameStarted = true; + gameStarted = true; endRoundButton.Visible = Voting.AllowEndVoting && myCharacter != null; @@ -783,18 +824,6 @@ namespace Barotrauma.Networking } } - if (respawnManager != null && - respawnManager.CurrentState == RespawnManager.State.Waiting && - respawnManager.CountdownStarted && - myCharacter != null && myCharacter.IsDead) - { - GUI.DrawString(spriteBatch, - new Vector2(GameMain.GraphicsWidth - 300.0f, 20), - "Respawning in " + (int)respawnManager.RespawnTimer + " s", - Color.White, null, 0, GUI.SmallFont); - } - - if (!GameMain.DebugDraw) return; int width = 200, height = 300; @@ -1032,6 +1061,8 @@ namespace Barotrauma.Networking string jobName = inc.ReadString(); JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Name == jobName); + ushort spawnPointID = inc.ReadUInt16(); + if (inc.Position > inc.LengthBits) { return null; @@ -1040,30 +1071,42 @@ namespace Barotrauma.Networking CharacterInfo ch = new CharacterInfo(Character.HumanConfigFile, newName, isFemale ? Gender.Female : Gender.Male, jobPrefab); ch.HeadSpriteId = headSpriteID; - WayPoint closestWaypoint = null; - float closestDist = 0.0f; - foreach (WayPoint wp in WayPoint.WayPointList) - { - if (wp.SpawnType != SpawnType.Human) continue; - float dist = Vector2.Distance(wp.WorldPosition, position); - if (closestWaypoint != null && dist > closestDist) continue; - - closestWaypoint = wp; - closestDist = dist; - continue; - } - Character character = Character.Create(ch, position, !isMyCharacter, false); GameMain.GameSession.CrewManager.characters.Add(character); character.ID = ID; - - character.GiveJobItems(closestWaypoint); + + WayPoint spawnPoint = Entity.FindEntityByID(spawnPointID) as WayPoint; + if (spawnPoint != null) + { + character.GiveJobItems(spawnPoint); + } + + for (int i = 0; i < character.Inventory.Items.Length; i++) + { + ushort itemID = inc.ReadUInt16(); + + System.Diagnostics.Debug.Assert((itemID == 0) == (character.Inventory.Items[i] == null)); + if (character.Inventory.Items[i] == null) continue; + + character.Inventory.Items[i].ID = itemID; + + int containedCount = inc.ReadByte(); + if (containedCount > 0) + { + var containedItems = character.Inventory.Items[i].ContainedItems; + for (int j = 0; j c2.IsNetworkPlayer && + Vector2.Distance(c2.WorldPosition, c.WorldPosition) < ignoreDistance)) + { + new NetworkEvent(NetworkEventType.EntityUpdate, c.ID, false); + } + //todo: take multiple subs into account //Vector2 diff = c.WorldPosition - Submarine.MainSub.WorldPosition; - //if (FarseerPhysics.ConvertUnits.ToSimUnits(diff.Length()) > NetConfig.CharacterIgnoreDistance) continue; - - new NetworkEvent(NetworkEventType.EntityUpdate, c.ID, false); + //if (FarseerPhysics.ConvertUnits.ToSimUnits(diff.Length()) > NetConfig.CharacterIgnoreDistance) continue; } } @@ -760,8 +767,16 @@ namespace Barotrauma.Networking if (recipients.Count == 0) return; - server.SendMessage(msg, recipients, deliveryMethod, 0); - + SendMessage(msg, deliveryMethod, recipients); + } + + private void SendMessage(NetOutgoingMessage msg, NetDeliveryMethod deliveryMethod, List recipients) + { + if (recipients == null) recipients = connectedClients.Select(c => c.Connection).ToList(); + + if (recipients.Count == 0) return; + + server.SendMessage(msg, recipients, deliveryMethod, 0); } private void SendNetworkEvents(List recipients = null) @@ -807,10 +822,21 @@ namespace Barotrauma.Networking { yield return new WaitForSeconds(3.0f); + foreach (Item item in Item.Remover.removedItems) + { + Item.Spawner.spawnItems.Remove(item); + } + + SendItemRemoveMessage(Item.Remover.removedItems, new List() { sender.Connection }); + SendItemSpawnMessage(Item.Spawner.spawnItems, null, new List() { sender.Connection }); + + yield return new WaitForSeconds(1.0f); + //save all the current events to a list and clear them var existingEvents = new List(NetworkEvent.Events); NetworkEvent.Events.Clear(); + foreach (Hull hull in Hull.hullList) { if (!hull.FireSources.Any() && hull.Volume < 0.01f) continue; @@ -824,6 +850,21 @@ namespace Barotrauma.Networking if (c.IsDead) new NetworkEvent(NetworkEventType.KillCharacter, c.ID, false); } + foreach (Structure wall in Structure.WallList) + { + bool takenDamage = false; + for (int i = 0; i StartGame(Submarine selectedSub, GameModePreset selectedMode) { + Item.Spawner.Clear(); + Item.Remover.Clear(); + GameMain.NetLobbyScreen.StartButton.Enabled = false; GUIMessageBox.CloseAll(); - AssignJobs(); + AssignJobs(connectedClients); roundStartSeed = DateTime.Now.Millisecond; Rand.SetSyncedSeed(roundStartSeed); @@ -988,8 +1032,9 @@ namespace Barotrauma.Networking { connectedClients[i].Character = Character.Create( connectedClients[i].characterInfo, assignedWayPoints[i].WorldPosition, true, false); + connectedClients[i].Character.SpawnPoint = assignedWayPoints[i]; connectedClients[i].Character.GiveJobItems(assignedWayPoints[i]); - + GameMain.GameSession.CrewManager.characters.Add(connectedClients[i].Character); } @@ -998,6 +1043,7 @@ namespace Barotrauma.Networking myCharacter = Character.Create(characterInfo, assignedWayPoints[assignedWayPoints.Length - 1].WorldPosition, false, false); Character.Controlled = myCharacter; + myCharacter.SpawnPoint = assignedWayPoints[assignedWayPoints.Length - 1]; myCharacter.GiveJobItems(assignedWayPoints[assignedWayPoints.Length - 1]); GameMain.GameSession.CrewManager.characters.Add(myCharacter); @@ -1005,8 +1051,7 @@ namespace Barotrauma.Networking var startMessage = CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset); SendMessage(startMessage, NetDeliveryMethod.ReliableUnordered); - - + yield return CoroutineStatus.Running; UpdateCrewFrame(); @@ -1065,20 +1110,65 @@ namespace Barotrauma.Networking //msg.Write(GameMain.NetLobbyScreen.GameDuration.TotalMinutes); - List playingClients = connectedClients.FindAll(c => c.Character != null); + var characters = Character.CharacterList.FindAll(c => !(c is AICharacter) || c.SpawnedMidRound); - msg.Write((myCharacter == null) ? (byte)playingClients.Count : (byte)(playingClients.Count + 1)); - foreach (Client client in playingClients) + msg.Write((byte)characters.Count); + foreach (Character c in characters) { - msg.Write(client.ID); - WriteCharacterData(msg, client.Character.Name, client.Character); + msg.Write(c is AICharacter); + msg.Write(c.ID); + + if (c is AICharacter) + { + msg.Write(c.ConfigPath); + + msg.Write(c.Position.X); + msg.Write(c.Position.Y); + } + else + { + Client client = connectedClients.Find(cl => cl.Character == c); + if (client != null) + { + msg.Write(true); + msg.Write(client.ID); + } + else if (myCharacter == c) + { + msg.Write(true); + msg.Write((byte)0); + } + else{ + msg.Write(false); + } + WriteCharacterData(msg, c.Name, c); + } } - if (myCharacter != null) - { - msg.Write((byte)0); - WriteCharacterData(msg, myCharacter.Info.Name, myCharacter); - } + // message.Write((byte)PacketTypes.NewCharacter); + + //message.Write(character.ConfigPath); + + //message.Write(character.ID); + + //message.Write(character.Position.X); + //message.Write(character.Position.Y); + + + //List playingClients = connectedClients.FindAll(c => c.Character != null); + + //msg.Write((myCharacter == null) ? (byte)playingClients.Count : (byte)(playingClients.Count + 1)); + //foreach (Client client in playingClients) + //{ + // msg.Write(client.ID); + // WriteCharacterData(msg, client.Character.Name, client.Character); + //} + + //if (myCharacter != null) + //{ + // msg.Write((byte)0); + // WriteCharacterData(msg, myCharacter.Info.Name, myCharacter); + //} return msg; } @@ -1096,7 +1186,7 @@ namespace Barotrauma.Networking GameMain.GameSession.gameMode.End(endMessage); - if (autoRestart) AutoRestartTimer = 20.0f; + if (autoRestart) AutoRestartTimer = AutoRestartInterval; if (SaveServerLogs) log.Save(); @@ -1110,6 +1200,13 @@ namespace Barotrauma.Networking GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; + Item.Spawner.Clear(); + Item.Remover.Clear(); + +#if DEBUG + messageCount.Clear(); +#endif + respawnManager = null; gameStarted = false; @@ -1357,14 +1454,6 @@ namespace Barotrauma.Networking log.LogFrame.Draw(spriteBatch); } - if (respawnManager != null && respawnManager.CurrentState == RespawnManager.State.Waiting && respawnManager.CountdownStarted) - { - GUI.DrawString(spriteBatch, - new Vector2(GameMain.GraphicsWidth - 500.0f, 20), - "Respawning in " + (int)respawnManager.RespawnTimer + " s", - Color.White, null, 0, GUI.SmallFont); - } - if (!ShowNetStats) return; int width = 200, height = 300; @@ -1656,11 +1745,38 @@ namespace Barotrauma.Networking message.Write(character.WorldPosition.X); message.Write(character.WorldPosition.Y); - + message.Write(character.Info.Job.Name); + message.Write(character.SpawnPoint == null ? (ushort)0 : character.SpawnPoint.ID); + + for (int i = 0; i < character.Inventory.Items.Length; i++) + { + if (character.Inventory.Items[i] == null) + { + message.Write((ushort)0); + } + else + { + message.Write(character.Inventory.Items[i].ID); + var containedItems = character.Inventory.Items[i].ContainedItems; + if (containedItems == null || !containedItems.Any(c => c != null)) + { + message.Write((byte)0); + } + else + { + message.Write((byte)containedItems.Length); + for (int j = 0; j < containedItems.Length; j++) + { + message.Write(containedItems[j] == null ? (ushort)0 : containedItems[j].ID); + } + } + } + + } } - public void SendCharacterSpawnMessage(Character character) + public void SendCharacterSpawnMessage(Character character, List recipients = null) { NetOutgoingMessage message = server.CreateMessage(); message.Write((byte)PacketTypes.NewCharacter); @@ -1672,35 +1788,35 @@ namespace Barotrauma.Networking message.Write(character.Position.X); message.Write(character.Position.Y); - SendMessage(message, NetDeliveryMethod.ReliableUnordered); + SendMessage(message, NetDeliveryMethod.ReliableUnordered, recipients); } - public void SendItemSpawnMessage(List items, List inventories = null) + public void SendItemSpawnMessage(List items, List inventories = null, List recipients = null) { if (items == null || !items.Any()) return; NetOutgoingMessage message = server.CreateMessage(); message.Write((byte)PacketTypes.NewItem); - Item.Spawner.FillNetworkData(message, items, inventories); + Item.Spawner.FillNetworkData(message, items, inventories); - SendMessage(message, NetDeliveryMethod.ReliableUnordered); + SendMessage(message, NetDeliveryMethod.ReliableOrdered, recipients); } - public void SendItemRemoveMessage(List items) + public void SendItemRemoveMessage(List items, List recipients = null) { if (items == null || !items.Any()) return; NetOutgoingMessage message = server.CreateMessage(); - + message.Write((byte)PacketTypes.RemoveItem); Item.Remover.FillNetworkData(message, items); - SendMessage(message, NetDeliveryMethod.ReliableUnordered); + SendMessage(message, NetDeliveryMethod.ReliableOrdered, recipients); } - private void AssignJobs() + public void AssignJobs(List unassigned) { - List unassigned = new List(connectedClients); + unassigned = new List(unassigned); int[] assignedClientCount = new int[JobPrefab.List.Count]; @@ -1709,6 +1825,15 @@ namespace Barotrauma.Networking assignedClientCount[JobPrefab.List.FindIndex(jp => jp == GameMain.NetLobbyScreen.JobPreferences[0])]=1; } + foreach (Client c in connectedClients) + { + if (unassigned.Contains(c)) continue; + if (c.Character == null || !c.Character.IsDead) continue; + + assignedClientCount[JobPrefab.List.IndexOf(c.Character.Info.Job.Prefab)]++; + + } + //if any of the players has chosen a job that is Always Allowed, give them that job for (int i = unassigned.Count - 1; i >= 0; i--) { @@ -1827,7 +1952,7 @@ namespace Barotrauma.Networking { msg.Write(Rand.Int(2) == 0); } - SendMessage(msg, (Rand.Int(2) == 0) ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable, null); + SendMessage(msg, (Rand.Int(2) == 0) ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable); } diff --git a/Subsurface/Source/Networking/NetStats.cs b/Subsurface/Source/Networking/NetStats.cs index 7a9de4c1b..8ef1a651a 100644 --- a/Subsurface/Source/Networking/NetStats.cs +++ b/Subsurface/Source/Networking/NetStats.cs @@ -86,7 +86,7 @@ namespace Barotrauma.Networking #if DEBUG int y = 10; - foreach (KeyValuePair msgBytesSent in server.messageCount.OrderBy(key => key.Value)) + foreach (KeyValuePair msgBytesSent in server.messageCount.OrderBy(key => -key.Value)) { spriteBatch.DrawString(GUI.SmallFont, msgBytesSent.Key + ": " + MathUtils.GetBytesReadable(msgBytesSent.Value), new Vector2(rect.Right - 200, rect.Y + y), Color.Red); diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index db16a2e4b..733a3ceac 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -385,6 +385,31 @@ namespace Barotrauma.Networking "Votes (y/n): " + EndVoteCount + "/" + (EndVoteMax - EndVoteCount), Color.White, null, 0, GUI.SmallFont); } } + + if (respawnManager != null) + { + string respawnInfo = ""; + + if (respawnManager.CurrentState == RespawnManager.State.Waiting && + respawnManager.CountdownStarted && + myCharacter != null && myCharacter.IsDead) + { + respawnInfo = respawnManager.RespawnTimer <= 0.0f ? "" : "Respawning in " + ToolBox.SecondsToReadableTime(respawnManager.RespawnTimer); + + } + else if (respawnManager.CurrentState == RespawnManager.State.Transporting) + { + respawnInfo = respawnManager.TransportTimer <= 0.0f ? "" : "Shuttle leaving in " + ToolBox.SecondsToReadableTime(respawnManager.TransportTimer); + } + + if (!string.IsNullOrEmpty(respawnInfo)) + { + GUI.DrawString(spriteBatch, + new Vector2(GameMain.GraphicsWidth - 300.0f, 20), + respawnInfo, Color.White, null, 0, GUI.SmallFont); + } + + } } public virtual bool SelectCrewCharacter(GUIComponent component, object obj) diff --git a/Subsurface/Source/Networking/RespawnManager.cs b/Subsurface/Source/Networking/RespawnManager.cs index 4cc78d598..50e525c7a 100644 --- a/Subsurface/Source/Networking/RespawnManager.cs +++ b/Subsurface/Source/Networking/RespawnManager.cs @@ -12,10 +12,9 @@ namespace Barotrauma.Networking { class RespawnManager { - const int MinCharactersToRespawn = 1; - - const float RespawnInterval = 20.0f; - + private readonly float respawnInterval; + private float maxTransportTime; + public enum State { Waiting, @@ -31,11 +30,22 @@ namespace Barotrauma.Networking private Steering shuttleSteering; private List shuttleDoors; + /// + /// How long until the shuttle is dispatched with respawned characters + /// public float RespawnTimer { get { return respawnTimer; } } + /// + /// how long until the shuttle starts heading back out of the level + /// + public float TransportTimer + { + get { return shuttleTransportTimer; } + } + public bool CountdownStarted { get; @@ -47,18 +57,20 @@ namespace Barotrauma.Networking get { return state; } } - private float respawnTimer, shuttleReturnTimer; + private float respawnTimer, shuttleReturnTimer, shuttleTransportTimer; - public RespawnManager(NetworkMember server) + private float updateReturnTimer; + + public RespawnManager(NetworkMember networkMember) { - this.networkMember = server; + this.networkMember = networkMember; respawnShuttle = new Submarine("Submarines/Shuttle Mark I.sub"); respawnShuttle.Load(false); - ResetShuttlePos(); + ResetShuttle(); - respawnShuttle.GodMode = true; + //respawnShuttle.GodMode = true; shuttleDoors = new List(); foreach (Item item in Item.ItemList) @@ -74,7 +86,14 @@ namespace Barotrauma.Networking shuttleSteering.TargetPosition = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition); - respawnTimer = RespawnInterval; + var server = networkMember as GameServer; + if (server != null) + { + respawnInterval = server.RespawnInterval; + maxTransportTime = server.MaxTransportTime; + } + + respawnTimer = respawnInterval; } private List GetClientsToRespawn() @@ -116,20 +135,33 @@ namespace Barotrauma.Networking shuttleSteering.MaintainPos = false; int characterToRespawnCount = GetClientsToRespawn().Count; - if (server.Character != null && server.Character.IsDead) characterToRespawnCount++; + 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); - bool startCountdown = characterToRespawnCount >= MinCharactersToRespawn; - - if (startCountdown && !CountdownStarted) server.SendRespawnManagerMsg(); - - CountdownStarted = startCountdown; + if (startCountdown) + { + if (!CountdownStarted) + { + CountdownStarted = true; + server.SendRespawnManagerMsg(); + } + } + else + { + CountdownStarted = false; + } if (!CountdownStarted) return; respawnTimer -= deltaTime; if (respawnTimer <= 0.0f) { - respawnTimer = RespawnInterval; + respawnTimer = respawnInterval; Respawn(); } @@ -137,27 +169,61 @@ namespace Barotrauma.Networking private void UpdateTransporting(float deltaTime) { + shuttleTransportTimer -= deltaTime; + + if (shuttleReturnTimer + deltaTime > 15.0f && shuttleReturnTimer <= 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 (Character.CharacterList.Any(c => c.Submarine == respawnShuttle && !c.IsDead)) return; - shuttleReturnTimer += deltaTime; - if (shuttleReturnTimer > 10.0f) + //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.SendRespawnManagerMsg(); - shuttleReturnTimer = 0.0f; + shuttleReturnTimer = maxTransportTime; + shuttleTransportTimer = maxTransportTime; } + + //shuttleReturnTimer += deltaTime; + //if (shuttleReturnTimer > 10.0f) + //{ + // state = State.Returning; + + // server.SendRespawnManagerMsg(); + // shuttleReturnTimer = 0.0f; + //} } private void UpdateReturning(float deltaTime) { - shuttleReturnTimer += deltaTime; - - if (shuttleReturnTimer > 1.0f) + //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 > 10.0f) { + updateReturnTimer = 0.0f; + shuttleSteering.AutoPilot = true; shuttleSteering.MaintainPos = false; @@ -166,31 +232,41 @@ namespace Barotrauma.Networking 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() != null); + dockingPorts.ForEach(d => d.GetComponent().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 (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)) + + 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"); - CoroutineManager.StartCoroutine( - ForceShuttleToPos(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + 1000.0f), 100.0f), "forcepos"); - - //string msg = "The transportation shuttle has returned to "; - //server.SendChatMessage(ChatMessage.Create("", msg, ChatMessageType.Server, null), server.ConnectedClients); - - + ResetShuttle(); + state = State.Waiting; - respawnTimer = RespawnInterval; + respawnTimer = respawnInterval; server.SendRespawnManagerMsg(); } - - shuttleReturnTimer = 0.0f; } } @@ -201,7 +277,7 @@ namespace Barotrauma.Networking state = State.Transporting; - ResetShuttlePos(); + ResetShuttle(); server.SendChatMessage(ChatMessage.Create("", "Transportation shuttle dispatched", ChatMessageType.Server, null), server.ConnectedClients); @@ -229,8 +305,44 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Success; } - private void ResetShuttlePos() + private void ResetShuttle() { + shuttleTransportTimer = maxTransportTime; + shuttleReturnTimer = maxTransportTime; + + 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; + //if (networkMember.Character == c) networkMember.Character = null; + c.Enabled = false; + + c.Kill(CauseOfDeath.Damage, true); + } + } + respawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + respawnShuttle.Borders.Height)); respawnShuttle.Velocity = Vector2.Zero; @@ -247,8 +359,13 @@ namespace Barotrauma.Networking switch (state) { case State.Transporting: + msg.Write(maxTransportTime); + var clients = GetClientsToRespawn(); + server.AssignJobs(clients); + clients.ForEach(c => c.characterInfo.Job = new Job(c.assignedJob)); + List characterInfos = clients.Select(c => c.characterInfo).ToList(); if (server.Character != null && server.Character.IsDead) characterInfos.Add(server.CharacterInfo); @@ -271,6 +388,7 @@ namespace Barotrauma.Networking Character.Controlled = character; } + character.SpawnPoint = waypoints[i]; character.GiveJobItems(waypoints[i]); GameMain.GameSession.CrewManager.characters.Add(character); @@ -280,8 +398,14 @@ namespace Barotrauma.Networking break; case State.Waiting: + msg.Write(CountdownStarted); msg.Write(respawnTimer); break; + case State.Returning: + //CoroutineManager.StopCoroutines("forcepos"); + //CoroutineManager.StartCoroutine( + // ForceShuttleToPos(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + 1000.0f), 100.0f), "forcepos"); + break; } } @@ -292,8 +416,10 @@ namespace Barotrauma.Networking switch (state) { case State.Transporting: + maxTransportTime = inc.ReadSingle(); + CountdownStarted = false; - ResetShuttlePos(); + ResetShuttle(); var client = networkMember as GameClient; @@ -310,9 +436,16 @@ namespace Barotrauma.Networking break; case State.Waiting: CountdownStarted = true; + + respawnShuttle.SubBody.Body.RestoreCollisionWith(Level.Loaded.ShaftBodies[0]); + + ResetShuttle(); + respawnTimer = inc.ReadSingle(); break; case State.Returning: + respawnShuttle.SubBody.Body.IgnoreCollisionWith(Level.Loaded.ShaftBodies[0]); + CountdownStarted = false; break; }