(6eeea9b7c) v0.9.10.0.0
This commit is contained in:
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive }
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect }
|
||||
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly float avoidLookAheadDistance;
|
||||
|
||||
private IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
private float updateTargetsTimer;
|
||||
@@ -85,8 +86,9 @@ namespace Barotrauma
|
||||
private readonly float colliderWidth;
|
||||
private readonly float colliderLength;
|
||||
private readonly int requiredHoleCount;
|
||||
private readonly bool canAttackSub;
|
||||
private readonly bool canAttackCharacters;
|
||||
private bool canAttackWalls;
|
||||
private bool canAttackDoors;
|
||||
private bool canAttackCharacters;
|
||||
|
||||
private readonly float priorityFearIncreasement = 2;
|
||||
private readonly float memoryFadeTime = 0.5f;
|
||||
@@ -94,9 +96,13 @@ namespace Barotrauma
|
||||
|
||||
private float avoidTimer;
|
||||
|
||||
public bool StayInsideLevel = true;
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||
|
||||
public CharacterParams.TargetParams SelectedTargetingParams { get { return selectedTargetingParams; } }
|
||||
|
||||
public bool AttackHumans
|
||||
{
|
||||
get
|
||||
@@ -186,24 +192,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool canBreakDoors = false;
|
||||
if (GetTarget("room")?.Priority > 0.0f)
|
||||
{
|
||||
var currentContexts = Character.GetAttackContexts();
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.attack == null) { continue; }
|
||||
if (!limb.attack.IsValidTarget(AttackTarget.Structure)) { continue; }
|
||||
if (limb.attack.IsValidContext(currentContexts) && limb.attack.StructureDamage > 0.0f)
|
||||
{
|
||||
canBreakDoors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReevaluateAttacks();
|
||||
outsideSteering = new SteeringManager(this);
|
||||
insideSteering = new IndoorsSteeringManager(this, false, canBreakDoors);
|
||||
insideSteering = new IndoorsSteeringManager(this, false, canAttackDoors);
|
||||
steeringManager = outsideSteering;
|
||||
State = AIState.Idle;
|
||||
|
||||
@@ -213,9 +204,6 @@ namespace Barotrauma
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f);
|
||||
|
||||
canAttackSub = Character.AnimController.CanAttackSubmarine;
|
||||
canAttackCharacters = Character.AnimController.CanAttackCharacters;
|
||||
}
|
||||
|
||||
private CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
@@ -226,31 +214,32 @@ namespace Barotrauma
|
||||
public void SelectTarget(AITarget target, float priority)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = GetTargetMemory(target);
|
||||
selectedTargetMemory = GetTargetMemory(target, true);
|
||||
selectedTargetMemory.Priority = priority;
|
||||
}
|
||||
|
||||
private float escapeMargin;
|
||||
private float movementMargin;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (DisableEnemyAI) { return; }
|
||||
|
||||
base.Update(deltaTime);
|
||||
bool ignorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
if (steeringManager is IndoorsSteeringManager)
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
{
|
||||
var currPath = ((IndoorsSteeringManager)steeringManager).CurrentPath;
|
||||
var currPath = PathSteering.CurrentPath;
|
||||
if (currPath != null && currPath.CurrentNode != null)
|
||||
{
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
{
|
||||
ignorePlatforms = true;
|
||||
// Don't allow to jump from too high.
|
||||
float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2;
|
||||
float height = Math.Abs(currPath.CurrentNode.SimPosition.Y - Character.SimPosition.Y);
|
||||
ignorePlatforms = height < allowedJumpHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||
|
||||
//clients get the facing direction from the server
|
||||
@@ -396,7 +385,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (squaredDistance <= Math.Pow(reactDistance + escapeMargin, 2))
|
||||
if (squaredDistance <= Math.Pow(reactDistance + movementMargin, 2))
|
||||
{
|
||||
float halfReactDistance = reactDistance / 2;
|
||||
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
|
||||
@@ -408,26 +397,56 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
|
||||
if (escapeMargin <= 0)
|
||||
if (movementMargin <= 0)
|
||||
{
|
||||
escapeMargin = halfReactDistance;
|
||||
movementMargin = halfReactDistance;
|
||||
}
|
||||
escapeMargin = MathHelper.Clamp(escapeMargin += deltaTime, halfReactDistance, reactDistance);
|
||||
movementMargin = MathHelper.Clamp(movementMargin += deltaTime, halfReactDistance, reactDistance);
|
||||
UpdateEscape(deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
escapeMargin = 0;
|
||||
movementMargin = 0;
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIState.Protect:
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker)
|
||||
{
|
||||
// Attack the character that attacked the target we are protecting
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
return;
|
||||
}
|
||||
float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (sqrDist > Math.Pow(reactDist + movementMargin, 2))
|
||||
{
|
||||
movementMargin = reactDist;
|
||||
run = true;
|
||||
UpdateFollow(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
movementMargin = MathHelper.Clamp(movementMargin -= deltaTime, 0, reactDist);
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
}
|
||||
IsSteeringThroughGap = false;
|
||||
if (SwarmBehavior != null)
|
||||
{
|
||||
@@ -435,6 +454,8 @@ namespace Barotrauma
|
||||
SwarmBehavior.Refresh();
|
||||
SwarmBehavior.UpdateSteering(deltaTime);
|
||||
}
|
||||
// Ensure that the creature keeps inside the level
|
||||
SteerInsideLevel(deltaTime);
|
||||
float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
|
||||
steeringManager.Update(speed);
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, State == AIState.Idle && Character.AnimController.InWater ? Steering.Length() : speed);
|
||||
@@ -459,13 +480,12 @@ namespace Barotrauma
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
return;
|
||||
}
|
||||
SteerInsideLevel(deltaTime);
|
||||
}
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target);
|
||||
var memory = GetTargetMemory(target, false);
|
||||
if (memory != null)
|
||||
{
|
||||
var location = memory.Location;
|
||||
@@ -579,10 +599,10 @@ namespace Barotrauma
|
||||
}
|
||||
else if (pathSteering != null)
|
||||
{
|
||||
if (canAttackSub && hasValidPath)
|
||||
if (canAttackDoors && hasValidPath)
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
if (SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
@@ -618,7 +638,6 @@ namespace Barotrauma
|
||||
{
|
||||
SteeringManager.SteeringWander();
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
SteerInsideLevel(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,17 +663,18 @@ namespace Barotrauma
|
||||
if (SelectedAiTarget.Entity is Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
Character owner = GetOwner(item);
|
||||
if (owner != null)
|
||||
{
|
||||
Entity owner = pickable.Picker ?? item.ParentInventory?.Owner;
|
||||
if (owner != null)
|
||||
if (IsFriendly(Character, owner))
|
||||
{
|
||||
var target = owner.AiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedAiTarget = owner.AiTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,7 +686,8 @@ namespace Barotrauma
|
||||
{
|
||||
attackWorldPos += wallTarget.Structure.Submarine.Position;
|
||||
}
|
||||
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
|
||||
attackSimPos = Character.Submarine == wallTarget.Structure.Submarine ? wallTarget.Position : attackWorldPos;
|
||||
attackSimPos = ConvertUnits.ToSimUnits(attackSimPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -704,7 +725,7 @@ namespace Barotrauma
|
||||
var door = i.GetComponent<Door>();
|
||||
// Steer through the door manually if it's open or broken
|
||||
// Don't try to enter dry hulls if cannot walk or if the gap is too narrow
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && door.IsOpen)
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.IsBroken))
|
||||
{
|
||||
if (Character.AnimController.CanWalk || door.LinkedGap.FlowTargetHull.WaterPercentage > 25)
|
||||
{
|
||||
@@ -722,7 +743,6 @@ namespace Barotrauma
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Structure w && wallTarget == null)
|
||||
{
|
||||
// Targeting only the outer walls
|
||||
bool isBroken = true;
|
||||
for (int i = 0; i < w.Sections.Length; i++)
|
||||
{
|
||||
@@ -893,35 +913,23 @@ namespace Barotrauma
|
||||
}
|
||||
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
|
||||
}
|
||||
if (!canAttack && SelectedAiTarget.Entity.Submarine != null && !canAttackSub)
|
||||
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
|
||||
{
|
||||
float dist = Vector2.Distance(Character.AnimController.MainLimb.WorldPosition, attackWorldPos);
|
||||
if (wallTarget != null)
|
||||
if (Vector2.DistanceSquared(Character.WorldPosition, attackWorldPos) < 2000 * 2000)
|
||||
{
|
||||
// Steer towards the target, but turn away if a wall is blocking the way
|
||||
if (dist < ConvertUnits.ToDisplayUnits(colliderLength) * 3)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
// Resetting the ai target prevents the character from chasing it
|
||||
ResetAITarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (dist < 1000)
|
||||
{
|
||||
// Check that we are not bumping into a door
|
||||
// Check that we are not bumping into a door or a wall
|
||||
Vector2 rayStart = SimPosition;
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
Vector2 toTarget = SelectedAiTarget.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + toTarget.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
|
||||
Vector2 dir = SelectedAiTarget.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null)
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null &&
|
||||
(!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
|
||||
{
|
||||
// Target is unreachable, there's a door ahead
|
||||
// Target is unreachable, there's a door or wall ahead
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
@@ -934,23 +942,28 @@ namespace Barotrauma
|
||||
Character targetCharacter = SelectedAiTarget.Entity as Character;
|
||||
if (canAttack)
|
||||
{
|
||||
// Target a specific limb instead of the target center position
|
||||
if (wallTarget == null && targetCharacter != null)
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
var targetLimbType = AttackingLimb.Params.Attack.Attack.TargetLimbType;
|
||||
attackTargetLimb = GetTargetLimb(AttackingLimb, targetCharacter, targetLimbType);
|
||||
if (attackTargetLimb == null)
|
||||
// Target a specific limb instead of the target center position
|
||||
if (wallTarget == null && targetCharacter != null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
return;
|
||||
var targetLimbType = AttackingLimb.Params.Attack.Attack.TargetLimbType;
|
||||
attackTargetLimb = GetTargetLimb(AttackingLimb, targetCharacter, targetLimbType);
|
||||
if (attackTargetLimb == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
return;
|
||||
}
|
||||
attackWorldPos = attackTargetLimb.WorldPosition;
|
||||
attackSimPos = Character.GetRelativeSimPosition(attackTargetLimb);
|
||||
}
|
||||
attackWorldPos = attackTargetLimb.WorldPosition;
|
||||
attackSimPos = Character.GetRelativeSimPosition(attackTargetLimb);
|
||||
}
|
||||
// Check that we can reach the target
|
||||
Vector2 toTarget = attackWorldPos - AttackingLimb.WorldPosition;
|
||||
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it (the attack takes some time to perform)
|
||||
if (wallTarget != null)
|
||||
{
|
||||
if (wallTarget.Structure.Submarine != null)
|
||||
@@ -961,7 +974,6 @@ namespace Barotrauma
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it (the attack takes some time to perform)
|
||||
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
|
||||
toTarget += margin;
|
||||
}
|
||||
@@ -981,6 +993,7 @@ namespace Barotrauma
|
||||
return ConvertUnits.ToDisplayUnits(targetVelocity) * AttackingLimb.attack.Duration * dot;
|
||||
}
|
||||
|
||||
// Check that we can reach the target
|
||||
distance = toTarget.Length();
|
||||
canAttack = distance < AttackingLimb.attack.Range;
|
||||
if (!canAttack && !IsCoolDownRunning)
|
||||
@@ -1035,21 +1048,25 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 steerPos = attackSimPos + offset;
|
||||
Vector2 steerPos = attackSimPos;
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
steerPos += offset;
|
||||
}
|
||||
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
if (pathSteering.CurrentPath != null)
|
||||
{
|
||||
// Attack doors
|
||||
if (canAttackSub)
|
||||
if (canAttackDoors)
|
||||
{
|
||||
// If the target is in the same hull, there shouldn't be any doors blocking the path
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
@@ -1063,13 +1080,14 @@ namespace Barotrauma
|
||||
if ((Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
|
||||
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity)))
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
|
||||
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 2);
|
||||
// Switch to Idle when cannot reach the target and if cannot damage the walls
|
||||
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -1145,7 +1163,7 @@ namespace Barotrauma
|
||||
if (attack == null) { continue; }
|
||||
if (attack.CoolDownTimer > 0) { continue; }
|
||||
if (!attack.IsValidContext(currentContexts)) { continue; }
|
||||
if (!attack.IsValidTarget(target)) { continue; }
|
||||
if (!attack.IsValidTarget(target as IDamageable)) { continue; }
|
||||
if (target is ISerializableEntity se && target is Character)
|
||||
{
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
|
||||
@@ -1176,6 +1194,7 @@ namespace Barotrauma
|
||||
|
||||
float CalculatePriority(Limb limb, Vector2 attackPos)
|
||||
{
|
||||
if (Character.AnimController.SimplePhysicsEnabled) { return 1 + limb.attack.Priority; }
|
||||
float dist = Vector2.Distance(limb.WorldPosition, attackPos);
|
||||
// The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it.
|
||||
// We also need a max value that is more than the actual range.
|
||||
@@ -1242,7 +1261,10 @@ namespace Barotrauma
|
||||
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner"))
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
|
||||
@@ -1271,7 +1293,7 @@ namespace Barotrauma
|
||||
}
|
||||
return isDisabled;
|
||||
}
|
||||
|
||||
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
float reactionTime = Rand.Range(0.1f, 0.3f);
|
||||
@@ -1281,7 +1303,7 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
if (attacker == null || attacker.AiTarget == null) { return; }
|
||||
bool isFriendly = attacker.SpeciesName == Character.SpeciesName || attacker.Params.Group == Character.Params.Group;
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
@@ -1302,7 +1324,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isFriendly && attackResult.Damage > 0.0f)
|
||||
{
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
|
||||
if (Character.Params.AI.AttackWhenProvoked && canAttack)
|
||||
{
|
||||
if (attacker.IsHusk)
|
||||
@@ -1356,7 +1378,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget);
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
@@ -1399,7 +1421,7 @@ namespace Barotrauma
|
||||
var aiTarget = wallTarget.Structure.AiTarget;
|
||||
if (aiTarget != null && SelectedAiTarget != aiTarget)
|
||||
{
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget).Priority);
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, true).Priority);
|
||||
}
|
||||
}
|
||||
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
|
||||
@@ -1464,7 +1486,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
Vector2 mouthPos = Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 mouthPos = Character.AnimController.SimplePhysicsEnabled ? SimPosition : Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = Character.GetRelativeSimPosition(target);
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2);
|
||||
@@ -1493,6 +1515,25 @@ namespace Barotrauma
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateFollow(float deltaTime)
|
||||
{
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
|
||||
if (!MathUtils.IsValid(dir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
steeringManager.SteeringManual(deltaTime, dir);
|
||||
if (Character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
}
|
||||
|
||||
#region Targeting
|
||||
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
|
||||
|
||||
@@ -1619,13 +1660,16 @@ namespace Barotrauma
|
||||
Door door = null;
|
||||
if (aiTarget.Entity is Item item)
|
||||
{
|
||||
//item inside and we're outside -> attack the hull
|
||||
if (item.CurrentHull != null && character.CurrentHull == null)
|
||||
{
|
||||
targetingTag = "room";
|
||||
}
|
||||
|
||||
door = item.GetComponent<Door>();
|
||||
bool targetingFromOutsideToInside = item.CurrentHull != null && character.CurrentHull == null;
|
||||
if (targetingFromOutsideToInside)
|
||||
{
|
||||
if (door != null && !canAttackDoors || !canAttackWalls)
|
||||
{
|
||||
// Can't reach
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var prio in AIParams.Targets)
|
||||
{
|
||||
if (item.HasTag(prio.Tag))
|
||||
@@ -1634,7 +1678,25 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (door == null && targetingTag == null)
|
||||
{
|
||||
if (item.GetComponent<Sonar>() != null)
|
||||
{
|
||||
targetingTag = "sonar";
|
||||
}
|
||||
else if (targetingFromOutsideToInside)
|
||||
{
|
||||
targetingTag = "room";
|
||||
}
|
||||
}
|
||||
else if (targetingTag == "nasonov")
|
||||
{
|
||||
if ((item.Submarine == null || !item.Submarine.Info.IsPlayer) && item.ParentInventory == null)
|
||||
{
|
||||
// Only target nasonovartifacts when they are held be a player or inside the playersub
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Ignore the target if it's a decoy and the character is already inside a sub
|
||||
if (character.CurrentHull != null && targetingTag == "decoy")
|
||||
{
|
||||
@@ -1649,15 +1711,13 @@ namespace Barotrauma
|
||||
// Ignore structures that doesn't have a body (not walls)
|
||||
continue;
|
||||
}
|
||||
if (s.IsPlatform)
|
||||
if (s.IsPlatform) { continue; }
|
||||
if (s.Submarine == null) { continue; }
|
||||
bool isCharacterInside = character.CurrentHull != null;
|
||||
bool isInnerWall = s.prefab.Tags.Contains("inner");
|
||||
if (isInnerWall && !isCharacterInside)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool isCharacterOutside = s.Submarine == null || character.CurrentHull == null;
|
||||
bool targetInnerWalls = AIParams.TargetInnerWalls;
|
||||
if (!isCharacterOutside && !targetInnerWalls)
|
||||
{
|
||||
// Ignore walls when inside (walltargets still work)
|
||||
// Ignore inner walls when outside (walltargets still work)
|
||||
continue;
|
||||
}
|
||||
valueModifier = 1;
|
||||
@@ -1670,48 +1730,71 @@ namespace Barotrauma
|
||||
var section = s.Sections[i];
|
||||
if (section.gap == null) { continue; }
|
||||
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
|
||||
isInnerWall = isInnerWall || !leadsInside;
|
||||
if (Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
if (isCharacterOutside)
|
||||
if (!isCharacterInside)
|
||||
{
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : (targetInnerWalls ? 1 : 0);
|
||||
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
|
||||
}
|
||||
else
|
||||
else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
|
||||
{
|
||||
// Ignore holes that cannot be passed through if cannot attack items/structures. Holes that are big enough should be targeted, so that we can get in
|
||||
if (!canAttackSub)
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inside
|
||||
if (AggressiveBoarding)
|
||||
{
|
||||
if (!isInnerWall)
|
||||
{
|
||||
// Only interested in getting inside (aggressive boarder) -> don't target outer walls when already inside
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
else if (CanPassThroughHole(s, i))
|
||||
{
|
||||
valueModifier *= isInnerWall ? 1 : 0;
|
||||
}
|
||||
else if (!canAttackWalls)
|
||||
{
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
if (AggressiveBoarding && leadsInside)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!canAttackWalls)
|
||||
{
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
// (Terminalcells)
|
||||
valueModifier *= 1 - section.gap.Open * 0.25f;
|
||||
}
|
||||
}
|
||||
else if (!canAttackSub || CanPassThroughHole(s, i))
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot enter
|
||||
if (isInnerWall || !canAttackWalls)
|
||||
{
|
||||
// Already inside -> ignore holes in the walls and ignore walls if cannot attack the sub.
|
||||
// Ignore inner walls and all walls if cannot do damage on walls.
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
else if (canAttackSub && !AggressiveBoarding)
|
||||
else if (AggressiveBoarding)
|
||||
{
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
valueModifier *= 1 - section.gap.Open * 0.25f;
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
// (Bonethreshers)
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
else if (!leadsInside || !canAttackSub)
|
||||
{
|
||||
// Can't get in, ignore inner walls
|
||||
// Also ignore all walls if cannot attack the sub
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1727,31 +1810,34 @@ namespace Barotrauma
|
||||
}
|
||||
if (door.Item.Submarine == null) { continue;}
|
||||
bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom;
|
||||
bool isOpen = door.IsOpen;
|
||||
if (!isOpen && (!canAttackSub))
|
||||
bool isOpen = door.IsOpen || door.IsBroken;
|
||||
if (!isOpen && !canAttackDoors || (isOutdoor && !AIParams.TargetOuterWalls))
|
||||
{
|
||||
// Ignore doors that are not open if cannot attack items/structures. Open doors should be targeted, so that we can get in if we are aggressive boarders
|
||||
valueModifier = 0;
|
||||
// Ignore doors that are not open if cannot attack doors or shouldn't target outer doors.
|
||||
continue;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
if (isOpen && (!Character.AnimController.CanEnterSubmarine || !AggressiveBoarding))
|
||||
{
|
||||
valueModifier = isOutdoor ? 1 : 0;
|
||||
// Ignore broken and open doors
|
||||
// Aggressive boarders don't ignore open doors, because they use them for get in.
|
||||
continue;
|
||||
}
|
||||
else if (AggressiveBoarding)
|
||||
if (AggressiveBoarding)
|
||||
{
|
||||
// Increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
|
||||
if (character.CurrentHull == null)
|
||||
// Increase the priority if the character is outside and the door is from outside to inside
|
||||
if (character.CurrentHull == null && isOutdoor)
|
||||
{
|
||||
valueModifier *= isOpen ? 5 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueModifier *= isOpen ? 0 : 1;
|
||||
// Inside
|
||||
valueModifier *= isOpen || isOutdoor ? 0 : 1;
|
||||
}
|
||||
}
|
||||
else if (!Character.AnimController.CanEnterSubmarine && isOpen) //ignore broken and open doors
|
||||
else if (character.CurrentHull == null)
|
||||
{
|
||||
continue;
|
||||
valueModifier = isOutdoor ? 1 : 0;
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f)
|
||||
@@ -1783,7 +1869,7 @@ namespace Barotrauma
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, true);
|
||||
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
|
||||
{
|
||||
// Inside the sub, treat objects that are up or down, as they were farther away.
|
||||
@@ -1793,9 +1879,18 @@ namespace Barotrauma
|
||||
|
||||
if (valueModifier > targetValue)
|
||||
{
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
|
||||
if (aiTarget.Entity is Item i)
|
||||
{
|
||||
Character owner = GetOwner(i);
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (owner == character) { continue; }
|
||||
if (owner != null && IsFriendly(Character, owner))
|
||||
{
|
||||
// If the item is held by a friendly character, ignore it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
@@ -1820,7 +1915,7 @@ namespace Barotrauma
|
||||
foreach (var gap in Character.CurrentHull.ConnectedGaps)
|
||||
{
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door == null || !door.IsOpen)
|
||||
if (door == null || !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
var wall = gap.ConnectedWall;
|
||||
if (wall != null)
|
||||
@@ -1855,12 +1950,15 @@ namespace Barotrauma
|
||||
return SelectedAiTarget;
|
||||
}
|
||||
|
||||
private AITargetMemory GetTargetMemory(AITarget target)
|
||||
private AITargetMemory GetTargetMemory(AITarget target, bool addIfNotFound)
|
||||
{
|
||||
if (!targetMemories.TryGetValue(target, out AITargetMemory memory))
|
||||
{
|
||||
memory = new AITargetMemory(target, 10);
|
||||
targetMemories.Add(target, memory);
|
||||
if (addIfNotFound)
|
||||
{
|
||||
memory = new AITargetMemory(target, 10);
|
||||
targetMemories.Add(target, memory);
|
||||
}
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
@@ -1875,8 +1973,11 @@ namespace Barotrauma
|
||||
}
|
||||
else if (CanPerceive(_selectedAiTarget, distSquared: Vector2.DistanceSquared(Character.WorldPosition, _selectedAiTarget.WorldPosition)))
|
||||
{
|
||||
var memory = GetTargetMemory(_selectedAiTarget);
|
||||
memory.Location = _selectedAiTarget.WorldPosition;
|
||||
var memory = GetTargetMemory(_selectedAiTarget, false);
|
||||
if (memory != null)
|
||||
{
|
||||
memory.Location = _selectedAiTarget.WorldPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2014,11 +2115,17 @@ namespace Barotrauma
|
||||
{
|
||||
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
|
||||
// --> Target the submarine too.
|
||||
if (target.Submarine != null && canAttackSub)
|
||||
if (target.Submarine != null && (canAttackDoors || canAttackWalls))
|
||||
{
|
||||
ChangeParams("room", state, priority);
|
||||
ChangeParams("wall", state, priority);
|
||||
ChangeParams("door", state, priority);
|
||||
if (canAttackWalls)
|
||||
{
|
||||
ChangeParams("wall", state, priority);
|
||||
}
|
||||
if (canAttackDoors)
|
||||
{
|
||||
ChangeParams("door", state, priority);
|
||||
}
|
||||
}
|
||||
ChangeParams("provocative", state, priority, onlyExisting: true);
|
||||
ChangeParams("light", state, priority, onlyExisting: true);
|
||||
@@ -2039,7 +2146,7 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
escapeTarget = null;
|
||||
AttackingLimb = null;
|
||||
escapeMargin = 0;
|
||||
movementMargin = 0;
|
||||
allGapsSearched = false;
|
||||
unreachableGaps.Clear();
|
||||
if (isStateChanged && to == AIState.Idle && from != to)
|
||||
@@ -2064,28 +2171,66 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ReevaluateAttacks()
|
||||
{
|
||||
canAttackWalls = LatchOntoAI != null && LatchOntoAI.AttachToSub;
|
||||
canAttackDoors = false;
|
||||
canAttackCharacters = false;
|
||||
foreach (var limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.attack == null) { continue; }
|
||||
if (!canAttackWalls)
|
||||
{
|
||||
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.StructureDamage > 0;
|
||||
}
|
||||
if (!canAttackDoors)
|
||||
{
|
||||
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.ItemDamage > 0;
|
||||
}
|
||||
if (!canAttackCharacters)
|
||||
{
|
||||
canAttackCharacters = limb.attack.IsValidTarget(AttackTarget.Character);
|
||||
}
|
||||
}
|
||||
if (PathSteering != null)
|
||||
{
|
||||
PathSteering.CanBreakDoors = canAttackDoors;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 returnDir;
|
||||
private float returnTimer;
|
||||
private void SteerInsideLevel(float deltaTime)
|
||||
{
|
||||
if (Level.Loaded == null) { return; }
|
||||
|
||||
Vector2 levelSimSize = new Vector2(
|
||||
ConvertUnits.ToSimUnits(Level.Loaded.Size.X),
|
||||
ConvertUnits.ToSimUnits(Level.Loaded.Size.Y));
|
||||
|
||||
float margin = 10.0f;
|
||||
|
||||
if (SimPosition.Y < 0.0)
|
||||
if (SteeringManager is IndoorsSteeringManager || !StayInsideLevel) { return; }
|
||||
if (Level.Loaded == null) { return; }
|
||||
Vector2 levelSimSize = ConvertUnits.ToSimUnits(Level.Loaded.Size.X, Level.Loaded.Size.Y);
|
||||
float returnTime = 3;
|
||||
if (SimPosition.Y < 0)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitY * MathUtils.InverseLerp(0.0f, -margin, SimPosition.Y));
|
||||
// Too far down
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitY;
|
||||
}
|
||||
if (SimPosition.X < 0.0f)
|
||||
if (SimPosition.X < 0)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitX * MathUtils.InverseLerp(0.0f, -margin, SimPosition.X));
|
||||
// Too far left
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitX;
|
||||
}
|
||||
if (SimPosition.X > levelSimSize.X)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitX * MathUtils.InverseLerp(levelSimSize.X, levelSimSize.X + margin, SimPosition.X));
|
||||
}
|
||||
// Too far right
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitX;
|
||||
}
|
||||
if (returnTimer > 0)
|
||||
{
|
||||
returnTimer -= deltaTime;
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringManual(deltaTime, returnDir);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
@@ -2116,7 +2261,6 @@ namespace Barotrauma
|
||||
targetLimbs.Clear();
|
||||
foreach (var limb in target.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == targetLimbType || targetLimbType == LimbType.None)
|
||||
{
|
||||
targetLimbs.Add(limb);
|
||||
@@ -2131,6 +2275,7 @@ namespace Barotrauma
|
||||
Limb targetLimb = null;
|
||||
foreach (Limb limb in targetLimbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
float dist = Vector2.DistanceSquared(limb.WorldPosition, attackLimb.WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
@@ -2140,6 +2285,27 @@ namespace Barotrauma
|
||||
}
|
||||
return targetLimb;
|
||||
}
|
||||
|
||||
private Character GetOwner(Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
{
|
||||
Character owner = pickable.Picker ?? item.FindParentInventory(i => i.Owner is Character)?.Owner as Character;
|
||||
if (owner != null)
|
||||
{
|
||||
var target = owner.AiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed)
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
|
||||
}
|
||||
|
||||
//the "memory" of the Character
|
||||
|
||||
@@ -30,8 +30,9 @@ namespace Barotrauma
|
||||
|
||||
public static float HULL_SAFETY_THRESHOLD = 50;
|
||||
|
||||
public HashSet<Hull> UnreachableHulls { get; private set; } = new HashSet<Hull>();
|
||||
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
||||
public readonly HashSet<Hull> UnreachableHulls = new HashSet<Hull>();
|
||||
public readonly HashSet<Hull> UnsafeHulls = new HashSet<Hull>();
|
||||
public readonly List<Item> IgnoredItems = new List<Item>();
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
@@ -55,13 +56,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public float CurrentHullSafety { get; private set; }
|
||||
public float CurrentHullSafety { get; private set; } = 100;
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
if (!c.IsHuman)
|
||||
{
|
||||
throw new System.Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
|
||||
throw new Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
|
||||
}
|
||||
insideSteering = new IndoorsSteeringManager(this, true, false);
|
||||
outsideSteering = new SteeringManager(this);
|
||||
@@ -85,7 +86,7 @@ namespace Barotrauma
|
||||
{
|
||||
unreachableClearTimer = clearUnreachableInterval;
|
||||
UnreachableHulls.Clear();
|
||||
ignoredContainers.Clear();
|
||||
IgnoredItems.Clear();
|
||||
}
|
||||
|
||||
// Use the pathfinding also outside of the sub, but not farther than the extents of the sub + 500 units.
|
||||
@@ -175,9 +176,7 @@ namespace Barotrauma
|
||||
}
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
|
||||
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
{
|
||||
var currPath = PathSteering.CurrentPath;
|
||||
@@ -185,51 +184,24 @@ namespace Barotrauma
|
||||
{
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
{
|
||||
// Don't allow to jump from too high. The formula might require tweaking.
|
||||
// Don't allow to jump from too high.
|
||||
float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2;
|
||||
float height = Math.Abs(currPath.CurrentNode.SimPosition.Y - Character.SimPosition.Y);
|
||||
ignorePlatforms = height < allowedJumpHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.IsClimbing && PathSteering.IsNextLadderSameAsCurrent)
|
||||
{
|
||||
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||
|
||||
Vector2 targetMovement = AnimController.TargetMovement;
|
||||
|
||||
if (!Character.AnimController.InWater)
|
||||
{
|
||||
targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
if (Character.AnimController.InWater && targetMovement.LengthSquared() < 0.000001f)
|
||||
{
|
||||
bool isAiming = false;
|
||||
var holdable = Character.SelectedConstruction?.GetComponent<Holdable>();
|
||||
if (holdable != null)
|
||||
{
|
||||
isAiming = holdable.ControlPose;
|
||||
}
|
||||
bool swimInPlace = !isAiming;
|
||||
if (swimInPlace && ObjectiveManager.GetActiveObjective() is AIObjectiveGoTo goToObjective)
|
||||
{
|
||||
if (goToObjective.Target != Character)
|
||||
{
|
||||
swimInPlace = false;
|
||||
}
|
||||
}
|
||||
if (swimInPlace)
|
||||
{
|
||||
// Swim in place so that we don't fall motionless and look dead.
|
||||
targetMovement = new Vector2(targetMovement.X, Rand.Range(-0.001f, 0.001f));
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
|
||||
|
||||
flipTimer -= deltaTime;
|
||||
@@ -280,14 +252,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
findItemState = FindItemState.Extinguisher;
|
||||
if (FindSuitableContainer(Character, extinguisher, out Item targetContainer))
|
||||
if (FindSuitableContainer(extinguisher, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, extinguisher, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => ignoredContainers.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -310,42 +282,47 @@ namespace Barotrauma
|
||||
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|
||||
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
|
||||
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
|
||||
bool takeMaskOff = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
if (!removeDivingSuit)
|
||||
{
|
||||
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
|
||||
removeDivingSuit = !shouldKeepTheGearOn && (gotoObjective == null || targetHasNoSuit);
|
||||
}
|
||||
bool takeMaskOff = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
if (!takeMaskOff && Character.CurrentHull.WaterPercentage < 40)
|
||||
{
|
||||
bool targetHasNoMask = gotoObjective != null && gotoObjective.mimic && !HasDivingMask(gotoObjective.Target as Character);
|
||||
takeMaskOff = !shouldKeepTheGearOn && (gotoObjective == null || targetHasNoMask);
|
||||
}
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
if (gotoObjective.Target is Hull h)
|
||||
if (shouldKeepTheGearOn)
|
||||
{
|
||||
if (NeedsDivingGear(Character, h, out _))
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
}
|
||||
removeDivingSuit = false;
|
||||
}
|
||||
else if (gotoObjective.Target is Character c)
|
||||
}
|
||||
if (!takeMaskOff)
|
||||
{
|
||||
if (shouldKeepTheGearOn)
|
||||
{
|
||||
if (NeedsDivingGear(Character, c.CurrentHull, out _))
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
}
|
||||
takeMaskOff = false;
|
||||
}
|
||||
else if (gotoObjective.Target is Item i)
|
||||
}
|
||||
if (!shouldKeepTheGearOn && (!takeMaskOff || !removeDivingSuit))
|
||||
{
|
||||
foreach (var objective in ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(includingSelf: true))
|
||||
{
|
||||
if (NeedsDivingGear(Character, i.CurrentHull, out _))
|
||||
if (objective is AIObjectiveGoTo gotoObjective)
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
Hull targetHull = gotoObjective.GetTargetHull();
|
||||
bool targetIsOutside = (gotoObjective.Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||
if (targetIsOutside || NeedsDivingGear(Character, targetHull, out _))
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
break;
|
||||
}
|
||||
else if (gotoObjective.mimic)
|
||||
{
|
||||
if (!removeDivingSuit)
|
||||
{
|
||||
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
|
||||
}
|
||||
if (!takeMaskOff)
|
||||
{
|
||||
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,7 +340,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
findItemState = FindItemState.DivingSuit;
|
||||
if (FindSuitableContainer(Character, divingSuit, out Item targetContainer))
|
||||
if (FindSuitableContainer(divingSuit, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
@@ -375,7 +352,7 @@ namespace Barotrauma
|
||||
};
|
||||
decontainObjective.Abandoned += () =>
|
||||
{
|
||||
ignoredContainers.Add(targetContainer);
|
||||
IgnoredItems.Add(targetContainer);
|
||||
};
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
@@ -405,14 +382,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
findItemState = FindItemState.DivingMask;
|
||||
if (FindSuitableContainer(Character, mask, out Item targetContainer))
|
||||
if (FindSuitableContainer(mask, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => ignoredContainers.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -442,14 +419,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
{
|
||||
if (FindSuitableContainer(Character, item, out Item targetContainer))
|
||||
if (FindSuitableContainer(item, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => ignoredContainers.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -478,11 +455,10 @@ namespace Barotrauma
|
||||
}
|
||||
private FindItemState findItemState;
|
||||
private int itemIndex;
|
||||
private List<Item> ignoredContainers = new List<Item>();
|
||||
public bool FindSuitableContainer(Character character, Item containableItem, out Item suitableContainer)
|
||||
public bool FindSuitableContainer(Item containableItem, out Item suitableContainer)
|
||||
{
|
||||
suitableContainer = null;
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredContainers, customPriorityFunction: i =>
|
||||
if (Character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: IgnoredItems, customPriorityFunction: i =>
|
||||
{
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
@@ -583,7 +559,7 @@ namespace Barotrauma
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.RepairThreshold)) { continue; }
|
||||
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbrokendevices");
|
||||
@@ -633,9 +609,13 @@ namespace Barotrauma
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; }
|
||||
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||
{
|
||||
// Don't react on the damage if there's no attacker.
|
||||
// We might consider launching the retreat combat objective in some cases, so that the bot does not just stand somewhere getting damaged and dying.
|
||||
// But fires and enemies should already be handled by the FindSafetyObjective.
|
||||
return;
|
||||
// Ignore damage from falling etc that we shouldn't react to.
|
||||
if (Character.LastDamageSource == null) { return; }
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
|
||||
//if (Character.LastDamageSource == null) { return; }
|
||||
//AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
|
||||
}
|
||||
else if (IsFriendly(attacker))
|
||||
{
|
||||
@@ -784,7 +764,6 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static bool HasDivingGear(Character character, float conditionPercentage = 0) => HasDivingSuit(character, conditionPercentage) || HasDivingMask(character, conditionPercentage);
|
||||
|
||||
/// <summary>
|
||||
@@ -852,7 +831,7 @@ namespace Barotrauma
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage >= r.AIRepairThreshold)) { continue; }
|
||||
if (item.Repairables.All(r => item.ConditionPercentage >= r.RepairThreshold)) { continue; }
|
||||
AddTargets<AIObjectiveRepairItems, Item>(character, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Barotrauma
|
||||
private PathFinder pathFinder;
|
||||
private SteeringPath currentPath;
|
||||
|
||||
private bool canOpenDoors, canBreakDoors;
|
||||
private bool canOpenDoors;
|
||||
public bool CanBreakDoors { get; set; }
|
||||
|
||||
private Character character;
|
||||
|
||||
@@ -50,8 +51,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool InLadders =>
|
||||
currentPath != null &&
|
||||
currentPath.CurrentNode != null && (currentPath.CurrentNode.Ladders != null ||
|
||||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null));
|
||||
currentPath.CurrentNode != null && (currentPath.CurrentNode.Ladders != null && !currentPath.CurrentNode.Ladders.Item.NonInteractable ||
|
||||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null && !currentPath.NextNode.Ladders.Item.NonInteractable));
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any node in the path is in stairs
|
||||
@@ -69,6 +70,7 @@ namespace Barotrauma
|
||||
if (currentPath.NextNode == null) { return false; }
|
||||
var currentLadder = currentPath.CurrentNode.Ladders;
|
||||
if (currentLadder == null) { return false; }
|
||||
if (currentLadder.Item.NonInteractable) { return false; }
|
||||
var nextLadder = GetNextLadder();
|
||||
return nextLadder != null && nextLadder == currentLadder;
|
||||
}
|
||||
@@ -80,7 +82,7 @@ namespace Barotrauma
|
||||
pathFinder.GetNodePenalty = GetNodePenalty;
|
||||
|
||||
this.canOpenDoors = canOpenDoors;
|
||||
this.canBreakDoors = canBreakDoors;
|
||||
this.CanBreakDoors = canBreakDoors;
|
||||
|
||||
character = (host as AIController).Character;
|
||||
|
||||
@@ -103,6 +105,12 @@ namespace Barotrauma
|
||||
IsPathDirty = false;
|
||||
}
|
||||
|
||||
public void ResetPath()
|
||||
{
|
||||
currentPath = null;
|
||||
IsPathDirty = true;
|
||||
}
|
||||
|
||||
public void SteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null)
|
||||
{
|
||||
steering += CalculateSteeringSeek(target, weight, startNodeFilter, endNodeFilter, nodeFilter);
|
||||
@@ -115,7 +123,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (currentPath == null) { return null; }
|
||||
if (currentPath.NextNode == null) { return null; }
|
||||
if (currentPath.NextNode.Ladders != null)
|
||||
if (currentPath.NextNode.Ladders != null && !currentPath.NextNode.Ladders.Item.NonInteractable)
|
||||
{
|
||||
return currentPath.NextNode.Ladders;
|
||||
}
|
||||
@@ -126,7 +134,10 @@ namespace Barotrauma
|
||||
{
|
||||
var node = currentPath.Nodes[index];
|
||||
if (node == null) { return null; }
|
||||
return node.Ladders;
|
||||
if (node.Ladders != null && !node.Ladders.Item.NonInteractable)
|
||||
{
|
||||
return node.Ladders;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -134,7 +145,19 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null)
|
||||
{
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || Vector2.DistanceSquared(target, currentTarget) > 1);
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any())
|
||||
{
|
||||
//current path calculated relative to a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.Nodes.First().Submarine;
|
||||
if (currentPathSub != character.Submarine && character.Submarine != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
}
|
||||
}
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || targetDiff.LengthSquared() > 1);
|
||||
//find a new path if one hasn't been found yet or the target is different from the current target
|
||||
if (needsNewPath || findPathTimer < -1.0f)
|
||||
{
|
||||
@@ -172,12 +195,13 @@ namespace Barotrauma
|
||||
|
||||
Vector2 diff = DiffToCurrentNode();
|
||||
var collider = character.AnimController.Collider;
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController;
|
||||
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
|
||||
if (!character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.height / 2 + collider.radius)
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.height / 2 + collider.radius)
|
||||
{
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
//if (diff.LengthSquared() < 0.001f) { return -host.Steering; }
|
||||
if (diff == Vector2.Zero) { return Vector2.Zero; }
|
||||
return Vector2.Normalize(diff) * weight;
|
||||
}
|
||||
@@ -186,8 +210,10 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 DiffToCurrentNode()
|
||||
{
|
||||
if (currentPath == null || currentPath.Unreachable) return Vector2.Zero;
|
||||
|
||||
if (currentPath == null || currentPath.Unreachable)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
if (currentPath.Finished)
|
||||
{
|
||||
Vector2 pos2 = host.SimPosition;
|
||||
@@ -197,15 +223,12 @@ namespace Barotrauma
|
||||
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
|
||||
}
|
||||
return currentTarget - pos2;
|
||||
}
|
||||
|
||||
}
|
||||
if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f)
|
||||
{
|
||||
CheckDoorsInPath();
|
||||
}
|
||||
|
||||
}
|
||||
Vector2 pos = host.SimPosition;
|
||||
|
||||
if (character != null && currentPath.CurrentNode != null)
|
||||
{
|
||||
if (CurrentPath.CurrentNode.Submarine != null)
|
||||
@@ -220,19 +243,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
|
||||
//only humanoids can climb ladders
|
||||
if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController;
|
||||
if (canClimb && !isDiving && IsNextLadderSameAsCurrent)
|
||||
{
|
||||
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
|
||||
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
|
||||
var ladders = currentPath.CurrentNode.Ladders;
|
||||
if (character.SelectedConstruction != ladders.Item && ladders.Item.IsInsideTrigger(character.WorldPosition))
|
||||
{
|
||||
currentPath.CurrentNode.Ladders.Item.TryInteract(character, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
var collider = character.AnimController.Collider;
|
||||
if (character.IsClimbing && !isDiving)
|
||||
{
|
||||
@@ -252,13 +273,16 @@ 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;
|
||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||
if (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50)
|
||||
if (isAboveFloor && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
else if (!nextLadderSameAsCurrent)
|
||||
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
{
|
||||
// Try to change the ladder (hatches between two submarines)
|
||||
if (character.SelectedConstruction != nextLadder.Item && nextLadder.Item.IsInsideTrigger(character.WorldPosition))
|
||||
@@ -266,9 +290,6 @@ namespace Barotrauma
|
||||
nextLadder.Item.TryInteract(character, false, true);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
if (nextLadder != null || isAboveFloor)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
@@ -286,7 +307,7 @@ namespace Barotrauma
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
else if (character.AnimController.InWater)
|
||||
else if (!canClimb || character.AnimController.InWater)
|
||||
{
|
||||
// If the character is underwater, we don't need the ladders anymore
|
||||
if (character.IsClimbing && isDiving)
|
||||
@@ -294,49 +315,59 @@ namespace Barotrauma
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
||||
float targetDistance = collider.GetSize().X * multiplier;
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
|
||||
if (character.CurrentHull != currentPath.CurrentNode.CurrentHull)
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken;
|
||||
if (!blockedByDoor)
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distance = horizontalDistance + verticalDistance;
|
||||
if (ConvertUnits.ToSimUnits(distance) < targetDistance)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
||||
float targetDistance = collider.GetSize().X * multiplier;
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
|
||||
if (character.CurrentHull != currentPath.CurrentNode.CurrentHull)
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distance = horizontalDistance + verticalDistance;
|
||||
if (ConvertUnits.ToSimUnits(distance) < targetDistance)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsNextLadderSameAsCurrent)
|
||||
{
|
||||
// Walking horizontally
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
Vector2 velocity = collider.LinearVelocity;
|
||||
// If the character is smaller than this, it fails to use the waypoint nodes, because they are always too high.
|
||||
// If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high.
|
||||
float minHeight = 1;
|
||||
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
|
||||
float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight);
|
||||
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
|
||||
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
|
||||
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken;
|
||||
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
|
||||
float targetDistance = collider.radius * margin;
|
||||
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh)
|
||||
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && !blockedByDoor)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPath.CurrentNode == null) return Vector2.Zero;
|
||||
|
||||
if (currentPath.CurrentNode == null)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
return currentPath.CurrentNode.SimPosition - pos;
|
||||
}
|
||||
|
||||
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
{
|
||||
if (door.IsOpen) { return true; }
|
||||
if (canBreakDoors) { return true; }
|
||||
if (door.Item.NonInteractable) { return false; }
|
||||
if (CanBreakDoors) { return true; }
|
||||
if (door.IsStuck) { return false; }
|
||||
if (!canOpenDoors || character.LockHands) { return false; }
|
||||
if (door.HasIntegratedButtons)
|
||||
@@ -345,7 +376,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return door.Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b)));
|
||||
return door.Item.GetConnectedComponents<Controller>(true).Any(b => !b.Item.NonInteractable && b.HasAccess(character) && (buttonFilter == null || buttonFilter(b)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +412,11 @@ namespace Barotrauma
|
||||
{
|
||||
//the node we're heading towards is the last one in the path, and at a door
|
||||
//the door needs to be open for the character to reach the node
|
||||
shouldBeOpen = true;
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap != null && currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom)
|
||||
{
|
||||
shouldBeOpen = true;
|
||||
door = currentWaypoint.ConnectedDoor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -519,9 +554,9 @@ namespace Barotrauma
|
||||
//non-humanoids can't climb up ladders
|
||||
if (!(character.AnimController is HumanoidAnimController))
|
||||
{
|
||||
if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null &&
|
||||
nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up
|
||||
nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y) //upper node not underwater
|
||||
if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null && nextNode.Waypoint.Ladders.Item.NonInteractable ||
|
||||
(nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up
|
||||
nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y)) //upper node not underwater
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -539,7 +574,10 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f)
|
||||
{
|
||||
penalty += 500.0f;
|
||||
if (!HumanAIController.HasDivingSuit(character))
|
||||
{
|
||||
penalty += 500.0f;
|
||||
}
|
||||
}
|
||||
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,8 +19,8 @@ namespace Barotrauma
|
||||
private Vector2 attachSurfaceNormal;
|
||||
private Submarine attachTargetSubmarine;
|
||||
|
||||
private bool attachToSub;
|
||||
private bool attachToWalls;
|
||||
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;
|
||||
@@ -58,8 +58,8 @@ namespace Barotrauma
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
attachToWalls = element.GetAttributeBool("attachtowalls", false);
|
||||
attachToSub = element.GetAttributeBool("attachtosub", false);
|
||||
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));
|
||||
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
|
||||
@@ -67,11 +67,19 @@ namespace Barotrauma
|
||||
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
|
||||
attachLimbRotation = MathHelper.ToRadians(element.GetAttributeFloat("attachlimbrotation", 0.0f));
|
||||
|
||||
if (Enum.TryParse(element.GetAttributeString("attachlimb", "Head"), out LimbType attachLimbType))
|
||||
string limbString = element.GetAttributeString("attachlimb", null);
|
||||
attachLimb = enemyAI.Character.AnimController.Limbs.FirstOrDefault(l => string.Equals(l.Name, limbString, StringComparison.OrdinalIgnoreCase));
|
||||
if (attachLimb == null)
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.GetLimb(attachLimbType);
|
||||
if (Enum.TryParse(limbString, out LimbType attachLimbType))
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.GetLimb(attachLimbType);
|
||||
}
|
||||
}
|
||||
if (attachLimb == null)
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.MainLimb;
|
||||
}
|
||||
if (attachLimb == null) attachLimb = enemyAI.Character.AnimController.MainLimb;
|
||||
|
||||
enemyAI.Character.OnDeath += OnCharacterDeath;
|
||||
}
|
||||
@@ -108,7 +116,9 @@ namespace Barotrauma
|
||||
//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 DEBUG
|
||||
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
|
||||
#endif
|
||||
DeattachFromBody();
|
||||
return;
|
||||
}
|
||||
@@ -131,7 +141,7 @@ namespace Barotrauma
|
||||
switch (enemyAI.State)
|
||||
{
|
||||
case AIState.Idle:
|
||||
if (attachToWalls && character.Submarine == null && Level.Loaded != null)
|
||||
if (AttachToWalls && character.Submarine == null && Level.Loaded != null)
|
||||
{
|
||||
if (!IsAttached)
|
||||
{
|
||||
@@ -180,8 +190,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float dist = Vector2.Distance(character.SimPosition, wallAttachPos);
|
||||
if (dist < Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f)
|
||||
float squaredDistance = Vector2.DistanceSquared(character.SimPosition, wallAttachPos);
|
||||
float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f;
|
||||
if (squaredDistance < targetDistance * targetDistance)
|
||||
{
|
||||
//close enough to a wall -> attach
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, wallAttachPos);
|
||||
@@ -197,12 +208,13 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case AIState.Attack:
|
||||
case AIState.Aggressive:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
{
|
||||
// is not attached or is attached to something else
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB != attachTargetBody)
|
||||
{
|
||||
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
||||
{
|
||||
@@ -247,16 +259,17 @@ namespace Barotrauma
|
||||
if (attachJoints.Count > 0)
|
||||
{
|
||||
//already attached to the target body, no need to do anything
|
||||
if (attachJoints[0].BodyB == targetBody) return;
|
||||
if (attachJoints[0].BodyB == targetBody) { return; }
|
||||
DeattachFromBody();
|
||||
}
|
||||
|
||||
jointDir = attachLimb.Dir;
|
||||
|
||||
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.Scale * attachLimb.Params.Ragdoll.LimbScale;
|
||||
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
||||
|
||||
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
|
||||
if (jointDir < 0.0f)
|
||||
{
|
||||
transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
||||
}
|
||||
|
||||
float angle = MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2 + attachLimbRotation * attachLimb.Dir;
|
||||
attachLimb.body.SetTransform(attachPos + attachSurfaceNormal * transformedLocalAttachPos.Length(), angle);
|
||||
@@ -274,7 +287,10 @@ namespace Barotrauma
|
||||
|
||||
// Limb scale is already taken into account when creating the collider.
|
||||
Vector2 colliderFront = collider.GetLocalFront();
|
||||
if (jointDir < 0.0f) colliderFront.X = -colliderFront.X;
|
||||
if (jointDir < 0.0f)
|
||||
{
|
||||
colliderFront.X = -colliderFront.X;
|
||||
}
|
||||
collider.SetTransform(attachPos + attachSurfaceNormal * colliderFront.Length(), MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2);
|
||||
|
||||
var colliderJoint = new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -375,9 +375,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
StreamWriter file = new StreamWriter(@"NPCConversations.csv");
|
||||
file.WriteLine(sb.ToString());
|
||||
file.Close();
|
||||
File.WriteAllText("NPCConversations.csv", sb.ToString());
|
||||
}
|
||||
|
||||
private static void WriteConversation(System.Text.StringBuilder sb, NPCConversation conv, int depthIndex)
|
||||
|
||||
@@ -118,7 +118,6 @@ namespace Barotrauma
|
||||
public void TryComplete(float deltaTime)
|
||||
{
|
||||
if (isCompleted) { return; }
|
||||
//if (Abandon && !IsLoop && subObjectives.None()) { return; }
|
||||
if (CheckState()) { return; }
|
||||
// Not ready -> act (can't do foreach because it's possible that the collection is modified in event callbacks.
|
||||
for (int i = 0; i < subObjectives.Count; i++)
|
||||
@@ -201,7 +200,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = CumulatedDevotion * PriorityModifier;
|
||||
Priority = CumulatedDevotion;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -211,7 +210,7 @@ namespace Barotrauma
|
||||
var currentObjective = objectiveManager.CurrentObjective;
|
||||
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
|
||||
{
|
||||
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
|
||||
CumulatedDevotion += Devotion * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
|
||||
+10
-9
@@ -121,6 +121,12 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Check()
|
||||
{
|
||||
if (initialMode == CombatMode.Offensive && Mode != CombatMode.Offensive)
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
return false;
|
||||
}
|
||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (initialMode != CombatMode.Offensive && coolDownTimer <= 0);
|
||||
if (completed)
|
||||
{
|
||||
@@ -465,7 +471,6 @@ namespace Barotrauma
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
retreatTarget = null;
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
RemoveSubObjective(ref seekAmmunition);
|
||||
@@ -482,9 +487,8 @@ namespace Barotrauma
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
Mode = CombatMode.Defensive;
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
RemoveSubObjective(ref followTargetObjective);
|
||||
});
|
||||
if (followTargetObjective != null)
|
||||
{
|
||||
@@ -593,10 +597,7 @@ namespace Barotrauma
|
||||
|
||||
private void Attack(float deltaTime)
|
||||
{
|
||||
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
character.CursorPosition = Enemy.Position;
|
||||
float engageDistance = 500;
|
||||
if (character.CurrentHull != Enemy.CurrentHull && squaredDistance > engageDistance * engageDistance) { return; }
|
||||
if (!character.CanSeeCharacter(Enemy)) { return; }
|
||||
if (Weapon.RequireAimToUse)
|
||||
{
|
||||
@@ -604,7 +605,7 @@ namespace Barotrauma
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
@@ -626,7 +627,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
@@ -636,7 +637,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (WeaponComponent is RepairTool repairTool)
|
||||
{
|
||||
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
|
||||
{
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}, onCompleted: () =>
|
||||
{
|
||||
if (getItemObjective.TargetItem != null)
|
||||
if (getItemObjective?.TargetItem != null)
|
||||
{
|
||||
containedItems.Add(getItemObjective.TargetItem);
|
||||
}
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ namespace Barotrauma
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
|
||||
+1
-6
@@ -1,9 +1,4 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+32
-26
@@ -43,7 +43,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -83,8 +83,9 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority = Math.Min(Priority + dangerFactor * priorityIncrease * deltaTime, 100);
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,34 +94,39 @@ namespace Barotrauma
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var currentHull = character.CurrentHull;
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0;
|
||||
if (!dangerousPressure)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
if (needsEquipment && divingGearObjective == null && !character.LockHands)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
// Don't try to seek diving gear if the pressure is dangerous. Just get out.
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
if (needsEquipment && divingGearObjective == null && !character.LockHands)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
// Don't reset the diving gear objective, because it's possible that there is no diving gear -> seek a safe hull and then reset so that we can check again.
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
resetPriority = true;
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
RemoveSubObjective(ref divingGearObjective);
|
||||
});
|
||||
onCompleted: () =>
|
||||
{
|
||||
resetPriority = true;
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
RemoveSubObjective(ref divingGearObjective);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
|
||||
if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated, onlyBots: true);
|
||||
int totalLeaks = Targets.Count();
|
||||
if (totalLeaks == 0) { return 0; }
|
||||
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
|
||||
|
||||
+49
-18
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -20,7 +21,9 @@ namespace Barotrauma
|
||||
//can be either tags or identifiers
|
||||
private string[] itemIdentifiers;
|
||||
public IEnumerable<string> Identifiers => itemIdentifiers;
|
||||
private Item targetItem, moveToTarget, rootContainer;
|
||||
|
||||
private Item targetItem;
|
||||
private ISpatialEntity moveToTarget;
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
@@ -29,6 +32,8 @@ namespace Barotrauma
|
||||
private float currItemPriority;
|
||||
private bool checkInventory;
|
||||
|
||||
public static float DefaultReach = 100;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
@@ -37,6 +42,7 @@ namespace Barotrauma
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
this.targetItem = targetItem;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1)
|
||||
@@ -62,8 +68,7 @@ namespace Barotrauma
|
||||
if (item != null)
|
||||
{
|
||||
targetItem = item;
|
||||
rootContainer = item.GetRootContainer();
|
||||
moveToTarget = rootContainer ?? item;
|
||||
moveToTarget = item.GetRootInventoryOwner();
|
||||
}
|
||||
return item != null;
|
||||
}
|
||||
@@ -86,6 +91,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isDoneSeeking)
|
||||
{
|
||||
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0;
|
||||
if (dangerousPressure)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Seeking item aborted, because the pressure is dangerous.", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
FindTargetItem();
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
return;
|
||||
@@ -108,7 +122,26 @@ namespace Barotrauma
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
if (character.CanInteractWith(targetItem, out _, checkLinked: false))
|
||||
bool canInteract = false;
|
||||
if (moveToTarget is Character c)
|
||||
{
|
||||
if (character == c)
|
||||
{
|
||||
canInteract = true;
|
||||
moveToTarget = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(c);
|
||||
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
}
|
||||
else if (moveToTarget is Item parentItem)
|
||||
{
|
||||
canInteract = character.CanInteractWith(parentItem, out _, checkLinked: false);
|
||||
}
|
||||
if (canInteract)
|
||||
{
|
||||
var pickable = targetItem.GetComponent<Pickable>();
|
||||
if (pickable == null)
|
||||
@@ -173,17 +206,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (moveToTarget != null)
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
|
||||
{
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
abortCondition = () => targetItem == null || targetItem.GetRootContainer() != rootContainer,
|
||||
abortCondition = () => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = moveToTarget.Name
|
||||
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
@@ -212,9 +245,9 @@ namespace Barotrauma
|
||||
{
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
if (item.Submarine == null) { continue; }
|
||||
if (item.CurrentHull == null) { continue; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
if (itemSub.TeamID != character.TeamID) { continue; }
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
@@ -222,8 +255,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { continue; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
if (itemSub.Info.Type != character.Submarine.Info.Type) { continue; }
|
||||
if (character.Submarine.GetConnectedSubs().None(s => s == itemSub && itemSub.TeamID == character.TeamID && itemSub.Info.Type == character.Submarine.Info.Type)) { continue; }
|
||||
}
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
float itemPriority = 1;
|
||||
@@ -231,8 +264,8 @@ namespace Barotrauma
|
||||
{
|
||||
itemPriority = GetItemPriority(item);
|
||||
}
|
||||
Item rootContainer = item.GetRootContainer();
|
||||
Vector2 itemPos = (rootContainer ?? item).WorldPosition;
|
||||
Entity rootInventoryOwner = item.GetRootInventoryOwner();
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - itemPos.X) + yDist;
|
||||
@@ -243,8 +276,7 @@ namespace Barotrauma
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootContainer ?? item;
|
||||
this.rootContainer = rootContainer;
|
||||
moveToTarget = rootInventoryOwner ?? item;
|
||||
}
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1)
|
||||
{
|
||||
@@ -293,7 +325,6 @@ namespace Barotrauma
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
targetItem = null;
|
||||
moveToTarget = null;
|
||||
rootContainer = null;
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
}
|
||||
|
||||
+37
-15
@@ -80,21 +80,28 @@ namespace Barotrauma
|
||||
this.repeat = repeat;
|
||||
waitUntilPathUnreachable = 3.0f;
|
||||
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
|
||||
CloseEnough = closeEnough;
|
||||
if (Target is Item i)
|
||||
{
|
||||
CloseEnough = Math.Max(CloseEnough, i.InteractDistance + Math.Max(i.Rect.Width, i.Rect.Height) / 2);
|
||||
}
|
||||
else if (Target is Character)
|
||||
{
|
||||
CloseEnough = Math.Max(closeEnough, AIObjectiveGetItem.DefaultReach);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseEnough = closeEnough;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target.ToString()}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
#endif
|
||||
if (objectiveManager.CurrentOrder != null && DialogueIdentifier != null)
|
||||
{
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, true);
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
|
||||
@@ -213,10 +220,19 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (repeat && IsCloseEnough)
|
||||
if (repeat)
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
if (IsCloseEnough)
|
||||
{
|
||||
if (requiredCondition == null || requiredCondition())
|
||||
{
|
||||
if (character.CanSeeTarget(Target))
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
@@ -244,7 +260,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Hull GetTargetHull()
|
||||
public Hull GetTargetHull()
|
||||
{
|
||||
if (Target is Hull h)
|
||||
{
|
||||
@@ -284,13 +300,7 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
|
||||
bool closeEnough = Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
if (closeEnough)
|
||||
{
|
||||
closeEnough = !(Target is Character) || Target is Character c && c.CurrentHull == character.CurrentHull;
|
||||
}
|
||||
return closeEnough;
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +336,9 @@ namespace Barotrauma
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
if (character.CanInteractWith(targetCharacter, CloseEnough)) { IsCompleted = true; }
|
||||
character.SelectCharacter(targetCharacter);
|
||||
if (character.CanInteractWith(targetCharacter, skipDistanceCheck: true)) { IsCompleted = true; }
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -338,6 +350,16 @@ namespace Barotrauma
|
||||
return IsCompleted;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
StopMovement();
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
}
|
||||
base.OnAbandon();
|
||||
}
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
|
||||
+21
-20
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -35,41 +34,43 @@ namespace Barotrauma
|
||||
{
|
||||
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
||||
walkDuration = Rand.Range(0.0f, 10.0f);
|
||||
CalculatePriority();
|
||||
}
|
||||
|
||||
protected override bool Check() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); }
|
||||
|
||||
private float randomTimer;
|
||||
private float randomUpdateInterval = 5;
|
||||
public float Random { get; private set; }
|
||||
|
||||
public void CalculatePriority()
|
||||
public void CalculatePriority(float max = 0)
|
||||
{
|
||||
Random = Rand.Range(0.5f, 1.5f);
|
||||
randomTimer = randomUpdateInterval;
|
||||
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
float initiative = character.GetSkillLevel("initiative");
|
||||
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
//Random = Rand.Range(0.5f, 1.5f);
|
||||
//randomTimer = randomUpdateInterval;
|
||||
//max = max > 0 ? max : Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
//float initiative = character.GetSkillLevel("initiative");
|
||||
//Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
Priority = 1;
|
||||
}
|
||||
|
||||
public override float GetPriority() => Priority;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
{
|
||||
if (randomTimer > 0)
|
||||
{
|
||||
randomTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
CalculatePriority();
|
||||
}
|
||||
}
|
||||
//if (objectiveManager.CurrentObjective == this)
|
||||
//{
|
||||
// if (randomTimer > 0)
|
||||
// {
|
||||
// randomTimer -= deltaTime;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// CalculatePriority();
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -129,7 +130,7 @@ namespace Barotrauma
|
||||
//choose a random available hull
|
||||
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||
bool isCurrentHullAllowed = !IsForbidden(character.CurrentHull);
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, nodeFilter: node =>
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node =>
|
||||
{
|
||||
if (node.Waypoint.CurrentHull == null) { return false; }
|
||||
// Check that there is no unsafe or forbidden hulls on the way to the target
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ namespace Barotrauma
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -204,7 +204,7 @@ namespace Barotrauma
|
||||
{
|
||||
Objectives.Remove(target);
|
||||
ignoreList.Add(target);
|
||||
targetUpdateTimer = 0;
|
||||
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+71
-16
@@ -14,8 +14,11 @@ namespace Barotrauma
|
||||
public const float OrderPriority = 70;
|
||||
public const float RunPriority = 50;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 3;
|
||||
public const float baseDevotion = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Excluding the current order.
|
||||
/// </summary>
|
||||
public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
|
||||
|
||||
private readonly Character character;
|
||||
@@ -88,8 +91,25 @@ namespace Barotrauma
|
||||
|
||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
|
||||
private void ClearIgnored()
|
||||
{
|
||||
if (character.AIController is HumanAIController humanAi)
|
||||
{
|
||||
humanAi.UnreachableHulls.Clear();
|
||||
humanAi.IgnoredItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateAutonomousObjectives()
|
||||
{
|
||||
if (character.IsDead)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to create autonomous orders for a dead character");
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
foreach (var delayedObjective in DelayedObjectives)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(delayedObjective.Value);
|
||||
@@ -99,15 +119,15 @@ namespace Barotrauma
|
||||
AddObjective(new AIObjectiveFindSafety(character, this));
|
||||
AddObjective(new AIObjectiveIdle(character, this));
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
|
||||
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjective)
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab(automaticOrder.identifier);
|
||||
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{automaticOrder.identifier}'"); }
|
||||
var orderPrefab = Order.GetPrefab(autonomousObjective.identifier);
|
||||
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.identifier}'"); }
|
||||
var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, false)?.GetRandom() : null;
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
|
||||
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
var objective = CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier);
|
||||
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
AddObjective(objective, delay: Rand.Value() / 2);
|
||||
@@ -160,7 +180,7 @@ namespace Barotrauma
|
||||
{
|
||||
previousObjective?.OnDeselected();
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
@@ -172,7 +192,21 @@ namespace Barotrauma
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
{
|
||||
CurrentOrder?.Update(deltaTime);
|
||||
if (CurrentOrder != null)
|
||||
{
|
||||
#if DEBUG
|
||||
// Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
|
||||
if (CurrentOrder.IsCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
}
|
||||
else if (!CurrentOrder.CanBeCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
}
|
||||
#endif
|
||||
CurrentOrder.Update(deltaTime);
|
||||
}
|
||||
if (WaitTimer > 0)
|
||||
{
|
||||
WaitTimer -= deltaTime;
|
||||
@@ -195,7 +229,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
Objectives.Remove(objective);
|
||||
}
|
||||
else if (objective != CurrentOrder)
|
||||
else
|
||||
{
|
||||
objective.Update(deltaTime);
|
||||
}
|
||||
@@ -233,7 +267,16 @@ namespace Barotrauma
|
||||
|
||||
public void SetOrder(Order order, string option, Character orderGiver)
|
||||
{
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver);
|
||||
if (character.IsDead)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to set an order for a dead character");
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
ClearIgnored();
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
|
||||
if (CurrentOrder == null)
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
@@ -245,7 +288,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, bool isAutonomous, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
AIObjective newObjective;
|
||||
@@ -284,14 +327,21 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier: priorityModifier, prioritizedItem: order.TargetEntity as Item)
|
||||
{
|
||||
RelevantSkill = order.AppropriateSkill,
|
||||
RequireAdequateSkills = option == "jobspecific"
|
||||
RequireAdequateSkills = isAutonomous
|
||||
};
|
||||
break;
|
||||
case "pumpwater":
|
||||
if (order.TargetItemComponent is Pump targetPump)
|
||||
{
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier);
|
||||
// newObjective.Completed += DismissSelf;
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
// ItemComponent.AIOperate() returns false by default -> We'd have to set IsLoop = false and implement a custom override of AIOperate for the Pump.cs,
|
||||
// if we want that the bot just switches the pump on/off and continues doing something else.
|
||||
// If we want that the bot does the objective and then forgets about it, I think we could do the same plus dismiss when the bot is done.
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -306,9 +356,11 @@ namespace Barotrauma
|
||||
break;
|
||||
case "steer":
|
||||
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
|
||||
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
|
||||
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
|
||||
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
// Don't override unless it's an order by a player
|
||||
@@ -317,12 +369,15 @@ namespace Barotrauma
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
|
||||
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
// Don't override unless it's an order by a player
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
if (newObjective.Abandon) { return null; }
|
||||
break;
|
||||
}
|
||||
return newObjective;
|
||||
|
||||
+47
-16
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -29,6 +28,7 @@ namespace Barotrauma
|
||||
public ItemComponent GetTarget() => useController ? controller : component;
|
||||
|
||||
public Func<bool> completionCondition;
|
||||
private bool isDoneOperating;
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
@@ -47,17 +47,41 @@ namespace Barotrauma
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
Item targetItem = GetTarget()?.Item;
|
||||
ItemComponent target = GetTarget();
|
||||
Item targetItem = target?.Item;
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Item or component of AI Objective Operate item wass null. This shouldn't happen.");
|
||||
DebugConsole.ThrowError("Item or component of AI Objective Operate item was null. This shouldn't happen.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
Priority = 0;
|
||||
return 0.0f;
|
||||
return Priority;
|
||||
}
|
||||
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(GetTarget(), out _))
|
||||
var reactor = component?.Item.GetComponent<Reactor>();
|
||||
if (reactor != null)
|
||||
{
|
||||
switch (Option)
|
||||
{
|
||||
case "shutdown":
|
||||
if (!reactor.PowerOn)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
break;
|
||||
case "powerup":
|
||||
// Check that we don't already have another order that is targeting the same item.
|
||||
// Without this the autonomous objective will tell the bot to turn the reactor on again.
|
||||
if (objectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder != this && operateOrder.GetTarget() == target)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
@@ -68,26 +92,33 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
|
||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float max = objectiveManager.CurrentOrder == this ? MathHelper.Min(AIObjectiveManager.OrderPriority, 90) : AIObjectiveManager.RunPriority - 1;
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip,
|
||||
Entity operateTarget = null, bool useController = false, ItemComponent controller = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option)
|
||||
{
|
||||
this.component = item ?? throw new System.ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
component = item ?? throw new ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
this.requireEquip = requireEquip;
|
||||
this.operateTarget = operateTarget;
|
||||
this.useController = useController;
|
||||
if (useController)
|
||||
if (useController) { this.controller = controller ?? component?.Item?.FindController(); }
|
||||
var target = GetTarget();
|
||||
if (target == null)
|
||||
{
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
controller =
|
||||
component.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
component.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
#if DEBUG
|
||||
throw new Exception("target null");
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
else if (target.Item.NonInteractable)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +153,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
IsCompleted = completionCondition == null || completionCondition();
|
||||
isDoneOperating = completionCondition == null || completionCondition();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -189,12 +220,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
IsCompleted = completionCondition == null || completionCondition();
|
||||
isDoneOperating = completionCondition == null || completionCondition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Check() => IsCompleted && !IsLoop;
|
||||
protected override bool Check() => isDoneOperating && !IsLoop;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -27,6 +26,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.NonInteractable) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
if (pump.Item.CurrentHull == null) { return false; }
|
||||
|
||||
+4
-5
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -52,12 +51,11 @@ namespace Barotrauma
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
}
|
||||
float damagePriority = isPriority ? 1 : MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = isPriority ? 1 : MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character);
|
||||
float isSelected = IsRepairing ? 50 : 0;
|
||||
float devotion = (CumulatedDevotion + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -150,7 +148,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.SelectedConstruction != Item)
|
||||
{
|
||||
if (!Item.TryInteract(character, true, true))
|
||||
if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) &&
|
||||
!Item.TryInteract(character, ignoreRequiredItems: true, forceActionKey: true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
+20
-8
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -56,7 +57,7 @@ namespace Barotrauma
|
||||
{
|
||||
Objectives.Remove(item);
|
||||
ignoreList.Add(item);
|
||||
targetUpdateTimer = 0;
|
||||
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -75,13 +76,9 @@ namespace Barotrauma
|
||||
if (item != character.SelectedConstruction)
|
||||
{
|
||||
float condition = item.ConditionPercentage;
|
||||
if (item.Repairables.All(r => condition >= r.AIRepairThreshold)) { return false; }
|
||||
if (item.Repairables.All(r => condition >= r.RepairThreshold)) { return false; }
|
||||
}
|
||||
}
|
||||
if (RequireAdequateSkills)
|
||||
{
|
||||
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(RelevantSkill))
|
||||
{
|
||||
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier.Equals(RelevantSkill, StringComparison.OrdinalIgnoreCase)))) { return false; }
|
||||
@@ -96,7 +93,7 @@ namespace Barotrauma
|
||||
// Don't stop fixing until done
|
||||
return 100;
|
||||
}
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>(), onlyBots: true);
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>() && !c.Character.IsIncapacitated, onlyBots: true);
|
||||
int items = Targets.Count;
|
||||
bool anyFixers = otherFixers > 0;
|
||||
float ratio = anyFixers ? items / (float)otherFixers : 1;
|
||||
@@ -111,10 +108,24 @@ namespace Barotrauma
|
||||
// Enough fixers
|
||||
return 0;
|
||||
}
|
||||
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
|
||||
if (RequireAdequateSkills)
|
||||
{
|
||||
return Targets.Sum(t => GetTargetPriority(t, character)) * ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetTargetPriority(Item item, Character character)
|
||||
{
|
||||
float damagePriority = MathHelper.Lerp(1, 0, item.Condition / item.MaxCondition);
|
||||
float successFactor = MathHelper.Lerp(0, 1, item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(damagePriority * successFactor, 0, 1));
|
||||
}
|
||||
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
@@ -126,6 +137,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
|
||||
+33
-12
@@ -38,7 +38,19 @@ namespace Barotrauma
|
||||
}
|
||||
this.targetCharacter = targetCharacter;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
character.SelectedCharacter = null;
|
||||
base.OnAbandon();
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
character.SelectedCharacter = null;
|
||||
base.OnCompleted();
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
@@ -46,16 +58,13 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
|
||||
var otherRescuer = targetCharacter.SelectedBy;
|
||||
if (otherRescuer != null && otherRescuer != character)
|
||||
{
|
||||
var otherCharacter = character.SelectedBy;
|
||||
if (otherCharacter != null)
|
||||
{
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
|
||||
}
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetCharacter != character)
|
||||
{
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
@@ -161,13 +170,23 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<string> suitableItemIdentifiers = new List<string>();
|
||||
private readonly List<string> itemNameList = new List<string>();
|
||||
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
|
||||
private readonly Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
string errorMsg = $"{character.Name}: Attempted to update a Rescue objective with no target!";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SteeringManager?.Reset();
|
||||
|
||||
if (!targetCharacter.IsPlayer)
|
||||
{
|
||||
// If the target is a bot, don't let it move
|
||||
targetCharacter.AIController?.SteeringManager.Reset();
|
||||
targetCharacter.AIController?.SteeringManager?.Reset();
|
||||
}
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
@@ -182,6 +201,8 @@ namespace Barotrauma
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
|
||||
{
|
||||
if (affliction == null) { throw new Exception("Affliction was null"); }
|
||||
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
|
||||
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) && currentTreatmentSuitabilities[treatmentSuitability.Key] > 0.0f)
|
||||
@@ -258,7 +279,7 @@ namespace Barotrauma
|
||||
ic.PlaySound(ActionType.OnUse, character);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: character);
|
||||
if (ic.DeleteOnUse)
|
||||
{
|
||||
remove = true;
|
||||
|
||||
+24
-34
@@ -33,46 +33,31 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int otherRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRescueAll>(), onlyBots: true);
|
||||
int targetCount = Targets.Count;
|
||||
bool anyRescuers = otherRescuers > 0;
|
||||
float ratio = anyRescuers ? targetCount / (float)otherRescuers : 1;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
{
|
||||
return Targets.Min(t => GetVitalityFactor(t)) / ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
float multiplier = 1;
|
||||
if (anyRescuers)
|
||||
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
|
||||
{
|
||||
float mySkill = character.GetSkillLevel("medical");
|
||||
int betterRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.Character.Info.Job.GetSkillLevel("medical") >= mySkill, onlyBots: true);
|
||||
if (targetCount / (float)betterRescuers <= 1)
|
||||
{
|
||||
// Enough rescuers
|
||||
return 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundOtherMedics = HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.Info.Job.Prefab.Identifier == "medicaldoctor");
|
||||
if (foundOtherMedics)
|
||||
{
|
||||
if (character.Info.Job.Prefab.Identifier != "medicaldoctor")
|
||||
{
|
||||
// Double the vitality factor -> less likely to take action
|
||||
multiplier = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't do anything if there's a medic on board and we are not a medic
|
||||
return 100;
|
||||
}
|
||||
return Targets.Min(t => GetVitalityFactor(t)) / ratio * multiplier;
|
||||
}
|
||||
float worstCondition = Targets.Min(t => GetVitalityFactor(t));
|
||||
if (Targets.Contains(character))
|
||||
{
|
||||
if (character.Bleeding > 10)
|
||||
{
|
||||
// Enforce the highest priority when bleeding out.
|
||||
worstCondition = 0;
|
||||
}
|
||||
// Boost the priority when wounded.
|
||||
worstCondition /= 2;
|
||||
}
|
||||
return worstCondition;
|
||||
}
|
||||
|
||||
public static float GetVitalityFactor(Character character)
|
||||
{
|
||||
float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
|
||||
float vitality = character.HealthPercentage - (character.Bleeding * 2) - character.Bloodloss + Math.Min(character.Oxygen, 0);
|
||||
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
@@ -92,6 +77,11 @@ namespace Barotrauma
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
@@ -111,10 +101,10 @@ namespace Barotrauma
|
||||
if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
}
|
||||
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
{
|
||||
// Ignore all concious targets that are currently fighting, fleeing or treating characters
|
||||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
|
||||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
|
||||
@@ -85,32 +85,22 @@ namespace Barotrauma
|
||||
//legacy support
|
||||
public readonly string[] AppropriateJobs;
|
||||
public readonly string[] Options;
|
||||
public readonly string[] OptionNames;
|
||||
private readonly Dictionary<string, string> OptionNames;
|
||||
|
||||
public readonly Dictionary<string, Sprite> OptionSprites;
|
||||
|
||||
private readonly Dictionary<string, Sprite> minimapIcons;
|
||||
public Dictionary<string, Sprite> MinimapIcons => IsPrefab ? minimapIcons : Prefab.minimapIcons;
|
||||
|
||||
public readonly float Weight;
|
||||
public readonly bool MustSetTarget;
|
||||
public readonly string AppropriateSkill;
|
||||
|
||||
public bool HasOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPrefab)
|
||||
{
|
||||
return MustSetTarget || Options.Length > 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Prefab.MustSetTarget || Prefab.Options.Length > 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool HasOptions => (IsPrefab ? Options : Prefab.Options).Length > 1;
|
||||
public bool IsPrefab { get; private set; }
|
||||
public readonly bool MustManuallyAssign;
|
||||
|
||||
static Order()
|
||||
public static void Init()
|
||||
{
|
||||
Prefabs = new Dictionary<string, Order>();
|
||||
OrderCategoryIcons = new Dictionary<OrderCategory, Tuple<Sprite, Color>>();
|
||||
@@ -219,25 +209,18 @@ namespace Barotrauma
|
||||
MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false);
|
||||
AppropriateSkill = orderElement.GetAttributeString("appropriateskill", null);
|
||||
|
||||
string translatedOptionNames = TextManager.Get("OrderOptions." + Identifier, true);
|
||||
if (translatedOptionNames == null)
|
||||
var optionNames = TextManager.Get("OrderOptions." + Identifier, true)?.Split(',', ',') ??
|
||||
orderElement.GetAttributeStringArray("optionnames", new string[0]);
|
||||
OptionNames = new Dictionary<string, string>();
|
||||
for (int i = 0; i < Options.Length && i < optionNames.Length; i++)
|
||||
{
|
||||
OptionNames = orderElement.GetAttributeStringArray("optionnames", new string[0]);
|
||||
OptionNames.Add(Options[i], optionNames[i].Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] splitOptionNames = translatedOptionNames.Split(',', ',');
|
||||
OptionNames = new string[Options.Length];
|
||||
for (int i = 0; i < Options.Length && i < splitOptionNames.Length; i++)
|
||||
{
|
||||
OptionNames[i] = splitOptionNames[i].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (OptionNames.Length != Options.Length)
|
||||
if (OptionNames.Count != Options.Length)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Order " + Name + " - the number of option names doesn't match the number of options.");
|
||||
OptionNames = Options;
|
||||
OptionNames.Clear();
|
||||
Options.ForEach(o => OptionNames.Add(o, o));
|
||||
}
|
||||
|
||||
var spriteElement = orderElement.GetChildElement("sprite");
|
||||
@@ -261,6 +244,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
minimapIcons = new Dictionary<string, Sprite>();
|
||||
var minimapIconElements = orderElement.GetChildElements("minimapicon");
|
||||
foreach (XElement minimapIconElement in minimapIconElements)
|
||||
{
|
||||
var id = minimapIconElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
minimapIcons.Add(id, new Sprite(minimapIconElement.GetChildElement("sprite"), lazyLoad: true));
|
||||
}
|
||||
|
||||
IsPrefab = true;
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
}
|
||||
@@ -268,7 +260,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Constructor for order instances
|
||||
/// </summary>
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null)
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
{
|
||||
Prefab = prefab;
|
||||
|
||||
@@ -293,27 +285,23 @@ namespace Barotrauma
|
||||
TargetEntity = targetEntity;
|
||||
if (targetItem != null)
|
||||
{
|
||||
if (UseController) { ConnectedController = FindController(targetItem); }
|
||||
if (UseController)
|
||||
{
|
||||
ConnectedController = targetItem.Item?.FindController();
|
||||
if (ConnectedController == null)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception("Tried to use controller, but couldn't find one");
|
||||
#endif
|
||||
UseController = false;
|
||||
}
|
||||
}
|
||||
TargetEntity = targetItem.Item;
|
||||
TargetItemComponent = targetItem;
|
||||
}
|
||||
|
||||
IsPrefab = false;
|
||||
}
|
||||
|
||||
private Controller FindController(ItemComponent targetComponent)
|
||||
{
|
||||
if (targetComponent?.Item == null) { return null; }
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
return targetComponent.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
targetComponent.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
}
|
||||
|
||||
private bool TryFindController(ItemComponent targetComponent, out Controller controller)
|
||||
{
|
||||
controller = FindController(targetComponent);
|
||||
return controller != null;
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
{
|
||||
@@ -368,7 +356,7 @@ namespace Barotrauma
|
||||
matchingItems.RemoveAll(it => it.NonInteractable);
|
||||
if (UseController)
|
||||
{
|
||||
matchingItems.RemoveAll(i => i.Components.None(c => c.GetType() == ItemComponentType && TryFindController(c, out _)));
|
||||
matchingItems.RemoveAll(i => i.Components.None(c => c.GetType() == ItemComponentType) && !i.TryFindController(out _));
|
||||
}
|
||||
}
|
||||
return matchingItems;
|
||||
@@ -381,5 +369,16 @@ namespace Barotrauma
|
||||
Submarine.MainSub;
|
||||
return GetMatchingItems(submarine, mustBelongToPlayerSub);
|
||||
}
|
||||
|
||||
public string GetOptionName(string id)
|
||||
{
|
||||
return Prefab == null ? OptionNames[id] : Prefab.OptionNames[id];
|
||||
}
|
||||
|
||||
public string GetOptionName(int index)
|
||||
{
|
||||
if (index < 0 || index >= Options.Length) { return null; }
|
||||
return GetOptionName(Options[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
var nodes = new Dictionary<int, PathNode>();
|
||||
foreach (WayPoint wayPoint in wayPoints)
|
||||
{
|
||||
if (wayPoint == null) continue;
|
||||
if (wayPoint == null) { continue; }
|
||||
if (nodes.ContainsKey(wayPoint.ID))
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
{
|
||||
PathNode connectedNode = null;
|
||||
nodes.TryGetValue(linked.ID, out connectedNode);
|
||||
if (connectedNode == null) continue;
|
||||
if (connectedNode == null) { continue; }
|
||||
|
||||
node.Value.connections.Add(connectedNode);
|
||||
}
|
||||
@@ -107,17 +107,17 @@ namespace Barotrauma
|
||||
|
||||
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (Submarine.Unloading) return;
|
||||
if (Submarine.Unloading) { return; }
|
||||
|
||||
var waypoints = sender as IEnumerable<MapEntity>;
|
||||
|
||||
foreach (MapEntity me in waypoints)
|
||||
{
|
||||
WayPoint wp = me as WayPoint;
|
||||
if (me == null) continue;
|
||||
if (me == null) { continue; }
|
||||
|
||||
var node = nodes.Find(n => n.Waypoint == wp);
|
||||
if (node == null) return;
|
||||
if (node == null) { return; }
|
||||
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
@@ -136,10 +136,10 @@ namespace Barotrauma
|
||||
for (int i = 0; i < wp.linkedTo.Count; i++)
|
||||
{
|
||||
WayPoint connected = wp.linkedTo[i] as WayPoint;
|
||||
if (connected == null) continue;
|
||||
if (connected == null) { continue; }
|
||||
|
||||
//already connected, continue
|
||||
if (node.connections.Any(n => n.Waypoint == connected)) continue;
|
||||
if (node.connections.Any(n => n.Waypoint == connected)) { continue; }
|
||||
|
||||
var matchingNode = nodes.Find(n => n.Waypoint == connected);
|
||||
if (matchingNode == null)
|
||||
@@ -201,8 +201,8 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
//if (body.UserData is Submarine) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) continue;
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +236,9 @@ namespace Barotrauma
|
||||
if (InsideSubmarine)
|
||||
{
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null) dist *= 10.0f;
|
||||
if (node.Waypoint.CurrentHull == null) { dist *= 10.0f; }
|
||||
//avoid stopping at a doorway
|
||||
if (node.Waypoint.ConnectedDoor != null) dist *= 10.0f;
|
||||
if (node.Waypoint.ConnectedDoor != null) { dist *= 10.0f; }
|
||||
}
|
||||
if (dist < closestDist || endNode == null)
|
||||
{
|
||||
@@ -251,8 +251,8 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
//if (body.UserData is Submarine) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) continue;
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,31 @@ namespace Barotrauma
|
||||
|
||||
int currentIndex;
|
||||
|
||||
private float? totalLength;
|
||||
|
||||
public bool Unreachable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float TotalLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unreachable) { return float.PositiveInfinity; }
|
||||
if (!totalLength.HasValue)
|
||||
{
|
||||
totalLength = 0.0f;
|
||||
for (int i = 0; i < nodes.Count - 1; i++)
|
||||
{
|
||||
totalLength += Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition);
|
||||
}
|
||||
}
|
||||
return totalLength.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public SteeringPath(bool unreachable = false)
|
||||
{
|
||||
nodes = new List<WayPoint>();
|
||||
@@ -23,10 +42,10 @@ namespace Barotrauma
|
||||
|
||||
public void AddNode(WayPoint node)
|
||||
{
|
||||
if (node == null) return;
|
||||
if (node == null) { return; }
|
||||
nodes.Add(node);
|
||||
|
||||
if (node.CurrentHull == null) HasOutdoorsNodes = true;
|
||||
if (node.CurrentHull == null) { HasOutdoorsNodes = true; }
|
||||
}
|
||||
|
||||
public bool HasOutdoorsNodes
|
||||
@@ -48,10 +67,10 @@ namespace Barotrauma
|
||||
|
||||
public WayPoint PrevNode
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (currentIndex-1 < 0 || currentIndex-1 > nodes.Count - 1) return null;
|
||||
return nodes[currentIndex-1];
|
||||
if (currentIndex - 1 < 0 || currentIndex - 1 > nodes.Count - 1) { return null; }
|
||||
return nodes[currentIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +78,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentIndex < 0 || currentIndex > nodes.Count - 1) return null;
|
||||
if (currentIndex < 0 || currentIndex > nodes.Count - 1) { return null; }
|
||||
return nodes[currentIndex];
|
||||
}
|
||||
}
|
||||
@@ -73,7 +92,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentIndex+1 < 0 || currentIndex+1 > nodes.Count - 1) return null;
|
||||
if (currentIndex + 1 < 0 || currentIndex + 1 > nodes.Count - 1) { return null; }
|
||||
return nodes[currentIndex+1];
|
||||
}
|
||||
}
|
||||
@@ -90,8 +109,8 @@ namespace Barotrauma
|
||||
|
||||
public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f)
|
||||
{
|
||||
if (nodes.Count == 0 || currentIndex>nodes.Count-1) return null;
|
||||
if (Vector2.Distance(simPosition, nodes[currentIndex].SimPosition) < minSimDistance) currentIndex++;
|
||||
if (nodes.Count == 0 || currentIndex > nodes.Count - 1) { return null; }
|
||||
if (Vector2.Distance(simPosition, nodes[currentIndex].SimPosition) < minSimDistance) { currentIndex++; }
|
||||
|
||||
return CurrentNode;
|
||||
}
|
||||
|
||||
@@ -268,7 +268,26 @@ namespace Barotrauma
|
||||
{
|
||||
if (Config.KillAgentsWhenEntityDies)
|
||||
{
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null, isNetworkMessage: true));
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null));
|
||||
if (!string.IsNullOrWhiteSpace(Config.OffensiveAgent))
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
// Kills ALL offensive agents that are near the thalamus. Not the ideal solution,
|
||||
// but as long as spawning is handled via status effects, I don't know if there is any better way.
|
||||
// In practice there shouldn't be terminal cells from different thalamus organisms at the same time.
|
||||
// And if there was, the distance check should prevent killing the agents of a different organism.
|
||||
if (character.SpeciesName.Equals(Config.OffensiveAgent, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Sonar distance is used also for wreck positioning. No wreck should be closer to each other than this.
|
||||
float maxDistance = Sonar.DefaultSonarRange;
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Wreck.WorldPosition) < maxDistance * maxDistance)
|
||||
{
|
||||
character.Kill(CauseOfDeathType.Unknown, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Barotrauma
|
||||
[Serialize("", false)]
|
||||
public string DefensiveAgent { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string OffensiveAgent { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Brain { get; private set; }
|
||||
|
||||
|
||||
@@ -47,12 +47,16 @@ namespace Barotrauma
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
if (!Enabled) { return; }
|
||||
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated) { return; }
|
||||
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated)
|
||||
{
|
||||
//don't enable simple physics on dead/incapacitated characters
|
||||
//the ragdoll controls the movement of incapacitated characters instead of the collider,
|
||||
//but in simple physics mode the ragdoll would get disabled, causing the character to not move at all
|
||||
AnimController.SimplePhysicsEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//don't enable simple physics on dead/incapacitated characters
|
||||
//the ragdoll controls the movement of incapacitated characters instead of the collider,
|
||||
//but in simple physics mode the ragdoll would get disabled, causing the character to not move at all
|
||||
if (!IsRemotePlayer)
|
||||
if (!IsRemotePlayer && !(AIController is HumanAIController))
|
||||
{
|
||||
float characterDist = float.MaxValue;
|
||||
#if CLIENT
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (InWater || !CanWalk)
|
||||
{
|
||||
return TargetMovement.Length() > (SwimSlowParams.MovementSpeed + SwimFastParams.MovementSpeed) / 2.0f;
|
||||
float avg = (SwimSlowParams.MovementSpeed + SwimFastParams.MovementSpeed) / 2.0f;
|
||||
return TargetMovement.LengthSquared() > avg * avg;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+45
-19
@@ -213,7 +213,12 @@ namespace Barotrauma
|
||||
UpdateWalkAnim(deltaTime);
|
||||
}
|
||||
|
||||
//don't flip or drag when simply physics is enabled
|
||||
if (character.SelectedCharacter != null)
|
||||
{
|
||||
DragCharacter(character.SelectedCharacter, deltaTime);
|
||||
}
|
||||
|
||||
//don't flip when simply physics is enabled
|
||||
if (SimplePhysicsEnabled) { return; }
|
||||
|
||||
if (!character.IsRemotePlayer && (character.AIController == null || character.AIController.CanFlip))
|
||||
@@ -248,29 +253,33 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SelectedCharacter != null)
|
||||
{
|
||||
DragCharacter(character.SelectedCharacter, deltaTime);
|
||||
}
|
||||
|
||||
if (!CurrentFishAnimation.Flip) { return; }
|
||||
if (IsStuck) { return; }
|
||||
if (character.AIController != null && !character.AIController.CanFlip) { return; }
|
||||
|
||||
flipCooldown -= deltaTime;
|
||||
|
||||
if (TargetDir != Direction.None && TargetDir != dir)
|
||||
if (TargetDir != Direction.None && TargetDir != dir)
|
||||
{
|
||||
flipTimer += deltaTime;
|
||||
if ((flipTimer > 0.5f && flipCooldown <= 0.0f) || character.IsRemotePlayer)
|
||||
// Speed reductions are not taken into account here. It's intentional: an ai character cannot flip if it's heavily paralyzed (for example).
|
||||
float requiredSpeed = CurrentAnimationParams.MovementSpeed / 2;
|
||||
if (CurrentHull != null)
|
||||
{
|
||||
// Enemy movement speeds are halved inside submarines
|
||||
requiredSpeed /= 2;
|
||||
}
|
||||
bool isMovingFastEnough = Math.Abs(MainLimb.LinearVelocity.X) > requiredSpeed;
|
||||
bool isTryingToMoveHorizontally = Math.Abs(TargetMovement.X) > Math.Abs(TargetMovement.Y);
|
||||
if ((flipTimer > CurrentFishAnimation.FlipDelay && flipCooldown <= 0.0f && ((isMovingFastEnough && isTryingToMoveHorizontally) || IsMovingBackwards))
|
||||
|| character.IsRemotePlayer)
|
||||
{
|
||||
Flip();
|
||||
if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
|
||||
{
|
||||
Mirror();
|
||||
Mirror(CurrentSwimParams != null ? CurrentSwimParams.MirrorLerp : true);
|
||||
}
|
||||
flipTimer = 0.0f;
|
||||
flipCooldown = 1.0f;
|
||||
flipCooldown = CurrentFishAnimation.FlipCooldown;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -295,7 +304,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//stop dragging if there's something between the pull limb and the target
|
||||
Vector2 sourceSimPos = mouthLimb.SimPosition;
|
||||
Vector2 sourceSimPos = SimplePhysicsEnabled ? character.SimPosition : mouthLimb.SimPosition;
|
||||
Vector2 targetSimPos = target.SimPosition;
|
||||
if (character.Submarine != null && character.SelectedCharacter.Submarine == null)
|
||||
{
|
||||
@@ -317,7 +326,7 @@ namespace Barotrauma
|
||||
float eatSpeed = dmg / ((float)Math.Sqrt(Math.Max(target.Mass, 1)) * 10);
|
||||
eatTimer += deltaTime * eatSpeed;
|
||||
|
||||
Vector2 mouthPos = GetMouthPosition().Value;
|
||||
Vector2 mouthPos = SimplePhysicsEnabled ? character.SimPosition : GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = character.Submarine == null ? ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition;
|
||||
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
@@ -525,6 +534,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
|
||||
@@ -550,10 +560,12 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
if (Limbs[i].SteerForce <= 0.0f) { continue; }
|
||||
var limb = Limbs[i];
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.SteerForce <= 0.0f) { continue; }
|
||||
if (!Collider.PhysEnabled) { continue; }
|
||||
Vector2 pullPos = Limbs[i].PullJointWorldAnchorA;
|
||||
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
|
||||
Vector2 pullPos = limb.PullJointWorldAnchorA;
|
||||
limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
|
||||
}
|
||||
|
||||
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
|
||||
@@ -604,6 +616,14 @@ namespace Barotrauma
|
||||
float stepLift = TargetMovement.X == 0.0f ? 0 :
|
||||
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
|
||||
|
||||
float limpAmount = character.GetLegPenalty();
|
||||
if (limpAmount > 0)
|
||||
{
|
||||
float walkPosX = (float)Math.Cos(WalkPos);
|
||||
//make the footpos oscillate when limping
|
||||
limpAmount = Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f) * Dir;
|
||||
}
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso != null)
|
||||
{
|
||||
@@ -613,7 +633,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (TorsoPosition.HasValue)
|
||||
{
|
||||
Vector2 pos = colliderBottom + new Vector2(0, TorsoPosition.Value + stepLift);
|
||||
Vector2 pos = colliderBottom + new Vector2(limpAmount, TorsoPosition.Value + stepLift);
|
||||
|
||||
if (torso != mainLimb)
|
||||
{
|
||||
@@ -635,7 +655,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (HeadPosition.HasValue)
|
||||
{
|
||||
Vector2 pos = colliderBottom + new Vector2(0, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier);
|
||||
Vector2 pos = colliderBottom + new Vector2(limpAmount, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier);
|
||||
|
||||
if (head != mainLimb)
|
||||
{
|
||||
@@ -670,6 +690,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
|
||||
@@ -766,6 +787,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
#if CLIENT
|
||||
if (limb.LightSource != null)
|
||||
{
|
||||
@@ -821,6 +843,7 @@ namespace Barotrauma
|
||||
base.Flip();
|
||||
foreach (Limb l in Limbs)
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
if (!l.DoesFlip) { continue; }
|
||||
if (RagdollParams.IsSpritesheetOrientationHorizontal)
|
||||
{
|
||||
@@ -838,10 +861,13 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb l in Limbs)
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
|
||||
TrySetLimbPosition(l,
|
||||
centerOfMass,
|
||||
new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
|
||||
lerp);
|
||||
|
||||
l.body.PositionSmoothingFactor = 0.8f;
|
||||
|
||||
if (!l.DoesFlip) { continue; }
|
||||
@@ -862,7 +888,7 @@ namespace Barotrauma
|
||||
if (diff < 100.0f)
|
||||
{
|
||||
character.SelectedCharacter.AnimController.SetPosition(
|
||||
new Vector2(centerOfMass.X - diff, character.SelectedCharacter.SimPosition.Y), lerp: true);
|
||||
new Vector2(centerOfMass.X - diff, character.SelectedCharacter.SimPosition.Y), lerp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-36
@@ -323,7 +323,14 @@ namespace Barotrauma
|
||||
|
||||
levitatingCollider = true;
|
||||
ColliderIndex = Crouching ? 1 : 0;
|
||||
if (!Crouching && ColliderIndex == 1) Crouching = true;
|
||||
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false)
|
||||
{
|
||||
Crouching = false;
|
||||
}
|
||||
else if (!Crouching && ColliderIndex == 1)
|
||||
{
|
||||
Crouching = true;
|
||||
}
|
||||
|
||||
//stun (= disable the animations) if the ragdoll receives a large enough impact
|
||||
if (strongestImpact > 0.0f)
|
||||
@@ -542,12 +549,6 @@ namespace Barotrauma
|
||||
Limb leftLeg = GetLimb(LimbType.LeftLeg);
|
||||
Limb rightLeg = GetLimb(LimbType.RightLeg);
|
||||
|
||||
float limpAmount =
|
||||
character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) +
|
||||
character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true) +
|
||||
character.CharacterHealth.GetAfflictionStrength("spaceherpes");
|
||||
limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f);
|
||||
|
||||
float walkCycleMultiplier = 1.0f;
|
||||
if (Stairs != null)
|
||||
{
|
||||
@@ -582,6 +583,11 @@ namespace Barotrauma
|
||||
stepSize.Y *= walkPosY;
|
||||
|
||||
float footMid = colliderPos.X;
|
||||
|
||||
var herpes = character.CharacterHealth.GetAffliction("spaceherpes", false);
|
||||
float herpesAmount = herpes == null ? 0 : herpes.Strength / herpes.Prefab.MaxStrength;
|
||||
float legDamage = character.GetLegPenalty(startSum: -0.1f) * 1.1f;
|
||||
float limpAmount = MathHelper.Lerp(0, 1, legDamage + herpesAmount);
|
||||
if (limpAmount > 0.0f)
|
||||
{
|
||||
//make the footpos oscillate when limping
|
||||
@@ -652,6 +658,7 @@ namespace Barotrauma
|
||||
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
|
||||
|
||||
float y = colliderPos.Y + stepLift;
|
||||
|
||||
if (TorsoPosition.HasValue)
|
||||
{
|
||||
y += TorsoPosition.Value;
|
||||
@@ -690,6 +697,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
MoveLimb(limb, limb.SimPosition + move, 15.0f, true);
|
||||
}
|
||||
|
||||
@@ -947,8 +955,6 @@ namespace Barotrauma
|
||||
|
||||
torso.body.MoveToPos(Collider.SimPosition + new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f, 5.0f);
|
||||
|
||||
if (TargetMovement == Vector2.Zero) { return; }
|
||||
|
||||
movement = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
|
||||
|
||||
if (TorsoAngle.HasValue)
|
||||
@@ -1001,29 +1007,31 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
WalkPos += movement.Length();
|
||||
legCyclePos += Vector2.Normalize(movement).Length();
|
||||
legCyclePos += Math.Min(movement.LengthSquared() + Collider.AngularVelocity, 1.0f);
|
||||
handCyclePos += MathHelper.ToRadians(CurrentSwimParams.HandCycleSpeed) * Math.Sign(movement.X);
|
||||
|
||||
var waist = GetLimb(LimbType.Waist);
|
||||
footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
|
||||
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength / character.SpeedMultiplier) * CurrentSwimParams.LegMoveAmount, 0.0f);
|
||||
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount, 0.0f);
|
||||
transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
|
||||
|
||||
float torque = CurrentSwimParams.FootRotateStrength * character.SpeedMultiplier * (1.2f - character.GetLegPenalty());
|
||||
if (rightFoot != null && !rightFoot.Disabled)
|
||||
{
|
||||
FootIK(rightFoot, footPos - transformedFootPos, CurrentSwimParams.FootRotateStrength, CurrentSwimParams.FootRotateStrength, CurrentSwimParams.FootAngleInRadians);
|
||||
FootIK(rightFoot, footPos - transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians);
|
||||
}
|
||||
if (leftFoot != null && !leftFoot.Disabled)
|
||||
{
|
||||
FootIK(leftFoot, footPos + transformedFootPos, CurrentSwimParams.FootRotateStrength, CurrentSwimParams.FootRotateStrength, CurrentSwimParams.FootAngleInRadians);
|
||||
FootIK(leftFoot, footPos + transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians);
|
||||
}
|
||||
|
||||
handPos = (torso.SimPosition + head.SimPosition) / 2.0f;
|
||||
|
||||
//at the surface, not moving sideways -> hands just float around
|
||||
if (!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0)
|
||||
//at the surface, not moving sideways OR not moving at all
|
||||
// -> hands just float around
|
||||
if ((!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0) || TargetMovement.LengthSquared() < 0.001f)
|
||||
{
|
||||
handPos.X = handPos.X + Dir * 0.6f;
|
||||
handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.6f, torso.Rotation);
|
||||
|
||||
float wobbleAmount = 0.1f;
|
||||
|
||||
@@ -1060,7 +1068,7 @@ namespace Barotrauma
|
||||
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
|
||||
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
|
||||
|
||||
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
|
||||
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()));
|
||||
}
|
||||
|
||||
if (leftHand != null && !leftHand.Disabled)
|
||||
@@ -1069,7 +1077,7 @@ namespace Barotrauma
|
||||
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
|
||||
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
|
||||
|
||||
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
|
||||
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,31 +1259,39 @@ namespace Barotrauma
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
//if the head is moving, try to protect it with the hands
|
||||
if (head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
|
||||
|
||||
if (head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
|
||||
{
|
||||
//if the head is moving, try to protect it with the hands
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
//move hands in front of the head in the direction of the movement
|
||||
Vector2 protectPos = head.SimPosition + Vector2.Normalize(head.LinearVelocity);
|
||||
if (!rightHand.IsSevered) HandIK(rightHand, protectPos, strength * 0.1f);
|
||||
if (!leftHand.IsSevered) HandIK(leftHand, protectPos, strength * 0.1f);
|
||||
if (rightHand != null && !rightHand.IsSevered)
|
||||
{
|
||||
HandIK(rightHand, protectPos, strength * 0.1f);
|
||||
}
|
||||
if (leftHand != null && !leftHand.IsSevered)
|
||||
{
|
||||
HandIK(leftHand, protectPos, strength * 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
if (torso == null) { return; }
|
||||
//attempt to make legs stay in a straight line with the torso to prevent the character from doing a split
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh);
|
||||
if (thigh.IsSevered) continue;
|
||||
if (thigh == null) { continue; }
|
||||
if (thigh.IsSevered) { continue; }
|
||||
|
||||
float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation));
|
||||
float thighTorque = thighDiff * thigh.Mass * Math.Sign(torso.Rotation - thigh.Rotation) * 5.0f;
|
||||
thigh.body.ApplyTorque(thighTorque * strength);
|
||||
|
||||
var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg);
|
||||
if (leg.IsSevered) continue;
|
||||
if (leg == null || leg.IsSevered) { continue; }
|
||||
float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation));
|
||||
float legTorque = legDiff * leg.Mass * Math.Sign(torso.Rotation - leg.Rotation) * 5.0f;
|
||||
leg.body.ApplyTorque(legTorque * strength);
|
||||
@@ -1697,7 +1713,7 @@ namespace Barotrauma
|
||||
|
||||
if (holdable.ControlPose)
|
||||
{
|
||||
head.body.SmoothRotate(itemAngle);
|
||||
head?.body.SmoothRotate(itemAngle);
|
||||
|
||||
if (TargetMovement == Vector2.Zero && inWater)
|
||||
{
|
||||
@@ -1719,13 +1735,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.SelectedItems[0] == item)
|
||||
{
|
||||
if (rightHand.IsSevered) return;
|
||||
if (rightHand == null || rightHand.IsSevered) { return; }
|
||||
transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0];
|
||||
itemAngle = (rightHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir);
|
||||
}
|
||||
else if (character.SelectedItems[1] == item)
|
||||
{
|
||||
if (leftHand.IsSevered) return;
|
||||
if (leftHand == null || leftHand.IsSevered) { return; }
|
||||
transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
|
||||
itemAngle = (leftHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir);
|
||||
}
|
||||
@@ -1734,12 +1750,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.SelectedItems[0] == item)
|
||||
{
|
||||
if (rightHand.IsSevered) return;
|
||||
if (rightHand == null || rightHand.IsSevered) { return; }
|
||||
rightHand.Disabled = true;
|
||||
}
|
||||
if (character.SelectedItems[1] == item)
|
||||
{
|
||||
if (leftHand.IsSevered) return;
|
||||
if (leftHand == null || leftHand.IsSevered) { return; }
|
||||
leftHand.Disabled = true;
|
||||
}
|
||||
|
||||
@@ -1797,17 +1813,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir, setPrevTransform: false);
|
||||
item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir, setPrevTransform: false);
|
||||
|
||||
if (!isClimbing)
|
||||
if (!isClimbing && !character.IsIncapacitated)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (character.SelectedItems[i] != item) continue;
|
||||
if (itemPos == Vector2.Zero) continue;
|
||||
|
||||
if (character.SelectedItems[i] != item || itemPos == Vector2.Zero) { continue; }
|
||||
Limb hand = (i == 0) ? rightHand : leftHand;
|
||||
|
||||
HandIK(hand, transformedHoldPos + transformedHandlePos[i]);
|
||||
}
|
||||
}
|
||||
@@ -1987,6 +2000,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
|
||||
bool mirror = false;
|
||||
bool flipAngle = false;
|
||||
bool wrapAngle = false;
|
||||
|
||||
@@ -47,7 +47,9 @@ namespace Barotrauma
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
protected Hull currentHull;
|
||||
|
||||
|
||||
private bool accessRemovedCharacterErrorShown;
|
||||
|
||||
private Limb[] limbs;
|
||||
public Limb[] Limbs
|
||||
{
|
||||
@@ -55,16 +57,17 @@ namespace Barotrauma
|
||||
{
|
||||
if (limbs == null)
|
||||
{
|
||||
string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this);
|
||||
#if DEBUG || UNSTABLE
|
||||
errorMsg += '\n' + Environment.StackTrace;
|
||||
#endif
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Ragdoll.Limbs:AccessRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace);
|
||||
|
||||
if (!accessRemovedCharacterErrorShown)
|
||||
{
|
||||
string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this);
|
||||
errorMsg += '\n' + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Ragdoll.Limbs:AccessRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace);
|
||||
accessRemovedCharacterErrorShown = true;
|
||||
}
|
||||
return new Limb[0];
|
||||
}
|
||||
return limbs;
|
||||
@@ -97,12 +100,17 @@ namespace Barotrauma
|
||||
|
||||
private bool simplePhysicsEnabled;
|
||||
|
||||
public Character Character => character;
|
||||
protected Character character;
|
||||
|
||||
protected float strongestImpact;
|
||||
|
||||
private float splashSoundTimer;
|
||||
|
||||
//the ragdoll builds a "tolerance" to the flow force when being pushed by water.
|
||||
//Allows sudden forces (breach, letting water through a door) to heavily push the character around while ensuring flowing water won't make the characters permanently stuck.
|
||||
private float flowForceTolerance, flowStunTolerance;
|
||||
|
||||
//the movement speed of the ragdoll
|
||||
public Vector2 movement;
|
||||
//the target speed towards which movement is interpolated
|
||||
@@ -156,8 +164,8 @@ namespace Barotrauma
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == colliderIndex || collider == null) return;
|
||||
if (value >= collider.Count || value < 0) return;
|
||||
if (value == colliderIndex || collider == null) { return; }
|
||||
if (value >= collider.Count || value < 0) { return; }
|
||||
|
||||
if (collider[colliderIndex].height < collider[value].height)
|
||||
{
|
||||
@@ -165,10 +173,9 @@ namespace Barotrauma
|
||||
pos1.Y -= collider[colliderIndex].height * ColliderHeightFromFloor;
|
||||
Vector2 pos2 = pos1;
|
||||
pos2.Y += collider[value].height * 1.1f;
|
||||
if (GameMain.World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall))) return;
|
||||
if (GameMain.World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall))) { return; }
|
||||
}
|
||||
|
||||
|
||||
Vector2 pos = collider[colliderIndex].SimPosition;
|
||||
pos.Y -= collider[colliderIndex].height * 0.5f;
|
||||
pos.Y += collider[value].height * 0.5f;
|
||||
@@ -216,7 +223,7 @@ namespace Barotrauma
|
||||
mainLimb = torso ?? head;
|
||||
if (mainLimb == null)
|
||||
{
|
||||
mainLimb = Limbs.FirstOrDefault();
|
||||
mainLimb = Limbs.FirstOrDefault(l => !l.IsSevered && !l.ignoreCollisions);
|
||||
}
|
||||
}
|
||||
return mainLimb;
|
||||
@@ -238,13 +245,13 @@ namespace Barotrauma
|
||||
get { return simplePhysicsEnabled; }
|
||||
set
|
||||
{
|
||||
if (value == simplePhysicsEnabled) return;
|
||||
if (value == simplePhysicsEnabled) { return; }
|
||||
|
||||
simplePhysicsEnabled = value;
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) continue;
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.body == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Limb has no body! (" + (character != null ? character.Name : "Unknown character") + ", " + limb.type.ToString());
|
||||
@@ -296,8 +303,6 @@ namespace Barotrauma
|
||||
public float ImpactTolerance => RagdollParams.ImpactTolerance;
|
||||
public bool Draggable => RagdollParams.Draggable;
|
||||
public bool CanEnterSubmarine => RagdollParams.CanEnterSubmarine;
|
||||
public bool CanAttackSubmarine => Limbs.Any(l => l.attack != null && l.attack.IsValidTarget(AttackTarget.Structure));
|
||||
public bool CanAttackCharacters => Limbs.Any(l => l.attack != null && l.attack.IsValidTarget(AttackTarget.Character));
|
||||
|
||||
public float Dir => dir == Direction.Left ? -1.0f : 1.0f;
|
||||
|
||||
@@ -324,6 +329,7 @@ namespace Barotrauma
|
||||
Submarine currSubmarine = currentHull?.Submarine;
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
limb.body.Submarine = currSubmarine;
|
||||
}
|
||||
Collider.Submarine = currSubmarine;
|
||||
@@ -377,7 +383,7 @@ namespace Barotrauma
|
||||
foreach (var kvp in items)
|
||||
{
|
||||
int id = kvp.Key.ID;
|
||||
// This can be the case if we manipulate the ragdoll in runtime (husk appendage, limb severance)
|
||||
// This can be the case if we manipulate the ragdoll at runtime (husk appendage, limb removal in the character editor)
|
||||
if (id > limbs.Length - 1) { continue; }
|
||||
var limb = limbs[id];
|
||||
var itemList = kvp.Value;
|
||||
@@ -438,7 +444,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (LimbJoint joint in LimbJoints)
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(joint)) { GameMain.World.Remove(joint); }
|
||||
if (GameMain.World.JointList.Contains(joint.Joint)) { GameMain.World.Remove(joint.Joint); }
|
||||
}
|
||||
}
|
||||
DebugConsole.Log($"Creating joints from {RagdollParams.Name}.");
|
||||
@@ -523,7 +529,7 @@ namespace Barotrauma
|
||||
public void AddJoint(JointParams jointParams)
|
||||
{
|
||||
LimbJoint joint = new LimbJoint(Limbs[jointParams.Limb1], Limbs[jointParams.Limb2], jointParams, this);
|
||||
GameMain.World.Add(joint);
|
||||
GameMain.World.Add(joint.Joint);
|
||||
for (int i = 0; i < LimbJoints.Length; i++)
|
||||
{
|
||||
if (LimbJoints[i] != null) continue;
|
||||
@@ -606,7 +612,7 @@ namespace Barotrauma
|
||||
limb.Remove();
|
||||
foreach (LimbJoint limbJoint in attachedJoints)
|
||||
{
|
||||
GameMain.World.Remove(limbJoint);
|
||||
GameMain.World.Remove(limbJoint.Joint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +712,7 @@ namespace Barotrauma
|
||||
float impactDamage = Math.Min((impact - ImpactTolerance) * ImpactDamageMultiplayer, character.MaxVitality * MaxImpactDamage);
|
||||
|
||||
character.LastDamageSource = null;
|
||||
character.AddDamage(impactPos, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(impactDamage) }, 0.0f, true);
|
||||
character.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), 0.0f, true);
|
||||
strongestImpact = Math.Max(strongestImpact, impact - ImpactTolerance);
|
||||
character.ApplyStatusEffects(ActionType.OnImpact, 1.0f);
|
||||
//briefly disable impact damage
|
||||
@@ -720,12 +726,14 @@ namespace Barotrauma
|
||||
|
||||
ImpactProjSpecific(impact, f1.Body);
|
||||
}
|
||||
|
||||
public void SeverLimbJoint(LimbJoint limbJoint, bool playSound = true)
|
||||
|
||||
private readonly List<Limb> connectedLimbs = new List<Limb>();
|
||||
private readonly List<LimbJoint> checkedJoints = new List<LimbJoint>();
|
||||
public bool SeverLimbJoint(LimbJoint limbJoint)
|
||||
{
|
||||
if (!limbJoint.CanBeSevered || limbJoint.IsSevered)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
limbJoint.IsSevered = true;
|
||||
@@ -738,22 +746,29 @@ namespace Barotrauma
|
||||
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
|
||||
List<Limb> connectedLimbs = new List<Limb>();
|
||||
List<LimbJoint> checkedJoints = new List<LimbJoint>();
|
||||
|
||||
connectedLimbs.Clear();
|
||||
checkedJoints.Clear();
|
||||
GetConnectedLimbs(connectedLimbs, checkedJoints, MainLimb);
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (connectedLimbs.Contains(limb)) { continue; }
|
||||
limb.IsSevered = true;
|
||||
if (limb.type == LimbType.RightHand)
|
||||
{
|
||||
character.SelectedItems[0]?.Drop(character);
|
||||
}
|
||||
else if (limb.type == LimbType.LeftHand)
|
||||
{
|
||||
character.SelectedItems[1]?.Drop(character);
|
||||
}
|
||||
}
|
||||
|
||||
SeverLimbJointProjSpecific(limbJoint, playSound: true);
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound);
|
||||
@@ -764,7 +779,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (LimbJoint joint in LimbJoints)
|
||||
{
|
||||
if (joint.IsSevered || checkedJoints.Contains(joint)) continue;
|
||||
if (joint.IsSevered || checkedJoints.Contains(joint)) { continue; }
|
||||
if (joint.LimbA == limb)
|
||||
{
|
||||
if (!connectedLimbs.Contains(joint.LimbB))
|
||||
@@ -861,7 +876,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
if (Limbs[i] == null) continue;
|
||||
if (Limbs[i] == null) { continue; }
|
||||
Limbs[i].PullJointEnabled = false;
|
||||
}
|
||||
}
|
||||
@@ -982,8 +997,8 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) continue;
|
||||
if (limb.body.FarseerBody.ContactList == null) continue;
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.body.FarseerBody.ContactList == null) { continue; }
|
||||
|
||||
ContactEdge ce = limb.body.FarseerBody.ContactList;
|
||||
while (ce != null && ce.Contact != null)
|
||||
@@ -995,7 +1010,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) continue;
|
||||
if (limb.IsSevered) { continue; }
|
||||
limb.body.LinearVelocity += velocityChange;
|
||||
}
|
||||
|
||||
@@ -1020,15 +1035,15 @@ namespace Barotrauma
|
||||
Category collisionCategory = (IgnorePlatforms) ?
|
||||
wall | Physics.CollisionProjectile | Physics.CollisionStairs
|
||||
: wall | Physics.CollisionProjectile | Physics.CollisionPlatform | Physics.CollisionStairs;
|
||||
|
||||
if (collisionCategory == prevCollisionCategory) return;
|
||||
|
||||
if (collisionCategory == prevCollisionCategory) { return; }
|
||||
prevCollisionCategory = collisionCategory;
|
||||
|
||||
Collider.CollidesWith = collisionCategory | Physics.CollisionItemBlocking;
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.ignoreCollisions || limb.IsSevered) continue;
|
||||
if (limb.ignoreCollisions || limb.IsSevered) { continue; }
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1081,8 +1096,6 @@ namespace Barotrauma
|
||||
CheckDistFromCollider();
|
||||
UpdateCollisionCategories();
|
||||
|
||||
Vector2 flowForce = Vector2.Zero;
|
||||
|
||||
FindHull();
|
||||
PreventOutsideCollision();
|
||||
|
||||
@@ -1104,10 +1117,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForce = GetFlowForce();
|
||||
|
||||
headInWater = false;
|
||||
|
||||
inWater = false;
|
||||
if (currentHull.WaterVolume > currentHull.Volume * 0.95f)
|
||||
{
|
||||
@@ -1129,7 +1139,7 @@ namespace Barotrauma
|
||||
if (lowerHull != null) floorY = ConvertUnits.ToSimUnits(lowerHull.Rect.Y - lowerHull.Rect.Height);
|
||||
}
|
||||
}
|
||||
float standHeight =
|
||||
float standHeight =
|
||||
HeadPosition.HasValue ? HeadPosition.Value :
|
||||
TorsoPosition.HasValue ? TorsoPosition.Value :
|
||||
Collider.GetMaxExtent() * 0.5f;
|
||||
@@ -1140,10 +1150,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (flowForce.LengthSquared() > 0.001f)
|
||||
{
|
||||
Collider.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
UpdateHullFlowForces(deltaTime);
|
||||
|
||||
if (currentHull == null ||
|
||||
currentHull.WaterVolume > currentHull.Volume * 0.95f ||
|
||||
@@ -1152,7 +1159,6 @@ namespace Barotrauma
|
||||
Collider.ApplyWaterForces();
|
||||
}
|
||||
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
//find the room which the limb is in
|
||||
@@ -1177,14 +1183,7 @@ namespace Barotrauma
|
||||
if (limb.Position.Y < limbHull.Surface)
|
||||
{
|
||||
limb.inWater = true;
|
||||
|
||||
if (flowForce.LengthSquared() > 0.001f)
|
||||
{
|
||||
limb.body.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
|
||||
surfaceY = limbHull.Surface;
|
||||
|
||||
if (limb.type == LimbType.Head)
|
||||
{
|
||||
headInWater = true;
|
||||
@@ -1256,6 +1255,12 @@ namespace Barotrauma
|
||||
private int validityResets;
|
||||
private bool CheckValidity()
|
||||
{
|
||||
if (limbs == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to check the validity of a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this));
|
||||
Invalid = true;
|
||||
return false;
|
||||
}
|
||||
bool isColliderValid = CheckValidity(Collider);
|
||||
bool limbsValid = true;
|
||||
foreach (Limb limb in limbs)
|
||||
@@ -1278,8 +1283,8 @@ namespace Barotrauma
|
||||
Collider.SetTransform(Vector2.Zero, 0.0f);
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
limb.body.SetTransform(Collider.SimPosition, 0.0f);
|
||||
limb.body.ResetDynamics();
|
||||
limb.body?.SetTransform(Collider.SimPosition, 0.0f);
|
||||
limb.body?.ResetDynamics();
|
||||
}
|
||||
Frozen = true;
|
||||
}
|
||||
@@ -1353,6 +1358,74 @@ namespace Barotrauma
|
||||
|
||||
partial void Splash(Limb limb, Hull limbHull);
|
||||
|
||||
private void UpdateHullFlowForces(float deltaTime)
|
||||
{
|
||||
if (currentHull == null) { return; }
|
||||
|
||||
const float StunForceThreshold = 5.0f;
|
||||
const float StunDuration = 0.5f;
|
||||
const float ToleranceIncreaseSpeed = 5.0f;
|
||||
const float ToleranceDecreaseSpeed = 1.0f;
|
||||
|
||||
//how much distance to a gap affects the force it exerts on the character
|
||||
const float DistanceFactor = 0.5f;
|
||||
const float ForceMultiplier = 0.035f;
|
||||
|
||||
Vector2 flowForce = Vector2.Zero;
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.Open <= 0.0f || !gap.linkedTo.Contains(currentHull) || gap.LerpedFlowForce.LengthSquared() < 0.01f) { continue; }
|
||||
float dist = Vector2.Distance(MainLimb.WorldPosition, gap.WorldPosition) * DistanceFactor;
|
||||
flowForce += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) * ForceMultiplier);
|
||||
}
|
||||
|
||||
//throwing conscious/moving characters around takes more force -> double the flow force
|
||||
if (character.CanMove) { flowForce *= 2.0f; }
|
||||
|
||||
float flowForceMagnitude = flowForce.Length();
|
||||
float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length;
|
||||
//if the force strong enough, stun the character to let it get thrown around by the water
|
||||
if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold)
|
||||
{
|
||||
character.Stun = Math.Max(character.Stun, StunDuration);
|
||||
flowStunTolerance = Math.Max(flowStunTolerance, flowForceMagnitude);
|
||||
}
|
||||
|
||||
if (character == Character.Controlled && inWater && Screen.Selected?.Cam != null)
|
||||
{
|
||||
float shakeStrength = Math.Min(flowForceMagnitude / 10.0f, 5.0f) * limbMultipier;
|
||||
Screen.Selected.Cam.Shake = Math.Max(Screen.Selected.Cam.Shake, shakeStrength);
|
||||
}
|
||||
|
||||
if (flowForceMagnitude > 0.0001f)
|
||||
{
|
||||
flowForce = Vector2.Normalize(flowForce) * Math.Max(flowForceMagnitude - flowForceTolerance, 0.0f);
|
||||
}
|
||||
|
||||
if (flowForceTolerance <= flowForceMagnitude * 1.5f && inWater)
|
||||
{
|
||||
//build up "tolerance" to the flow force
|
||||
//ensures the character won't get permanently stuck by forces, while allowing sudden changes in flow to push the character hard
|
||||
flowForceTolerance += deltaTime * ToleranceIncreaseSpeed;
|
||||
flowStunTolerance = Math.Max(flowStunTolerance, flowForceTolerance);
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForceTolerance = Math.Max(flowForceTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
|
||||
flowStunTolerance = Math.Max(flowStunTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
|
||||
}
|
||||
|
||||
if (flowForce.LengthSquared() > 0.001f)
|
||||
{
|
||||
Collider.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
if (!limb.inWater) { continue; }
|
||||
limb.body.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshFloorY(Limb refLimb = null, bool ignoreStairs = false)
|
||||
{
|
||||
PhysicsBody refBody = refLimb == null ? Collider : refLimb.body;
|
||||
@@ -1490,7 +1563,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) continue;
|
||||
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;
|
||||
|
||||
@@ -1554,7 +1627,7 @@ namespace Barotrauma
|
||||
Vector2 forceDir = diff / (float)Math.Sqrt(distSqrd);
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) continue;
|
||||
if (limb.IsSevered) { continue; }
|
||||
limb.body.CollidesWith = Physics.CollisionNone;
|
||||
limb.body.ApplyForce(forceDir * limb.Mass * 10.0f, maxVelocity: 10.0f);
|
||||
}
|
||||
@@ -1600,28 +1673,37 @@ namespace Barotrauma
|
||||
UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
|
||||
}
|
||||
|
||||
private Vector2 GetFlowForce()
|
||||
{
|
||||
Vector2 limbPos = Limbs[0].Position;
|
||||
|
||||
Vector2 force = Vector2.Zero;
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.Open <= 0.0f || gap.FlowTargetHull != currentHull || gap.LerpedFlowForce.LengthSquared() < 0.01f) continue;
|
||||
|
||||
Vector2 gapPos = gap.SimPosition;
|
||||
float dist = Vector2.Distance(limbPos, gapPos);
|
||||
force += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) / 500.0f);
|
||||
}
|
||||
return force;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note that if there are multiple limbs of the same type, only the first of them is found in the dictionary.
|
||||
/// </summary>
|
||||
public Limb GetLimb(LimbType limbType)
|
||||
public Limb GetLimb(LimbType limbType, bool excludeSevered = true)
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out Limb limb);
|
||||
Limb limb = null;
|
||||
if (HasMultipleLimbsOfSameType)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
if (limb == null)
|
||||
{
|
||||
// No limbs found
|
||||
break;
|
||||
}
|
||||
if (!excludeSevered || !limb.IsSevered)
|
||||
{
|
||||
// Found a valid limb
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
}
|
||||
if (excludeSevered && limb != null && limb.IsSevered)
|
||||
{
|
||||
limb = null;
|
||||
}
|
||||
return limb;
|
||||
}
|
||||
|
||||
@@ -1664,10 +1746,15 @@ namespace Barotrauma
|
||||
Limb lowestLimb = null;
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (lowestLimb == null)
|
||||
{
|
||||
lowestLimb = limb;
|
||||
}
|
||||
else if (limb.SimPosition.Y < lowestLimb.SimPosition.Y)
|
||||
{
|
||||
lowestLimb = limb;
|
||||
}
|
||||
}
|
||||
|
||||
return lowestLimb;
|
||||
@@ -1700,11 +1787,12 @@ namespace Barotrauma
|
||||
|
||||
if (LimbJoints != null)
|
||||
{
|
||||
foreach (RevoluteJoint joint in LimbJoints)
|
||||
foreach (var joint in LimbJoints)
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(joint))
|
||||
var j = joint.Joint;
|
||||
if (GameMain.World.JointList.Contains(j))
|
||||
{
|
||||
GameMain.World.Remove(joint);
|
||||
GameMain.World.Remove(j);
|
||||
}
|
||||
}
|
||||
LimbJoints = null;
|
||||
|
||||
@@ -316,11 +316,6 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
||||
//var affliction = afflictionPrefab.Instantiate(afflictionStrength);
|
||||
//Afflictions.Add(affliction, subElement);
|
||||
|
||||
break;
|
||||
case "conditional":
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
@@ -347,14 +342,18 @@ namespace Barotrauma
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab != null)
|
||||
{
|
||||
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
||||
affliction = afflictionPrefab.Instantiate(afflictionStrength);
|
||||
affliction = afflictionPrefab.Instantiate(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
affliction = new Affliction(null, 0);
|
||||
}
|
||||
affliction.Deserialize(subElement);
|
||||
//backwards compatibility
|
||||
if (subElement.Attribute("amount") != null && subElement.Attribute("strength") == null)
|
||||
{
|
||||
affliction.Strength = subElement.GetAttributeFloat("amount", 0.0f);
|
||||
}
|
||||
// add the affliction anyway, so that it can be shown in the editor.
|
||||
Afflictions.Add(affliction, subElement);
|
||||
}
|
||||
@@ -572,18 +571,14 @@ namespace Barotrauma
|
||||
|
||||
public bool IsValidTarget(AttackTarget targetType) => TargetType == AttackTarget.Any || TargetType == targetType;
|
||||
|
||||
public bool IsValidTarget(Entity target)
|
||||
public bool IsValidTarget(IDamageable target)
|
||||
{
|
||||
switch (TargetType)
|
||||
return TargetType switch
|
||||
{
|
||||
case AttackTarget.Character:
|
||||
return target is Character;
|
||||
case AttackTarget.Structure:
|
||||
return !(target is Character);
|
||||
case AttackTarget.Any:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
AttackTarget.Character => target is Character,
|
||||
AttackTarget.Structure => !(target is Character),
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
public Vector2 CalculateAttackPhase(TransitionMode easing = TransitionMode.Linear)
|
||||
|
||||
@@ -3,7 +3,7 @@ using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -43,6 +43,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.body != null)
|
||||
{
|
||||
limb.body.Enabled = enabled;
|
||||
@@ -235,6 +236,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; }
|
||||
var displayName = Params.DisplayName;
|
||||
if (string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
@@ -587,30 +589,32 @@ namespace Barotrauma
|
||||
set { canInventoryBeAccessed = value; }
|
||||
}
|
||||
|
||||
private bool accessRemovedCharacterErrorShown;
|
||||
public override Vector2 SimPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AnimController?.Collider == null)
|
||||
{
|
||||
string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed + ".";
|
||||
if (AnimController == null)
|
||||
if (!accessRemovedCharacterErrorShown)
|
||||
{
|
||||
errorMsg += " AnimController == null";
|
||||
string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed + ".";
|
||||
if (AnimController == null)
|
||||
{
|
||||
errorMsg += " AnimController == null";
|
||||
}
|
||||
else if (AnimController.Collider == null)
|
||||
{
|
||||
errorMsg += " AnimController.Collider == null";
|
||||
}
|
||||
errorMsg += '\n' + Environment.StackTrace;
|
||||
DebugConsole.NewMessage(errorMsg, Color.Red);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Character.SimPosition:AccessRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
errorMsg + "\n" + Environment.StackTrace);
|
||||
accessRemovedCharacterErrorShown = true;
|
||||
}
|
||||
else if (AnimController.Collider == null)
|
||||
{
|
||||
errorMsg += " AnimController.Collider == null";
|
||||
}
|
||||
#if DEBUG || UNSTABLE
|
||||
errorMsg += '\n' + Environment.StackTrace;
|
||||
#endif
|
||||
DebugConsole.NewMessage(errorMsg, Color.Red);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Character.SimPosition:AccessRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
errorMsg + "\n" + Environment.StackTrace);
|
||||
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
@@ -1140,22 +1144,86 @@ namespace Barotrauma
|
||||
greatestNegativeSpeedMultiplier = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Speed reduction from the current limb specific damage. Min 0, max 1.
|
||||
/// </summary>
|
||||
public float GetTemporarySpeedReduction()
|
||||
{
|
||||
float reduction = 0;
|
||||
reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.RightFoot, excludeSevered: false), reduction);
|
||||
reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.LeftFoot, excludeSevered: false), reduction);
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
if (AnimController.InWater)
|
||||
{
|
||||
// Currently only humans use hands for swimming.
|
||||
reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.RightHand, excludeSevered: false), reduction);
|
||||
reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.LeftHand, excludeSevered: false), reduction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalTailLimbs = 0;
|
||||
int destroyedTailLimbs = 0;
|
||||
foreach (var limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.type == LimbType.Tail)
|
||||
{
|
||||
totalTailLimbs++;
|
||||
if (limb.IsSevered)
|
||||
{
|
||||
destroyedTailLimbs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (destroyedTailLimbs > 0)
|
||||
{
|
||||
reduction += MathHelper.Lerp(0, AnimController.InWater ? 1f : 0.5f, (float)destroyedTailLimbs / totalTailLimbs);
|
||||
}
|
||||
}
|
||||
return Math.Clamp(reduction, 0, 1f);
|
||||
}
|
||||
|
||||
private float CalculateMovementPenalty(Limb limb, float sum, float max = 0.4f)
|
||||
{
|
||||
if (limb != null)
|
||||
{
|
||||
sum += MathHelper.Lerp(0, max, CharacterHealth.GetLimbDamage(limb, afflictionType: "damage"));
|
||||
}
|
||||
return Math.Clamp(sum, 0, 1f);
|
||||
}
|
||||
|
||||
public float GetRightHandPenalty() => CalculateMovementPenalty(AnimController.GetLimb(LimbType.RightHand, excludeSevered: false), 0, max: 1);
|
||||
public float GetLeftHandPenalty() => CalculateMovementPenalty(AnimController.GetLimb(LimbType.LeftHand, excludeSevered: false), 0, max: 1);
|
||||
|
||||
public float GetLegPenalty(float startSum = 0)
|
||||
{
|
||||
float sum = startSum;
|
||||
foreach (var limb in AnimController.Limbs)
|
||||
{
|
||||
switch (limb.type)
|
||||
{
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.LeftFoot:
|
||||
sum += CalculateMovementPenalty(limb, sum, max: 0.5f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Math.Clamp(sum, 0, 1f);
|
||||
}
|
||||
|
||||
public float ApplyTemporarySpeedLimits(float speed)
|
||||
{
|
||||
var leftFoot = AnimController.GetLimb(LimbType.LeftFoot);
|
||||
if (leftFoot != null)
|
||||
float max;
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true);
|
||||
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
|
||||
max = AnimController.InWater ? 0.5f : 0.7f;
|
||||
}
|
||||
|
||||
var rightFoot = AnimController.GetLimb(LimbType.RightFoot);
|
||||
if (rightFoot != null)
|
||||
else
|
||||
{
|
||||
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
|
||||
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
|
||||
max = AnimController.InWater ? 0.9f : 0.5f;
|
||||
}
|
||||
|
||||
speed *= 1f - MathHelper.Lerp(0, max, GetTemporarySpeedReduction());
|
||||
return speed;
|
||||
}
|
||||
|
||||
@@ -1256,58 +1324,72 @@ namespace Barotrauma
|
||||
}
|
||||
else if (IsKeyDown(InputType.Attack) && (IsRemotePlayer || Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)))
|
||||
{
|
||||
Vector2 attackPos = SimPosition + ConvertUnits.ToSimUnits(cursorPosition - Position);
|
||||
List<Body> ignoredBodies = AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
|
||||
ignoredBodies.Add(AnimController.Collider.FarseerBody);
|
||||
|
||||
var body = Submarine.PickBody(
|
||||
SimPosition,
|
||||
attackPos,
|
||||
ignoredBodies,
|
||||
Physics.CollisionCharacter | Physics.CollisionWall);
|
||||
|
||||
IDamageable attackTarget = null;
|
||||
if (body != null)
|
||||
{
|
||||
attackPos = Submarine.LastPickedPosition;
|
||||
|
||||
if (body.UserData is Submarine sub)
|
||||
{
|
||||
body = Submarine.PickBody(
|
||||
SimPosition - ((Submarine)body.UserData).SimPosition,
|
||||
attackPos - ((Submarine)body.UserData).SimPosition,
|
||||
ignoredBodies,
|
||||
Physics.CollisionWall);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
attackPos = Submarine.LastPickedPosition + sub.SimPosition;
|
||||
attackTarget = body.UserData as IDamageable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (body.UserData is IDamageable)
|
||||
{
|
||||
attackTarget = (IDamageable)body.UserData;
|
||||
}
|
||||
else if (body.UserData is Limb)
|
||||
{
|
||||
attackTarget = ((Limb)body.UserData).character;
|
||||
}
|
||||
}
|
||||
}
|
||||
var currentContexts = GetAttackContexts();
|
||||
var validLimbs = AnimController.Limbs.Where(l => !l.IsSevered && !l.IsStuck && l.attack != null && l.attack.IsValidContext(currentContexts));
|
||||
var validLimbs = AnimController.Limbs.Where(l =>
|
||||
{
|
||||
if (l.IsSevered || l.IsStuck) { return false; }
|
||||
var attack = l.attack;
|
||||
if (attack == null) { return false; }
|
||||
if (attack.CoolDownTimer > 0) { return false; }
|
||||
if (!attack.IsValidContext(currentContexts)) { return false; }
|
||||
if (attackTarget != null)
|
||||
{
|
||||
if (!attack.IsValidTarget(attackTarget)) { return false; }
|
||||
if (attackTarget is ISerializableEntity se && attackTarget is Character)
|
||||
{
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { return false; }
|
||||
}
|
||||
}
|
||||
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(this))) { return false; }
|
||||
return true;
|
||||
});
|
||||
var sortedLimbs = validLimbs.OrderBy(l => Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(l.SimPosition), cursorPosition));
|
||||
// Select closest
|
||||
var attackLimb = sortedLimbs.FirstOrDefault();
|
||||
if (attackLimb != null)
|
||||
{
|
||||
Vector2 attackPos = attackLimb.SimPosition + Vector2.Normalize(cursorPosition - attackLimb.Position) * ConvertUnits.ToSimUnits(attackLimb.attack.Range);
|
||||
|
||||
List<Body> ignoredBodies = AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
|
||||
ignoredBodies.Add(AnimController.Collider.FarseerBody);
|
||||
|
||||
var body = Submarine.PickBody(
|
||||
attackLimb.SimPosition,
|
||||
attackPos,
|
||||
ignoredBodies,
|
||||
Physics.CollisionCharacter | Physics.CollisionWall);
|
||||
|
||||
IDamageable attackTarget = null;
|
||||
if (body != null)
|
||||
{
|
||||
attackPos = Submarine.LastPickedPosition;
|
||||
|
||||
if (body.UserData is Submarine sub)
|
||||
{
|
||||
body = Submarine.PickBody(
|
||||
attackLimb.SimPosition - ((Submarine)body.UserData).SimPosition,
|
||||
attackPos - ((Submarine)body.UserData).SimPosition,
|
||||
ignoredBodies,
|
||||
Physics.CollisionWall);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
attackPos = Submarine.LastPickedPosition + sub.SimPosition;
|
||||
attackTarget = body.UserData as IDamageable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (body.UserData is IDamageable)
|
||||
{
|
||||
attackTarget = (IDamageable)body.UserData;
|
||||
}
|
||||
else if (body.UserData is Limb)
|
||||
{
|
||||
attackTarget = ((Limb)body.UserData).character;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attackLimb.UpdateAttack(deltaTime, attackPos, attackTarget, out AttackResult attackResult);
|
||||
|
||||
if (!attackLimb.attack.IsRunning)
|
||||
{
|
||||
attackCoolDown = 1.0f;
|
||||
@@ -1394,22 +1476,23 @@ namespace Barotrauma
|
||||
{
|
||||
Limb selfLimb = AnimController.GetLimb(LimbType.Head);
|
||||
if (selfLimb == null) { selfLimb = AnimController.GetLimb(LimbType.Torso); }
|
||||
if (selfLimb == null) { selfLimb = AnimController.Limbs.FirstOrDefault(); }
|
||||
if (selfLimb == null) { selfLimb = AnimController.MainLimb; }
|
||||
return selfLimb;
|
||||
}
|
||||
|
||||
public bool CanSeeTarget(ISpatialEntity target, Limb seeingLimb = null)
|
||||
{
|
||||
seeingLimb = seeingLimb ?? GetSeeingLimb();
|
||||
seeingLimb ??= GetSeeingLimb();
|
||||
if (seeingLimb == null) { return false; }
|
||||
ISpatialEntity seeingEntity = AnimController.SimplePhysicsEnabled ? this : seeingLimb as ISpatialEntity;
|
||||
// TODO: Could we just use the method below? If not, let's refactor it so that we can.
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - seeingLimb.WorldPosition);
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - seeingEntity.WorldPosition);
|
||||
Body closestBody;
|
||||
//both inside the same sub (or both outside)
|
||||
//OR the we're inside, the other character outside
|
||||
if (target.Submarine == Submarine || target.Submarine == null)
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(seeingLimb.SimPosition, seeingLimb.SimPosition + diff);
|
||||
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
|
||||
}
|
||||
//we're outside, the other character inside
|
||||
else if (Submarine == null)
|
||||
@@ -1419,7 +1502,7 @@ namespace Barotrauma
|
||||
//both inside different subs
|
||||
else
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(seeingLimb.SimPosition, seeingLimb.SimPosition + diff);
|
||||
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
|
||||
if (!IsBlocking(closestBody))
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff);
|
||||
@@ -1436,10 +1519,11 @@ namespace Barotrauma
|
||||
}
|
||||
else if (body.UserData is Item item && item != target)
|
||||
{
|
||||
// TODO: The door collider should be disabled, so this check is probably unnecessary.
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
return !door.IsOpen;
|
||||
return !door.IsOpen && !door.IsBroken;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1466,7 +1550,7 @@ namespace Barotrauma
|
||||
Structure wall = closestBody.UserData as Structure;
|
||||
Item item = closestBody.UserData as Item;
|
||||
Door door = item?.GetComponent<Door>();
|
||||
return (wall == null || !wall.CastShadow) && (door == null || door.IsOpen);
|
||||
return (wall == null || !wall.CastShadow) && (door == null || door.IsOpen || door.IsBroken);
|
||||
}
|
||||
|
||||
public bool HasItem(Item item, bool requireEquipped = false) => requireEquipped ? HasEquippedItem(item) : item.IsOwnedBy(this);
|
||||
@@ -1601,8 +1685,8 @@ namespace Barotrauma
|
||||
if (IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
float itemPriority = customPriorityFunction != null ? customPriorityFunction(item) : 1;
|
||||
if (itemPriority <= 0) { continue; }
|
||||
Item rootContainer = item.GetRootContainer();
|
||||
Vector2 itemPos = (rootContainer ?? item).WorldPosition;
|
||||
Entity rootInventoryOwner = item.GetRootInventoryOwner();
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
float yDist = Math.Abs(WorldPosition.Y - itemPos.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(WorldPosition.X - itemPos.X) + yDist;
|
||||
@@ -1620,13 +1704,16 @@ namespace Barotrauma
|
||||
|
||||
public bool IsItemTakenBySomeoneElse(Item item) => item.FindParentInventory(i => i.Owner != this && i.Owner is Character owner && !owner.IsDead && !owner.Removed) != null;
|
||||
|
||||
public bool CanInteractWith(Character c, float maxDist = 200.0f, bool checkVisibility = true)
|
||||
public bool CanInteractWith(Character c, float maxDist = 200.0f, bool checkVisibility = true, bool skipDistanceCheck = false)
|
||||
{
|
||||
if (c == this || Removed || !c.Enabled || !c.CanBeSelected) return false;
|
||||
if (!c.CharacterHealth.UseHealthWindow && !c.CanBeDragged && c.onCustomInteract == null) return false;
|
||||
if (c == this || Removed || !c.Enabled || !c.CanBeSelected) { return false; }
|
||||
if (!c.CharacterHealth.UseHealthWindow && !c.CanBeDragged && c.onCustomInteract == null) { return false; }
|
||||
|
||||
maxDist = ConvertUnits.ToSimUnits(maxDist);
|
||||
if (Vector2.DistanceSquared(SimPosition, c.SimPosition) > maxDist * maxDist) return false;
|
||||
if (!skipDistanceCheck)
|
||||
{
|
||||
maxDist = ConvertUnits.ToSimUnits(maxDist);
|
||||
if (Vector2.DistanceSquared(SimPosition, c.SimPosition) > maxDist * maxDist) { return false; }
|
||||
}
|
||||
|
||||
return checkVisibility ? CanSeeCharacter(c) : true;
|
||||
}
|
||||
@@ -2394,7 +2481,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly float maxAIRange = 10000;
|
||||
private readonly float maxAIRange = 20000;
|
||||
private readonly float aiTargetChangeSpeed = 5;
|
||||
|
||||
private void UpdateSightRange(float deltaTime)
|
||||
@@ -2628,40 +2715,60 @@ namespace Barotrauma
|
||||
GameServer.Log(sb.ToString(), ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isNotClient = GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient;
|
||||
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability);
|
||||
// Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head)
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: AIController == null || AIController is HumanAIController);
|
||||
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability)
|
||||
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading)
|
||||
{
|
||||
bool isNotClient = GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient;
|
||||
|
||||
if (isNotClient &&
|
||||
IsDead && Rand.Range(0.0f, 1.0f) < severLimbsProbability)
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
#if DEBUG
|
||||
if (targetLimb.character != this)
|
||||
{
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
if (joint.CanBeSevered && (joint.LimbA == targetLimb || joint.LimbB == targetLimb))
|
||||
{
|
||||
#if CLIENT
|
||||
CurrentHull?.AddDecal("blood", WorldPosition, Rand.Range(0.5f, 1.5f));
|
||||
DebugConsole.ThrowError($"{Name} is attempting to sever joints of {targetLimb.character.Name}!");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
AnimController.SeverLimbJoint(joint);
|
||||
|
||||
if (joint.LimbA == targetLimb)
|
||||
{
|
||||
joint.LimbB.body.LinearVelocity += targetLimb.LinearVelocity * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
joint.LimbA.body.LinearVelocity += targetLimb.LinearVelocity * 0.5f;
|
||||
}
|
||||
}
|
||||
if (damage < targetLimb.Params.MinSeveranceDamage) { return; }
|
||||
if (!IsDead)
|
||||
{
|
||||
if (!allowBeheading && targetLimb.type == LimbType.Head) { return; }
|
||||
if (!targetLimb.CanBeSeveredAlive) { return; }
|
||||
}
|
||||
bool wasSevered = false;
|
||||
float random = Rand.Value();
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
if (!joint.CanBeSevered) { continue; }
|
||||
if (joint.LimbA != targetLimb && joint.LimbB != targetLimb) { continue; }
|
||||
float probability = severLimbsProbability;
|
||||
if (!IsDead)
|
||||
{
|
||||
probability *= joint.Params.SeveranceProbabilityModifier;
|
||||
}
|
||||
if (probability <= 0) { continue; }
|
||||
if (random > probability) { continue; }
|
||||
bool severed = AnimController.SeverLimbJoint(joint);
|
||||
if (!wasSevered)
|
||||
{
|
||||
wasSevered = severed;
|
||||
}
|
||||
if (severed)
|
||||
{
|
||||
Limb otherLimb = joint.LimbA == targetLimb ? joint.LimbB : joint.LimbA;
|
||||
otherLimb.body.ApplyLinearImpulse(targetLimb.LinearVelocity * targetLimb.Mass);
|
||||
}
|
||||
}
|
||||
if (wasSevered)
|
||||
{
|
||||
if (targetLimb.character.AIController is EnemyAIController enemyAI)
|
||||
{
|
||||
enemyAI.ReevaluateAttacks();
|
||||
}
|
||||
ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
targetLimb.ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2742,6 +2849,10 @@ namespace Barotrauma
|
||||
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound);
|
||||
CharacterHealth.ApplyDamage(hitLimb, attackResult);
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
if (attackResult.Damage > 0)
|
||||
{
|
||||
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
}
|
||||
if (attacker != this)
|
||||
{
|
||||
OnAttacked?.Invoke(attacker, attackResult);
|
||||
@@ -2800,8 +2911,10 @@ namespace Barotrauma
|
||||
{
|
||||
SelectedConstruction = null;
|
||||
}
|
||||
HealthUpdateInterval = 0.0f;
|
||||
}
|
||||
|
||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||
{
|
||||
foreach (StatusEffect statusEffect in statusEffects)
|
||||
@@ -2810,30 +2923,61 @@ namespace Barotrauma
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
targets.Clear();
|
||||
statusEffect.GetNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, this, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
if (statusEffect.targetLimbs != null)
|
||||
{
|
||||
foreach (var limbType in statusEffect.targetLimbs)
|
||||
{
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
// Target all matching limbs
|
||||
foreach (var limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == limbType)
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
// Target just the first matching limb
|
||||
Limb limb = AnimController.GetLimb(limbType);
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
|
||||
{
|
||||
// OnDamaged is called only for the limb that is hit.
|
||||
AnimController.Limbs.ForEach(l => l.ApplyStatusEffects(actionType, deltaTime));
|
||||
}
|
||||
}
|
||||
|
||||
private void Implode(bool isNetworkMessage = false)
|
||||
{
|
||||
if (CharacterHealth.Unkillable) { return; }
|
||||
if (CharacterHealth.Unkillable || IsDead) { return; }
|
||||
|
||||
if (!isNetworkMessage)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
}
|
||||
|
||||
Kill(CauseOfDeathType.Pressure, null, isNetworkMessage);
|
||||
CharacterHealth.PressureAffliction.Strength = CharacterHealth.PressureAffliction.Prefab.MaxStrength;
|
||||
CharacterHealth.SetAllDamage(200.0f, 0.0f, 0.0f);
|
||||
BreakJoints();
|
||||
CharacterHealth.ApplyAffliction(null, new Affliction(AfflictionPrefab.Pressure, AfflictionPrefab.Pressure.MaxStrength));
|
||||
if (isNetworkMessage && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Vitality <= CharacterHealth.MinVitality) { Kill(CauseOfDeathType.Pressure, null, isNetworkMessage: true); }
|
||||
if (IsDead)
|
||||
{
|
||||
BreakJoints();
|
||||
}
|
||||
}
|
||||
|
||||
public void BreakJoints()
|
||||
@@ -2841,6 +2985,7 @@ namespace Barotrauma
|
||||
Vector2 centerOfMass = AnimController.GetCenterOfMass();
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
limb.AddDamage(limb.SimPosition, 500.0f, 0.0f, 0.0f, false);
|
||||
|
||||
Vector2 diff = centerOfMass - limb.SimPosition;
|
||||
@@ -2861,7 +3006,10 @@ namespace Barotrauma
|
||||
|
||||
foreach (var joint in AnimController.LimbJoints)
|
||||
{
|
||||
joint.LimitEnabled = false;
|
||||
if (joint.revoluteJoint != null)
|
||||
{
|
||||
joint.revoluteJoint.LimitEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2928,9 +3076,12 @@ namespace Barotrauma
|
||||
|
||||
AnimController.ResetPullJoints();
|
||||
|
||||
foreach (RevoluteJoint joint in AnimController.LimbJoints)
|
||||
foreach (var joint in AnimController.LimbJoints)
|
||||
{
|
||||
joint.MotorEnabled = false;
|
||||
if (joint.revoluteJoint != null)
|
||||
{
|
||||
joint.revoluteJoint.MotorEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession != null)
|
||||
@@ -2961,7 +3112,11 @@ namespace Barotrauma
|
||||
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
joint.MotorEnabled = true;
|
||||
var revoluteJoint = joint.revoluteJoint;
|
||||
if (revoluteJoint != null)
|
||||
{
|
||||
revoluteJoint.MotorEnabled = true;
|
||||
}
|
||||
joint.Enabled = true;
|
||||
joint.IsSevered = false;
|
||||
}
|
||||
@@ -3176,5 +3331,15 @@ namespace Barotrauma
|
||||
}
|
||||
return targetPos;
|
||||
}
|
||||
|
||||
public bool IsCaptain => HasJob("captain");
|
||||
public bool IsEngineer => HasJob("engineer");
|
||||
public bool IsMechanic => HasJob("mechanic");
|
||||
public bool IsMedic => HasJob("medicaldoctor");
|
||||
public bool IsOfficer => HasJob("securityofficer");
|
||||
public bool IsAsssitant => HasJob("assistant");
|
||||
public bool IsWatchman => HasJob("watchman");
|
||||
|
||||
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
+20
-21
@@ -97,11 +97,13 @@ namespace Barotrauma
|
||||
|
||||
private void ApplyDamage(float deltaTime, bool applyForce)
|
||||
{
|
||||
int limbCount = character.AnimController.Limbs.Count(l => !l.ignoreCollisions && !l.IsSevered);
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
float random = Rand.Value(Rand.RandSync.Server);
|
||||
huskInfection.Clear();
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / character.AnimController.Limbs.Length));
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / limbCount));
|
||||
character.LastDamageSource = null;
|
||||
float force = applyForce ? random * 0.5f * limb.Mass : 0;
|
||||
character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
|
||||
@@ -186,18 +188,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (character.Inventory.Items.Length != husk.Inventory.Items.Length)
|
||||
if (character.Inventory != null && husk.Inventory != null)
|
||||
{
|
||||
string errorMsg = "Failed to move items from the source character's inventory into a husk's inventory (inventory sizes don't match)";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AfflictionHusk.CreateAIHusk:InventoryMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Items.Length && i < husk.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (character.Inventory.Items[i] == null) continue;
|
||||
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true, false, null);
|
||||
if (character.Inventory.Items.Length != husk.Inventory.Items.Length)
|
||||
{
|
||||
string errorMsg = "Failed to move items from the source character's inventory into a husk's inventory (inventory sizes don't match)";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AfflictionHusk.CreateAIHusk:InventoryMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
for (int i = 0; i < character.Inventory.Items.Length && i < husk.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (character.Inventory.Items[i] == null) continue;
|
||||
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
husk.SetStun(5);
|
||||
@@ -255,20 +259,19 @@ namespace Barotrauma
|
||||
Limb attachLimb = null;
|
||||
if (matchingAffliction.AttachLimbId > -1)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Params.ID == matchingAffliction.AttachLimbId);
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbName != null)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Name == matchingAffliction.AttachLimbName);
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbType != LimbType.None)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.type == matchingAffliction.AttachLimbType);
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
|
||||
}
|
||||
if (attachLimb == null)
|
||||
{
|
||||
DebugConsole.Log("Attachment limb not defined in the affliction prefab or no matching limb could be found. Using the appendage definition as it is.");
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Params.ID == jointParams.Limb1);
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
|
||||
}
|
||||
if (attachLimb != null)
|
||||
{
|
||||
@@ -286,10 +289,6 @@ namespace Barotrauma
|
||||
ragdoll.AddJoint(jointParams);
|
||||
appendage.Add(huskAppendage);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Attachment limb not found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return appendage;
|
||||
|
||||
+7
@@ -189,6 +189,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public static AfflictionPrefab InternalDamage;
|
||||
public static AfflictionPrefab ImpactDamage;
|
||||
public static AfflictionPrefab Bleeding;
|
||||
public static AfflictionPrefab Burn;
|
||||
public static AfflictionPrefab OxygenLow;
|
||||
@@ -291,6 +292,7 @@ namespace Barotrauma
|
||||
{
|
||||
CPRSettings.Unload();
|
||||
InternalDamage = null;
|
||||
ImpactDamage = null;
|
||||
Bleeding = null;
|
||||
Burn = null;
|
||||
OxygenLow = null;
|
||||
@@ -437,6 +439,9 @@ namespace Barotrauma
|
||||
case "internaldamage":
|
||||
InternalDamage = prefab;
|
||||
break;
|
||||
case "blunttrauma":
|
||||
ImpactDamage = prefab;
|
||||
break;
|
||||
case "bleeding":
|
||||
Bleeding = prefab;
|
||||
break;
|
||||
@@ -456,6 +461,8 @@ namespace Barotrauma
|
||||
Stun = prefab;
|
||||
break;
|
||||
}
|
||||
if (ImpactDamage == null) { ImpactDamage = InternalDamage; }
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
Prefabs.Add(prefab, isOverride);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
+8
-6
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (!affliction.Prefab.IsBuff || affliction == this || affliction.MultiplierSource != this) continue;
|
||||
if (!affliction.Prefab.IsBuff || affliction == this || affliction.MultiplierSource != this) { continue; }
|
||||
affliction.MultiplierSource = null;
|
||||
affliction.StrengthDiminishMultiplier = 1f;
|
||||
}
|
||||
@@ -28,9 +29,9 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (!affliction.Prefab.IsBuff || affliction == this || affliction.MultiplierSource == this) continue;
|
||||
if (!affliction.Prefab.IsBuff || affliction == this) { continue; }
|
||||
float multiplier = GetDiminishMultiplier();
|
||||
if (affliction.StrengthDiminishMultiplier < multiplier) continue;
|
||||
if (affliction.StrengthDiminishMultiplier < multiplier && affliction.MultiplierSource != this) { continue; }
|
||||
|
||||
affliction.MultiplierSource = this;
|
||||
affliction.StrengthDiminishMultiplier = multiplier;
|
||||
@@ -40,14 +41,15 @@ namespace Barotrauma
|
||||
|
||||
private float GetDiminishMultiplier()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 1.0f;
|
||||
if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 1.0f;
|
||||
if (currentEffect == null) { return 1.0f; }
|
||||
|
||||
return MathHelper.Lerp(
|
||||
float multiplier = MathHelper.Lerp(
|
||||
currentEffect.MinBuffMultiplier,
|
||||
currentEffect.MaxBuffMultiplier,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
return 1.0f / Math.Max(multiplier, 0.001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsUnconscious
|
||||
{
|
||||
get { return Vitality <= 0.0f; }
|
||||
get { return Vitality <= 0.0f || Character.IsDead; }
|
||||
}
|
||||
|
||||
public float PressureKillDelay { get; private set; } = 5.0f;
|
||||
@@ -252,8 +252,8 @@ namespace Barotrauma
|
||||
: afflictions.Where(limbHealthFilter).Union(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter)));
|
||||
}
|
||||
|
||||
private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex];
|
||||
private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb));
|
||||
private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex];
|
||||
private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
|
||||
@@ -349,13 +349,16 @@ namespace Barotrauma
|
||||
/// Most monsters for example don't have separate healths for different limbs, essentially meaning that every affliction is applied to every limb.</param>
|
||||
public float GetAfflictionStrength(string afflictionType, Limb limb, bool requireLimbSpecific)
|
||||
{
|
||||
if (requireLimbSpecific && limbHealths.Count == 1) return 0.0f;
|
||||
if (requireLimbSpecific && limbHealths.Count == 1) { return 0.0f; }
|
||||
|
||||
float strength = 0.0f;
|
||||
foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions)
|
||||
{
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) continue;
|
||||
if (affliction.Prefab.AfflictionType == afflictionType) strength += affliction.Strength;
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; }
|
||||
if (affliction.Prefab.AfflictionType == afflictionType)
|
||||
{
|
||||
strength += affliction.Strength;
|
||||
}
|
||||
}
|
||||
return strength;
|
||||
}
|
||||
@@ -365,17 +368,23 @@ namespace Barotrauma
|
||||
float strength = 0.0f;
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) continue;
|
||||
if (affliction.Prefab.AfflictionType == afflictionType) strength += affliction.Strength;
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; }
|
||||
if (affliction.Prefab.AfflictionType == afflictionType)
|
||||
{
|
||||
strength += affliction.Strength;
|
||||
}
|
||||
}
|
||||
if (!allowLimbAfflictions) return strength;
|
||||
if (!allowLimbAfflictions) { return strength; }
|
||||
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
{
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) continue;
|
||||
if (affliction.Prefab.AfflictionType == afflictionType) strength += affliction.Strength;
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; }
|
||||
if (affliction.Prefab.AfflictionType == afflictionType)
|
||||
{
|
||||
strength += affliction.Strength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,6 +515,34 @@ namespace Barotrauma
|
||||
if (Vitality <= MinVitality) { Kill(); }
|
||||
}
|
||||
|
||||
public float GetLimbDamage(Limb limb, string afflictionType = null)
|
||||
{
|
||||
float damageStrength;
|
||||
if (limb.IsSevered)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Instead of using the limbhealth count here, I think it's best to define the max vitality per limb roughly with a constant value.
|
||||
// Therefore with e.g. 80 health, the max damage per limb would be 20.
|
||||
// Having at least 20 damage on both legs would cause maximum limping.
|
||||
float max = MaxVitality / 4;
|
||||
if (string.IsNullOrEmpty(afflictionType))
|
||||
{
|
||||
float damage = GetAfflictionStrength("damage", limb, true);
|
||||
float bleeding = GetAfflictionStrength("bleeding", limb, true);
|
||||
float burn = GetAfflictionStrength("burn", limb, true);
|
||||
damageStrength = Math.Min(damage + bleeding + burn, max);
|
||||
}
|
||||
else
|
||||
{
|
||||
damageStrength = Math.Min(GetAfflictionStrength("damage", limb, true), max);
|
||||
}
|
||||
return damageStrength / max;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAllAfflictions()
|
||||
{
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
@@ -523,7 +560,7 @@ namespace Barotrauma
|
||||
|
||||
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
|
||||
{
|
||||
if (!newAffliction.Prefab.LimbSpecific || limb == null) return;
|
||||
if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; }
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name +
|
||||
@@ -535,8 +572,8 @@ namespace Barotrauma
|
||||
|
||||
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction)
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) return;
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
@@ -545,7 +582,10 @@ namespace Barotrauma
|
||||
affliction.Strength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
|
||||
affliction.Source = newAffliction.Source;
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -560,7 +600,10 @@ namespace Barotrauma
|
||||
Character.HealthUpdateInterval = 0.0f;
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
#if CLIENT
|
||||
selectedLimbIndex = -1;
|
||||
#endif
|
||||
@@ -894,10 +937,7 @@ namespace Barotrauma
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
|
||||
/// <summary>
|
||||
/// Automatically filters out buffs.
|
||||
/// </summary>
|
||||
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions) =>
|
||||
afflictions.Where(a => !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
|
||||
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions, bool excludeBuffs = true) =>
|
||||
afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
public readonly Dictionary<int, List<string>> ItemIdentifiers = new Dictionary<int, List<string>>();
|
||||
public readonly Dictionary<int, Dictionary<string, bool>> ShowItemPreview = new Dictionary<int, Dictionary<string, bool>>();
|
||||
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
|
||||
public readonly List<AutonomousObjective> AutomaticOrders = new List<AutonomousObjective>();
|
||||
public readonly List<AutonomousObjective> AutonomousObjective = new List<AutonomousObjective>();
|
||||
public readonly List<string> AppropriateOrders = new List<string>();
|
||||
|
||||
[Serialize("1,1,1,1", false)]
|
||||
@@ -163,6 +163,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public Sprite Icon;
|
||||
public Sprite IconSmall;
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
public XElement Element { get; private set; }
|
||||
@@ -198,7 +199,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case "autonomousobjectives":
|
||||
subElement.Elements().ForEach(order => AutomaticOrders.Add(new AutonomousObjective(order)));
|
||||
subElement.Elements().ForEach(order => AutonomousObjective.Add(new AutonomousObjective(order)));
|
||||
break;
|
||||
case "appropriateobjectives":
|
||||
case "appropriateorders":
|
||||
@@ -207,6 +208,9 @@ namespace Barotrauma
|
||||
case "jobicon":
|
||||
Icon = new Sprite(subElement.FirstElement());
|
||||
break;
|
||||
case "jobiconsmall":
|
||||
IconSmall = new Sprite(subElement.FirstElement());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Barotrauma
|
||||
None, LeftHand, RightHand, LeftArm, RightArm, LeftForearm, RightForearm,
|
||||
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist, Jaw
|
||||
};
|
||||
|
||||
partial class LimbJoint : RevoluteJoint
|
||||
|
||||
partial class LimbJoint
|
||||
{
|
||||
public bool IsSevered;
|
||||
public bool CanBeSevered => Params.CanBeSevered;
|
||||
@@ -30,27 +30,135 @@ namespace Barotrauma
|
||||
|
||||
public float Scale => Params.Scale * ragdoll.RagdollParams.JointScale;
|
||||
|
||||
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One)
|
||||
public readonly RevoluteJoint revoluteJoint;
|
||||
public readonly WeldJoint weldJoint;
|
||||
public Joint Joint => revoluteJoint ?? weldJoint as Joint;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => Joint.Enabled;
|
||||
set => Joint.Enabled = value;
|
||||
}
|
||||
|
||||
public Body BodyA => Joint.BodyA;
|
||||
|
||||
public Body BodyB => Joint.BodyB;
|
||||
|
||||
public Vector2 WorldAnchorA
|
||||
{
|
||||
get => Joint.WorldAnchorA;
|
||||
set => Joint.WorldAnchorA = value;
|
||||
}
|
||||
|
||||
public Vector2 WorldAnchorB
|
||||
{
|
||||
get => Joint.WorldAnchorB;
|
||||
set => Joint.WorldAnchorB = value;
|
||||
}
|
||||
|
||||
public Vector2 LocalAnchorA
|
||||
{
|
||||
get => revoluteJoint != null ? revoluteJoint.LocalAnchorA : weldJoint.LocalAnchorA;
|
||||
set
|
||||
{
|
||||
if (weldJoint != null)
|
||||
{
|
||||
weldJoint.LocalAnchorA = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
revoluteJoint.LocalAnchorA = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 LocalAnchorB
|
||||
{
|
||||
get => revoluteJoint != null ? revoluteJoint.LocalAnchorB : weldJoint.LocalAnchorB;
|
||||
set
|
||||
{
|
||||
if (weldJoint != null)
|
||||
{
|
||||
weldJoint.LocalAnchorB = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
revoluteJoint.LocalAnchorB = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool LimitEnabled
|
||||
{
|
||||
get => revoluteJoint != null ? revoluteJoint.LimitEnabled : false;
|
||||
set
|
||||
{
|
||||
if (revoluteJoint != null)
|
||||
{
|
||||
revoluteJoint.LimitEnabled = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float LowerLimit
|
||||
{
|
||||
get => revoluteJoint != null ? revoluteJoint.LowerLimit : 0;
|
||||
set
|
||||
{
|
||||
if (revoluteJoint != null)
|
||||
{
|
||||
revoluteJoint.LowerLimit = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float UpperLimit
|
||||
{
|
||||
get => revoluteJoint != null ? revoluteJoint.UpperLimit : 0;
|
||||
set
|
||||
{
|
||||
if (revoluteJoint != null)
|
||||
{
|
||||
revoluteJoint.UpperLimit = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float JointAngle => revoluteJoint != null ? revoluteJoint.JointAngle : weldJoint.ReferenceAngle;
|
||||
|
||||
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One, jointParams.WeldJoint)
|
||||
{
|
||||
Params = jointParams;
|
||||
this.ragdoll = ragdoll;
|
||||
LoadParams();
|
||||
}
|
||||
|
||||
public LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2)
|
||||
: base(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2)
|
||||
public LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2, bool weld = false)
|
||||
{
|
||||
CollideConnected = false;
|
||||
MotorEnabled = true;
|
||||
MaxMotorTorque = 0.25f;
|
||||
if (weld)
|
||||
{
|
||||
weldJoint = new WeldJoint(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2);
|
||||
}
|
||||
else
|
||||
{
|
||||
revoluteJoint = new RevoluteJoint(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2)
|
||||
{
|
||||
MotorEnabled = true,
|
||||
MaxMotorTorque = 0.25f
|
||||
};
|
||||
}
|
||||
Joint.CollideConnected = false;
|
||||
LimbA = limbA;
|
||||
LimbB = limbB;
|
||||
}
|
||||
|
||||
public void LoadParams()
|
||||
{
|
||||
MaxMotorTorque = Params.Stiffness;
|
||||
LimitEnabled = Params.LimitEnabled;
|
||||
if (revoluteJoint != null)
|
||||
{
|
||||
revoluteJoint.MaxMotorTorque = Params.Stiffness;
|
||||
revoluteJoint.LimitEnabled = Params.LimitEnabled;
|
||||
}
|
||||
if (float.IsNaN(Params.LowerLimit))
|
||||
{
|
||||
Params.LowerLimit = 0;
|
||||
@@ -61,17 +169,33 @@ namespace Barotrauma
|
||||
}
|
||||
if (ragdoll.IsFlipped)
|
||||
{
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
|
||||
UpperLimit = MathHelper.ToRadians(-Params.LowerLimit);
|
||||
LowerLimit = MathHelper.ToRadians(-Params.UpperLimit);
|
||||
if (weldJoint != null)
|
||||
{
|
||||
weldJoint.LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
|
||||
weldJoint.LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
revoluteJoint.LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
|
||||
revoluteJoint.LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
|
||||
revoluteJoint.UpperLimit = MathHelper.ToRadians(-Params.LowerLimit);
|
||||
revoluteJoint.LowerLimit = MathHelper.ToRadians(-Params.UpperLimit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
|
||||
UpperLimit = MathHelper.ToRadians(Params.UpperLimit);
|
||||
LowerLimit = MathHelper.ToRadians(Params.LowerLimit);
|
||||
if (weldJoint != null)
|
||||
{
|
||||
weldJoint.LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
|
||||
weldJoint.LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
revoluteJoint.LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
|
||||
revoluteJoint.LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
|
||||
revoluteJoint.UpperLimit = MathHelper.ToRadians(Params.UpperLimit);
|
||||
revoluteJoint.LowerLimit = MathHelper.ToRadians(Params.LowerLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,10 +290,20 @@ namespace Barotrauma
|
||||
if (isSevered)
|
||||
{
|
||||
ragdoll.SubtractMass(this);
|
||||
if (type == LimbType.Head)
|
||||
{
|
||||
character.Kill(CauseOfDeathType.Unknown, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
severedFadeOutTimer = 0.0f;
|
||||
}
|
||||
if (!isSevered) severedFadeOutTimer = 0.0f;
|
||||
#if CLIENT
|
||||
if (isSevered) damageOverlayStrength = 100.0f;
|
||||
if (isSevered)
|
||||
{
|
||||
damageOverlayStrength = 100.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -366,14 +500,42 @@ namespace Barotrauma
|
||||
|
||||
public string Name => Params.Name;
|
||||
|
||||
// Exposed for status effects
|
||||
public bool IsDead => character.IsDead;
|
||||
|
||||
public bool CanBeSeveredAlive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (character.IsHumanoid) { return false; }
|
||||
if (this == character.AnimController.MainLimb) { return false; }
|
||||
if (character.AnimController.CanWalk)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightLeg:
|
||||
case LimbType.LeftThigh:
|
||||
case LimbType.RightThigh:
|
||||
case LimbType.Legs:
|
||||
case LimbType.Waist:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
public Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
|
||||
{
|
||||
this.ragdoll = ragdoll;
|
||||
@@ -436,6 +598,9 @@ namespace Barotrauma
|
||||
case "damagemodifier":
|
||||
DamageModifiers.Add(new DamageModifier(subElement, character.Name));
|
||||
break;
|
||||
case "statuseffect":
|
||||
statusEffects.Add(StatusEffect.Load(subElement, Name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,11 +686,12 @@ namespace Barotrauma
|
||||
afflictionsCopy.Add(newAffliction);
|
||||
}
|
||||
}
|
||||
AddDamageProjSpecific(afflictionsCopy, playSound, appliedDamageModifiers);
|
||||
return new AttackResult(afflictionsCopy, this, appliedDamageModifiers);
|
||||
var result = new AttackResult(afflictionsCopy, this, appliedDamageModifiers);
|
||||
AddDamageProjSpecific(playSound, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(IEnumerable<Affliction> afflictions, bool playSound, IEnumerable<DamageModifier> appliedDamageModifiers);
|
||||
partial void AddDamageProjSpecific(bool playSound, AttackResult result);
|
||||
|
||||
public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
|
||||
{
|
||||
@@ -582,7 +748,8 @@ namespace Barotrauma
|
||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
||||
{
|
||||
attackResult = default(AttackResult);
|
||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos));
|
||||
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
||||
bool wasRunning = attack.IsRunning;
|
||||
attack.UpdateAttackTimer(deltaTime);
|
||||
|
||||
@@ -595,7 +762,7 @@ namespace Barotrauma
|
||||
case HitDetection.Distance:
|
||||
if (dist < attack.DamageRange)
|
||||
{
|
||||
structureBody = Submarine.PickBody(SimPosition, attackSimPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true);
|
||||
structureBody = Submarine.PickBody(simPos, attackSimPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true);
|
||||
if (damageTarget is Item i && i.GetComponent<Items.Components.Door>() != null)
|
||||
{
|
||||
// If the attack is aimed to an item and hits an item, it's successful.
|
||||
@@ -689,6 +856,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) { continue; }
|
||||
Limb limb = character.AnimController.Limbs[limbIndex];
|
||||
if (limb.IsSevered) { continue; }
|
||||
diff = attackSimPos - limb.SimPosition;
|
||||
if (diff == Vector2.Zero) { continue; }
|
||||
limb.body.ApplyTorque(limb.Mass * character.AnimController.Dir * attack.Torque * limb.Params.AttackForceMultiplier);
|
||||
@@ -811,6 +979,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||
{
|
||||
foreach (StatusEffect statusEffect in statusEffects)
|
||||
{
|
||||
if (statusEffect.type != actionType) { continue; }
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
statusEffect.GetNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, character, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, character, character, WorldPosition);
|
||||
}
|
||||
statusEffect.Apply(actionType, deltaTime, character, this, WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
body?.Remove();
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
+18
-1
@@ -72,6 +72,12 @@ namespace Barotrauma
|
||||
[Editable, Serialize(true, true, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(1f, true, description: "Reduces continuous flipping when the character abruptly changes direction."), Editable]
|
||||
public float FlipCooldown { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, description: "How much it takes before the character flips. The timer starts when the character starts to move in the different direction."), Editable]
|
||||
public float FlipDelay { get; set; }
|
||||
|
||||
[Serialize(10.0f, true, description: "How much force is used to move the head to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float HeadMoveForce { get; set; }
|
||||
|
||||
@@ -146,9 +152,18 @@ namespace Barotrauma
|
||||
[Editable, Serialize(true, true, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(1f, true, description: "Reduces continuous flipping when the character abruptly changes direction."), Editable]
|
||||
public float FlipCooldown { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, description: "How much it takes before the character flips. The timer starts when the character starts to move in the different direction."), Editable]
|
||||
public float FlipDelay { get; set; }
|
||||
|
||||
[Editable, Serialize(true, true, description: "If enabled, the character will simply be mirrored horizontally when it wants to turn around. If disabled, it will rotate itself to face the other direction.")]
|
||||
public bool Mirror { get; set; }
|
||||
|
||||
[Editable, Serialize(true, true, description: "Disabling this will make mirroring instantaneous.")]
|
||||
public bool MirrorLerp { get; set; }
|
||||
|
||||
[Serialize(5f, true), Editable]
|
||||
public float WaveAmplitude { get; set; }
|
||||
|
||||
@@ -205,7 +220,6 @@ namespace Barotrauma
|
||||
|
||||
interface IFishAnimation
|
||||
{
|
||||
bool Flip { get; set; }
|
||||
string FootAngles { get; set; }
|
||||
Dictionary<int, float> FootAnglesInRadians { get; set; }
|
||||
float TailAngle { get; set; }
|
||||
@@ -214,5 +228,8 @@ namespace Barotrauma
|
||||
float TorsoTorque { get; set; }
|
||||
float TailTorque { get; set; }
|
||||
float FootTorque { get; set; }
|
||||
bool Flip { get; set; }
|
||||
float FlipCooldown { get; set; }
|
||||
float FlipDelay { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ namespace Barotrauma
|
||||
[Serialize("", true, description: "If defined, different species of the same group are considered like the characters of the same species by the AI."), Editable]
|
||||
public string Group { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, true), Editable(ReadOnly = true)]
|
||||
public bool Humanoid { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, true), Editable(ReadOnly = true)]
|
||||
public bool HasInfo { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
@@ -43,13 +43,13 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "Can the creature live without water or does it die on dry land?"), Editable]
|
||||
public bool NeedsWater { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, false), Editable]
|
||||
public bool CanSpeak { get; set; }
|
||||
|
||||
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 100000f)]
|
||||
public float Noise { get; set; }
|
||||
|
||||
[Serialize(100f, true, description: "How visible the character is?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
[Serialize(100f, true, description: "How visible the character is?"), Editable(minValue: 0f, maxValue: 100000f)]
|
||||
public float Visibility { get; set; }
|
||||
|
||||
[Serialize("blood", true), Editable]
|
||||
@@ -70,6 +70,9 @@ namespace Barotrauma
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HideInSonar { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float SonarDisruption { get; set; }
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public readonly List<SubParam> SubParams = new List<SubParam>();
|
||||
@@ -474,8 +477,8 @@ namespace Barotrauma
|
||||
[Serialize(true, true, description: "Enforce aggressive behavior if the creature is spawned as a target of a monster mission."), Editable()]
|
||||
public bool EnforceAggressiveBehaviorForMissions { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the character target or ignore walls when it's inside the submarine. Doesn't have any effect if no target priority for walls is defined."), Editable()]
|
||||
public bool TargetInnerWalls { get; private set; }
|
||||
[Serialize(true, true, description: "Should the character target or ignore walls when it's outside the submarine. Doesn't have any effect if no target priority for walls is defined."), Editable()]
|
||||
public bool TargetOuterWalls { get; private set; }
|
||||
|
||||
[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; }
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
#if DEBUG
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
#else
|
||||
using Barotrauma.IO;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -75,7 +79,7 @@ namespace Barotrauma
|
||||
Folder = Path.GetDirectoryName(FullPath);
|
||||
}
|
||||
|
||||
public virtual bool Save(string fileNameWithoutExtension = null, XmlWriterSettings settings = null)
|
||||
public virtual bool Save(string fileNameWithoutExtension = null, System.Xml.XmlWriterSettings settings = null)
|
||||
{
|
||||
if (!Directory.Exists(Folder))
|
||||
{
|
||||
@@ -85,7 +89,7 @@ namespace Barotrauma
|
||||
Serialize();
|
||||
if (settings == null)
|
||||
{
|
||||
settings = new XmlWriterSettings
|
||||
settings = new System.Xml.XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
|
||||
+15
-2
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Xml;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
@@ -470,6 +470,12 @@ namespace Barotrauma
|
||||
[Serialize(true, true), Editable]
|
||||
public bool CanBeSevered { get; set; }
|
||||
|
||||
[Serialize(0f, true, description:"Default 0 (Can't be severed when the creature is alive). Modifies the severance probability (defined per item/attack) when the character is alive. Currently only affects non-humanoid ragdolls. Also note that if CanBeSevered is false, this property doesn't have any effect."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float SeveranceProbabilityModifier { get; set; }
|
||||
|
||||
[Serialize("gore", true), Editable]
|
||||
public string BreakSound { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool LimitEnabled { get; set; }
|
||||
|
||||
@@ -491,6 +497,9 @@ namespace Barotrauma
|
||||
[Serialize(1f, true, description: "CAUTION: Not fully implemented. Only use for limb joints that connect non-animated limbs!"), Editable]
|
||||
public float Scale { get; set; }
|
||||
|
||||
[Serialize(false, false), Editable(ReadOnly = true)]
|
||||
public bool WeldJoint { get; set; }
|
||||
|
||||
public JointParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
}
|
||||
|
||||
@@ -605,7 +614,11 @@ namespace Barotrauma
|
||||
[Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float AttackForceMultiplier { get; set; }
|
||||
|
||||
[Serialize(1f, true, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float MinSeveranceDamage { get; set; }
|
||||
|
||||
// Non-editable ->
|
||||
// TODO: make read-only
|
||||
[Serialize(0, true)]
|
||||
public int HealthIndex { get; set; }
|
||||
|
||||
@@ -936,7 +949,7 @@ namespace Barotrauma
|
||||
{
|
||||
public override string Name => "Light Texture";
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
[Serialize("Content/Lights/pointlight_bright.png", true), Editable]
|
||||
public string Texture { get; private set; }
|
||||
|
||||
[Serialize("0.5, 0.5", true), Editable(DecimalCount = 2)]
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Barotrauma
|
||||
|
||||
if (Current == null)
|
||||
{
|
||||
DebugConsole.NewMessage("Now skill settings found in the selected content packages. Using default values.");
|
||||
DebugConsole.NewMessage("No skill settings found in the selected content packages. Using default values.");
|
||||
Current = new SkillSettings(null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user