From b84c965be34e6e2d4234ece743f43cd129de527f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 28 Dec 2017 19:44:48 +0200 Subject: [PATCH] More AI fixes/improvements: - AI characters can refuel the reactor when needed. - AIObjectiveContainItem.CanBeCompleted returns true if the target item cannot be obtained or if the container cannot be reached. - AI characters only weld leaks that are part of some submarine (i.e. not ruins). --- .../Source/Characters/AI/CrewCommander.cs | 2 +- .../Content/Items/Reactor/reactor.xml | 7 +- .../AI/Objectives/AIObjectiveContainItem.cs | 47 ++++++-- .../AI/Objectives/AIObjectiveFixLeaks.cs | 3 + .../AI/Objectives/AIObjectiveGetItem.cs | 21 +++- .../Source/Items/Components/ItemComponent.cs | 4 +- .../Items/Components/Machines/Reactor.cs | 104 +++++++++++++----- 7 files changed, 142 insertions(+), 46 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/CrewCommander.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/CrewCommander.cs index cdd86656d..99746d6d4 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/CrewCommander.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/CrewCommander.cs @@ -250,7 +250,7 @@ namespace Barotrauma var existingOrder = characterFrame.children.Find(c => c.UserData is Order); if (existingOrder != null) characterFrame.RemoveChild(existingOrder); - var orderFrame = new GUIFrame(new Rectangle(-5, characterFrame.Rect.Height, characterFrame.Rect.Width, 30 + order.Options.Length * 15), "InnerFrame", characterFrame); + var orderFrame = new GUIFrame(new Rectangle(-5, 40, characterFrame.Rect.Width, 30 + order.Options.Length * 15), "InnerFrame", characterFrame); /*orderFrame.OutlineColor = Color.LightGray * 0.5f;*/ orderFrame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); orderFrame.UserData = order; diff --git a/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml b/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml index 255bf051f..567d58a35 100644 --- a/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml +++ b/Barotrauma/BarotraumaShared/Content/Items/Reactor/reactor.xml @@ -23,6 +23,7 @@ + @@ -55,8 +56,7 @@ @@ -73,8 +73,7 @@ diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index 2e1f95d8c..f36d8fbab 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -1,19 +1,27 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; +using System; using System.Linq; namespace Barotrauma { class AIObjectiveContainItem: AIObjective { + public int MinContainedAmount = 1; + private string[] itemNames; private ItemContainer container; - - bool isCompleted; + + private bool isCompleted; public bool IgnoreAlreadyContainedItems; + public Func GetItemPriority; + + private AIObjectiveGetItem getItemObjective; + private AIObjectiveGoTo goToObjective; + public AIObjectiveContainItem(Character character, string itemName, ItemContainer container) : this(character, new string[] { itemName }, container) { @@ -28,7 +36,28 @@ namespace Barotrauma public override bool IsCompleted() { - return isCompleted || itemNames.Any(name => container.Inventory.FindItem(name) != null); + if (isCompleted) return true; + + int containedItemCount = 0; + foreach (Item item in container.Inventory.Items) + { + if (item != null && itemNames.Any(name => item.Prefab.NameMatches(name) || item.HasTag(name))) containedItemCount++; + } + + return containedItemCount >= MinContainedAmount; + } + + public override bool CanBeCompleted + { + get + { + if (goToObjective != null) + { + return goToObjective.CanBeCompleted; + } + + return getItemObjective == null || !getItemObjective.CanBeCompleted; + } } public override float GetPriority(AIObjectiveManager objectiveManager) @@ -49,9 +78,10 @@ namespace Barotrauma var itemToContain = character.Inventory.FindItem(itemNames); if (itemToContain == null) { - var getItem = new AIObjectiveGetItem(character, itemNames); - getItem.IgnoreContainedItems = IgnoreAlreadyContainedItems; - AddSubObjective(getItem); + getItemObjective = new AIObjectiveGetItem(character, itemNames); + getItemObjective.GetItemPriority = GetItemPriority; + getItemObjective.IgnoreContainedItems = IgnoreAlreadyContainedItems; + AddSubObjective(getItemObjective); return; } @@ -68,9 +98,10 @@ namespace Barotrauma else { if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance - && !container.Item.IsInsideTrigger(character.Position)) + && !container.Item.IsInsideTrigger(character.WorldPosition)) { - AddSubObjective(new AIObjectiveGoTo(container.Item, character)); + goToObjective = new AIObjectiveGoTo(container.Item, character); + AddSubObjective(goToObjective); return; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index 2414a726c..727210533 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -106,6 +106,9 @@ namespace Barotrauma if (gap.ConnectedWall == null) continue; if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue; + //TODO: prevent the AI characters from fixing leaks in the enemy sub in sub-vs-sub missions if/when multiplayer bots are implemented + if (gap.Submarine == null) continue; + float gapPriority = GetGapFixPriority(gap); int index = 0; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 2f69713bd..5f7d81577 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -1,5 +1,6 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; @@ -7,6 +8,8 @@ namespace Barotrauma { class AIObjectiveGetItem : AIObjective { + public Func GetItemPriority; + private string[] itemNames; private Item targetItem, moveToTarget; @@ -19,6 +22,8 @@ namespace Barotrauma private AIObjectiveGoTo goToObjective; + private float currItemPriority; + private bool equip; public override bool CanBeCompleted @@ -172,8 +177,20 @@ namespace Barotrauma if (owner != null && !owner.IsDead) continue; } - //ignore if item is further away than the currently targeted item - if (moveToTarget != null && Vector2.DistanceSquared((rootContainer ?? item).Position, character.Position) > currDist) continue; + float itemPriority = 0.0f; + if (GetItemPriority != null) + { + //ignore if the item has zero priority + itemPriority = GetItemPriority(item); + if (itemPriority <= 0.0f) continue; + } + + itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f; + + //ignore if the item has a lower priority than the currently selected one + if (moveToTarget != null && itemPriority < currItemPriority) continue; + + currItemPriority = itemPriority; targetItem = item; moveToTarget = rootContainer ?? item; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index c65d884c3..d30282e3b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -399,7 +399,7 @@ namespace Barotrauma.Items.Components float[] skillSuccess = new float[requiredSkills.Count]; - for (int i = 0; i < requiredSkills.Count; i++ ) + for (int i = 0; i < requiredSkills.Count; i++) { int characterLevel = character.GetSkillLevel(requiredSkills[i].Name); @@ -408,7 +408,7 @@ namespace Barotrauma.Items.Components float average = skillSuccess.Average(); - return (average+100.0f)/2.0f; + return (average + 100.0f) / 2.0f; } public virtual void FlipX() { } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index 9e3b031f5..dcf134a29 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -3,6 +3,7 @@ using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -141,6 +142,9 @@ namespace Barotrauma.Items.Components public float AvailableFuel { get; set; } + private float availableHeat, availableCooling; + private float prevTemperature, temperatureChange; + [Serialize(500.0f, true)] public float ShutDownTemp { @@ -191,12 +195,20 @@ namespace Barotrauma.Items.Components fissionRate = Math.Min(fissionRate, AvailableFuel); - float heat = 80 * fissionRate * (AvailableFuel / 2000.0f); - float heatDissipation = 50 * coolingRate + Math.Max(ExtraCooling, 5.0f); + //the amount of cooling is always non-zero, so that the reactor always needs + //to generate some amount of heat to prevent the temperature from dropping + availableCooling = Math.Max(ExtraCooling, 5.0f); + availableHeat = 80 * (AvailableFuel / 2000.0f); - float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f; + float heat = availableHeat * fissionRate; + float heatDissipation = 50 * coolingRate + availableCooling; + + float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f; Temperature = temperature + deltaTemp; + temperatureChange = Temperature - prevTemperature; + prevTemperature = temperature; + if (temperature > fireTemp && temperature - deltaTemp < fireTemp) { #if CLIENT @@ -345,37 +357,71 @@ namespace Barotrauma.Items.Components public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { + float degreeOfSuccess = DegreeOfSuccess(character); + + //characters with insufficient skill levels don't refuel the reactor + if (degreeOfSuccess > 0.2f) + { + //remove used-up fuel from the reactor + var containedItems = item.ContainedItems; + foreach (Item item in containedItems) + { + if (item != null && item.Condition <= 0.0f) + { + item.Drop(); + } + } + + //the temperature is too low and not increasing even though the fission rate is high and cooling low + // -> we need more fuel + if (temperature < load * 0.5f && temperatureChange <= 0.0f && fissionRate > 0.9f && coolingRate < 0.1f) + { + var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "Fuel Rod", "reactorfuel" }, item.GetComponent()); + containFuelObjective.MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.NameMatches("Fuel Rod") || i.HasTag("reactorfuel")) + 1; + containFuelObjective.GetItemPriority = (Item fuelItem) => + { + if (fuelItem.ParentInventory?.Owner is Item) + { + //don't take fuel from other reactors + if (((Item)fuelItem.ParentInventory.Owner).GetComponent() != null) return 0.0f; + } + return 1.0f; + }; + objective.AddSubObjective(containFuelObjective); + + return false; + } + } + + switch (objective.Option.ToLowerInvariant()) - { - case "power up": - float tempDiff = load - temperature; + { + case "power up": + float tempDiff = load - temperature; - shutDownTemp = Math.Min(load + 1000.0f, 7500.0f); + shutDownTemp = Math.Min(load + 1000.0f, 7500.0f); - //temperature too high/low - if (Math.Abs(tempDiff)>500.0f) - { - AutoTemp = false; - FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff); - CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff); - } - //temperature OK - else - { - AutoTemp = true; - } + //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually + if (Math.Abs(tempDiff) < 500.0f || degreeOfSuccess < 0.5f) + { + AutoTemp = true; + } + else + { + AutoTemp = false; + //higher skill levels make the character adjust the temperature faster + FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess; + CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess; + } + break; + case "shutdown": + shutDownTemp = 0.0f; + break; + } - break; - case "shutdown": - - shutDownTemp = 0.0f; - - break; - } - - return false; + return false; } - + public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power) { switch (connection.Name)