Unstable 0.1300.0.3
This commit is contained in:
@@ -172,6 +172,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The monster won't try to damage these submarines
|
||||
/// </summary>
|
||||
public HashSet<Submarine> UnattackableSubmarines
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new HashSet<Submarine>();
|
||||
|
||||
public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack);
|
||||
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter);
|
||||
|
||||
@@ -2150,7 +2159,13 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Ignore all structures, items, and hulls inside wrecks and beacons
|
||||
if (aiTarget.Entity.Submarine != null && (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon)) { continue; }
|
||||
if (aiTarget.Entity.Submarine != null)
|
||||
{
|
||||
if (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon || UnattackableSubmarines.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (aiTarget.Entity is Hull hull)
|
||||
{
|
||||
// Ignore the target if it's a room and the character is already inside a sub
|
||||
@@ -2439,13 +2454,23 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
|
||||
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid)
|
||||
|
||||
// Don't target characters that are outside of the allowed zone, unless chasing or escaping.
|
||||
switch (targetParams.State)
|
||||
{
|
||||
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
case AIState.Escape:
|
||||
case AIState.Avoid:
|
||||
break;
|
||||
default:
|
||||
if (targetParams.State == AIState.Attack)
|
||||
{
|
||||
if (State == targetParams.State && SelectedAiTarget == aiTarget) { break; }
|
||||
}
|
||||
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist);
|
||||
|
||||
@@ -1012,11 +1012,6 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
bool allowOffensive = HasItem(attacker, "handlocker", out _, requireEquipped: true);
|
||||
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
|
||||
{
|
||||
cumulativeDamage = 100;
|
||||
}
|
||||
// Don't react to minor (accidental) dmg done by characters that are in the same team
|
||||
if (cumulativeDamage < 10)
|
||||
{
|
||||
@@ -1027,7 +1022,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 20, allowOffensive: allowOffensive), attacker, GetReactionTime() * 2);
|
||||
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 50), attacker, GetReactionTime() * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1056,7 +1051,7 @@ namespace Barotrauma
|
||||
if (!otherHumanAI.IsFriendly(Character)) { continue; }
|
||||
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
|
||||
if (!isWitnessing && !CheckReportRange(Character, otherCharacter, ReportRange)) { continue; }
|
||||
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing);
|
||||
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing, dmgThreshold: attacker.TeamID == Character.TeamID ? 50 : 10);
|
||||
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
|
||||
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
|
||||
}
|
||||
@@ -1099,6 +1094,15 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c.AIController is HumanAIController humanAI && humanAI.ObjectiveManager.GetActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
|
||||
{
|
||||
// Already targeting the attacker -> treat as a more serious threat.
|
||||
cumulativeDamage *= 2;
|
||||
}
|
||||
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
|
||||
{
|
||||
cumulativeDamage = 100;
|
||||
}
|
||||
if (cumulativeDamage > dmgThreshold)
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
@@ -1154,7 +1158,7 @@ namespace Barotrauma
|
||||
HoldPosition =
|
||||
Character.Info?.Job?.Prefab.Identifier == "watchman" ||
|
||||
Character.CurrentHull == null ||
|
||||
Character.IsOnPlayerTeam && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
|
||||
Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
|
||||
abortCondition = abortCondition,
|
||||
allowHoldFire = allowHoldFire,
|
||||
};
|
||||
@@ -1841,15 +1845,14 @@ namespace Barotrauma
|
||||
if (character == null) { continue; }
|
||||
if (c == character) { continue; }
|
||||
if (c.IsDead || c.IsIncapacitated) { continue; }
|
||||
if (c.SelectedConstruction != target.Item) { continue; }
|
||||
if (!IsFriendly(character, c, onlySameTeam: true)) { continue; }
|
||||
operatingCharacter = c;
|
||||
// If the other character is player, don't try to operate
|
||||
if (c.IsPlayer) { return true; }
|
||||
if (c.AIController is HumanAIController controllingHumanAi)
|
||||
{
|
||||
Item otherTarget = controllingHumanAi.objectiveManager.GetActiveObjective<AIObjectiveOperateItem>()?.Component.Item ?? c.SelectedConstruction;
|
||||
if (otherTarget != target.Item) { continue; }
|
||||
// If the other character is player, don't try to operate
|
||||
if (c.IsPlayer) { return true; }
|
||||
// If the other character is ordered to operate the item, let him do it
|
||||
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
|
||||
{
|
||||
@@ -1874,8 +1877,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't go here, unless we allow non-humans to operate items
|
||||
return false;
|
||||
return c.SelectedConstruction == target.Item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f &&
|
||||
speaker?.CurrentHull != null &&
|
||||
speaker.TeamID == CharacterTeamType.FriendlyNPC &&
|
||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||
{
|
||||
currentFlags.Add("EnterOutpost");
|
||||
@@ -188,6 +188,11 @@ namespace Barotrauma
|
||||
{
|
||||
currentFlags.Add("Casual");
|
||||
}
|
||||
|
||||
if (GameMain.GameSession.IsCurrentLocationRadiated())
|
||||
{
|
||||
currentFlags.Add("InRadiation");
|
||||
}
|
||||
}
|
||||
|
||||
if (speaker != null)
|
||||
|
||||
+13
@@ -50,6 +50,11 @@ namespace Barotrauma
|
||||
float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
|
||||
if (decontainObjective == null)
|
||||
{
|
||||
// Halve the priority until there's a decontain objective (a valid container was found).
|
||||
Priority /= 2;
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -126,5 +131,13 @@ namespace Barotrauma
|
||||
itemIndex = 0;
|
||||
decontainObjective = null;
|
||||
}
|
||||
|
||||
public void DropTarget()
|
||||
{
|
||||
if (item != null && character.HasItem(item))
|
||||
{
|
||||
item.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-5
@@ -2,6 +2,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -29,7 +30,21 @@ namespace Barotrauma
|
||||
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation() => Targets.Any() ? (objectiveManager.IsOrder(this) ? objectiveManager.GetOrderPriority(this) : AIObjectiveManager.RunPriority - 1) : 0;
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
float prio = objectiveManager.GetOrderPriority(this);
|
||||
if (subObjectives.All(so => so.SubObjectives.None()))
|
||||
{
|
||||
// If none of the subobjectives have subobjectives, no valid container was found. In this case, let's reduce the priority below the run threshold.
|
||||
prio = Math.Min(prio, AIObjectiveManager.RunPriority - 1);
|
||||
}
|
||||
return prio;
|
||||
}
|
||||
return AIObjectiveManager.RunPriority - 0.5f;
|
||||
}
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
{
|
||||
@@ -65,10 +80,10 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsValidContainer(Item item, Character character) =>
|
||||
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
|
||||
public static bool IsValidContainer(Item item, Character character, bool allowUnloading = true) =>
|
||||
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && allowUnloading && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory)
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
@@ -76,7 +91,7 @@ namespace Barotrauma
|
||||
if (item.SpawnedInOutpost) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container == null || !IsValidContainer(item.Container, character)) { return false; }
|
||||
if (item.Container == null || !IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
}
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
@@ -127,5 +142,17 @@ namespace Barotrauma
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
foreach (var subObjective in SubObjectives)
|
||||
{
|
||||
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
|
||||
{
|
||||
cleanUpObjective.DropTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-10
@@ -457,7 +457,7 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (Enemy.Stun > 1)
|
||||
if (Enemy.IsKnockedDown)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
@@ -730,11 +730,12 @@ namespace Barotrauma
|
||||
{
|
||||
IgnoreIfTargetDead = true,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = Enemy.DisplayName
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false
|
||||
},
|
||||
onAbandon: () => Abandon = true);
|
||||
if (followTargetObjective == null) { return; }
|
||||
if (Mode == CombatMode.Arrest && Enemy.Stun > 2)
|
||||
if (Mode == CombatMode.Arrest && (Enemy.Stun > 1 || Enemy.IsKnockedDown))
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "handlocker", out _))
|
||||
{
|
||||
@@ -742,8 +743,8 @@ namespace Barotrauma
|
||||
{
|
||||
arrestingRegistered = true;
|
||||
followTargetObjective.Completed += OnArrestTargetReached;
|
||||
followTargetObjective.CloseEnough = 100;
|
||||
}
|
||||
followTargetObjective.CloseEnough = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -759,7 +760,7 @@ namespace Barotrauma
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
if (followTargetObjective != null)
|
||||
if (!arrestingRegistered && followTargetObjective != null)
|
||||
{
|
||||
followTargetObjective.CloseEnough =
|
||||
WeaponComponent is RangedWeapon ? 1000 :
|
||||
@@ -782,7 +783,7 @@ namespace Barotrauma
|
||||
|
||||
private void OnArrestTargetReached()
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
|
||||
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
|
||||
{
|
||||
var handCuffs = matchingItems.First();
|
||||
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
|
||||
@@ -802,8 +803,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
|
||||
IsCompleted = true;
|
||||
}
|
||||
IsCompleted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -937,7 +938,14 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
|
||||
if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
// If the target is arrested or if it's stunned and we can't lock the target up, consider the objective done.
|
||||
if (Enemy.IsKnockedDown && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false) || HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
}
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
@@ -1017,6 +1025,8 @@ namespace Barotrauma
|
||||
|
||||
private void UseWeapon(float deltaTime)
|
||||
{
|
||||
// Never allow to attack characters with deadly weapons while trying to arrest.
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon) { return; }
|
||||
float reloadTime = 0;
|
||||
if (WeaponComponent is RangedWeapon rangedWeapon)
|
||||
{
|
||||
@@ -1038,10 +1048,16 @@ namespace Barotrauma
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
private bool ShouldUnequipWeapon =>
|
||||
Weapon != null &&
|
||||
character.Submarine != null &&
|
||||
character.Submarine.TeamID == character.TeamID &&
|
||||
Character.CharacterList.None(c => c.Submarine == character.Submarine && HumanAIController.IsActive(c) && !HumanAIController.IsFriendly(character, c) && HumanAIController.VisibleHulls.Contains(c.CurrentHull));
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
if (Weapon != null)
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
@@ -1051,7 +1067,7 @@ namespace Barotrauma
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (Weapon != null)
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ namespace Barotrauma
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
|
||||
+44
-18
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "get item";
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
|
||||
private readonly bool equip;
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -225,18 +227,8 @@ namespace Barotrauma
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
|
||||
{
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
abortCondition = obj =>
|
||||
{
|
||||
bool abort = targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget;
|
||||
if (abort)
|
||||
{
|
||||
// Fail silently if someone takes the suit.
|
||||
obj.speakIfFails = false;
|
||||
}
|
||||
return abort;
|
||||
},
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
|
||||
abortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
SpeakIfFails = false
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
@@ -263,13 +255,18 @@ namespace Barotrauma
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item was defined.", Color.Red);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
|
||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.followControlledCharacter);
|
||||
bool hasCalledPathFinder = false;
|
||||
int itemsPerFrame = (int)priority;
|
||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
{
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
@@ -310,8 +307,18 @@ namespace Barotrauma
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
|
||||
itemPriority *= distanceFactor;
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
//ignore if the item has a lower priority than the currently selected one
|
||||
// Ignore if the item has a lower priority than the currently selected one
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
||||
{
|
||||
// While following the player, let's ensure that there's a valid path to the target before accepting it.
|
||||
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
|
||||
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
|
||||
// Only allow one path find call per frame.
|
||||
hasCalledPathFinder = true;
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable) { continue; }
|
||||
}
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootInventoryOwner ?? item;
|
||||
@@ -326,7 +333,7 @@ namespace Barotrauma
|
||||
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
@@ -345,7 +352,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
@@ -393,11 +400,30 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private void ResetInternal()
|
||||
{
|
||||
goToObjective = null;
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
targetItem = originalTarget;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
currItemPriority = 0;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (moveToTarget == null) { return; }
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
#endif
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null)
|
||||
{
|
||||
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
|
||||
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-22
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
|
||||
public bool followControlledCharacter;
|
||||
public bool mimic;
|
||||
public bool speakIfFails = true;
|
||||
public bool SpeakIfFails { get; set; } = true;
|
||||
|
||||
public float extraDistanceWhileSwimming;
|
||||
public float extraDistanceOutsideSub;
|
||||
@@ -67,6 +67,8 @@ namespace Barotrauma
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
|
||||
public bool AlwaysUseEuclideanDistance { get; set; } = true;
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
@@ -88,12 +90,7 @@ namespace Barotrauma
|
||||
Abandon = !isOrder;
|
||||
return Priority;
|
||||
}
|
||||
if (followControlledCharacter && Character.Controlled == null)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
}
|
||||
if (Target is Entity e && e.Removed)
|
||||
if (Target == null || Target is Entity e && e.Removed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
@@ -147,11 +144,10 @@ namespace Barotrauma
|
||||
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
if (!character.IsOnPlayerTeam) { return; }
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
#endif
|
||||
if (objectiveManager.HasOrders() && DialogueIdentifier != null && speakIfFails)
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null && DialogueIdentifier != null && SpeakIfFails)
|
||||
{
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
|
||||
if (msg != null)
|
||||
@@ -165,12 +161,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (followControlledCharacter)
|
||||
{
|
||||
if (Character.Controlled == null || !HumanAIController.IsFriendly(Character.Controlled))
|
||||
if (Character.Controlled != null && HumanAIController.IsFriendly(Character.Controlled))
|
||||
{
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
if (Target == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
{
|
||||
@@ -260,6 +259,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
@@ -288,9 +288,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
float maxGapDistance = 500;
|
||||
Character targetCharacter = Target as Character;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (character.CurrentHull == null)
|
||||
if (character.CurrentHull == null ||
|
||||
followControlledCharacter &&
|
||||
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
|
||||
{
|
||||
if (seekGapsTimer > 0)
|
||||
{
|
||||
@@ -298,7 +303,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
SeekGaps(maxDistance: 500);
|
||||
SeekGaps(maxGapDistance);
|
||||
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
||||
if (TargetGap != null)
|
||||
{
|
||||
@@ -327,7 +332,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (TargetGap != null)
|
||||
{
|
||||
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, TargetGap.FlowTargetHull.WorldPosition, deltaTime))
|
||||
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, followControlledCharacter ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
|
||||
return;
|
||||
@@ -347,7 +352,7 @@ namespace Barotrauma
|
||||
float closeEnough = 250;
|
||||
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
|
||||
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
|
||||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
|
||||
(targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
|
||||
{
|
||||
// Currently equipped scooter
|
||||
@@ -528,17 +533,24 @@ namespace Barotrauma
|
||||
{
|
||||
Gap selectedGap = null;
|
||||
float selectedDistance = -1;
|
||||
Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition);
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.Open < 1) { continue; }
|
||||
if (gap.FlowTargetHull == null) { continue; }
|
||||
if (gap.Submarine != Target.Submarine) { continue; }
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition);
|
||||
if (distance > maxDistance * maxDistance) { continue; }
|
||||
if (selectedGap == null || distance < selectedDistance)
|
||||
if (gap.Submarine == null) { continue; }
|
||||
if (!followControlledCharacter)
|
||||
{
|
||||
if (gap.FlowTargetHull == null) { continue; }
|
||||
if (gap.Submarine != Target.Submarine) { continue; }
|
||||
}
|
||||
Vector2 toGap = gap.WorldPosition - character.WorldPosition;
|
||||
if (Vector2.Dot(Vector2.Normalize(toGap), toTargetNormalized) < 0) { continue; }
|
||||
float squaredDistance = toGap.LengthSquared();
|
||||
if (squaredDistance > maxDistance * maxDistance) { continue; }
|
||||
if (selectedGap == null || squaredDistance < selectedDistance)
|
||||
{
|
||||
selectedGap = gap;
|
||||
selectedDistance = distance;
|
||||
selectedDistance = squaredDistance;
|
||||
}
|
||||
}
|
||||
TargetGap = selectedGap;
|
||||
@@ -555,7 +567,7 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
if (!character.AnimController.InWater)
|
||||
if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
|
||||
{
|
||||
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (yDiff > CloseEnough) { return false; }
|
||||
|
||||
+13
-1
@@ -495,7 +495,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true) && !ignoredItems.Contains(item))
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
|
||||
{
|
||||
itemsToClean.Add(item);
|
||||
}
|
||||
@@ -540,5 +540,17 @@ namespace Barotrauma
|
||||
ignoredItems.Clear();
|
||||
autonomousObjectiveRetryTimer = 10;
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
foreach (var subObjective in SubObjectives)
|
||||
{
|
||||
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
|
||||
{
|
||||
cleanUpObjective.DropTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -445,7 +445,7 @@ namespace Barotrauma
|
||||
extraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
followControlledCharacter = orderGiver == character,
|
||||
followControlledCharacter = true,
|
||||
mimic = true,
|
||||
DialogueIdentifier = "dialogcannotreachplace"
|
||||
};
|
||||
|
||||
+4
-6
@@ -69,10 +69,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!isOrder)
|
||||
{
|
||||
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC ||
|
||||
HumanAIController.IsTrueForAnyCrewMember(c =>
|
||||
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
|
||||
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
// The reactor was previously operated by a player -> ignore.
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
@@ -89,7 +88,6 @@ namespace Barotrauma
|
||||
case "powerup":
|
||||
// Check that we don't already have another order that is targeting the same item.
|
||||
// Without this the autonomous objective will tell the bot to turn the reactor on again.
|
||||
|
||||
if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective)))
|
||||
{
|
||||
Priority = 0;
|
||||
@@ -177,9 +175,9 @@ namespace Barotrauma
|
||||
}
|
||||
if (operateTarget != null)
|
||||
{
|
||||
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||
{
|
||||
// Another crew member is already targeting this entity.
|
||||
// Another crew member is already targeting this entity (leak).
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -471,13 +471,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private double pressureProtectionLastSet;
|
||||
private float pressureProtection;
|
||||
public float PressureProtection
|
||||
{
|
||||
get { return pressureProtection; }
|
||||
set
|
||||
{
|
||||
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
pressureProtection = Math.Max(value, 0.0f);
|
||||
pressureProtectionLastSet = Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,11 +530,10 @@ namespace Barotrauma
|
||||
|
||||
public float Stun
|
||||
{
|
||||
get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; }
|
||||
get { return IsRagdolled ? 1.0f : CharacterHealth.Stun; }
|
||||
set
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
SetStun(value, true);
|
||||
}
|
||||
}
|
||||
@@ -697,7 +698,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!canBeDragged) { return false; }
|
||||
if (Removed || !AnimController.Draggable) { return false; }
|
||||
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated || IsPet;
|
||||
return IsKnockedDown || LockHands || IsPet;
|
||||
}
|
||||
set { canBeDragged = value; }
|
||||
}
|
||||
@@ -715,7 +716,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated;
|
||||
return IsKnockedDown || LockHands;
|
||||
}
|
||||
}
|
||||
set { canInventoryBeAccessed = value; }
|
||||
@@ -1016,7 +1017,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Get the non husked name and find the ragdoll with it
|
||||
var matchingAffliction = AfflictionPrefab.List
|
||||
.Where(p => p.AfflictionType == "huskinfection")
|
||||
.Where(p => p is AfflictionPrefabHusk)
|
||||
.Select(p => p as AfflictionPrefabHusk)
|
||||
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase)));
|
||||
string nonHuskedSpeciesName = string.Empty;
|
||||
@@ -1052,7 +1053,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
AnimController = new FishAnimController(this, seed, ragdollParams as FishRagdollParams);
|
||||
PressureProtection = 100.0f;
|
||||
PressureProtection = int.MaxValue;
|
||||
}
|
||||
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
|
||||
@@ -1271,7 +1272,13 @@ namespace Barotrauma
|
||||
|
||||
public float GetSkillLevel(string skillIdentifier)
|
||||
{
|
||||
return (Info == null || Info.Job == null) ? 0.0f : Info.Job.GetSkillLevel(skillIdentifier);
|
||||
if (Info?.Job == null) { return 0.0f; }
|
||||
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
|
||||
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
|
||||
{
|
||||
skillLevel *= affliction.GetSkillMultiplier();
|
||||
}
|
||||
return skillLevel;
|
||||
}
|
||||
|
||||
// TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner
|
||||
@@ -2466,11 +2473,8 @@ namespace Barotrauma
|
||||
|
||||
if (NeedsAir)
|
||||
{
|
||||
bool protectedFromPressure = PressureProtection > 0.0f;
|
||||
//cannot be protected from pressure when below crush depth
|
||||
protectedFromPressure = protectedFromPressure && WorldPosition.Y > CharacterHealth.CrushDepth;
|
||||
//implode if not protected from pressure, and either outside or in a high-pressure hull
|
||||
if (!protectedFromPressure &&
|
||||
if (!IsProtectedFromPressure() &&
|
||||
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f))
|
||||
{
|
||||
if (CharacterHealth.PressureKillDelay <= 0.0f)
|
||||
@@ -2656,7 +2660,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (NeedsAir)
|
||||
{
|
||||
PressureProtection -= deltaTime * 100.0f;
|
||||
if (Timing.TotalTime > pressureProtectionLastSet + 0.1)
|
||||
{
|
||||
PressureProtection = 0.0f;
|
||||
}
|
||||
}
|
||||
if (NeedsWater)
|
||||
{
|
||||
@@ -3281,7 +3288,7 @@ namespace Barotrauma
|
||||
GameMain.Config.RecentlyEncounteredCreatures.Add(other.SpeciesName);
|
||||
}
|
||||
|
||||
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1)
|
||||
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true)
|
||||
{
|
||||
if (Removed) { return new AttackResult(); }
|
||||
|
||||
@@ -3327,7 +3334,7 @@ namespace Barotrauma
|
||||
bool wasDead = IsDead;
|
||||
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
|
||||
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier);
|
||||
CharacterHealth.ApplyDamage(hitLimb, attackResult);
|
||||
CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking);
|
||||
if (attacker != this)
|
||||
{
|
||||
OnAttacked?.Invoke(attacker, attackResult);
|
||||
@@ -3382,6 +3389,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the character knocked down regardless whether the technical state is dead, unconcious, paralyzed, or stunned.
|
||||
/// With stunning, the parameter uses a half a second delay before the character is treated as knocked down. The purpose of this is to ignore minor stunning. If you don't want to to ignore any stun, use the Stun property.
|
||||
/// </summary>
|
||||
public bool IsKnockedDown => IsDead || IsIncapacitated || CharacterHealth.StunTimer > 0.5f;
|
||||
|
||||
public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
|
||||
@@ -3391,7 +3404,7 @@ namespace Barotrauma
|
||||
{
|
||||
AnimController.ResetPullJoints();
|
||||
}
|
||||
CharacterHealth.StunTimer = newStun;
|
||||
CharacterHealth.Stun = newStun;
|
||||
if (newStun > 0.0f)
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
@@ -3964,5 +3977,10 @@ namespace Barotrauma
|
||||
public bool IsWatchman => HasJob("watchman");
|
||||
|
||||
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
|
||||
|
||||
public bool IsProtectedFromPressure()
|
||||
{
|
||||
return PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,6 +961,20 @@ namespace Barotrauma
|
||||
public void Rename(string newName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newName)) { return; }
|
||||
// Replace the name tag of any existing id cards or duffel bags
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
if (item.Prefab.Identifier != "idcard" && !item.Tags.Contains("despawncontainer")) { continue; }
|
||||
foreach (var tag in item.Tags.Split(','))
|
||||
{
|
||||
var splitTag = tag.Split(":");
|
||||
if (splitTag.Length < 2) { continue; }
|
||||
if (splitTag[0] != "name") { continue; }
|
||||
if (splitTag[1] != Name) { continue; }
|
||||
item.ReplaceTag(tag, $"name:{newName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Name = newName;
|
||||
}
|
||||
|
||||
|
||||
+60
-15
@@ -14,6 +14,9 @@ namespace Barotrauma
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
|
||||
|
||||
public float PendingAdditionStrenght { get; set; }
|
||||
public float AdditionStrength { get; set; }
|
||||
|
||||
protected float _strength;
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
@@ -26,7 +29,12 @@ namespace Barotrauma
|
||||
{
|
||||
_nonClampedStrength = value;
|
||||
}
|
||||
_strength = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength);
|
||||
float newValue = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength);
|
||||
if (newValue > _strength)
|
||||
{
|
||||
PendingAdditionStrenght = Prefab.GrainBurst;
|
||||
}
|
||||
_strength = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +64,7 @@ namespace Barotrauma
|
||||
public Affliction(AfflictionPrefab prefab, float strength)
|
||||
{
|
||||
Prefab = prefab;
|
||||
PendingAdditionStrenght = Prefab.GrainBurst;
|
||||
_strength = strength;
|
||||
Identifier = prefab?.Identifier;
|
||||
|
||||
@@ -109,18 +118,25 @@ namespace Barotrauma
|
||||
if (currentEffect == null) { return 0.0f; }
|
||||
if (MathUtils.NearlyEqual(currentEffect.MaxGrainStrength, 0f)) { return 0.0f; }
|
||||
|
||||
return MathHelper.Lerp(
|
||||
float amount = MathHelper.Lerp(
|
||||
currentEffect.MinGrainStrength,
|
||||
currentEffect.MaxGrainStrength,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
|
||||
if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
|
||||
{
|
||||
return AdditionStrength;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
public float GetScreenDistortStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength <= 0.0f) return 0.0f;
|
||||
if (currentEffect == null) { return 0.0f; }
|
||||
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; }
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinScreenDistortStrength,
|
||||
@@ -130,10 +146,10 @@ namespace Barotrauma
|
||||
|
||||
public float GetRadialDistortStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength <= 0.0f) return 0.0f;
|
||||
if (currentEffect == null) { return 0.0f; }
|
||||
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; }
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinRadialDistortStrength,
|
||||
@@ -143,10 +159,10 @@ namespace Barotrauma
|
||||
|
||||
public float GetChromaticAberrationStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength <= 0.0f) return 0.0f;
|
||||
if (currentEffect == null) { return 0.0f; }
|
||||
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; }
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinChromaticAberrationStrength,
|
||||
@@ -156,10 +172,10 @@ namespace Barotrauma
|
||||
|
||||
public float GetScreenBlurStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength <= 0.0f) return 0.0f;
|
||||
if (currentEffect == null) { return 0.0f; }
|
||||
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; }
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinScreenBlurStrength,
|
||||
@@ -167,6 +183,20 @@ namespace Barotrauma
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public float GetSkillMultiplier()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) { return 1.0f; }
|
||||
|
||||
float amount = MathHelper.Lerp(
|
||||
currentEffect.MinSkillMultiplier,
|
||||
currentEffect.MaxSkillMultiplier,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void CalculateDamagePerSecond(float currentVitalityDecrease)
|
||||
{
|
||||
DamagePerSecond = Math.Max(DamagePerSecond, currentVitalityDecrease - PreviousVitalityDecrease);
|
||||
@@ -245,6 +275,21 @@ namespace Barotrauma
|
||||
{
|
||||
ApplyStatusEffect(statusEffect, deltaTime, characterHealth, targetLimb);
|
||||
}
|
||||
|
||||
float amount = deltaTime;
|
||||
if (Prefab.GrainBurst > 0)
|
||||
{
|
||||
amount /= Prefab.GrainBurst;
|
||||
}
|
||||
if (PendingAdditionStrenght >= 0)
|
||||
{
|
||||
AdditionStrength += amount;
|
||||
PendingAdditionStrenght -= deltaTime;
|
||||
}
|
||||
else if (AdditionStrength > 0)
|
||||
{
|
||||
AdditionStrength -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyStatusEffect(StatusEffect statusEffect, float deltaTime, CharacterHealth characterHealth, Limb targetLimb)
|
||||
@@ -267,7 +312,7 @@ namespace Barotrauma
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets);
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, targetLimb.character, targets);
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, targets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -134,6 +134,8 @@ namespace Barotrauma
|
||||
public float MinSpeedMultiplier, MaxSpeedMultiplier;
|
||||
public float MinBuffMultiplier, MaxBuffMultiplier;
|
||||
|
||||
public float MinSkillMultiplier, MaxSkillMultiplier;
|
||||
|
||||
public float MinResistance, MaxResistance;
|
||||
public string ResistanceFor;
|
||||
public string DialogFlag;
|
||||
@@ -172,6 +174,9 @@ namespace Barotrauma
|
||||
MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f);
|
||||
MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength);
|
||||
|
||||
MinSkillMultiplier = element.GetAttributeFloat("minskillmultiplier", 1.0f);
|
||||
MaxSkillMultiplier = element.GetAttributeFloat("maxskillmultiplier", 1.0f);
|
||||
|
||||
ResistanceFor = element.GetAttributeString("resistancefor", "");
|
||||
MinResistance = element.GetAttributeFloat("minresistance", 0.0f);
|
||||
MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f);
|
||||
@@ -292,6 +297,8 @@ namespace Barotrauma
|
||||
public readonly float ShowIconToOthersThreshold = 0.05f;
|
||||
public readonly float MaxStrength = 100.0f;
|
||||
|
||||
public readonly float GrainBurst;
|
||||
|
||||
//how high the strength has to be for the affliction icon to be shown with a health scanner
|
||||
public readonly float ShowInHealthScannerThreshold = 0.05f;
|
||||
|
||||
@@ -451,6 +458,7 @@ namespace Barotrauma
|
||||
prefab = new AfflictionPrefab(sourceElement, file.Path, typeof(AfflictionBleeding));
|
||||
break;
|
||||
case "huskinfection":
|
||||
case "alieninfection":
|
||||
prefab = new AfflictionPrefabHusk(sourceElement, file.Path, typeof(AfflictionHusk));
|
||||
break;
|
||||
case "cprsettings":
|
||||
@@ -521,7 +529,7 @@ namespace Barotrauma
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
loadedAfflictions.Add((prefab, element));
|
||||
loadedAfflictions.Add((prefab, sourceElement));
|
||||
Prefabs.Add(prefab, isOverride);
|
||||
prefab.CalculatePrefabUIntIdentifier(Prefabs);
|
||||
}
|
||||
@@ -582,6 +590,7 @@ namespace Barotrauma
|
||||
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLower(), 0.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
|
||||
|
||||
@@ -190,12 +190,14 @@ namespace Barotrauma
|
||||
set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public float StunTimer
|
||||
public float Stun
|
||||
{
|
||||
get { return stunAffliction.Strength; }
|
||||
set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); }
|
||||
}
|
||||
|
||||
public float StunTimer { get; private set; }
|
||||
|
||||
public Affliction PressureAffliction
|
||||
{
|
||||
get { return pressureAffliction; }
|
||||
@@ -488,7 +490,7 @@ namespace Barotrauma
|
||||
CalculateVitality();
|
||||
}
|
||||
|
||||
public void ApplyDamage(Limb hitLimb, AttackResult attackResult)
|
||||
public void ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking = true)
|
||||
{
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
if (hitLimb.HealthIndex < 0 || hitLimb.HealthIndex >= limbHealths.Count)
|
||||
@@ -502,11 +504,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (newAffliction.Prefab.LimbSpecific)
|
||||
{
|
||||
AddLimbAffliction(hitLimb, newAffliction);
|
||||
AddLimbAffliction(hitLimb, newAffliction, allowStacking);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddAffliction(newAffliction);
|
||||
AddAffliction(newAffliction, allowStacking);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,7 +575,7 @@ namespace Barotrauma
|
||||
CalculateVitality();
|
||||
}
|
||||
|
||||
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
|
||||
private void AddLimbAffliction(Limb limb, Affliction newAffliction, bool allowStacking = true)
|
||||
{
|
||||
if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; }
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
@@ -582,10 +584,10 @@ namespace Barotrauma
|
||||
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
|
||||
return;
|
||||
}
|
||||
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction);
|
||||
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction, allowStacking);
|
||||
}
|
||||
|
||||
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction)
|
||||
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true)
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
@@ -594,7 +596,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (newAffliction.Prefab == affliction.Prefab)
|
||||
{
|
||||
affliction.Strength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
|
||||
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
|
||||
if (allowStacking)
|
||||
{
|
||||
// Add the existing strength
|
||||
newStrength += affliction.Strength;
|
||||
}
|
||||
newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength);
|
||||
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
|
||||
affliction.Strength = newStrength;
|
||||
affliction.Source = newAffliction.Source;
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality)
|
||||
@@ -624,13 +634,12 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private void AddAffliction(Affliction newAffliction)
|
||||
private void AddAffliction(Affliction newAffliction, bool allowStacking = true)
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
if (newAffliction.Prefab.AfflictionType == "huskinfection")
|
||||
if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
|
||||
{
|
||||
var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk;
|
||||
if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return;
|
||||
@@ -640,7 +649,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (newAffliction.Prefab == affliction.Prefab)
|
||||
{
|
||||
float newStrength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
|
||||
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
|
||||
if (allowStacking)
|
||||
{
|
||||
// Add the existing strength
|
||||
newStrength += affliction.Strength;
|
||||
}
|
||||
newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength);
|
||||
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
|
||||
affliction.Strength = newStrength;
|
||||
affliction.Source = newAffliction.Source;
|
||||
@@ -676,6 +691,8 @@ namespace Barotrauma
|
||||
{
|
||||
UpdateOxygen(deltaTime);
|
||||
|
||||
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
|
||||
|
||||
for (int i = 0; i < limbHealths.Count; i++)
|
||||
{
|
||||
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
|
||||
@@ -795,6 +812,13 @@ namespace Barotrauma
|
||||
Vitality -= vitalityDecrease;
|
||||
affliction.CalculateDamagePerSecond(vitalityDecrease);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (IsUnconscious)
|
||||
{
|
||||
HintManager.OnCharacterUnconscious(Character);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Kill()
|
||||
|
||||
@@ -79,6 +79,9 @@ namespace Barotrauma
|
||||
[Serialize(0f, true), Editable]
|
||||
public float SonarDisruption { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float DistantSonarRange { get; set; }
|
||||
|
||||
[Serialize(25000f, true, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)]
|
||||
public float DisableDistance { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user