(18eb9f712) Fix bots stealing items if they happen to target them before somebody equips them.

This commit is contained in:
Joonas Rikkonen
2019-05-16 06:19:10 +03:00
parent df89114850
commit e8ee3f9d18
27 changed files with 338 additions and 421 deletions
@@ -493,20 +493,25 @@ namespace Barotrauma
item.ContainedItems.Any(i => i.HasTag(containedTag) && i.ConditionPercentage > conditionPercentage)));
}
/// <summary>
/// Updates the hull safety for all ai characters in the team.
/// </summary>
public static void PropagateHullSafety(Character character, Hull hull)
public static void DoForEachCrewMember(Character character, Action<HumanAIController> action)
{
foreach (var c in Character.CharacterList)
{
if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character))
{
humanAi.RefreshHullSafety(hull);
action(humanAi);
}
}
}
/// <summary>
/// Updates the hull safety for all ai characters in the team.
/// </summary>
public static void PropagateHullSafety(Character character, Hull hull)
{
DoForEachCrewMember(character, (humanAi) => humanAi.RefreshHullSafety(hull));
}
private void RefreshHullSafety(Hull hull)
{
if (GetHullSafety(hull) > HULL_SAFETY_THRESHOLD)
@@ -577,21 +582,26 @@ namespace Barotrauma
private static bool AddTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
{
bool targetAdded = false;
foreach (var c in Character.CharacterList)
DoForEachCrewMember(caller, humanAI =>
{
if (IsFriendly(caller, c) && c.AIController is HumanAIController humanAI)
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
if (objective != null)
{
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
if (objective == null) { continue; }
if (!targetAdded && objective.AddTarget(target))
{
targetAdded = true;
}
}
}
});
return targetAdded;
}
public static void RemoveTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
{
DoForEachCrewMember(caller, humanAI =>
humanAI.ObjectiveManager.GetObjective<T1>()?.ReportedTargets.Remove(target));
}
public float GetHullSafety(Hull hull)
{
if (hull == null) { return 0; }
@@ -71,6 +71,7 @@ namespace Barotrauma
#if DEBUG
DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is completed.");
#endif
subObjective.OnCompleted();
subObjectives.Remove(subObjective);
}
else if (!subObjective.CanBeCompleted)
@@ -93,9 +94,9 @@ namespace Barotrauma
bool wasCompleted = IsCompleted();
Act(deltaTime);
if (!wasCompleted && IsCompleted())
if (IsCompleted())
{
Completed?.Invoke();
OnCompleted();
}
}
@@ -213,6 +214,15 @@ namespace Barotrauma
//}
}
protected virtual void OnCompleted()
{
if (Completed != null)
{
Completed();
Completed = null;
}
}
public virtual void Reset() { }
protected abstract void Act(float deltaTime);
@@ -65,5 +65,8 @@ namespace Barotrauma
protected override AIObjective ObjectiveConstructor(PowerContainer battery)
=> new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier) { IsLoop = false };
protected override void OnObjectiveCompleted(AIObjective objective, PowerContainer target)
=> HumanAIController.RemoveTargets<AIObjectiveChargeBatteries, PowerContainer>(character, target);
}
}
@@ -1,6 +1,7 @@
using System.Linq;
using System.Collections.Generic;
using Barotrauma.Extensions;
using System;
namespace Barotrauma
{
@@ -20,7 +21,11 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFires;
protected override IEnumerable<Hull> GetList() => Hull.hullList;
protected override AIObjective ObjectiveConstructor(Hull target) => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier);
protected override AIObjective ObjectiveConstructor(Hull target)
=> new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier);
protected override void OnObjectiveCompleted(AIObjective objective, Hull target)
=> HumanAIController.RemoveTargets<AIObjectiveExtinguishFires, Hull>(character, target);
public static bool IsValidTarget(Hull hull, Character character)
{
@@ -21,14 +21,18 @@ namespace Barotrauma
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier);
protected override float TargetEvaluation()
{
// TODO: sorting criteria
return 100;
}
protected override AIObjective ObjectiveConstructor(Character target)
=> new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier);
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
=> HumanAIController.RemoveTargets<AIObjectiveFightIntruders, Character>(character, target);
public static bool IsValidTarget(Character target, Character character)
{
if (target == null || target.IsDead || target.Removed) { return false; }
@@ -773,6 +773,32 @@ namespace Barotrauma
{
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
}
if (subObjectives.Any()) { return; }
var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
#endif
abandon = true;
return;
}
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
// TODO: use the collider size/reach?
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{
HumanAIController.AnimController.Crouching = true;
}
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach * 1.5f;
if (canOperate)
{
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
}
else
{
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
}
}
private Vector2 GetStandPosition()
@@ -27,7 +27,11 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
protected override float TargetEvaluation() => Targets.Max(t => GetLeakSeverity(t));
protected override IEnumerable<Gap> GetList() => Gap.GapList;
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
protected override AIObjective ObjectiveConstructor(Gap gap)
=> new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
protected override void OnObjectiveCompleted(AIObjective objective, Gap target)
=> HumanAIController.RemoveTargets<AIObjectiveFixLeaks, Gap>(character, target);
public static bool IsValidTarget(Gap gap, Character character)
{
@@ -103,37 +103,44 @@ namespace Barotrauma
if (moveToTarget.CurrentHull == character.CurrentHull &&
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
{
int targetSlot = -1;
if (equip)
if (IsTakenBySomeone(targetItem))
{
var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null)
abandon = true;
}
else
{
int targetSlot = -1;
if (equip)
{
abandon = true;
return;
}
//check if all the slots required by the item are free
foreach (InvSlotType slots in pickable.AllowedSlots)
{
if (slots.HasFlag(InvSlotType.Any)) { continue; }
for (int i = 0; i < character.Inventory.Items.Length; i++)
var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null)
{
//slot not needed by the item, continue
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; }
targetSlot = i;
//slot free, continue
if (character.Inventory.Items[i] == null) { continue; }
//try to move the existing item to LimbSlot.Any and continue if successful
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) { continue; }
//if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop(character);
abandon = true;
return;
}
//check if all the slots required by the item are free
foreach (InvSlotType slots in pickable.AllowedSlots)
{
if (slots.HasFlag(InvSlotType.Any)) { continue; }
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
//slot not needed by the item, continue
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; }
targetSlot = i;
//slot free, continue
if (character.Inventory.Items[i] == null) { continue; }
//try to move the existing item to LimbSlot.Any and continue if successful
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) { continue; }
//if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop(character);
}
}
}
}
targetItem.TryInteract(character, false, true);
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
{
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
targetItem.TryInteract(character, false, true);
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
{
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
}
}
}
else
@@ -185,23 +192,7 @@ namespace Barotrauma
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
// TODO: need to exclude items that already are in the inventory?
//// Don't allow to get items in rooms that have a fire (except fire extinguishers) or an enemy inside (except weapons)
//if (!itemIdentifiers.Contains("extinguisher") && item.CurrentHull.FireSources.Count > 0 ||
// item.GetComponent<MeleeWeapon>() == null && item.GetComponent<RangedWeapon>() == null && Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { continue; }
//if the item is inside a character's inventory, don't steal it unless the character is dead
if (item.ParentInventory is CharacterInventory)
{
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
}
//if the item is inside an item, which is inside a character's inventory, don't steal it
Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
}
if (IsTakenBySomeone(item)) { continue; }
float itemPriority = 0.0f;
if (GetItemPriority != null)
{
@@ -209,6 +200,7 @@ namespace Barotrauma
itemPriority = GetItemPriority(item);
if (itemPriority <= 0.0f) { continue; }
}
Item rootContainer = item.GetRootContainer();
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; }
@@ -271,10 +263,29 @@ namespace Barotrauma
{
bool isEquipped = !equip || character.HasEquippedItem(item);
if (character.Inventory.Items.Contains(item) && isEquipped) { return true; }
if (!equip)
{
Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{
return rootContainer.ParentInventory.Owner == character;
}
}
return false;
}
public static bool IsTakenBySomeone(Item item)
{
//if the item is inside a character's inventory, don't steal it unless the character is dead
if (item.ParentInventory is CharacterInventory)
{
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { return true; }
}
//if the item is inside an item, which is inside a character's inventory, don't steal it unless the character is dead
Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{
return rootContainer.ParentInventory.Owner == character;
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { return true; }
}
return false;
}
@@ -73,21 +73,6 @@ namespace Barotrauma
}
}
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
{
if (randomTimer > 0)
{
randomTimer -= deltaTime;
}
else
{
SetRandom();
}
}
}
public override bool IsCompleted() => false;
public override bool CanBeCompleted => true;
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
@@ -87,7 +88,7 @@ namespace Barotrauma
}
}
// the timer is set between 1 and 10 seconds, depending on the priority
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1);
public override void Reset()
@@ -158,13 +159,18 @@ namespace Barotrauma
if (!Objectives.TryGetValue(target, out AIObjective objective))
{
objective = ObjectiveConstructor(target);
objective.Completed += () => ReportedTargets.Remove(target);
objective.Completed += () => OnObjectiveCompleted(objective, target);
Objectives.Add(target, objective);
AddSubObjective(objective);
if (!subObjectives.Contains(objective))
{
subObjectives.Add(objective);
}
}
}
}
protected abstract void OnObjectiveCompleted(AIObjective objective, T target);
/// <summary>
/// List of all possible items of the specified type. Used for filtering the removed objectives.
/// </summary>
@@ -358,6 +358,10 @@ namespace Barotrauma
{
isCompleted = true;
}
if (component.AIOperate(deltaTime, character, this))
{
isCompleted = true;
}
}
else
{
@@ -54,7 +54,6 @@ namespace Barotrauma
return pumpList;
}
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false };
protected override float TargetEvaluation()
{
if (Option == "stoppumping")
@@ -66,5 +65,11 @@ namespace Barotrauma
return Targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100)));
}
}
protected override AIObjective ObjectiveConstructor(Pump pump)
=> new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false };
protected override void OnObjectiveCompleted(AIObjective objective, Pump target)
=> HumanAIController.RemoveTargets<AIObjectivePumpWater, Pump>(character, target);
}
}
@@ -54,7 +54,12 @@ namespace Barotrauma
protected override float TargetEvaluation() => Targets.Max(t => 100 - t.ConditionPercentage);
protected override IEnumerable<Item> GetList() => Item.ItemList;
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
protected override AIObjective ObjectiveConstructor(Item item)
=> new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
=> HumanAIController.RemoveTargets<AIObjectiveRepairItems, Item>(character, target);
public static bool IsValidTarget(Item item, Character character)
{
@@ -22,12 +22,16 @@ namespace Barotrauma
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
protected override float TargetEvaluation() => Targets.Max(t => GetVitalityFactor(t)) * 100;
public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality;
protected override AIObjective ObjectiveConstructor(Character target)
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
=> HumanAIController.RemoveTargets<AIObjectiveRescueAll, Character>(character, target);
public static bool IsValidTarget(Character target, Character character)
{
if (target == null || target.IsDead || target.Removed) { return false; }
@@ -734,6 +734,25 @@ namespace Barotrauma.Items.Components
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
}
else
{
if (outputContainer.Inventory.Items.All(i => i != null))
{
targetItem.Drop(dropper: null);
}
else
{
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);
@@ -212,6 +212,33 @@ namespace Barotrauma.Items.Components
}
}
public Vector2? PosToMaintain
{
get { return posToMaintain; }
set { posToMaintain = value; }
}
struct ObstacleDebugInfo
{
public Vector2 Point1;
public Vector2 Point2;
public Vector2? Intersection;
public float Dot;
public Vector2 AvoidStrength;
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
{
Point1 = edge.Point1;
Point2 = edge.Point2;
Intersection = intersection;
Dot = dot;
AvoidStrength = avoidStrength;
}
}
//edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
@@ -1128,6 +1128,10 @@ namespace Barotrauma
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
if (!broken)
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
@@ -1172,6 +1176,27 @@ namespace Barotrauma
}
}
public void UpdateTransform()
{
Submarine prevSub = Submarine;
FindHull();
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
if (Math.Abs(body.LinearVelocity.X) > NetConfig.MaxPhysicsBodyVelocity ||
Math.Abs(body.LinearVelocity.Y) > NetConfig.MaxPhysicsBodyVelocity)
{
body.LinearVelocity = new Vector2(
MathHelper.Clamp(body.LinearVelocity.X, -NetConfig.MaxPhysicsBodyVelocity, NetConfig.MaxPhysicsBodyVelocity),
MathHelper.Clamp(body.LinearVelocity.Y, -NetConfig.MaxPhysicsBodyVelocity, NetConfig.MaxPhysicsBodyVelocity));
}
}
public void UpdateTransform()
{
Submarine prevSub = Submarine;
@@ -607,6 +607,25 @@ namespace Barotrauma
}
}
public string DisplayName
{
get;
private set;
}
private string roomName;
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
public string RoomName
{
get { return roomName; }
set
{
if (roomName == value) { return; }
roomName = value;
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
}
}
public override Rectangle Rect
{
get