(965c31410a) Unstable v0.10.4.0

This commit is contained in:
Juan Pablo Arce
2020-07-21 08:57:50 -03:00
parent 4f8bd39789
commit 33d3a41104
546 changed files with 45952 additions and 25762 deletions
@@ -34,6 +34,37 @@ namespace Barotrauma
public readonly HashSet<Hull> UnsafeHulls = new HashSet<Hull>();
public readonly List<Item> IgnoredItems = new List<Item>();
private class HullSafety
{
public float safety;
public float timer;
public bool IsStale => timer <= 0;
public HullSafety(float safety)
{
Reset(safety);
}
public void Reset(float safety)
{
this.safety = safety;
// How long before the hull safety is considered stale
timer = 0.5f;
}
/// <summary>
/// Returns true when the safety is stale
/// </summary>
public bool Update(float deltaTime)
{
timer = Math.Max(timer - deltaTime, 0);
return IsStale;
}
}
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
private SteeringManager outsideSteering, insideSteering;
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
@@ -58,6 +89,10 @@ namespace Barotrauma
public float CurrentHullSafety { get; private set; } = 100;
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
private readonly List<Character> attackers = new List<Character>();
public HumanAIController(Character c) : base(c)
{
if (!c.IsHuman)
@@ -67,7 +102,7 @@ namespace Barotrauma
insideSteering = new IndoorsSteeringManager(this, true, false);
outsideSteering = new SteeringManager(this);
objectiveManager = new AIObjectiveManager(c);
reactTimer = Rand.Range(0f, reactionTime);
reactTimer = GetReactionTime();
sortTimer = Rand.Range(0f, sortObjectiveInterval);
InitProjSpecific();
}
@@ -78,6 +113,12 @@ namespace Barotrauma
if (DisableCrewAI || Character.IsIncapacitated || Character.Removed) { return; }
base.Update(deltaTime);
foreach (var values in knownHulls)
{
HullSafety hullSafety = values.Value;
hullSafety.Update(deltaTime);
}
if (unreachableClearTimer > 0)
{
unreachableClearTimer -= deltaTime;
@@ -123,6 +164,15 @@ namespace Barotrauma
}
objectiveManager.UpdateObjectives(deltaTime);
//slowly forget about damage done by attackers
foreach (Character enemy in attackers)
{
if (damageDoneByAttacker[enemy] > 0)
{
damageDoneByAttacker[enemy] -= deltaTime * 0.01f;
}
}
if (reactTimer > 0.0f)
{
reactTimer -= deltaTime;
@@ -136,7 +186,15 @@ namespace Barotrauma
{
if (Character.CurrentHull != null)
{
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
if (Character.TeamID == Character.TeamType.FriendlyNPC)
{
// Outpost npcs don't inform each other about threads, like crew members do.
VisibleHulls.ForEach(h => RefreshHullSafety(h));
}
else
{
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
}
}
if (Character.SpeechImpediment < 100.0f)
{
@@ -147,7 +205,7 @@ namespace Barotrauma
UpdateSpeaking();
}
UnequipUnnecessaryItems();
reactTimer = reactionTime * Rand.Range(0.75f, 1.25f);
reactTimer = GetReactionTime();
}
if (objectiveManager.CurrentObjective == null) { return; }
@@ -170,7 +228,7 @@ namespace Barotrauma
else
{
float xDiff = goTo.Target.WorldPosition.X - Character.WorldPosition.X;
run = Math.Abs(xDiff) > 300;
run = Math.Abs(xDiff) > 500;
}
}
}
@@ -276,127 +334,137 @@ namespace Barotrauma
{
if (!NeedsDivingGear(Character, Character.CurrentHull, out _))
{
bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
bool shouldKeepTheGearOn = Character.AnimController.HeadInWater
|| Character.CurrentHull.WaterPercentage > 50
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
bool takeMaskOff = !Character.AnimController.HeadInWater && oxygenLow;
if (!removeDivingSuit)
bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
if (oxygenLow)
{
if (shouldKeepTheGearOn)
{
removeDivingSuit = false;
}
shouldKeepTheGearOn = false;
}
if (!takeMaskOff)
bool removeDivingSuit = !shouldKeepTheGearOn;
bool takeMaskOff = !shouldKeepTheGearOn;
if (!shouldKeepTheGearOn && !oxygenLow)
{
if (shouldKeepTheGearOn)
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
{
takeMaskOff = false;
removeDivingSuit = true;
takeMaskOff = true;
}
}
if (!shouldKeepTheGearOn && (!takeMaskOff || !removeDivingSuit))
{
foreach (var objective in ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(includingSelf: true))
else
{
if (objective is AIObjectiveGoTo gotoObjective)
bool removeSuit = false;
bool removeMask = false;
foreach (var objective in ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(includingSelf: true))
{
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
Hull targetHull = gotoObjective.GetTargetHull();
bool targetIsOutside = (gotoObjective.Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
if (targetIsOutside || NeedsDivingGear(Character, targetHull, out _))
if (objective is AIObjectiveGoTo gotoObjective)
{
removeDivingSuit = false;
takeMaskOff = false;
break;
}
else if (gotoObjective.mimic)
{
if (!removeDivingSuit)
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
Hull targetHull = gotoObjective.GetTargetHull();
bool targetIsOutside = (gotoObjective.Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
if (targetIsOutside || NeedsDivingGear(Character, targetHull, out _))
{
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
removeDivingSuit = false;
takeMaskOff = false;
break;
}
if (!takeMaskOff)
else if (gotoObjective.mimic)
{
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
}
}
}
}
}
if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit)
{
if (removeDivingSuit)
{
var divingSuit = Character.Inventory.FindItemByTag("divingsuit");
if (divingSuit != null)
{
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
divingSuit.Drop(Character);
}
else
{
findItemState = FindItemState.DivingSuit;
if (FindSuitableContainer(divingSuit, out Item targetContainer))
{
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
if (!removeSuit)
{
var decontainObjective = new AIObjectiveDecontainItem(Character, divingSuit, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>())
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
if (removeDivingSuit)
{
DropIfFailsToContain = false
};
decontainObjective.Abandoned += () =>
{
IgnoredItems.Add(targetContainer);
};
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
removeSuit = true;
}
}
else
if (!removeMask)
{
divingSuit.Drop(Character);
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
if (takeMaskOff)
{
removeMask = true;
}
}
}
}
}
}
}
if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
{
if (takeMaskOff)
if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit)
{
var mask = Character.Inventory.FindItemByTag("divingmask");
if (mask != null && Character.Inventory.IsInLimbSlot(mask, InvSlotType.Head))
if (removeDivingSuit)
{
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
var divingSuit = Character.Inventory.FindItemByTag("divingsuit");
if (divingSuit != null)
{
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
mask.Drop(Character);
divingSuit.Drop(Character);
}
else
{
findItemState = FindItemState.DivingMask;
if (FindSuitableContainer(mask, out Item targetContainer))
findItemState = FindItemState.DivingSuit;
if (FindSuitableContainer(divingSuit, out Item targetContainer))
{
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
{
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
var decontainObjective = new AIObjectiveDecontainItem(Character, divingSuit, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>())
{
DropIfFailsToContain = false
};
decontainObjective.Abandoned += () =>
{
IgnoredItems.Add(targetContainer);
};
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
}
else
{
divingSuit.Drop(Character);
}
}
}
}
}
}
if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
{
if (takeMaskOff)
{
if (Character.HasEquippedItem("divingmask"))
{
var mask = Character.Inventory.FindItemByTag("divingmask");
if (mask != null)
{
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
mask.Drop(Character);
}
else
{
findItemState = FindItemState.DivingMask;
if (FindSuitableContainer(mask, out Item targetContainer))
{
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
{
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
}
else
{
mask.Drop(Character);
}
}
}
}
}
}
@@ -404,41 +472,41 @@ namespace Barotrauma
}
}
}
}
if (findItemState == FindItemState.None || findItemState == FindItemState.OtherItem)
{
if (!ObjectiveManager.CurrentObjective.UnequipItems || !ObjectiveManager.GetActiveObjective().UnequipItems) { return; }
if (ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>()) { return; }
foreach (var item in Character.Inventory.Items)
if (findItemState == FindItemState.None || findItemState == FindItemState.OtherItem)
{
if (item == null) { continue; }
if (Character.HasEquippedItem(item) &&
(Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand) ||
Character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand) ||
Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand | InvSlotType.LeftHand)))
if (!ObjectiveManager.CurrentObjective.UnequipItems || !ObjectiveManager.GetActiveObjective().UnequipItems) { return; }
if (ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>()) { return; }
foreach (var item in Character.Inventory.Items)
{
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
if (item == null) { continue; }
if (Character.HasEquippedItem(item) &&
(Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand) ||
Character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand) ||
Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand | InvSlotType.LeftHand)))
{
if (FindSuitableContainer(item, out Item targetContainer))
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
if (FindSuitableContainer(item, out Item targetContainer))
{
var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
{
var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
}
else
{
item.Drop(Character);
}
}
else
{
item.Drop(Character);
findItemState = FindItemState.OtherItem;
}
}
else
{
findItemState = FindItemState.OtherItem;
}
}
}
}
@@ -559,7 +627,7 @@ namespace Barotrauma
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
if (item.Repairables.All(r => item.ConditionPercentage > r.RepairThreshold)) { continue; }
if (item.Repairables.All(r => item.ConditionPercentage > r.RepairIconThreshold)) { continue; }
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{
var orderPrefab = Order.GetPrefab("reportbrokendevices");
@@ -574,7 +642,13 @@ namespace Barotrauma
}
if (newOrder != null)
{
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
if (Character.TeamID == Character.TeamType.FriendlyNPC)
{
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Default,
identifier: newOrder.Prefab.Identifier + (targetHull?.DisplayName ?? "null"),
minDurationBetweenSimilar: 60.0f);
}
else if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
{
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
#if SERVER
@@ -588,24 +662,40 @@ namespace Barotrauma
{
if (Character.Oxygen < 20.0f)
{
Character.Speak(TextManager.Get("DialogLowOxygen"), null, 0, "lowoxygen", 30.0f);
Character.Speak(TextManager.Get("DialogLowOxygen"), null, Rand.Range(0.5f, 5.0f), "lowoxygen", 30.0f);
}
if (Character.Bleeding > 2.0f)
{
Character.Speak(TextManager.Get("DialogBleeding"), null, 0, "bleeding", 30.0f);
Character.Speak(TextManager.Get("DialogBleeding"), null, Rand.Range(0.5f, 5.0f), "bleeding", 30.0f);
}
if (Character.PressureTimer > 50.0f && Character.CurrentHull != null)
{
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, true), null, 0, "pressure", 30.0f);
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, true), null, Rand.Range(0.5f, 5.0f), "pressure", 30.0f);
}
}
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
float damage = attackResult.Damage;
if (damage <= 0) { return; }
// excluding poisons etc
float realDamage = attackResult.Damage;
// including poisons etc
float totalDamage = realDamage;
foreach (Affliction affliction in attackResult.Afflictions)
{
totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength;
}
if (totalDamage <= 0) { return; }
if (attacker != null)
{
if (!damageDoneByAttacker.ContainsKey(attacker))
{
damageDoneByAttacker[attacker] = 0.0f;
}
damageDoneByAttacker[attacker] += totalDamage;
attackers.Add(attacker);
}
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
@@ -617,6 +707,11 @@ namespace Barotrauma
//if (Character.LastDamageSource == null) { return; }
//AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
}
else if (realDamage <= 0 && (attacker.IsBot || attacker.TeamID == Character.TeamID))
{
// Don't react on damage that is entirely based on karma penalties (medics, poisons etc), unless applier is player
return;
}
else if (IsFriendly(attacker))
{
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
@@ -627,62 +722,133 @@ namespace Barotrauma
}
if (attacker.IsBot)
{
// Don't retaliate on damage done by friendly ai, because we know that it's accidental
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
// Don't retaliate on damage done by human ai, because we know it's accidental
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker, GetReactionTime() * 2);
}
else
{
// If not on the same team, always stay defensive
if (attacker.TeamID != Character.TeamID)
if (Character.IsSecurity)
{
AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
// TODO
}
else
{
float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality);
if (dmgPercentage < 10)
Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f);
}
if (Character.TeamID == Character.TeamType.FriendlyNPC && !Character.TurnedHostileByEvent)
{
// Inform other characters in the same team
foreach (Character otherCharacter in Character.CharacterList)
{
// Don't retaliate on minor (accidental) dmg done by characters that are in the same team
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
if (otherCharacter == Character || otherCharacter.TeamID != Character.TeamID || otherCharacter.IsDead ||
otherCharacter.Info?.Job == null ||
!(otherCharacter.AIController is HumanAIController otherHumanAI) ||
otherCharacter.TurnedHostileByEvent)
{
continue;
}
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
if (otherCharacter.IsSecurity)
{
// Alert all the security officers magically
float delay = isWitnessing ? GetReactionTime() * 2 : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
otherHumanAI.AddCombatObjective(DetermineCombatMode(otherCharacter), attacker, delay);
}
else if (isWitnessing)
{
// Other witnesses retreat to safety
otherHumanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker, GetReactionTime());
}
}
(GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult);
}
if (attacker.TeamID != Character.TeamID)
{
AddCombatObjective(DetermineCombatMode(Character), attacker, GetReactionTime());
}
else
{
// Don't react on minor (accidental) dmg done by characters that are in the same team
if (GetDamageDoneByAttacker(attacker) < 10)
{
if (!Character.IsSecurity)
{
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker, GetReactionTime() * 2);
}
}
else
{
AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
AddCombatObjective(DetermineCombatMode(Character, dmgThreshold: 20, allowOffensive: false), attacker, GetReactionTime() * 2);
}
}
}
}
else
{
AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive);
AddCombatObjective(DetermineCombatMode(Character), attacker);
}
void AddCombatObjective(AIObjectiveCombat.CombatMode mode, float delay = 0)
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float dmgThreshold = 10, bool allowOffensive = true)
{
bool holdPosition = Character.Info?.Job?.Prefab.Identifier == "watchman";
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
if (!IsFriendly(attacker))
{
if (combatObjective.Enemy != attacker || (combatObjective.Enemy == null && attacker == null))
{
// Replace the old objective with the new.
ObjectiveManager.Objectives.Remove(combatObjective);
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager) { HoldPosition = holdPosition});
}
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
}
else
{
if (delay > 0)
if (GetDamageDoneByAttacker(attacker) > dmgThreshold)
{
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager) { HoldPosition = holdPosition }, delay);
return c.IsSecurity && allowOffensive ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
}
else
{
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager) { HoldPosition = holdPosition });
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
}
}
}
}
private void AddCombatObjective(AIObjectiveCombat.CombatMode mode, Character attacker, float delay = 0, Func<bool> abortCondition = null, Action onAbort = null, bool allowHoldFire = false)
{
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
{
// Don't replace offensive mode with something else
if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; }
if (combatObjective.Mode != mode || combatObjective.Enemy != attacker || (combatObjective.Enemy == null && attacker == null))
{
// Replace the old objective with the new.
ObjectiveManager.Objectives.Remove(combatObjective);
ObjectiveManager.AddObjective(CreateCombatObjective());
}
}
else
{
if (delay > 0)
{
ObjectiveManager.AddObjective(CreateCombatObjective(), delay);
}
else
{
ObjectiveManager.AddObjective(CreateCombatObjective());
}
}
AIObjectiveCombat CreateCombatObjective()
{
var objective = new AIObjectiveCombat(Character, attacker, mode, objectiveManager)
{
HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman",
abortCondition = abortCondition,
allowHoldFire = allowHoldFire,
};
if (onAbort != null)
{
objective.Abandoned += onAbort;
}
return objective;
}
}
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
{
CurrentOrderOption = option;
@@ -733,7 +899,7 @@ namespace Barotrauma
private void CheckCrouching(float deltaTime)
{
crouchRaycastTimer -= deltaTime;
if (crouchRaycastTimer > 0.0f) return;
if (crouchRaycastTimer > 0.0f) { return; }
crouchRaycastTimer = crouchRaycastInterval;
@@ -743,7 +909,59 @@ namespace Barotrauma
//do a raycast upwards to find any walls
float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f;
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null;
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return !(fixture.Body.UserData is Submarine); }) != null;
}
public bool AllowCampaignInteraction()
{
if (Character == null || Character.Removed || Character.IsIncapacitated) { return false; }
switch (ObjectiveManager.CurrentObjective)
{
case AIObjectiveCombat _:
case AIObjectiveFindSafety _:
case AIObjectiveExtinguishFires _:
case AIObjectiveFightIntruders _:
case AIObjectiveFixLeaks _:
return false;
}
return true;
}
public bool TryToMoveItem(Item item, Inventory targetInventory, bool dropIfCannotMove = true)
{
var pickable = item.GetComponent<Pickable>();
if (pickable == null) { return false; }
int targetSlot = -1;
//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 < targetInventory.Items.Length; i++)
{
if (targetInventory is CharacterInventory characterInventory)
{
//slot not needed by the item, continue
if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; }
}
targetSlot = i;
//slot free, continue
var otherItem = targetInventory.Items[i];
if (otherItem == null) { continue; }
//try to move the existing item to LimbSlot.Any and continue if successful
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
continue;
}
if (dropIfCannotMove)
{
//if everything else fails, simply drop the existing item
otherItem.Drop(Character);
}
}
}
return targetInventory.TryPutItem(item, targetSlot, false, false, Character);
}
public static bool NeedsDivingGear(Character character, Hull hull, out bool needsSuit)
@@ -769,28 +987,108 @@ namespace Barotrauma
/// <summary>
/// Check whether the character has a diving suit in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, "divingsuit", "oxygensource", conditionPercentage);
public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, "divingsuit", out _, "oxygensource", conditionPercentage, requireEquipped: true);
/// <summary>
/// Check whether the character has a diving mask in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage);
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", out _, "oxygensource", conditionPercentage, requireEquipped: true);
public static bool HasItem(Character character, string tagOrIdentifier, string containedTag = null, float conditionPercentage = 0)
public static bool HasItem(Character character, string tagOrIdentifier, out Item item, string containedTag = null, float conditionPercentage = 0, bool requireEquipped = false)
{
item = null;
if (character == null) { return false; }
if (character.Inventory == null) { return false; }
var item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier);
item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier);
return item != null &&
item.ConditionPercentage > conditionPercentage &&
character.HasEquippedItem(item) &&
item.ConditionPercentage >= conditionPercentage &&
(!requireEquipped || character.HasEquippedItem(item)) &&
(containedTag == null ||
(item.ContainedItems != null &&
item.ContainedItems.Any(i => i.HasTag(containedTag) && i.ConditionPercentage > conditionPercentage)));
}
public static void ItemTaken(Item item, Character character)
{
if (item == null || character == null || item.GetComponent<LevelResource>() != null) { return; }
Character thief = character;
bool someoneSpoke = false;
if (item.SpawnedInOutpost && thief.TeamID != Character.TeamType.FriendlyNPC && !item.HasTag("handlocker"))
{
foreach (Character otherCharacter in Character.CharacterList)
{
if (otherCharacter == thief || otherCharacter.TeamID == thief.TeamID || otherCharacter.IsDead ||
otherCharacter.Info?.Job == null ||
!(otherCharacter.AIController is HumanAIController otherHumanAI) ||
!otherHumanAI.VisibleHulls.Contains(thief.CurrentHull))
{
continue;
}
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
if (!otherCharacter.CanSeeCharacter(thief)) { continue; }
if (!someoneSpoke)
{
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
{
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss;
}
item.StolenDuringRound = true;
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
someoneSpoke = true;
}
// React if we are security
if (!TriggerSecurity(otherHumanAI))
{
// Else call the others
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition)))
{
if (TriggerSecurity(security.AIController as HumanAIController))
{
// Only alert one guard at a time
break;
}
}
}
}
}
else if (item.OwnInventory?.FindItem(it => it.SpawnedInOutpost, true) is { } foundItem)
{
ItemTaken(foundItem, character);
}
bool TriggerSecurity(HumanAIController humanAI)
{
if (humanAI == null) { return false; }
if (!humanAI.Character.IsSecurity) { return false; }
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
humanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, thief, delay: GetReactionTime(),
abortCondition: () => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null,
onAbort: () =>
{
if (item != null && !item.Removed && humanAI != null && !humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveGetItem>())
{
humanAI.ObjectiveManager.AddObjective(new AIObjectiveGetItem(humanAI.Character, item, humanAI.ObjectiveManager, equip: false)
{
BasePriority = 10
});
}
},
allowHoldFire: true);
return true;
}
}
// 0.225 - 0.375
private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f);
/// <summary>
/// Updates the hull safety for all ai characters in the team.
/// Updates the hull safety for all ai characters in the team. The idea is that the crew communicates (magically) via radio about the threads.
/// The safety levels need to be calculated for each bot individually, because the formula takes into account things like current orders.
/// There's now a cached value per each hull, which should prevent too frequent calculations.
/// </summary>
public static void PropagateHullSafety(Character character, Hull hull)
{
@@ -887,7 +1185,30 @@ namespace Barotrauma
humanAI.ObjectiveManager.GetObjective<T1>()?.ReportedTargets.Remove(target));
}
public float GetHullSafety(Hull hull, Character character, IEnumerable<Hull> visibleHulls = null)
public float GetDamageDoneByAttacker(Character attacker)
{
if (!damageDoneByAttacker.TryGetValue(attacker, out float dmg))
{
dmg = 0;
}
return dmg;
}
private void StoreHullSafety(Hull hull, HullSafety safety)
{
if (knownHulls.ContainsKey(hull))
{
// Update existing. Shouldn't currently happen, but things might change.
knownHulls[hull] = safety;
}
else
{
// Add new
knownHulls.Add(hull, safety);
}
}
private float CalculateHullSafety(Hull hull, Character character, IEnumerable<Hull> visibleHulls = null)
{
bool isCurrentHull = character == Character && character.CurrentHull == hull;
if (hull == null)
@@ -903,12 +1224,11 @@ namespace Barotrauma
// Use the cached visible hulls
visibleHulls = VisibleHulls;
}
// TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy.
bool ignoreFire = objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
float safety = GetHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull)
{
CurrentHullSafety = safety;
@@ -916,7 +1236,7 @@ namespace Barotrauma
return safety;
}
public static float GetHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
private static float CalculateHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
{
if (hull == null) { return 0; }
if (hull.LethalPressure > 0 && character.PressureProtection <= 0) { return 0; }
@@ -949,13 +1269,65 @@ namespace Barotrauma
return MathHelper.Clamp(safety * 100, 0, 100);
}
public float GetHullSafety(Hull hull, Character character, IEnumerable<Hull> visibleHulls = null)
{
if (!knownHulls.TryGetValue(hull, out HullSafety hullSafety))
{
hullSafety = new HullSafety(CalculateHullSafety(hull, character, visibleHulls));
StoreHullSafety(hull, hullSafety);
}
else if (hullSafety.IsStale)
{
hullSafety.Reset(CalculateHullSafety(hull, character, visibleHulls));
}
return hullSafety.safety;
}
public static float GetHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
{
HullSafety hullSafety;
if (character.AIController is HumanAIController controller)
{
if (!controller.knownHulls.TryGetValue(hull, out hullSafety))
{
hullSafety = new HullSafety(CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies));
controller.StoreHullSafety(hull, hullSafety);
}
else if (hullSafety.IsStale)
{
hullSafety.Reset(CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies));
}
}
else
{
#if DEBUG
DebugConsole.ThrowError("Cannot store the hull safety, because was unable to cast the AIController as HumanAIController. This should never happen!");
#endif
return CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
}
return hullSafety.safety;
}
public void FaceTarget(ISpatialEntity target) => Character.AnimController.TargetDir = target.WorldPosition.X > Character.WorldPosition.X ? Direction.Right : Direction.Left;
public static bool IsFriendly(Character me, Character other)
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
{
bool sameSpecies = other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
bool differentTeam = me.TeamID == Character.TeamType.Team1 && other.TeamID == Character.TeamType.Team2 || me.TeamID == Character.TeamType.Team2 && other.TeamID == Character.TeamType.Team1;
return sameSpecies && !differentTeam;
bool sameTeam = me.TeamID == other.TeamID;
// Only enemies are in the Team "None"
bool friendlyTeam = me.TeamID != Character.TeamType.None && other.TeamID != Character.TeamType.None;
bool teamGood = sameTeam || friendlyTeam && !onlySameTeam;
if (!teamGood) { return false; }
bool speciesGood = other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
if (!speciesGood) { return false; }
if (me.TeamID == Character.TeamType.FriendlyNPC && other.TeamID == Character.TeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
{
var reputation = campaign.Map?.CurrentLocation?.Reputation;
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
{
return false;
}
}
return true;
}
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;