Faction Test v1.0.1.0

This commit is contained in:
Regalis11
2023-02-16 15:01:28 +02:00
parent caa5a2f762
commit 2c5a7923b0
309 changed files with 7502 additions and 4335 deletions
@@ -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; }
@@ -320,33 +323,36 @@ namespace Barotrauma.Items.Components
[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?"), Editable]
public float RandomAimAmount { get; private 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?"), Editable]
public float RandomAimMinTime { get; private 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?"), Editable]
public float RandomAimMaxTime { get; private 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 always aim at targets without delay?"), Editable]
public bool IgnoreAimDelay { 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?"), Editable]
[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 monsters?"), Editable]
[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 humans (or pets)"), Editable]
[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; }
@@ -379,6 +385,7 @@ namespace Barotrauma.Items.Components
}
item.IsShootable = true;
item.RequireAimToUse = false;
isSlowTurret = item.HasTag("slowturret");
InitProjSpecific(element);
}
@@ -940,7 +947,7 @@ namespace Barotrauma.Items.Components
}
private float waitTimer;
private float disorderTimer;
private float randomAimTimer;
private float prevTargetRotation;
private float updateTimer;
@@ -950,10 +957,6 @@ namespace Barotrauma.Items.Components
{
IsActive = true;
bool targetCharacters = TargetCharacters || TargetHumans || TargetMonsters;
bool targetHumans = TargetCharacters && TargetHumans;
bool targetMonsters = TargetCharacters && TargetMonsters;
if (friendlyTag.IsEmpty)
{
friendlyTag = FriendlyTag;
@@ -977,7 +980,7 @@ namespace Barotrauma.Items.Components
updateTimer -= deltaTime;
}
if (!IgnoreAimDelay && waitTimer > 0)
if (AimDelay && waitTimer > 0)
{
waitTimer -= deltaTime;
return;
@@ -987,30 +990,34 @@ namespace Barotrauma.Items.Components
float shootDistance = AIRange;
ISpatialEntity target = null;
float closestDist = shootDistance * shootDistance;
if (targetCharacters)
if (TargetCharacters)
{
foreach (var character in Character.CharacterList)
{
if (character == null || character.Removed || character.IsDead) { continue; }
if (!friendlyTag.IsEmpty && (character.SpeciesName.Equals(friendlyTag) || character.Group.Equals(friendlyTag))) { continue; }
bool isHuman = character.IsHuman || character.Group == CharacterPrefab.HumanSpeciesName;
if (isHuman)
{
if (!targetHumans)
{
// Don't target humans if not defined to.
continue;
}
}
else if (!targetMonsters)
{
// 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 (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)
@@ -1020,8 +1027,11 @@ namespace Barotrauma.Items.Components
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;
@@ -1035,6 +1045,7 @@ 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;
}
@@ -1051,22 +1062,23 @@ namespace Barotrauma.Items.Components
return;
}
if (!IgnoreAimDelay)
if (AimDelay)
{
if (RandomAimAmount > 0)
{
if (disorderTimer < 0)
if (randomAimTimer < 0)
{
// Random disorder
disorderTimer = Rand.Range(RandomAimMinTime, RandomAimMaxTime);
// Random disorder or other flaw in the targeting.
randomAimTimer = Rand.Range(RandomAimMinTime, RandomAimMaxTime);
waitTimer = Rand.Range(0.25f, 1f);
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-RandomAimAmount, RandomAimAmount));
float randomAim = MathHelper.ToRadians(RandomAimAmount);
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-randomAim, randomAim));
updatePending = true;
return;
}
else
{
disorderTimer -= deltaTime;
randomAimTimer -= deltaTime;
}
}
}
@@ -1264,18 +1276,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; }
@@ -1292,30 +1305,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; }
@@ -1329,13 +1365,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;
@@ -1345,50 +1382,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;
}
}
}
@@ -1404,13 +1440,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(),
@@ -1423,17 +1459,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(),
@@ -1496,6 +1532,54 @@ namespace Barotrauma.Items.Components
return 0;
}
// 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; }
@@ -1508,7 +1592,7 @@ namespace Barotrauma.Items.Components
{
targetCharacter = limb.character;
}
if (targetCharacter != null)
if (targetCharacter != null && !targetCharacter.Removed)
{
if (user != null)
{
@@ -1517,27 +1601,25 @@ namespace Barotrauma.Items.Components
return false;
}
}
if (!friendlyTag.IsEmpty)
else if (!IsValidTargetForAutoOperate(targetCharacter, friendlyTag))
{
if (targetCharacter.SpeciesName.Equals(friendlyTag) || targetCharacter.Group.Equals(friendlyTag))
{
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;
@@ -1548,7 +1630,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) =>
{