(c1468d39d) Overhauled docking interface (WIP)
This commit is contained in:
@@ -320,9 +320,8 @@ namespace Barotrauma
|
||||
{
|
||||
State = AIState.Idle;
|
||||
}
|
||||
else if (Character.Health < fleeHealthThreshold && SwarmBehavior == null)
|
||||
else if (Character.Health < fleeHealthThreshold)
|
||||
{
|
||||
// Don't flee from damage if in a swarm.
|
||||
State = AIState.Escape;
|
||||
}
|
||||
else if (targetingPriority != null)
|
||||
@@ -331,6 +330,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
|
||||
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
|
||||
{
|
||||
State = AIState.Idle;
|
||||
@@ -368,15 +369,8 @@ namespace Barotrauma
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
IsSteeringThroughGap = false;
|
||||
if (SwarmBehavior != null)
|
||||
{
|
||||
SwarmBehavior.IsActive = State == AIState.Idle && Character.CurrentHull == null;
|
||||
SwarmBehavior.Refresh();
|
||||
SwarmBehavior.UpdateSteering(deltaTime);
|
||||
}
|
||||
|
||||
SwarmBehavior?.Update(deltaTime);
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run));
|
||||
}
|
||||
|
||||
@@ -499,7 +493,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsLatchedOnSub)
|
||||
if (!IsProperlyLatchedOnSub)
|
||||
{
|
||||
UpdateWallTarget();
|
||||
}
|
||||
@@ -558,7 +552,7 @@ namespace Barotrauma
|
||||
{
|
||||
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
|
||||
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
|
||||
if (section?.gap != null && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
|
||||
if (section?.gap != null && section.gap.IsRoomToRoom && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -799,21 +793,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSteeringThroughGap { get; private set; }
|
||||
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
|
||||
{
|
||||
IsSteeringThroughGap = true;
|
||||
SelectedAiTarget = wall.AiTarget;
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
Hull targetHull = section.gap?.FlowTargetHull;
|
||||
float distance = Vector2.Distance(Character.WorldPosition, targetWorldPos);
|
||||
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
|
||||
if (distance > maxDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
if (wall.IsHorizontal)
|
||||
@@ -824,7 +806,16 @@ namespace Barotrauma
|
||||
{
|
||||
targetWorldPos.X = targetHull.WorldRect.Center.X;
|
||||
}
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
|
||||
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;
|
||||
@@ -1056,43 +1047,19 @@ namespace Barotrauma
|
||||
#region Targeting
|
||||
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
|
||||
|
||||
private bool IsProperlyLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub && SelectedAiTarget?.Entity == wallTarget?.Structure;
|
||||
|
||||
//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 AITarget UpdateTargets(Character character, out TargetingPriority priority)
|
||||
{
|
||||
if ((SelectedAiTarget != null || wallTarget != null) && IsLatchedOnSub)
|
||||
if (IsProperlyLatchedOnSub)
|
||||
{
|
||||
var wall = SelectedAiTarget.Entity as Structure;
|
||||
if (wall == null)
|
||||
{
|
||||
wall = wallTarget?.Structure;
|
||||
}
|
||||
// The target is not a wall or it's not the same as we are attached to -> release
|
||||
bool releaseTarget = wall == null || !wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB);
|
||||
if (!releaseTarget)
|
||||
{
|
||||
for (int i = 0; i < wall.Sections.Length; i++)
|
||||
{
|
||||
if (CanPassThroughHole(wall, i))
|
||||
{
|
||||
releaseTarget = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (releaseTarget)
|
||||
{
|
||||
SelectedAiTarget = null;
|
||||
wallTarget = null;
|
||||
LatchOntoAI.DeattachFromBody();
|
||||
}
|
||||
else if (SelectedAiTarget?.Entity == wallTarget?.Structure)
|
||||
{
|
||||
// If attached to a valid target, just keep the target.
|
||||
// Priority not used in this case.
|
||||
priority = null;
|
||||
return SelectedAiTarget;
|
||||
}
|
||||
// If attached to a valid target, just keep the target.
|
||||
// Priority not used in this case.
|
||||
priority = null;
|
||||
return SelectedAiTarget;
|
||||
}
|
||||
AITarget newTarget = null;
|
||||
priority = null;
|
||||
@@ -1175,7 +1142,7 @@ namespace Barotrauma
|
||||
else if (target.Entity != null)
|
||||
{
|
||||
//skip the target if it's a room and the character is already inside a sub
|
||||
if (character.CurrentHull != null && target.Entity is Hull) { continue; }
|
||||
if (character.CurrentHull != null && target.Entity is Hull) continue;
|
||||
|
||||
Door door = null;
|
||||
if (target.Entity is Item item)
|
||||
@@ -1204,37 +1171,23 @@ namespace Barotrauma
|
||||
// Ignore structures that doesn't have a body (not walls)
|
||||
continue;
|
||||
}
|
||||
if (s.IsPlatform)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
// Ignore walls when inside.
|
||||
continue;
|
||||
}
|
||||
valueModifier = 1;
|
||||
float wallMaxHealth = 400; // Anything more than this is ignored -> 200 = 1
|
||||
// Prefer weaker targets.
|
||||
valueModifier *= MathHelper.Lerp(1.5f, 0.5f, MathUtils.InverseLerp(0, 1, s.Health / wallMaxHealth));
|
||||
// Ignore walls when inside.
|
||||
valueModifier = character.CurrentHull == null ? 1 : 0;
|
||||
if (aggressiveBoarding)
|
||||
{
|
||||
var hulls = s.Submarine.GetHulls(false);
|
||||
for (int i = 0; i < s.Sections.Length; i++)
|
||||
{
|
||||
var section = s.Sections[i];
|
||||
if (section.gap != null)
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null && hulls.Any(h => h.Rect.Intersects(section.rect));
|
||||
valueModifier *= leadsInside ? 5 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// up to 100% priority increase for every gap in the wall
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,10 +223,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
|
||||
//only humanoids can climb ladders
|
||||
if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
|
||||
if (character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
|
||||
{
|
||||
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
|
||||
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
|
||||
@@ -236,7 +234,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var collider = character.AnimController.Collider;
|
||||
if (character.IsClimbing && !isDiving)
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
|
||||
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
|
||||
@@ -280,12 +278,6 @@ namespace Barotrauma
|
||||
}
|
||||
else if (character.AnimController.InWater)
|
||||
{
|
||||
// If the character is underwater, we don't need the ladders anymore
|
||||
if (character.IsClimbing && isDiving)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Barotrauma
|
||||
get { return attachJoints.Count > 0; }
|
||||
}
|
||||
|
||||
public bool IsAttachedToSub => IsAttached && (attachTargetBody?.UserData is Submarine || attachTargetBody?.UserData is Entity entity && entity.Submarine != null);
|
||||
public bool IsAttachedToSub => IsAttached && attachTargetBody?.UserData is Entity entity && (entity is Submarine sub || entity?.Submarine != null);
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
@@ -190,7 +190,7 @@ namespace Barotrauma
|
||||
case AIController.AIState.Attack:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
if (attachToSub && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
{
|
||||
// is not attached or is attached to something else
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
|
||||
|
||||
@@ -14,16 +14,11 @@ namespace Barotrauma
|
||||
private float maxDistFromCenter;
|
||||
private float cohesion;
|
||||
|
||||
public List<AICharacter> Members { get; private set; } = new List<AICharacter>();
|
||||
public HashSet<AICharacter> ActiveMembers { get; private set; } = new HashSet<AICharacter>();
|
||||
private List<AICharacter> members = new List<AICharacter>();
|
||||
|
||||
private EnemyAIController ai;
|
||||
private AIController ai;
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsEnoughMembers => ActiveMembers.Count > 1;
|
||||
|
||||
|
||||
public SwarmBehavior(XElement element, EnemyAIController ai)
|
||||
public SwarmBehavior(XElement element, AIController ai)
|
||||
{
|
||||
this.ai = ai;
|
||||
minDistFromClosest = ConvertUnits.ToSimUnits(element.GetAttributeFloat("mindistfromclosest", 10.0f));
|
||||
@@ -37,36 +32,21 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.AIController is EnemyAIController enemyAI && enemyAI.SwarmBehavior != null)
|
||||
{
|
||||
enemyAI.SwarmBehavior.Members = swarm.ToList();
|
||||
enemyAI.SwarmBehavior.members = swarm.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
Members.RemoveAll(m => m.IsDead || m.Removed);
|
||||
foreach (var member in Members)
|
||||
{
|
||||
if (!member.AIController.Enabled && member.IsRemotePlayer || Character.Controlled == member || !((EnemyAIController)member.AIController).SwarmBehavior.IsActive)
|
||||
{
|
||||
ActiveMembers.Remove(member);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveMembers.Add(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
members.RemoveAll(m => m.IsDead || m.Removed);
|
||||
if (members.Count < 2) { return; }
|
||||
|
||||
public void UpdateSteering(float deltaTime)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
if (!IsEnoughMembers) { return; }
|
||||
//calculate the "center of mass" of the swarm and the distance to the closest character in the swarm
|
||||
float closestDistSqr = float.MaxValue;
|
||||
Vector2 center = Vector2.Zero;
|
||||
AICharacter closest = null;
|
||||
foreach (AICharacter member in Members)
|
||||
foreach (AICharacter member in members)
|
||||
{
|
||||
center += member.SimPosition;
|
||||
if (member == ai.Character) { continue; }
|
||||
@@ -77,7 +57,7 @@ namespace Barotrauma
|
||||
closest = member;
|
||||
}
|
||||
}
|
||||
center /= Members.Count;
|
||||
center /= members.Count;
|
||||
|
||||
if (closest == null) { return; }
|
||||
|
||||
@@ -103,11 +83,11 @@ namespace Barotrauma
|
||||
if (cohesion > 0.0f)
|
||||
{
|
||||
Vector2 avgVel = Vector2.Zero;
|
||||
foreach (AICharacter member in Members)
|
||||
foreach (AICharacter member in members)
|
||||
{
|
||||
avgVel += member.AnimController.TargetMovement;
|
||||
}
|
||||
avgVel /= Members.Count;
|
||||
avgVel /= members.Count;
|
||||
ai.SteeringManager.SteeringManual(deltaTime, avgVel * cohesion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,27 +293,6 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//stop dragging if there's something between the pull limb and the target
|
||||
Vector2 sourceSimPos = mouthLimb.SimPosition;
|
||||
Vector2 targetSimPos = target.SimPosition;
|
||||
if (character.Submarine != null && character.SelectedCharacter.Submarine == null)
|
||||
{
|
||||
targetSimPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
|
||||
{
|
||||
sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition;
|
||||
}
|
||||
var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
|
||||
if (body != null)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Character targetCharacter = target;
|
||||
float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f;
|
||||
eatTimer += deltaTime * eatSpeed;
|
||||
@@ -751,6 +730,22 @@ namespace Barotrauma
|
||||
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
|
||||
}
|
||||
|
||||
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
|
||||
{
|
||||
//make sure the angle "has the same number of revolutions" as the reference limb
|
||||
//(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
|
||||
while (referenceLimb.Rotation - angle > MathHelper.TwoPi)
|
||||
{
|
||||
angle += MathHelper.TwoPi;
|
||||
}
|
||||
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
|
||||
{
|
||||
angle -= MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
|
||||
}
|
||||
|
||||
public override void Flip()
|
||||
{
|
||||
base.Flip();
|
||||
|
||||
@@ -560,8 +560,7 @@ namespace Barotrauma
|
||||
//TODO: take into account that the feet aren't necessarily in CurrentHull
|
||||
//full slowdown (1.5f) when water is up to the torso
|
||||
surfaceY = ConvertUnits.ToSimUnits(currentHull.Surface);
|
||||
float bottomPos = Math.Max(colliderPos.Y, currentHull.Rect.Y - currentHull.Rect.Height);
|
||||
slowdownAmount = MathHelper.Clamp((surfaceY - bottomPos) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
|
||||
slowdownAmount = MathHelper.Clamp((surfaceY - colliderPos.Y) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
|
||||
}
|
||||
|
||||
float maxSpeed = Math.Max(TargetMovement.Length() - slowdownAmount, 1.0f);
|
||||
|
||||
@@ -426,7 +426,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanSpeak;
|
||||
private bool canSpeak;
|
||||
|
||||
private bool speechImpedimentSet;
|
||||
|
||||
@@ -436,7 +436,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
|
||||
if (!canSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
|
||||
return speechImpediment;
|
||||
}
|
||||
set
|
||||
@@ -710,7 +710,7 @@ namespace Barotrauma
|
||||
displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true);
|
||||
|
||||
IsHumanoid = doc.Root.GetAttributeBool("humanoid", false);
|
||||
CanSpeak = doc.Root.GetAttributeBool("canspeak", false);
|
||||
canSpeak = doc.Root.GetAttributeBool("canspeak", false);
|
||||
needsAir = doc.Root.GetAttributeBool("needsair", false);
|
||||
Noise = doc.Root.GetAttributeFloat("noise", 100f);
|
||||
|
||||
@@ -853,7 +853,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (characterConfigFiles == null)
|
||||
{
|
||||
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character, searchAllContentPackages: true);
|
||||
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character);
|
||||
}
|
||||
return characterConfigFiles;
|
||||
}
|
||||
@@ -2555,9 +2555,11 @@ namespace Barotrauma
|
||||
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
|
||||
#endif
|
||||
|
||||
CharacterList.Remove(this);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
|
||||
#endif
|
||||
|
||||
if (Controlled == this) { Controlled = null; }
|
||||
CharacterList.Remove(this);
|
||||
|
||||
if (Inventory != null)
|
||||
{
|
||||
|
||||
@@ -399,7 +399,7 @@ namespace Barotrauma
|
||||
{
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
Name = element.GetAttributeString("name", "");
|
||||
Name = element.GetAttributeString("name", "unnamed");
|
||||
string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
File = element.GetAttributeString("file", "");
|
||||
SourceElement = GetConfig(File).Root;
|
||||
@@ -423,29 +423,6 @@ namespace Barotrauma
|
||||
element.GetAttributeInt("beardindex", -1),
|
||||
element.GetAttributeInt("moustacheindex", -1),
|
||||
element.GetAttributeInt("faceattachmentindex", -1));
|
||||
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
{
|
||||
if (SourceElement.Element("name") != null)
|
||||
{
|
||||
string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", "");
|
||||
if (firstNamePath != "")
|
||||
{
|
||||
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
Name = ToolBox.GetRandomLine(firstNamePath);
|
||||
}
|
||||
|
||||
string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", "");
|
||||
if (lastNamePath != "")
|
||||
{
|
||||
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
if (Name != "") Name += " ";
|
||||
Name += ToolBox.GetRandomLine(lastNamePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StartItemsGiven = element.GetAttributeBool("startitemsgiven", false);
|
||||
string personalityName = element.GetAttributeString("personality", "");
|
||||
ragdollFileName = element.GetAttributeString("ragdoll", string.Empty);
|
||||
|
||||
Reference in New Issue
Block a user