Build 1.1.4.0
This commit is contained in:
@@ -59,8 +59,9 @@ namespace Barotrauma.Items.Components
|
||||
private float aiTargetingGraceTimer;
|
||||
|
||||
private float aiFindTargetTimer;
|
||||
private Character currentTarget;
|
||||
const float aiFindTargetInterval = 5.0f;
|
||||
private ISpatialEntity currentTarget;
|
||||
private const float CrewAiFindTargetMaxInterval = 3.0f;
|
||||
private const float CrewAIFindTargetMinInverval = 0.2f;
|
||||
|
||||
private int currentLoaderIndex;
|
||||
|
||||
@@ -73,6 +74,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private List<LightComponent> lightComponents;
|
||||
|
||||
private readonly bool isSlowTurret;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
@@ -317,6 +320,42 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description:"Should the turret operate automatically using AI targeting? Comes with some optional random movement that can be adjusted below."), Editable]
|
||||
public bool AutoOperate { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "[Auto Operate] How much the turret should adjust the aim off the target randomly instead of tracking the target perfectly? In Degrees."), Editable]
|
||||
public float RandomAimAmount { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "[Auto Operate] How often the turret should adjust the aim randomly instead of tracking the target perfectly? Minimum wait time, in seconds."), Editable]
|
||||
public float RandomAimMinTime { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "[Auto Operate] How often the turret should adjust the aim randomly instead of tracking the target perfectly? Maximum wait time, in seconds."), Editable]
|
||||
public float RandomAimMaxTime { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret move randomly while idle?"), Editable]
|
||||
public bool RandomMovement { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret have a delay while targeting targets or always aim prefectly?"), Editable]
|
||||
public bool AimDelay { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target characters in general?"), Editable]
|
||||
public bool TargetCharacters { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target all monsters?"), Editable]
|
||||
public bool TargetMonsters { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target all humans (or creatures in the same group, like pets)?"), Editable]
|
||||
public bool TargetHumans { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target other submarines?"), Editable]
|
||||
public bool TargetSubmarines { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "[Auto Operate] Should the turret target items?"), Editable]
|
||||
public bool TargetItems { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "[Auto Operate] Group or SpeciesName that the AI ignores when the turret is operated automatically."), Editable]
|
||||
public Identifier FriendlyTag { get; private set; }
|
||||
|
||||
public Turret(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -346,6 +385,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
item.IsShootable = true;
|
||||
item.RequireAimToUse = false;
|
||||
isSlowTurret = item.HasTag("slowturret");
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -560,6 +600,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
UpdateLightComponents();
|
||||
|
||||
if (AutoOperate)
|
||||
{
|
||||
UpdateAutoOperate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLightComponents()
|
||||
@@ -658,13 +703,20 @@ namespace Barotrauma.Items.Components
|
||||
loaderBroken = true;
|
||||
continue;
|
||||
}
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (tryUseProjectileContainer(linkedItem)) { break; }
|
||||
}
|
||||
tryUseProjectileContainer(item);
|
||||
|
||||
bool tryUseProjectileContainer(Item containerItem)
|
||||
{
|
||||
ItemContainer projectileContainer = containerItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
containerItem.Use(deltaTime, null);
|
||||
projectiles = GetLoadedProjectiles();
|
||||
if (projectiles.Any()) { break; }
|
||||
if (projectiles.Any()) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
|
||||
@@ -833,9 +885,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
|
||||
projectile.SetTransform(
|
||||
ConvertUnits.ToSimUnits(GetRelativeFiringPosition()),
|
||||
-(launchRotation ?? rotation) + spread);
|
||||
|
||||
Vector2 launchPos = ConvertUnits.ToSimUnits(GetRelativeFiringPosition());
|
||||
|
||||
//check if there's some other sub between the turret's origin and the launch pos,
|
||||
//and if so, launch at the intersection of the turret and the sub to prevent the projectile from spawning inside the other sub
|
||||
Body pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), launchPos, null, Physics.CollisionWall, allowInsideFixture: true,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
return f.Body.UserData is not Submarine sub || sub != item.Submarine;
|
||||
});
|
||||
if (pickedBody != null)
|
||||
{
|
||||
launchPos = Submarine.LastPickedPosition;
|
||||
}
|
||||
projectile.SetTransform(launchPos, -(launchRotation ?? rotation) + spread);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body?.Submarine;
|
||||
|
||||
@@ -895,17 +959,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private float waitTimer;
|
||||
private float disorderTimer;
|
||||
private float randomAimTimer;
|
||||
|
||||
private float prevTargetRotation;
|
||||
private float updateTimer;
|
||||
private bool updatePending;
|
||||
public void ThalamusOperate(WreckAI ai, float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
|
||||
{
|
||||
if (ai == null) { return; }
|
||||
|
||||
public void UpdateAutoOperate(float deltaTime, Identifier friendlyTag = default)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
if (friendlyTag.IsEmpty)
|
||||
{
|
||||
friendlyTag = FriendlyTag;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
@@ -924,7 +992,7 @@ namespace Barotrauma.Items.Components
|
||||
updateTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (!ignoreDelay && waitTimer > 0)
|
||||
if (AimDelay && waitTimer > 0)
|
||||
{
|
||||
waitTimer -= deltaTime;
|
||||
return;
|
||||
@@ -934,40 +1002,48 @@ namespace Barotrauma.Items.Components
|
||||
float shootDistance = AIRange;
|
||||
ISpatialEntity target = null;
|
||||
float closestDist = shootDistance * shootDistance;
|
||||
if (targetHumans || targetOtherCreatures)
|
||||
if (TargetCharacters)
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
if (character == null || character.Removed || character.IsDead) { continue; }
|
||||
if (character.Params.Group == ai.Config.Entity) { continue; }
|
||||
bool isHuman = character.IsHuman || character.Params.Group == CharacterPrefab.HumanSpeciesName;
|
||||
if (isHuman)
|
||||
{
|
||||
if (!targetHumans)
|
||||
{
|
||||
// Don't target humans if not defined to.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!targetOtherCreatures)
|
||||
{
|
||||
// Don't target other creatures if not defined to.
|
||||
continue;
|
||||
}
|
||||
if (!IsValidTarget(character)) { continue; }
|
||||
float priority = isSlowTurret ? character.Params.AISlowTurretPriority : character.Params.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
if (!IsValidTargetForAutoOperate(character, friendlyTag)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(character.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
if (!CheckTurretAngle(character.WorldPosition)) { continue; }
|
||||
target = character;
|
||||
closestDist = dist;
|
||||
closestDist = dist / priority;
|
||||
}
|
||||
}
|
||||
if (targetSubmarines)
|
||||
if (TargetItems)
|
||||
{
|
||||
foreach (Item targetItem in Item.ItemList)
|
||||
{
|
||||
if (!IsValidTarget(targetItem)) { continue; }
|
||||
float priority = isSlowTurret ? targetItem.Prefab.AISlowTurretPriority : targetItem.Prefab.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
if (dist > shootDistance * shootDistance) { continue; }
|
||||
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
|
||||
target = targetItem;
|
||||
closestDist = dist / priority;
|
||||
}
|
||||
}
|
||||
if (TargetSubmarines)
|
||||
{
|
||||
if (target == null || target.Submarine != null)
|
||||
{
|
||||
closestDist = maxDistance * maxDistance;
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (sub == Item.Submarine) { continue; }
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
if (Character.IsOnFriendlyTeam(item.Submarine.TeamID, sub.TeamID)) { continue; }
|
||||
}
|
||||
float dist = Vector2.DistanceSquared(sub.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
closestSub = sub;
|
||||
@@ -981,34 +1057,41 @@ namespace Barotrauma.Items.Components
|
||||
if (!closestSub.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(hull.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
// Don't check the angle, because it doesn't work on Thalamus spike. The angle check wouldn't be very important here anyway.
|
||||
target = hull;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreDelay)
|
||||
|
||||
if (target == null && RandomMovement)
|
||||
{
|
||||
if (target == null)
|
||||
// Random movement while there's no target
|
||||
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
|
||||
targetRotation = Rand.Range(minRotation, maxRotation);
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (AimDelay)
|
||||
{
|
||||
if (RandomAimAmount > 0)
|
||||
{
|
||||
// Random movement
|
||||
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
|
||||
targetRotation = Rand.Range(minRotation, maxRotation);
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
if (disorderTimer < 0)
|
||||
{
|
||||
// Random disorder
|
||||
disorderTimer = Rand.Range(0f, 3f);
|
||||
waitTimer = Rand.Range(0.25f, 1f);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-1f, 1f));
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
disorderTimer -= deltaTime;
|
||||
if (randomAimTimer < 0)
|
||||
{
|
||||
// Random disorder or other flaw in the targeting.
|
||||
randomAimTimer = Rand.Range(RandomAimMinTime, RandomAimMaxTime);
|
||||
waitTimer = Rand.Range(0.25f, 1f);
|
||||
float randomAim = MathHelper.ToRadians(RandomAimAmount);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-randomAim, randomAim));
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
randomAimTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target == null) { return; }
|
||||
@@ -1043,11 +1126,11 @@ namespace Barotrauma.Items.Components
|
||||
start -= target.Submarine.SimPosition;
|
||||
end -= target.Submarine.SimPosition;
|
||||
Body transformedTarget = CheckLineOfSight(start, end);
|
||||
shoot = CanShoot(transformedTarget, user: null, ai, targetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, ai, targetSubmarines));
|
||||
shoot = CanShoot(transformedTarget, user: null, friendlyTag, TargetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines));
|
||||
}
|
||||
else
|
||||
{
|
||||
shoot = CanShoot(worldTarget, user: null, ai, targetSubmarines);
|
||||
shoot = CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines);
|
||||
}
|
||||
if (shoot)
|
||||
{
|
||||
@@ -1055,7 +1138,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead)
|
||||
{
|
||||
@@ -1205,18 +1288,19 @@ namespace Barotrauma.Items.Components
|
||||
bool hadCurrentTarget = currentTarget != null;
|
||||
if (hadCurrentTarget)
|
||||
{
|
||||
if (currentTarget.Removed || currentTarget.IsDead)
|
||||
if (!IsValidTarget(currentTarget))
|
||||
{
|
||||
currentTarget = null;
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
}
|
||||
}
|
||||
|
||||
if (aiFindTargetTimer <= 0.0f || currentTarget == null)
|
||||
if (aiFindTargetTimer <= 0.0f)
|
||||
{
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
if (enemy.IsDead || !enemy.Enabled) { continue; }
|
||||
if (!IsValidTarget(enemy)) { continue; }
|
||||
float priority = isSlowTurret ? enemy.Params.AISlowTurretPriority : enemy.Params.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (enemy.Submarine == character.Submarine) { continue; }
|
||||
@@ -1233,30 +1317,53 @@ namespace Barotrauma.Items.Components
|
||||
// We shouldn't check the angle when a long creature is traveling outside of the shooting range, because doing so would not allow us to shoot the limbs that might be close enough to shoot at.
|
||||
if (!CheckTurretAngle(enemy.WorldPosition)) { continue; }
|
||||
}
|
||||
targetPos = enemy.WorldPosition;
|
||||
closestEnemy = enemy;
|
||||
closestDistance = dist;
|
||||
closestDistance = dist / priority;
|
||||
currentTarget = closestEnemy;
|
||||
}
|
||||
currentTarget = closestEnemy;
|
||||
aiFindTargetTimer = aiFindTargetInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
closestEnemy = currentTarget;
|
||||
}
|
||||
|
||||
if (closestEnemy != null)
|
||||
{
|
||||
targetPos = closestEnemy.WorldPosition;
|
||||
//if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is
|
||||
if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine && !closestEnemy.CanSeeTarget(Item))
|
||||
foreach (Item targetItem in Item.ItemList)
|
||||
{
|
||||
targetPos = closestEnemy.CurrentHull.WorldPosition;
|
||||
if (!IsValidTarget(targetItem)) { continue; }
|
||||
float priority = isSlowTurret ? targetItem.Prefab.AISlowTurretPriority : targetItem.Prefab.AITurretPriority;
|
||||
if (priority <= 0) { continue; }
|
||||
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (dist > shootDistance * shootDistance) { continue; }
|
||||
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
|
||||
targetPos = targetItem.WorldPosition;
|
||||
closestDistance = dist / priority;
|
||||
// Override the target character so that we can target the item instead.
|
||||
closestEnemy = null;
|
||||
currentTarget = targetItem;
|
||||
}
|
||||
if (currentTarget == null)
|
||||
{
|
||||
aiFindTargetTimer = CrewAIFindTargetMinInverval;
|
||||
}
|
||||
else
|
||||
{
|
||||
aiFindTargetTimer = CrewAiFindTargetMaxInterval;
|
||||
}
|
||||
}
|
||||
else if (currentTarget != null)
|
||||
{
|
||||
targetPos = currentTarget.WorldPosition;
|
||||
}
|
||||
bool iceSpireSpotted = false;
|
||||
// Adjust the target character position (limb or submarine)
|
||||
if (currentTarget is Character targetCharacter)
|
||||
{
|
||||
//if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is
|
||||
if (targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine && !targetCharacter.CanSeeTarget(Item))
|
||||
{
|
||||
targetPos = targetCharacter.CurrentHull.WorldPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Target the closest limb. Doesn't make much difference with smaller creatures, but enables the bots to shoot longer abyss creatures like the endworm. Otherwise they just target the main body = head.
|
||||
float closestDist = closestDistance;
|
||||
foreach (Limb limb in closestEnemy.AnimController.Limbs)
|
||||
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
@@ -1270,13 +1377,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (closestDist > shootDistance * shootDistance)
|
||||
{
|
||||
// Not close enough to shoot
|
||||
// Not close enough to shoot.
|
||||
currentTarget = null;
|
||||
closestEnemy = null;
|
||||
targetPos = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item.Submarine != null && Level.Loaded != null)
|
||||
else if (targetPos == null && item.Submarine != null && Level.Loaded != null)
|
||||
{
|
||||
// Check ice spires
|
||||
shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
|
||||
@@ -1286,50 +1394,49 @@ namespace Barotrauma.Items.Components
|
||||
if (wall is not DestructibleLevelWall destructibleWall || destructibleWall.Destroyed) { continue; }
|
||||
foreach (var cell in wall.Cells)
|
||||
{
|
||||
if (cell.DoesDamage)
|
||||
if (!cell.DoesDamage) { continue; }
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
Vector2 p1 = edge.Point1 + cell.Translation;
|
||||
Vector2 p2 = edge.Point2 + cell.Translation;
|
||||
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(p1, p2, item.WorldPosition);
|
||||
if (!CheckTurretAngle(closestPoint))
|
||||
{
|
||||
Vector2 p1 = edge.Point1 + cell.Translation;
|
||||
Vector2 p2 = edge.Point2 + cell.Translation;
|
||||
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(p1, p2, item.WorldPosition);
|
||||
if (!CheckTurretAngle(closestPoint))
|
||||
// The closest point can't be targeted -> get a point directly in front of the turret
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
if (MathUtils.GetLineIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
|
||||
{
|
||||
// The closest point can't be targeted -> get a point directly in front of the turret
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
if (MathUtils.GetLineIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
|
||||
{
|
||||
closestPoint = intersection;
|
||||
if (!CheckTurretAngle(closestPoint)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
closestPoint = intersection;
|
||||
if (!CheckTurretAngle(closestPoint)) { continue; }
|
||||
}
|
||||
float dist = Vector2.Distance(closestPoint, item.WorldPosition);
|
||||
|
||||
//add one px to make sure the visibility raycast doesn't miss the cell due to the end position being right at the edge of the cell
|
||||
closestPoint += (closestPoint - item.WorldPosition) / Math.Max(dist, 1);
|
||||
|
||||
if (dist > AIRange + 1000) { continue; }
|
||||
float dot = 0;
|
||||
if (!MathUtils.NearlyEqual(item.Submarine.Velocity, Vector2.Zero))
|
||||
else
|
||||
{
|
||||
dot = Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), Vector2.Normalize(closestPoint - item.Submarine.WorldPosition));
|
||||
}
|
||||
float minAngle = 0.5f;
|
||||
if (dot < minAngle && dist > 1000)
|
||||
{
|
||||
// The sub is not moving towards the target and it's not very close to the turret either -> ignore
|
||||
continue;
|
||||
}
|
||||
// Allow targeting farther when heading towards the spire (up to 1000 px)
|
||||
dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot));
|
||||
if (dist > closestDistance) { continue; }
|
||||
targetPos = closestPoint;
|
||||
closestDistance = dist;
|
||||
}
|
||||
float dist = Vector2.Distance(closestPoint, item.WorldPosition);
|
||||
|
||||
//add one px to make sure the visibility raycast doesn't miss the cell due to the end position being right at the edge of the cell
|
||||
closestPoint += (closestPoint - item.WorldPosition) / Math.Max(dist, 1);
|
||||
|
||||
if (dist > AIRange + 1000) { continue; }
|
||||
float dot = 0;
|
||||
if (!MathUtils.NearlyEqual(item.Submarine.Velocity, Vector2.Zero))
|
||||
{
|
||||
dot = Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), Vector2.Normalize(closestPoint - item.Submarine.WorldPosition));
|
||||
}
|
||||
float minAngle = 0.5f;
|
||||
if (dot < minAngle && dist > 1000)
|
||||
{
|
||||
// The sub is not moving towards the target and it's not very close to the turret either -> ignore
|
||||
continue;
|
||||
}
|
||||
// Allow targeting farther when heading towards the spire (up to 1000 px)
|
||||
dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot));
|
||||
if (dist > closestDistance) { continue; }
|
||||
targetPos = closestPoint;
|
||||
closestDistance = dist;
|
||||
iceSpireSpotted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1345,13 +1452,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget)
|
||||
{
|
||||
if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman)
|
||||
if (CreatureMetrics.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNewTargetSpotted").Value,
|
||||
identifier: "newtargetspotted".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (CreatureMetrics.Instance.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
else if (CreatureMetrics.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName).Value,
|
||||
identifier: "identifiedtargetspotted".ToIdentifier(),
|
||||
@@ -1364,17 +1471,17 @@ namespace Barotrauma.Items.Components
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
}
|
||||
else if (!CreatureMetrics.Instance.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
else if (!CreatureMetrics.Encountered.Contains(closestEnemy.SpeciesName))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted").Value,
|
||||
identifier: "unidentifiedtargetspotted".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
character.AddEncounter(closestEnemy);
|
||||
CreatureMetrics.AddEncounter(closestEnemy.SpeciesName);
|
||||
}
|
||||
character.AIController.SelectTarget(closestEnemy.AiTarget);
|
||||
}
|
||||
else if (closestEnemy == null && character.IsOnPlayerTeam)
|
||||
else if (iceSpireSpotted && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogIceSpireSpotted").Value,
|
||||
identifier: "icespirespotted".ToIdentifier(),
|
||||
@@ -1437,7 +1544,55 @@ namespace Barotrauma.Items.Components
|
||||
return 0;
|
||||
}
|
||||
|
||||
private bool CanShoot(Body targetBody, Character user = null, WreckAI ai = null, bool targetSubmarines = true)
|
||||
// Not exahustive, but helps to get rid of some code duplication
|
||||
private static bool IsValidTarget(ISpatialEntity target)
|
||||
{
|
||||
if (target == null) { return false; }
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
if (!targetCharacter.Enabled || targetCharacter.Removed || targetCharacter.IsDead || targetCharacter.AITurretPriority <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target is Item targetItem)
|
||||
{
|
||||
if (targetItem.Removed || targetItem.Condition <= 0 || !targetItem.Prefab.IsAITurretTarget || targetItem.Prefab.AITurretPriority <= 0 || targetItem.HiddenInGame)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (targetItem.Submarine != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsValidTargetForAutoOperate(Character target, Identifier friendlyTag)
|
||||
{
|
||||
if (!friendlyTag.IsEmpty)
|
||||
{
|
||||
if (target.SpeciesName.Equals(friendlyTag) || target.Group.Equals(friendlyTag)) { return false; }
|
||||
}
|
||||
bool isHuman = target.IsHuman || target.Group == CharacterPrefab.HumanSpeciesName;
|
||||
if (isHuman)
|
||||
{
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
// Check that the target is not in the friendly team, e.g. pirate or a hostile player sub (PvP).
|
||||
return !target.IsOnFriendlyTeam(item.Submarine.TeamID) && TargetHumans;
|
||||
}
|
||||
return TargetHumans;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't check the team here, because all the enemies are in the same team (None).
|
||||
return TargetMonsters;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanShoot(Body targetBody, Character user = null, Identifier friendlyTag = default, bool targetSubmarines = true)
|
||||
{
|
||||
if (targetBody == null) { return false; }
|
||||
Character targetCharacter = null;
|
||||
@@ -1449,7 +1604,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
if (targetCharacter != null && !targetCharacter.Removed)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
@@ -1458,27 +1613,25 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ai != null)
|
||||
else if (!IsValidTargetForAutoOperate(targetCharacter, friendlyTag))
|
||||
{
|
||||
if (targetCharacter.Params.Group == ai.Config.Entity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Note that Thalamus runs this even when AutoOperate is false.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetBody.UserData is ISpatialEntity e)
|
||||
{
|
||||
if (e is Structure s && s.Indestructible) { return false; }
|
||||
Submarine sub = e.Submarine ?? e as Submarine;
|
||||
if (e is Structure { Indestructible: true }) { return false; }
|
||||
if (!targetSubmarines && e is Submarine) { return false; }
|
||||
if (sub == null) { return false; }
|
||||
Submarine sub = e.Submarine ?? e as Submarine;
|
||||
if (sub == null) { return true; }
|
||||
if (sub == Item.Submarine) { return false; }
|
||||
if (sub.Info.IsOutpost || sub.Info.IsWreck || sub.Info.IsBeacon) { return false; }
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
}
|
||||
else if (!(targetBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible))
|
||||
else if (targetBody.UserData is not Voronoi2.VoronoiCell { IsDestructible: true })
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return false;
|
||||
@@ -1489,7 +1642,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Body CheckLineOfSight(Vector2 start, Vector2 end)
|
||||
{
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionProjectile;
|
||||
Body pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user