From 4b5126675cf06fa54503d56fc068e80e79c6762c Mon Sep 17 00:00:00 2001 From: Regalis Date: Wed, 25 Nov 2015 16:04:51 +0200 Subject: [PATCH] Human AI improvements, minor UI tweaking --- Subsurface/Barotrauma.csproj | 3 + Subsurface/Source/Characters/AI/AITarget.cs | 2 +- .../Source/Characters/AI/EnemyAIController.cs | 15 +-- .../Source/Characters/AI/HumanAIController.cs | 24 ++-- .../Characters/AI/Objectives/AIObjective.cs | 21 +++- .../AI/Objectives/AIObjectiveFindSafety.cs | 81 ++++++++---- .../AI/Objectives/AIObjectiveFixLeak.cs | 48 +++++++ .../AI/Objectives/AIObjectiveGetItem.cs | 78 ++++++++++++ .../AI/Objectives/AIObjectiveGoTo.cs | 40 ++++-- .../AI/Objectives/AIObjectiveIdle.cs | 90 ++++++++++++++ .../AI/Objectives/AIObjectiveManager.cs | 24 +++- .../AI/Objectives/AIObjectiveOperateItem.cs | 5 +- Subsurface/Source/Characters/AI/PathFinder.cs | 31 ++++- .../Characters/AI/PathSteeringManager.cs | 117 +++++++++--------- .../Source/Characters/AI/SteeringPath.cs | 23 +++- Subsurface/Source/Characters/AICharacter.cs | 2 +- Subsurface/Source/Characters/Character.cs | 34 +++-- Subsurface/Source/Characters/CharacterInfo.cs | 2 +- .../Characters/HumanoidAnimController.cs | 6 +- Subsurface/Source/DebugConsole.cs | 6 + Subsurface/Source/GUI/GUIButton.cs | 2 +- Subsurface/Source/GameSession/CrewManager.cs | 3 +- .../GameSession/GameModes/SinglePlayerMode.cs | 5 +- .../GameSession/GameModes/TutorialMode.cs | 16 +-- Subsurface/Source/Items/CharacterInventory.cs | 46 +++---- Subsurface/Source/Items/Components/Door.cs | 77 ++++++++---- .../Source/Items/Components/ItemComponent.cs | 2 +- .../Source/Items/Components/ItemContainer.cs | 12 +- .../Items/Components/Machines/Fabricator.cs | 4 +- Subsurface/Source/Items/Components/Turret.cs | 8 +- Subsurface/Source/Items/FixRequirement.cs | 2 +- Subsurface/Source/Items/Inventory.cs | 41 +++--- Subsurface/Source/Items/Item.cs | 2 +- Subsurface/Source/Items/ItemInventory.cs | 8 +- Subsurface/Source/Items/ItemPrefab.cs | 4 +- Subsurface/Source/Items/RelatedItem.cs | 2 +- Subsurface/Source/Map/Map.cs | 43 +++++-- Subsurface/Source/Map/Submarine.cs | 2 + Subsurface/Source/Map/WayPoint.cs | 13 +- Subsurface/Source/Physics/PhysicsBody.cs | 62 ---------- Subsurface/Source/Screens/EditMapScreen.cs | 2 +- Subsurface_Solution.v12.suo | Bin 803840 -> 831488 bytes 42 files changed, 687 insertions(+), 321 deletions(-) create mode 100644 Subsurface/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs create mode 100644 Subsurface/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs create mode 100644 Subsurface/Source/Characters/AI/Objectives/AIObjectiveIdle.cs diff --git a/Subsurface/Barotrauma.csproj b/Subsurface/Barotrauma.csproj index f75ce7183..4d256f821 100644 --- a/Subsurface/Barotrauma.csproj +++ b/Subsurface/Barotrauma.csproj @@ -62,7 +62,10 @@ + + + diff --git a/Subsurface/Source/Characters/AI/AITarget.cs b/Subsurface/Source/Characters/AI/AITarget.cs index 46e006afa..b4f0f3af6 100644 --- a/Subsurface/Source/Characters/AI/AITarget.cs +++ b/Subsurface/Source/Characters/AI/AITarget.cs @@ -27,7 +27,7 @@ namespace Barotrauma set { sightRange = value; } } - public Vector2 Position + public Vector2 SimPosition { get { return Entity.SimPosition; } } diff --git a/Subsurface/Source/Characters/AI/EnemyAIController.cs b/Subsurface/Source/Characters/AI/EnemyAIController.cs index 8e57c410e..e777daa91 100644 --- a/Subsurface/Source/Characters/AI/EnemyAIController.cs +++ b/Subsurface/Source/Characters/AI/EnemyAIController.cs @@ -21,10 +21,7 @@ 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; - private float attackHumans; - private float attackWeaker; - private float attackStronger; + private float attackRooms, attackHumans, attackWeaker, attackStronger; private float updateTargetsTimer; @@ -165,7 +162,7 @@ namespace Barotrauma selectedTargetMemory.Priority -= deltaTime; - Vector2 attackPosition = selectedAiTarget.Position; + Vector2 attackPosition = selectedAiTarget.SimPosition; if (wallAttackPos != Vector2.Zero) attackPosition = wallAttackPos; if (coolDownTimer>0.0f) @@ -230,7 +227,7 @@ namespace Barotrauma targetEntity = null; //check if there's a wall between the target and the Character Vector2 rayStart = Character.AnimController.Limbs[0].SimPosition; - Vector2 rayEnd = selectedAiTarget.Position; + Vector2 rayEnd = selectedAiTarget.SimPosition; Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); if (Submarine.LastPickedFraction == 1.0f || closestBody == null) @@ -391,7 +388,7 @@ namespace Barotrauma dist = Vector2.Distance( character.AnimController.Limbs[0].SimPosition, - target.Position); + target.SimPosition); dist = ConvertUnits.ToDisplayUnits(dist); AITargetMemory targetMemory = FindTargetMemory(target); @@ -402,7 +399,7 @@ namespace Barotrauma if (Math.Abs(valueModifier) > Math.Abs(targetValue) && (dist < target.SightRange * sight || dist < target.SoundRange * hearing)) { Vector2 rayStart = character.AnimController.Limbs[0].SimPosition; - Vector2 rayEnd = target.Position; + Vector2 rayEnd = target.SimPosition; Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd); Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure; @@ -493,7 +490,7 @@ namespace Barotrauma if (selectedAiTarget!=null) { - GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(new Vector2(selectedAiTarget.Position.X, -selectedAiTarget.Position.Y)), Color.Red); + GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(new Vector2(selectedAiTarget.SimPosition.X, -selectedAiTarget.SimPosition.Y)), Color.Red); if (wallAttackPos!=Vector2.Zero) { diff --git a/Subsurface/Source/Characters/AI/HumanAIController.cs b/Subsurface/Source/Characters/AI/HumanAIController.cs index e67a0d049..b092bd76c 100644 --- a/Subsurface/Source/Characters/AI/HumanAIController.cs +++ b/Subsurface/Source/Characters/AI/HumanAIController.cs @@ -13,20 +13,27 @@ namespace Barotrauma private AIObjectiveManager objectiveManager; + private IndoorsSteeringManager indoorsSteeringManager; + private SteeringManager outdoorsSteeringManager; + private AITarget selectedAiTarget; private float updateObjectiveTimer; public HumanAIController(Character c) : base(c) { - steeringManager = new PathSteeringManager(this); + indoorsSteeringManager = new IndoorsSteeringManager(this, true); + outdoorsSteeringManager = new SteeringManager(this); objectiveManager = new AIObjectiveManager(c); - objectiveManager.AddObjective(new AIObjectiveFindSafety()); + objectiveManager.AddObjective(new AIObjectiveFindSafety(c)); + objectiveManager.AddObjective(new AIObjectiveIdle(c)); } public override void Update(float deltaTime) { + steeringManager = Character.AnimController.CurrentHull == null ? outdoorsSteeringManager : indoorsSteeringManager; + if (updateObjectiveTimer>0.0f) { updateObjectiveTimer -= deltaTime; @@ -46,12 +53,15 @@ namespace Barotrauma Character.AnimController.IgnorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); - if (Math.Abs(Character.AnimController.TargetMovement.X)>0.1f) + if (Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater) { Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left; } - steeringManager.Update(); + float currObjectivePriority = objectiveManager.CurrentObjective == null ? 0.0f : objectiveManager.CurrentObjective.GetPriority(Character); + float moveSpeed = MathHelper.Clamp(currObjectivePriority/10.0f, 1.0f, 3.0f); + + steeringManager.Update(moveSpeed); } public override void SelectTarget(AITarget target) @@ -61,13 +71,12 @@ namespace Barotrauma public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { - if (selectedAiTarget != null) { - GUI.DrawLine(spriteBatch, new Vector2(Character.Position.X, -Character.Position.Y), ConvertUnits.ToDisplayUnits(new Vector2(selectedAiTarget.Position.X, -selectedAiTarget.Position.Y)), Color.Red); + GUI.DrawLine(spriteBatch, new Vector2(Character.Position.X, -Character.Position.Y), ConvertUnits.ToDisplayUnits(new Vector2(selectedAiTarget.SimPosition.X, -selectedAiTarget.SimPosition.Y)), Color.Red); } - PathSteeringManager pathSteering = steeringManager as PathSteeringManager; + IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager; if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode==null) return; GUI.DrawLine(spriteBatch, @@ -83,7 +92,6 @@ namespace Barotrauma new Vector2(pathSteering.CurrentPath.Nodes[i - 1].Position.X, -pathSteering.CurrentPath.Nodes[i-1].Position.Y), Color.LightGreen); } - } } } diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjective.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjective.cs index 420d1441c..5da96ddf8 100644 --- a/Subsurface/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjective.cs @@ -11,14 +11,23 @@ namespace Barotrauma protected float priority; + protected Character character; + public virtual bool IsCompleted() { return false; } - public AIObjective() + public virtual bool CanBeCompleted + { + get { return false; } + } + + public AIObjective(Character character) { subObjectives = new List(); + + this.character = character; } /// @@ -26,20 +35,20 @@ namespace Barotrauma /// need to be completed before this one /// /// the character who's trying to achieve the objective - public void TryComplete(float deltaTime, Character character) + public void TryComplete(float deltaTime) { foreach (AIObjective objective in subObjectives) { if (objective.IsCompleted()) continue; - objective.TryComplete(deltaTime, character); + objective.TryComplete(deltaTime); return; } - Act(deltaTime, character); + Act(deltaTime); } - protected virtual void Act(float deltaTime, Character character) { } + protected virtual void Act(float deltaTime) { } public virtual float GetPriority(Character character) { @@ -48,7 +57,7 @@ namespace Barotrauma public virtual bool IsDuplicate(AIObjective otherObjective) { - return true; + throw new NotImplementedException(); } } } diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index e79d065a3..2fec74906 100644 --- a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -12,16 +12,26 @@ namespace Barotrauma const float MinSafety = 50.0f; AIObjectiveGoTo gotoObjective; + + private List unreachable; float currenthullSafety; float searchHullTimer; - protected override void Act(float deltaTime, Character character) + public AIObjectiveFindSafety(Character character) + : base(character) + { + unreachable = new List(); + } + + protected override void Act(float deltaTime) { if (character.AnimController.CurrentHull == null || GetHullSafety(character.AnimController.CurrentHull) > MinSafety) { - character.AIController.SteeringManager.SteeringSeek(character.AnimController.CurrentHull.Position); + character.AIController.SteeringManager.SteeringSeek(character.AnimController.CurrentHull.SimPosition); + + character.AIController.SelectTarget(null); gotoObjective = null; return; @@ -32,34 +42,56 @@ namespace Barotrauma searchHullTimer -= deltaTime; return; } - - searchHullTimer = SearchHullInterval; - - Hull bestHull = null; - float bestValue = currenthullSafety; - - foreach (Hull hull in Hull.hullList) + else { - if (hull == character.AnimController.CurrentHull) continue; - float hullValue = GetHullSafety(hull); - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X- hull.Position.X)); - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y)*2.0f); + Hull bestHull = null; + float bestValue = currenthullSafety; - if (bestHull==null || hullValue > bestValue) + foreach (Hull hull in Hull.hullList) { - bestHull = hull; - bestValue = hullValue; + if (hull == character.AnimController.CurrentHull) continue; + if (unreachable.Contains(hull.AiTarget)) continue; + + float hullValue = GetHullSafety(hull); + hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X- hull.Position.X)); + hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y)*2.0f); + + if (bestHull==null || hullValue > bestValue) + { + bestHull = hull; + bestValue = hullValue; + } + } + + if (bestHull != null) + { + gotoObjective = new AIObjectiveGoTo(bestHull.AiTarget, character); + //character.AIController.SelectTarget(bestHull.AiTarget); + } + + + searchHullTimer = SearchHullInterval; + } + + if (gotoObjective != null) + { + var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; + if (pathSteering!=null && pathSteering.CurrentPath!= null && + pathSteering.CurrentPath.Unreachable && !unreachable.Contains(gotoObjective.Target)) + { + unreachable.Add(gotoObjective.Target); } } - if (bestHull != null) - { - gotoObjective = new AIObjectiveGoTo(bestHull.AiTarget, character); - //character.AIController.SelectTarget(bestHull.AiTarget); - } - gotoObjective.TryComplete(deltaTime, character); + + gotoObjective.TryComplete(deltaTime); + } + + public override bool IsDuplicate(AIObjective otherObjective) + { + return (otherObjective is AIObjectiveFindSafety); } public override float GetPriority(Character character) @@ -77,10 +109,11 @@ namespace Barotrauma foreach (FireSource fireSource in hull.FireSources) { - fireAmount += fireSource.Size.X; + fireAmount += Math.Max(fireSource.Size.X,50.0f); } - float safety = 100.0f - fireAmount - waterPercentage; + float safety = 100.0f - fireAmount; + if (waterPercentage > 30.0f) safety -= waterPercentage; if (hull.OxygenPercentage < 30.0f) safety -= (30.0f-hull.OxygenPercentage)*3.0f; return safety; diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs new file mode 100644 index 000000000..0d23d7de4 --- /dev/null +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -0,0 +1,48 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Barotrauma +{ + class AIObjectiveFixLeak : AIObjective + { + Gap leak; + + public AIObjectiveFixLeak(Gap leak, Character character) + :base (character) + { + this.leak = leak; + } + + public override float GetPriority(Character character) + { + return leak.isHorizontal ? leak.Rect.Height * leak.Open : leak.Rect.Width * leak.Open; + } + + public override bool IsDuplicate(AIObjective otherObjective) + { + AIObjectiveFixLeak fixLeak = otherObjective as AIObjectiveFixLeak; + if (fixLeak == null) return false; + return fixLeak.leak == leak; + } + + protected override void Act(float deltaTime) + { + var weldingTool = character.Inventory.FindItem("Welding Tool"); + + if (weldingTool == null) + { + subObjectives.Add(new AIObjectiveGetItem(character, "Welding Tool")); + } + else + { + if (Vector2.Distance(character.Position, leak.Position)>10.0f) + { + subObjectives.Add(new AIObjectiveGoTo(leak.Position,character)); + } + } + } + } +} diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs new file mode 100644 index 000000000..f2e1dc6ba --- /dev/null +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -0,0 +1,78 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Barotrauma +{ + class AIObjectiveGetItem : AIObjective + { + private string itemName; + + private Item targetItem; + + private int currSearchIndex; + + private bool canBeCompleted; + + public override bool CanBeCompleted + { + get { return canBeCompleted; } + } + + public AIObjectiveGetItem(Character character, string itemName) + : base (character) + { + canBeCompleted = true; + + currSearchIndex = 0; + + this.itemName = itemName; + } + + protected override void Act(float deltaTime) + { + if (targetItem != null) + { + if (Vector2.Distance(character.SimPosition, targetItem.SimPosition) < targetItem.PickDistance) + { + targetItem.Pick(character, false, true); + } + return; + } + + if (currSearchIndex >= Item.ItemList.Count) + { + canBeCompleted = false; + return; + } + + if (Item.ItemList[currSearchIndex].HasTag(itemName) || Item.ItemList[currSearchIndex].Name == itemName) + { + targetItem = Item.ItemList[currSearchIndex]; + + while (targetItem.container != null) + { + targetItem = targetItem.container; + } + + subObjectives.Add(new AIObjectiveGoTo(targetItem.Position, character)); + } + + currSearchIndex++; + } + + public override bool IsDuplicate(AIObjective otherObjective) + { + AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem; + if (getItem == null) return false; + return (getItem.itemName == itemName); + } + + public override bool IsCompleted() + { + return character.Inventory.Items.FirstOrDefault(i => i != null && (i.HasTag(itemName) || i.Name == itemName)) != null; + } + } +} diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index e8bd7d3ff..63e6a1614 100644 --- a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -12,22 +12,46 @@ namespace Barotrauma { AITarget target; - private Character character; + Vector2 targetPos; - public AIObjectiveGoTo(AITarget target, Character character) + public override bool CanBeCompleted { - this.character = character; - this.target = target; - + get + { + var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; + return (pathSteering.CurrentPath == null || !pathSteering.CurrentPath.Unreachable); + } } - protected override void Act(float deltaTime, Character character) + public AITarget Target { - if (target == null) return; + get { return target; } + } + public AIObjectiveGoTo(AITarget target, Character character) + : base (character) + { + this.target = target; + } + + + public AIObjectiveGoTo(Vector2 targetPos, Character character) + : base(character) + { + this.targetPos = targetPos; + } + + protected override void Act(float deltaTime) + { character.AIController.SelectTarget(target); - character.AIController.SteeringManager.SteeringSeek(ConvertUnits.ToDisplayUnits(target.Position)); + character.AIController.SteeringManager.SteeringSeek( + target != null ? target.SimPosition : targetPos); + } + + public override bool IsCompleted() + { + return Vector2.Distance(target != null ? target.SimPosition : ConvertUnits.ToDisplayUnits(targetPos), character.SimPosition) < 0.5f; } } } diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveIdle.cs new file mode 100644 index 000000000..1d24d7f78 --- /dev/null +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -0,0 +1,90 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Barotrauma +{ + class AIObjectiveIdle : AIObjective + { + AITarget currentTarget; + private float newTargetTimer; + + + + public AIObjectiveIdle(Character character) : base(character) + { + + } + + public override float GetPriority(Character character) + { + return 1.0f; + } + + + protected override void Act(float deltaTime) + { + if (newTargetTimer <= 0.0f) + { + currentTarget = FindRandomTarget(); + + newTargetTimer = currentTarget == null ? 5.0f : 10.0f; + } + else + { + newTargetTimer -= deltaTime; + } + + + if (currentTarget == null) return; + + character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition); + + var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; + if (pathSteering!=null && pathSteering.CurrentPath != null) + { + if (pathSteering.CurrentPath.NextNode==null || pathSteering.CurrentPath.Unreachable) + { + character.AIController.SteeringManager.SteeringWander(1.0f); + } + } + } + + private AITarget FindRandomTarget() + { + if (Rand.Int(5)==1) + { + var idCard = character.Inventory.FindItem("ID Card"); + if (idCard==null) return null; + + foreach (WayPoint wp in WayPoint.WayPointList) + { + if (wp.SpawnType != SpawnType.Human) continue; + + foreach (string tag in wp.IdCardTags) + { + if (idCard.HasTag(tag)) return wp.CurrentHull.AiTarget; + } + } + } + else + { + List targetHulls = new List(Hull.hullList); + //ignore all hulls with fires or water in them + targetHulls.RemoveAll(h => h.FireSources.Any() || (h.Volume/h.FullVolume)>0.1f); + if (!targetHulls.Any()) return null; + + return targetHulls[Rand.Range(0, targetHulls.Count)].AiTarget; + } + + return null; + } + + public override bool IsDuplicate(AIObjective otherObjective) + { + return true; + } + } +} diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveManager.cs index 68d4f82ea..3f413d27f 100644 --- a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -10,6 +10,14 @@ namespace Barotrauma private List objectives; private Character character; + + public AIObjective CurrentObjective + { + get + { + return objectives.Any() ? objectives[0] : null; + } + } public AIObjectiveManager(Character character) { @@ -33,14 +41,24 @@ namespace Barotrauma objectives = objectives.FindAll(o => !o.IsCompleted()); //sort objectives according to priority - objectives.Sort((x, y) => x.GetPriority(character).CompareTo(y.GetPriority(character))); - + objectives.Sort((x, y) => y.GetPriority(character).CompareTo(x.GetPriority(character))); + + if (character.AnimController.CurrentHull!=null) + { + var gaps = character.AnimController.CurrentHull.FindGaps(); + + foreach (Gap gap in gaps) + { + if (gap.linkedTo.Count > 1) continue; + AddObjective(new AIObjectiveFixLeak(gap, character)); + } + } } public void DoCurrentObjective(float deltaTime) { if (!objectives.Any()) return; - objectives[0].TryComplete(deltaTime, character); + objectives[0].TryComplete(deltaTime); } } } diff --git a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 8da97fd54..fe4107793 100644 --- a/Subsurface/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Subsurface/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -9,12 +9,13 @@ namespace Barotrauma { private Item targetItem; - public AIObjectiveOperateItem(Item item) + public AIObjectiveOperateItem(Item item, Character character) + :base (character) { targetItem = item; } - protected override void Act(float deltaTime, Character character) + protected override void Act(float deltaTime) { //item.AIOperate(float deltaTime, Character character) or something } diff --git a/Subsurface/Source/Characters/AI/PathFinder.cs b/Subsurface/Source/Characters/AI/PathFinder.cs index f6fe77c3a..5af610785 100644 --- a/Subsurface/Source/Characters/AI/PathFinder.cs +++ b/Subsurface/Source/Characters/AI/PathFinder.cs @@ -77,8 +77,8 @@ namespace Barotrauma class PathFinder { - public delegate float GetNodePenaltyHandler(PathNode node, PathNode prevNode); - public GetNodePenaltyHandler GetNodePriority; + public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode); + public GetNodePenaltyHandler GetNodePenalty; List nodes; @@ -95,12 +95,14 @@ namespace Barotrauma { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); - + float closestDist = 0.0f; PathNode startNode = null; foreach (PathNode node in nodes) { - float dist = Vector2.Distance(start,node.Position); + float dist = System.Math.Abs(start.X-node.Position.X)+ + System.Math.Abs(start.Y - node.Position.Y)*10.0f + + Vector2.Distance(end,node.Position)/2.0f; if (dist openableButtons; + private Character character; public SteeringPath CurrentPath { @@ -31,15 +31,15 @@ namespace Barotrauma private float findPathTimer; - public PathSteeringManager(ISteerable host) + public IndoorsSteeringManager(ISteerable host, bool canOpenDoors) : base(host) { pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true); - pathFinder.GetNodePriority = GetNodePriority; + pathFinder.GetNodePenalty = GetNodePenalty; + + this.canOpenDoors = canOpenDoors; character = (host as AIController).Character; - - openableButtons = new List(); } public override void Update(float speed = 1) @@ -53,37 +53,22 @@ namespace Barotrauma protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1) { //find a new path if one hasn't been found yet or the target is different from the current target - if (currentPath == null || Vector2.DistanceSquared(target, currentTarget)>10.0f) + if (currentPath == null || Vector2.Distance(target, currentTarget)>1.0f || findPathTimer < -10.0f) { if (findPathTimer > 0.0f) return Vector2.Zero; currentTarget = target; - currentPath = pathFinder.FindPath(host.SimPosition, ConvertUnits.ToSimUnits(target)); - + currentPath = pathFinder.FindPath(host.SimPosition, target); + findPathTimer = 1.0f; return DiffToCurrentNode(); } - - //if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return; - - //if (currentPath.CurrentNode.ConnectedGap != null && currentPath.CurrentNode.ConnectedGap.Open < 0.9f) - //{ - foreach (Controller controller in openableButtons) - { - if (Vector2.Distance(controller.Item.SimPosition, character.SimPosition) > controller.Item.PickDistance) continue; - - controller.Item.Pick(character, false, true); - } - //} - - - Vector2 diff = DiffToCurrentNode(); if (diff == Vector2.Zero) return -host.Steering; - + return (diff == Vector2.Zero) ? Vector2.Zero : Vector2.Normalize(diff)*speed; } @@ -91,61 +76,77 @@ namespace Barotrauma { if (currentPath == null) return Vector2.Zero; - currentPath.CheckProgress(host.SimPosition, 0.45f); + if (canOpenDoors) CheckDoorsInPath(); + + currentPath.CheckProgress(host.SimPosition, character.AnimController.InWater ? 1.0f : 0.6f); if (currentPath.CurrentNode == null) return Vector2.Zero; return currentPath.CurrentNode.SimPosition - host.SimPosition; } - private float GetNodePriority(PathNode node, PathNode nextNode) + private void CheckDoorsInPath() { - if (character==null) return 0.0f; - if (nextNode.Waypoint.ConnectedGap!=null) + for (int i = 0; i < 2; i++) + { + WayPoint node = i == 0 ? currentPath.CurrentNode : currentPath.PrevNode; + + if (node == null || node.ConnectedGap == null || node.ConnectedGap.ConnectedDoor == null) continue; + + var door = node.ConnectedGap.ConnectedDoor; + + bool open = currentPath.CurrentNode != null && + Math.Sign(door.Item.SimPosition.X - host.SimPosition.X) == Math.Sign(currentPath.CurrentNode.SimPosition.X - host.SimPosition.X); + + //toggle the door if it's the previous node and open, or if it's current node and closed + if (door.IsOpen != open) + { + var buttons = door.GetButtons(); + foreach (Controller controller in buttons) + { + if (Vector2.Distance(controller.Item.SimPosition, character.SimPosition) > controller.Item.PickDistance * 2.0f) continue; + + controller.Item.Pick(character, false, true); + break; + } + } + } + } + + private float? GetNodePenalty(PathNode node, PathNode nextNode) + { + if (character == null) return 0.0f; + if (nextNode.Waypoint.ConnectedGap != null) { if (nextNode.Waypoint.ConnectedGap.Open > 0.9f) return 0.0f; if (nextNode.Waypoint.ConnectedGap.ConnectedDoor == null) return 100.0f; - var doorButtons = GetDoorButtons(nextNode.Waypoint.ConnectedGap.ConnectedDoor); + if (!canOpenDoors) return null; + + var doorButtons = nextNode.Waypoint.ConnectedGap.ConnectedDoor.GetButtons(); foreach (Controller button in doorButtons) { if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) != Math.Sign(node.Position.X - nextNode.Position.X)) continue; - if (!button.HasRequiredItems(character, false)) return 1000.0f; + if (!button.HasRequiredItems(character, false)) return null; } } + if (node.Waypoint!=null && node.Waypoint.CurrentHull!=null) + { + var hull = node.Waypoint.CurrentHull; + + float penalty = hull.FireSources.Any() ? 1000.0f : 0.0f; + + if (character.NeedsAir && hull.Volume / hull.Rect.Width > 100.0f) penalty += 500.0f; + if (character.PressureProtection < 10.0f && hull.Volume > hull.FullVolume) penalty += 1000.0f; + } + return 0.0f; } - private List GetDoorButtons(Door door) - { - if (door == null) return new List(); - ConnectionPanel connectionPanel = door.Item.GetComponent(); - List doorButtons = new List(); - - foreach (Connection c in connectionPanel.Connections) - { - foreach (Wire w in c.Wires) - { - if (w == null) continue; - var otherConnection = w.OtherConnection(c); - - if (otherConnection.Item == door.Item || otherConnection == null) continue; - - var controller = otherConnection.Item.GetComponent(); - if (controller != null) - { - doorButtons.Add(controller); - if (!openableButtons.Contains(controller)) openableButtons.Add(controller); - } - } - } - - return doorButtons; - } } } diff --git a/Subsurface/Source/Characters/AI/SteeringPath.cs b/Subsurface/Source/Characters/AI/SteeringPath.cs index e52f748a7..d508da2d8 100644 --- a/Subsurface/Source/Characters/AI/SteeringPath.cs +++ b/Subsurface/Source/Characters/AI/SteeringPath.cs @@ -9,9 +9,16 @@ namespace Barotrauma int currentIndex; - public SteeringPath() + public bool Unreachable + { + get; + private set; + } + + public SteeringPath(bool unreachable = false) { nodes = new List(); + Unreachable = unreachable; } public void AddNode(WayPoint node) @@ -20,6 +27,20 @@ namespace Barotrauma nodes.Add(node); } + public int CurrentIndex + { + get { return currentIndex; } + } + + public WayPoint PrevNode + { + get + { + if (currentIndex-1 < 0 || currentIndex-1 > nodes.Count - 1) return null; + return nodes[currentIndex-1]; + } + } + public WayPoint CurrentNode { get diff --git a/Subsurface/Source/Characters/AICharacter.cs b/Subsurface/Source/Characters/AICharacter.cs index e4563302b..ba52cab67 100644 --- a/Subsurface/Source/Characters/AICharacter.cs +++ b/Subsurface/Source/Characters/AICharacter.cs @@ -75,7 +75,7 @@ namespace Barotrauma { base.DrawFront(spriteBatch); - if (GameMain.DebugDraw) aiController.DebugDraw(spriteBatch); + if (GameMain.DebugDraw && !isDead) aiController.DebugDraw(spriteBatch); } public override AttackResult AddDamage(IDamageable attacker, Vector2 position, Attack attack, float deltaTime, bool playSound = false) diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index 6c0a21867..f75d75bfe 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -84,6 +84,17 @@ namespace Barotrauma //the name of the species (e.q. human) public readonly string SpeciesName; + protected float soundTimer; + protected float soundInterval; + + private float bleeding; + + private Sound[] sounds; + private float[] soundRange; + //which AIstate each sound is for + private AIController.AiState[] soundStates; + + private CharacterInfo info; public CharacterInfo Info @@ -99,16 +110,6 @@ namespace Barotrauma } } - protected float soundTimer; - protected float soundInterval; - - private float bleeding; - - private Sound[] sounds; - private float[] soundRange; - //which AIstate each sound is for - private AIController.AiState[] soundStates; - public string Name { get @@ -171,6 +172,7 @@ namespace Barotrauma { get { return aiTarget.SightRange; } } + private float pressureProtection; public float PressureProtection { @@ -181,11 +183,17 @@ namespace Barotrauma } } + public bool NeedsAir + { + get { return needsAir; } + } + public float Oxygen { get { return oxygen; } set { + if (!MathUtils.IsValid(value)) return; oxygen = MathHelper.Clamp(value, 0.0f, 100.0f); if (oxygen == 0.0f) Kill(CauseOfDeath.Suffocation); } @@ -567,6 +575,8 @@ namespace Barotrauma AnimController.TargetMovement = targetMovement; AnimController.IsStanding = true; + AnimController.IgnorePlatforms = targetMovement.Y < 0.0f; + if (AnimController.onGround && !AnimController.InWater && AnimController.Anim != AnimController.Animation.UsingConstruction) @@ -877,7 +887,7 @@ namespace Barotrauma if (isDead) return; - if (!(this is AICharacter)) + if (!(AnimController is FishAnimController)) { bool protectedFromPressure = PressureProtection > 0.0f; @@ -993,7 +1003,7 @@ namespace Barotrauma AnimController.DebugDraw(spriteBatch); } - Vector2 healthBarPos = new Vector2(Position.X - 50, -Position.Y - 50.0f); + Vector2 healthBarPos = new Vector2(Position.X - 50, -Position.Y - 100.0f); GUI.DrawRectangle(spriteBatch, new Rectangle((int)healthBarPos.X - 2, (int)healthBarPos.Y - 2, 100 + 4, 15 + 4), Color.Black, false); GUI.DrawRectangle(spriteBatch, new Rectangle((int)healthBarPos.X, (int)healthBarPos.Y, (int)(100.0f * (health / maxHealth)), 15), Color.Red, true); } diff --git a/Subsurface/Source/Characters/CharacterInfo.cs b/Subsurface/Source/Characters/CharacterInfo.cs index 162931585..cc6b805d0 100644 --- a/Subsurface/Source/Characters/CharacterInfo.cs +++ b/Subsurface/Source/Characters/CharacterInfo.cs @@ -219,7 +219,7 @@ namespace Barotrauma public void UpdateCharacterItems() { pickedItems.Clear(); - foreach (Item item in Character.Inventory.items) + foreach (Item item in Character.Inventory.Items) { pickedItems.Add(item == null ? (ushort)0 : item.ID); } diff --git a/Subsurface/Source/Characters/HumanoidAnimController.cs b/Subsurface/Source/Characters/HumanoidAnimController.cs index 8244d80e6..3fea8f485 100644 --- a/Subsurface/Source/Characters/HumanoidAnimController.cs +++ b/Subsurface/Source/Characters/HumanoidAnimController.cs @@ -133,11 +133,7 @@ namespace Barotrauma stunTimer -= deltaTime; return; } - - IgnorePlatforms = (TargetMovement.Y < 0.0f); - - - + if (Anim != Animation.UsingConstruction) ResetPullJoints(); if (TargetDir != dir) Flip(); diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index b245b55df..8226cec06 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -269,6 +269,12 @@ namespace Barotrauma case "edit": GameMain.EditMapScreen.Select(); break; + case "test": + Submarine.Load("aegir mark ii"); + GameMain.DebugDraw = true; + GameMain.LightManager.LosEnabled = false; + GameMain.EditMapScreen.Select(); + break; case "editcharacter": case "editchar": GameMain.EditCharacterScreen.Select(); diff --git a/Subsurface/Source/GUI/GUIButton.cs b/Subsurface/Source/GUI/GUIButton.cs index 235bbecbf..8973d95eb 100644 --- a/Subsurface/Source/GUI/GUIButton.cs +++ b/Subsurface/Source/GUI/GUIButton.cs @@ -63,8 +63,8 @@ namespace Barotrauma } set { - if (textBlock == null) return; base.Font = value; + if (textBlock != null) textBlock.Font = value; } } diff --git a/Subsurface/Source/GameSession/CrewManager.cs b/Subsurface/Source/GameSession/CrewManager.cs index c81d87ffb..e7c30ad55 100644 --- a/Subsurface/Source/GameSession/CrewManager.cs +++ b/Subsurface/Source/GameSession/CrewManager.cs @@ -98,7 +98,8 @@ namespace Barotrauma name, Color.Transparent, Color.White, Alignment.Left, Alignment.Left, - null, frame); + null, frame, false); + textBlock.Font = GUI.SmallFont; textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f); new GUIImage(new Rectangle(-10, -10, 0, 0), character.AnimController.Limbs[0].sprite, Alignment.Left, frame); diff --git a/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs b/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs index 5178374bb..5a6fe2582 100644 --- a/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs +++ b/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs @@ -52,6 +52,7 @@ namespace Barotrauma CargoManager = new CargoManager(); endShiftButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 220, 20, 200, 25), "End shift", Alignment.TopLeft, GUI.Style); + endShiftButton.Font = GUI.SmallFont; endShiftButton.OnClicked = EndShift; for (int i = 0; i < 3; i++) @@ -142,14 +143,12 @@ namespace Barotrauma if (Level.Loaded.AtEndPosition) { - endShiftButton.Text = "Enter " + Map.SelectedLocation.Name; - endShiftButton.Font = GUI.SmallFont; + endShiftButton.Text = "Enter " + Map.SelectedLocation.Name; endShiftButton.Draw(spriteBatch); } else if (Level.Loaded.AtStartPosition) { endShiftButton.Text = "Enter " + Map.CurrentLocation.Name; - endShiftButton.Font = GUI.SmallFont; endShiftButton.Draw(spriteBatch); } diff --git a/Subsurface/Source/GameSession/GameModes/TutorialMode.cs b/Subsurface/Source/GameSession/GameModes/TutorialMode.cs index 96d6fec8b..b9854f2ff 100644 --- a/Subsurface/Source/GameSession/GameModes/TutorialMode.cs +++ b/Subsurface/Source/GameSession/GameModes/TutorialMode.cs @@ -50,16 +50,10 @@ namespace Barotrauma Character character = Character.Create(charInfo, wayPoint.SimPosition); Character.Controlled = character; character.GiveJobItems(null); - - foreach (Item item in character.Inventory.items) - { - if (item == null || item.Name != "ID Card") continue; - item.AddTag("com"); - item.AddTag("eng"); - - break; - } + var idCard = character.Inventory.FindItem("ID Card"); + idCard.AddTag("com"); + idCard.AddTag("eng"); CrewManager.AddCharacter(character); @@ -495,7 +489,7 @@ namespace Barotrauma do { - var weldingTool = Character.Controlled.Inventory.items.FirstOrDefault(i => i != null && i.Name == "Welding Tool"); + var weldingTool = Character.Controlled.Inventory.Items.FirstOrDefault(i => i != null && i.Name == "Welding Tool"); if (weldingTool != null && weldingTool.ContainedItems.FirstOrDefault(contained => contained != null && contained.Name == "Welding Fuel Tank") != null) break; @@ -604,7 +598,7 @@ namespace Barotrauma private bool HasItem(string itemName) { if (Character.Controlled == null) return false; - return Character.Controlled.Inventory.items.FirstOrDefault(i => i != null && i.Name == itemName)!=null; + return Character.Controlled.Inventory.Items.FirstOrDefault(i => i != null && i.Name == itemName)!=null; } /// diff --git a/Subsurface/Source/Items/CharacterInventory.cs b/Subsurface/Source/Items/CharacterInventory.cs index 95c3fed58..8bdf72dfb 100644 --- a/Subsurface/Source/Items/CharacterInventory.cs +++ b/Subsurface/Source/Items/CharacterInventory.cs @@ -81,7 +81,7 @@ namespace Barotrauma public int FindLimbSlot(LimbSlot limbSlot) { - for (int i = 0; i < items.Length; i++) + for (int i = 0; i < Items.Length; i++) { if ( limbSlots[i] == limbSlot) return i; } @@ -90,9 +90,9 @@ namespace Barotrauma public bool IsInLimbSlot(Item item, LimbSlot limbSlot) { - for (int i = 0; i 4); + UpdateSlot(spriteBatch, slotRect, i, Items[i], i > 4); - if (draggingItem!=null && draggingItem == items[i]) draggingItemSlot = slotRect; + if (draggingItem!=null && draggingItem == Items[i]) draggingItemSlot = slotRect; } @@ -406,7 +406,7 @@ namespace Barotrauma bool multiSlot = false; //check if the item is in multiple slots - if (items[i] != null) + if (Items[i] != null) { slotRect.X = (int)slotPositions[i].X; slotRect.Y = (int)slotPositions[i].Y; @@ -415,7 +415,7 @@ namespace Barotrauma for (int n = 0; n < capacity; n++) { - if (items[n] != items[i]) continue; + if (Items[n] != Items[i]) continue; if (!multiSlot && i > n) break; @@ -430,7 +430,7 @@ namespace Barotrauma if (!multiSlot) continue; - UpdateSlot(spriteBatch, slotRect, i, items[i], i > 4); + UpdateSlot(spriteBatch, slotRect, i, Items[i], i > 4); //if (multiSlot && i == first) //{ @@ -469,7 +469,7 @@ namespace Barotrauma { for (int i = 0; i < capacity; i++) { - message.Write(items[i]==null ? (ushort)0 : (ushort)items[i].ID); + message.Write(Items[i]==null ? (ushort)0 : (ushort)Items[i].ID); } return true; @@ -486,14 +486,14 @@ namespace Barotrauma ushort itemId = message.ReadUInt16(); if (itemId==0) { - if (items[i] != null) items[i].Drop(character, false); + if (Items[i] != null) Items[i].Drop(character, false); } else { Item item = Entity.FindEntityByID(itemId) as Item; if (item == null) continue; - if (items[i] != item && items[i] != null) items[i].Drop(character, false); + if (Items[i] != item && Items[i] != null) Items[i].Drop(character, false); TryPutItem(item, i, false); } } diff --git a/Subsurface/Source/Items/Components/Door.cs b/Subsurface/Source/Items/Components/Door.cs index eb83c9986..c6e60c2d3 100644 --- a/Subsurface/Source/Items/Components/Door.cs +++ b/Subsurface/Source/Items/Components/Door.cs @@ -7,6 +7,7 @@ using FarseerPhysics.Factories; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Barotrauma.Lights; +using System.Collections.Generic; namespace Barotrauma.Items.Components { @@ -41,8 +42,7 @@ namespace Barotrauma.Items.Components if (linkedGap != null) return linkedGap; foreach (MapEntity e in item.linkedTo) { - linkedGap = e as Gap; - linkedGap.ConnectedDoor = this; + linkedGap = e as Gap; if (linkedGap != null) return linkedGap; } linkedGap = new Gap(item.Rect); @@ -256,6 +256,30 @@ namespace Barotrauma.Items.Components linkedGap.Open = 1.0f; } + public List GetButtons() + { + ConnectionPanel connectionPanel = Item.GetComponent(); + if (connectionPanel == null) return new List(); + + List buttons = new List(); + + foreach (Connection c in connectionPanel.Connections) + { + foreach (Wire w in c.Wires) + { + if (w == null) continue; + var otherConnection = w.OtherConnection(c); + + if (otherConnection.Item == Item || otherConnection == null) continue; + + var controller = otherConnection.Item.GetComponent(); + if (controller != null) buttons.Add(controller); + } + } + + return buttons; + } + public override void Draw(SpriteBatch spriteBatch, bool editing) { Color color = (item.IsSelected) ? Color.Green : Color.White; @@ -272,38 +296,43 @@ namespace Barotrauma.Items.Components if (openState == 1.0f) { body.Enabled = false; + return; + } + + spriteBatch.Draw(doorSprite.Texture, new Vector2(item.Rect.Center.X, -item.Rect.Y), + new Rectangle(doorSprite.SourceRect.X, (int)(doorSprite.size.Y * openState), + (int)doorSprite.size.X, (int)(doorSprite.size.Y * (1.0f - openState))), + color, 0.0f, doorSprite.Origin, 1.0f, SpriteEffects.None, doorSprite.Depth); + + if (openState == 0.0f) + { + body.Enabled = true; } else { - spriteBatch.Draw(doorSprite.Texture, new Vector2(item.Rect.Center.X, -item.Rect.Y), - new Rectangle(doorSprite.SourceRect.X, (int)(doorSprite.size.Y * openState), - (int)doorSprite.size.X, (int)(doorSprite.size.Y * (1.0f - openState))), - color, 0.0f, doorSprite.Origin, 1.0f, SpriteEffects.None, doorSprite.Depth); + //push characters out of the doorway when the door is closing/opening + Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y)); + Vector2 simSize = ConvertUnits.ToSimUnits(new Vector2(item.Rect.Width, + item.Rect.Height * (1.0f - openState))); - if (openState == 0.0f) + foreach (Character c in Character.CharacterList) { - body.Enabled = true; - } - else - { - //push characters out of the doorway when the door is closing/opening - Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y)); - Vector2 simSize = ConvertUnits.ToSimUnits(new Vector2(item.Rect.Width, - item.Rect.Height * (1.0f - openState))); - - foreach (Character c in Character.CharacterList) + int dir = Math.Sign(c.AnimController.Limbs[0].SimPosition.X - simPos.X); + foreach (Limb l in c.AnimController.Limbs) { - int dir = Math.Sign(c.AnimController.Limbs[0].SimPosition.X - simPos.X); - foreach (Limb l in c.AnimController.Limbs) - { - if (l.SimPosition.Y < simPos.Y || l.SimPosition.Y > simPos.Y - simSize.Y) continue; - if (Math.Abs(l.SimPosition.X - simPos.X) > simSize.X * 2.0f) continue; + if (l.SimPosition.Y < simPos.Y || l.SimPosition.Y > simPos.Y - simSize.Y) continue; + if (Math.Abs(l.SimPosition.X - simPos.X) > simSize.X * 2.0f) continue; - l.body.ApplyForce(new Vector2(dir * 10.0f, 0.0f)); - } + l.body.ApplyForce(new Vector2(dir * 10.0f, 0.0f)); } } } + + } + + public override void OnMapLoaded() + { + LinkedGap.ConnectedDoor = this; } public override void Remove() diff --git a/Subsurface/Source/Items/Components/ItemComponent.cs b/Subsurface/Source/Items/Components/ItemComponent.cs index 2550ce4b6..7f4f750f8 100644 --- a/Subsurface/Source/Items/Components/ItemComponent.cs +++ b/Subsurface/Source/Items/Components/ItemComponent.cs @@ -551,7 +551,7 @@ namespace Barotrauma.Items.Components } if (!hasItem && ri.Type.HasFlag(RelatedItem.RelationType.Picked)) { - if (character.Inventory.items.FirstOrDefault(x => x!=null && x.Condition>0.0f && ri.MatchesItem(x))!=null) hasItem = true; + if (character.Inventory.Items.FirstOrDefault(x => x!=null && x.Condition>0.0f && ri.MatchesItem(x))!=null) hasItem = true; } if (!hasItem) { diff --git a/Subsurface/Source/Items/Components/ItemContainer.cs b/Subsurface/Source/Items/Components/ItemContainer.cs index 3cb9cfad4..0babebb28 100644 --- a/Subsurface/Source/Items/Components/ItemContainer.cs +++ b/Subsurface/Source/Items/Components/ItemContainer.cs @@ -127,7 +127,7 @@ namespace Barotrauma.Items.Components { if (!hasStatusEffects) return; - foreach (Item contained in inventory.items) + foreach (Item contained in inventory.Items) { if (contained == null || contained.Condition <= 0.0f) continue; //if (contained.body != null) contained.body.Enabled = false; @@ -179,7 +179,7 @@ namespace Barotrauma.Items.Components currentRotation += item.body.Rotation; } - foreach (Item containedItem in inventory.items) + foreach (Item containedItem in inventory.Items) { if (containedItem == null) continue; @@ -243,7 +243,7 @@ namespace Barotrauma.Items.Components { base.Remove(); - foreach (Item item in inventory.items) + foreach (Item item in inventory.Items) { if (item == null) continue; item.Remove(); @@ -274,10 +274,10 @@ namespace Barotrauma.Items.Components { XElement componentElement = base.Save(parentElement); - string[] itemIdStrings = new string[inventory.items.Length]; - for (int i = 0; i < inventory.items.Length; i++) + string[] itemIdStrings = new string[inventory.Items.Length]; + for (int i = 0; i < inventory.Items.Length; i++) { - itemIdStrings[i] = (inventory.items[i]==null) ? "0" : inventory.items[i].ID.ToString(); + itemIdStrings[i] = (inventory.Items[i]==null) ? "0" : inventory.Items[i].ID.ToString(); } componentElement.Add(new XAttribute("contained", string.Join(",",itemIdStrings))); diff --git a/Subsurface/Source/Items/Components/Machines/Fabricator.cs b/Subsurface/Source/Items/Components/Machines/Fabricator.cs index ffde50cc3..70b302f71 100644 --- a/Subsurface/Source/Items/Components/Machines/Fabricator.cs +++ b/Subsurface/Source/Items/Components/Machines/Fabricator.cs @@ -162,7 +162,7 @@ namespace Barotrauma.Items.Components ItemContainer container = item.GetComponent(); foreach (ItemPrefab ip in fabricatedItem.RequiredItems) { - var requiredItem = Array.Find(container.inventory.items, it => it != null && it.Prefab == ip); + var requiredItem = container.inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip); container.inventory.RemoveItem(requiredItem); } @@ -182,7 +182,7 @@ namespace Barotrauma.Items.Components ItemContainer container = item.GetComponent(); foreach (ItemPrefab ip in targetItem.RequiredItems) { - if (Array.Find(container.inventory.items, it => it != null && it.Prefab == ip) != null) continue; + if (Array.Find(container.inventory.Items, it => it != null && it.Prefab == ip) != null) continue; selectedItemFrame.GetChild().Enabled = false; break; } diff --git a/Subsurface/Source/Items/Components/Turret.cs b/Subsurface/Source/Items/Components/Turret.cs index ff617ef98..142ce006c 100644 --- a/Subsurface/Source/Items/Components/Turret.cs +++ b/Subsurface/Source/Items/Components/Turret.cs @@ -139,12 +139,12 @@ namespace Barotrauma.Items.Components ItemContainer containerComponent = projectileContainer.GetComponent(); if (containerComponent == null) continue; - for (int i = 0; i < containerComponent.inventory.items.Length; i++) + for (int i = 0; i < containerComponent.inventory.Items.Length; i++) { - if (containerComponent.inventory.items[i] == null) continue; - if ((projectileComponent = containerComponent.inventory.items[i].GetComponent()) != null) + if (containerComponent.inventory.Items[i] == null) continue; + if ((projectileComponent = containerComponent.inventory.Items[i].GetComponent()) != null) { - projectile = containerComponent.inventory.items[i]; + projectile = containerComponent.inventory.Items[i]; break; } } diff --git a/Subsurface/Source/Items/FixRequirement.cs b/Subsurface/Source/Items/FixRequirement.cs index f740473be..99f7228d2 100644 --- a/Subsurface/Source/Items/FixRequirement.cs +++ b/Subsurface/Source/Items/FixRequirement.cs @@ -54,7 +54,7 @@ namespace Barotrauma GUIComponent component = reqFrame.children.Find(c => c.UserData as string == itemName); GUITextBlock text = component as GUITextBlock; - Item item = character.Inventory.items.FirstOrDefault(i => i !=null && (i.Name == itemName || i.HasTag(itemName))); + Item item = character.Inventory.FindItem(itemName); bool itemFound = (item != null); if (!itemFound) success = false; diff --git a/Subsurface/Source/Items/Inventory.cs b/Subsurface/Source/Items/Inventory.cs index 6f431a12b..33e73244a 100644 --- a/Subsurface/Source/Items/Inventory.cs +++ b/Subsurface/Source/Items/Inventory.cs @@ -42,7 +42,7 @@ namespace Barotrauma protected int selectedSlot; - public Item[] items; + public Item[] Items; public Inventory(Entity owner, int capacity, Vector2? centerPos = null, int slotsPerRow=5) { @@ -52,7 +52,7 @@ namespace Barotrauma this.slotsPerRow = slotsPerRow; - items = new Item[capacity]; + Items = new Item[capacity]; CenterPos = (centerPos==null) ? new Vector2(0.5f, 0.5f) : (Vector2)centerPos; } @@ -61,7 +61,7 @@ namespace Barotrauma { for (int i = 0; i < capacity; i++) { - if (items[i] == item) return i; + if (Items[i] == item) return i; } return -1; } @@ -71,12 +71,12 @@ namespace Barotrauma for (int i = 0; i < capacity; i++) { //item is already in the inventory! - if (items[i] == item) return -1; + if (Items[i] == item) return -1; } for (int i = 0; i < capacity; i++) { - if (items[i] == null) return i; + if (Items[i] == null) return i; } return -1; @@ -84,8 +84,8 @@ namespace Barotrauma public virtual bool CanBePut(Item item, int i) { - if (i < 0 || i >= items.Length) return false; - return (items[i] == null); + if (i < 0 || i >= Items.Length) return false; + return (Items[i] == null); } /// @@ -124,7 +124,7 @@ namespace Barotrauma if (item.inventory != null) item.inventory.RemoveItem(item); } - items[i] = item; + Items[i] = item; item.inventory = this; if (item.body != null) { @@ -134,13 +134,18 @@ namespace Barotrauma if (createNetworkEvent) new NetworkEvent(NetworkEventType.InventoryUpdate, Owner.ID, true, true); } + public Item FindItem(string itemName) + { + return Items.FirstOrDefault(i => i != null && (i.Name == itemName || i.HasTag(itemName))); + } + public void RemoveItem(Item item) { //go through the inventory and remove the item from all slots for (int n = 0; n < capacity; n++) { - if (items[n] != item) continue; - items[n] = null; + if (Items[n] != item) continue; + Items[n] = null; item.inventory = null; } } @@ -177,9 +182,9 @@ namespace Barotrauma slotRect.X = startX + (rectWidth + spacing) * (i % slotsPerRow); slotRect.Y = startY + (rectHeight + spacing) * ((int)Math.Floor((double)i / slotsPerRow)); - if (draggingItem == items[i]) draggingItemSlot = slotRect; + if (draggingItem == Items[i]) draggingItemSlot = slotRect; - UpdateSlot(spriteBatch, slotRect, i, items[i], false); + UpdateSlot(spriteBatch, slotRect, i, Items[i], false); } if (draggingItem != null && !draggingItemSlot.Contains(PlayerInput.MousePosition) && draggingItem.container == this.Owner) @@ -247,7 +252,7 @@ namespace Barotrauma { #if DEBUG - System.Diagnostics.Debug.Assert(slotIndex >= 0 && slotIndex < items.Length); + System.Diagnostics.Debug.Assert(slotIndex >= 0 && slotIndex < Items.Length); #else if (slotIndex<0 || slotIndex>=items.Length) return; #endif @@ -266,7 +271,7 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, containerRect, Color.White); Item[] containedItems = null; - if (items[slotIndex] != null) containedItems = items[slotIndex].ContainedItems; + if (Items[slotIndex] != null) containedItems = Items[slotIndex].ContainedItems; if (containedItems != null) { @@ -316,7 +321,7 @@ namespace Barotrauma public virtual bool FillNetworkData(NetworkEventType type, NetBuffer message, object data) { - var foundItems = Array.FindAll(items, i => i != null); + var foundItems = Array.FindAll(Items, i => i != null); message.Write((byte)foundItems.Count()); foreach (Item item in foundItems) { @@ -340,10 +345,10 @@ namespace Barotrauma for (int i = 0; i < capacity; i++) { - if (items[i] == null) continue; - if (!newItemIDs.Contains(items[i].ID)) + if (Items[i] == null) continue; + if (!newItemIDs.Contains(Items[i].ID)) { - items[i].Drop(null, false); + Items[i].Drop(null, false); continue; } } diff --git a/Subsurface/Source/Items/Item.cs b/Subsurface/Source/Items/Item.cs index 3a9e957a5..7609969bc 100644 --- a/Subsurface/Source/Items/Item.cs +++ b/Subsurface/Source/Items/Item.cs @@ -219,7 +219,7 @@ namespace Barotrauma get { ItemContainer c = GetComponent(); - return (c == null) ? null : Array.FindAll(c.inventory.items, i=>i!=null); + return (c == null) ? null : Array.FindAll(c.inventory.Items, i=>i!=null); } } diff --git a/Subsurface/Source/Items/ItemInventory.cs b/Subsurface/Source/Items/ItemInventory.cs index 3acbf3209..712f46c97 100644 --- a/Subsurface/Source/Items/ItemInventory.cs +++ b/Subsurface/Source/Items/ItemInventory.cs @@ -25,14 +25,14 @@ namespace Barotrauma for (int i = 0; i < capacity; i++) { //item is already in the inventory! - if (items[i] == item) return -1; + if (Items[i] == item) return -1; } if (!container.CanBeContained(item)) return -1; for (int i = 0; i < capacity; i++) { - if (items[i] == null) return i; + if (Items[i] == null) return i; } return -1; @@ -40,8 +40,8 @@ namespace Barotrauma public override bool CanBePut(Item item, int i) { - if (i < 0 || i >= items.Length) return false; - return (item!=null && items[i]==null && container.CanBeContained(item)); + if (i < 0 || i >= Items.Length) return false; + return (item!=null && Items[i]==null && container.CanBeContained(item)); } public override bool TryPutItem(Item item, int i, bool createNetworkEvent) diff --git a/Subsurface/Source/Items/ItemPrefab.cs b/Subsurface/Source/Items/ItemPrefab.cs index 76c2658d2..8f9bd920d 100644 --- a/Subsurface/Source/Items/ItemPrefab.cs +++ b/Subsurface/Source/Items/ItemPrefab.cs @@ -161,8 +161,8 @@ namespace Barotrauma name = ToolBox.GetAttributeString(element, "name", ""); if (name == "") DebugConsole.ThrowError("Unnamed item in "+filePath+"!"); - pickThroughWalls = ToolBox.GetAttributeBool(element, "pickthroughwalls", false); - pickDistance = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "pickdistance", 0.0f)); + pickThroughWalls = ToolBox.GetAttributeBool(element, "pickthroughwalls", false); + pickDistance = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "pickdistance", 0.0f)); isLinkable = ToolBox.GetAttributeBool(element, "linkable", false); diff --git a/Subsurface/Source/Items/RelatedItem.cs b/Subsurface/Source/Items/RelatedItem.cs index 2bccf83cc..c0c9db549 100644 --- a/Subsurface/Source/Items/RelatedItem.cs +++ b/Subsurface/Source/Items/RelatedItem.cs @@ -93,7 +93,7 @@ namespace Barotrauma break; case RelationType.Picked: if (character == null || character.Inventory==null) return false; - foreach (Item pickedItem in character.Inventory.items) + foreach (Item pickedItem in character.Inventory.Items) { if (pickedItem == null) continue; diff --git a/Subsurface/Source/Map/Map.cs b/Subsurface/Source/Map/Map.cs index 9e7e68a5e..1a9c9a407 100644 --- a/Subsurface/Source/Map/Map.cs +++ b/Subsurface/Source/Map/Map.cs @@ -328,7 +328,16 @@ namespace Barotrauma Location location = locations[i]; Vector2 pos = rectCenter + (location.MapPosition + offset) * scale; - if (!rect.Contains(pos)) continue; + + + + + Rectangle drawRect = location.Type.Sprite.SourceRect; + Rectangle sourceRect = drawRect; + drawRect.X = (int)pos.X - drawRect.Width/2; + drawRect.Y = (int)pos.Y - drawRect.Width/2; + + if (!rect.Intersects(drawRect)) continue; Color color = location.Connections.Find(c => c.Locations.Contains(currentLocation))==null ? Color.White : Color.Green; @@ -336,16 +345,32 @@ namespace Barotrauma if (location == currentLocation) color = Color.Orange; - location.Type.Sprite.Draw(spriteBatch, pos, color, 0.0f, scale/3.0f); + if (drawRect.X < rect.X) + { + sourceRect.X += rect.X - drawRect.X; + sourceRect.Width -= sourceRect.X; + drawRect.X = rect.X; + } + else if (drawRect.Right > rect.Right) + { + sourceRect.Width -= (drawRect.Right - rect.Right); + } - //int imgIndex = i % 16; - //int xCell = imgIndex % 4; - //int yCell = (int)Math.Floor(imgIndex / 4.0f); - //spriteBatch.Draw(iceCraters, pos, - // new Rectangle(xCell * 64, yCell * 64, 64, 64), - // Color.White, i, - // new Vector2(32, 32), 0.5f*scale, SpriteEffects.None, 0.0f); + if (drawRect.Y < rect.Y) + { + sourceRect.Y += rect.Y - drawRect.Y; + sourceRect.Height -= sourceRect.Y; + drawRect.Y = rect.Y; + } + else if (drawRect.Bottom > rect.Bottom) + { + sourceRect.Height -= drawRect.Bottom - rect.Bottom; + } + drawRect.Width = sourceRect.Width; + drawRect.Height = sourceRect.Height; + + spriteBatch.Draw(location.Type.Sprite.Texture, drawRect, sourceRect, color); } for (int i = 0; i < 3; i++ ) diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index a05debddb..9fd5853ed 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -636,6 +636,8 @@ namespace Barotrauma } } + WayPoint.GenerateSubWaypoints(); + GameMain.LightManager.OnMapLoaded(); ID = ushort.MaxValue-10; diff --git a/Subsurface/Source/Map/WayPoint.cs b/Subsurface/Source/Map/WayPoint.cs index d825fc3bd..b34ba97af 100644 --- a/Subsurface/Source/Map/WayPoint.cs +++ b/Subsurface/Source/Map/WayPoint.cs @@ -23,12 +23,19 @@ namespace Barotrauma //only characters with this job will be spawned at the waypoint private JobPrefab assignedJob; + private Hull currentHull; + public Gap ConnectedGap { get; private set; } + public Hull CurrentHull + { + get { return currentHull; } + } + public SpawnType SpawnType { get { return spawnType; } @@ -396,10 +403,14 @@ namespace Barotrauma return assignedWayPoints; } + public override void OnMapLoaded() + { + currentHull = Hull.FindHull(this.Position); + } public override XElement Save(XDocument doc) { - if (MoveWithLevel) return null; + if (MoveWithLevel || spawnType == SpawnType.Path) return null; XElement element = new XElement("WayPoint"); element.Add(new XAttribute("ID", ID), diff --git a/Subsurface/Source/Physics/PhysicsBody.cs b/Subsurface/Source/Physics/PhysicsBody.cs index 1e42e05a9..cb01c7a18 100644 --- a/Subsurface/Source/Physics/PhysicsBody.cs +++ b/Subsurface/Source/Physics/PhysicsBody.cs @@ -315,8 +315,6 @@ namespace Barotrauma sprite.Draw(spriteBatch, new Vector2(drawPosition.X, -drawPosition.Y), color, -drawRotation, 1.0f, spriteEffect, depth); - //prevPosition = body.Position; - //prevRotation = body.Rotation; } /// @@ -331,20 +329,6 @@ namespace Barotrauma float torque = body.Mass * angle * 60.0f * (force/100.0f); body.ApplyTorque(torque); - - //float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0; - //float totalRotation = desiredAngle - nextAngle; - //while (totalRotation < -180 * DEGTORAD) totalRotation += 360 * DEGTORAD; - //while (totalRotation > 180 * DEGTORAD) totalRotation -= 360 * DEGTORAD; - //float desiredAngularVelocity = totalRotation * 60; - //float torque = body->GetInertia() * desiredAngularVelocity / (1 / 60.0); - //body->ApplyTorque(torque); - - - - - //body.ApplyTorque((Math.Sign(angle) + Math.Max(Math.Min(angle * force, force / 2.0f), -force / 2.0f)) * body.Mass); - //body.ApplyTorque(-body.AngularVelocity * 0.5f * body.Mass); } @@ -352,53 +336,7 @@ namespace Barotrauma { list.Remove(this); GameMain.World.RemoveBody(body); - } - public void FillNetworkData(NetworkEventType type, NetOutgoingMessage message) - { - message.Write(body.Position.X); - message.Write(body.Position.Y); - message.Write(body.LinearVelocity.X); - message.Write(body.LinearVelocity.Y); - - message.Write(body.Rotation); - message.Write(body.AngularVelocity); - } - - public void ReadNetworkData(NetworkEventType type, NetIncomingMessage message) - { - Vector2 newTargetPos = Vector2.Zero; - Vector2 newTargetVel = Vector2.Zero; - - float newTargetRotation = 0.0f, newTargetAngularVel = 0.0f; - try - { - newTargetPos = new Vector2(message.ReadFloat(),message.ReadFloat()); - newTargetVel = new Vector2(message.ReadFloat(),message.ReadFloat()); - - newTargetRotation = message.ReadFloat(); - newTargetAngularVel = message.ReadFloat(); - } - - catch (Exception e) - { -#if DEBUG - DebugConsole.ThrowError("invalid network message", e); -#endif - return; - } - - if (!MathUtils.IsValid(newTargetPos) || !MathUtils.IsValid(newTargetVel) || - !MathUtils.IsValid(newTargetRotation) || !MathUtils.IsValid(newTargetAngularVel)) return; - - targetPosition = newTargetPos; - targetVelocity = newTargetVel; - - targetRotation = newTargetRotation; - targetAngularVelocity = newTargetAngularVel; - - - } } } diff --git a/Subsurface/Source/Screens/EditMapScreen.cs b/Subsurface/Source/Screens/EditMapScreen.cs index 7ff101292..c55633494 100644 --- a/Subsurface/Source/Screens/EditMapScreen.cs +++ b/Subsurface/Source/Screens/EditMapScreen.cs @@ -181,7 +181,7 @@ namespace Barotrauma } else if (dummyCharacter != null) { - foreach (Item item in dummyCharacter.Inventory.items) + foreach (Item item in dummyCharacter.Inventory.Items) { if (item == null) continue; diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 54ca7c8c8e41ed112b6a1220d2a24573b7a9a9ae..1bffe684d319a261108ac845730565d36fd8944b 100644 GIT binary patch delta 26482 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 C6rSJ! delta 16548 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