(965c31410a) Unstable v0.10.4.0
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user