From 6736f1dd828f08abe6ac20c4cf089205a0f16917 Mon Sep 17 00:00:00 2001 From: Regalis Date: Fri, 23 Oct 2015 19:28:21 +0300 Subject: [PATCH] Remove items in dummycharacter inventory in editmapscreen, important character updates a separate networkeventtype --- Subsurface/Properties/AssemblyInfo.cs | 4 +- Subsurface/Source/Characters/AICharacter.cs | 229 +++--- Subsurface/Source/Characters/Attack.cs | 4 + Subsurface/Source/Characters/Character.cs | 663 +++++++++--------- Subsurface/Source/Characters/Ragdoll.cs | 20 +- Subsurface/Source/DebugConsole.cs | 10 +- .../Source/Items/Components/ItemContainer.cs | 11 + Subsurface/Source/Map/Hull.cs | 1 - Subsurface/Source/Map/MapEntity.cs | 12 +- Subsurface/Source/Map/Submarine.cs | 2 +- Subsurface/Source/Networking/GameClient.cs | 7 +- Subsurface/Source/Networking/GameServer.cs | 9 +- Subsurface/Source/Networking/NetConfig.cs | 2 + Subsurface/Source/Networking/NetworkEvent.cs | 3 +- Subsurface/Source/Screens/EditMapScreen.cs | 7 + Subsurface/changelog.txt | 36 + Subsurface_Solution.v12.suo | Bin 835584 -> 804352 bytes 17 files changed, 545 insertions(+), 475 deletions(-) diff --git a/Subsurface/Properties/AssemblyInfo.cs b/Subsurface/Properties/AssemblyInfo.cs index caaef8883..744e0f391 100644 --- a/Subsurface/Properties/AssemblyInfo.cs +++ b/Subsurface/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.2.3.0")] -[assembly: AssemblyFileVersion("0.2.3.0")] +[assembly: AssemblyVersion("0.2.3.1")] +[assembly: AssemblyFileVersion("0.2.3.1")] diff --git a/Subsurface/Source/Characters/AICharacter.cs b/Subsurface/Source/Characters/AICharacter.cs index 6ea38dab6..30f24bc05 100644 --- a/Subsurface/Source/Characters/AICharacter.cs +++ b/Subsurface/Source/Characters/AICharacter.cs @@ -85,50 +85,43 @@ namespace Barotrauma public override bool FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data) { - if (type == NetworkEventType.KillCharacter) + switch (type) { - return true; - } + case NetworkEventType.KillCharacter: + return true; + case NetworkEventType.ImportantEntityUpdate: + int i = 0; + foreach (Limb limb in AnimController.Limbs) + { + if (limb.ignoreCollisions) continue; - message.Write((float)NetTime.Now); + message.Write(limb.body.SimPosition.X); + message.Write(limb.body.SimPosition.Y); - message.Write(LargeUpdateTimer <= 0); + message.Write(limb.body.Rotation); + i++; + } + + message.WriteRangedSingle(MathHelper.Clamp(AnimController.StunTimer, 0.0f, 60.0f), 0.0f, 60.0f, 8); + message.Write((byte)((health / maxHealth) * 255.0f)); + + aiController.FillNetworkData(message); + return true; + case NetworkEventType.EntityUpdate: + message.Write((float)NetTime.Now); + + message.Write(AnimController.TargetDir == Direction.Right); + message.WriteRangedSingle(MathHelper.Clamp(AnimController.TargetMovement.X, -10.0f, 10.0f), -10.0f, 10.0f, 16); + message.WriteRangedSingle(MathHelper.Clamp(AnimController.TargetMovement.Y, -10.0f, 10.0f), -10.0f, 10.0f, 16); - message.Write(AnimController.TargetDir == Direction.Right); - message.WriteRangedSingle(MathHelper.Clamp(AnimController.TargetMovement.X, -10.0f, 10.0f), -10.0f, 10.0f, 16); - message.WriteRangedSingle(MathHelper.Clamp(AnimController.TargetMovement.Y, -10.0f, 10.0f), -10.0f, 10.0f, 16); + message.Write(AnimController.RefLimb.SimPosition.X); + message.Write(AnimController.RefLimb.SimPosition.Y); + + message.Write(AnimController.RefLimb.LinearVelocity.X); + message.Write(AnimController.RefLimb.LinearVelocity.Y); + return true; + } - if (LargeUpdateTimer <= 0) - { - int i = 0; - foreach (Limb limb in AnimController.Limbs) - { - message.Write(limb.body.SimPosition.X); - message.Write(limb.body.SimPosition.Y); - - message.Write(limb.body.Rotation); - i++; - } - - message.WriteRangedSingle(MathHelper.Clamp(AnimController.StunTimer, 0.0f, 60.0f), 0.0f, 60.0f, 8); - message.Write((byte)((health / maxHealth) * 255.0f)); - - aiController.FillNetworkData(message); - - LargeUpdateTimer = 50; - } - else - { - message.Write(AnimController.RefLimb.SimPosition.X); - message.Write(AnimController.RefLimb.SimPosition.Y); - - message.Write(AnimController.RefLimb.LinearVelocity.X); - message.Write(AnimController.RefLimb.LinearVelocity.Y); - - LargeUpdateTimer = Math.Max(0, LargeUpdateTimer - 1); - } - - return true; } @@ -136,115 +129,91 @@ namespace Barotrauma { Enabled = true; - if (type == NetworkEventType.KillCharacter) + switch (type) { - Kill(true); - return; - } + case NetworkEventType.KillCharacter: + Kill(CauseOfDeath.Damage, true); + return; + case NetworkEventType.ImportantEntityUpdate: + foreach (Limb limb in AnimController.Limbs) + { + if (limb.ignoreCollisions) continue; - float sendingTime = 0.0f; - Vector2 targetMovement = Vector2.Zero; - bool targetDir = false; + Vector2 limbPos = limb.SimPosition; + float rotation = limb.Rotation; - bool isLargeUpdate; + try + { + limbPos.X = message.ReadFloat(); + limbPos.Y = message.ReadFloat(); - - try - { - sendingTime = message.ReadFloat(); - isLargeUpdate = message.ReadBoolean(); - } + rotation = message.ReadFloat(); + } + catch + { + return; + } - catch - { - return; - } + if (limb.body != null) + { + limb.body.TargetVelocity = limb.body.LinearVelocity; + limb.body.TargetPosition = limbPos;// +vel * (float)(deltaTime / 60.0); + limb.body.TargetRotation = rotation;// +angularVel * (float)(deltaTime / 60.0); + limb.body.TargetAngularVelocity = limb.body.AngularVelocity; + } + } - if (sendingTime <= LastNetworkUpdate) return; - - try - { - targetDir = message.ReadBoolean(); - targetMovement.X = message.ReadRangedSingle(-10.0f, 10.0f, 16); - targetMovement.Y = message.ReadRangedSingle(-10.0f, 10.0f, 16); - - } - catch - { - return; - } - - AnimController.TargetDir = (targetDir) ? Direction.Right : Direction.Left; - AnimController.TargetMovement = targetMovement; - - - if (isLargeUpdate) - { - foreach (Limb limb in AnimController.Limbs) - { - Vector2 pos = Vector2.Zero, vel = Vector2.Zero; - float rotation = 0.0f; + float newStunTimer = 0.0f, newHealth = 0.0f; try { + newStunTimer = message.ReadRangedSingle(0.0f, 60.0f, 8); + newHealth = (message.ReadByte() / 255.0f) * maxHealth; + } + catch { return; } + + AnimController.StunTimer = newStunTimer; + health = newHealth; + + aiController.ReadNetworkData(message); + return; + case NetworkEventType.EntityUpdate: + float sendingTime = 0.0f; + Vector2 targetMovement = Vector2.Zero; + bool targetDir = false; + + sendingTime = message.ReadFloat(); + if (sendingTime <= LastNetworkUpdate) return; + + Vector2 pos = Vector2.Zero, vel = Vector2.Zero; + + try + { + targetDir = message.ReadBoolean(); + targetMovement.X = message.ReadRangedSingle(-10.0f, 10.0f, 16); + targetMovement.Y = message.ReadRangedSingle(-10.0f, 10.0f, 16); + pos.X = message.ReadFloat(); pos.Y = message.ReadFloat(); - rotation = message.ReadFloat(); + vel.X = message.ReadFloat(); + vel.Y = message.ReadFloat(); + } - catch + catch (Exception e) { return; } - if (limb.body != null) - { - limb.body.TargetVelocity = limb.body.LinearVelocity; - limb.body.TargetPosition = pos;// +vel * (float)(deltaTime / 60.0); - limb.body.TargetRotation = rotation;// +angularVel * (float)(deltaTime / 60.0); - limb.body.TargetAngularVelocity = limb.body.AngularVelocity; - } - } - - float newStunTimer = 0.0f, newHealth = 0.0f; - - try - { - newStunTimer = message.ReadRangedSingle(0.0f, 60.0f, 8); - newHealth = (message.ReadByte() / 255.0f) * maxHealth; - } - catch { return; } - - AnimController.StunTimer = newStunTimer; - Health = newHealth; - - LargeUpdateTimer = 1; - - aiController.ReadNetworkData(message); + AnimController.TargetDir = (targetDir) ? Direction.Right : Direction.Left; + AnimController.TargetMovement = targetMovement; + + AnimController.RefLimb.body.TargetPosition = pos; + AnimController.RefLimb.body.TargetVelocity = vel; + + LastNetworkUpdate = sendingTime; + return; } - else - { - Vector2 pos = Vector2.Zero, vel = Vector2.Zero; - try - { - pos.X = message.ReadFloat(); - pos.Y = message.ReadFloat(); - - vel.X = message.ReadFloat(); - vel.Y = message.ReadFloat(); - } - - catch { return; } - - //error - AnimController.RefLimb.body.TargetPosition = pos; - AnimController.RefLimb.body.TargetVelocity = vel; - - LargeUpdateTimer = 0; - } - - LastNetworkUpdate = sendingTime; - } diff --git a/Subsurface/Source/Characters/Attack.cs b/Subsurface/Source/Characters/Attack.cs index 68e217394..26a19cc89 100644 --- a/Subsurface/Source/Characters/Attack.cs +++ b/Subsurface/Source/Characters/Attack.cs @@ -6,6 +6,10 @@ using System.Xml.Linq; namespace Barotrauma { + enum CauseOfDeath + { + Damage, Bloodloss, Pressure, Suffocation, Drowning + } public enum DamageType { None, Blunt, Slash } diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index d8e9835a5..961b6d05b 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -15,9 +15,11 @@ using System.Xml.Linq; namespace Barotrauma { - + class Character : Entity, IDamageable, IPropertyObject { + public static string[] DeathMsg = new string[Enum.GetNames(typeof(CauseOfDeath)).Length]; + public static List CharacterList = new List(); public static Queue NewCharacterQueue = new Queue(); @@ -40,12 +42,13 @@ namespace Barotrauma } public readonly bool IsNetworkPlayer; + private int importantUpdateTimer; private CharacterInventory inventory; public float LastNetworkUpdate; - public int LargeUpdateTimer; + //public int LargeUpdateTimer; public readonly Dictionary Properties; public Dictionary ObjectProperties @@ -173,7 +176,7 @@ namespace Barotrauma set { oxygen = MathHelper.Clamp(value, 0.0f, 100.0f); - if (oxygen == 0.0f) Kill(); + if (oxygen == 0.0f) Kill(CauseOfDeath.Suffocation); } } @@ -185,17 +188,14 @@ namespace Barotrauma public float Health { - get - { - return health; - } - set - { + get { return health; } + set + { health = MathHelper.Clamp(value, 0.0f, maxHealth); - if (health == 0.0f) Kill(); + if (health <= 0.0f) Kill(CauseOfDeath.Damage); } - } - + } + public float MaxHealth { get { return maxHealth; } @@ -221,47 +221,6 @@ namespace Barotrauma { get { return selectedItems; } } - - public bool HasSelectedItem(Item item) - { - return selectedItems.Contains(item); - } - - public bool TrySelectItem(Item item) - { - bool rightHand = ((CharacterInventory)inventory).IsInLimbSlot(item, LimbSlot.RightHand); - bool leftHand = ((CharacterInventory)inventory).IsInLimbSlot(item, LimbSlot.LeftHand); - - bool selected = false; - if (rightHand && SelectedItems[0] == null) - { - selectedItems[0] = item; - selected = true; - } - if (leftHand && SelectedItems[1] == null) - { - selectedItems[1] = item; - selected = true; - } - - return selected; - } - - public bool TrySelectItem(Item item, int index) - { - if (selectedItems[index] != null) return false; - - selectedItems[index] = item; - return true; - } - - public void DeselectItem(Item item) - { - for (int i = 0; i < selectedItems.Length; i++) - { - if (selectedItems[i] == item) selectedItems[i] = null; - } - } public Item SelectedConstruction { @@ -294,6 +253,15 @@ namespace Barotrauma get { return ConvertUnits.ToDisplayUnits(AnimController.Limbs[0].SimPosition); } } + static Character() + { + DeathMsg[(int)CauseOfDeath.Damage] = "died"; + DeathMsg[(int)CauseOfDeath.Bloodloss] = "bled out"; + DeathMsg[(int)CauseOfDeath.Drowning] = "drowned"; + DeathMsg[(int)CauseOfDeath.Suffocation] = "suffocated"; + DeathMsg[(int)CauseOfDeath.Pressure] = "been crushed by water pressure"; + } + public Character(string file) : this(file, Vector2.Zero, null) { } @@ -559,7 +527,7 @@ namespace Barotrauma if (selectedConstruction != null) { if (GetInputState(InputType.ActionHeld)) selectedConstruction.Use(deltaTime, this); - if (GetInputState(InputType.SecondaryHeld)) selectedConstruction.SecondaryUse(deltaTime, this); + if (selectedConstruction != null && GetInputState(InputType.SecondaryHeld)) selectedConstruction.SecondaryUse(deltaTime, this); } if (IsNetworkPlayer) @@ -571,6 +539,56 @@ namespace Barotrauma } } + public void CreateUpdateNetworkEvent(bool isClient) + { + new NetworkEvent(importantUpdateTimer <= 0 ? NetworkEventType.ImportantEntityUpdate : NetworkEventType.EntityUpdate, ID, isClient); + + importantUpdateTimer -= 1; + if (importantUpdateTimer < 0) importantUpdateTimer = (this is AICharacter) ? 40 : 25; + } + + + public bool HasSelectedItem(Item item) + { + return selectedItems.Contains(item); + } + + public bool TrySelectItem(Item item) + { + bool rightHand = ((CharacterInventory)inventory).IsInLimbSlot(item, LimbSlot.RightHand); + bool leftHand = ((CharacterInventory)inventory).IsInLimbSlot(item, LimbSlot.LeftHand); + + bool selected = false; + if (rightHand && SelectedItems[0] == null) + { + selectedItems[0] = item; + selected = true; + } + if (leftHand && SelectedItems[1] == null) + { + selectedItems[1] = item; + selected = true; + } + + return selected; + } + + public bool TrySelectItem(Item item, int index) + { + if (selectedItems[index] != null) return false; + + selectedItems[index] = item; + return true; + } + + public void DeselectItem(Item item) + { + for (int i = 0; i < selectedItems.Length; i++) + { + if (selectedItems[i] == item) selectedItems[i] = null; + } + } + private Item FindClosestItem(Vector2 mouseSimPos) { Limb torso = AnimController.GetLimb(LimbType.Torso); @@ -824,6 +842,7 @@ namespace Barotrauma } Health = health - bleeding * deltaTime; + if (health <= 0.0f) Kill(CauseOfDeath.Bloodloss, false); } @@ -960,8 +979,14 @@ namespace Barotrauma selectedConstruction = null; } - private void Implode() + private void Implode(bool isNetworkMessage = false) { + if (!isNetworkMessage) + { + //if the game is run by a client, characters are only killed when the server says so + if (GameMain.Client != null && GameMain.Server == null) return; + } + Vector2 centerOfMass = AnimController.GetCenterOfMass(); health = 0.0f; @@ -992,7 +1017,7 @@ namespace Barotrauma { joint.LimitEnabled = false; } - Kill(); + Kill(CauseOfDeath.Pressure, isNetworkMessage); } private IEnumerable DeathAnim(Camera cam) @@ -1040,7 +1065,7 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - public void Kill(bool networkMessage = false) + public void Kill(CauseOfDeath causeOfDeath, bool networkMessage = false) { if (isDead) return; @@ -1077,7 +1102,7 @@ namespace Barotrauma if (GameMain.Server != null) { - new NetworkEvent(NetworkEventType.KillCharacter, ID, false, true); + new NetworkEvent(NetworkEventType.KillCharacter, ID, false, causeOfDeath); } if (GameMain.GameSession != null) @@ -1088,313 +1113,311 @@ namespace Barotrauma public override bool FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data) { - if (type == NetworkEventType.PickItem) + switch (type) { - int[] pickData = (int[])data; - if (pickData.Length != 3) return false; + case NetworkEventType.PickItem: + int[] pickData = (int[])data; + if (pickData.Length != 3) return false; - message.Write((ushort)pickData[0]); - message.Write((int)pickData[1] == 1); - message.Write((int)pickData[2] == 1); - message.WritePadBits(); + message.Write((ushort)pickData[0]); + message.Write((int)pickData[1] == 1); + message.Write((int)pickData[2] == 1); + message.WritePadBits(); - return true; - } - else if (type == NetworkEventType.SelectCharacter) - { - message.Write((ushort)data); - return true; - } - else if (type == NetworkEventType.KillCharacter) - { - return true; - } - else if (type == NetworkEventType.InventoryUpdate) - { - if (inventory == null) return false; - return inventory.FillNetworkData(NetworkEventType.InventoryUpdate, message, data); - } + return true; + case NetworkEventType.SelectCharacter: + message.Write((ushort)data); + return true; + case NetworkEventType.KillCharacter: + CauseOfDeath causeOfDeath = CauseOfDeath.Damage; + try + { + causeOfDeath = (CauseOfDeath)data; + } + catch + { + causeOfDeath = CauseOfDeath.Damage; + } - var hasInputs = - (GetInputState(InputType.Left) || - GetInputState(InputType.Right) || - GetInputState(InputType.Up) || - GetInputState(InputType.Down) || - GetInputState(InputType.ActionHeld) || - GetInputState(InputType.SecondaryHeld)) || LargeUpdateTimer <= 0; - - message.Write(hasInputs); - message.Write((float)NetTime.Now); + message.Write((byte)causeOfDeath); - if (!hasInputs) return true; + return true; + case NetworkEventType.InventoryUpdate: + if (inventory == null) return false; + return inventory.FillNetworkData(NetworkEventType.InventoryUpdate, message, data); + case NetworkEventType.ImportantEntityUpdate: + int i = 0; + foreach (Limb limb in AnimController.Limbs) + { + message.Write(limb.body.SimPosition.X); + message.Write(limb.body.SimPosition.Y); - - // Write byte = move direction - //message.WriteRangedSingle(MathHelper.Clamp(AnimController.TargetMovement.X, -10.0f, 10.0f), -10.0f, 10.0f, 8); - //message.WriteRangedSingle(MathHelper.Clamp(AnimController.TargetMovement.Y, -10.0f, 10.0f), -10.0f, 10.0f, 8); - - message.Write(keys[(int)InputType.ActionHeld].Dequeue); + //message.Write(limb.body.LinearVelocity.X); + //message.Write(limb.body.LinearVelocity.Y); - bool secondaryHeld = keys[(int)InputType.SecondaryHeld].Dequeue; - message.Write(secondaryHeld); + message.Write(limb.body.Rotation); + //message.WriteRangedSingle(MathHelper.Clamp(limb.body.AngularVelocity, -10.0f, 10.0f), -10.0f, 10.0f, 8); + i++; + } + + message.WriteRangedSingle(MathHelper.Clamp(AnimController.StunTimer,0.0f,60.0f), 0.0f, 60.0f, 8); + message.Write((byte)((health/maxHealth)*255.0f)); + message.Write((byte)(MathHelper.Clamp(oxygen * 2.55f, 0.0f, 255.0f))); + + + return true; + case NetworkEventType.EntityUpdate: + var hasInputs = + GetInputState(InputType.Left) || + GetInputState(InputType.Right) || + GetInputState(InputType.Up) || + GetInputState(InputType.Down) || + GetInputState(InputType.ActionHeld) || + GetInputState(InputType.SecondaryHeld); + + message.Write(hasInputs); + message.Write((float)NetTime.Now); + + if (!hasInputs) return true; + + message.Write(keys[(int)InputType.ActionHeld].Dequeue); + + bool secondaryHeld = keys[(int)InputType.SecondaryHeld].Dequeue; + message.Write(secondaryHeld); - message.Write(keys[(int)InputType.Left].Dequeue); - message.Write(keys[(int)InputType.Right].Dequeue); + message.Write(keys[(int)InputType.Left].Dequeue); + message.Write(keys[(int)InputType.Right].Dequeue); - message.Write(keys[(int)InputType.Up].Dequeue); - message.Write(keys[(int)InputType.Down].Dequeue); + message.Write(keys[(int)InputType.Up].Dequeue); + message.Write(keys[(int)InputType.Down].Dequeue); - message.Write(keys[(int)InputType.Run].Dequeue); + message.Write(keys[(int)InputType.Run].Dequeue); - if (secondaryHeld) - { - message.Write(cursorPosition.X); - message.Write(cursorPosition.Y); + if (secondaryHeld) + { + message.Write(cursorPosition.X); + message.Write(cursorPosition.Y); + } + else + { + message.Write(AnimController.Dir > 0.0f); + } + + message.Write(AnimController.RefLimb.SimPosition.X); + message.Write(AnimController.RefLimb.SimPosition.Y); + + return true; + default: +#if DEBUG + DebugConsole.ThrowError("Character "+this+" tried to fill a networkevent of the wrong type: "+type); +#endif + return false; } - else - { - message.Write(AnimController.Dir > 0.0f); - } - - message.Write(LargeUpdateTimer <= 0); - - if (LargeUpdateTimer<=0) - { - int i = 0; - foreach (Limb limb in AnimController.Limbs) - { - message.Write(limb.body.SimPosition.X); - message.Write(limb.body.SimPosition.Y); - - //message.Write(limb.body.LinearVelocity.X); - //message.Write(limb.body.LinearVelocity.Y); - - message.Write(limb.body.Rotation); - //message.WriteRangedSingle(MathHelper.Clamp(limb.body.AngularVelocity, -10.0f, 10.0f), -10.0f, 10.0f, 8); - i++; - } - - message.WriteRangedSingle(MathHelper.Clamp(AnimController.StunTimer,0.0f,60.0f), 0.0f, 60.0f, 8); - message.Write((byte)((health/maxHealth)*255.0f)); - - LargeUpdateTimer = 30; - } - else - { - message.Write(AnimController.RefLimb.SimPosition.X); - message.Write(AnimController.RefLimb.SimPosition.Y); - - LargeUpdateTimer = Math.Max(0, LargeUpdateTimer-1); - } - - return true; } public override void ReadNetworkData(NetworkEventType type, NetIncomingMessage message) { Enabled = true; - if (type == NetworkEventType.PickItem) + switch (type) { - System.Diagnostics.Debug.WriteLine("**************** PickItem networkevent received"); + case NetworkEventType.PickItem: + System.Diagnostics.Debug.WriteLine("**************** PickItem networkevent received"); - ushort itemId = 0; + ushort itemId = message.ReadUInt16(); - itemId = message.ReadUInt16(); + bool pickHit = message.ReadBoolean(); + bool actionHit = message.ReadBoolean(); - bool pickHit = message.ReadBoolean(); - bool actionHit = message.ReadBoolean(); + System.Diagnostics.Debug.WriteLine("item id: "+itemId); - System.Diagnostics.Debug.WriteLine("item id: "+itemId); + Item item = FindEntityByID(itemId) as Item; + if (item != null) item.Pick(this, false, pickHit, actionHit); - Item item = FindEntityByID(itemId) as Item; - if (item != null) - { - item.Pick(this, false, pickHit, actionHit); - } - - return; - } - else if (type == NetworkEventType.SelectCharacter) - { - ushort characterId = message.ReadUInt16(); - if (characterId==0) - { - DeselectCharacter(false); - } - else - { - Character character = FindEntityByID(characterId) as Character; - if (character != null) SelectCharacter(character, false); - } - return; - } - else if (type == NetworkEventType.KillCharacter) - { - Kill(true); - if (GameMain.NetworkMember != null && controlled == this) - { - GameMain.NetworkMember.AddChatMessage("YOU HAVE DIED. Your chat messages will only be visible to other dead players.", ChatMessageType.Dead); - GameMain.LightManager.LosEnabled = false; - } - return; - } - else if (type == NetworkEventType.InventoryUpdate) - { - if (inventory == null) return; - inventory.ReadNetworkData(NetworkEventType.InventoryUpdate, message); - return; - } - - bool actionKeyState = false; - bool secondaryKeyState = false; - float sendingTime = 0.0f; - Vector2 cursorPos = Vector2.Zero; - - bool leftKeyState = false, rightKeyState = false; - bool upKeyState = false, downKeyState = false; - - bool runState = false; - - try - { - bool hasInputs = message.ReadBoolean(); - sendingTime = message.ReadFloat(); - - if (!hasInputs) - { - if (sendingTime > LastNetworkUpdate) ClearInputs(); return; - } + case NetworkEventType.SelectCharacter: + ushort characterId = message.ReadUInt16(); + if (characterId==0) + { + DeselectCharacter(false); + } + else + { + Character character = FindEntityByID(characterId) as Character; + if (character != null) SelectCharacter(character, false); + } + return; + case NetworkEventType.KillCharacter: + CauseOfDeath causeOfDeath = CauseOfDeath.Damage; + try + { + byte causeOfDeathByte = message.ReadByte(); + causeOfDeath = (CauseOfDeath)causeOfDeathByte; + } + catch + { + causeOfDeath = CauseOfDeath.Damage; + } - actionKeyState = message.ReadBoolean(); - secondaryKeyState = message.ReadBoolean(); - - leftKeyState = message.ReadBoolean(); - rightKeyState = message.ReadBoolean(); - upKeyState = message.ReadBoolean(); - downKeyState = message.ReadBoolean(); + if (causeOfDeath==CauseOfDeath.Pressure) + { + Implode(true); + } + else + { + Kill(causeOfDeath, true); + } - runState = message.ReadBoolean(); - } + if (GameMain.NetworkMember != null && controlled == this) + { + GameMain.NetworkMember.AddChatMessage("You have "+DeathMsg[(int)causeOfDeath]+". Your chat messages will only be visible to other dead players.", ChatMessageType.Dead); + GameMain.LightManager.LosEnabled = false; + } + return; + case NetworkEventType.InventoryUpdate: + if (inventory == null) return; + inventory.ReadNetworkData(NetworkEventType.InventoryUpdate, message); + return; + case NetworkEventType.ImportantEntityUpdate: + foreach (Limb limb in AnimController.Limbs) + { + Vector2 limbPos = limb.SimPosition, vel = Vector2.Zero; + float rotation = limb.Rotation; - catch (Exception e) - { - return; - } + try + { + limbPos.X = message.ReadFloat(); + limbPos.Y = message.ReadFloat(); + + rotation = message.ReadFloat(); + } + catch + { + return; + } - AnimController.IsStanding = true; + if (limb.body != null) + { + limb.body.TargetVelocity = limb.body.LinearVelocity; + limb.body.TargetPosition = limbPos;// +vel * (float)(deltaTime / 60.0); + limb.body.TargetRotation = rotation;// +angularVel * (float)(deltaTime / 60.0); + limb.body.TargetAngularVelocity = limb.body.AngularVelocity; + } - keys[(int)InputType.ActionHeld].State = actionKeyState; - keys[(int)InputType.SecondaryHeld].State = secondaryKeyState; + } - if (sendingTime <= LastNetworkUpdate) return; - - keys[(int)InputType.Left].State = leftKeyState; - keys[(int)InputType.Right].State = rightKeyState; - - keys[(int)InputType.Up].State = upKeyState; - keys[(int)InputType.Down].State = downKeyState; - - keys[(int)InputType.Run].State = runState; - - float dir = 1.0f; - bool isLargeUpdate; - - try - { - if (secondaryKeyState) - { - cursorPos = new Vector2( - message.ReadFloat(), - message.ReadFloat()); - } - else - { - dir = message.ReadBoolean() ? 1.0f : -1.0f; - } - - isLargeUpdate = message.ReadBoolean(); - } - catch - { - return; - } - if (secondaryKeyState) - { - cursorPosition = MathUtils.IsValid(cursorPos) ? cursorPos : Vector2.Zero; - } - else - { - cursorPosition = Position + new Vector2(1000.0f, 0.0f) * dir; - } - - if (isLargeUpdate) - { - foreach (Limb limb in AnimController.Limbs) - { - Vector2 pos = Vector2.Zero, vel = Vector2.Zero; - float rotation = 0.0f; + float newStunTimer = 0.0f, newHealth = 0.0f, newOxygen = 0.0f; try { - pos.X = message.ReadFloat(); - pos.Y = message.ReadFloat(); - - //vel.X = message.ReadFloat(); - //vel.Y = message.ReadFloat(); - - rotation = message.ReadFloat(); - //angularVel = message.ReadFloat(); + newStunTimer = message.ReadRangedSingle(0.0f, 60.0f, 8); + newHealth = (message.ReadByte() / 255.0f) * maxHealth; + newOxygen = (message.ReadByte() / 2.55f); } - catch + catch { return; } + + StartStun(newStunTimer); + Health = newHealth; + oxygen = newOxygen; + + return; + case NetworkEventType.EntityUpdate: + float sendingTime = 0.0f; + Vector2 cursorPos = Vector2.Zero; + + bool actionKeyState, secondaryKeyState; + bool leftKeyState, rightKeyState, upKeyState, downKeyState; + bool runState; + + try + { + bool hasInputs = message.ReadBoolean(); + sendingTime = message.ReadFloat(); + + if (!hasInputs) + { + if (sendingTime > LastNetworkUpdate) ClearInputs(); + return; + } + + actionKeyState = message.ReadBoolean(); + secondaryKeyState = message.ReadBoolean(); + + leftKeyState = message.ReadBoolean(); + rightKeyState = message.ReadBoolean(); + upKeyState = message.ReadBoolean(); + downKeyState = message.ReadBoolean(); + + runState = message.ReadBoolean(); + } + + catch (Exception e) { return; } - if (limb.body != null) + AnimController.IsStanding = true; + + keys[(int)InputType.ActionHeld].State = actionKeyState; + keys[(int)InputType.SecondaryHeld].State = secondaryKeyState; + + if (sendingTime <= LastNetworkUpdate) return; + + keys[(int)InputType.Left].State = leftKeyState; + keys[(int)InputType.Right].State = rightKeyState; + + keys[(int)InputType.Up].State = upKeyState; + keys[(int)InputType.Down].State = downKeyState; + + keys[(int)InputType.Run].State = runState; + + float dir = 1.0f; + Vector2 pos = Vector2.Zero; + + try { - limb.body.TargetVelocity = limb.body.LinearVelocity; - limb.body.TargetPosition = pos;// +vel * (float)(deltaTime / 60.0); - limb.body.TargetRotation = rotation;// +angularVel * (float)(deltaTime / 60.0); - limb.body.TargetAngularVelocity = limb.body.AngularVelocity; + if (secondaryKeyState) + { + cursorPos = new Vector2( + message.ReadFloat(), + message.ReadFloat()); + } + else + { + dir = message.ReadBoolean() ? 1.0f : -1.0f; + } + + pos.X = message.ReadFloat(); + pos.Y = message.ReadFloat(); + } + catch + { +#if DEBUG + DebugConsole.ThrowError("Failed to read netowkrevent for "+this.ToString()); +#endif + return; + } + if (secondaryKeyState) + { + cursorPosition = MathUtils.IsValid(cursorPos) ? cursorPos : Vector2.Zero; + } + else + { + cursorPosition = Position + new Vector2(1000.0f, 0.0f) * dir; + } - } + AnimController.RefLimb.body.TargetPosition = pos; - float newStunTimer = 0.0f, newHealth = 0.0f; + LastNetworkUpdate = sendingTime; - try - { - newStunTimer = message.ReadRangedSingle(0.0f, 60.0f, 8); - newHealth = (message.ReadByte() / 255.0f) * maxHealth; - } - catch { return; } - - StartStun(newStunTimer); - Health = newHealth; - - LargeUpdateTimer = 1; + return; + default: +#if DEBUG + DebugConsole.ThrowError("Character " + this + " tried to read a networkevent of the wrong type: " + type); +#endif + return; } - else - { - Vector2 pos = Vector2.Zero; - try - { - pos.X = message.ReadFloat(); - pos.Y = message.ReadFloat(); - } - - catch { return; } - - - AnimController.RefLimb.body.TargetPosition = pos; - - LargeUpdateTimer = 0; - } - - LastNetworkUpdate = sendingTime; - } public override void Remove() diff --git a/Subsurface/Source/Characters/Ragdoll.cs b/Subsurface/Source/Characters/Ragdoll.cs index b2cd94834..fe01e2bd4 100644 --- a/Subsurface/Source/Characters/Ragdoll.cs +++ b/Subsurface/Source/Characters/Ragdoll.cs @@ -667,11 +667,15 @@ namespace Barotrauma float allowedDistance = NetConfig.AllowedRagdollDistance * ((inWater) ? 2.0f : 1.0f); float dist = Vector2.Distance(refLimb.body.SimPosition, refLimb.body.TargetPosition); - bool resetAll = (dist > resetDistance && character.LargeUpdateTimer == 1); + bool resetAll = dist > resetDistance; + if (resetAll) + { + if (Limbs.FirstOrDefault(limb => !limb.ignoreCollisions && limb.body.TargetPosition == Vector2.Zero) != null) resetAll = false; + } - Vector2 newMovement = (refLimb.body.TargetPosition - refLimb.body.SimPosition); + Vector2 diff = (refLimb.body.TargetPosition - refLimb.body.SimPosition); - if (newMovement == Vector2.Zero || newMovement.Length() < allowedDistance) + if (diff == Vector2.Zero || diff.Length() < allowedDistance) { refLimb.body.TargetPosition = Vector2.Zero; foreach (Limb limb in Limbs) @@ -694,12 +698,12 @@ namespace Barotrauma } correctionMovement = - Vector2.Lerp(targetMovement, Vector2.Normalize(newMovement) * MathHelper.Clamp(dist * 5.0f, 0.1f, 5.0f), 0.2f); + Vector2.Lerp(targetMovement, Vector2.Normalize(diff) * MathHelper.Clamp(dist * 5.0f, 0.1f, 5.0f), 0.2f); } else { correctionMovement = - Vector2.Lerp(targetMovement, Vector2.Normalize(newMovement) * MathHelper.Clamp(dist * 5.0f, 0.1f, 5.0f), 0.2f); + Vector2.Lerp(targetMovement, Vector2.Normalize(diff) * MathHelper.Clamp(dist * 5.0f, 0.1f, 5.0f), 0.2f); if (Math.Abs(correctionMovement.Y) < 0.1f) correctionMovement.Y = 0.0f; } @@ -711,7 +715,11 @@ namespace Barotrauma foreach (Limb limb in Limbs) { - if (limb.body.TargetPosition == Vector2.Zero) continue; + if (limb.body.TargetPosition == Vector2.Zero) + { + limb.body.SetTransform(limb.body.SimPosition + diff, limb.body.Rotation); + continue; + } limb.body.LinearVelocity = limb.body.TargetVelocity; limb.body.AngularVelocity = limb.body.TargetAngularVelocity; diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index d132e6a6c..69945e1c4 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -43,7 +43,7 @@ namespace Barotrauma public static void Init(GameWindow window) { textBox = new GUITextBox(new Rectangle(30, 480,780, 30), Color.Black, Color.White, Alignment.Left, Alignment.Left); - NewMessage("Press F3 to open/close the debug console", Color.Green); + NewMessage("Press F3 to open/close the debug console", Color.Cyan); } public static void Update(GameMain game, float deltaTime) @@ -212,8 +212,14 @@ namespace Barotrauma case "editchar": GameMain.EditCharacterScreen.Select(); break; + case "controlcharacter": + case "control": + if (commands.Length < 2) break; + commands[1] = commands[1].ToLower(); + Character.Controlled = Character.CharacterList.Find(c => c.Name.ToLower() == commands[1]); + break; case "heal": - if (Character.Controlled!=null) + if (Character.Controlled != null) { Character.Controlled.Health = Character.Controlled.MaxHealth; Character.Controlled.Bleeding = 0.0f; diff --git a/Subsurface/Source/Items/Components/ItemContainer.cs b/Subsurface/Source/Items/Components/ItemContainer.cs index e8ccd3022..3cb9cfad4 100644 --- a/Subsurface/Source/Items/Components/ItemContainer.cs +++ b/Subsurface/Source/Items/Components/ItemContainer.cs @@ -239,6 +239,17 @@ namespace Barotrauma.Items.Components itemIds = null; } + public override void Remove() + { + base.Remove(); + + foreach (Item item in inventory.items) + { + if (item == null) continue; + item.Remove(); + } + } + public override void Load(XElement componentElement) { base.Load(componentElement); diff --git a/Subsurface/Source/Map/Hull.cs b/Subsurface/Source/Map/Hull.cs index 97244e295..b3a3e42f0 100644 --- a/Subsurface/Source/Map/Hull.cs +++ b/Subsurface/Source/Map/Hull.cs @@ -308,7 +308,6 @@ namespace Barotrauma Color.Red*((100.0f-OxygenPercentage)/400.0f), true); spriteBatch.DrawString(GUI.Font, "Pressure: " + ((int)pressure - rect.Y).ToString() + - " - Lethality: " + lethalPressure + " - Oxygen: "+((int)OxygenPercentage), new Vector2(rect.X+10, -rect.Y+10), Color.Black); spriteBatch.DrawString(GUI.Font, volume +" / "+ FullVolume, new Vector2(rect.X+10, -rect.Y+30), Color.Black); diff --git a/Subsurface/Source/Map/MapEntity.cs b/Subsurface/Source/Map/MapEntity.cs index 4722963ba..9660beee3 100644 --- a/Subsurface/Source/Map/MapEntity.cs +++ b/Subsurface/Source/Map/MapEntity.cs @@ -506,7 +506,7 @@ namespace Barotrauma /// Has to be done after all the entities have been loaded (an entity can't /// be linked to some other entity that hasn't been loaded yet) /// - public static void OnMapLoaded() + public static void MapLoaded() { foreach (MapEntity e in mapEntityList) { @@ -524,13 +524,19 @@ namespace Barotrauma } } + foreach (MapEntity e in mapEntityList) + { + e.OnMapLoaded(); + } + //mapEntityList.Sort((x, y) => //{ // return x.Name.CompareTo(y.Name); //}); - } - + + + public virtual void OnMapLoaded() { } public void RemoveLinked(MapEntity e) diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index 1ef28c468..3018e175c 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -623,7 +623,7 @@ namespace Barotrauma subBody = new SubmarineBody(this); - MapEntity.OnMapLoaded(); + MapEntity.MapLoaded(); foreach (Item item in Item.itemList) { diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 350da59ed..29d693b85 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -266,8 +266,9 @@ namespace Barotrauma.Networking } } - catch - { + catch (Exception e) + { + DebugConsole.ThrowError("Error while connecting to server", e); break; } } @@ -354,7 +355,7 @@ namespace Barotrauma.Networking } else if (gameStarted) { - new NetworkEvent(myCharacter.ID, true); + myCharacter.CreateUpdateNetworkEvent(true); } } diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index 9b38cfab3..b8c5da921 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -302,16 +302,15 @@ namespace Barotrauma.Networking { if (gameStarted) { - if (myCharacter != null) new NetworkEvent(myCharacter.ID, true); + if (myCharacter != null) myCharacter.CreateUpdateNetworkEvent(false); foreach (Character c in Character.CharacterList) { if (c as AICharacter == null) continue; - if (c.SimPosition == Vector2.Zero || c.SimPosition.Length() < 100.0f) - { - new NetworkEvent(c.ID, false); - } + if (c.SimPosition.Length() > 100.0f) continue; + + c.CreateUpdateNetworkEvent(false); } } diff --git a/Subsurface/Source/Networking/NetConfig.cs b/Subsurface/Source/Networking/NetConfig.cs index d957dda9b..5777d8107 100644 --- a/Subsurface/Source/Networking/NetConfig.cs +++ b/Subsurface/Source/Networking/NetConfig.cs @@ -21,6 +21,8 @@ namespace Barotrauma.Networking //if the ragdoll is closer than this, don't try to correct its position public const float AllowedRagdollDistance = 0.1f; + public const float LargeCharacterUpdateInterval = 5.0f; + public const float DeleteDisconnectedTime = 10.0f; public const float AckInterval = 0.2f; diff --git a/Subsurface/Source/Networking/NetworkEvent.cs b/Subsurface/Source/Networking/NetworkEvent.cs index 8195c75b6..11edf668c 100644 --- a/Subsurface/Source/Networking/NetworkEvent.cs +++ b/Subsurface/Source/Networking/NetworkEvent.cs @@ -18,8 +18,7 @@ namespace Barotrauma.Networking PickItem = 6, DropItem = 7, InventoryUpdate = 8, - - + UpdateProperty = 9, WallDamage = 10, diff --git a/Subsurface/Source/Screens/EditMapScreen.cs b/Subsurface/Source/Screens/EditMapScreen.cs index 57611e69e..5a4c68f78 100644 --- a/Subsurface/Source/Screens/EditMapScreen.cs +++ b/Subsurface/Source/Screens/EditMapScreen.cs @@ -176,6 +176,13 @@ namespace Barotrauma } else if (dummyCharacter != null) { + foreach (Item item in dummyCharacter.Inventory.items) + { + if (item == null) continue; + + item.Remove(); + } + dummyCharacter.Remove(); dummyCharacter = null; } diff --git a/Subsurface/changelog.txt b/Subsurface/changelog.txt index c6d7790bc..d1bf2b2e4 100644 --- a/Subsurface/changelog.txt +++ b/Subsurface/changelog.txt @@ -1,3 +1,39 @@ +--------------------------------------------------------------------------------------------------------- +v0.2.3.1 +--------------------------------------------------------------------------------------------------------- + +- fixed some broken items in Aegir Mark II which caused inventories to get messed up +- fixed the gap at observation deck which vents water out from the lower level of the room + +--------------------------------------------------------------------------------------------------------- +v0.2.3 +--------------------------------------------------------------------------------------------------------- + +Multiplayer: + - major changes to the way the game handles sending reliable messages through UDP, should get rid of + the occasional massive lag spikes + - fixed multiple issues in inventory syncing + - fixed attachable items occasionally crashing the game + - players can be banned from servers + - fixed not being able to kick players while a round is running + - misc optimization + +Items: + - screwdrivers and wrenches are used by left clicking now, so it's possible to start rewiring a + button without activating it for example + - the inventory slots are "combined" when equipping an item that takes up more than one slot + - door shadows aren't visible if the door is open when loading a map + - fixed projectiles crashing the game if they're stuck to a wall while a hole appears on it + - wrenches can be used as an ineffective melee weapon + +Submarine: + - another minimap and a sonar monitor at the "observation deck" on Aegir + - changed one of the cabins to a holding cell on Aegir + +Misc: + - fixed a bug in UI listboxes that may have caused crashes in the server list screen + + --------------------------------------------------------------------------------------------------------- v0.2.2 --------------------------------------------------------------------------------------------------------- diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 162aad8732b25cf6090097918f5d8082ea4e789d..4715cb1df09cf0177175b8f900437c1f4709e899 100644 GIT binary patch delta 15343 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