(5c35a640e) Update tutorial-rework with dev
This commit is contained in:
@@ -110,7 +110,7 @@ namespace Barotrauma
|
||||
SonarLabel = element.GetAttributeString("sonarlabel", "");
|
||||
}
|
||||
|
||||
public AITarget(Entity e)
|
||||
public AITarget(Entity e, float sightRange = 3000, float soundRange = 0)
|
||||
{
|
||||
Entity = e;
|
||||
SightRange = sightRange;
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class TargetingPriority
|
||||
{
|
||||
public string TargetTag;
|
||||
@@ -100,6 +99,9 @@ namespace Barotrauma
|
||||
|
||||
private readonly float aggressiongreed;
|
||||
private readonly float aggressionhurt;
|
||||
// TODO: expose?
|
||||
private readonly float priorityFearIncreasement = 2;
|
||||
private readonly float memoryFadeTime = 0.5f;
|
||||
|
||||
public bool AttackHumans
|
||||
{
|
||||
@@ -249,8 +251,7 @@ namespace Barotrauma
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = FindTargetMemory(target);
|
||||
|
||||
selectedTargetMemory = GetTargetMemory(target);
|
||||
targetValue = 100.0f;
|
||||
}
|
||||
|
||||
@@ -287,8 +288,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetingPriority targetingPriority = null;
|
||||
UpdateTargets(Character, out targetingPriority);
|
||||
var targetingPriority = UpdateTargets(Character);
|
||||
updateTargetsTimer = UpdateTargetsInterval;
|
||||
|
||||
if (SelectedAiTarget == null)
|
||||
@@ -395,13 +395,44 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
SteeringManager.SteeringWander();
|
||||
if (Character.CurrentHull == null)
|
||||
else if (selectedTargetMemory != null)
|
||||
{
|
||||
selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement;
|
||||
}
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
// Seek exit, if inside
|
||||
if (SteeringManager is IndoorsSteeringManager indoorSteering && escapePoint == Vector2.Zero)
|
||||
{
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.Submarine != Character.Submarine) { continue; }
|
||||
if (gap.Open < 1 || gap.IsRoomToRoom) { continue; }
|
||||
var path = indoorSteering.PathFinder.FindPath(Character.SimPosition, gap.SimPosition);
|
||||
if (!path.Unreachable)
|
||||
{
|
||||
if (escapePoint != Vector2.Zero)
|
||||
{
|
||||
// Ignore the gap if it's further away than the previously assigned escape point
|
||||
if (Vector2.DistanceSquared(Character.SimPosition, gap.SimPosition) > Vector2.DistanceSquared(Character.SimPosition, escapePoint)) { continue; }
|
||||
}
|
||||
escapePoint = gap.SimPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (escapePoint != Vector2.Zero && Vector2.DistanceSquared(Character.SimPosition, escapePoint) > 1)
|
||||
{
|
||||
SteeringManager.SteeringSeek(escapePoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If outside or near enough the escapePoint, steer away
|
||||
escapePoint = Vector2.Zero;
|
||||
Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
SteeringManager.SteeringWander();
|
||||
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f);
|
||||
}
|
||||
}
|
||||
@@ -418,14 +449,8 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
selectedTargetMemory.Priority -= deltaTime * 0.1f;
|
||||
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine != null && Character.Submarine != SelectedAiTarget.Entity.Submarine)
|
||||
{
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition - Character.Submarine.Position);
|
||||
}
|
||||
Vector2 attackWorldPos = SelectedAiTarget.WorldPosition;
|
||||
Vector2 attackSimPos = SelectedAiTarget.SimPosition;
|
||||
|
||||
if (SelectedAiTarget.Entity is Item item)
|
||||
{
|
||||
@@ -441,22 +466,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (wallTarget != null)
|
||||
if (raycastTimer > 0.0)
|
||||
{
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position);
|
||||
if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null)
|
||||
{
|
||||
attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
|
||||
}
|
||||
raycastTimer -= deltaTime;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Character c)
|
||||
else
|
||||
{
|
||||
UpdateWallTarget();
|
||||
raycastTimer = RaycastInterval;
|
||||
}
|
||||
|
||||
if (SelectedAiTarget.Entity is Character c)
|
||||
{
|
||||
//target the closest limb if the target is a character
|
||||
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f;
|
||||
foreach (Limb limb in ((Character)SelectedAiTarget.Entity).AnimController.Limbs)
|
||||
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.WorldPosition, WorldPosition) * 10.0f;
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb == null) continue;
|
||||
float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
float dist = Vector2.DistanceSquared(limb.WorldPosition, WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
@@ -466,12 +493,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
|
||||
if (wallTarget != null)
|
||||
{
|
||||
Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left;
|
||||
attackWorldPos = wallTarget.Position;
|
||||
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Take the sub position into account in the sim pos
|
||||
if (Character.Submarine == null && SelectedAiTarget.Entity.Submarine != null)
|
||||
{
|
||||
attackSimPos += SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
else if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine == null)
|
||||
{
|
||||
attackSimPos -= Character.Submarine.SimPosition;
|
||||
}
|
||||
}
|
||||
|
||||
if (raycastTimer > 0.0)
|
||||
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
|
||||
{
|
||||
Character.AnimController.TargetDir = Character.WorldPosition.X < attackWorldPos.X ? Direction.Right : Direction.Left;
|
||||
}
|
||||
|
||||
if (aggressiveBoarding)
|
||||
{
|
||||
//targeting a wall section that can be passed through -> steer manually through the hole
|
||||
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex))
|
||||
@@ -529,72 +574,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateWallTarget();
|
||||
raycastTimer = RaycastInterval;
|
||||
}
|
||||
|
||||
if (aggressiveBoarding)
|
||||
{
|
||||
//targeting a wall section that can be passed through -> steer manually through the hole
|
||||
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex))
|
||||
{
|
||||
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
|
||||
Hull targetHull = section.gap?.FlowTargetHull;
|
||||
if (targetHull != null && !section.gap.IsRoomToRoom)
|
||||
{
|
||||
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
|
||||
if (wallTarget.Structure.IsHorizontal)
|
||||
{
|
||||
targetPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPos.X = targetHull.WorldRect.Center.X;
|
||||
}
|
||||
|
||||
latchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
if (steeringManager is IndoorsSteeringManager)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetPos - Character.WorldPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetPos));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Item)
|
||||
{
|
||||
var door = ((Item)SelectedAiTarget.Entity).GetComponent<Door>();
|
||||
//steer through the door manually if it's open or broken
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f))
|
||||
{
|
||||
var velocity = Vector2.Normalize(door.LinkedGap.FlowTargetHull.WorldPosition - Character.WorldPosition);
|
||||
if (door.LinkedGap.IsHorizontal)
|
||||
{
|
||||
if (Character.WorldPosition.Y < door.Item.WorldRect.Y && Character.WorldPosition.Y > door.Item.WorldRect.Y - door.Item.Rect.Height)
|
||||
{
|
||||
velocity.Y = 0;
|
||||
steeringManager.SteeringManual(deltaTime, velocity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Character.WorldPosition.X < door.Item.WorldRect.X && Character.WorldPosition.X > door.Item.WorldRect.Right)
|
||||
{
|
||||
velocity.X = 0;
|
||||
steeringManager.SteeringManual(deltaTime, velocity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool canAttack = true;
|
||||
if (IsCoolDownRunning)
|
||||
@@ -612,7 +591,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
UpdateFallBack(attackWorldPos, deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -620,14 +599,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (attackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// Don't allow attacking when the attack target has changed.
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
canAttack = false;
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
{
|
||||
// Fall back if cannot attack.
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
UpdateFallBack(attackWorldPos, deltaTime);
|
||||
return;
|
||||
}
|
||||
attackingLimb = null;
|
||||
@@ -636,7 +615,7 @@ namespace Barotrauma
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var previousLimb = attackingLimb;
|
||||
var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
|
||||
var newLimb = GetAttackLimb(attackWorldPos, previousLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
attackingLimb = newLimb;
|
||||
@@ -650,7 +629,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
UpdateFallBack(attackWorldPos, deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -665,15 +644,15 @@ namespace Barotrauma
|
||||
break;
|
||||
case AIBehaviorAfterAttack.FallBack:
|
||||
default:
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
UpdateFallBack(attackWorldPos, deltaTime);
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (attackingLimb == null)
|
||||
if (attackingLimb == null || _previousAiTarget != SelectedAiTarget)
|
||||
{
|
||||
attackingLimb = GetAttackLimb(attackSimPosition);
|
||||
attackingLimb = GetAttackLimb(attackWorldPos);
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
@@ -683,24 +662,39 @@ namespace Barotrauma
|
||||
if (canAttack)
|
||||
{
|
||||
// Check that we can reach the target
|
||||
distance = ConvertUnits.ToDisplayUnits(Vector2.Distance(attackingLimb.SimPosition, attackSimPosition));
|
||||
distance = Vector2.Distance(attackingLimb.WorldPosition, attackWorldPos);
|
||||
canAttack = distance < attackingLimb.attack.Range;
|
||||
}
|
||||
|
||||
Limb steeringLimb = Character.AnimController.MainLimb;
|
||||
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso.
|
||||
// If we always use the main limb, this causes the character to seek the target with it's torso/head, when it should not. For example Mudraptor steering with it's belly, when it should use it's head.
|
||||
// So let's use the one that's closer to the attacking limb.
|
||||
Limb steeringLimb;
|
||||
var torso = Character.AnimController.GetLimb(LimbType.Torso);
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (attackingLimb == null)
|
||||
{
|
||||
steeringLimb = head ?? torso;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (head != null && torso != null)
|
||||
{
|
||||
steeringLimb = Vector2.DistanceSquared(attackingLimb.SimPosition, head.SimPosition) < Vector2.DistanceSquared(attackingLimb.SimPosition, torso.SimPosition) ? head : torso;
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringLimb = head ?? torso;
|
||||
}
|
||||
}
|
||||
if (steeringLimb != null)
|
||||
{
|
||||
Vector2 steeringVector = attackSimPosition - steeringLimb.SimPosition;
|
||||
Vector2 targetingVector = Vector2.Normalize(steeringVector) * attackingLimb.attack.Range;
|
||||
// Offset the position a bit so that we don't overshoot the movement.
|
||||
Vector2 steerPos = attackSimPosition + targetingVector;
|
||||
steeringManager.SteeringSeek(steerPos, 10);
|
||||
if (Character.CurrentHull == null)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
|
||||
}
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 steerPos = attackSimPos + offset;
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
|
||||
if (steeringManager is IndoorsSteeringManager indoorsSteering)
|
||||
if (SteeringManager is IndoorsSteeringManager indoorsSteering)
|
||||
{
|
||||
if (indoorsSteering.CurrentPath != null && !indoorsSteering.IsPathDirty)
|
||||
{
|
||||
@@ -715,7 +709,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (indoorsSteering.CurrentPath.Finished)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(steeringVector));
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
|
||||
}
|
||||
else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null)
|
||||
{
|
||||
@@ -729,40 +723,42 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Character.CurrentHull == null)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
|
||||
}
|
||||
}
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition, distance);
|
||||
UpdateLimbAttack(deltaTime, attackingLimb, attackSimPos, distance);
|
||||
}
|
||||
}
|
||||
|
||||
private Limb GetAttackLimb(Vector2 attackSimPosition, Limb ignoredLimb = null)
|
||||
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
|
||||
{
|
||||
AttackContext currentContext = Character.GetAttackContext();
|
||||
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
|
||||
var limbs = Character.AnimController.Limbs
|
||||
.Where(l =>
|
||||
l != ignoredLimb &&
|
||||
l.attack != null &&
|
||||
!l.IsSevered &&
|
||||
!l.IsStuck &&
|
||||
l.attack.IsValidContext(currentContext) &&
|
||||
l.attack.IsValidTarget(target) &&
|
||||
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character)))
|
||||
.OrderByDescending(l => l.attack.Priority)
|
||||
.ThenBy(l => ConvertUnits.ToDisplayUnits(Vector2.Distance(l.SimPosition, attackSimPosition)));
|
||||
// TODO: priority should probably not override the distance -> use values instead of booleans
|
||||
return limbs.FirstOrDefault();
|
||||
}
|
||||
|
||||
private void UpdateWallTarget()
|
||||
{
|
||||
wallTarget = null;
|
||||
|
||||
if (Character.AnimController.CurrentHull != null)
|
||||
{
|
||||
return;
|
||||
Hull targetHull = section.gap?.FlowTargetHull;
|
||||
if (targetHull != null)
|
||||
{
|
||||
if (wall.IsHorizontal)
|
||||
{
|
||||
targetWorldPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetWorldPos.X = targetHull.WorldRect.Center.X;
|
||||
}
|
||||
latchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
if (steeringManager is IndoorsSteeringManager)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -791,32 +787,23 @@ namespace Barotrauma
|
||||
wallTarget = null;
|
||||
|
||||
//check if there's a wall between the target and the Character
|
||||
Vector2 rayStart = Character.SimPosition;
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null;
|
||||
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
if (offset)
|
||||
{
|
||||
rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
|
||||
if (Submarine.LastPickedFraction == 1.0f || closestBody == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Structure wall = closestBody.UserData as Structure;
|
||||
if (wall?.Submarine == null)
|
||||
{
|
||||
return;
|
||||
/*if (selectedAiTarget.Entity.Submarine != null)
|
||||
{
|
||||
wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition), selectedAiTarget.Entity.Submarine);
|
||||
latchOntoAI?.SetAttachTarget(closestBody, selectedAiTarget.Entity.Submarine, Submarine.LastPickedPosition);
|
||||
}*/
|
||||
//if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null) wallAttackPos += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
|
||||
}
|
||||
else
|
||||
if (closestBody.UserData is Structure wall && wall.Submarine != null)
|
||||
{
|
||||
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
|
||||
int passableHoleCount = GetMinimumPassableHoleCount();
|
||||
@@ -826,49 +813,46 @@ namespace Barotrauma
|
||||
{
|
||||
if (wall.SectionBodyDisabled(i))
|
||||
{
|
||||
if (aggressiveBoarding && CanPassThroughHole(wall, i)) //aggressive boarders always target holes they can pass through
|
||||
if (aggressiveBoarding && CanPassThroughHole(wall, i))
|
||||
{
|
||||
//aggressive boarders always target holes they can pass through
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
else //otherwise ignore and keep breaking other sections
|
||||
else
|
||||
{
|
||||
//otherwise ignore and keep breaking other sections
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
|
||||
}
|
||||
|
||||
Vector2 sectionPos = ConvertUnits.ToSimUnits(wall.SectionPosition(sectionIndex));
|
||||
Vector2 sectionPos = wall.SectionPosition(sectionIndex);
|
||||
Vector2 attachTargetNormal;
|
||||
if (wall.IsHorizontal)
|
||||
{
|
||||
attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y));
|
||||
sectionPos.Y += ConvertUnits.ToSimUnits((wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2) * attachTargetNormal.Y;
|
||||
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y));
|
||||
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f);
|
||||
sectionPos.X += ConvertUnits.ToSimUnits((wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2) * attachTargetNormal.X;
|
||||
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
|
||||
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
|
||||
}
|
||||
wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(sectionPos), wall, sectionIndex);
|
||||
|
||||
latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, sectionPos, attachTargetNormal);
|
||||
if (wall.Submarine != null)
|
||||
{
|
||||
sectionPos += wall.Submarine.Position;
|
||||
}
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f);
|
||||
|
||||
// Reduce the cooldown so that the character can react
|
||||
foreach (var limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.attack != null)
|
||||
{
|
||||
limb.attack.CoolDownTimer *= 0.1f;
|
||||
// secondary cooldown?
|
||||
}
|
||||
}
|
||||
|
||||
if (attackResult.Damage > 0.0f && attackWhenProvoked)
|
||||
{
|
||||
@@ -883,37 +867,60 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
|
||||
if (attacker == null || attacker.AiTarget == null) return;
|
||||
AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget);
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt;
|
||||
|
||||
// Reduce the cooldown so that the character can react
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of cooldown
|
||||
if (SelectedAiTarget != attacker.AiTarget)
|
||||
{
|
||||
foreach (var limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.attack != null)
|
||||
{
|
||||
limb.attack.CoolDownTimer *= 0.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10 dmg, 100 health -> 0.1
|
||||
private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f);
|
||||
|
||||
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition, float distance = -1)
|
||||
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackSimPos, float distance = -1)
|
||||
{
|
||||
var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
|
||||
if (damageTarget == null) return;
|
||||
|
||||
float prevHealth = damageTarget.Health;
|
||||
if (limb.UpdateAttack(deltaTime, attackPosition, damageTarget, out AttackResult attackResult, distance))
|
||||
if (SelectedAiTarget == null) { return; }
|
||||
if (wallTarget != null)
|
||||
{
|
||||
if (damageTarget.Health > 0)
|
||||
// If the selected target is not the wall target, make the wall target the selected target.
|
||||
var aiTarget = wallTarget.Structure.AiTarget;
|
||||
if (aiTarget != null && SelectedAiTarget != aiTarget)
|
||||
{
|
||||
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
|
||||
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed;
|
||||
SelectTarget(aiTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (!limb.attack.IsRunning)
|
||||
if (SelectedAiTarget.Entity is IDamageable damageTarget)
|
||||
{
|
||||
wallTarget = null;
|
||||
float prevHealth = damageTarget.Health;
|
||||
if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance))
|
||||
{
|
||||
if (damageTarget.Health > 0)
|
||||
{
|
||||
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
|
||||
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedTargetMemory.Priority = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFallBack(Vector2 attackPosition, float deltaTime)
|
||||
private void UpdateFallBack(Vector2 attackWorldPos, float deltaTime)
|
||||
{
|
||||
float dist = Vector2.Distance(attackPosition, Character.SimPosition);
|
||||
Vector2 attackVector = attackWorldPos - WorldPosition;
|
||||
float dist = attackVector.Length();
|
||||
float desiredDist = colliderSize * 2.0f;
|
||||
if (dist < desiredDist)
|
||||
{
|
||||
@@ -921,7 +928,6 @@ namespace Barotrauma
|
||||
if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY;
|
||||
steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f)));
|
||||
}
|
||||
|
||||
steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f);
|
||||
}
|
||||
|
||||
@@ -972,15 +978,13 @@ namespace Barotrauma
|
||||
//goes through all the AItargets, evaluates how preferable it is to attack the target,
|
||||
//whether the Character can see/hear the target and chooses the most preferable target within
|
||||
//sight/hearing range
|
||||
public void UpdateTargets(Character character, out TargetingPriority targetingPriority)
|
||||
public TargetingPriority UpdateTargets(Character character)
|
||||
{
|
||||
targetingPriority = null;
|
||||
SelectedAiTarget = null;
|
||||
AITarget newTarget = null;
|
||||
TargetingPriority targetingPriority = null;
|
||||
selectedTargetMemory = null;
|
||||
targetValue = 0.0f;
|
||||
|
||||
UpdateTargetMemories();
|
||||
|
||||
foreach (AITarget target in AITarget.List)
|
||||
{
|
||||
if (!target.Enabled) continue;
|
||||
@@ -989,48 +993,67 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
float valueModifier = 1.0f;
|
||||
float dist = 0.0f;
|
||||
|
||||
Character targetCharacter = target.Entity as Character;
|
||||
|
||||
//ignore the aitarget if it is the Character itself
|
||||
if (targetCharacter == character) continue;
|
||||
|
||||
float valueModifier = 1;
|
||||
string targetingTag = null;
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
targetingTag = "dead";
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
// In a different sub or the target is outside when we are inside or vice versa -> Ignore the target
|
||||
continue;
|
||||
}
|
||||
else if (targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
// In the same sub, halve the priority, if not in the same hull.
|
||||
valueModifier = 0.5f;
|
||||
}
|
||||
}
|
||||
else if (targetCharacter.AIController is EnemyAIController enemy)
|
||||
{
|
||||
if (enemy.combatStrength > combatStrength)
|
||||
{
|
||||
targetingTag = "stronger";
|
||||
}
|
||||
else if (enemy.combatStrength < combatStrength)
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
if (State == AIState.Escape && targetingTag == "stronger")
|
||||
{
|
||||
// Frightened
|
||||
valueModifier = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
// In a different sub or the target is outside when we are inside or vice versa -> Ignore the target
|
||||
continue;
|
||||
}
|
||||
else if (targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
// In the same sub, halve the priority, if not in the same hull.
|
||||
valueModifier = 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (targetCharacter.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
//target inside, AI outside -> we'll be attacking a wall between the characters so use the priority for attacking rooms
|
||||
targetingTag = "room";
|
||||
}
|
||||
else if (targetCharacter.IsDead)
|
||||
{
|
||||
targetingTag = "dead";
|
||||
}
|
||||
else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
|
||||
{
|
||||
targetingTag = targetCharacter.SpeciesName.ToLowerInvariant();
|
||||
}
|
||||
else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
|
||||
{
|
||||
targetingTag = targetCharacter.SpeciesName.ToLowerInvariant();
|
||||
}
|
||||
else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
|
||||
{
|
||||
if (targetCharacter.AIController is EnemyAIController enemy)
|
||||
{
|
||||
if (enemy.combatStrength > combatStrength)
|
||||
{
|
||||
targetingTag = "stronger";
|
||||
}
|
||||
else if (enemy.combatStrength < combatStrength)
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target.Entity != null)
|
||||
{
|
||||
@@ -1056,19 +1079,63 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target.Entity is Structure s)
|
||||
{
|
||||
targetingTag = "wall";
|
||||
if (aggressiveBoarding)
|
||||
{
|
||||
// Ignore walls when inside.
|
||||
valueModifier = character.CurrentHull == null ? 2 : 0;
|
||||
if (valueModifier > 0)
|
||||
{
|
||||
// Ignore structures that doesn't have a body (not walls)
|
||||
valueModifier *= s.HasBody ? 1 : 0;
|
||||
}
|
||||
for (int i = 0; i < s.Sections.Length; i++)
|
||||
{
|
||||
var section = s.Sections[i];
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
// Ignore walls that can be passed through
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
else if (section.gap != null)
|
||||
{
|
||||
// up to 100% priority increase for every gap in the wall
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetingTag = "room";
|
||||
}
|
||||
|
||||
if (door != null)
|
||||
{
|
||||
//increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
|
||||
if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom)
|
||||
// If there's not a more specific tag for the door
|
||||
if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room")
|
||||
{
|
||||
valueModifier = door.IsOpen ? 10 : 5;
|
||||
targetingTag = "door";
|
||||
}
|
||||
else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors
|
||||
bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom;
|
||||
bool isOpen = door.IsOpen || door.Item.Condition <= 0.0f;
|
||||
//increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
|
||||
if (aggressiveBoarding)
|
||||
{
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
valueModifier = isOutdoor ? 1 : 0;
|
||||
valueModifier *= isOpen ? 5 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueModifier = isOutdoor ? 0 : 1;
|
||||
valueModifier *= isOpen ? 0 : 1;
|
||||
}
|
||||
}
|
||||
else if (isOpen) //ignore broken and open doors
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1084,10 +1151,15 @@ namespace Barotrauma
|
||||
|
||||
valueModifier *= targetingPriorities[targetingTag].Priority;
|
||||
|
||||
if (targetingTag == null) continue;
|
||||
if (!targetingPriorities.ContainsKey(targetingTag)) continue;
|
||||
|
||||
valueModifier *= targetingPriorities[targetingTag].Priority;
|
||||
|
||||
if (valueModifier == 0.0f) continue;
|
||||
|
||||
Vector2 toTarget = target.WorldPosition - character.WorldPosition;
|
||||
dist = toTarget.Length();
|
||||
float dist = toTarget.Length();
|
||||
|
||||
//if the target has been within range earlier, the character will notice it more easily
|
||||
//(i.e. remember where the target was)
|
||||
@@ -1101,28 +1173,29 @@ namespace Barotrauma
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
|
||||
AITargetMemory targetMemory = FindTargetMemory(target);
|
||||
AITargetMemory targetMemory = GetTargetMemory(target);
|
||||
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.
|
||||
dist *= 3;
|
||||
}
|
||||
valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist);
|
||||
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist);
|
||||
|
||||
if (valueModifier > targetValue)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
newTarget = target;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetingPriority = targetingPriorities[targetingTag];
|
||||
targetValue = valueModifier;
|
||||
}
|
||||
}
|
||||
|
||||
SelectedAiTarget = newTarget;
|
||||
if (SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
wallTarget = null;
|
||||
}
|
||||
_previousAiTarget = SelectedAiTarget;
|
||||
return targetingPriority;
|
||||
}
|
||||
|
||||
private AITargetMemory GetTargetMemory(AITarget target)
|
||||
@@ -1132,69 +1205,39 @@ namespace Barotrauma
|
||||
memory = new AITargetMemory(10);
|
||||
targetMemories.Add(target, memory);
|
||||
}
|
||||
|
||||
memory = new AITargetMemory(10.0f);
|
||||
targetMemories.Add(target, memory);
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
private List<AITarget> removals = new List<AITarget>();
|
||||
private void UpdateTargetMemories(float deltaTime)
|
||||
{
|
||||
List<AITarget> toBeRemoved = null;
|
||||
foreach (KeyValuePair<AITarget, AITargetMemory> memory in targetMemories)
|
||||
removals.Clear();
|
||||
foreach (var memory in targetMemories)
|
||||
{
|
||||
memory.Value.Priority += 0.1f;
|
||||
if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key))
|
||||
// Slowly decrease all memories
|
||||
memory.Value.Priority -= memoryFadeTime * deltaTime;
|
||||
// Remove targets that have no priority or have been removed
|
||||
if (memory.Value.Priority <= 1 || !AITarget.List.Contains(memory.Key))
|
||||
{
|
||||
if (toBeRemoved == null) toBeRemoved = new List<AITarget>();
|
||||
toBeRemoved.Add(memory.Key);
|
||||
removals.Add(memory.Key);
|
||||
}
|
||||
}
|
||||
removals.ForEach(r => targetMemories.Remove(r));
|
||||
}
|
||||
|
||||
if (toBeRemoved != null)
|
||||
{
|
||||
foreach (AITarget target in toBeRemoved)
|
||||
{
|
||||
targetMemories.Remove(target);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
{
|
||||
latchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
{
|
||||
latchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
escapePoint = Vector2.Zero;
|
||||
wallTarget = null;
|
||||
}
|
||||
|
||||
private int GetMinimumPassableHoleCount()
|
||||
{
|
||||
return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
{
|
||||
latchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
}
|
||||
|
||||
private int GetMinimumPassableHoleCount()
|
||||
{
|
||||
return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize);
|
||||
return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize);
|
||||
}
|
||||
|
||||
private bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
|
||||
(c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
|
||||
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC)))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
@@ -290,7 +290,7 @@ namespace Barotrauma
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
float damage = attackResult.Damage;
|
||||
if (damage < 0) { return; }
|
||||
if (damage <= 0) { return; }
|
||||
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == null)
|
||||
@@ -466,7 +466,9 @@ namespace Barotrauma
|
||||
// Even the smallest fire reduces the safety by 50%
|
||||
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
|
||||
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
|
||||
int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || e.TeamID != character.TeamID));
|
||||
int enemyCount = Character.CharacterList.Count(e =>
|
||||
e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious &&
|
||||
(e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC)));
|
||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||
float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ namespace Barotrauma
|
||||
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
IgnoreContainedItems = IgnoreAlreadyContainedItems
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
||||
};
|
||||
AddSubObjective(getItemObjective);
|
||||
return;
|
||||
|
||||
@@ -115,6 +115,7 @@ namespace Barotrauma
|
||||
unreachable.Add(goToObjective.Target as Hull);
|
||||
}
|
||||
goToObjective = null;
|
||||
SteeringManager.SteeringWander();
|
||||
}
|
||||
}
|
||||
else if (currentHull != null)
|
||||
@@ -131,6 +132,11 @@ namespace Barotrauma
|
||||
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
|
||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Barotrauma
|
||||
private string[] itemIdentifiers;
|
||||
private Item targetItem, moveToTarget;
|
||||
private int currSearchIndex;
|
||||
public bool IgnoreContainedItems;
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private bool equip;
|
||||
@@ -99,11 +99,12 @@ namespace Barotrauma
|
||||
FindTargetItem();
|
||||
if (targetItem == null || moveToTarget == null)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringWander();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
|
||||
if (moveToTarget.CurrentHull == character.CurrentHull &&
|
||||
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
|
||||
{
|
||||
int targetSlot = -1;
|
||||
if (equip)
|
||||
@@ -196,8 +197,12 @@ namespace Barotrauma
|
||||
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
|
||||
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
|
||||
if (IgnoreContainedItems && item.Container != null) { continue; }
|
||||
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
||||
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
||||
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
|
||||
//if the item is inside a character's inventory, don't steal it unless the character is dead
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
|
||||
@@ -1097,6 +1097,8 @@ namespace Barotrauma
|
||||
|
||||
CheckValidity();
|
||||
|
||||
CheckValidity();
|
||||
|
||||
UpdateNetPlayerPosition(deltaTime);
|
||||
CheckDistFromCollider();
|
||||
UpdateCollisionCategories();
|
||||
@@ -1389,6 +1391,19 @@ namespace Barotrauma
|
||||
}
|
||||
if (errorMsg != null)
|
||||
{
|
||||
if (character.IsRemotePlayer)
|
||||
{
|
||||
errorMsg += " Ragdoll controlled remotely.";
|
||||
}
|
||||
if (SimplePhysicsEnabled)
|
||||
{
|
||||
errorMsg += " Simple physics enabled.";
|
||||
}
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
|
||||
@@ -1110,13 +1110,15 @@ namespace Barotrauma
|
||||
ViewTarget = null;
|
||||
if (!AllowInput) return;
|
||||
|
||||
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
|
||||
if (Controlled == this)
|
||||
if (Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer))
|
||||
{
|
||||
SmoothedCursorPosition = cursorPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
//apply some smoothing to the cursor positions of remote players when playing as a client
|
||||
//to make aiming look a little less choppy
|
||||
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
|
||||
smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff);
|
||||
SmoothedCursorPosition = cursorPosition - smoothedCursorDiff;
|
||||
}
|
||||
@@ -1664,10 +1666,12 @@ namespace Barotrauma
|
||||
focusedItem = null;
|
||||
}
|
||||
findFocusedTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//climb ladders automatically when pressing up/down inside their trigger area
|
||||
if (SelectedConstruction == null && !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
|
||||
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>();
|
||||
if ((SelectedConstruction == null || currentLadder != null) &&
|
||||
!AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
|
||||
bool isControlled = Controlled == this;
|
||||
@@ -1678,6 +1682,19 @@ namespace Barotrauma
|
||||
float minDist = float.PositiveInfinity;
|
||||
foreach (Ladder ladder in Ladder.List)
|
||||
{
|
||||
if (ladder == currentLadder)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (currentLadder != null)
|
||||
{
|
||||
//only switch from ladder to another if the ladders are above the current ladders and pressing up, or vice versa
|
||||
if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y != IsKeyDown(InputType.Up))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (CanInteractWith(ladder.Item, out float dist, checkLinked: false) && dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
@@ -1934,9 +1951,23 @@ namespace Barotrauma
|
||||
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
|
||||
bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true;
|
||||
if (IsForceRagdolled)
|
||||
{
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
}
|
||||
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
|
||||
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f))
|
||||
{
|
||||
if (ragdollingLockTimer > 0.0f)
|
||||
{
|
||||
ragdollingLockTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; }
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSightRange();
|
||||
UpdateSoundRange();
|
||||
@@ -2518,7 +2549,14 @@ namespace Barotrauma
|
||||
{
|
||||
item.Submarine = inventory.Owner.Submarine;
|
||||
var itemElement = item.Save(parentElement);
|
||||
itemElement.Add(new XAttribute("i", Array.IndexOf(inventory.Items, item)));
|
||||
|
||||
List<int> slotIndices = new List<int>();
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (inventory.Items[i] == item) { slotIndices.Add(i); }
|
||||
}
|
||||
|
||||
itemElement.Add(new XAttribute("i", string.Join(",", slotIndices)));
|
||||
|
||||
foreach (ItemContainer container in item.GetComponents<ItemContainer>())
|
||||
{
|
||||
|
||||
@@ -801,13 +801,32 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (XElement itemElement in element.Elements())
|
||||
{
|
||||
var newItem = Item.Load(itemElement, inventory.Owner.Submarine);
|
||||
int slotIndex = itemElement.GetAttributeInt("i", 0);
|
||||
if (newItem == null) continue;
|
||||
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true);
|
||||
if (newItem == null) { continue; }
|
||||
|
||||
SpawnInventoryItemProjSpecific(newItem);
|
||||
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
|
||||
if (!slotIndices.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
|
||||
continue;
|
||||
}
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndex, false, false, null);
|
||||
//force the item to the correct slots
|
||||
// e.g. putting the item in a hand slot will also put it in the first available Any-slot,
|
||||
// which may not be where it actually was
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i))
|
||||
{
|
||||
inventory.Items[i] = newItem;
|
||||
}
|
||||
else if (inventory.Items[i] == newItem)
|
||||
{
|
||||
inventory.Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int itemContainerIndex = 0;
|
||||
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
|
||||
@@ -821,8 +840,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void SpawnInventoryItemProjSpecific(Item item);
|
||||
|
||||
public void ReloadHeadAttachments()
|
||||
{
|
||||
ResetLoadedAttachments();
|
||||
|
||||
@@ -13,17 +13,17 @@ namespace Barotrauma
|
||||
|
||||
public readonly AnimController.Animation Animation;
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float rotation, Vector2 velocity, float angularVelocity, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, interact, animation)
|
||||
{
|
||||
}
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float rotation, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, interact, animation)
|
||||
{
|
||||
}
|
||||
|
||||
protected CharacterStateInfo(Vector2 pos, float rotation, Vector2 velocity, float angularVelocity, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: base(pos, rotation, velocity, angularVelocity, ID, time)
|
||||
{
|
||||
Direction = dir;
|
||||
|
||||
+8
-3
@@ -151,9 +151,12 @@ namespace Barotrauma
|
||||
//how high the strength has to be for the affliction to take affect
|
||||
public readonly float ActivationThreshold = 0.0f;
|
||||
//how high the strength has to be for the affliction icon to be shown in the UI
|
||||
public readonly float ShowIconThreshold = 0.0f;
|
||||
public readonly float ShowIconThreshold = 0.05f;
|
||||
public readonly float MaxStrength = 100.0f;
|
||||
|
||||
|
||||
//how high the strength has to be for the affliction icon to be shown with a health scanner
|
||||
public readonly float ShowInHealthScannerThreshold = 0.05f;
|
||||
|
||||
public float BurnOverlayAlpha;
|
||||
public float DamageOverlayAlpha;
|
||||
|
||||
@@ -254,9 +257,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
|
||||
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", ActivationThreshold);
|
||||
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
|
||||
|
||||
|
||||
@@ -460,6 +460,7 @@ namespace Barotrauma
|
||||
{
|
||||
affliction.Strength = 0.0f;
|
||||
}
|
||||
CalculateVitality();
|
||||
}
|
||||
|
||||
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
|
||||
|
||||
@@ -491,10 +491,10 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||
/// </summary>
|
||||
public bool UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
|
||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
|
||||
{
|
||||
attackResult = default(AttackResult);
|
||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition));
|
||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos));
|
||||
bool wasRunning = attack.IsRunning;
|
||||
attack.UpdateAttackTimer(deltaTime);
|
||||
|
||||
@@ -511,7 +511,7 @@ namespace Barotrauma
|
||||
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
|
||||
|
||||
structureBody = Submarine.PickBody(
|
||||
SimPosition, attackPosition,
|
||||
SimPosition, attackSimPos,
|
||||
ignoredBodies, Physics.CollisionWall);
|
||||
|
||||
if (damageTarget is Item)
|
||||
@@ -520,9 +520,10 @@ namespace Barotrauma
|
||||
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
|
||||
wasHit = true;
|
||||
}
|
||||
else if (damageTarget is Structure && structureBody?.UserData is Structure)
|
||||
else if (damageTarget is Structure wall && structureBody != null &&
|
||||
(structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine)))
|
||||
{
|
||||
// If the attack is aimed to a structure and hits a structure, it's successful
|
||||
// If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful
|
||||
wasHit = true;
|
||||
}
|
||||
else
|
||||
@@ -607,7 +608,7 @@ namespace Barotrauma
|
||||
attack.SetCoolDown();
|
||||
}
|
||||
|
||||
Vector2 diff = attackPosition - SimPosition;
|
||||
Vector2 diff = attackSimPos - SimPosition;
|
||||
bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
|
||||
if (applyForces)
|
||||
{
|
||||
@@ -620,13 +621,13 @@ namespace Barotrauma
|
||||
|
||||
Limb limb = character.AnimController.Limbs[limbIndex];
|
||||
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
|
||||
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
|
||||
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
|
||||
}
|
||||
}
|
||||
return wasHit;
|
||||
|
||||
Reference in New Issue
Block a user