Unstable 0.16.3.0

This commit is contained in:
Markus Isberg
2022-02-10 02:52:08 +09:00
parent 6814a11520
commit 2190fe08ef
115 changed files with 993 additions and 453 deletions
@@ -207,8 +207,10 @@ namespace Barotrauma
} = new HashSet<Submarine>();
public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget);
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);
public static bool IsTargetBeingChasedBy(Character target, Character character)
=> character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && (enemyAI.State == AIState.Attack || enemyAI.State == AIState.Aggressive);
public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c);
private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character);
private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
@@ -1322,7 +1324,7 @@ namespace Barotrauma
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (Submarine.LastPickedFraction != 1.0f && closestBody != null &&
(!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
((!AIParams.TargetOuterWalls || !canAttackWalls) && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
{
// Target is unreachable, there's a door or wall ahead
State = AIState.Idle;
@@ -2385,6 +2387,24 @@ namespace Barotrauma
}
#region Targeting
public static bool IsLatchedTo(Character target, Character character)
{
if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null)
{
return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter == character;
}
return false;
}
public static bool IsLatchedToSomeoneElse(Character target, Character character)
{
if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null)
{
return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter != null && enemyAI.LatchOntoAI.TargetCharacter != character;
}
return false;
}
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
//goes through all the AItargets, evaluates how preferable it is to attack the target,
@@ -2398,6 +2418,7 @@ namespace Barotrauma
targetingParams = null;
bool isAnyTargetClose = false;
bool isBeingChased = IsBeingChased;
float maxModifier = 5;
foreach (AITarget aiTarget in AITarget.List)
{
if (aiTarget.InDetectable) { continue; }
@@ -2537,12 +2558,12 @@ namespace Barotrauma
{
if (CanPassThroughHole(s, i))
{
valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0;
valueModifier *= leadsInside ? (IsAggressiveBoarder ? maxModifier : 1) : 0;
}
else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
else if (IsAggressiveBoarder && leadsInside && canAttackWalls)
{
// Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open * 0.25f;
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open;
}
}
else
@@ -2580,6 +2601,7 @@ namespace Barotrauma
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
// (Terminalcells)
valueModifier *= 1 - section.gap.Open * 0.25f;
valueModifier = Math.Max(valueModifier, 0.1f);
}
}
}
@@ -2599,6 +2621,7 @@ namespace Barotrauma
valueModifier *= 1 + section.gap.Open;
}
}
valueModifier = Math.Clamp(valueModifier, 0, maxModifier);
}
}
if (door != null)
@@ -2610,7 +2633,7 @@ namespace Barotrauma
bool isOpen = door.CanBeTraversed;
if (!isOpen)
{
if (!canAttackDoors || isOutdoor && !AIParams.TargetOuterWalls) { continue; }
if (!canAttackDoors) { continue; }
}
else if (!Character.AnimController.CanEnterSubmarine)
{
@@ -2624,11 +2647,11 @@ namespace Barotrauma
// Increase the priority if the character is outside and the door is from outside to inside
if (door.CanBeTraversed)
{
valueModifier = 3;
valueModifier = maxModifier;
}
else if (door.LinkedGap != null)
{
valueModifier = 1 + door.LinkedGap.Open;
valueModifier = 1 + door.LinkedGap.Open * (maxModifier - 1);
}
}
else
@@ -2727,6 +2750,37 @@ namespace Barotrauma
if (SelectedAiTarget == aiTarget)
{
if (Character.Submarine == null && aiTarget.Entity is ISpatialEntity spatialEntity && spatialEntity.Submarine != null)
{
if (targetingTag == "door" || targetingTag == "wall")
{
Vector2 rayStart = Character.SimPosition;
Vector2 rayEnd = aiTarget.SimPosition + spatialEntity.Submarine.SimPosition;
Body closestBody = Submarine.PickBody(rayStart, rayEnd, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true);
if (closestBody != null && closestBody.UserData is ISpatialEntity hit)
{
Vector2 hitPos = hit.SimPosition;
if (closestBody.UserData is Submarine)
{
hitPos = Submarine.LastPickedPosition;
}
else if (hit.Submarine != null)
{
hitPos += hit.Submarine.SimPosition;
}
float subHalfWidth = spatialEntity.Submarine.Borders.Width / 2;
float subHalfHeight = spatialEntity.Submarine.Borders.Height / 2;
Vector2 diff = ConvertUnits.ToDisplayUnits(rayEnd - hitPos);
bool isOtherSideOfTheSub = Math.Abs(diff.X) > subHalfWidth || Math.Abs(diff.Y) > subHalfHeight;
if (isOtherSideOfTheSub)
{
IgnoreTarget(aiTarget);
ResetAITarget();
continue;
}
}
}
}
// Stick to the current target
valueModifier *= 1.1f;
}
@@ -2757,19 +2811,22 @@ namespace Barotrauma
}
}
if (targetParams.AttackPattern == AttackPattern.Circle)
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && targetCharacter == null)
{
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose)
if (targetParams.AttackPattern == AttackPattern.Circle || targetParams.AttackPattern == AttackPattern.Sweep)
{
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
if (!isAnyTargetClose)
{
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
}
else
{
dist *= 5;
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
{
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
}
else if (targetParams.AttackPattern == AttackPattern.Circle)
{
dist *= 5;
}
}
}
}
@@ -2936,9 +2993,9 @@ namespace Barotrauma
if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear();
Structure wall = null;
Vector2 rayStart = AttackingLimb != null ? AttackingLimb.SimPosition : SimPosition;
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
{
Vector2 rayStart = SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
@@ -2952,7 +3009,6 @@ namespace Barotrauma
}
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading))
{
Vector2 rayStart = SimPosition;
Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5);
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
@@ -2968,7 +3024,6 @@ namespace Barotrauma
}
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering))
{
Vector2 rayStart = SimPosition;
Vector2 rayEnd = rayStart + Steering * 5;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
@@ -3021,6 +3076,7 @@ namespace Barotrauma
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces.
if (!isTargetingDoor)
{
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
}
}
@@ -3032,6 +3088,7 @@ namespace Barotrauma
else
{
// Blocked by a disabled wall.
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
}
}
@@ -3082,8 +3139,17 @@ namespace Barotrauma
if (!(hit.UserData is Structure w)) { return false; }
if (w.Submarine == null) { return false; }
if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; }
if (Character.Submarine == null && w.prefab.Tags.Contains("inner")) { return false; }
if (!AIParams.TargetOuterWalls && !w.prefab.Tags.Contains("inner")) { return false; }
if (Character.Submarine == null)
{
if (w.prefab.Tags.Contains("inner"))
{
if (!Character.AnimController.CanEnterSubmarine) { return false; }
}
else if (!AIParams.TargetOuterWalls)
{
return false;
}
}
wall = w;
return true;
}
@@ -3120,7 +3186,8 @@ namespace Barotrauma
{
if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth))
{
return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: 100);
float maxDistance = Math.Max(ConvertUnits.ToDisplayUnits(colliderLength), 100);
return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: maxDistance);
}
}
}
@@ -3584,12 +3651,12 @@ namespace Barotrauma
public override bool SteerThroughGap(Gap gap, Vector2 targetWorldPos, float deltaTime, float maxDistance = -1)
{
wallTarget = null;
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
Character.AnimController.ReleaseStuckLimbs();
bool success = base.SteerThroughGap(gap, targetWorldPos, deltaTime, maxDistance);
if (success)
{
wallTarget = null;
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
Character.AnimController.ReleaseStuckLimbs();
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
}
IsSteeringThroughGap = success;
@@ -20,7 +20,6 @@ namespace Barotrauma
private float reactTimer;
private float unreachableClearTimer;
private bool shouldCrouch;
public bool IsInsideCave { get; private set; }
/// <summary>
/// Resets each frame
/// </summary>
@@ -58,14 +57,14 @@ namespace Barotrauma
private float obstacleRaycastTimer;
private readonly float enemyCheckInterval = 0.2f;
private readonly float enemySpotDistanceOutside = 1500;
private readonly float enemySpotDistanceOutside = 800;
private readonly float enemySpotDistanceInside = 1000;
private float enemycheckTimer;
/// <summary>
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity.
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders).
/// </summary>
public float ReportRange { get; set; } = float.PositiveInfinity;
public float ReportRange { get; set; }
private float _aimSpeed = 1;
public float AimSpeed
@@ -167,6 +166,7 @@ namespace Barotrauma
objectiveManager = new AIObjectiveManager(c);
reactTimer = GetReactionTime();
SortTimer = Rand.Range(0f, sortObjectiveInterval);
ReportRange = Character.IsOnPlayerTeam ? float.PositiveInfinity : 1000;
}
public override void Update(float deltaTime)
@@ -306,7 +306,7 @@ namespace Barotrauma
UseIndoorSteeringOutside = false;
}
if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted)
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID))
{
// Spot enemies while staying outside or inside an enemy ship.
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
@@ -327,9 +327,13 @@ namespace Barotrauma
float dist = toTarget.LengthSquared();
float maxDistance = Character.Submarine == null ? enemySpotDistanceOutside : enemySpotDistanceInside;
if (dist > maxDistance * maxDistance) { continue; }
Vector2 forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation);
forward.X *= Character.AnimController.Dir;
if (Vector2.Dot(toTarget, forward) < 0.2f) { continue; }
if (EnemyAIController.IsLatchedToSomeoneElse(c, Character)) { continue; }
var head = Character.AnimController.GetLimb(LimbType.Head);
if (head == null) { continue; }
float rotation = head.body.TransformedRotation;
Vector2 forward = VectorExtensions.Forward(rotation);
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(toTarget, forward));
if (angle > 70) { continue; }
if (!Character.CanSeeCharacter(c)) { continue; }
if (dist < closestDistance || closestEnemy == null)
{
@@ -344,8 +348,6 @@ namespace Barotrauma
}
}
}
IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave;
if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer))
{
@@ -1242,7 +1244,7 @@ namespace Barotrauma
{
//if the other character did not witness the attack, and the character is not within report range (or capable of reporting)
//don't react to the attack
if (Character.IsDead || Character.IsUnconscious || !CheckReportRange(Character, otherCharacter, ReportRange))
if (Character.IsDead || Character.IsUnconscious || otherCharacter.TeamID != Character.TeamID || !CheckReportRange(Character, otherCharacter, ReportRange))
{
continue;
}
@@ -1259,8 +1261,8 @@ namespace Barotrauma
{
if (Character.Submarine == null)
{
// Outside -> don't react.
return AIObjectiveCombat.CombatMode.None;
// Outside
return attacker.Submarine == null ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
}
if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{
@@ -1852,7 +1854,7 @@ namespace Barotrauma
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.GetActiveObjectives<AIObjectiveFightIntruders>().Any();
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull)
{
@@ -52,7 +52,7 @@ namespace Barotrauma
// The validity changes when a character picks the item up.
if (!IsValidTarget(target, character, checkInventory: true)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
if (target.CurrentHull.FireSources.Count > 0) { return false; }
// Don't repair items in rooms that have enemies inside.
// Don't clean up items in rooms that have enemies inside.
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
return true;
}
@@ -117,7 +117,10 @@ namespace Barotrauma
private float AimSpeed => HumanAIController.AimSpeed;
private float AimAccuracy => HumanAIController.AimAccuracy;
private bool EnemyIsClose() => Enemy != null && Enemy.CurrentHull != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < 300;
private bool IsEnemyCloserThan(float margin) =>
Enemy != null && Enemy.CurrentHull != null &&
character.InWater && Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition) < margin * margin ||
HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < margin;
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
: base(character, objectiveManager, priorityModifier)
@@ -144,12 +147,19 @@ namespace Barotrauma
Mode = CombatMode.Retreat;
}
spreadTimer = Rand.Range(-10f, 10f);
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
HumanAIController.SortTimer = 0;
}
protected override float GetPriority()
{
if (character.TeamID == CharacterTeamType.FriendlyNPC && Enemy != null)
if (Enemy == null)
{
Priority = 0;
Abandon = true;
return Priority;
}
if (character.TeamID == CharacterTeamType.FriendlyNPC)
{
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
{
@@ -160,6 +170,13 @@ namespace Barotrauma
}
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
if (Priority > 0)
{
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
{
Priority = 0;
}
}
return Priority;
}
@@ -366,7 +383,7 @@ namespace Barotrauma
}
}
}
bool isAllowedToSeekWeapons = character.CurrentHull != null && !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest;
bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
if (!isAllowedToSeekWeapons)
{
if (WeaponComponent == null)
@@ -418,9 +435,16 @@ namespace Barotrauma
onCompleted: () => RemoveSubObjective(ref seekWeaponObjective),
onAbandon: () =>
{
SpeakNoWeapons();
RemoveSubObjective(ref seekWeaponObjective);
Mode = CombatMode.Retreat;
if (Weapon == null)
{
SpeakNoWeapons();
Mode = CombatMode.Retreat;
}
else
{
Mode = CombatMode.Defensive;
}
});
}
}
@@ -478,13 +502,25 @@ namespace Barotrauma
weaponComponent = null;
float bestPriority = 0;
float lethalDmg = -1;
bool enemyIsClose = EnemyIsClose();
bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300);
bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character);
foreach (var weapon in weaponList)
{
float priority = weapon.CombatPriority;
if (prioritizeMelee)
{
if (weapon is MeleeWeapon)
{
priority *= 5;
}
else
{
priority /= 2;
}
}
if (!weapon.IsLoaded(character))
{
if (weapon is RangedWeapon && enemyIsClose)
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
{
// Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo).
continue;
@@ -693,7 +729,7 @@ namespace Barotrauma
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
if (character.Inventory.TryPutItem(Weapon, character, slots))
{
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
}
else
{
@@ -1014,7 +1050,7 @@ namespace Barotrauma
}
if (!canSeeTarget)
{
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
return;
}
if (Weapon.RequireAimToUse)
@@ -1074,7 +1110,7 @@ namespace Barotrauma
else if (!character.IsFacing(Enemy.WorldPosition))
{
// Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around.
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
}
}
else
@@ -1190,5 +1226,7 @@ namespace Barotrauma
}
}
}
private void SetAimTimer(float newTimer) => aimTimer = Math.Max(aimTimer, newTimer);
}
}
@@ -120,7 +120,7 @@ namespace Barotrauma
}
if (character.CanInteractWith(container.Item, checkLinked: false))
{
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item)))
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(ItemToContain)))
{
HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax);
}
@@ -70,6 +70,7 @@ namespace Barotrauma
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (target.IsArrested) { return false; }
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
return true;
}
@@ -108,7 +108,7 @@ namespace Barotrauma
AllowToFindDivingGear = false,
AllowDangerousPressure = true,
ConditionLevel = MIN_OXYGEN,
RemoveExisting = true
RemoveExistingWhenNecessary = true
};
},
onAbandon: () =>
@@ -76,6 +76,10 @@ namespace Barotrauma
// -> ignore find safety unless we need to find a diving gear
Priority = 0;
}
else if (objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
{
Priority = 0;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
{
@@ -401,6 +401,11 @@ namespace Barotrauma
{
if (!ownerItem.IsInteractable(character)) { continue; }
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
if (ownerItem != item.Container)
{
itemPriority *= 0.1f;
}
}
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
@@ -77,6 +77,9 @@ namespace Barotrauma
// TODO: Currently we never check the visibility (to the end node), which is actually unintentional.
// I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better.
// However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true.
// NOTE: This seemes to have caused an issue now Regalis11/Barotrauma#8067: namely, the bot was trying to use a waypoint that was obstructed by a shuttle
// because obstruction was only checked when checking visibility in PathFinder. Changed that so that obstructed nodes are no longer used.
public bool CheckVisibility { get; set; }
public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; }
@@ -7,11 +7,11 @@ namespace Barotrauma
class AIObjectiveReturn : AIObjective
{
public override string Identifier { get; set; } = "return";
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
private bool usingEscapeBehavior;
private bool isSteeringThroughGap;
public Submarine ReturnTarget { get; }
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
private bool usingEscapeBehavior, isSteeringThroughGap;
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
{
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
@@ -112,7 +112,6 @@ namespace Barotrauma
}
if (targetHull != null)
{
RemoveSubObjective(ref moveInCaveObjective);
RemoveSubObjective(ref moveOutsideObjective);
TryAddSubObjective(ref moveInsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
@@ -137,91 +136,41 @@ namespace Barotrauma
IsCompleted = true;
}
}
else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null)
else if (!isSteeringThroughGap && moveOutsideObjective == null)
{
if (HumanAIController.IsInsideCave)
Hull targetHull = null;
float targetDistanceSquared = float.MaxValue;
bool targetIsAirlock = false;
foreach (var hull in ReturnTarget.GetHulls(false))
{
WayPoint closestOutsideWaypoint = null;
float closestDistance = float.MaxValue;
foreach (var w in WayPoint.WayPointList)
bool hullIsAirlock = hull.IsTaggedAirlock();
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
{
if (w.Tunnel != null && w.Tunnel.Type == Level.TunnelType.Cave) { continue; }
if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; }
float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition);
if (closestOutsideWaypoint == null || distance < closestDistance)
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
if (targetHull == null || distanceSquared < targetDistanceSquared)
{
closestOutsideWaypoint = w;
closestDistance = distance;
targetHull = hull;
targetDistanceSquared = distanceSquared;
targetIsAirlock = hullIsAirlock;
}
}
if (closestOutsideWaypoint != null)
{
RemoveSubObjective(ref moveInsideObjective);
RemoveSubObjective(ref moveOutsideObjective);
TryAddSubObjective(ref moveInCaveObjective,
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
{
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint,
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective),
onAbandon: () => Abandon = true);
}
else
{
#if DEBUG
DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'");
#endif
}
}
if (targetHull != null)
{
RemoveSubObjective(ref moveInsideObjective);
TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onAbandon: () => Abandon = true);
}
else
{
Hull targetHull = null;
float targetDistanceSquared = float.MaxValue;
bool targetIsAirlock = false;
foreach (var hull in ReturnTarget.GetHulls(false))
{
bool hullIsAirlock = hull.IsTaggedAirlock();
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
{
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
if (targetHull == null || distanceSquared < targetDistanceSquared)
{
targetHull = hull;
targetDistanceSquared = distanceSquared;
targetIsAirlock = hullIsAirlock;
}
}
}
if (targetHull != null)
{
RemoveSubObjective(ref moveInsideObjective);
RemoveSubObjective(ref moveInCaveObjective);
TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onAbandon: () => Abandon = true);
}
else
{
#if DEBUG
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'");
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'");
#endif
}
}
}
else
{
if (HumanAIController.IsInsideCave)
{
RemoveSubObjective(ref moveOutsideObjective);
}
else
{
RemoveSubObjective(ref moveInCaveObjective);
}
}
usingEscapeBehavior = shouldUseEscapeBehavior;
@@ -249,7 +198,6 @@ namespace Barotrauma
{
base.Reset();
moveInsideObjective = null;
moveInCaveObjective = null;
moveOutsideObjective = null;
usingEscapeBehavior = false;
isSteeringThroughGap = false;
@@ -333,7 +333,6 @@ namespace Barotrauma
//if searching for a path inside the sub, make sure the waypoint is visible
if (checkVisibility && isCharacter)
{
if (node.Waypoint.isObstructed) { return false; }
var body = Submarine.PickBody(rayStart, node.TempPosition,
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
if (body != null)
@@ -350,6 +349,7 @@ namespace Barotrauma
{
if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (startNodeFilter != null && !startNodeFilter(node)) { return false; }
if (node.Waypoint.isObstructed) { return false; }
// Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { return false; }
if (node.IsBlocked()) { return false; }
@@ -364,6 +364,7 @@ namespace Barotrauma
{
if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (endNodeFilter != null && !endNodeFilter(node)) { return false; }
if (node.Waypoint.isObstructed) { return false; }
// Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; }
if (node.IsBlocked()) { return false; }
@@ -776,8 +776,8 @@ namespace Barotrauma
if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
limbDiff = Vector2.Normalize(limbDiff);
float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * Math.Min(mass, limbJoint.BodyA.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * Math.Min(mass, limbJoint.BodyB.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
connectedLimbs.Clear();
checkedJoints.Clear();
@@ -461,7 +461,7 @@ namespace Barotrauma
ReloadAfflictions(element);
}
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
{
Character targetCharacter = target as Character;
if (OnlyHumans)
@@ -486,10 +486,10 @@ namespace Barotrauma
foreach (StatusEffect effect in statusEffects)
{
effect.sourceBody = sourceBody;
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(effectType, deltaTime, attacker, attacker, worldPosition);
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity, worldPosition);
}
if (targetCharacter != null)
{
@@ -526,7 +526,7 @@ namespace Barotrauma
}
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
{
if (targetLimb == null)
{
@@ -553,7 +553,7 @@ namespace Barotrauma
effect.sourceBody = sourceBody;
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(effectType, deltaTime, attacker, attacker);
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
}
if (effect.HasTargetType(StatusEffect.TargetType.Character))
{
@@ -503,7 +503,7 @@ namespace Barotrauma
get { return cursorPosition; }
set
{
if (!MathUtils.IsValid(value)) return;
if (!MathUtils.IsValid(value)) { return; }
cursorPosition = value;
}
}
@@ -853,7 +853,7 @@ namespace Barotrauma
}
else
{
return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC;
return IsKnockedDown || LockHands || IsBot && IsOnPlayerTeam;
}
}
set { canInventoryBeAccessed = value; }
@@ -3596,8 +3596,12 @@ namespace Barotrauma
foreach (LimbJoint joint in AnimController.LimbJoints)
{
if (!joint.CanBeSevered) { continue; }
// Limb A is where we usually create the joints from. Let's not allow severing when the "parent" limb is hit, or the head can pop off when we hit the torso, for example.
if (joint.LimbB != targetLimb) { continue; }
// Limb A is where we start creating the joint and LimbB is where the joint ends.
// Normally the joints have been created starting from the body, in which case we'd want to use LimbB e.g. to severe a hand when it's hit.
// But heads are a different case, because many characters have been created so that the head is first and then comes the rest of the body.
// If this is the case, we'll have to use LimbA to decapitate the creature when it's hit on the head. Otherwise decapitation could happen only when we hit the body, not the head.
var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB;
if (referenceLimb != targetLimb) { continue; }
float probability = severLimbsProbability;
if (!IsDead)
{
@@ -1134,6 +1134,10 @@ namespace Barotrauma
{
head.HairWithHatElement = hairs[hairWithHatIndex];
}
else
{
head.HairWithHatElement = null;
}
}
if (IsValidIndex(Head.BeardIndex, beards))
@@ -246,7 +246,7 @@ namespace Barotrauma
if (huskPrefab.ControlHusk)
{
#if SERVER
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.CharacterInfo.Character == character);
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
if (client != null)
{
GameMain.Server.SetClientCharacter(client, husk);
@@ -18,7 +18,7 @@ namespace Barotrauma
public void IncreaseSkill(float value, bool increasePastMax)
{
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumOlympianSkill : MaximumSkill);
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumSkillWithTalents : MaximumSkill);
}
private Sprite icon;
@@ -556,6 +556,7 @@ namespace Barotrauma
// TODO: We might need this or solve the cases where a limb is severed while holding on to an item
//if (character.Params.CanInteract) { return false; }
if (this == character.AnimController.MainLimb) { return false; }
bool canBeSevered = Params.CanBeSeveredAlive;
if (character.AnimController.CanWalk)
{
switch (type)
@@ -571,7 +572,7 @@ namespace Barotrauma
return false;
}
}
return true;
return canBeSevered;
}
}
@@ -1070,7 +1071,7 @@ namespace Barotrauma
#endif
if (damageTarget is Character targetCharacter && targetLimb != null)
{
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body);
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body, this);
}
else
{
@@ -1080,7 +1081,7 @@ namespace Barotrauma
}
else
{
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body);
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body, this);
}
}
/*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
@@ -648,6 +648,9 @@ namespace Barotrauma
[Serialize(1f, true, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)]
public float MinSeveranceDamage { get; set; }
[Serialize(true, true, description: "Disable if you don't want to allow severing this joint while the creature is alive. Note: Does nothing if the 'Severance Probability Modifier' in the joint settings is 0 (default). Also note that the setting doesn't override certain limitations, e.g. severing the main limb, or legs of a walking creature is not allowed."), Editable]
public bool CanBeSeveredAlive { get; set; }
//how long it takes for severed limbs to fade out
[Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)]
public float SeveredFadeOutTime { get; set; } = 10.0f;
@@ -96,8 +96,8 @@ namespace Barotrauma
set;
}
[Serialize(500.0f, true)]
public float MaximumOlympianSkill
[Serialize(200.0f, true)]
public float MaximumSkillWithTalents
{
get;
set;
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -7,9 +8,26 @@ namespace Barotrauma.Abilities
{
private readonly List<TargetType> targetTypes;
private List<PropertyConditional> conditionals = new List<PropertyConditional>();
public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
foreach (XElement subElement in conditionElement.Elements())
{
if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase))
{
foreach (XAttribute attribute in subElement.Attributes())
{
if (PropertyConditional.IsValid(attribute))
{
conditionals.Add(new PropertyConditional(attribute));
}
}
break;
}
}
}
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
@@ -18,7 +36,10 @@ namespace Barotrauma.Abilities
{
if (!(abilityCharacter.Character is Character character)) { return false; }
if (!IsViableTarget(targetTypes, character)) { return false; }
foreach (var conditional in conditionals)
{
if (!conditional.Matches(character)) { return false; }
}
return true;
}
else