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).
This commit is contained in:
Joonas Rikkonen
2017-12-28 19:44:48 +02:00
parent d074d3d443
commit b84c965be3
7 changed files with 142 additions and 46 deletions
@@ -250,7 +250,7 @@ namespace Barotrauma
var existingOrder = characterFrame.children.Find(c => c.UserData is Order); var existingOrder = characterFrame.children.Find(c => c.UserData is Order);
if (existingOrder != null) characterFrame.RemoveChild(existingOrder); 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.OutlineColor = Color.LightGray * 0.5f;*/
orderFrame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); orderFrame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
orderFrame.UserData = order; orderFrame.UserData = order;
@@ -23,6 +23,7 @@
<Reactor canbeselected = "true"> <Reactor canbeselected = "true">
<GuiFrame rect="0,0,760,460" alignment="Center" style="ItemUI"/> <GuiFrame rect="0,0,760,460" alignment="Center" style="ItemUI"/>
<RequiredSkill name="Construction" level="30"/>
<StatusEffect type="InWater" target="This" Temperature="-500.0"/> <StatusEffect type="InWater" target="This" Temperature="-500.0"/>
<StatusEffect type="OnActive" target="Contained" targetnames="Fuel Rod, Heat Absorber, Temperature Control Circuit" Condition="-0.1" /> <StatusEffect type="OnActive" target="Contained" targetnames="Fuel Rod, Heat Absorber, Temperature Control Circuit" Condition="-0.1" />
<sound file="reactor.ogg" type="OnActive" range="2000.0" volume="FissionRate" volumemultiplier="0.02" loop="true"/> <sound file="reactor.ogg" type="OnActive" range="2000.0" volume="FissionRate" volumemultiplier="0.02" loop="true"/>
@@ -55,8 +56,7 @@
<Item <Item
name="Fuel Rod" name="Fuel Rod"
Tags="smallitem" Tags="smallitem,reactorfuel"
price="200"> price="200">
<Deconstruct time="10"> <Deconstruct time="10">
@@ -73,8 +73,7 @@
<Item <Item
name="Incendium Fuel Rod" name="Incendium Fuel Rod"
Tags="smallitem" Tags="smallitem,reactorfuel"
spritecolor="0.5,0.0,0.0,1.0"> spritecolor="0.5,0.0,0.0,1.0">
<Deconstruct time="10"> <Deconstruct time="10">
@@ -1,19 +1,27 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
class AIObjectiveContainItem: AIObjective class AIObjectiveContainItem: AIObjective
{ {
public int MinContainedAmount = 1;
private string[] itemNames; private string[] itemNames;
private ItemContainer container; private ItemContainer container;
bool isCompleted; private bool isCompleted;
public bool IgnoreAlreadyContainedItems; public bool IgnoreAlreadyContainedItems;
public Func<Item, float> GetItemPriority;
private AIObjectiveGetItem getItemObjective;
private AIObjectiveGoTo goToObjective;
public AIObjectiveContainItem(Character character, string itemName, ItemContainer container) public AIObjectiveContainItem(Character character, string itemName, ItemContainer container)
: this(character, new string[] { itemName }, container) : this(character, new string[] { itemName }, container)
{ {
@@ -28,7 +36,28 @@ namespace Barotrauma
public override bool IsCompleted() 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) public override float GetPriority(AIObjectiveManager objectiveManager)
@@ -49,9 +78,10 @@ namespace Barotrauma
var itemToContain = character.Inventory.FindItem(itemNames); var itemToContain = character.Inventory.FindItem(itemNames);
if (itemToContain == null) if (itemToContain == null)
{ {
var getItem = new AIObjectiveGetItem(character, itemNames); getItemObjective = new AIObjectiveGetItem(character, itemNames);
getItem.IgnoreContainedItems = IgnoreAlreadyContainedItems; getItemObjective.GetItemPriority = GetItemPriority;
AddSubObjective(getItem); getItemObjective.IgnoreContainedItems = IgnoreAlreadyContainedItems;
AddSubObjective(getItemObjective);
return; return;
} }
@@ -68,9 +98,10 @@ namespace Barotrauma
else else
{ {
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance 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; return;
} }
@@ -106,6 +106,9 @@ namespace Barotrauma
if (gap.ConnectedWall == null) continue; if (gap.ConnectedWall == null) continue;
if (gap.ConnectedDoor != null || gap.Open <= 0.0f) 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); float gapPriority = GetGapFixPriority(gap);
int index = 0; int index = 0;
@@ -1,5 +1,6 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -7,6 +8,8 @@ namespace Barotrauma
{ {
class AIObjectiveGetItem : AIObjective class AIObjectiveGetItem : AIObjective
{ {
public Func<Item, float> GetItemPriority;
private string[] itemNames; private string[] itemNames;
private Item targetItem, moveToTarget; private Item targetItem, moveToTarget;
@@ -19,6 +22,8 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private bool equip; private bool equip;
public override bool CanBeCompleted public override bool CanBeCompleted
@@ -172,8 +177,20 @@ namespace Barotrauma
if (owner != null && !owner.IsDead) continue; if (owner != null && !owner.IsDead) continue;
} }
//ignore if item is further away than the currently targeted item float itemPriority = 0.0f;
if (moveToTarget != null && Vector2.DistanceSquared((rootContainer ?? item).Position, character.Position) > currDist) continue; 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; targetItem = item;
moveToTarget = rootContainer ?? item; moveToTarget = rootContainer ?? item;
@@ -399,7 +399,7 @@ namespace Barotrauma.Items.Components
float[] skillSuccess = new float[requiredSkills.Count]; 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); int characterLevel = character.GetSkillLevel(requiredSkills[i].Name);
@@ -408,7 +408,7 @@ namespace Barotrauma.Items.Components
float average = skillSuccess.Average(); float average = skillSuccess.Average();
return (average+100.0f)/2.0f; return (average + 100.0f) / 2.0f;
} }
public virtual void FlipX() { } public virtual void FlipX() { }
@@ -3,6 +3,7 @@ using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
@@ -141,6 +142,9 @@ namespace Barotrauma.Items.Components
public float AvailableFuel { get; set; } public float AvailableFuel { get; set; }
private float availableHeat, availableCooling;
private float prevTemperature, temperatureChange;
[Serialize(500.0f, true)] [Serialize(500.0f, true)]
public float ShutDownTemp public float ShutDownTemp
{ {
@@ -191,12 +195,20 @@ namespace Barotrauma.Items.Components
fissionRate = Math.Min(fissionRate, AvailableFuel); fissionRate = Math.Min(fissionRate, AvailableFuel);
float heat = 80 * fissionRate * (AvailableFuel / 2000.0f); //the amount of cooling is always non-zero, so that the reactor always needs
float heatDissipation = 50 * coolingRate + Math.Max(ExtraCooling, 5.0f); //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; Temperature = temperature + deltaTemp;
temperatureChange = Temperature - prevTemperature;
prevTemperature = temperature;
if (temperature > fireTemp && temperature - deltaTemp < fireTemp) if (temperature > fireTemp && temperature - deltaTemp < fireTemp)
{ {
#if CLIENT #if CLIENT
@@ -345,37 +357,71 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) 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<ItemContainer>());
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<Reactor>() != null) return 0.0f;
}
return 1.0f;
};
objective.AddSubObjective(containFuelObjective);
return false;
}
}
switch (objective.Option.ToLowerInvariant()) switch (objective.Option.ToLowerInvariant())
{ {
case "power up": case "power up":
float tempDiff = load - temperature; float tempDiff = load - temperature;
shutDownTemp = Math.Min(load + 1000.0f, 7500.0f); shutDownTemp = Math.Min(load + 1000.0f, 7500.0f);
//temperature too high/low //characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (Math.Abs(tempDiff)>500.0f) if (Math.Abs(tempDiff) < 500.0f || degreeOfSuccess < 0.5f)
{ {
AutoTemp = false; AutoTemp = true;
FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff); }
CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff); else
} {
//temperature OK AutoTemp = false;
else //higher skill levels make the character adjust the temperature faster
{ FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
AutoTemp = true; CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
} }
break;
case "shutdown":
shutDownTemp = 0.0f;
break;
}
break; return false;
case "shutdown":
shutDownTemp = 0.0f;
break;
}
return false;
} }
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power) public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
{ {
switch (connection.Name) switch (connection.Name)