From 77a07a95afa8a091204def27be98325355547896 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sat, 23 Mar 2019 19:22:13 +0200 Subject: [PATCH] 1473f77...ece6ead commit ece6ead54c021d084f406f4f99daa5a0a7ef4b19 Author: Regalis11 Date: Fri Mar 22 21:52:56 2019 +0200 v0.8.9.7 commit c10dd821ca1a89b4ae62046cf8e558589ff8e6af Author: Regalis11 Date: Fri Mar 22 21:00:02 2019 +0200 Fixed release builds crashing due to simulatedlatency etc commands not existing in release builds commit dee0dded80cbbf30d484232b6706dd705a577eb7 Author: Joonas Rikkonen Date: Fri Mar 22 15:24:44 2019 +0200 Fixed nullref exception if a client disconnects while netstats is enabled commit c45d5bf0c5a4a68200c9eca461cd04090a5de23a Author: Joonas Rikkonen Date: Fri Mar 22 15:24:26 2019 +0200 Togglehud, toggleupperhud and togglecharacternames don't require a permission to use, made simulatedlatency, simulatedloss and simulatedduplicateschange usable to clients commit cec1ac6bccac058bc12ddf18c8e060a7a47c9301 Merge: 411cd9726 1473f77ba Author: itchyOwl Date: Fri Mar 22 14:01:58 2019 +0200 Merge branch 'dev' into enemy-ai commit 411cd9726979668764eea782b515c7510ec4f5a8 Author: itchyOwl Date: Fri Mar 22 14:01:09 2019 +0200 If the target has changed, re-evaluate the attacking limb. Fixes Hammerhead getting stuck next to the sub, because it treats the claw as the attacking limb when targeting characters inside the sub. It should use the head, because it has a wall target. commit 2522bec262f9cb1dea9df75e1d2c22307be5254c Author: itchyOwl Date: Fri Mar 22 13:58:18 2019 +0200 Enemy ai/steering fixes: - Store both sim and world positions. Offset the simposition with the subposition and use it for steering. Fixes enemy indoorsteering, which was broken. - Use head or torso for steering instead of always using the main limb. Fixes characters like Mudraptor overshooting their targets badly. commit c667ff9e4edf8af8f95278fbad42e0c8dd37d84c Author: itchyOwl Date: Fri Mar 22 13:54:21 2019 +0200 Improve the ai debug graphics. commit 4c6c13e07e43a4e3ce2a11dfc4064961c442044a Author: itchyOwl Date: Thu Mar 21 17:11:17 2019 +0200 Refactor, fix and adjust the enemy targeting logic: - Change the logic for fading the memories - When attacking a wall target, set it as the currently selected ai target so that we adjust the right memory - Significantly reduce the value of character targets that are not in the same submarine - In aggressive boarding, double the priority of walls when outside. Set the priority to 0 when inside. Reduce the attractiveness of doors, but still keep the values high. - Redefine priorities for Mudraptor and Crawler (wip) commit b085a95cff6bcf5e3f13e90dd1b71ac15e5ec1ab Author: itchyOwl Date: Thu Mar 21 13:16:22 2019 +0200 Allow enemies to target walls by decision (not only when they happen to be on their way). TODO: target only outer walls and only when outside of the sub. --- .../Properties/AssemblyInfo.cs | 4 +- .../Source/Characters/AI/AITarget.cs | 30 +- .../Source/Characters/AI/EnemyAIController.cs | 48 +-- .../Source/Characters/AI/HumanAIController.cs | 5 +- .../BarotraumaClient/Source/DebugConsole.cs | 10 + .../BarotraumaClient/Source/Map/Structure.cs | 1 + .../Source/Networking/GameClient.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../Source/Characters/AI/AITarget.cs | 4 +- .../Source/Characters/AI/EnemyAIController.cs | 273 +++++++++++------- .../BarotraumaShared/Source/Map/Structure.cs | 8 +- .../Source/Map/StructurePrefab.cs | 4 + Barotrauma/BarotraumaShared/changelog.txt | 2 + 13 files changed, 256 insertions(+), 139 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index def51c503..bd0597493 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/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.8.9.6")] -[assembly: AssemblyFileVersion("0.8.9.6")] +[assembly: AssemblyVersion("0.8.9.7")] +[assembly: AssemblyFileVersion("0.8.9.7")] diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs index 6a7d9ec27..318d1d048 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/AITarget.cs @@ -13,13 +13,39 @@ namespace Barotrauma var pos = new Vector2(WorldPosition.X, -WorldPosition.Y); if (soundRange > 0.0f) { - Color color = Entity is Character ? Color.Yellow : Color.Orange; + Color color; + if (Entity is Character) + { + color = Color.Yellow; + } + else if (Entity is Item) + { + color = Color.Orange; + } + else + { + color = Color.OrangeRed; + } ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); + ShapeExtensions.DrawCircle(spriteBatch, pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom); } if (sightRange > 0.0f) { - Color color = Entity is Character ? Color.CornflowerBlue : Color.CadetBlue; + Color color; + if (Entity is Character) + { + color = Color.CornflowerBlue; + } + else if (Entity is Item) + { + color = Color.CadetBlue; + } + else + { + color = Color.WhiteSmoke; + } ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); + ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs index 0ca2fef80..cb2c213d0 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs @@ -26,8 +26,7 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false); GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5); } - - GUI.Font.DrawString(spriteBatch, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.ToString()})", pos - Vector2.UnitY * 20.0f, Color.Red); + GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.FormatZeroDecimal()})", Color.Red, Color.Black); } /*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red); @@ -70,26 +69,37 @@ namespace Barotrauma GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); - IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager; - if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return; - - GUI.DrawLine(spriteBatch, - new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y), - new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y), - Color.Orange * 0.6f, 0, 3); - - for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++) + if (steeringManager is IndoorsSteeringManager pathSteering) { - GUI.DrawLine(spriteBatch, - new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y), - new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y), - Color.Orange * 0.6f, 0, 3); + var path = pathSteering.CurrentPath; + if (path != null) + { + if (path.CurrentNode != null) + { + GUI.DrawLine(spriteBatch, pos, + new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y), + Color.DarkViolet, 0, 3); - GUI.SmallFont.DrawString(spriteBatch, - pathSteering.CurrentPath.Nodes[i].ID.ToString(), - new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10), - Color.LightGreen); + GUI.DrawString(spriteBatch, pos - new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); + } + for (int i = 1; i < path.Nodes.Count; i++) + { + var previousNode = path.Nodes[i - 1]; + var currentNode = path.Nodes[i]; + GUI.DrawLine(spriteBatch, + new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y), + new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y), + Color.Red * 0.5f, 0, 3); + + GUI.SmallFont.DrawString(spriteBatch, + currentNode.ID.ToString(), + new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), + Color.Red); + } + } } + GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2); + GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs index b588b93a4..28686501e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs @@ -75,11 +75,12 @@ namespace Barotrauma GUI.SmallFont.DrawString(spriteBatch, currentNode.ID.ToString(), - new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y - 10), - Color.LightGreen); + new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), + Color.SkyBlue); } } } + GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2); GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); //if (Character.IsKeyDown(InputType.Aim)) diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index f2da4d263..b993f7deb 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -182,6 +182,9 @@ namespace Barotrauma case "dumpids": case "admin": case "entitylist": + case "togglehud": + case "toggleupperhud": + case "togglecharacternames": return true; default: return client.HasConsoleCommandPermission(command); @@ -400,6 +403,11 @@ namespace Barotrauma AssignRelayToServer("help", false); AssignRelayToServer("verboselogging", false); AssignRelayToServer("freecam", false); +#if DEBUG + AssignRelayToServer("simulatedlatency", false); + AssignRelayToServer("simulatedloss", false); + AssignRelayToServer("simulatedduplicateschance", false); +#endif commands.Add(new Command("clientlist", "", (string[] args) => { })); AssignRelayToServer("clientlist", true); @@ -954,6 +962,8 @@ namespace Barotrauma } } } + element.Value = lines[i]; + i++; } }, isCheat: false)); #endif diff --git a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs index 02545e1e0..7631b14c5 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs @@ -360,6 +360,7 @@ namespace Barotrauma -Bodies[i].Rotation, Color.White); } } + AiTarget?.Draw(spriteBatch); } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index e8a7fc784..12396e460 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -622,7 +622,7 @@ namespace Barotrauma.Networking if (gameStarted) SetRadioButtonColor(); - if (ShowNetStats) + if (ShowNetStats && client?.ServerConnection != null) { netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes); netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes); diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index eb0a60828..1a54bc6aa 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/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.8.9.6")] -[assembly: AssemblyFileVersion("0.8.9.6")] +[assembly: AssemblyVersion("0.8.9.7")] +[assembly: AssemblyFileVersion("0.8.9.7")] diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index 8d39bfb03..5384ef408 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -110,9 +110,11 @@ namespace Barotrauma SonarLabel = element.GetAttributeString("sonarlabel", ""); } - public AITarget(Entity e) + public AITarget(Entity e, float sightRange = 3000, float soundRange = 0) { Entity = e; + SightRange = sightRange; + SoundRange = soundRange; List.Add(this); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 6187cf67a..f085e6b07 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -249,8 +249,7 @@ namespace Barotrauma public override void SelectTarget(AITarget target) { SelectedAiTarget = target; - selectedTargetMemory = FindTargetMemory(target); - + selectedTargetMemory = GetTargetMemory(target); targetValue = 100.0f; } @@ -280,14 +279,14 @@ namespace Barotrauma } } + UpdateTargetMemories(deltaTime); if (updateTargetsTimer > 0.0) { updateTargetsTimer -= deltaTime; } else { - TargetingPriority targetingPriority = null; - UpdateTargets(Character, out targetingPriority); + var targetingPriority = UpdateTargets(Character); updateTargetsTimer = UpdateTargetsInterval; if (SelectedAiTarget == null) @@ -417,9 +416,8 @@ namespace Barotrauma return; } - selectedTargetMemory.Priority -= deltaTime * 0.1f; - - Vector2 attackPos = SelectedAiTarget.WorldPosition; + Vector2 attackWorldPos = SelectedAiTarget.WorldPosition; + Vector2 attackSimPos = SelectedAiTarget.SimPosition; if (SelectedAiTarget.Entity is Item item) { @@ -447,10 +445,11 @@ namespace Barotrauma if (wallTarget != null) { - attackPos = wallTarget.Position; + attackWorldPos = wallTarget.Position; + attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos); if (Character.Submarine == null && wallTarget.Structure.Submarine != null) { - attackPos += wallTarget.Structure.Submarine.Position; + attackWorldPos += wallTarget.Structure.Submarine.Position; } } else if (SelectedAiTarget.Entity is Character c) @@ -464,14 +463,25 @@ namespace Barotrauma if (dist < closestDist) { closestDist = dist; - attackPos = limb.WorldPosition; + attackWorldPos = limb.WorldPosition; + attackSimPos = limb.SimPosition; } } } + // Take the sub position into account in the sim pos + if (Character.Submarine == null && SelectedAiTarget.Entity.Submarine != null) + { + attackSimPos += SelectedAiTarget.Entity.Submarine.SimPosition; + } + else if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine == null) + { + attackSimPos -= Character.Submarine.SimPosition; + } + if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater) { - Character.AnimController.TargetDir = Character.WorldPosition.X < attackPos.X ? Direction.Right : Direction.Left; + Character.AnimController.TargetDir = Character.WorldPosition.X < attackWorldPos.X ? Direction.Right : Direction.Left; } if (aggressiveBoarding) @@ -551,7 +561,7 @@ namespace Barotrauma } else { - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } } @@ -566,7 +576,7 @@ namespace Barotrauma if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack) { // Fall back if cannot attack. - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } attackingLimb = null; @@ -575,7 +585,7 @@ namespace Barotrauma { // If the secondary cooldown is defined and expired, check if we can switch the attack var previousLimb = attackingLimb; - var newLimb = GetAttackLimb(attackPos, previousLimb); + var newLimb = GetAttackLimb(attackWorldPos, previousLimb); if (newLimb != null) { attackingLimb = newLimb; @@ -589,7 +599,7 @@ namespace Barotrauma } else { - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } } @@ -604,15 +614,15 @@ namespace Barotrauma break; case AIBehaviorAfterAttack.FallBack: default: - UpdateFallBack(attackPos, deltaTime); + UpdateFallBack(attackWorldPos, deltaTime); return; } } - if (attackingLimb == null) + if (attackingLimb == null || _previousAiTarget != SelectedAiTarget) { - attackingLimb = GetAttackLimb(attackPos); + attackingLimb = GetAttackLimb(attackWorldPos); } if (canAttack) { @@ -622,24 +632,39 @@ namespace Barotrauma if (canAttack) { // Check that we can reach the target - distance = Vector2.Distance(attackingLimb.WorldPosition, attackPos); + distance = Vector2.Distance(attackingLimb.WorldPosition, attackWorldPos); canAttack = distance < attackingLimb.attack.Range; } - Limb steeringLimb = Character.AnimController.MainLimb; + // If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso. + // If we always use the main limb, this causes the character to seek the target with it's torso/head, when it should not. For example Mudraptor steering with it's belly, when it should use it's head. + // So let's use the one that's closer to the attacking limb. + Limb steeringLimb; + var torso = Character.AnimController.GetLimb(LimbType.Torso); + var head = Character.AnimController.GetLimb(LimbType.Head); + if (attackingLimb == null) + { + steeringLimb = head ?? torso; + } + else + { + if (head != null && torso != null) + { + steeringLimb = Vector2.DistanceSquared(attackingLimb.SimPosition, head.SimPosition) < Vector2.DistanceSquared(attackingLimb.SimPosition, torso.SimPosition) ? head : torso; + } + else + { + steeringLimb = head ?? torso; + } + } if (steeringLimb != null) { - Vector2 toTarget = Vector2.Normalize(attackPos - steeringLimb.WorldPosition); - Vector2 targetingVector = toTarget * attackingLimb.attack.Range; - // Offset the position a bit so that we don't overshoot the movement. - Vector2 steerPos = attackPos + targetingVector; - steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(attackPos), 10); - if (Character.CurrentHull == null) - { - SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f); - } + Vector2 offset = Character.SimPosition - steeringLimb.SimPosition; + // Offset so that we don't overshoot the movement + Vector2 steerPos = attackSimPos + offset; + SteeringManager.SteeringSeek(steerPos, 10); - if (steeringManager is IndoorsSteeringManager indoorsSteering) + if (SteeringManager is IndoorsSteeringManager indoorsSteering) { if (indoorsSteering.CurrentPath != null && !indoorsSteering.IsPathDirty) { @@ -647,11 +672,11 @@ namespace Barotrauma { //wander around randomly and decrease the priority faster if no path is found if (selectedTargetMemory != null) selectedTargetMemory.Priority -= deltaTime * 10.0f; - steeringManager.SteeringWander(); + SteeringManager.SteeringWander(); } else if (indoorsSteering.CurrentPath.Finished) { - steeringManager.SteeringManual(deltaTime, toTarget); + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition)); } else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null) { @@ -665,11 +690,15 @@ namespace Barotrauma } } } + else if (Character.CurrentHull == null) + { + SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f); + } } if (canAttack) { - UpdateLimbAttack(deltaTime, attackingLimb, ConvertUnits.ToSimUnits(attackPos), distance); + UpdateLimbAttack(deltaTime, attackingLimb, attackSimPos, distance); } } @@ -702,7 +731,7 @@ namespace Barotrauma } //check if there's a wall between the target and the Character - Vector2 rayStart = Character.SimPosition; + Vector2 rayStart = SimPosition; Vector2 rayEnd = SelectedAiTarget.SimPosition; bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null; @@ -728,13 +757,15 @@ namespace Barotrauma { if (wall.SectionBodyDisabled(i)) { - if (aggressiveBoarding && CanPassThroughHole(wall, i)) //aggressive boarders always target holes they can pass through + if (aggressiveBoarding && CanPassThroughHole(wall, i)) { + //aggressive boarders always target holes they can pass through sectionIndex = i; break; } - else //otherwise ignore and keep breaking other sections + else { + //otherwise ignore and keep breaking other sections continue; } } @@ -745,12 +776,12 @@ namespace Barotrauma Vector2 attachTargetNormal; if (wall.IsHorizontal) { - attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y)); + attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y)); sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y; } else { - attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f); + attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f); sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X; } @@ -776,7 +807,7 @@ namespace Barotrauma Character.AnimController.ReleaseStuckLimbs(); if (attacker == null || attacker.AiTarget == null) return; - AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget); + AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget); targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt; // Reduce the cooldown so that the character can react @@ -798,23 +829,37 @@ namespace Barotrauma private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackSimPos, float distance = -1) { - var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable; - if (damageTarget == null) return; - - float prevHealth = damageTarget.Health; - if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance)) + if (SelectedAiTarget == null) { return; } + if (wallTarget != null) { - if (damageTarget.Health > 0) + // If the selected target is not the wall target, make the wall target the selected target. + var aiTarget = wallTarget.Structure.AiTarget; + if (aiTarget != null && SelectedAiTarget != aiTarget) { - // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon - selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed; + SelectTarget(aiTarget); + } + } + if (SelectedAiTarget.Entity is IDamageable damageTarget) + { + float prevHealth = damageTarget.Health; + if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance)) + { + if (damageTarget.Health > 0) + { + // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon + selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed; + } + else + { + selectedTargetMemory.Priority = 0; + } } } } - private void UpdateFallBack(Vector2 attackPosition, float deltaTime) + private void UpdateFallBack(Vector2 attackWorldPos, float deltaTime) { - Vector2 attackVector = attackPosition - Character.WorldPosition; + Vector2 attackVector = attackWorldPos - WorldPosition; float dist = attackVector.Length(); float desiredDist = colliderSize * 2.0f; if (dist < desiredDist) @@ -871,15 +916,13 @@ namespace Barotrauma //goes through all the AItargets, evaluates how preferable it is to attack the target, //whether the Character can see/hear the target and chooses the most preferable target within //sight/hearing range - public void UpdateTargets(Character character, out TargetingPriority targetingPriority) + public TargetingPriority UpdateTargets(Character character) { AITarget newTarget = null; - targetingPriority = null; + TargetingPriority targetingPriority = null; selectedTargetMemory = null; targetValue = 0.0f; - UpdateTargetMemories(); - foreach (AITarget target in AITarget.List) { if (!target.Enabled) continue; @@ -888,44 +931,49 @@ namespace Barotrauma continue; } - float valueModifier = 1.0f; - float dist = 0.0f; - Character targetCharacter = target.Entity as Character; //ignore the aitarget if it is the Character itself if (targetCharacter == character) continue; + float valueModifier = 1; string targetingTag = null; if (targetCharacter != null) { if (targetCharacter.Submarine != null && Character.Submarine == null) + { + targetingTag = "dead"; + if (targetCharacter.Submarine != Character.Submarine) + { + // In a different sub or the target is outside when we are inside or vice versa -> Significantly reduce the value + valueModifier = 0.1f; + } + } + else if (targetCharacter.AIController is EnemyAIController enemy) + { + if (enemy.combatStrength > combatStrength) + { + targetingTag = "stronger"; + } + else if (enemy.combatStrength < combatStrength) + { + targetingTag = "weaker"; + } + if (targetCharacter.Submarine != Character.Submarine) + { + // In a different sub or the target is outside when we are inside or vice versa -> Significantly reduce the value + valueModifier = 0.1f; + } + } + else if (targetCharacter.Submarine != null && Character.Submarine == null) { //target inside, AI outside -> we'll be attacking a wall between the characters so use the priority for attacking rooms targetingTag = "room"; } - else if (targetCharacter.IsDead) - { - targetingTag = "dead"; - } else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant())) { targetingTag = targetCharacter.SpeciesName.ToLowerInvariant(); } - else - { - if (targetCharacter.AIController is EnemyAIController enemy) - { - if (enemy.combatStrength > combatStrength) - { - targetingTag = "stronger"; - } - else if (enemy.combatStrength < combatStrength) - { - targetingTag = "weaker"; - } - } - } } else if (target.Entity != null) { @@ -951,17 +999,32 @@ namespace Barotrauma } } } + else if (target.Entity is Structure s) + { + targetingTag = "wall"; + if (character.CurrentHull == null && aggressiveBoarding) + { + valueModifier = s.HasBody ? 2 : 0; + foreach (var section in s.Sections) + { + if (section.gap != null) + { + // up to 100% more priority for every gap in the wall + valueModifier *= 1 + section.gap.Open; + } + } + } + } else { targetingTag = "room"; } - if (door != null) { //increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom) { - valueModifier = door.IsOpen ? 10 : 5; + valueModifier = door.IsOpen ? 5 : 3; } else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors { @@ -979,10 +1042,15 @@ namespace Barotrauma valueModifier *= targetingPriorities[targetingTag].Priority; + if (targetingTag == null) continue; + if (!targetingPriorities.ContainsKey(targetingTag)) continue; + + valueModifier *= targetingPriorities[targetingTag].Priority; + if (valueModifier == 0.0f) continue; Vector2 toTarget = target.WorldPosition - character.WorldPosition; - dist = toTarget.Length(); + float dist = toTarget.Length(); //if the target has been within range earlier, the character will notice it more easily //(i.e. remember where the target was) @@ -996,13 +1064,13 @@ namespace Barotrauma // -> just ignore the distance and attack whatever has the highest priority dist = Math.Max(dist, 100.0f); - AITargetMemory targetMemory = FindTargetMemory(target); + AITargetMemory targetMemory = GetTargetMemory(target); if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) { // Inside the sub, treat objects that are up or down, as they were farther away. dist *= 3; } - valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist); + valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist); if (valueModifier > targetValue) { @@ -1014,51 +1082,39 @@ namespace Barotrauma } SelectedAiTarget = newTarget; - if (SelectedAiTarget != _previousAiTarget) { wallTarget = null; } - _previousAiTarget = SelectedAiTarget; + return targetingPriority; } - //find the targetMemory that corresponds to some AItarget or create if there isn't one yet - private AITargetMemory FindTargetMemory(AITarget target) + private AITargetMemory GetTargetMemory(AITarget target) { - AITargetMemory memory = null; - if (targetMemories.TryGetValue(target, out memory)) + if (!targetMemories.TryGetValue(target, out AITargetMemory memory)) { - return memory; + memory = new AITargetMemory(10); + targetMemories.Add(target, memory); } - - memory = new AITargetMemory(10.0f); - targetMemories.Add(target, memory); - return memory; } - //go through all the targetmemories and delete ones that don't - //have a corresponding AItarget or whose priority is 0.0f - private void UpdateTargetMemories() + private float memoryFadeTime = 0.5f; + private List removals = new List(); + private void UpdateTargetMemories(float deltaTime) { - List toBeRemoved = null; - foreach (KeyValuePair memory in targetMemories) + removals.Clear(); + foreach (var memory in targetMemories) { - memory.Value.Priority += 0.1f; - if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key)) + // Slowly decrease all memories + memory.Value.Priority -= memoryFadeTime * deltaTime; + // Remove targets that have no priority or have been removed + if (memory.Value.Priority <= 1 || !AITarget.List.Contains(memory.Key)) { - if (toBeRemoved == null) toBeRemoved = new List(); - toBeRemoved.Add(memory.Key); - } - } - - if (toBeRemoved != null) - { - foreach (AITarget target in toBeRemoved) - { - targetMemories.Remove(target); + removals.Add(memory.Key); } } + removals.ForEach(r => targetMemories.Remove(r)); } #endregion @@ -1071,7 +1127,7 @@ namespace Barotrauma private int GetMinimumPassableHoleCount() { - return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize); + return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize); } private bool CanPassThroughHole(Structure wall, int sectionIndex) @@ -1116,6 +1172,5 @@ namespace Barotrauma { this.priority = priority; } - } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 6e4fe089b..82d0dad3e 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -297,7 +297,13 @@ namespace Barotrauma CreateStairBodies(); } } - + + // Only add ai targets automatically to walls + if (aiTarget == null && HasBody && Tags.Contains("wall")) + { + aiTarget = new AITarget(this); + } + InsertToList(); } diff --git a/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs index a4077554a..037364597 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs @@ -217,6 +217,10 @@ namespace Barotrauma } SerializableProperty.DeserializeProperties(sp, element); + if (sp.Body) + { + sp.Tags.Add("wall"); + } string translatedDescription = TextManager.Get("EntityDescription." + sp.identifier, true); if (!string.IsNullOrEmpty(translatedDescription)) sp.Description = translatedDescription; diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 15722f9ce..421c3d7df 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -9,6 +9,8 @@ Additions and changes: - Clients communicate syncing errors to the server, and the server logs a more descriptive error about what went wrong. Should make it easier to diagnose disconnection issues from now on. - Ending a multiplayer campaign round by talking to watchman doesn't require any special permissions. +- Server automatically ends rounds if there have been no players alive in 60 seconds and respawning +is not allowed during the round. - Added a button for resetting an entity's properties to the default values to the sub editor. - Updated handheld sonar UI graphics.