v0.11.0.9
This commit is contained in:
@@ -91,9 +91,12 @@ namespace Barotrauma
|
||||
|
||||
private float avoidTimer;
|
||||
private float observeTimer;
|
||||
private float sweepTimer;
|
||||
|
||||
public bool StayInsideLevel = true;
|
||||
|
||||
private readonly IEnumerable<Body> myBodies;
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||
public PetBehavior PetBehavior { get; private set; }
|
||||
@@ -195,6 +198,19 @@ namespace Barotrauma
|
||||
MTRandom random = new MTRandom(ToolBox.StringToInt(seed));
|
||||
XElement aiElement = aiElements.Count == 1 ? aiElements[0] : ToolBox.SelectWeightedRandom(aiElements, aiCommonness, random);
|
||||
foreach (XElement subElement in aiElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "chooserandom":
|
||||
LoadSubElement(subElement.Elements().GetRandom(random));
|
||||
break;
|
||||
default:
|
||||
LoadSubElement(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSubElement(XElement subElement)
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -223,6 +239,7 @@ namespace Barotrauma
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f);
|
||||
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
|
||||
public CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
@@ -434,7 +451,7 @@ namespace Barotrauma
|
||||
UpdateIdle(deltaTime);
|
||||
break;
|
||||
case AIState.Attack:
|
||||
run = !IsCoolDownRunning;
|
||||
run = !IsCoolDownRunning || AttackingLimb != null && AttackingLimb.attack.FullSpeedAfterAttack;
|
||||
UpdateAttack(deltaTime);
|
||||
break;
|
||||
case AIState.Eat:
|
||||
@@ -471,7 +488,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (squaredDistance <= Math.Pow(reactDistance + movementMargin, 2))
|
||||
if (squaredDistance <= Math.Pow(reactDistance, 2))
|
||||
{
|
||||
float halfReactDistance = reactDistance / 2;
|
||||
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
|
||||
@@ -483,17 +500,12 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
|
||||
if (movementMargin <= 0)
|
||||
{
|
||||
movementMargin = halfReactDistance;
|
||||
}
|
||||
movementMargin = MathHelper.Clamp(movementMargin += deltaTime, halfReactDistance, reactDistance);
|
||||
UpdateEscape(deltaTime);
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * 0.5f * Rand.Range(0.75f, 1.25f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movementMargin = 0;
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -617,7 +629,7 @@ namespace Barotrauma
|
||||
|
||||
#region Idle
|
||||
|
||||
private void UpdateIdle(float deltaTime)
|
||||
private void UpdateIdle(float deltaTime, bool followLastTarget = true)
|
||||
{
|
||||
var pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
if (pathSteering == null)
|
||||
@@ -630,32 +642,35 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
if (followLastTarget)
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
if (memory != null)
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
{
|
||||
var location = memory.Location;
|
||||
float dist = Vector2.DistanceSquared(WorldPosition, location);
|
||||
if (dist < 50 * 50)
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
if (memory != null)
|
||||
{
|
||||
// Target is gone
|
||||
ResetAITarget();
|
||||
var location = memory.Location;
|
||||
float dist = Vector2.DistanceSquared(WorldPosition, location);
|
||||
if (dist < 50 * 50)
|
||||
{
|
||||
// Target is gone
|
||||
ResetAITarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Steer towards the target
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(target.Entity, location), 5);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Steer towards the target
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(target.Entity, location), 5);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
return;
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
if (pathSteering != null && !Character.AnimController.InWater)
|
||||
{
|
||||
@@ -686,7 +701,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else if (selectedTargetMemory != null)
|
||||
else if (selectedTargetMemory != null && SelectedAiTarget?.Entity is Character)
|
||||
{
|
||||
selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement;
|
||||
}
|
||||
@@ -880,7 +895,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth))
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 2);
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
var velocity = Vector2.Normalize(door.LinkedGap.FlowTargetHull.WorldPosition - Character.WorldPosition);
|
||||
steeringManager.SteeringManual(deltaTime, velocity);
|
||||
@@ -1040,6 +1055,48 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIBehaviorAfterAttack.IdleUntilCanAttack:
|
||||
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
AttackingLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cooldown not yet expired
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIBehaviorAfterAttack.FollowThrough:
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: true);
|
||||
return;
|
||||
@@ -1145,7 +1202,56 @@ namespace Barotrauma
|
||||
// Check that we can reach the target
|
||||
distance = toTarget.Length();
|
||||
canAttack = distance < AttackingLimb.attack.Range;
|
||||
if (!canAttack && !IsCoolDownRunning)
|
||||
if (canAttack)
|
||||
{
|
||||
if (AttackingLimb.attack.Ranged)
|
||||
{
|
||||
// Check that is facing the target
|
||||
float offset = AttackingLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
Vector2 forward = VectorExtensions.Forward(AttackingLimb.body.TransformedRotation - offset * Character.AnimController.Dir);
|
||||
float angle = VectorExtensions.Angle(forward, toTarget);
|
||||
canAttack = angle < MathHelper.ToRadians(AttackingLimb.attack.RequiredAngle);
|
||||
if (canAttack && AttackingLimb.attack.AvoidFriendlyFire)
|
||||
{
|
||||
float minDistance = MathUtils.Pow(ConvertUnits.ToDisplayUnits(Character.AnimController.Collider.GetMaxExtent() * 3), 2);
|
||||
bool IsFarEnough(Character other) => Vector2.DistanceSquared(Character.WorldPosition, other.WorldPosition) > minDistance;
|
||||
if (SwarmBehavior != null)
|
||||
{
|
||||
canAttack = SwarmBehavior.Members.All(c => c == Character || IsFarEnough(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
canAttack = Character.CharacterList.All(c => c == Character || !IsFriendly(Character, c) || IsFarEnough(c));
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
canAttack = !IsBlocked(attackSimPos) && !IsBlocked(AttackingLimb.SimPosition + forward * ConvertUnits.ToSimUnits(AttackingLimb.attack.Range));
|
||||
|
||||
bool IsBlocked(Vector2 targetPosition)
|
||||
{
|
||||
foreach (var body in Submarine.PickBodies(AttackingLimb.SimPosition, targetPosition, myBodies, Physics.CollisionCharacter))
|
||||
{
|
||||
Character hitTarget = null;
|
||||
if (body.UserData is Character c)
|
||||
{
|
||||
hitTarget = c;
|
||||
}
|
||||
else if (body.UserData is Limb limb)
|
||||
{
|
||||
hitTarget = limb.character;
|
||||
}
|
||||
if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsCoolDownRunning)
|
||||
{
|
||||
// If not, reset the attacking limb, if the cooldown is not running
|
||||
// Don't use the property, because we don't want cancel reversing, if we are reversing.
|
||||
@@ -1160,29 +1266,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
Limb steeringLimb = canAttack ? AttackingLimb : null;
|
||||
Limb steeringLimb = canAttack && !AttackingLimb.attack.Ranged ? AttackingLimb : null;
|
||||
if (steeringLimb == null)
|
||||
{
|
||||
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso.
|
||||
// If we always use the main limb, this causes the character to seek the target with it's torso/head, when it should not. For example Mudraptor steering with it's belly, when it should use it's head.
|
||||
// So let's use the one that's closer to the attacking limb.
|
||||
var torso = Character.AnimController.GetLimb(LimbType.Torso);
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (AttackingLimb == null)
|
||||
{
|
||||
steeringLimb = head ?? torso;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (head != null && torso != null)
|
||||
{
|
||||
steeringLimb = Vector2.DistanceSquared(AttackingLimb.SimPosition, head.SimPosition) < Vector2.DistanceSquared(AttackingLimb.SimPosition, torso.SimPosition) ? head : torso;
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringLimb = head ?? torso;
|
||||
}
|
||||
}
|
||||
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target.
|
||||
steeringLimb = Character.AnimController.GetLimb(LimbType.Head) ?? Character.AnimController.GetLimb(LimbType.Torso);
|
||||
}
|
||||
|
||||
if (steeringLimb == null)
|
||||
@@ -1190,7 +1278,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (AttackingLimb != null && AttackingLimb.attack.Retreat)
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, false);
|
||||
@@ -1250,6 +1338,25 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectedTargetingParams.SweepDistance > 0)
|
||||
{
|
||||
Vector2 toTarget = attackWorldPos - WorldPosition;
|
||||
if (distance <= 0)
|
||||
{
|
||||
distance = toTarget.Length();
|
||||
}
|
||||
float amplitude = MathHelper.Lerp(0, selectedTargetingParams.SweepStrength, MathUtils.InverseLerp(selectedTargetingParams.SweepDistance, 0, distance));
|
||||
if (amplitude > 0)
|
||||
{
|
||||
sweepTimer += deltaTime * selectedTargetingParams.SweepSpeed;
|
||||
float sin = (float)Math.Sin(sweepTimer) * amplitude;
|
||||
steerPos = MathUtils.RotatePointAroundTarget(attackSimPos, SimPosition, MathHelper.ToDegrees(sin));
|
||||
}
|
||||
else
|
||||
{
|
||||
sweepTimer = Rand.Range(-1000, 1000) * selectedTargetingParams.SweepSpeed;
|
||||
}
|
||||
}
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
@@ -1268,7 +1375,7 @@ namespace Barotrauma
|
||||
{
|
||||
IsSteeringThroughGap = true;
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 2);
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
Hull targetHull = section.gap?.FlowTargetHull;
|
||||
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
|
||||
@@ -1308,6 +1415,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (limb == ignoredLimb) { continue; }
|
||||
if (limb.IsSevered || limb.IsStuck) { continue; }
|
||||
if (limb.Disabled) { continue; }
|
||||
var attack = limb.attack;
|
||||
if (attack == null) { continue; }
|
||||
if (attack.CoolDownTimer > 0) { continue; }
|
||||
@@ -1318,6 +1426,17 @@ namespace Barotrauma
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
|
||||
}
|
||||
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { continue; }
|
||||
if (attack.Ranged)
|
||||
{
|
||||
// Check that is approximately facing the target
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : limb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
Vector2 forward = VectorExtensions.Forward(limb.body.TransformedRotation - offset * Character.AnimController.Dir);
|
||||
float angle = VectorExtensions.Angle(forward, toTarget);
|
||||
if (angle > MathHelper.ToRadians(attack.RequiredAngle)) { continue; }
|
||||
}
|
||||
|
||||
if (AIParams.RandomAttack)
|
||||
{
|
||||
attackLimbs.Add(limb);
|
||||
@@ -1407,12 +1526,21 @@ namespace Barotrauma
|
||||
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
|
||||
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
|
||||
}
|
||||
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
LatchOntoAI?.SetAttachTarget(wall, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner") || wall.Submarine != null && wall.Submarine == Character.Submarine)
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevents monsters from entering the the tail and the nose pieces.
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1450,13 +1578,13 @@ namespace Barotrauma
|
||||
|
||||
bool wasLatched = IsLatchedOnSub;
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 1);
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
|
||||
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
|
||||
avoidTimer = AIParams.AvoidTime * 0.5f * Rand.Range(0.75f, 1.25f);
|
||||
if (!isFriendly)
|
||||
{
|
||||
SelectTarget(attacker.AiTarget);
|
||||
@@ -2029,6 +2157,8 @@ namespace Barotrauma
|
||||
if (targetingTag == null) { continue; }
|
||||
var targetParams = GetTargetParams(targetingTag);
|
||||
if (targetParams == null) { continue; }
|
||||
if (targetParams.IgnoreWhileInside && character.CurrentHull != null) { continue; }
|
||||
if (targetParams.IgnoreWhileOutside && character.CurrentHull == null) { continue; }
|
||||
if (targetParams.State == AIState.Observe || targetParams.State == AIState.Eat)
|
||||
{
|
||||
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine)
|
||||
@@ -2037,10 +2167,7 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null) { continue; }
|
||||
valueModifier *= targetParams.Priority;
|
||||
|
||||
if (valueModifier == 0.0f) { continue; }
|
||||
@@ -2180,7 +2307,7 @@ namespace Barotrauma
|
||||
wall = wallTarget?.Structure;
|
||||
}
|
||||
// The target is not a wall or it's not the same as we are attached to -> release
|
||||
bool releaseTarget = wall == null || !wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB);
|
||||
bool releaseTarget = wall == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB);
|
||||
if (!releaseTarget)
|
||||
{
|
||||
for (int i = 0; i < wall.Sections.Length; i++)
|
||||
@@ -2194,7 +2321,7 @@ namespace Barotrauma
|
||||
if (releaseTarget)
|
||||
{
|
||||
wallTarget = null;
|
||||
LatchOntoAI.DeattachFromBody(cooldown: 1);
|
||||
LatchOntoAI.DeattachFromBody(reset: true, cooldown: 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2408,7 +2535,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
LatchOntoAI?.DeattachFromBody(reset: true);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
escapeTarget = null;
|
||||
AttackingLimb = null;
|
||||
@@ -2449,14 +2576,15 @@ namespace Barotrauma
|
||||
foreach (var limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Disabled) { continue; }
|
||||
if (limb.attack == null) { continue; }
|
||||
if (!canAttackWalls)
|
||||
{
|
||||
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.StructureDamage > 0;
|
||||
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && (limb.attack.StructureDamage > 0 || limb.attack.Ranged);
|
||||
}
|
||||
if (!canAttackDoors)
|
||||
{
|
||||
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.ItemDamage > 0;
|
||||
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && (limb.attack.ItemDamage > 0 || limb.attack.Ranged);
|
||||
}
|
||||
if (!canAttackCharacters)
|
||||
{
|
||||
@@ -2503,7 +2631,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
public bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
{
|
||||
if (!wall.SectionBodyDisabled(sectionIndex)) return false;
|
||||
int holeCount = 1;
|
||||
@@ -2546,6 +2674,7 @@ namespace Barotrauma
|
||||
foreach (Limb limb in targetLimbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
float dist = Vector2.DistanceSquared(limb.WorldPosition, attackLimb.WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class HumanAIController : AIController
|
||||
{
|
||||
public static bool debugai;
|
||||
public static bool DisableCrewAI;
|
||||
|
||||
private readonly AIObjectiveManager objectiveManager;
|
||||
@@ -37,12 +38,51 @@ namespace Barotrauma
|
||||
|
||||
private float respondToAttackTimer;
|
||||
private const float RespondToAttackInterval = 1.0f;
|
||||
private bool wasConscious;
|
||||
|
||||
private bool freezeAI;
|
||||
|
||||
private readonly float maxSteeringBuffer = 5000;
|
||||
private readonly float minSteeringBuffer = 500;
|
||||
private readonly float steeringBufferIncreaseSpeed = 100;
|
||||
private float steeringBuffer;
|
||||
|
||||
private readonly float obstacleRaycastInterval = 1;
|
||||
private float obstacleRaycastTimer;
|
||||
|
||||
/// <summary>
|
||||
/// List of previous attacks done to this character
|
||||
/// </summary>
|
||||
private readonly Dictionary<Character, AttackResult> previousAttackResults = new Dictionary<Character, AttackResult>();
|
||||
|
||||
private readonly SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
|
||||
public override AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return objectiveManager; }
|
||||
}
|
||||
|
||||
public Order CurrentOrder
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string CurrentOrderOption
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float CurrentHullSafety { get; private set; } = 100;
|
||||
|
||||
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
|
||||
private readonly HashSet<Character> attackers = new HashSet<Character>();
|
||||
|
||||
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
|
||||
private class HullSafety
|
||||
{
|
||||
public float safety;
|
||||
@@ -72,35 +112,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
|
||||
public override AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return objectiveManager; }
|
||||
}
|
||||
|
||||
public Order CurrentOrder
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string CurrentOrderOption
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float CurrentHullSafety { get; private set; } = 100;
|
||||
|
||||
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
|
||||
private readonly HashSet<Character> attackers = new HashSet<Character>();
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
if (!c.IsHuman)
|
||||
@@ -117,8 +128,6 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
private bool freezeAI;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (DisableCrewAI || Character.Removed) { return; }
|
||||
@@ -146,6 +155,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (isIncapacitated) { return; }
|
||||
|
||||
wasConscious = true;
|
||||
|
||||
respondToAttackTimer -= deltaTime;
|
||||
if (respondToAttackTimer <= 0.0f)
|
||||
{
|
||||
@@ -176,15 +187,38 @@ namespace Barotrauma
|
||||
IgnoredItems.Clear();
|
||||
}
|
||||
|
||||
// Use the pathfinding also outside of the sub, but not farther than the extents of the sub + 500 units.
|
||||
if (Character.Submarine != null || SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null &&
|
||||
Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + 500, 2))
|
||||
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
|
||||
bool hasValidPath = steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable;
|
||||
|
||||
if (Character.Submarine == null && hasValidPath)
|
||||
{
|
||||
obstacleRaycastTimer -= deltaTime;
|
||||
if (obstacleRaycastTimer <= 0)
|
||||
{
|
||||
obstacleRaycastTimer = obstacleRaycastInterval;
|
||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||
{
|
||||
if (connectedSub == Submarine.MainSub) { continue; }
|
||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||
{
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Character.Submarine != null || hasValidPath && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer))
|
||||
{
|
||||
if (steeringManager != insideSteering)
|
||||
{
|
||||
insideSteering.Reset();
|
||||
}
|
||||
steeringManager = insideSteering;
|
||||
steeringBuffer += steeringBufferIncreaseSpeed * deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -193,7 +227,9 @@ namespace Barotrauma
|
||||
outsideSteering.Reset();
|
||||
}
|
||||
steeringManager = outsideSteering;
|
||||
steeringBuffer = minSteeringBuffer;
|
||||
}
|
||||
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
|
||||
|
||||
AnimController.Crouching = shouldCrouch;
|
||||
CheckCrouching(deltaTime);
|
||||
@@ -369,7 +405,8 @@ namespace Barotrauma
|
||||
if (!NeedsDivingGear(Character.CurrentHull, out bool needsSuit) || !needsSuit || oxygenLow)
|
||||
{
|
||||
bool shouldKeepTheGearOn = Character.AnimController.HeadInWater
|
||||
|| Character.Submarine.TeamID != Character.TeamID && Character.Submarine.TeamID != Character.TeamType.FriendlyNPC
|
||||
|| Character.Submarine == null
|
||||
|| Character.Submarine.TeamID != Character.TeamID
|
||||
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|
||||
|| ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character // wait order
|
||||
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
|
||||
@@ -435,6 +472,7 @@ namespace Barotrauma
|
||||
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
divingSuit.Drop(Character);
|
||||
HandleRelocation(divingSuit);
|
||||
}
|
||||
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit)
|
||||
{
|
||||
@@ -461,6 +499,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
divingSuit.Drop(Character);
|
||||
HandleRelocation(divingSuit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,6 +517,7 @@ namespace Barotrauma
|
||||
if (ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
mask.Drop(Character);
|
||||
HandleRelocation(mask);
|
||||
}
|
||||
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
|
||||
{
|
||||
@@ -501,6 +541,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
mask.Drop(Character);
|
||||
HandleRelocation(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,6 +589,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
item.Drop(Character);
|
||||
HandleRelocation(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,6 +598,62 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<Item> itemsToRelocate = new HashSet<Item>();
|
||||
|
||||
private void HandleRelocation(Item item)
|
||||
{
|
||||
if (item.Submarine?.TeamID == Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
if (itemsToRelocate.Contains(item)) { return; }
|
||||
itemsToRelocate.Add(item);
|
||||
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
|
||||
{
|
||||
myPort.OnUnDocked += Relocate;
|
||||
}
|
||||
var campaign = GameMain.GameSession.Campaign;
|
||||
if (campaign != null)
|
||||
{
|
||||
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
|
||||
campaign.BeforeLevelLoading += Relocate;
|
||||
}
|
||||
}
|
||||
|
||||
void Relocate()
|
||||
{
|
||||
if (item == null || item.Removed) { return; }
|
||||
if (!itemsToRelocate.Contains(item)) { return; }
|
||||
var mainSub = Submarine.MainSub;
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.ParentInventory.Owner is Character c)
|
||||
{
|
||||
if (c.TeamID == Character.TeamType.Team1 || c.TeamID == Character.TeamType.Team2)
|
||||
{
|
||||
// Taken by a player/bot (if npc or monster would take the item, we'd probably still want it to spawn back to the main sub.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (item.ParentInventory.Owner.Submarine == mainSub)
|
||||
{
|
||||
// Placed inside an inventory that's already in the main sub.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Laying on ground inside the main sub.
|
||||
if (item.Submarine == mainSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub);
|
||||
if (wp != null)
|
||||
{
|
||||
item.Submarine = mainSub;
|
||||
item.SetTransform(wp.SimPosition, 0.0f);
|
||||
}
|
||||
itemsToRelocate.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReequipUnequipped()
|
||||
{
|
||||
foreach (var item in unequippedItems)
|
||||
@@ -585,6 +683,7 @@ namespace Barotrauma
|
||||
suitableContainer = null;
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredItems, customPriorityFunction: i =>
|
||||
{
|
||||
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (container.Inventory.IsFull()) { return 0; }
|
||||
@@ -746,6 +845,13 @@ namespace Barotrauma
|
||||
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
// The attack incapacitated/killed the character: respond immediately to trigger nearby characters because the update loop no longer runs
|
||||
if (wasConscious && (Character.IsIncapacitated || Character.Stun > 0.0f))
|
||||
{
|
||||
RespondToAttack(attacker, attackResult);
|
||||
wasConscious = false;
|
||||
return;
|
||||
}
|
||||
if (Character.IsDead) { return; }
|
||||
if (attacker == null || Character.IsPlayer)
|
||||
{
|
||||
@@ -1003,7 +1109,7 @@ namespace Barotrauma
|
||||
{
|
||||
var objective = new AIObjectiveCombat(Character, attacker, mode, objectiveManager)
|
||||
{
|
||||
HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman",
|
||||
HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman" || Character.CurrentHull == null && ObjectiveManager.IsCurrentOrder<AIObjectiveGoTo>(),
|
||||
abortCondition = abortCondition,
|
||||
allowHoldFire = allowHoldFire,
|
||||
};
|
||||
@@ -1168,7 +1274,7 @@ namespace Barotrauma
|
||||
needsSuit = true;
|
||||
return true;
|
||||
}
|
||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < CharacterHealth.LowOxygenThreshold)
|
||||
if (hull.WaterPercentage > 60 || hull.Oxygen < CharacterHealth.LowOxygenThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1223,6 +1329,14 @@ namespace Barotrauma
|
||||
}
|
||||
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
|
||||
if (!otherCharacter.CanSeeCharacter(thief)) { continue; }
|
||||
// Don't react if the player is taking an extinguisher and there's any fires on the sub, or diving gear when the sub is flooding
|
||||
// -> allow them to use the emergency items
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
var connectedHulls = character.Submarine.GetHulls(alsoFromConnectedSubs: true);
|
||||
if (item.HasTag("fireextinguisher") && connectedHulls.Any(h => h.FireSources.Any())) { continue; }
|
||||
if (item.HasTag("diving") && connectedHulls.Any(h => h.ConnectedGaps.Any(g => AIObjectiveFixLeaks.IsValidTarget(g, thief)))) { continue; }
|
||||
}
|
||||
if (!someoneSpoke && !character.IsIncapacitated && character.Stun <= 0.0f)
|
||||
{
|
||||
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
@@ -1236,8 +1350,6 @@ namespace Barotrauma
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
|
||||
someoneSpoke = true;
|
||||
}
|
||||
// Don't react if the player is taking an extinguisher and there's any fires on the sub -> allow them to use the emergency items
|
||||
if (item.HasTag("fireextinguisher") && character.Submarine.GetHulls(alsoFromConnectedSubs: true).Any(h => h.FireSources.Any())) { continue; }
|
||||
// React if we are security
|
||||
if (!TriggerSecurity(otherHumanAI))
|
||||
{
|
||||
@@ -1425,7 +1537,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.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders);
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
if (isCurrentHull)
|
||||
{
|
||||
@@ -1464,7 +1576,17 @@ namespace Barotrauma
|
||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
|
||||
}
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
|
||||
float dangerousItemsFactor = 1f;
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (item.Prefab != null && item.Prefab.IsDangerous)
|
||||
{
|
||||
dangerousItemsFactor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor;
|
||||
return MathHelper.Clamp(safety * 100, 0, 100);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seeks the ladder from the current and the next two nodes.
|
||||
/// Seeks the ladder from the next and next + 1 nodes.
|
||||
/// </summary>
|
||||
public Ladder GetNextLadder()
|
||||
{
|
||||
@@ -294,7 +294,13 @@ namespace Barotrauma
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController && !character.LockHands;
|
||||
var ladders = GetNextLadder();
|
||||
Ladder currentLadder = currentPath.CurrentNode.Ladders;
|
||||
if (currentLadder != null && currentLadder.Item.NonInteractable)
|
||||
{
|
||||
currentLadder = null;
|
||||
}
|
||||
Ladder nextLadder = GetNextLadder();
|
||||
var ladders = currentLadder ?? nextLadder;
|
||||
if (canClimb && !isDiving && ladders != null && character.SelectedConstruction != ladders.Item)
|
||||
{
|
||||
if (IsNextNodeLadder || currentPath.CurrentIndex == currentPath.Nodes.Count - 1)
|
||||
@@ -325,7 +331,6 @@ namespace Barotrauma
|
||||
if (character.IsClimbing && !isDiving)
|
||||
{
|
||||
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
|
||||
Ladder nextLadder = GetNextLadder();
|
||||
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
|
||||
if (nextLadderSameAsCurrent)
|
||||
{
|
||||
@@ -341,8 +346,7 @@ namespace Barotrauma
|
||||
diff.Y = Math.Max(diff.Y, 1.0f);
|
||||
}
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
float margin = 0.1f;
|
||||
bool isAboveFloor = heightFromFloor > -margin && heightFromFloor < collider.height * 1.5f;
|
||||
bool isAboveFloor = heightFromFloor > -0.1f;
|
||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||
if (isAboveFloor && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
{
|
||||
@@ -437,7 +441,7 @@ namespace Barotrauma
|
||||
if (door.IsOpen) { return true; }
|
||||
if (door.Item.NonInteractable) { return false; }
|
||||
if (CanBreakDoors) { return true; }
|
||||
if (door.IsStuck) { return false; }
|
||||
if (door.IsStuck || door.IsJammed) { return false; }
|
||||
if (!canOpenDoors || character.LockHands) { return false; }
|
||||
if (door.HasIntegratedButtons)
|
||||
{
|
||||
@@ -719,7 +723,10 @@ namespace Barotrauma
|
||||
if (wander)
|
||||
{
|
||||
SteeringWander();
|
||||
SteeringAvoid(deltaTime, lookAheadDistance: ConvertUnits.ToSimUnits(wallAvoidDistance), 5);
|
||||
if (inWater)
|
||||
{
|
||||
SteeringAvoid(deltaTime, lookAheadDistance: ConvertUnits.ToSimUnits(wallAvoidDistance), 5);
|
||||
}
|
||||
}
|
||||
if (!inWater)
|
||||
{
|
||||
|
||||
@@ -15,15 +15,17 @@ namespace Barotrauma
|
||||
|
||||
private float raycastTimer;
|
||||
|
||||
private Body attachTargetBody;
|
||||
private Structure targetWall;
|
||||
private Body targetBody;
|
||||
private Vector2 attachSurfaceNormal;
|
||||
private Submarine attachTargetSubmarine;
|
||||
private Submarine targetSubmarine;
|
||||
private readonly Character character;
|
||||
|
||||
public bool AttachToSub { get; private set; }
|
||||
public bool AttachToWalls { get; private set; }
|
||||
|
||||
private float minDeattachSpeed = 3.0f, maxDeattachSpeed = 10.0f;
|
||||
private float damageOnDetach = 0.0f, detachStun = 0.0f;
|
||||
private readonly float minDeattachSpeed, maxDeattachSpeed;
|
||||
private readonly float damageOnDetach, detachStun;
|
||||
private float deattachTimer;
|
||||
|
||||
private Vector2 wallAttachPos;
|
||||
@@ -35,13 +37,8 @@ namespace Barotrauma
|
||||
private float attachLimbRotation;
|
||||
|
||||
private float jointDir;
|
||||
|
||||
private List<WeldJoint> attachJoints = new List<WeldJoint>();
|
||||
|
||||
public List<WeldJoint> AttachJoints
|
||||
{
|
||||
get { return attachJoints; }
|
||||
}
|
||||
public List<WeldJoint> AttachJoints { get; } = new List<WeldJoint>();
|
||||
|
||||
public Vector2? WallAttachPos
|
||||
{
|
||||
@@ -49,19 +46,16 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsAttached
|
||||
{
|
||||
get { return attachJoints.Count > 0; }
|
||||
}
|
||||
public bool IsAttached => AttachJoints.Count > 0;
|
||||
|
||||
public bool IsAttachedToSub => IsAttached && (attachTargetBody?.UserData is Submarine || attachTargetBody?.UserData is Entity entity && entity.Submarine != null);
|
||||
public bool IsAttachedToSub => IsAttached && targetSubmarine != null;
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
AttachToWalls = element.GetAttributeBool("attachtowalls", false);
|
||||
AttachToSub = element.GetAttributeBool("attachtosub", false);
|
||||
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 3.0f);
|
||||
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 10.0f));
|
||||
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 5.0f);
|
||||
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 8.0f));
|
||||
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
|
||||
detachStun = element.GetAttributeFloat("detachstun", 0.0f);
|
||||
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
|
||||
@@ -81,45 +75,47 @@ namespace Barotrauma
|
||||
attachLimb = enemyAI.Character.AnimController.MainLimb;
|
||||
}
|
||||
|
||||
character = enemyAI.Character;
|
||||
enemyAI.Character.OnDeath += OnCharacterDeath;
|
||||
}
|
||||
|
||||
public void SetAttachTarget(Body attachTarget, Submarine attachTargetSub, Vector2 attachPos, Vector2 attachSurfaceNormal)
|
||||
public void SetAttachTarget(Structure wall, Vector2 attachPos, Vector2 attachSurfaceNormal)
|
||||
{
|
||||
attachTargetBody = attachTarget;
|
||||
attachTargetSubmarine = attachTargetSub;
|
||||
if (wall == null) { return; }
|
||||
var sub = wall.Submarine;
|
||||
if (sub == null) { return; }
|
||||
targetWall = wall;
|
||||
targetSubmarine = sub;
|
||||
targetBody = targetSubmarine.PhysicsBody.FarseerBody;
|
||||
this.attachSurfaceNormal = attachSurfaceNormal;
|
||||
wallAttachPos = attachPos;
|
||||
}
|
||||
|
||||
public void Update(EnemyAIController enemyAI, float deltaTime)
|
||||
{
|
||||
Character character = enemyAI.Character;
|
||||
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
DeattachFromBody();
|
||||
WallAttachPos = null;
|
||||
DeattachFromBody(reset: true);
|
||||
return;
|
||||
}
|
||||
if (attachJoints.Count > 0)
|
||||
if (AttachJoints.Count > 0)
|
||||
{
|
||||
if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir))
|
||||
{
|
||||
attachJoints[0].LocalAnchorA =
|
||||
new Vector2(-attachJoints[0].LocalAnchorA.X, attachJoints[0].LocalAnchorA.Y);
|
||||
attachJoints[0].ReferenceAngle = -attachJoints[0].ReferenceAngle;
|
||||
AttachJoints[0].LocalAnchorA =
|
||||
new Vector2(-AttachJoints[0].LocalAnchorA.X, AttachJoints[0].LocalAnchorA.Y);
|
||||
AttachJoints[0].ReferenceAngle = -AttachJoints[0].ReferenceAngle;
|
||||
jointDir = attachLimb.Dir;
|
||||
}
|
||||
for (int i = 0; i < attachJoints.Count; i++)
|
||||
for (int i = 0; i < AttachJoints.Count; i++)
|
||||
{
|
||||
//something went wrong, limb body is very far from the joint anchor -> deattach
|
||||
if (Vector2.DistanceSquared(attachJoints[i].WorldAnchorB, attachJoints[i].BodyA.Position) > 10.0f * 10.0f)
|
||||
if (Vector2.DistanceSquared(AttachJoints[i].WorldAnchorB, AttachJoints[i].BodyA.Position) > 10.0f * 10.0f)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
|
||||
#endif
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -135,9 +131,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 transformedAttachPos = wallAttachPos;
|
||||
if (character.Submarine == null && attachTargetSubmarine != null)
|
||||
if (character.Submarine == null && targetSubmarine != null)
|
||||
{
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(attachTargetSubmarine.Position);
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(targetSubmarine.Position);
|
||||
}
|
||||
if (transformedAttachPos != Vector2.Zero)
|
||||
{
|
||||
@@ -167,12 +163,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
attachSurfaceNormal = edge.GetNormal(cell);
|
||||
attachTargetBody = cell.Body;
|
||||
Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection);
|
||||
float distSqr = Vector2.DistanceSquared(character.SimPosition, wallAttachPos);
|
||||
float distSqr = Vector2.DistanceSquared(character.SimPosition, potentialAttachPos);
|
||||
if (distSqr < closestDist)
|
||||
{
|
||||
attachSurfaceNormal = edge.GetNormal(cell);
|
||||
targetBody = cell.Body;
|
||||
wallAttachPos = potentialAttachPos;
|
||||
closestDist = distSqr;
|
||||
}
|
||||
@@ -190,9 +186,9 @@ namespace Barotrauma
|
||||
wallAttachPos = Vector2.Zero;
|
||||
}
|
||||
|
||||
if (wallAttachPos == Vector2.Zero)
|
||||
if (wallAttachPos == Vector2.Zero || targetBody == null)
|
||||
{
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -201,13 +197,13 @@ namespace Barotrauma
|
||||
if (squaredDistance < targetDistance * targetDistance)
|
||||
{
|
||||
//close enough to a wall -> attach
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, wallAttachPos);
|
||||
AttachToBody(wallAttachPos);
|
||||
enemyAI.SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
//move closer to the wall
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: false);
|
||||
enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f);
|
||||
enemyAI.SteeringManager.SteeringSeek(wallAttachPos);
|
||||
}
|
||||
@@ -217,57 +213,76 @@ namespace Barotrauma
|
||||
case AIState.Aggressive:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && targetBody != null)
|
||||
{
|
||||
// is not attached or is attached to something else
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB != attachTargetBody)
|
||||
if (!IsAttached || IsAttached && AttachJoints[0].BodyB != targetBody)
|
||||
{
|
||||
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
||||
{
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos);
|
||||
AttachToBody(transformedAttachPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WallAttachPos = null;
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsAttached && attachTargetBody != null && deattachTimer < 0.0f)
|
||||
if (IsAttached && targetBody != null && targetWall != null && targetSubmarine != null && deattachTimer <= 0.0f)
|
||||
{
|
||||
Entity entity = attachTargetBody.UserData as Entity;
|
||||
Submarine attachedSub = entity is Submarine sub ? sub : entity?.Submarine;
|
||||
if (attachedSub != null)
|
||||
bool deattach = false;
|
||||
// Deattach if the wall is broken enough where we are attached to
|
||||
int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true);
|
||||
if (enemyAI.CanPassThroughHole(targetWall, targetSection))
|
||||
{
|
||||
float velocity = attachedSub.Velocity == Vector2.Zero ? 0.0f : attachedSub.Velocity.Length();
|
||||
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
|
||||
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
|
||||
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
|
||||
deattach = true;
|
||||
attachCooldown = 2;
|
||||
}
|
||||
if (!deattach)
|
||||
{
|
||||
// Deattach if the velocity is high
|
||||
float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length();
|
||||
deattach = velocity > maxDeattachSpeed;
|
||||
if (!deattach)
|
||||
{
|
||||
DeattachFromBody();
|
||||
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
|
||||
attachCooldown = 5.0f;
|
||||
if (velocity > minDeattachSpeed)
|
||||
{
|
||||
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
|
||||
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
|
||||
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
|
||||
{
|
||||
deattach = true;
|
||||
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
|
||||
attachCooldown = detachStun * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deattach)
|
||||
{
|
||||
DeattachFromBody(reset: true);
|
||||
}
|
||||
deattachTimer = 5.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachToBody(PhysicsBody collider, Limb attachLimb, Body targetBody, Vector2 attachPos)
|
||||
private void AttachToBody(Vector2 attachPos)
|
||||
{
|
||||
if (attachLimb == null) { return; }
|
||||
if (targetBody == null) { return; }
|
||||
if (attachCooldown > 0) { return; }
|
||||
var collider = character.AnimController.Collider;
|
||||
//already attached to something
|
||||
if (attachJoints.Count > 0)
|
||||
if (AttachJoints.Count > 0)
|
||||
{
|
||||
//already attached to the target body, no need to do anything
|
||||
if (attachJoints[0].BodyB == targetBody) { return; }
|
||||
DeattachFromBody();
|
||||
if (AttachJoints[0].BodyB == targetBody) { return; }
|
||||
DeattachFromBody(reset: false);
|
||||
}
|
||||
|
||||
jointDir = attachLimb.Dir;
|
||||
@@ -290,7 +305,7 @@ namespace Barotrauma
|
||||
CollideConnected = false,
|
||||
};
|
||||
GameMain.World.Add(limbJoint);
|
||||
attachJoints.Add(limbJoint);
|
||||
AttachJoints.Add(limbJoint);
|
||||
|
||||
// Limb scale is already taken into account when creating the collider.
|
||||
Vector2 colliderFront = collider.GetLocalFront();
|
||||
@@ -309,25 +324,37 @@ namespace Barotrauma
|
||||
//Length = 0.1f
|
||||
};
|
||||
GameMain.World.Add(colliderJoint);
|
||||
attachJoints.Add(colliderJoint);
|
||||
AttachJoints.Add(colliderJoint);
|
||||
}
|
||||
|
||||
public void DeattachFromBody(float cooldown = 0)
|
||||
public void DeattachFromBody(bool reset, float cooldown = 0)
|
||||
{
|
||||
foreach (Joint joint in attachJoints)
|
||||
foreach (Joint joint in AttachJoints)
|
||||
{
|
||||
GameMain.World.Remove(joint);
|
||||
}
|
||||
attachJoints.Clear();
|
||||
AttachJoints.Clear();
|
||||
if (cooldown > 0)
|
||||
{
|
||||
attachCooldown = cooldown;
|
||||
}
|
||||
if (reset)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
targetWall = null;
|
||||
targetSubmarine = null;
|
||||
targetBody = null;
|
||||
WallAttachPos = null;
|
||||
}
|
||||
|
||||
private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: true);
|
||||
character.OnDeath -= OnCharacterDeath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,12 @@ namespace Barotrauma
|
||||
_abandon = value;
|
||||
if (_abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
if (HumanAIController.debugai && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
throw new Exception("Order abandoned!");
|
||||
}
|
||||
#endif
|
||||
OnAbandon();
|
||||
}
|
||||
}
|
||||
@@ -96,9 +102,21 @@ namespace Barotrauma
|
||||
return all;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single shot event. Automatically cleared after launching. Use OnCompleted method for implementing (internal) persistent behavior.
|
||||
/// </summary>
|
||||
public event Action Completed;
|
||||
/// <summary>
|
||||
/// A single shot event. Automatically cleared after launching. Use OnAbandoned method for implementing (internal) persistent behavior.
|
||||
/// </summary>
|
||||
public event Action Abandoned;
|
||||
/// <summary>
|
||||
/// A single shot event. Automatically cleared after launching. Use OnSelected method for implementing (internal) persistent behavior.
|
||||
/// </summary>
|
||||
public event Action Selected;
|
||||
/// <summary>
|
||||
/// A single shot event. Automatically cleared after launching. Use OnDeselected method for implementing (internal) persistent behavior.
|
||||
/// </summary>
|
||||
public event Action Deselected;
|
||||
|
||||
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
|
||||
@@ -318,22 +336,26 @@ namespace Barotrauma
|
||||
{
|
||||
Reset();
|
||||
Selected?.Invoke();
|
||||
Selected = null;
|
||||
}
|
||||
|
||||
public virtual void OnDeselected()
|
||||
{
|
||||
CumulatedDevotion = 0;
|
||||
Deselected?.Invoke();
|
||||
Deselected = null;
|
||||
}
|
||||
|
||||
protected virtual void OnCompleted()
|
||||
{
|
||||
Completed?.Invoke();
|
||||
Completed = null;
|
||||
}
|
||||
|
||||
protected virtual void OnAbandon()
|
||||
{
|
||||
Abandoned?.Invoke();
|
||||
Abandoned = null;
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
@@ -408,7 +430,14 @@ namespace Barotrauma
|
||||
subObjectives.Remove(subObjective);
|
||||
if (AbandonWhenCannotCompleteSubjectives)
|
||||
{
|
||||
Abandon = true;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
|
||||
+38
-7
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -25,7 +26,7 @@ namespace Barotrauma
|
||||
{
|
||||
// If the target was selected as a valid target, we'll have to accept it so that the objective can be completed.
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
|
||||
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.
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
@@ -55,15 +56,13 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.ParentInventory != null) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
//var rootContainer = item.GetRootContainer();
|
||||
//// Only target items lying on the ground (= not inside a container) (do we need this check?)
|
||||
//if (rootContainer != null) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
if (pickable is Holdable h && h.Attachable && h.Attached) { return false; }
|
||||
@@ -80,7 +79,39 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return item.Prefab.PreferredContainers.Any();
|
||||
if (item.Prefab.PreferredContainers.None())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!checkInventory)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool canEquip = true;
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
canEquip = false;
|
||||
var inv = character.Inventory;
|
||||
foreach (var allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
foreach (var slotType in inv.SlotTypes)
|
||||
{
|
||||
if (allowedSlot.HasFlag(slotType))
|
||||
{
|
||||
for (int i = 0; i < inv.Capacity; i++)
|
||||
{
|
||||
canEquip = true;
|
||||
if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.Items[i] != null)
|
||||
{
|
||||
canEquip = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ namespace Barotrauma
|
||||
private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest;
|
||||
private bool TargetEliminated => Enemy == null || Enemy.Removed || Enemy.IsUnconscious;
|
||||
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
|
||||
private bool EnemyIsClose() => Enemy != null && character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500;
|
||||
private bool EnemyIsClose() => Enemy != null && character.CurrentHull != null && character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500;
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
|
||||
+22
-16
@@ -21,7 +21,8 @@ namespace Barotrauma
|
||||
//can either be a tag or an identifier
|
||||
public readonly string[] itemIdentifiers;
|
||||
public readonly ItemContainer container;
|
||||
public readonly Item item;
|
||||
private readonly Item item;
|
||||
public Item ItemToContain { get; private set; }
|
||||
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
@@ -30,7 +31,7 @@ namespace Barotrauma
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool AllowDangerousPressure { get; set; }
|
||||
public float ConditionLevel { get; set; }
|
||||
public float ConditionLevel { get; set; } = 1;
|
||||
public bool Equip { get; set; }
|
||||
public bool RemoveEmpty { get; set; } = true;
|
||||
|
||||
@@ -59,7 +60,7 @@ namespace Barotrauma
|
||||
protected override bool Check()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (container == null)
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI()))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
@@ -82,19 +83,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage > ConditionLevel;
|
||||
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel;
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (container == null)
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI()))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
Item itemToContain = item ?? character.Inventory.FindItem(i => CheckItem(i) && i.Container != container.Item, recursive: true);
|
||||
if (itemToContain != null)
|
||||
ItemToContain = item ?? character.Inventory.FindItem(i => CheckItem(i) && i.Container != container.Item, recursive: true);
|
||||
if (ItemToContain != null)
|
||||
{
|
||||
if (character.CanInteractWith(container.Item, out _, checkLinked: false))
|
||||
if (!character.CanInteractWith(ItemToContain, checkLinked: false))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (character.CanInteractWith(container.Item, checkLinked: false))
|
||||
{
|
||||
if (RemoveEmpty)
|
||||
{
|
||||
@@ -108,29 +114,29 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
// Contain the item
|
||||
if (itemToContain.ParentInventory == character.Inventory)
|
||||
if (ItemToContain.ParentInventory == character.Inventory)
|
||||
{
|
||||
if (!container.Inventory.CanBePut(itemToContain))
|
||||
if (!container.Inventory.CanBePut(ItemToContain))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Inventory.RemoveItem(itemToContain);
|
||||
if (container.Inventory.TryPutItem(itemToContain, null))
|
||||
character.Inventory.RemoveItem(ItemToContain);
|
||||
if (container.Inventory.TryPutItem(ItemToContain, null))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemToContain.Drop(character);
|
||||
ItemToContain.Drop(character);
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (container.Combine(itemToContain, character))
|
||||
if (container.Combine(ItemToContain, character))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
@@ -142,11 +148,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: should we just use GetItem?
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name
|
||||
TargetName = container.Item.Name,
|
||||
abortCondition = () => !ItemToContain.IsOwnedBy(character)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+19
-23
@@ -78,12 +78,21 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
return new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
|
||||
if (!character.HasEquippedItem("fireextinguisher", allowBroken: false))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
}
|
||||
var getItemObjective = new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
|
||||
{
|
||||
AllowStealing = true,
|
||||
// If the item is inside an unsafe hull, decrease the priority
|
||||
GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
|
||||
};
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>())
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher"), null, 0.0f, "dialogcannotfindfireextinguisher", 10.0f);
|
||||
};
|
||||
return getItemObjective;
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -99,9 +108,12 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (FireSource fs in targetHull.FireSources)
|
||||
{
|
||||
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
||||
bool move = !inRange || !HumanAIController.VisibleHulls.Contains(fs.Hull);
|
||||
if (inRange || useExtinquisherTimer > 0.0f)
|
||||
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X) - fs.DamageRange;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - fs.WorldPosition.Y);
|
||||
bool inRange = xDist + yDist < extinguisher.Range;
|
||||
bool canSee = HumanAIController.VisibleHulls.Contains(fs.Hull) || character.CanSeeTarget(fs);
|
||||
bool move = !inRange || !canSee;
|
||||
if ((inRange && canSee) || useExtinquisherTimer > 0)
|
||||
{
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
@@ -115,19 +127,7 @@ namespace Barotrauma
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
|
||||
if (extinguisherItem.RequireAimToUse)
|
||||
{
|
||||
bool isOperatingButtons = false;
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
}
|
||||
if (!isOperatingButtons)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
sinTime += deltaTime * 10;
|
||||
}
|
||||
character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
|
||||
@@ -136,15 +136,11 @@ namespace Barotrauma
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, true), null, 0, "putoutfire", 10.0f);
|
||||
}
|
||||
if (!character.CanSeeTarget(fs))
|
||||
{
|
||||
move = true;
|
||||
}
|
||||
}
|
||||
if (move)
|
||||
{
|
||||
//go to the first firesource
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: Math.Max(fs.DamageRange, extinguisher.Range * 0.7f))
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire",
|
||||
TargetName = fs.Hull.DisplayName
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Hull hull, Character character)
|
||||
{
|
||||
if (hull == null) { return false; }
|
||||
if (hull.IgnoreByAI) { return false; }
|
||||
if (hull.FireSources.None()) { return false; }
|
||||
if (hull.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
|
||||
+2
-2
@@ -46,6 +46,7 @@ namespace Barotrauma
|
||||
}
|
||||
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
|
||||
{
|
||||
AllowStealing = true,
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true
|
||||
};
|
||||
@@ -85,8 +86,7 @@ namespace Barotrauma
|
||||
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
ConditionLevel = 0
|
||||
AllowDangerousPressure = true
|
||||
};
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.Objectives.Any(o => o is AIObjectiveCombat)) && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+8
-1
@@ -65,7 +65,14 @@ namespace Barotrauma
|
||||
if (weldingTool == null)
|
||||
{
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
|
||||
onAbandon: () => Abandon = true,
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcannotfindweldingequipment"), null, 0.0f, "dialogcannotfindweldingequipment", 10.0f);
|
||||
}
|
||||
Abandon = true;
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref getWeldingTool));
|
||||
return;
|
||||
}
|
||||
|
||||
+2
@@ -71,6 +71,8 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Gap gap, Character character)
|
||||
{
|
||||
if (gap == null) { return false; }
|
||||
// Don't fix a leak on a wall section set to be ignored
|
||||
if (gap.ConnectedWall?.Sections?.Any(s => s.gap == gap && s.IgnoreByAI) ?? false) { return false; }
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null || character.Submarine == null) { return false; }
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
|
||||
+14
-13
@@ -18,13 +18,13 @@ namespace Barotrauma
|
||||
public float TargetCondition { get; set; } = 1;
|
||||
public bool AllowDangerousPressure { get; set; }
|
||||
|
||||
private string[] identifiersOrTags;
|
||||
private readonly string[] identifiersOrTags;
|
||||
|
||||
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
|
||||
private bool spawnItemIfNotFound = false;
|
||||
|
||||
private Item targetItem;
|
||||
private Item originalTarget;
|
||||
private readonly Item originalTarget;
|
||||
private ISpatialEntity moveToTarget;
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
@@ -32,13 +32,18 @@ namespace Barotrauma
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private bool checkInventory;
|
||||
private readonly bool checkInventory;
|
||||
|
||||
public static float DefaultReach = 100;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool MustBeSpecificItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the character allowed to take the item from somewhere else than their own sub (e.g. an outpost)
|
||||
/// </summary>
|
||||
public bool AllowStealing { get; set; }
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
@@ -246,9 +251,13 @@ namespace Barotrauma
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||
Submarine mySub = character.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
Submarine mySub = character.Submarine;
|
||||
if (mySub == null) { continue; }
|
||||
if (!AllowStealing)
|
||||
{
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
|
||||
}
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
@@ -339,6 +348,7 @@ namespace Barotrauma
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsThisOrAnyContainerIgnoredByAI()) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
@@ -362,14 +372,5 @@ namespace Barotrauma
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (objectiveManager.CurrentOrder != null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCannotFindItem"), null, 0.0f, "cannotfinditem", 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -30,6 +30,8 @@ namespace Barotrauma
|
||||
public bool followControlledCharacter;
|
||||
public bool mimic;
|
||||
|
||||
public float extraDistanceWhileSwimming;
|
||||
public float extraDistanceOutsideSub;
|
||||
private float _closeEnough = 50;
|
||||
private readonly float minDistance = 50;
|
||||
/// <summary>
|
||||
@@ -37,7 +39,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public float CloseEnough
|
||||
{
|
||||
get { return _closeEnough; }
|
||||
get
|
||||
{
|
||||
float dist = _closeEnough;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
dist += extraDistanceWhileSwimming;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
dist += extraDistanceOutsideSub;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
set
|
||||
{
|
||||
_closeEnough = Math.Max(minDistance, value);
|
||||
|
||||
+26
-13
@@ -21,14 +21,13 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
behavior = value;
|
||||
if (behavior == BehaviorType.StayInHull && character.TeamID != Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
DebugConsole.NewMessage($"AIObjectiveIdle.BehaviorType.StayInHull is implemented only for outpost NPCs. Using passive behavior for {character.Name} ({character.Info.Job.Prefab.Identifier})", color: Color.Red);
|
||||
behavior = BehaviorType.Passive;
|
||||
}
|
||||
switch (behavior)
|
||||
{
|
||||
case BehaviorType.Active:
|
||||
newTargetIntervalMin = 10;
|
||||
newTargetIntervalMax = 20;
|
||||
standStillMin = 2;
|
||||
standStillMax = 10;
|
||||
break;
|
||||
case BehaviorType.Passive:
|
||||
case BehaviorType.StayInHull:
|
||||
newTargetIntervalMin = 60;
|
||||
@@ -36,6 +35,18 @@ namespace Barotrauma
|
||||
standStillMin = 30;
|
||||
standStillMax = 60;
|
||||
break;
|
||||
case BehaviorType.Active:
|
||||
newTargetIntervalMin = 40;
|
||||
newTargetIntervalMax = 60;
|
||||
standStillMin = 20;
|
||||
standStillMax = 40;
|
||||
break;
|
||||
case BehaviorType.Patrol:
|
||||
newTargetIntervalMin = 15;
|
||||
newTargetIntervalMax = 30;
|
||||
standStillMin = 5;
|
||||
standStillMax = 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,9 +60,10 @@ namespace Barotrauma
|
||||
|
||||
public enum BehaviorType
|
||||
{
|
||||
Active,
|
||||
Patrol,
|
||||
Passive,
|
||||
StayInHull
|
||||
StayInHull,
|
||||
Active
|
||||
}
|
||||
public Hull TargetHull { get; set; }
|
||||
private Hull currentTarget;
|
||||
@@ -307,14 +319,14 @@ namespace Barotrauma
|
||||
public void Wander(float deltaTime)
|
||||
{
|
||||
if (character.IsClimbing) { return; }
|
||||
if (!character.AnimController.InWater)
|
||||
var currentHull = character.CurrentHull;
|
||||
if (!character.AnimController.InWater && currentHull != null)
|
||||
{
|
||||
standStillTimer -= deltaTime;
|
||||
if (standStillTimer > 0.0f)
|
||||
{
|
||||
walkDuration = Rand.Range(walkDurationMin, walkDurationMax);
|
||||
var currentHull = character.CurrentHull;
|
||||
if (currentHull != null && currentHull.Rect.Width > IndoorsSteeringManager.smallRoomSize / 2 && tooCloseCharacter == null)
|
||||
if (currentHull.Rect.Width > IndoorsSteeringManager.smallRoomSize / 2 && tooCloseCharacter == null)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
@@ -434,10 +446,11 @@ namespace Barotrauma
|
||||
targetHulls.Add(hull);
|
||||
float weight = hull.RectWidth;
|
||||
// Prefer rooms that are closer. Avoid rooms that are not in the same level.
|
||||
// If the behavior is active, prefer rooms that are not close.
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 2500, dist));
|
||||
float distanceFactor = behavior == BehaviorType.Patrol ? MathHelper.Lerp(1, 0, MathUtils.InverseLerp(2500, 0, dist)) : MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 2500, dist));
|
||||
float waterFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 100, hull.WaterPercentage * 2));
|
||||
weight *= distanceFactor * waterFactor;
|
||||
hullWeights.Add(weight);
|
||||
@@ -474,7 +487,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character) && !ignoredItems.Contains(item))
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true) && !ignoredItems.Contains(item))
|
||||
{
|
||||
itemsToClean.Add(item);
|
||||
}
|
||||
|
||||
+6
-1
@@ -245,7 +245,10 @@ namespace Barotrauma
|
||||
public void SortObjectives()
|
||||
{
|
||||
CurrentOrder?.GetPriority();
|
||||
Objectives.ForEach(o => o.GetPriority());
|
||||
for (int i = Objectives.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Objectives[i].GetPriority();
|
||||
}
|
||||
if (Objectives.Any())
|
||||
{
|
||||
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
@@ -305,6 +308,8 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
CloseEnough = Rand.Range(90, 100) + Rand.Range(50, 70) * Math.Min(HumanAIController.CountCrew(c => c.ObjectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.Target == orderGiver, onlyBots: true), 4),
|
||||
extraDistanceOutsideSub = 100,
|
||||
extraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
followControlledCharacter = orderGiver == character,
|
||||
|
||||
+13
-4
@@ -157,11 +157,20 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
|
||||
if (objectiveManager.CurrentOrder != this && HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
// If this is not an order...
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
{
|
||||
// Don't abandon
|
||||
return;
|
||||
// Don't allow to operate an item that someone with a better skills already operates
|
||||
if (HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
{
|
||||
// Don't abandon
|
||||
return;
|
||||
}
|
||||
if (component.Item.IgnoreByAI || (useController && controller.Item.IgnoreByAI))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (operateTarget != null)
|
||||
{
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.IgnoreByAI) { return false; }
|
||||
if (pump.Item.NonInteractable) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
|
||||
+6
-1
@@ -90,7 +90,12 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
subObjectives.Add(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true));
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true);
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>())
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair"), null, 0.0f, "dialogcannotfindrequireditemtorepair", 10.0f);
|
||||
}
|
||||
subObjectives.Add(getItemObjective);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
+1
@@ -148,6 +148,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
|
||||
@@ -94,7 +94,9 @@ namespace Barotrauma
|
||||
|
||||
|
||||
//if true, the order is issued to all available characters
|
||||
public bool TargetAllCharacters;
|
||||
public bool TargetAllCharacters { get; }
|
||||
public bool IsReport => TargetAllCharacters && !MustSetTarget;
|
||||
|
||||
|
||||
public readonly float FadeOutTime;
|
||||
|
||||
@@ -132,11 +134,31 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (targetSpatialEntity == null) { targetSpatialEntity = TargetEntity ?? TargetPosition as ISpatialEntity; }
|
||||
if (targetSpatialEntity == null)
|
||||
{
|
||||
if (TargetType == OrderTargetType.WallSection && WallSectionIndex.HasValue)
|
||||
{
|
||||
targetSpatialEntity = (TargetEntity as Structure)?.Sections[WallSectionIndex.Value];
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSpatialEntity = TargetEntity ?? TargetPosition as ISpatialEntity;
|
||||
}
|
||||
}
|
||||
return targetSpatialEntity;
|
||||
}
|
||||
}
|
||||
|
||||
public enum OrderTargetType
|
||||
{
|
||||
Entity,
|
||||
Position,
|
||||
WallSection
|
||||
}
|
||||
public OrderTargetType TargetType { get; }
|
||||
public int? WallSectionIndex { get; }
|
||||
public bool IsIgnoreOrder { get; }
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
Prefabs = new Dictionary<string, Order>();
|
||||
@@ -292,6 +314,7 @@ namespace Barotrauma
|
||||
|
||||
IsPrefab = true;
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
IsIgnoreOrder = Identifier == "ignorethis" || Identifier == "unignorethis";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -299,7 +322,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Prefab = prefab.Prefab ?? prefab;
|
||||
|
||||
Name = prefab.Name;
|
||||
Identifier = prefab.Identifier;
|
||||
@@ -317,6 +340,7 @@ namespace Barotrauma
|
||||
AppropriateSkill = prefab.AppropriateSkill;
|
||||
Category = prefab.Category;
|
||||
MustManuallyAssign = prefab.MustManuallyAssign;
|
||||
IsIgnoreOrder = prefab.IsIgnoreOrder;
|
||||
|
||||
OrderGiver = orderGiver;
|
||||
TargetEntity = targetEntity;
|
||||
@@ -337,12 +361,21 @@ namespace Barotrauma
|
||||
TargetItemComponent = targetItem;
|
||||
}
|
||||
|
||||
TargetType = OrderTargetType.Entity;
|
||||
|
||||
IsPrefab = false;
|
||||
}
|
||||
|
||||
public Order(Order prefab, OrderTarget target, Character orderGiver = null) : this(prefab, targetEntity: null, targetItem: null, orderGiver)
|
||||
{
|
||||
TargetPosition = target;
|
||||
TargetType = OrderTargetType.Position;
|
||||
}
|
||||
|
||||
public Order(Order prefab, Structure wall, int? sectionIndex, Character orderGiver = null) : this(prefab, targetEntity: wall, null, orderGiver: orderGiver)
|
||||
{
|
||||
WallSectionIndex = sectionIndex;
|
||||
TargetType = OrderTargetType.WallSection;
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
|
||||
@@ -7,41 +7,32 @@ namespace Barotrauma
|
||||
{
|
||||
class PathNode
|
||||
{
|
||||
private readonly int wayPointID;
|
||||
|
||||
public int state;
|
||||
|
||||
public PathNode Parent;
|
||||
|
||||
private Vector2 position;
|
||||
|
||||
public float F, G, H;
|
||||
|
||||
public List<PathNode> connections;
|
||||
public readonly List<PathNode> connections = new List<PathNode>();
|
||||
public List<float> distances;
|
||||
|
||||
public Vector2 TempPosition;
|
||||
public float TempDistance;
|
||||
|
||||
public WayPoint Waypoint { get; private set; }
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return position; }
|
||||
}
|
||||
public readonly WayPoint Waypoint;
|
||||
public readonly Vector2 Position;
|
||||
public readonly int WayPointID;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"PathNode {wayPointID}";
|
||||
return $"PathNode {WayPointID}";
|
||||
}
|
||||
|
||||
public PathNode(WayPoint wayPoint)
|
||||
{
|
||||
this.Waypoint = wayPoint;
|
||||
this.position = wayPoint.SimPosition;
|
||||
wayPointID = wayPoint.ID;
|
||||
|
||||
connections = new List<PathNode>();
|
||||
Waypoint = wayPoint;
|
||||
Position = wayPoint.SimPosition;
|
||||
WayPointID = Waypoint.ID;
|
||||
}
|
||||
|
||||
public static List<PathNode> GenerateNodes(List<WayPoint> wayPoints)
|
||||
@@ -78,7 +69,7 @@ namespace Barotrauma
|
||||
node.distances = new List<float>();
|
||||
for (int i = 0; i < node.connections.Count; i++)
|
||||
{
|
||||
node.distances.Add(Vector2.Distance(node.position, node.connections[i].position));
|
||||
node.distances.Add(Vector2.Distance(node.Position, node.connections[i].Position));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -404,13 +404,19 @@ namespace Barotrauma
|
||||
if (petBehavior != null)
|
||||
{
|
||||
petBehavior.Owner = owner;
|
||||
var petBehaviorElement = subElement.Attribute("petbehavior");
|
||||
var petBehaviorElement = subElement.Element("petbehavior");
|
||||
if (petBehaviorElement != null)
|
||||
{
|
||||
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat(50.0f);
|
||||
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat(50.0f);
|
||||
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat("hunger", 50.0f);
|
||||
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat("happiness", 50.0f);
|
||||
}
|
||||
}
|
||||
|
||||
var inventoryElement = subElement.Element("inventory");
|
||||
if (inventoryElement != null)
|
||||
{
|
||||
pet.SpawnInventoryItems(pet.Inventory, inventoryElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace Barotrauma
|
||||
{
|
||||
get { return aiController; }
|
||||
}
|
||||
|
||||
|
||||
public AICharacter(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isNetworkPlayer = false, RagdollParams ragdoll = null)
|
||||
: base(speciesName, position, seed, characterInfo, isNetworkPlayer, ragdoll)
|
||||
: base(speciesName, position, seed, characterInfo, id: Entity.NullEntityID, isRemotePlayer: isNetworkPlayer, ragdollParams: ragdoll)
|
||||
{
|
||||
InitProjSpecific();
|
||||
}
|
||||
@@ -62,21 +62,12 @@ namespace Barotrauma
|
||||
|
||||
if (!IsRemotePlayer && !(AIController is HumanAIController))
|
||||
{
|
||||
float characterDist = float.MaxValue;
|
||||
#if CLIENT
|
||||
characterDist = Vector2.DistanceSquared(cam.GetPosition(), WorldPosition);
|
||||
#elif SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
characterDist = GetClosestDistance();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (characterDist > EnableSimplePhysicsDistSqr)
|
||||
float characterDistSqr = GetDistanceSqrToClosestPlayer();
|
||||
if (characterDistSqr > EnableSimplePhysicsDistSqr)
|
||||
{
|
||||
AnimController.SimplePhysicsEnabled = true;
|
||||
}
|
||||
else if (characterDist < DisableSimplePhysicsDistSqr)
|
||||
else if (characterDistSqr < DisableSimplePhysicsDistSqr)
|
||||
{
|
||||
AnimController.SimplePhysicsEnabled = false;
|
||||
}
|
||||
@@ -90,50 +81,5 @@ namespace Barotrauma
|
||||
aiController.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
// Gets the closest distance, either an active player character or spectator
|
||||
private float GetClosestDistance()
|
||||
{
|
||||
float minDist = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < GameMain.Server.ConnectedClients.Count; i++)
|
||||
{
|
||||
var spectatePos = GameMain.Server.ConnectedClients[i].SpectatePos;
|
||||
if (spectatePos != null)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(spectatePos.Value, WorldPosition);
|
||||
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
}
|
||||
if (dist < DisableSimplePhysicsDistSqr)
|
||||
{
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character c in CharacterList)
|
||||
{
|
||||
if (c != this && c.IsRemotePlayer)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(c.WorldPosition, WorldPosition);
|
||||
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
}
|
||||
if (dist < DisableSimplePhysicsDistSqr)
|
||||
{
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minDist;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,8 +407,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (CurrentSwimParams == null) { return; }
|
||||
movement = TargetMovement;
|
||||
|
||||
if (movement.LengthSquared() > 0.00001f)
|
||||
bool isMoving = movement.LengthSquared() > 0.00001f;
|
||||
if (isMoving)
|
||||
{
|
||||
float t = 0.5f;
|
||||
if (CurrentSwimParams.RotateTowardsMovement && VectorExtensions.Angle(VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2), movement) > MathHelper.PiOver2)
|
||||
@@ -425,7 +425,7 @@ namespace Barotrauma
|
||||
mainLimb.PullJointEnabled = true;
|
||||
//mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
|
||||
if (movement.LengthSquared() < 0.00001f)
|
||||
if (!isMoving)
|
||||
{
|
||||
WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
|
||||
mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
@@ -625,7 +625,8 @@ namespace Barotrauma
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
|
||||
float movementFactor = Math.Max(character.AnimController.Collider.LinearVelocity.Length() * 0.5f, 1);
|
||||
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque * movementFactor, wrapAngle: true);
|
||||
}
|
||||
if (limb.Params.BlinkFrequency > 0)
|
||||
{
|
||||
|
||||
+14
-9
@@ -1974,9 +1974,6 @@ namespace Barotrauma
|
||||
|
||||
public override void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
var rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
useItemTimer = 0.5f;
|
||||
Anim = Animation.UsingConstruction;
|
||||
|
||||
@@ -1999,13 +1996,21 @@ namespace Barotrauma
|
||||
handSimPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
|
||||
leftHand.Disabled = true;
|
||||
leftHand.PullJointEnabled = true;
|
||||
leftHand.PullJointWorldAnchorB = handSimPos;
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
if (leftHand != null)
|
||||
{
|
||||
leftHand.Disabled = true;
|
||||
leftHand.PullJointEnabled = true;
|
||||
leftHand.PullJointWorldAnchorB = handSimPos;
|
||||
}
|
||||
|
||||
rightHand.Disabled = true;
|
||||
rightHand.PullJointEnabled = true;
|
||||
rightHand.PullJointWorldAnchorB = handSimPos;
|
||||
var rightHand = GetLimb(LimbType.RightHand);
|
||||
if (rightHand != null)
|
||||
{
|
||||
rightHand.Disabled = true;
|
||||
rightHand.PullJointEnabled = true;
|
||||
rightHand.PullJointWorldAnchorB = handSimPos;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flip()
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.ignoreCollisions;
|
||||
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.IgnoreCollisions && !limb.Hidden;
|
||||
return mainLimb;
|
||||
}
|
||||
}
|
||||
@@ -1060,7 +1060,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.ignoreCollisions || limb.IsSevered) { continue; }
|
||||
if (limb.IgnoreCollisions || limb.IsSevered) { continue; }
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1562,7 +1562,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true)
|
||||
public void SetPosition(Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true, bool forceMainLimbToCollider = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(simPosition))
|
||||
{
|
||||
@@ -1575,8 +1575,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (MainLimb == null) { return; }
|
||||
|
||||
Vector2 limbMoveAmount = simPosition - Collider.SimPosition;
|
||||
|
||||
Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
|
||||
if (lerp)
|
||||
{
|
||||
Collider.TargetPosition = simPosition;
|
||||
@@ -1587,13 +1586,15 @@ namespace Barotrauma
|
||||
Collider.SetTransform(simPosition, Collider.Rotation);
|
||||
}
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
if (!MathUtils.NearlyEqual(limbMoveAmount, Vector2.Zero))
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
//check visibility from the new position of the collider to the new position of this limb
|
||||
Vector2 movePos = limb.SimPosition + limbMoveAmount;
|
||||
|
||||
TrySetLimbPosition(limb, simPosition, movePos, lerp, ignorePlatforms);
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
//check visibility from the new position of the collider to the new position of this limb
|
||||
Vector2 movePos = limb.SimPosition + limbMoveAmount;
|
||||
TrySetLimbPosition(limb, simPosition, movePos, lerp, ignorePlatforms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1634,7 +1635,8 @@ namespace Barotrauma
|
||||
|
||||
protected void CheckDistFromCollider()
|
||||
{
|
||||
float allowedDist = Math.Max(Math.Max(Collider.radius, Collider.width), Collider.height) * 2.0f;
|
||||
float allowedDist = Math.Max(Math.Max(Collider.radius, Collider.width), Collider.height) * 2.0f;
|
||||
allowedDist = Math.Max(allowedDist, 1.0f);
|
||||
float resetDist = allowedDist * 5.0f;
|
||||
|
||||
Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
|
||||
@@ -1643,7 +1645,7 @@ namespace Barotrauma
|
||||
if (distSqrd > resetDist * resetDist)
|
||||
{
|
||||
//ragdoll way too far, reset position
|
||||
SetPosition(Collider.SimPosition, true);
|
||||
SetPosition(Collider.SimPosition, true, forceMainLimbToCollider: true);
|
||||
}
|
||||
if (distSqrd > allowedDist * allowedDist)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace Barotrauma
|
||||
PursueIfCanAttack,
|
||||
Pursue,
|
||||
FollowThrough,
|
||||
FollowThroughUntilCanAttack
|
||||
FollowThroughUntilCanAttack,
|
||||
IdleUntilCanAttack
|
||||
}
|
||||
|
||||
struct AttackResult
|
||||
@@ -117,12 +118,27 @@ namespace Barotrauma
|
||||
[Serialize(0f, true, description: "A random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases)."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
|
||||
public float CoolDownRandomFactor { get; private set; } = 0;
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool FullSpeedAfterAttack { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
|
||||
public float StructureDamage { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float ItemDamage { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float LevelWallDamage { get; set; }
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool Ranged { get; set; }
|
||||
|
||||
[Serialize(false, true, description:"Only affects ranged attacks.")]
|
||||
public bool AvoidFriendlyFire { get; set; }
|
||||
|
||||
[Serialize(20f, true)]
|
||||
public float RequiredAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Legacy support. Use Afflictions.
|
||||
/// </summary>
|
||||
@@ -247,6 +263,11 @@ namespace Barotrauma
|
||||
return (Duration == 0.0f) ? StructureDamage : StructureDamage * deltaTime;
|
||||
}
|
||||
|
||||
public float GetLevelWallDamage(float deltaTime)
|
||||
{
|
||||
return (Duration == 0.0f) ? LevelWallDamage : LevelWallDamage * deltaTime;
|
||||
}
|
||||
|
||||
public float GetItemDamage(float deltaTime)
|
||||
{
|
||||
return (Duration == 0.0f) ? ItemDamage : ItemDamage * deltaTime;
|
||||
@@ -270,7 +291,7 @@ namespace Barotrauma
|
||||
|
||||
Range = range;
|
||||
DamageRange = range;
|
||||
StructureDamage = structureDamage;
|
||||
StructureDamage = LevelWallDamage = structureDamage;
|
||||
ItemDamage = itemDamage;
|
||||
}
|
||||
|
||||
@@ -286,6 +307,13 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. <Affliction identifier=\"internaldamage\" strength=\"10\" />).");
|
||||
}
|
||||
|
||||
//if level wall damage is not defined, default to the structure damage
|
||||
if (element.Attribute("LevelWallDamage") == null &&
|
||||
element.Attribute("levelwalldamage") == null)
|
||||
{
|
||||
LevelWallDamage = StructureDamage;
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
@@ -379,7 +407,7 @@ namespace Barotrauma
|
||||
ReloadAfflictions(element);
|
||||
}
|
||||
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
|
||||
{
|
||||
Character targetCharacter = target as Character;
|
||||
if (OnlyHumans)
|
||||
@@ -403,6 +431,7 @@ 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))
|
||||
{
|
||||
@@ -423,14 +452,18 @@ namespace Barotrauma
|
||||
effect.Apply(effectType, deltaTime, targetCharacter, targetCharacter.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
}
|
||||
if (target is Entity entity)
|
||||
if (target is Entity targetEntity)
|
||||
{
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, deltaTime, entity, targets);
|
||||
effect.Apply(effectType, deltaTime, targetEntity, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetEntity, attacker, worldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,7 +471,7 @@ namespace Barotrauma
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
|
||||
{
|
||||
if (targetLimb == null)
|
||||
{
|
||||
@@ -462,6 +495,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.sourceBody = sourceBody;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
@@ -478,6 +512,17 @@ namespace Barotrauma
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, attacker, worldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
return attackResult;
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool IsRemotePlayer { get; set; }
|
||||
|
||||
public bool IsLocalPlayer => Controlled == this;
|
||||
public bool IsPlayer => Controlled == this || IsRemotePlayer;
|
||||
public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled;
|
||||
|
||||
@@ -314,6 +315,7 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
hideFaceTimer = MathHelper.Clamp(hideFaceTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
|
||||
if (info != null && info.IsDisguisedAsAnother != HideFace) info.CheckDisguiseStatus(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,9 +746,9 @@ namespace Barotrauma
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null)
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null)
|
||||
{
|
||||
return Create(characterInfo.SpeciesName, position, seed, characterInfo, isRemotePlayer, hasAi, true, ragdoll);
|
||||
return Create(characterInfo.SpeciesName, position, seed, characterInfo, id, isRemotePlayer, hasAi, true, ragdoll);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -756,11 +758,12 @@ namespace Barotrauma
|
||||
/// <param name="position">Position in display units.</param>
|
||||
/// <param name="seed">RNG seed to use if the character config has randomizable parameters.</param>
|
||||
/// <param name="characterInfo">The name, gender, etc of the character. Only used for humans, and if the parameter is not given, a random CharacterInfo is generated.</param>
|
||||
/// <param name="id">ID to assign to the character. If set to 0, automatically find an available ID.</param>
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="createNetworkEvent">Should clients receive a network event about the creation of this character?</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null)
|
||||
public static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null)
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -790,7 +793,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
newCharacter = new Character(speciesName, position, seed, characterInfo, isRemotePlayer, ragdoll);
|
||||
newCharacter = new Character(speciesName, position, seed, characterInfo, id: id, isRemotePlayer: isRemotePlayer, ragdollParams: ragdoll);
|
||||
}
|
||||
|
||||
float healthRegen = newCharacter.Params.Health.ConstantHealthRegeneration;
|
||||
@@ -830,8 +833,8 @@ namespace Barotrauma
|
||||
return newCharacter;
|
||||
}
|
||||
|
||||
protected Character(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
|
||||
: base(null)
|
||||
protected Character(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
|
||||
: base(null, id)
|
||||
{
|
||||
prefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
|
||||
@@ -1468,7 +1471,7 @@ namespace Barotrauma
|
||||
AnimController.ReleaseStuckLimbs();
|
||||
if (AIController != null && AIController is EnemyAIController enemyAI)
|
||||
{
|
||||
enemyAI.LatchOntoAI?.DeattachFromBody();
|
||||
enemyAI.LatchOntoAI?.DeattachFromBody(reset: true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1524,6 +1527,7 @@ namespace Barotrauma
|
||||
var validLimbs = AnimController.Limbs.Where(l =>
|
||||
{
|
||||
if (l.IsSevered || l.IsStuck) { return false; }
|
||||
if (l.Disabled) { return false; }
|
||||
var attack = l.attack;
|
||||
if (attack == null) { return false; }
|
||||
if (attack.CoolDownTimer > 0) { return false; }
|
||||
@@ -1636,6 +1640,7 @@ namespace Barotrauma
|
||||
foreach (Limb limb in target.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || limb == target.AnimController.MainLimb) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
Vector2 limbDir = limb.WorldPosition - WorldPosition;
|
||||
float leftDot = Vector2.Dot(limbDir, leftDir);
|
||||
if (leftDot > leftMostDot)
|
||||
@@ -1906,9 +1911,9 @@ namespace Barotrauma
|
||||
return checkVisibility ? CanSeeCharacter(c) : true;
|
||||
}
|
||||
|
||||
public bool CanInteractWith(Item item)
|
||||
public bool CanInteractWith(Item item, bool checkLinked = true)
|
||||
{
|
||||
return CanInteractWith(item, out _, checkLinked: true);
|
||||
return CanInteractWith(item, out _, checkLinked);
|
||||
}
|
||||
|
||||
public bool CanInteractWith(Item item, out float distanceToItem, bool checkLinked)
|
||||
@@ -1998,7 +2003,7 @@ namespace Barotrauma
|
||||
distanceToItem = Vector2.Distance(rectIntersectionPoint, playerDistanceCheckPosition);
|
||||
}
|
||||
|
||||
if (distanceToItem > item.InteractDistance && item.InteractDistance > 0.0f) return false;
|
||||
if (distanceToItem > item.InteractDistance && item.InteractDistance > 0.0f) { return false; }
|
||||
|
||||
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
|
||||
{
|
||||
@@ -2019,8 +2024,8 @@ namespace Barotrauma
|
||||
itemPosition += item.Submarine.SimPosition;
|
||||
itemPosition -= Submarine.SimPosition;
|
||||
}
|
||||
var body = Submarine.CheckVisibility(SimPosition, itemPosition, true);
|
||||
if (body != null && body.UserData as Item != item) return false;
|
||||
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
|
||||
if (body != null && body.UserData as Item != item) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2549,21 +2554,30 @@ namespace Barotrauma
|
||||
}
|
||||
OxygenAvailable += MathHelper.Clamp(hullAvailableOxygen - oxygenAvailable, -deltaTime * 50.0f, deltaTime * 50.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial void UpdateOxygenProjSpecific(float prevOxygen);
|
||||
|
||||
/// <summary>
|
||||
/// How far the character is from the closest human player (including spectators)
|
||||
/// </summary>
|
||||
private float GetDistanceToClosestPlayer()
|
||||
protected float GetDistanceToClosestPlayer()
|
||||
{
|
||||
return (float)Math.Sqrt(GetDistanceSqrToClosestPlayer());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How far the character is from the closest human player (including spectators)
|
||||
/// </summary>
|
||||
protected float GetDistanceSqrToClosestPlayer()
|
||||
{
|
||||
float distSqr = float.MaxValue;
|
||||
foreach (Character otherCharacter in CharacterList)
|
||||
{
|
||||
if (otherCharacter == this || !otherCharacter.IsRemotePlayer) { continue; }
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(otherCharacter.WorldPosition, WorldPosition));
|
||||
if (otherCharacter.ViewTarget != null)
|
||||
{
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(otherCharacter.ViewTarget.WorldPosition, WorldPosition));
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
for (int i = 0; i < GameMain.Server.ConnectedClients.Count; i++)
|
||||
@@ -2582,7 +2596,7 @@ namespace Barotrauma
|
||||
}
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(GameMain.GameScreen.Cam.Position, WorldPosition));
|
||||
#endif
|
||||
return (float)Math.Sqrt(distSqr);
|
||||
return distSqr;
|
||||
}
|
||||
|
||||
private float despawnTimer;
|
||||
@@ -3430,6 +3444,75 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SpawnInventoryItems(Inventory inventory, XElement itemData)
|
||||
{
|
||||
SpawnInventoryItemsRecursive(inventory, itemData);
|
||||
}
|
||||
|
||||
private void SpawnInventoryItemsRecursive(Inventory inventory, XElement element)
|
||||
{
|
||||
foreach (XElement itemElement in element.Elements())
|
||||
{
|
||||
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true, idRemap: IdRemap.DiscardId);
|
||||
if (newItem == null) { continue; }
|
||||
|
||||
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
|
||||
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
#if SERVER
|
||||
newItem.GetComponent<Terminal>()?.SyncHistory();
|
||||
#endif
|
||||
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
|
||||
if (!slotIndices.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure there's no other item in the slot
|
||||
//this should not happen normally, but can occur if the character is accidentally given new job items while also loading previous items in the campaign
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i) && inventory.Items[i] != null && inventory.Items[i] != newItem)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading character inventory data. The slot {i} was already occupied by the item \"{inventory.Items[i].Name} ({inventory.Items[i].ID})\" when loading the item \"{newItem.Name} ({newItem.ID})\"");
|
||||
inventory.Items[i].Drop(null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
|
||||
newItem.ParentInventory = inventory;
|
||||
|
||||
//force the item to the correct slots
|
||||
// e.g. putting the item in a hand slot will also put it in the first available Any-slot,
|
||||
// which may not be where it actually was
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i))
|
||||
{
|
||||
inventory.Items[i] = newItem;
|
||||
}
|
||||
else if (inventory.Items[i] == newItem)
|
||||
{
|
||||
inventory.Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int itemContainerIndex = 0;
|
||||
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
|
||||
foreach (XElement childInvElement in itemElement.Elements())
|
||||
{
|
||||
if (itemContainerIndex >= itemContainers.Count) break;
|
||||
if (!childInvElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement);
|
||||
itemContainerIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly HashSet<AttackContext> currentContexts = new HashSet<AttackContext>();
|
||||
|
||||
public IEnumerable<AttackContext> GetAttackContexts()
|
||||
|
||||
@@ -160,10 +160,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character == null || !Character.HideFace)
|
||||
{
|
||||
IsDisguised = IsDisguisedAsAnother = false;
|
||||
return Name;
|
||||
}
|
||||
else if ((GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowDisguises))
|
||||
{
|
||||
IsDisguised = IsDisguisedAsAnother = false;
|
||||
return Name;
|
||||
}
|
||||
|
||||
@@ -263,6 +265,62 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDisguised = false;
|
||||
public bool IsDisguisedAsAnother = false;
|
||||
|
||||
public void CheckDisguiseStatus(bool handleBuff, IdCard idCard = null)
|
||||
{
|
||||
if (Character == null) { return; }
|
||||
|
||||
string currentlyDisplayedName = DisplayName;
|
||||
|
||||
IsDisguised = currentlyDisplayedName == disguiseName;
|
||||
IsDisguisedAsAnother = !IsDisguised && currentlyDisplayedName != Name;
|
||||
|
||||
if (IsDisguisedAsAnother)
|
||||
{
|
||||
if (handleBuff)
|
||||
{
|
||||
Character.CharacterHealth.ApplyAffliction(Character.AnimController.GetLimb(LimbType.Head), AfflictionPrefab.List.FirstOrDefault(a => a.Identifier.Equals("disguised", StringComparison.OrdinalIgnoreCase)).Instantiate(100f));
|
||||
}
|
||||
|
||||
if (idCard != null)
|
||||
{
|
||||
#if CLIENT
|
||||
GetDisguisedSprites(idCard);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (Character.Inventory != null)
|
||||
{
|
||||
int cardSlotIndex = Character.Inventory.FindLimbSlot(InvSlotType.Card);
|
||||
if (cardSlotIndex >= 0)
|
||||
{
|
||||
idCard = Character.Inventory.Items[cardSlotIndex].GetComponent<IdCard>();
|
||||
|
||||
if (idCard != null)
|
||||
{
|
||||
#if CLIENT
|
||||
GetDisguisedSprites(idCard);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
disguisedJobIcon = null;
|
||||
disguisedPortrait = null;
|
||||
#endif
|
||||
|
||||
if (handleBuff)
|
||||
{
|
||||
Character.CharacterHealth.ReduceAffliction(Character.AnimController.GetLimb(LimbType.Head), "disguised", 100f);
|
||||
}
|
||||
}
|
||||
|
||||
private List<WearableSprite> attachmentSprites;
|
||||
public List<WearableSprite> AttachmentSprites
|
||||
{
|
||||
@@ -582,7 +640,7 @@ namespace Barotrauma
|
||||
return id;
|
||||
}
|
||||
|
||||
public IEnumerable<XElement> FilterByTypeAndHeadID(IEnumerable<XElement> elements, WearableType targetType)
|
||||
public IEnumerable<XElement> FilterByTypeAndHeadID(IEnumerable<XElement> elements, WearableType targetType, int headSpriteId)
|
||||
{
|
||||
if (elements == null) { return elements; }
|
||||
return elements.Where(e =>
|
||||
@@ -590,16 +648,16 @@ namespace Barotrauma
|
||||
if (Enum.TryParse(e.GetAttributeString("type", ""), true, out WearableType type) && type != targetType) { return false; }
|
||||
int headId = e.GetAttributeInt("headid", -1);
|
||||
// if the head id is less than 1, the id is not valid and the condition is ignored.
|
||||
return headId < 1 || headId == Head.HeadSpriteId;
|
||||
return headId < 1 || headId == headSpriteId;
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<XElement> FilterElementsByGenderAndRace(IEnumerable<XElement> elements)
|
||||
public IEnumerable<XElement> FilterElementsByGenderAndRace(IEnumerable<XElement> elements, Gender gender, Race race)
|
||||
{
|
||||
if (elements == null) { return elements; }
|
||||
return elements.Where(w =>
|
||||
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == Head.gender &&
|
||||
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == Head.race);
|
||||
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == gender &&
|
||||
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == race);
|
||||
}
|
||||
|
||||
private void LoadHeadPresets()
|
||||
@@ -639,7 +697,7 @@ namespace Barotrauma
|
||||
{
|
||||
var wearableElements = Wearables;
|
||||
if (wearableElements == null) { return; }
|
||||
var wearables = FilterElementsByGenderAndRace(wearableElements).ToList();
|
||||
var wearables = FilterElementsByGenderAndRace(wearableElements, head.gender, head.race).ToList();
|
||||
if (wearables == null)
|
||||
{
|
||||
Head.headSpriteRange = Vector2.Zero;
|
||||
@@ -739,19 +797,19 @@ namespace Barotrauma
|
||||
if (hairs == null)
|
||||
{
|
||||
float commonness = Gender == Gender.Female ? 0.05f : 0.2f;
|
||||
hairs = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.Hair), WearableType.Hair, commonness);
|
||||
hairs = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.Hair, head.HeadSpriteId), WearableType.Hair, commonness);
|
||||
}
|
||||
if (beards == null)
|
||||
{
|
||||
beards = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.Beard), WearableType.Beard);
|
||||
beards = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.Beard, head.HeadSpriteId), WearableType.Beard);
|
||||
}
|
||||
if (moustaches == null)
|
||||
{
|
||||
moustaches = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.Moustache), WearableType.Moustache);
|
||||
moustaches = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.Moustache, head.HeadSpriteId), WearableType.Moustache);
|
||||
}
|
||||
if (faceAttachments == null)
|
||||
{
|
||||
faceAttachments = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.FaceAttachment), WearableType.FaceAttachment);
|
||||
faceAttachments = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.FaceAttachment, head.HeadSpriteId), WearableType.FaceAttachment);
|
||||
}
|
||||
|
||||
if (IsValidIndex(Head.HairIndex, hairs))
|
||||
@@ -790,49 +848,49 @@ namespace Barotrauma
|
||||
Head.FaceAttachment = GetRandomElement(faceAttachments);
|
||||
Head.FaceAttachmentIndex = faceAttachments.IndexOf(Head.FaceAttachment);
|
||||
}
|
||||
|
||||
static List<XElement> AddEmpty(IEnumerable<XElement> elements, WearableType type, float commonness = 1)
|
||||
{
|
||||
// Let's add an empty element so that there's a chance that we don't get any actual element -> allows bald and beardless guys, for example.
|
||||
var emptyElement = new XElement("EmptyWearable", type.ToString(), new XAttribute("commonness", commonness));
|
||||
var list = new List<XElement>() { emptyElement };
|
||||
list.AddRange(elements);
|
||||
return list;
|
||||
}
|
||||
|
||||
XElement GetRandomElement(IEnumerable<XElement> elements)
|
||||
{
|
||||
var filtered = elements.Where(e => IsWearableAllowed(e));
|
||||
if (filtered.Count() == 0) { return null; }
|
||||
var element = ToolBox.SelectWeightedRandom(filtered.ToList(), GetWeights(filtered).ToList(), Rand.RandSync.Unsynced);
|
||||
return element == null || element.Name == "Empty" ? null : element;
|
||||
}
|
||||
|
||||
bool IsWearableAllowed(XElement element)
|
||||
{
|
||||
string spriteName = element.Element("sprite").GetAttributeString("name", string.Empty);
|
||||
return IsAllowed(Head.HairElement, spriteName) && IsAllowed(Head.BeardElement, spriteName) && IsAllowed(Head.MoustacheElement, spriteName) && IsAllowed(Head.FaceAttachment, spriteName);
|
||||
}
|
||||
|
||||
bool IsAllowed(XElement element, string spriteName)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
var disallowed = element.GetAttributeStringArray("disallow", new string[0]);
|
||||
if (disallowed.Any(s => spriteName.Contains(s)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidIndex(int index, List<XElement> list) => index >= 0 && index < list.Count;
|
||||
|
||||
static IEnumerable<float> GetWeights(IEnumerable<XElement> elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<XElement> AddEmpty(IEnumerable<XElement> elements, WearableType type, float commonness = 1)
|
||||
{
|
||||
// Let's add an empty element so that there's a chance that we don't get any actual element -> allows bald and beardless guys, for example.
|
||||
var emptyElement = new XElement("EmptyWearable", type.ToString(), new XAttribute("commonness", commonness));
|
||||
var list = new List<XElement>() { emptyElement };
|
||||
list.AddRange(elements);
|
||||
return list;
|
||||
}
|
||||
|
||||
private XElement GetRandomElement(IEnumerable<XElement> elements)
|
||||
{
|
||||
var filtered = elements.Where(e => IsWearableAllowed(e));
|
||||
if (filtered.Count() == 0) { return null; }
|
||||
var element = ToolBox.SelectWeightedRandom(filtered.ToList(), GetWeights(filtered).ToList(), Rand.RandSync.Unsynced);
|
||||
return element == null || element.Name == "Empty" ? null : element;
|
||||
}
|
||||
|
||||
private bool IsWearableAllowed(XElement element)
|
||||
{
|
||||
string spriteName = element.Element("sprite").GetAttributeString("name", string.Empty);
|
||||
return IsAllowed(Head.HairElement, spriteName) && IsAllowed(Head.BeardElement, spriteName) && IsAllowed(Head.MoustacheElement, spriteName) && IsAllowed(Head.FaceAttachment, spriteName);
|
||||
}
|
||||
|
||||
private bool IsAllowed(XElement element, string spriteName)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
var disallowed = element.GetAttributeStringArray("disallow", new string[0]);
|
||||
if (disallowed.Any(s => spriteName.Contains(s)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidIndex(int index, List<XElement> list) => index >= 0 && index < list.Count;
|
||||
|
||||
private static IEnumerable<float> GetWeights(IEnumerable<XElement> elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f));
|
||||
|
||||
partial void LoadAttachmentSprites(bool omitJob);
|
||||
|
||||
private int CalculateSalary()
|
||||
@@ -925,72 +983,6 @@ namespace Barotrauma
|
||||
return charElement;
|
||||
}
|
||||
|
||||
public void SpawnInventoryItems(Inventory inventory, XElement itemData)
|
||||
{
|
||||
SpawnInventoryItemsRecursive(inventory, itemData);
|
||||
}
|
||||
|
||||
private void SpawnInventoryItemsRecursive(Inventory inventory, XElement element)
|
||||
{
|
||||
foreach (XElement itemElement in element.Elements())
|
||||
{
|
||||
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true);
|
||||
if (newItem == null) { continue; }
|
||||
|
||||
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
|
||||
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
|
||||
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
|
||||
if (!slotIndices.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure there's no other item in the slot
|
||||
//this should not happen normally, but can occur if the character is accidentally given new job items while also loading previous items in the campaign
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i) && inventory.Items[i] != null && inventory.Items[i] != newItem)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading character inventory data. The slot {i} was already occupied by the item \"{inventory.Items[i].Name} ({inventory.Items[i].ID})\" when loading the item \"{newItem.Name} ({newItem.ID})\"");
|
||||
inventory.Items[i].Drop(null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
|
||||
newItem.ParentInventory = inventory;
|
||||
|
||||
//force the item to the correct slots
|
||||
// e.g. putting the item in a hand slot will also put it in the first available Any-slot,
|
||||
// which may not be where it actually was
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i))
|
||||
{
|
||||
inventory.Items[i] = newItem;
|
||||
}
|
||||
else if (inventory.Items[i] == newItem)
|
||||
{
|
||||
inventory.Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int itemContainerIndex = 0;
|
||||
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
|
||||
foreach (XElement childInvElement in itemElement.Elements())
|
||||
{
|
||||
if (itemContainerIndex >= itemContainers.Count) break;
|
||||
if (!childInvElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement);
|
||||
itemContainerIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyHealthData(Character character, XElement healthData)
|
||||
{
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData); }
|
||||
|
||||
+12
-2
@@ -98,10 +98,11 @@ namespace Barotrauma
|
||||
|
||||
private void ApplyDamage(float deltaTime, bool applyForce)
|
||||
{
|
||||
int limbCount = character.AnimController.Limbs.Count(l => !l.ignoreCollisions && !l.IsSevered);
|
||||
int limbCount = character.AnimController.Limbs.Count(l => !l.IgnoreCollisions && !l.IsSevered);
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
float random = Rand.Value();
|
||||
huskInfection.Clear();
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / limbCount));
|
||||
@@ -170,7 +171,16 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);
|
||||
|
||||
XElement parentElement = new XElement("CharacterInfo");
|
||||
XElement infoElement = character.Info?.Save(parentElement);
|
||||
CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement);
|
||||
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer: false, hasAi: true);
|
||||
if (husk.Info != null)
|
||||
{
|
||||
husk.Info.Character = husk;
|
||||
husk.Info.TeamID = Character.TeamType.None;
|
||||
}
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
|
||||
@@ -399,7 +399,7 @@ namespace Barotrauma
|
||||
|
||||
public void ApplyAffliction(Limb targetLimb, Affliction affliction)
|
||||
{
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
if (!affliction.Prefab.IsBuff && Unkillable || Character.GodMode) { return; }
|
||||
if (affliction.Prefab.LimbSpecific)
|
||||
{
|
||||
if (targetLimb == null)
|
||||
|
||||
@@ -61,11 +61,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("None", false)]
|
||||
[Serialize(CampaignMode.InteractionType.None, false)]
|
||||
public CampaignMode.InteractionType CampaignInteractionType { get; protected set; }
|
||||
|
||||
[Serialize("Passive", false)]
|
||||
public AIObjectiveIdle.BehaviorType BehaviorType { get; protected set; }
|
||||
[Serialize(AIObjectiveIdle.BehaviorType.Passive, false)]
|
||||
public AIObjectiveIdle.BehaviorType Behavior { get; protected set; }
|
||||
|
||||
public List<string> PreferredOutpostModuleTypes { get; protected set; }
|
||||
|
||||
@@ -163,6 +163,13 @@ namespace Barotrauma
|
||||
{
|
||||
item.AddTag("job:" + job.Name);
|
||||
}
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
if (idCardComponent != null)
|
||||
{
|
||||
idCardComponent.Initialize(character.Info);
|
||||
}
|
||||
|
||||
var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in idCardTags)
|
||||
{
|
||||
|
||||
@@ -197,6 +197,12 @@ namespace Barotrauma
|
||||
item.AddTag("job:" + Name);
|
||||
if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc))
|
||||
item.Description = spawnPoint.IdCardDesc;
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
if (idCardComponent != null)
|
||||
{
|
||||
idCardComponent.Initialize(character.Info);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
|
||||
@@ -90,6 +90,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(AIObjectiveIdle.BehaviorType.Passive, false)]
|
||||
public AIObjectiveIdle.BehaviorType IdleBehavior
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string OriginalName { get { return Identifier; } }
|
||||
|
||||
public ContentPackage ContentPackage { get; private set; }
|
||||
|
||||
@@ -223,7 +223,29 @@ namespace Barotrauma
|
||||
|
||||
public readonly LimbType type;
|
||||
|
||||
public readonly bool ignoreCollisions;
|
||||
private bool ignoreCollisions;
|
||||
public bool IgnoreCollisions
|
||||
{
|
||||
get { return ignoreCollisions; }
|
||||
set
|
||||
{
|
||||
ignoreCollisions = value;
|
||||
if (body != null)
|
||||
{
|
||||
if (ignoreCollisions)
|
||||
{
|
||||
body.CollisionCategories = Category.None;
|
||||
body.CollidesWith = Category.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
//limbs don't collide with each other
|
||||
body.CollisionCategories = Physics.CollisionCharacter;
|
||||
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool isSevered;
|
||||
private float severedFadeOutTimer;
|
||||
@@ -310,6 +332,12 @@ namespace Barotrauma
|
||||
|
||||
public Submarine Submarine => character.Submarine;
|
||||
|
||||
public bool Hidden
|
||||
{
|
||||
get => Params.Hide;
|
||||
set => Params.Hide = value;
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }
|
||||
@@ -549,7 +577,7 @@ namespace Barotrauma
|
||||
{
|
||||
body.CollisionCategories = Category.None;
|
||||
body.CollidesWith = Category.None;
|
||||
ignoreCollisions = true;
|
||||
IgnoreCollisions = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -763,27 +791,56 @@ namespace Barotrauma
|
||||
severedFadeOutTimer = SeveredFadeOutTime;
|
||||
}
|
||||
}
|
||||
else if (!IsDead)
|
||||
{
|
||||
if (Params.BlinkFrequency > 0)
|
||||
{
|
||||
if (blinkTimer > -TotalBlinkDurationOut)
|
||||
{
|
||||
blinkTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
blinkTimer = Params.BlinkFrequency;
|
||||
}
|
||||
}
|
||||
if (reEnableTimer > 0)
|
||||
{
|
||||
reEnableTimer -= deltaTime;
|
||||
}
|
||||
else if (reEnableTimer > -1)
|
||||
{
|
||||
ReEnable();
|
||||
}
|
||||
}
|
||||
|
||||
if (attack != null)
|
||||
{
|
||||
attack.UpdateCoolDown(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (Params.BlinkFrequency > 0)
|
||||
private float reEnableTimer = -1;
|
||||
public void HideAndDisable(float duration = 0)
|
||||
{
|
||||
Hidden = true;
|
||||
Disabled = true;
|
||||
IgnoreCollisions = true;
|
||||
if (duration > 0)
|
||||
{
|
||||
if (blinkTimer > -TotalBlinkDurationOut)
|
||||
{
|
||||
blinkTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
blinkTimer = Params.BlinkFrequency;
|
||||
}
|
||||
reEnableTimer = duration;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
private void ReEnable()
|
||||
{
|
||||
Hidden = false;
|
||||
Disabled = false;
|
||||
IgnoreCollisions = false;
|
||||
reEnableTimer = -1;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
private readonly List<Body> contactBodies = new List<Body>();
|
||||
/// <summary>
|
||||
@@ -942,7 +999,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
if (damageTarget is Character targetCharacter && targetLimb != null)
|
||||
{
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound);
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -952,7 +1009,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound);
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body);
|
||||
}
|
||||
}
|
||||
/*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
|
||||
|
||||
@@ -492,8 +492,6 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable()]
|
||||
public bool RandomAttack { get; private set; }
|
||||
|
||||
// TODO: latchonto, swarming
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
protected readonly List<TargetParams> targets = new List<TargetParams>();
|
||||
|
||||
@@ -589,6 +587,21 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "Should the target be ignored if it's inside a container/inventory. Only affects items."), Editable]
|
||||
public bool IgnoreContained { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the target be ignored while the creature is inside. Doesn't matter where the target is."), Editable]
|
||||
public bool IgnoreWhileInside { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the target be ignored while the creature is outside. Doesn't matter where the target is."), Editable]
|
||||
public bool IgnoreWhileOutside { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "Use to define a distance at which the creature starts the sweeping movement."), Editable(MinValueFloat = 0, MaxValueFloat = 10000, ValueStep = 1, DecimalCount = 0)]
|
||||
public float SweepDistance { get; private set; }
|
||||
|
||||
[Serialize(10f, true, description: "How much the sweep affects the steering?"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1f, DecimalCount = 1)]
|
||||
public float SweepStrength { get; private set; }
|
||||
|
||||
[Serialize(1f, true, description: "How quickly the sweep direction changes. Uses the sine wave pattern."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float SweepSpeed { get; private set; }
|
||||
|
||||
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
|
||||
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
|
||||
|
||||
@@ -745,7 +745,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (LightSource != null) { return false; }
|
||||
var lightSourceElement = new XElement("lightsource",
|
||||
new XElement("lighttexture", new XAttribute("texture", "Content/Lights/light.png")));
|
||||
new XElement("lighttexture", new XAttribute("texture", "Content/Lights/pointlight_bright.png")));
|
||||
TryAddSubParam(lightSourceElement, (e, c) => new LightSourceParams(e, c), out LightSourceParams newLightSource);
|
||||
LightSource = newLightSource;
|
||||
return LightSource != null;
|
||||
|
||||
Reference in New Issue
Block a user