diff --git a/Barotrauma/Barotrauma.csproj b/Barotrauma/Barotrauma.csproj index 0c0d2c2fe..9eb20d525 100644 --- a/Barotrauma/Barotrauma.csproj +++ b/Barotrauma/Barotrauma.csproj @@ -387,6 +387,7 @@ PreserveNewest + Designer PreserveNewest @@ -868,9 +869,21 @@ PreserveNewest + + PreserveNewest + PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/Barotrauma.csproj.user b/Barotrauma/Barotrauma.csproj.user index df35fcf91..1782abafa 100644 --- a/Barotrauma/Barotrauma.csproj.user +++ b/Barotrauma/Barotrauma.csproj.user @@ -9,7 +9,7 @@ en-US false - ProjectFiles + ShowAllFiles diff --git a/Barotrauma/Content/Characters/Carrier/carrier.xml b/Barotrauma/Content/Characters/Carrier/carrier.xml index 4ff3312d2..268b41d76 100644 --- a/Barotrauma/Content/Characters/Carrier/carrier.xml +++ b/Barotrauma/Content/Characters/Carrier/carrier.xml @@ -8,6 +8,16 @@ + + @@ -44,16 +54,12 @@ - - + + - + - \ No newline at end of file diff --git a/Barotrauma/Content/Characters/Charybdis/charybdis.png b/Barotrauma/Content/Characters/Charybdis/charybdis.png index ef2796ae1..6d1ba2da8 100644 Binary files a/Barotrauma/Content/Characters/Charybdis/charybdis.png and b/Barotrauma/Content/Characters/Charybdis/charybdis.png differ diff --git a/Barotrauma/Content/Characters/Charybdis/charybdis.xml b/Barotrauma/Content/Characters/Charybdis/charybdis.xml index e027dce94..44eeab013 100644 --- a/Barotrauma/Content/Characters/Charybdis/charybdis.xml +++ b/Barotrauma/Content/Characters/Charybdis/charybdis.xml @@ -2,20 +2,36 @@ + + - + - + - + @@ -24,18 +40,25 @@ - - + + + + + + + + + - - + + + - \ No newline at end of file diff --git a/Barotrauma/Content/Characters/Coelanth/coelanth.xml b/Barotrauma/Content/Characters/Coelanth/coelanth.xml index 4e820b397..194c95cd9 100644 --- a/Barotrauma/Content/Characters/Coelanth/coelanth.xml +++ b/Barotrauma/Content/Characters/Coelanth/coelanth.xml @@ -5,19 +5,35 @@ - + + + - + - + @@ -51,10 +67,7 @@ - - - + - - + \ No newline at end of file diff --git a/Barotrauma/Content/Characters/Crawler/crawler.xml b/Barotrauma/Content/Characters/Crawler/crawler.xml index de3270ce2..c429a3e9b 100644 --- a/Barotrauma/Content/Characters/Crawler/crawler.xml +++ b/Barotrauma/Content/Characters/Crawler/crawler.xml @@ -1,14 +1,28 @@  - + + + @@ -16,7 +30,7 @@ - + @@ -32,7 +46,9 @@ - + @@ -59,25 +75,21 @@ - + - + - + - - + + - - + + - - + + - - diff --git a/Barotrauma/Content/Characters/Endworm/endworm.xml b/Barotrauma/Content/Characters/Endworm/endworm.xml index 1ed66da24..23f5b98d6 100644 --- a/Barotrauma/Content/Characters/Endworm/endworm.xml +++ b/Barotrauma/Content/Characters/Endworm/endworm.xml @@ -1,10 +1,18 @@  - + + + @@ -75,6 +83,5 @@ - diff --git a/Barotrauma/Content/Characters/Fractalguardian/fractalguardian.xml b/Barotrauma/Content/Characters/Fractalguardian/fractalguardian.xml index 69ad89036..abb438036 100644 --- a/Barotrauma/Content/Characters/Fractalguardian/fractalguardian.xml +++ b/Barotrauma/Content/Characters/Fractalguardian/fractalguardian.xml @@ -5,6 +5,14 @@ + + @@ -41,16 +49,15 @@ - + - + - + - - + + - diff --git a/Barotrauma/Content/Characters/Fractalguardian2/fractalguardian2.xml b/Barotrauma/Content/Characters/Fractalguardian2/fractalguardian2.xml index 9f45de877..c3841f2c4 100644 --- a/Barotrauma/Content/Characters/Fractalguardian2/fractalguardian2.xml +++ b/Barotrauma/Content/Characters/Fractalguardian2/fractalguardian2.xml @@ -5,6 +5,14 @@ + + @@ -32,19 +40,16 @@ - - + - - - - - + + + + - diff --git a/Barotrauma/Content/Characters/Human/human.xml b/Barotrauma/Content/Characters/Human/human.xml index b0f000efd..1d45173db 100644 --- a/Barotrauma/Content/Characters/Human/human.xml +++ b/Barotrauma/Content/Characters/Human/human.xml @@ -9,8 +9,8 @@ movementlerp="0.4" legtorque="15.0" thightorque="-5.0" - walkspeed="1.5" - swimspeed="2.0" + walkspeed="1.5" swimspeed="2.0" + runspeedmultiplier="3.0" swimspeedmultiplier="1.5" colliderheightfromfloor="55" impacttolerance="7.5"> @@ -84,25 +84,25 @@ - + - + - + - - - + + + - - - + + + diff --git a/Barotrauma/Content/Characters/Human/humanhusk.xml b/Barotrauma/Content/Characters/Human/humanhusk.xml index 52f0e389e..29b52fdce 100644 --- a/Barotrauma/Content/Characters/Human/humanhusk.xml +++ b/Barotrauma/Content/Characters/Human/humanhusk.xml @@ -9,12 +9,12 @@ movementlerp="0.4" legtorque="15.0" thightorque="-5.0" - walkspeed="1.5" - swimspeed="2.5" + walkspeed="1.5" swimspeed="2.5" + runspeedmultiplier="2.0" swimspeedmultiplier="1.5" impacttolerance="7.5"> - + @@ -84,34 +84,35 @@ - + - + - + - + - - - + + + - - - + + + - + diff --git a/Barotrauma/Content/Characters/Husk/husk.xml b/Barotrauma/Content/Characters/Husk/husk.xml index 9ea4a67d2..179d8dbbe 100644 --- a/Barotrauma/Content/Characters/Husk/husk.xml +++ b/Barotrauma/Content/Characters/Husk/husk.xml @@ -5,28 +5,38 @@ + + + walkspeed="1.2" swimspeed="2.5" + runspeedmultiplier="2.0" swimspeedmultiplier="1.5"> - + - - + @@ -82,7 +92,7 @@ - + @@ -90,26 +100,21 @@ - + - + - - - + + + - - - - + + + - - diff --git a/Barotrauma/Content/Characters/Mantis/mantis.png b/Barotrauma/Content/Characters/Mantis/mantis.png index b0b287343..a8a6618e6 100644 Binary files a/Barotrauma/Content/Characters/Mantis/mantis.png and b/Barotrauma/Content/Characters/Mantis/mantis.png differ diff --git a/Barotrauma/Content/Characters/Mantis/mantis.xml b/Barotrauma/Content/Characters/Mantis/mantis.xml index 298d4c9e6..36b2b9750 100644 --- a/Barotrauma/Content/Characters/Mantis/mantis.xml +++ b/Barotrauma/Content/Characters/Mantis/mantis.xml @@ -7,10 +7,24 @@ + + - + @@ -35,7 +49,7 @@ - + @@ -49,7 +63,8 @@ - + @@ -69,27 +84,23 @@ - + - + - + - - - + + + - - + + - - - + + - diff --git a/Barotrauma/Content/Characters/Moloch/moloch.xml b/Barotrauma/Content/Characters/Moloch/moloch.xml index 12c85dc13..30ab3d997 100644 --- a/Barotrauma/Content/Characters/Moloch/moloch.xml +++ b/Barotrauma/Content/Characters/Moloch/moloch.xml @@ -6,6 +6,14 @@ + + @@ -46,22 +54,18 @@ - - - + + + - - - + + + - - - + + + - \ No newline at end of file diff --git a/Barotrauma/Content/Characters/Tigerthresher/tigerthresher.xml b/Barotrauma/Content/Characters/Tigerthresher/tigerthresher.xml index aa3d7d237..6c3d35f6e 100644 --- a/Barotrauma/Content/Characters/Tigerthresher/tigerthresher.xml +++ b/Barotrauma/Content/Characters/Tigerthresher/tigerthresher.xml @@ -5,12 +5,27 @@ - + + + - + @@ -19,7 +34,7 @@ - + @@ -67,10 +82,8 @@ - - + - \ No newline at end of file diff --git a/Barotrauma/Content/Characters/Watcher/watcher.xml b/Barotrauma/Content/Characters/Watcher/watcher.xml index 0baeb14b1..24033c190 100644 --- a/Barotrauma/Content/Characters/Watcher/watcher.xml +++ b/Barotrauma/Content/Characters/Watcher/watcher.xml @@ -12,6 +12,14 @@ + + @@ -19,7 +27,7 @@ - + @@ -36,6 +44,4 @@ - - \ No newline at end of file diff --git a/Barotrauma/Content/Items/Weapons/explosives.xml b/Barotrauma/Content/Items/Weapons/explosives.xml index 57c683c19..e6a87f07f 100644 --- a/Barotrauma/Content/Items/Weapons/explosives.xml +++ b/Barotrauma/Content/Items/Weapons/explosives.xml @@ -13,7 +13,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -69,7 +69,7 @@ - + @@ -121,7 +121,7 @@ - + diff --git a/Barotrauma/Content/Items/Weapons/railgun.xml b/Barotrauma/Content/Items/Weapons/railgun.xml index ec3636731..3f73ec4ee 100644 --- a/Barotrauma/Content/Items/Weapons/railgun.xml +++ b/Barotrauma/Content/Items/Weapons/railgun.xml @@ -83,7 +83,7 @@ - + @@ -115,10 +115,10 @@ - + - + diff --git a/Barotrauma/Content/Particles/ParticlePrefabs.xml b/Barotrauma/Content/Particles/ParticlePrefabs.xml index f258b789b..ca9c81561 100644 --- a/Barotrauma/Content/Particles/ParticlePrefabs.xml +++ b/Barotrauma/Content/Particles/ParticlePrefabs.xml @@ -57,31 +57,29 @@ - - - + velocitychange="0.0, -0.1"> + + + - + @@ -20,13 +21,14 @@ characterfile="Content/Characters/Moloch/moloch.xml" commonness="10" difficulty="30" - spawntype="mainpath" + spawntype="mainpath" musictype="monster"/> @@ -70,7 +72,7 @@ commonness="5" difficulty="10" minamount="2" maxamount="3" - + repeat="true" spawntype="mainpath,cave,ruin" musictype="monster"/> diff --git a/Barotrauma/Source/Characters/AI/AIController.cs b/Barotrauma/Source/Characters/AI/AIController.cs index a909e4e31..dee7aab5d 100644 --- a/Barotrauma/Source/Characters/AI/AIController.cs +++ b/Barotrauma/Source/Characters/AI/AIController.cs @@ -6,15 +6,13 @@ namespace Barotrauma { class AIController : ISteerable { - - public enum AiState { None, Attack, GoTo, Escape } - public enum SteeringState { Wander, Seek, Escape } + public enum AIState { None, Attack, GoTo, Escape, Eat } public bool Enabled; public readonly Character Character; - protected AiState state; + protected AIState state; protected SteeringManager steeringManager; @@ -44,7 +42,7 @@ namespace Barotrauma get { return Character.AnimController.Collider.LinearVelocity; } } - public AiState State + public AIState State { get { return state; } set { state = value; } diff --git a/Barotrauma/Source/Characters/AI/AITarget.cs b/Barotrauma/Source/Characters/AI/AITarget.cs index 1f5f47f69..f3038082f 100644 --- a/Barotrauma/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/Source/Characters/AI/AITarget.cs @@ -11,7 +11,11 @@ namespace Barotrauma public static List List = new List(); - public readonly Entity Entity; + public Entity Entity + { + get; + private set; + } private float soundRange; private float sightRange; @@ -47,6 +51,7 @@ namespace Barotrauma public void Remove() { List.Remove(this); + Entity = null; } public void Draw(SpriteBatch spriteBatch) diff --git a/Barotrauma/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/Source/Characters/AI/EnemyAIController.cs index a18c4e22e..e546032f7 100644 --- a/Barotrauma/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/Source/Characters/AI/EnemyAIController.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Xml.Linq; using FarseerPhysics; -using Lidgren.Network; using Microsoft.Xna.Framework; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework.Graphics; @@ -23,21 +21,22 @@ namespace Barotrauma //0.0 = doesn't attack targets of the type //positive values = attacks targets of this type //negative values = escapes targets of this type - private float attackRooms, attackHumans, attackWeaker, attackStronger; + private float attackRooms, attackHumans, attackWeaker, attackStronger, eatDeadPriority; + + //determines which characters are considered weaker/stronger + private float combatStrength; private SteeringManager outsideSteering, insideSteering; private float updateTargetsTimer; private float raycastTimer; - - //a timer for attacks such as biting that last for a specific amount of time - //the duration is determined by the attackDuration of the attacking limb - private float attackTimer; - + //a "cooldown time" after an attack during which the Character doesn't try to attack again private float attackCoolDown; private float coolDownTimer; + + private bool attachToWalls; //a point in a wall which the Character is currently targeting private Vector2 wallAttackPos; @@ -46,10 +45,15 @@ namespace Barotrauma //the limb selected for the current attack private Limb attackingLimb; + + //flee when the health is below this value + private float fleeHealthThreshold; private AITarget selectedAiTarget; private AITargetMemory selectedTargetMemory; private float targetValue; + + private float eatTimer; private Dictionary targetMemories; @@ -73,10 +77,13 @@ namespace Barotrauma XElement aiElement = doc.Root.Element("ai"); if (aiElement == null) return; - attackRooms = ToolBox.GetAttributeFloat(aiElement, "attackrooms", 0.0f) / 100.0f; - attackHumans = ToolBox.GetAttributeFloat(aiElement, "attackhumans", 0.0f) / 100.0f; - attackWeaker = ToolBox.GetAttributeFloat(aiElement, "attackweaker", 0.0f) / 100.0f; - attackStronger = ToolBox.GetAttributeFloat(aiElement, "attackstronger", 0.0f) / 100.0f; + attackRooms = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackrooms", "attackpriorityrooms") / 100.0f; + attackHumans = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackhumans", "attackpriorityhumans") / 100.0f; + attackWeaker = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackweaker", "attackpriorityweaker") / 100.0f; + attackStronger = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackstronger", "attackprioritystronger") / 100.0f; + eatDeadPriority = ToolBox.GetAttributeFloat(aiElement, "eatpriority", 0.0f) / 100.0f; + + combatStrength = ToolBox.GetAttributeFloat(aiElement, "combatstrength", 1.0f); attackCoolDown = ToolBox.GetAttributeFloat(aiElement, "attackcooldown", 5.0f); @@ -85,12 +92,16 @@ namespace Barotrauma attackWhenProvoked = ToolBox.GetAttributeBool(aiElement, "attackwhenprovoked", false); + fleeHealthThreshold = ToolBox.GetAttributeFloat(aiElement, "fleehealththreshold", 0.0f); + + attachToWalls = ToolBox.GetAttributeBool(aiElement, "attachtowalls", false); + outsideSteering = new SteeringManager(this); insideSteering = new IndoorsSteeringManager(this, false); steeringManager = outsideSteering; - state = AiState.None; + state = AIState.None; } public override void SelectTarget(AITarget target) @@ -138,54 +149,140 @@ namespace Barotrauma if (selectedAiTarget == null) { - state = AiState.None; + state = AIState.None; + } + else if ((selectedAiTarget.Entity is Character) && ((Character)selectedAiTarget.Entity).IsDead) + { + if (state != AIState.Eat) + { + eatTimer = 0.0f; + state = AIState.Eat; + } } else { - state = (targetValue > 0.0f) ? AiState.Attack : AiState.Escape; + state = (targetValue < 0.0f || Character.Health < fleeHealthThreshold) ? AIState.Escape : AIState.Attack; } //if (coolDownTimer >= 0.0f) return; } steeringManager = Character.Submarine == null ? outsideSteering : insideSteering; + bool run = false; switch (state) { - case AiState.None: + case AIState.None: UpdateNone(deltaTime); break; - case AiState.Attack: + case AIState.Attack: + run = coolDownTimer <= 0.0f; UpdateAttack(deltaTime); break; + case AIState.Eat: + UpdateEating(deltaTime); + break; + case AIState.Escape: + run = true; + UpdateEscape(deltaTime); + break; + default: + throw new NotImplementedException(); } - steeringManager.Update(); - } - - private void UpdateNone(float deltaTime) - { - //wander around randomly - if (Character.Submarine==null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth*0.5f)) + if (run) { - steeringManager.SteeringManual(deltaTime, Vector2.UnitY); + steeringManager.Update(Character.AnimController.InWater ? + Character.AnimController.SwimSpeedMultiplier : Character.AnimController.RunSpeedMultiplier); } else { - steeringManager.SteeringAvoid(deltaTime, 0.1f); - steeringManager.SteeringWander(0.5f); + steeringManager.Update(); + } + } + + #region Idle + + private void UpdateNone(float deltaTime) + { + attackingLimb = null; + coolDownTimer -= deltaTime; + + if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth * 0.5f)) + { + //steer straight up if very deep + steeringManager.SteeringManual(deltaTime, Vector2.UnitY); + return; } - attackingLimb = null; - attackTimer = 0.0f; + if (attachToWalls && Character.Submarine == null) + { + raycastTimer -= deltaTime; + //check if there are any walls nearby the character could attach to + if (raycastTimer < 1.0f) + { + wallAttackPos = Vector2.Zero; - coolDownTimer -= deltaTime; + var cells = Level.Loaded.GetCells(WorldPosition, 1); + if (cells.Count > 0) + { + Body closestBody = Submarine.CheckVisibility(Character.SimPosition, ConvertUnits.ToSimUnits(cells[0].Center)); + if (closestBody != null && closestBody.UserData is Voronoi2.VoronoiCell) + { + wallAttackPos = Submarine.LastPickedPosition; + } + } + raycastTimer = RaycastInterval; + } + } + else + { + wallAttackPos = Vector2.Zero; + } + + if (wallAttackPos == Vector2.Zero) + { + //wander around randomly + steeringManager.SteeringAvoid(deltaTime, 0.1f); + steeringManager.SteeringWander(0.5f); + return; + } + + float dist = Vector2.Distance(SimPosition, wallAttackPos); + if (dist < Math.Max(Math.Max(Character.AnimController.Collider.radius, Character.AnimController.Collider.width), Character.AnimController.Collider.height) * 1.2f) + { + //close enough to a wall -> attach + Character.AnimController.Collider.MoveToPos(wallAttackPos, 1.0f); + steeringManager.Reset(); + + } + else + { + //move closer to the wall + steeringManager.SteeringAvoid(deltaTime, 0.1f); + steeringManager.SteeringSeek(wallAttackPos); + } } - + + #endregion + + #region Escape + + private void UpdateEscape(float deltaTime) + { + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition) * 5); + SteeringManager.SteeringWander(1.0f); + SteeringManager.SteeringAvoid(deltaTime, 2f); + } + + #endregion + + #region Attack + private void UpdateAttack(float deltaTime) { if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) { - state = AiState.None; + state = AIState.None; return; } @@ -198,7 +295,22 @@ namespace Barotrauma if (selectedAiTarget.Entity != null && Character.Submarine == null && selectedAiTarget.Entity.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position); } - + else if (selectedAiTarget.Entity is Character) + { + //target the closest limb if the target is a character + float closestDist = Vector2.DistanceSquared(selectedAiTarget.SimPosition, SimPosition); + foreach (Limb limb in ((Character)selectedAiTarget.Entity).AnimController.Limbs) + { + if (limb == null) continue; + float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition); + if (dist < closestDist) + { + closestDist = dist; + attackSimPosition = limb.SimPosition; + } + } + } + if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater) { Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left; @@ -229,13 +341,13 @@ namespace Barotrauma { foreach (Limb limb in Character.AnimController.Limbs) { - if (limb.attack==null) continue; + if (limb.attack == null) continue; attackLimb = limb; if (ConvertUnits.ToDisplayUnits(Vector2.Distance(limb.SimPosition, attackSimPosition)) > limb.attack.Range) continue; - + attackingLimb = limb; - break; + break; } if (Character.IsRemotePlayer) @@ -245,36 +357,18 @@ namespace Barotrauma } if (attackLimb != null) { - steeringManager.SteeringSeek(attackSimPosition - (attackLimb.SimPosition - SimPosition)); + steeringManager.SteeringSeek(attackSimPosition - (attackLimb.SimPosition - SimPosition), 3); if (steeringManager is IndoorsSteeringManager) { var indoorsSteering = (IndoorsSteeringManager)steeringManager; - if (indoorsSteering.CurrentPath!=null && (indoorsSteering.CurrentPath.Finished || indoorsSteering.CurrentPath.Unreachable)) + if (indoorsSteering.CurrentPath != null && (indoorsSteering.CurrentPath.Finished || indoorsSteering.CurrentPath.Unreachable)) { steeringManager.SteeringManual(deltaTime, attackSimPosition - attackLimb.SimPosition); } } if (attackingLimb != null) UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition); - } - } - - private void UpdateCoolDown(Vector2 attackPosition, float deltaTime) - { - coolDownTimer -= deltaTime; - attackingLimb = null; - - if (selectedAiTarget.Entity is Hull || - Vector2.Distance(attackPosition, Character.AnimController.Limbs[0].SimPosition) < ConvertUnits.ToSimUnits(500.0f)) - { - steeringManager.SteeringSeek(attackPosition, -0.8f); - steeringManager.SteeringAvoid(deltaTime, 1.0f); - } - else - { - steeringManager.SteeringSeek(attackPosition, -0.5f); - steeringManager.SteeringAvoid(deltaTime, 1.0f); } } @@ -339,13 +433,16 @@ namespace Barotrauma updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f); coolDownTimer *= 0.1f; - if (amount > 0.1f && attackWhenProvoked) + if (amount > 0.0f && attackWhenProvoked) { - attackHumans = 100.0f; - attackRooms = 100.0f; + if (!(attacker is AICharacter) || (((AICharacter)attacker).AIController is HumanAIController)) + { + attackHumans = 100.0f; + attackRooms = 100.0f; + } } - if (attacker==null || attacker.AiTarget==null) return; + if (attacker == null || attacker.AiTarget == null) return; AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget); targetMemory.Priority += amount; } @@ -363,7 +460,112 @@ namespace Barotrauma coolDownTimer = attackCoolDown; } } + + private void UpdateCoolDown(Vector2 attackPosition, float deltaTime) + { + coolDownTimer -= deltaTime; + attackingLimb = null; + + float dist = Vector2.Distance(attackPosition, Character.SimPosition); + + if (dist < ConvertUnits.ToSimUnits(500.0f)) + { + steeringManager.SteeringSeek(attackPosition, -0.8f); + steeringManager.SteeringManual(deltaTime, Vector2.Normalize(Character.SimPosition - attackPosition) * (1.0f - (dist / 500.0f))); + } + + steeringManager.SteeringAvoid(deltaTime, 1.0f); + } + + #endregion + + #region Eat + + private void UpdateEating(float deltaTime) + { + if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) + { + state = AIState.None; + return; + } + + Limb mouthLimb = Array.Find(Character.AnimController.Limbs, l => l != null && l.MouthPos.HasValue); + if (mouthLimb == null) mouthLimb = Character.AnimController.GetLimb(LimbType.Head); + + if (mouthLimb == null) + { + DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)"); + state = AIState.None; + return; + } + + Character targetCharacter = selectedAiTarget.Entity as Character; + float eatSpeed = Character.Mass / targetCharacter.Mass * 0.1f; + + eatTimer += deltaTime * eatSpeed; + + Vector2 mouthPos = mouthLimb.SimPosition; + if (mouthLimb.MouthPos.HasValue) + { + float cos = (float)Math.Cos(mouthLimb.Rotation); + float sin = (float)Math.Sin(mouthLimb.Rotation); + + mouthPos += new Vector2( + mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin, + mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos); + } + + Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition; + + + Vector2 limbDiff = attackSimPosition - mouthPos; + float limbDist = limbDiff.Length(); + if (limbDist < 1.0f) + { + //pull the target character to the position of the mouth + //(+ make the force fluctuate to waggle the character a bit) + targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); + + //pull the character's mouth to the target character (again with a fluctuating force) + float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); + steeringManager.SteeringManual(deltaTime, limbDiff * pullStrength); + mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength); + + if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) + { + //apply damage to the target character to get some blood particles flying + targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), 10.0f, false); + + //keep severing joints until there is only one limb left + LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered); + if (nonSeveredJoints.Length == 0) + { + //only one limb left, the character is now full eaten + Entity.Spawner.AddToRemoveQueue(targetCharacter); + selectedAiTarget = null; + state = AIState.None; + } + else //sever a random joint + { + targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]); + } + } + } + else if (limbDist < 2.0f) + { + steeringManager.SteeringManual(deltaTime, limbDiff); + Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos); + } + else + { + steeringManager.SteeringSeek(attackSimPosition + (mouthPos - SimPosition), 3); + } + } + #endregion + + #region Targeting + //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 @@ -387,33 +589,62 @@ namespace Barotrauma float valueModifier = 0.0f; float dist = 0.0f; - IDamageable targetDamageable = target.Entity as IDamageable; - if (targetDamageable!=null && targetDamageable.Health <= 0.0f) continue; Character targetCharacter = target.Entity as Character; //ignore the aitarget if it is the Character itself if (targetCharacter == character) continue; - - if (targetCharacter!=null) + + if (targetCharacter != null) { - if (attackHumans == 0.0f || targetCharacter.SpeciesName != "human") continue; - - valueModifier = attackHumans; + if (targetCharacter.IsDead) + { + if (eatDeadPriority == 0.0f) continue; + valueModifier = eatDeadPriority; + } + else if (targetCharacter.SpeciesName == "human") + { + if (attackHumans == 0.0f) continue; + valueModifier = attackHumans; + } + else + { + EnemyAIController enemy = targetCharacter.AIController as EnemyAIController; + if (enemy != null) + { + if (enemy.combatStrength > combatStrength) + { + valueModifier = attackStronger; + } + else if (enemy.combatStrength < combatStrength) + { + valueModifier = attackWeaker; + } + else + { + continue; + } + } + } } else if (target.Entity!=null && attackRooms != 0.0f) { + IDamageable targetDamageable = target.Entity as IDamageable; + if (targetDamageable != null && targetDamageable.Health <= 0.0f) continue; + //skip the target if it's the room the Character is inside of if (character.AnimController.CurrentHull != null && character.AnimController.CurrentHull == target.Entity as Hull) continue; valueModifier = attackRooms; } + if (valueModifier == 0.0f) continue; + dist = Vector2.Distance(character.WorldPosition, target.WorldPosition); //if the target has been within range earlier, the character will notice it more easily //(i.e. remember where the target was) - if (targetMemories.ContainsKey(target)) dist *= 0.1f; + if (targetMemories.ContainsKey(target)) dist *= 0.5f; //ignore target if it's too far to see or hear if (dist > target.SightRange * sight && dist > target.SoundRange * hearing) continue; @@ -433,20 +664,7 @@ namespace Barotrauma Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure; - - if (targetDamageable != null) - { - valueModifier = valueModifier / targetDamageable.Health; - } - else if (closestStructure!=null) - { - valueModifier = valueModifier / ((IDamageable)closestStructure).Health; - } - else - { - valueModifier = valueModifier / 1000.0f; - } - + if (selectedAiTarget == null || Math.Abs(valueModifier) > Math.Abs(targetValue)) { selectedAiTarget = target; @@ -497,6 +715,8 @@ namespace Barotrauma } } + #endregion + public override void DebugDraw(SpriteBatch spriteBatch) { if (Character.IsDead) return; diff --git a/Barotrauma/Source/Characters/AICharacter.cs b/Barotrauma/Source/Characters/AICharacter.cs index 7a32bd5b6..7411305f7 100644 --- a/Barotrauma/Source/Characters/AICharacter.cs +++ b/Barotrauma/Source/Characters/AICharacter.cs @@ -51,7 +51,7 @@ namespace Barotrauma { switch (aiController.State) { - case AIController.AiState.Attack: + case AIController.AIState.Attack: PlaySound(CharacterSound.SoundType.Attack); break; default: diff --git a/Barotrauma/Source/Characters/Animation/AnimController.cs b/Barotrauma/Source/Characters/Animation/AnimController.cs index 8eb710cd9..b7e03d710 100644 --- a/Barotrauma/Source/Characters/Animation/AnimController.cs +++ b/Barotrauma/Source/Characters/Animation/AnimController.cs @@ -12,13 +12,25 @@ namespace Barotrauma protected Character character; - protected float walkSpeed, swimSpeed; - + protected float walkSpeed, swimSpeed; + protected float walkPos; protected readonly Vector2 stepSize; protected readonly float legTorque; - + + public float RunSpeedMultiplier + { + get; + private set; + } + + public float SwimSpeedMultiplier + { + get; + private set; + } + public AnimController(Character character, XElement element) : base(character, element) { @@ -30,6 +42,9 @@ namespace Barotrauma walkSpeed = ToolBox.GetAttributeFloat(element, "walkspeed", 1.0f); swimSpeed = ToolBox.GetAttributeFloat(element, "swimspeed", 1.0f); + RunSpeedMultiplier = ToolBox.GetAttributeFloat(element, "runspeedmultiplier", 2f); + SwimSpeedMultiplier = ToolBox.GetAttributeFloat(element, "swimspeedmultiplier", 1.5f); + legTorque = ToolBox.GetAttributeFloat(element, "legtorque", 0.0f); } @@ -37,7 +52,7 @@ namespace Barotrauma public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle) { } - public virtual void DragCharacter(Character target, LimbType rightHandTarget = LimbType.RightHand, LimbType leftHandTarget = LimbType.LeftHand) { } + public virtual void DragCharacter(Character target) { } } diff --git a/Barotrauma/Source/Characters/Animation/FishAnimController.cs b/Barotrauma/Source/Characters/Animation/FishAnimController.cs index bce305169..1d352de69 100644 --- a/Barotrauma/Source/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/Source/Characters/Animation/FishAnimController.cs @@ -15,6 +15,8 @@ namespace Barotrauma private float waveAmplitude; private float waveLength; + private float steerTorque; + private bool rotateTowardsMovement; private bool mirror, flip; @@ -23,16 +25,20 @@ namespace Barotrauma private float? footRotation; + private float deathAnimTimer, deathAnimDuration = 5.0f; + public FishAnimController(Character character, XElement element) : base(character, element) { - waveAmplitude = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "waveamplitude", 0.0f)); - waveLength = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "wavelength", 0.0f)); + waveAmplitude = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "waveamplitude", 0.0f)); + waveLength = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "wavelength", 0.0f)); + + steerTorque = ToolBox.GetAttributeFloat(element, "steertorque", 25.0f); - flip = ToolBox.GetAttributeBool(element, "flip", true); - mirror = ToolBox.GetAttributeBool(element, "mirror", false); + flip = ToolBox.GetAttributeBool(element, "flip", true); + mirror = ToolBox.GetAttributeBool(element, "mirror", false); - float footRot = ToolBox.GetAttributeFloat(element,"footrotation", float.NaN); + float footRot = ToolBox.GetAttributeFloat(element, "footrotation", float.NaN); if (float.IsNaN(footRot)) { footRotation = null; @@ -63,6 +69,12 @@ namespace Barotrauma Collider.LinearVelocity = (MainLimb.SimPosition - Collider.SimPosition) * 60.0f; Collider.SmoothRotate(MainLimb.Rotation); } + + if (character.IsDead && deathAnimTimer < deathAnimDuration) + { + deathAnimTimer += deltaTime; + UpdateDying(deltaTime); + } return; } @@ -176,12 +188,12 @@ namespace Barotrauma if (rotateTowardsMovement) { Collider.SmoothRotate(movementAngle, 25.0f); - MainLimb.body.SmoothRotate(movementAngle, 25.0f); + MainLimb.body.SmoothRotate(movementAngle, steerTorque); } else { Collider.SmoothRotate(HeadAngle * Dir, 25.0f); - MainLimb.body.SmoothRotate(HeadAngle * Dir, 25.0f); + MainLimb.body.SmoothRotate(HeadAngle * Dir, steerTorque); } Limb tail = GetLimb(LimbType.Tail); @@ -260,7 +272,7 @@ namespace Barotrauma if (limb.RefJointIndex>-1) { - RevoluteJoint refJoint = limbJoints[limb.RefJointIndex]; + RevoluteJoint refJoint = LimbJoints[limb.RefJointIndex]; footPos.X = refJoint.WorldAnchorA.X; } footPos.X += limb.StepOffset.X * Dir; @@ -297,8 +309,8 @@ namespace Barotrauma Limb head = GetLimb(LimbType.Head); Limb tail = GetLimb(LimbType.Tail); - if (head != null) head.body.ApplyTorque(head.Mass * Dir * (float)Math.Sin(walkPos) * 5.0f); - if (tail != null) tail.body.ApplyTorque(tail.Mass * -Dir * (float)Math.Sin(walkPos) * 5.0f); + if (head != null && !head.IsSevered) head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * Math.Sin(walkPos)) * 10.0f); + if (tail != null && !tail.IsSevered) tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (float)Math.Sin(walkPos)) * 10.0f); walkPos += deltaTime * 5.0f; @@ -306,9 +318,9 @@ namespace Barotrauma foreach (Limb limb in Limbs) { - if (limb.type == LimbType.Head || limb.type == LimbType.Tail) continue; + if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered) continue; - limb.body.ApplyForce((centerOfMass - limb.SimPosition) * (float)Math.Sin(walkPos) * limb.Mass * 10.0f); + limb.body.ApplyForce((centerOfMass - limb.SimPosition) * (float)(Math.Sin(walkPos) * Math.Sqrt(limb.Mass)) * 10.0f); } } diff --git a/Barotrauma/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/Source/Characters/Animation/HumanoidAnimController.cs index 74de33026..c126eaa2b 100644 --- a/Barotrauma/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/Source/Characters/Animation/HumanoidAnimController.cs @@ -571,7 +571,6 @@ namespace Barotrauma } float targetSpeed = TargetMovement.Length(); - if (targetSpeed > 0.0f) TargetMovement /= targetSpeed; if (targetSpeed > 0.1f) { @@ -633,7 +632,7 @@ namespace Barotrauma Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement * swimSpeed, movementLerp); } - walkPos += movement.Length() * 0.15f; + walkPos += movement.Length() * 0.2f; footPos = Collider.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f; for (int i = -1; i<2; i+=2) @@ -691,7 +690,7 @@ namespace Barotrauma handPos += head.LinearVelocity * 0.1f; - float handCyclePos = walkPos / 3.0f * -Dir; + float handCyclePos = walkPos / 2.0f * -Dir; float handPosX = (float)Math.Cos(handCyclePos) * 0.4f; float handPosY = (float)Math.Sin(handCyclePos) * 1.0f; handPosY = MathHelper.Clamp(handPosY, -0.8f, 0.8f); @@ -883,23 +882,48 @@ namespace Barotrauma head.pullJoint.WorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.6f + yPos); head.pullJoint.Enabled = true; } - public override void DragCharacter(Character target, LimbType rightHandTarget = LimbType.RightHand, LimbType leftHandTarget = LimbType.LeftHand) + public override void DragCharacter(Character target) { if (target == null) return; Limb leftHand = GetLimb(LimbType.LeftHand); Limb rightHand = GetLimb(LimbType.RightHand); + Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftHand); + Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightHand); + //only grab with one hand when swimming leftHand.Disabled = true; if (!inWater) rightHand.Disabled = true; for (int i = 0; i < 2; i++) { - LimbType type = i == 0 ? leftHandTarget : rightHandTarget; - Limb targetLimb = target.AnimController.GetLimb(type); + Limb targetLimb = target.AnimController.GetLimb(LimbType.Torso); - Limb pullLimb = GetLimb(i == 0 ? LimbType.LeftHand : LimbType.RightHand); + if (i == 0) + { + if (!targetLeftHand.IsSevered) + { + targetLimb = targetLeftHand; + } + else if (!targetRightHand.IsSevered) + { + targetLimb = targetRightHand; + } + } + else + { + if (!targetRightHand.IsSevered) + { + targetLimb = targetRightHand; + } + else if (!targetLeftHand.IsSevered) + { + targetLimb = targetLeftHand; + } + } + + Limb pullLimb = i == 0 ? leftHand : rightHand; if (i == 1 && inWater) { @@ -1013,7 +1037,7 @@ namespace Barotrauma itemAngle = (torso.body.Rotation + holdAngle * Dir); } - Vector2 shoulderPos = limbJoints[2].WorldAnchorA; + Vector2 shoulderPos = LimbJoints[2].WorldAnchorA; Vector2 transformedHoldPos = shoulderPos; if (itemPos == Vector2.Zero || Anim == Animation.Climbing || usingController) @@ -1072,7 +1096,7 @@ namespace Barotrauma private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) { - Vector2 shoulderPos = limbJoints[2].WorldAnchorA; + Vector2 shoulderPos = LimbJoints[2].WorldAnchorA; Limb arm = (hand.type == LimbType.LeftHand) ? GetLimb(LimbType.LeftArm) : GetLimb(LimbType.RightArm); diff --git a/Barotrauma/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/Source/Characters/Animation/Ragdoll.cs index f069d5d1f..bde487a1e 100644 --- a/Barotrauma/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/Source/Characters/Animation/Ragdoll.cs @@ -28,17 +28,13 @@ namespace Barotrauma if (frozen == value) return; frozen = value; - - /*foreach (Limb l in Limbs) - { - l.body.PhysEnabled = !frozen; - }*/ + Collider.PhysEnabled = !frozen; } } private Dictionary limbDictionary; - public RevoluteJoint[] limbJoints; + public LimbJoint[] LimbJoints; private bool simplePhysicsEnabled; @@ -163,18 +159,20 @@ namespace Barotrauma foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; limb.body.Enabled = !simplePhysicsEnabled; } - foreach (RevoluteJoint joint in limbJoints) + foreach (LimbJoint joint in LimbJoints) { - joint.Enabled = !simplePhysicsEnabled; + joint.Enabled = !joint.IsSevered && !simplePhysicsEnabled; } if (!simplePhysicsEnabled) { foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; limb.body.SetTransform(Collider.SimPosition, Collider.Rotation); } } @@ -278,9 +276,9 @@ namespace Barotrauma float scale = ToolBox.GetAttributeFloat(element, "scale", 1.0f); - Limbs = new Limb[element.Elements("limb").Count()]; - limbJoints = new RevoluteJoint[element.Elements("joint").Count()]; - limbDictionary = new Dictionary(); + Limbs = new Limb[element.Elements("limb").Count()]; + LimbJoints = new LimbJoint[element.Elements("joint").Count()]; + limbDictionary = new Dictionary(); headPosition = ToolBox.GetAttributeFloat(element, "headposition", 50.0f); headPosition = ConvertUnits.ToSimUnits(headPosition); @@ -346,7 +344,7 @@ namespace Barotrauma UpdateCollisionCategories(); - foreach (var joint in limbJoints) + foreach (var joint in LimbJoints) { joint.BodyB.SetTransform( joint.BodyA.Position + (joint.LocalAnchorA - joint.LocalAnchorB)*0.1f, @@ -385,9 +383,8 @@ namespace Barotrauma Vector2 limb2Pos = ToolBox.GetAttributeVector2(subElement, "limb2anchor", Vector2.Zero) * scale; limb2Pos = ConvertUnits.ToSimUnits(limb2Pos); - RevoluteJoint joint = new RevoluteJoint(Limbs[limb1ID].body.FarseerBody, Limbs[limb2ID].body.FarseerBody, limb1Pos, limb2Pos); - - joint.CollideConnected = false; + LimbJoint joint = new LimbJoint(Limbs[limb1ID], Limbs[limb2ID], limb1Pos, limb2Pos); + joint.CanBeSevered = ToolBox.GetAttributeBool(subElement, "canbesevered", false); if (subElement.Attribute("lowerlimit") != null) { @@ -396,22 +393,18 @@ namespace Barotrauma joint.UpperLimit = float.Parse(subElement.Attribute("upperlimit").Value) * ((float)Math.PI / 180.0f); } - joint.MotorEnabled = true; - joint.MaxMotorTorque = 0.25f; - GameMain.World.AddJoint(joint); - for (int i = 0; i < limbJoints.Length; i++) + for (int i = 0; i < LimbJoints.Length; i++) { - if (limbJoints[i] != null) continue; + if (LimbJoints[i] != null) continue; - limbJoints[i] = joint; + LimbJoints[i] = joint; return; } - Array.Resize(ref limbJoints, limbJoints.Length + 1); - limbJoints[limbJoints.Length - 1] = joint; - + Array.Resize(ref LimbJoints, LimbJoints.Length + 1); + LimbJoints[LimbJoints.Length - 1] = joint; } public void AddLimb(Limb limb) @@ -509,9 +502,9 @@ namespace Barotrauma { Limb limb = (Limb)f1.Body.UserData; - if (impact > 3.0f && limb.HitSound != null && limb.soundTimer <= 0.0f) + if (impact > 3.0f && limb.HitSound != null && limb.SoundTimer <= 0.0f) { - limb.soundTimer = Limb.SoundInterval; + limb.SoundTimer = Limb.SoundInterval; limb.HitSound.Play(volume, impact * 100.0f, limb.WorldPosition); } } @@ -531,6 +524,51 @@ namespace Barotrauma } } + public void SeverLimbJoint(LimbJoint limbJoint) + { + limbJoint.IsSevered = true; + limbJoint.Enabled = false; + + List connectedLimbs = new List(); + List checkedJoints = new List(); + + GetConnectedLimbs(connectedLimbs, checkedJoints, MainLimb); + foreach (Limb limb in Limbs) + { + if (!connectedLimbs.Contains(limb)) + { + limb.IsSevered = true; + } + } + + } + + private void GetConnectedLimbs(List connectedLimbs, List checkedJoints, Limb limb) + { + connectedLimbs.Add(limb); + + foreach (LimbJoint joint in LimbJoints) + { + if (joint.IsSevered || checkedJoints.Contains(joint)) continue; + if (joint.LimbA == limb) + { + if (!connectedLimbs.Contains(joint.LimbB)) + { + checkedJoints.Add(joint); + GetConnectedLimbs(connectedLimbs, checkedJoints, joint.LimbB); + } + } + else if (joint.LimbB == limb) + { + if (!connectedLimbs.Contains(joint.LimbA)) + { + checkedJoints.Add(joint); + GetConnectedLimbs(connectedLimbs, checkedJoints, joint.LimbA); + } + } + } + } + public virtual void Draw(SpriteBatch spriteBatch) { if (simplePhysicsEnabled) return; @@ -559,13 +597,15 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Red, true, 0.01f); } - limb.body.DebugDraw(spriteBatch, inWater ? Color.Cyan : Color.White); + Color limbColor = inWater ? Color.Cyan : Color.White; + if (limb.IsSevered) limbColor = Color.Red; + limb.body.DebugDraw(spriteBatch, limbColor); } Collider.DebugDraw(spriteBatch, frozen ? Color.Red : (inWater ? Color.SkyBlue : Color.Gray)); GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.ToString(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange); - foreach (RevoluteJoint joint in limbJoints) + foreach (RevoluteJoint joint in LimbJoints) { Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true); @@ -619,22 +659,24 @@ namespace Barotrauma { dir = (dir == Direction.Left) ? Direction.Right : Direction.Left; - for (int i = 0; i < limbJoints.Length; i++) + for (int i = 0; i < LimbJoints.Length; i++) { - float lowerLimit = -limbJoints[i].UpperLimit; - float upperLimit = -limbJoints[i].LowerLimit; + float lowerLimit = -LimbJoints[i].UpperLimit; + float upperLimit = -LimbJoints[i].LowerLimit; - limbJoints[i].LowerLimit = lowerLimit; - limbJoints[i].UpperLimit = upperLimit; + LimbJoints[i].LowerLimit = lowerLimit; + LimbJoints[i].UpperLimit = upperLimit; - limbJoints[i].LocalAnchorA = new Vector2(-limbJoints[i].LocalAnchorA.X, limbJoints[i].LocalAnchorA.Y); - limbJoints[i].LocalAnchorB = new Vector2(-limbJoints[i].LocalAnchorB.X, limbJoints[i].LocalAnchorB.Y); + LimbJoints[i].LocalAnchorA = new Vector2(-LimbJoints[i].LocalAnchorA.X, LimbJoints[i].LocalAnchorA.Y); + LimbJoints[i].LocalAnchorB = new Vector2(-LimbJoints[i].LocalAnchorB.X, LimbJoints[i].LocalAnchorB.Y); } foreach (Limb limb in Limbs) { - if (limb == null) continue; + if (limb == null || limb.IsSevered) continue; + + limb.Dir = Dir; if (limb.sprite != null) { @@ -643,12 +685,13 @@ namespace Barotrauma limb.sprite.Origin = spriteOrigin; } - limb.Dir = Dir; - - /*if (limb.LightSource != null) + + if (limb.MouthPos.HasValue) { - limb.LightSource.FlipX(); - }*/ + limb.MouthPos = new Vector2( + -limb.MouthPos.Value.X, + limb.MouthPos.Value.Y); + } if (limb.pullJoint != null) { @@ -663,12 +706,15 @@ namespace Barotrauma public Vector2 GetCenterOfMass() { Vector2 centerOfMass = Vector2.Zero; + float totalMass = 0.0f; foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; centerOfMass += limb.Mass * limb.SimPosition; + totalMass += limb.Mass; } - centerOfMass /= Mass; + centerOfMass /= totalMass; return centerOfMass; } @@ -769,6 +815,7 @@ namespace Barotrauma { foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; if (limb.body.FarseerBody.ContactList == null) continue; ContactEdge ce = limb.body.FarseerBody.ContactList; @@ -781,6 +828,7 @@ namespace Barotrauma foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; limb.body.LinearVelocity += velocityChange; } @@ -805,7 +853,7 @@ namespace Barotrauma foreach (Limb limb in Limbs) { - if (limb.ignoreCollisions) continue; + if (limb.ignoreCollisions || limb.IsSevered) continue; try { @@ -1145,6 +1193,7 @@ namespace Barotrauma foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; //check visibility from the new position of the collider to the new position of this limb Vector2 movePos = limb.SimPosition + limbMoveAmount; @@ -1208,6 +1257,7 @@ namespace Barotrauma //(in case the ragdoll has gotten stuck somewhere) foreach (Limb limb in Limbs) { + if (limb.IsSevered) continue; limb.body.CollidesWith = Physics.CollisionNone; } @@ -1463,22 +1513,28 @@ namespace Barotrauma public void Remove() { - foreach (Limb l in Limbs) + if (Limbs != null) { - l.Remove(); + foreach (Limb l in Limbs) + { + l.Remove(); + } + Limbs = null; } - Limbs = null; foreach (PhysicsBody b in collider) { b.Remove(); } - foreach (RevoluteJoint joint in limbJoints) + if (LimbJoints != null) { - GameMain.World.RemoveJoint(joint); + foreach (RevoluteJoint joint in LimbJoints) + { + GameMain.World.RemoveJoint(joint); + } + LimbJoints = null; } - limbJoints = null; list.Remove(this); } diff --git a/Barotrauma/Source/Characters/Attack.cs b/Barotrauma/Source/Characters/Attack.cs index ee2d24c04..dcdf7dd0e 100644 --- a/Barotrauma/Source/Characters/Attack.cs +++ b/Barotrauma/Source/Characters/Attack.cs @@ -18,7 +18,7 @@ namespace Barotrauma { public readonly float Damage; public readonly float Bleeding; - + public readonly bool HitArmor; public AttackResult(float damage, float bleeding, bool hitArmor=false) @@ -49,6 +49,12 @@ namespace Barotrauma public readonly float TargetForce; + public readonly float SeverLimbsProbability; + + //the indices of the limbs Force is applied on + //(if none, force is applied only to the limb the attack is attached to) + public readonly List ApplyForceOnLimbs; + private Sound sound; private ParticleEmitterPrefab particleEmitterPrefab; @@ -91,29 +97,41 @@ namespace Barotrauma DamageType = DamageType.None; } - - damage = ToolBox.GetAttributeFloat(element, "damage", 0.0f); + damage = ToolBox.GetAttributeFloat(element, "damage", 0.0f); structureDamage = ToolBox.GetAttributeFloat(element, "structuredamage", 0.0f); - bleedingDamage = ToolBox.GetAttributeFloat(element, "bleedingdamage", 0.0f); + bleedingDamage = ToolBox.GetAttributeFloat(element, "bleedingdamage", 0.0f); + Stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f); - Force = ToolBox.GetAttributeFloat(element,"force", 0.0f); - TargetForce = ToolBox.GetAttributeFloat(element, "targetforce", 0.0f); - - Torque = ToolBox.GetAttributeFloat(element, "torque", 0.0f); - - Stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f); + SeverLimbsProbability = ToolBox.GetAttributeFloat(element, "severlimbsprobability", 0.0f); + Force = ToolBox.GetAttributeFloat(element, "force", 0.0f); + TargetForce = ToolBox.GetAttributeFloat(element, "targetforce", 0.0f); + Torque = ToolBox.GetAttributeFloat(element, "torque", 0.0f); + string soundPath = ToolBox.GetAttributeString(element, "sound", ""); if (!string.IsNullOrWhiteSpace(soundPath)) { sound = Sound.Load(soundPath); } - Range = ToolBox.GetAttributeFloat(element, "range", 0.0f); + Range = ToolBox.GetAttributeFloat(element, "range", 0.0f); + Duration = ToolBox.GetAttributeFloat(element, "duration", 0.0f); - Duration = ToolBox.GetAttributeFloat(element, "duration", 0.0f); - - priority = ToolBox.GetAttributeFloat(element, "priority", 1.0f); + priority = ToolBox.GetAttributeFloat(element, "priority", 1.0f); + + string limbIndicesStr = ToolBox.GetAttributeString(element, "applyforceonlimbs", ""); + if (!string.IsNullOrWhiteSpace(limbIndicesStr)) + { + ApplyForceOnLimbs = new List(); + foreach (string limbIndexStr in limbIndicesStr.Split(',')) + { + int limbIndex; + if (int.TryParse(limbIndexStr, out limbIndex)) + { + ApplyForceOnLimbs.Add(limbIndex); + } + } + } foreach (XElement subElement in element.Elements()) { diff --git a/Barotrauma/Source/Characters/Character.cs b/Barotrauma/Source/Characters/Character.cs index 0e133f1fa..471a0aab2 100644 --- a/Barotrauma/Source/Characters/Character.cs +++ b/Barotrauma/Source/Characters/Character.cs @@ -785,16 +785,14 @@ namespace Barotrauma if (IsKeyDown(InputType.Run)) { //can't run if - // - not a humanoid // - dragging someone // - crouching // - moving backwards - if (AnimController is HumanoidAnimController && - selectedCharacter == null && - !((HumanoidAnimController)AnimController).Crouching && + if (selectedCharacter == null && + (!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) && Math.Sign(targetMovement.X) != -Math.Sign(AnimController.Dir)) { - targetMovement *= 3.0f; + targetMovement *= AnimController.InWater ? AnimController.SwimSpeedMultiplier : AnimController.RunSpeedMultiplier; } } @@ -1161,9 +1159,10 @@ namespace Barotrauma } } - if (moveCam && needsAir) + if (moveCam) { - if (pressureProtection < 80.0f && + if (needsAir && + pressureProtection < 80.0f && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 50.0f)) { float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; @@ -1172,7 +1171,16 @@ namespace Barotrauma (pressure / 50.0f) * Rand.Range(1.0f, 1.05f), (pressure - 50.0f) / 50.0f); } - cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 250.0f, 0.05f); + + if (IsHumanoid) + { + cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 250.0f, deltaTime); + } + else + { + //increased visibility range when controlling large a non-humanoid + cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, MathHelper.Clamp(Mass, 250.0f, 800.0f), deltaTime); + } } cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition); @@ -1721,46 +1729,77 @@ namespace Barotrauma public virtual AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false) { - var attackResult = AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound, attack.TargetForce); + Limb limbHit = null; + var attackResult = AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound, attack.TargetForce, out limbHit); + if (limbHit == null) return new AttackResult(); var attackingCharacter = attacker as Character; if (attackingCharacter != null && attackingCharacter.AIController == null) { GameServer.Log(Name + " attacked by " + attackingCharacter.Name+". Damage: "+attackResult.Damage+" Bleeding damage: "+attackResult.Bleeding, ServerLog.MessageType.Attack); } + + if (GameMain.Client == null && + isDead && + health - attackResult.Damage <= minHealth && Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability) + { + foreach (LimbJoint joint in AnimController.LimbJoints) + { + if (joint.CanBeSevered && (joint.LimbA == limbHit || joint.LimbB == limbHit)) + { + AnimController.SeverLimbJoint(joint); + + if (joint.LimbA == limbHit) + { + joint.LimbB.body.LinearVelocity += limbHit.LinearVelocity * 0.5f; + } + else + { + joint.LimbA.body.LinearVelocity += limbHit.LinearVelocity * 0.5f; + } + } + } + } return attackResult; } public AttackResult AddDamage(Vector2 worldPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound, float attackForce = 0.0f) { + Limb temp = null; + return AddDamage(worldPosition, damageType, amount, bleedingAmount, stun, playSound, attackForce, out temp); + } + + public AttackResult AddDamage(Vector2 worldPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound, float attackForce, out Limb hitLimb) + { + hitLimb = null; + if (Removed) return new AttackResult(); SetStun(stun); - - Limb closestLimb = null; + float closestDistance = 0.0f; foreach (Limb limb in AnimController.Limbs) { float distance = Vector2.Distance(worldPosition, limb.WorldPosition); - if (closestLimb == null || distance < closestDistance) + if (hitLimb == null || distance < closestDistance) { - closestLimb = limb; + hitLimb = limb; closestDistance = distance; } } if (Math.Abs(attackForce) > 0.0f) { - closestLimb.body.ApplyForce((closestLimb.WorldPosition - worldPosition) * attackForce); + Vector2 diff = hitLimb.WorldPosition - worldPosition; + if (diff == Vector2.Zero) diff = Rand.Vector(1.0f); + hitLimb.body.ApplyForce(Vector2.Normalize(diff) * attackForce, hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff)); } - AttackResult attackResult = closestLimb.AddDamage(worldPosition, damageType, amount, bleedingAmount, playSound); + AttackResult attackResult = hitLimb.AddDamage(worldPosition, damageType, amount, bleedingAmount, playSound); AddDamage(damageType == DamageType.Burn ? CauseOfDeath.Burn : causeOfDeath, attackResult.Damage, null); - - //health -= attackResult.Damage; - //if (health <= 0.0f && damageType == DamageType.Burn) Kill(CauseOfDeath.Burn); + if (DoesBleed) { Bleeding += attackResult.Bleeding; @@ -1834,7 +1873,7 @@ namespace Barotrauma new Vector2(Rand.Range(-50f, 50f), Rand.Range(-100f, 50f))); } - foreach (var joint in AnimController.limbJoints) + foreach (var joint in AnimController.LimbJoints) { joint.LimitEnabled = false; } @@ -1884,20 +1923,14 @@ namespace Barotrauma { if (selectedItems[i] != null) selectedItems[i].Drop(this); } - - if (aiTarget != null) - { - aiTarget.Remove(); - aiTarget = null; - } - + foreach (Limb limb in AnimController.Limbs) { if (limb.pullJoint == null) continue; limb.pullJoint.Enabled = false; } - foreach (RevoluteJoint joint in AnimController.limbJoints) + foreach (RevoluteJoint joint in AnimController.LimbJoints) { joint.MotorEnabled = false; } @@ -1916,9 +1949,16 @@ namespace Barotrauma Health = Math.Max(maxHealth * 0.1f, health); - foreach (RevoluteJoint joint in AnimController.limbJoints) + foreach (LimbJoint joint in AnimController.LimbJoints) { joint.MotorEnabled = true; + joint.Enabled = true; + joint.IsSevered = false; + } + + foreach (Limb limb in AnimController.Limbs) + { + limb.IsSevered = false; } if (GameMain.GameSession != null) @@ -1929,6 +1969,13 @@ namespace Barotrauma public override void Remove() { +#if DEBUG + if (Removed) + { + DebugConsole.ThrowError("Attempting to remove an already removed character\n" + Environment.StackTrace); + } +#endif + base.Remove(); if (info != null) info.Remove(); @@ -1939,7 +1986,7 @@ namespace Barotrauma if (GameMain.Client != null && GameMain.Client.Character == this) GameMain.Client.Character = null; - if (aiTarget != null) aiTarget.Remove(); + if (aiTarget != null) aiTarget.Remove(); if (AnimController != null) AnimController.Remove(); diff --git a/Barotrauma/Source/Characters/CharacterNetworking.cs b/Barotrauma/Source/Characters/CharacterNetworking.cs index 0f52f1c80..83ef5b208 100644 --- a/Barotrauma/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/Source/Characters/CharacterNetworking.cs @@ -638,6 +638,19 @@ namespace Barotrauma if (isDead) { msg.Write((byte)causeOfDeath); + List severedJointIndices = new List(); + for (int i = 0; i < AnimController.LimbJoints.Length; i++) + { + if (AnimController.LimbJoints[i] != null && AnimController.LimbJoints[i].IsSevered) + { + severedJointIndices.Add(i); + } + } + msg.Write((byte)severedJointIndices.Count); + foreach (int jointIndex in severedJointIndices) + { + msg.Write((byte)jointIndex); + } } else { @@ -677,6 +690,7 @@ namespace Barotrauma if (isDead) { causeOfDeath = (CauseOfDeath)msg.ReadByte(); + byte severedLimbCount = msg.ReadByte(); if (causeOfDeath == CauseOfDeath.Pressure) { Implode(true); @@ -685,6 +699,11 @@ namespace Barotrauma { Kill(causeOfDeath, true); } + for (int i = 0; i < severedLimbCount; i++) + { + int severedJointIndex = msg.ReadByte(); + AnimController.SeverLimbJoint(AnimController.LimbJoints[severedJointIndex]); + } } else { diff --git a/Barotrauma/Source/Characters/Limb.cs b/Barotrauma/Source/Characters/Limb.cs index 2fa474003..fb61f3c50 100644 --- a/Barotrauma/Source/Characters/Limb.cs +++ b/Barotrauma/Source/Characters/Limb.cs @@ -19,11 +19,33 @@ namespace Barotrauma LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist }; + class LimbJoint : RevoluteJoint + { + public bool IsSevered; + public bool CanBeSevered; + + public readonly Limb LimbA, LimbB; + + public LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2) + : base(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2) + { + CollideConnected = false; + MotorEnabled = true; + MaxMotorTorque = 0.25f; + + LimbA = limbA; + LimbB = limbB; + } + } + class Limb { private const float LimbDensity = 15; private const float LimbAngularDamping = 7; + //how long it takes for severed limbs to fade out + private const float SeveredFadeOutTime = 10.0f; + public readonly Character character; //the physics body of the limb @@ -51,12 +73,17 @@ namespace Barotrauma private float damage, burnt; + private bool isSevered; + private float severedFadeOutTimer; + private readonly Vector2 armorSector; private readonly float armorValue; - Sound hitSound; + public Vector2? MouthPos; + + private Sound hitSound; //a timer for delaying when a hitsound/attacksound can be played again - public float soundTimer; + public float SoundTimer; public const float SoundInterval = 0.4f; public readonly Attack attack; @@ -66,7 +93,24 @@ namespace Barotrauma private List wearingItems; private Vector2 animTargetPos; + + private float scale; + public float AttackTimer; + + public bool IsSevered + { + get { return isSevered; } + set + { + isSevered = value; + if (isSevered) + { + damage = 100.0f; + } + } + } + public bool DoesFlip { get { return doesFlip; } @@ -92,6 +136,11 @@ namespace Barotrauma get { return body.Rotation; } } + public float Scale + { + get { return scale; } + } + //where an animcontroller is trying to pull the limb, only used for debug visualization public Vector2 AnimTargetPos { @@ -135,55 +184,23 @@ namespace Barotrauma { get { return stepOffset; } } - + public float Burnt { get { return burnt; } set { burnt = MathHelper.Clamp(value,0.0f,100.0f); } } - - private float scale; - - public float AttackTimer; - - //public float Damage - //{ - // get { return damage; } - // set - // { - // damage = Math.Max(value, 0.0f); - // if (damage >=maxHealth) Character.Kill(); - // } - //} - - //public float MaxHealth - //{ - // get { return maxHealth; } - //} - - //public float Bleeding - //{ - // get { return bleeding; } - // set { bleeding = MathHelper.Clamp(value, 0.0f, 100.0f); } - //} - + public List WearingItems { get { return wearingItems; } - set { wearingItems = value; } } - - //public WearableSprite WearingItemSprite - //{ - // get { return wearingItemSprite; } - // set { wearingItemSprite = value; } - //} - + public Limb (Character character, XElement element, float scale = 1.0f) { this.character = character; - WearingItems = new List(); + wearingItems = new List(); dir = Direction.Right; @@ -255,7 +272,12 @@ namespace Barotrauma armorSector.Y = MathHelper.ToRadians(armorSector.Y); armorValue = Math.Max(ToolBox.GetAttributeFloat(element, "armor", 0.0f), 0.0f); - + + if (element.Attribute("mouthpos") != null) + { + MouthPos = ConvertUnits.ToSimUnits(ToolBox.GetAttributeVector2(element, "mouthpos", Vector2.Zero)); + } + body.BodyType = BodyType.Dynamic; body.FarseerBody.AngularDamping = LimbAngularDamping; @@ -340,7 +362,7 @@ namespace Barotrauma bool hitArmor = false; float totalArmorValue = 0.0f; - if (armorValue>0.0f && SectorHit(armorSector, position)) + if (armorValue > 0.0f && SectorHit(armorSector, position)) { hitArmor = true; totalArmorValue += armorValue; @@ -354,8 +376,7 @@ namespace Barotrauma hitArmor = true; totalArmorValue += wearable.WearableComponent.ArmorValue; } - } - + } if (hitArmor) { @@ -371,28 +392,19 @@ namespace Barotrauma SoundPlayer.PlayDamageSound(damageSoundType, amount, position); } - //Bleeding += bleedingAmount; - //Damage += amount; + float bloodParticleAmount = hitArmor || bleedingAmount <= 0.0f ? 0 : (int)Math.Min(amount / 5, 10); + float bloodParticleSize = MathHelper.Clamp(amount / 50.0f, 0.1f, 1.0f); - float bloodAmount = hitArmor || bleedingAmount<=0.0f ? 0 : (int)Math.Min((int)(amount * 2.0f), 20); + for (int i = 0; i < bloodParticleAmount; i++) + { + var blood = GameMain.ParticleManager.CreateParticle(inWater ? "waterblood" : "blood", WorldPosition, Vector2.Zero, 0.0f, character.AnimController.CurrentHull); + if (blood != null) + { + blood.Size *= bloodParticleSize; + } + } - for (int i = 0; i < bloodAmount; i++) - { - Vector2 particleVel = SimPosition - position; - if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel); - - GameMain.ParticleManager.CreateParticle("blood", - WorldPosition, - particleVel * Rand.Range(100.0f, 300.0f), 0.0f, character.AnimController.CurrentHull); - } - - for (int i = 0; i < bloodAmount / 2; i++) - { - GameMain.ParticleManager.CreateParticle("waterblood", WorldPosition, Vector2.Zero, 0.0f, character.AnimController.CurrentHull); - } - - damage += Math.Max(amount,bleedingAmount) / character.MaxHealth * 100.0f; - + damage += Math.Max(amount, bleedingAmount) / character.MaxHealth * 100.0f; return new AttackResult(amount, bleedingAmount, hitArmor); } @@ -418,9 +430,7 @@ namespace Barotrauma { LightSource.ParentSub = body.Submarine; } - - if (!character.IsDead) damage = Math.Max(0.0f, damage-deltaTime*0.1f); - + if (burnt > 0.0f) Burnt -= deltaTime; if (LinearVelocity.X > 500.0f) @@ -435,9 +445,19 @@ namespace Barotrauma body.ApplyWaterForces(); } + if (isSevered) + { + severedFadeOutTimer += deltaTime; + if (severedFadeOutTimer > SeveredFadeOutTime) + { + body.Enabled = false; + } + } + if (character.IsDead) return; - soundTimer -= deltaTime; + damage = Math.Max(0.0f, damage - deltaTime * 0.1f); + SoundTimer -= deltaTime; //if (MathUtils.RandomFloat(0.0f, 1000.0f) < Bleeding) //{ @@ -446,12 +466,7 @@ namespace Barotrauma // SimPosition, Vector2.Zero); //} } - - public void ActivateDamagedSprite() - { - damage = 100.0f; - } - + public void UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget) { float dist = ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition)); @@ -464,19 +479,34 @@ namespace Barotrauma { if (AttackTimer >= attack.Duration && damageTarget != null) { - attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, (soundTimer <= 0.0f)); + attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, (SoundTimer <= 0.0f)); - soundTimer = Limb.SoundInterval; + SoundTimer = SoundInterval; } } Vector2 diff = attackPosition - SimPosition; - if (diff.LengthSquared() > 0.00001f) + if (diff.LengthSquared() < 0.00001f) return; + + if (attack.ApplyForceOnLimbs != null) { - Vector2 pos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA; - body.ApplyLinearImpulse(Mass * attack.Force * - Vector2.Normalize(attackPosition - SimPosition), pos); + foreach (int limbIndex in attack.ApplyForceOnLimbs) + { + if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) continue; + + Limb limb = character.AnimController.Limbs[limbIndex]; + Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA; + limb.body.ApplyLinearImpulse( + limb.Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos); + } } + else + { + Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA; + body.ApplyLinearImpulse(Mass * attack.Force * + Vector2.Normalize(attackPosition - SimPosition), forcePos); + } + } public void Draw(SpriteBatch spriteBatch) @@ -484,10 +514,21 @@ namespace Barotrauma float brightness = 1.0f - (burnt / 100.0f) * 0.5f; Color color = new Color(brightness, brightness, brightness); + if (isSevered) + { + if (severedFadeOutTimer > SeveredFadeOutTime) + { + return; + } + else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f) + { + color *= SeveredFadeOutTime - severedFadeOutTimer; + } + } + body.Dir = Dir; bool hideLimb = wearingItems.Any(w => w != null && w.HideLimb); - if (!hideLimb) { body.Draw(spriteBatch, sprite, color, null, scale); diff --git a/Barotrauma/Source/Events/MonsterEvent.cs b/Barotrauma/Source/Events/MonsterEvent.cs index bdc894828..9bc7a5cdd 100644 --- a/Barotrauma/Source/Events/MonsterEvent.cs +++ b/Barotrauma/Source/Events/MonsterEvent.cs @@ -17,6 +17,8 @@ namespace Barotrauma private bool spawnDeep; private bool disallowed; + + private bool repeat; private Level.PositionType spawnPosType; @@ -39,7 +41,9 @@ namespace Barotrauma { characterFile = ToolBox.GetAttributeString(element, "characterfile", ""); - minAmount = ToolBox.GetAttributeInt(element, "minamount", 1); + int defaultAmount = ToolBox.GetAttributeInt(element, "amount", 1); + + minAmount = ToolBox.GetAttributeInt(element, "minamount", defaultAmount); maxAmount = Math.Max(ToolBox.GetAttributeInt(element, "maxamount", 1), minAmount); var spawnPosTypeStr = ToolBox.GetAttributeString(element, "spawntype", ""); @@ -52,6 +56,8 @@ namespace Barotrauma spawnDeep = ToolBox.GetAttributeBool(element, "spawndeep", false); + repeat = ToolBox.GetAttributeBool(element, "repeat", repeat); + if (GameMain.NetworkMember != null) { List monsterNames = GameMain.NetworkMember.monsterEnabled.Keys.ToList(); @@ -67,18 +73,17 @@ namespace Barotrauma { base.Init(); - SpawnMonsters(); + SpawnMonsters(Rand.Range(minAmount, maxAmount, false)); } - private void SpawnMonsters() + private Character[] SpawnMonsters(int amount) { - if (disallowed) return; + if (disallowed) return null; Vector2 spawnPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true); - int amount = Rand.Range(minAmount, maxAmount, false); - monsters = new Character[amount]; + var monsters = new Character[amount]; if (spawnDeep) spawnPos.Y -= Level.Loaded.Size.Y; @@ -88,6 +93,8 @@ namespace Barotrauma spawnPos.Y += Rand.Range(-0.5f, 0.5f, false); monsters[i] = Character.Create(characterFile, spawnPos, null, GameMain.Client != null); } + + return monsters; } public override void Update(float deltaTime) @@ -97,7 +104,29 @@ namespace Barotrauma Finished(); return; } - if (monsters == null) SpawnMonsters(); + + if (monsters == null) + { + monsters = SpawnMonsters(Rand.Range(minAmount, maxAmount, false)); + return; + } + + if (repeat) + { + //clients aren't allowed to spawn more monsters mid-round + if (GameMain.Client != null) + { + return; + } + + for (int i = 0; i < monsters.Length; i++) + { + if (monsters[i] == null || monsters[i].Removed || monsters[i].IsDead) + { + monsters[i] = SpawnMonsters(1)[0]; + } + } + } if (isFinished) return; @@ -128,7 +157,7 @@ namespace Barotrauma } } - if (monstersDead) Finished(); + if (monstersDead && !repeat) Finished(); } } } diff --git a/Barotrauma/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs index fbd40ca05..0a391d901 100644 --- a/Barotrauma/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -655,7 +655,7 @@ namespace Barotrauma.Tutorials enemy.Health = 50.0f; - enemy.AIController.State = AIController.AiState.None; + enemy.AIController.State = AIController.AIState.None; Vector2 targetPos = Character.Controlled.WorldPosition + new Vector2(0.0f, 3000.0f); diff --git a/Barotrauma/Source/Items/Components/Projectile.cs b/Barotrauma/Source/Items/Components/Projectile.cs index c525bb5a1..999c35bb9 100644 --- a/Barotrauma/Source/Items/Components/Projectile.cs +++ b/Barotrauma/Source/Items/Components/Projectile.cs @@ -60,6 +60,8 @@ namespace Barotrauma.Items.Components { if (character != null && !characterUsable) return false; + User = character; + Launch(new Vector2( (float)Math.Cos(item.body.Rotation), (float)Math.Sin(item.body.Rotation)) * launchImpulse * item.body.Mass); @@ -147,7 +149,7 @@ namespace Barotrauma.Items.Components return false; } - AttackResult attackResult = new AttackResult(0.0f, 0.0f); + AttackResult attackResult = new AttackResult(); if (attack != null) { var submarine = f2.Body.UserData as Submarine; diff --git a/Barotrauma/Source/Items/Components/Turret.cs b/Barotrauma/Source/Items/Components/Turret.cs index c97085f14..5297de04e 100644 --- a/Barotrauma/Source/Items/Components/Turret.cs +++ b/Barotrauma/Source/Items/Components/Turret.cs @@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components } } - Launch(projectiles[0].Item); + Launch(projectiles[0].Item, character); if (character != null) { @@ -200,7 +200,7 @@ namespace Barotrauma.Items.Components return true; } - private void Launch(Item projectile) + private void Launch(Item projectile, Character user = null) { reload = reloadTime; @@ -217,6 +217,7 @@ namespace Barotrauma.Items.Components if (projectileComponent != null) { projectileComponent.Use((float)Timing.Step); + projectileComponent.User = user; } if (projectile.Container != null) projectile.Container.RemoveContained(projectile); diff --git a/Barotrauma/Source/Map/Explosion.cs b/Barotrauma/Source/Map/Explosion.cs index 85705f051..cb88f3a08 100644 --- a/Barotrauma/Source/Map/Explosion.cs +++ b/Barotrauma/Source/Map/Explosion.cs @@ -90,7 +90,7 @@ namespace Barotrauma if (force == 0.0f && attack.Stun == 0.0f && attack.GetDamage(1.0f) == 0.0f) return; - ApplyExplosionForces(worldPosition, attack.Range, force, attack.GetDamage(1.0f), attack.Stun); + ApplyExplosionForces(worldPosition, attack, force); if (flames && GameMain.Client == null) { @@ -141,9 +141,9 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - public static void ApplyExplosionForces(Vector2 worldPosition, float range, float force, float damage = 0.0f, float stun = 0.0f) + public static void ApplyExplosionForces(Vector2 worldPosition, Attack attack, float force) { - if (range <= 0.0f) return; + if (attack.Range <= 0.0f) return; foreach (Character c in Character.CharacterList) { @@ -152,6 +152,7 @@ namespace Barotrauma explosionPos = ConvertUnits.ToSimUnits(explosionPos); + bool wasDead = c.IsDead; foreach (Limb limb in c.AnimController.Limbs) { float dist = Vector2.Distance(limb.WorldPosition, worldPosition); @@ -160,24 +161,36 @@ namespace Barotrauma //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius); dist = Math.Max(0.0f, dist - FarseerPhysics.ConvertUnits.ToDisplayUnits(limbRadius)); + + if (dist > attack.Range) continue; - if (dist > range) continue; - - float distFactor = 1.0f - dist / range; + float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion by 90% if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) distFactor *= 0.1f; c.AddDamage(limb.WorldPosition, DamageType.None, - damage / c.AnimController.Limbs.Length * distFactor, 0.0f, stun * distFactor, false); + attack.GetDamage(1.0f) / c.AnimController.Limbs.Length * distFactor, + attack.GetBleedingDamage(1.0f) / c.AnimController.Limbs.Length * distFactor, + attack.Stun * distFactor, + false); - if (limb.WorldPosition == worldPosition) continue; - - if (force > 0.0f) + if (limb.WorldPosition != worldPosition && force > 0.0f) { limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.WorldPosition - worldPosition) * distFactor * force); } - } + } + + if (!wasDead && c.IsDead) + { + foreach (LimbJoint joint in c.AnimController.LimbJoints) + { + if (Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability) + { + c.AnimController.SeverLimbJoint(joint); + } + } + } } } diff --git a/Barotrauma/Source/Networking/EntitySpawner.cs b/Barotrauma/Source/Networking/EntitySpawner.cs index 229fe6ab8..17a32b977 100644 --- a/Barotrauma/Source/Networking/EntitySpawner.cs +++ b/Barotrauma/Source/Networking/EntitySpawner.cs @@ -109,6 +109,7 @@ namespace Barotrauma public void AddToRemoveQueue(Entity entity) { if (GameMain.Client != null) return; + if (removeQueue.Contains(entity) || entity.Removed) return; removeQueue.Enqueue(entity); } @@ -116,6 +117,7 @@ namespace Barotrauma public void AddToRemoveQueue(Item item) { if (GameMain.Client != null) return; + if (removeQueue.Contains(item) || item.Removed) return; removeQueue.Enqueue(item); if (item.ContainedItems == null) return; diff --git a/Barotrauma/Source/Screens/EditCharacterScreen.cs b/Barotrauma/Source/Screens/EditCharacterScreen.cs index 5c70fbd7b..6cae14af2 100644 --- a/Barotrauma/Source/Screens/EditCharacterScreen.cs +++ b/Barotrauma/Source/Screens/EditCharacterScreen.cs @@ -217,7 +217,7 @@ namespace Barotrauma } jointList.ClearChildren(); - foreach (RevoluteJoint joint in character.AnimController.limbJoints) + foreach (RevoluteJoint joint in character.AnimController.LimbJoints) { Limb limb1 = (Limb)(joint.BodyA.UserData); Limb limb2 = (Limb)(joint.BodyB.UserData); @@ -236,7 +236,7 @@ namespace Barotrauma private void DrawJoints(SpriteBatch spriteBatch, Limb limb, Vector2 limbBodyPos) { - foreach (var joint in editingCharacter.AnimController.limbJoints) + foreach (var joint in editingCharacter.AnimController.LimbJoints) { Vector2 jointPos = Vector2.Zero; @@ -253,24 +253,27 @@ namespace Barotrauma { continue; } - - jointPos.Y = -jointPos.Y; - jointPos += limbBodyPos; + + Vector2 tformedJointPos = jointPos /= limb.Scale; + tformedJointPos.Y = -tformedJointPos.Y; + tformedJointPos += limbBodyPos; + if (joint.BodyA == limb.body.FarseerBody) { float a1 = joint.UpperLimit - MathHelper.PiOver2; float a2 = joint.LowerLimit - MathHelper.PiOver2; - float a3 =( a1+a2)/2.0f; - GUI.DrawLine(spriteBatch, jointPos, jointPos + new Vector2((float)Math.Cos(a1), -(float)Math.Sin(a1)) * 30.0f, Color.Green); - GUI.DrawLine(spriteBatch, jointPos, jointPos + new Vector2((float)Math.Cos(a2), -(float)Math.Sin(a2)) * 30.0f, Color.DarkGreen); + float a3 = (a1 + a2) / 2.0f; + GUI.DrawLine(spriteBatch, tformedJointPos, tformedJointPos + new Vector2((float)Math.Cos(a1), -(float)Math.Sin(a1)) * 30.0f, Color.Green); + GUI.DrawLine(spriteBatch, tformedJointPos, tformedJointPos + new Vector2((float)Math.Cos(a2), -(float)Math.Sin(a2)) * 30.0f, Color.DarkGreen); - GUI.DrawLine(spriteBatch, jointPos, jointPos + new Vector2((float)Math.Cos(a3), -(float)Math.Sin(a3)) * 30.0f, Color.LightGray); + GUI.DrawLine(spriteBatch, tformedJointPos, tformedJointPos + new Vector2((float)Math.Cos(a3), -(float)Math.Sin(a3)) * 30.0f, Color.LightGray); } - GUI.DrawRectangle(spriteBatch, jointPos, new Vector2(5.0f, 5.0f), Color.Red, true); - if (Vector2.Distance(PlayerInput.MousePosition, jointPos) < 6.0f) + GUI.DrawRectangle(spriteBatch, tformedJointPos, new Vector2(5.0f, 5.0f), Color.Red, true); + if (Vector2.Distance(PlayerInput.MousePosition, tformedJointPos) < 10.0f) { - GUI.DrawRectangle(spriteBatch, jointPos - new Vector2(3.0f, 3.0f), new Vector2(11.0f, 11.0f), Color.Red, false); + GUI.DrawString(spriteBatch, tformedJointPos + Vector2.One*10.0f, jointPos.ToString(), Color.White, Color.Black * 0.5f); + GUI.DrawRectangle(spriteBatch, tformedJointPos - new Vector2(3.0f, 3.0f), new Vector2(11.0f, 11.0f), Color.Red, false); if (PlayerInput.LeftButtonHeld()) { Vector2 speed = ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed); diff --git a/Barotrauma/Source/Utils/ToolBox.cs b/Barotrauma/Source/Utils/ToolBox.cs index bbf2aa6c5..c75c35cc2 100644 --- a/Barotrauma/Source/Utils/ToolBox.cs +++ b/Barotrauma/Source/Utils/ToolBox.cs @@ -162,6 +162,35 @@ namespace Barotrauma return value; } + public static float GetAttributeFloat(XElement element, float defaultValue, params string[] matchingAttributeName) + { + if (element == null) return defaultValue; + + foreach (string name in matchingAttributeName) + { + if (element.Attribute(name) == null) continue; + + float val = defaultValue; + + try + { + if (!float.TryParse(element.Attribute(name).Value, NumberStyles.Float, CultureInfo.InvariantCulture, out val)) + { + continue; + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Error in "+element+"!", e); + continue; + } + + return val; + } + + return defaultValue; + } + public static float GetAttributeFloat(XElement element, string name, float defaultValue) { if (element == null || element.Attribute(name) == null) return defaultValue;