Release 1.8.6.2 - Calm Before the Storm

This commit is contained in:
Markus Isberg
2025-04-10 11:29:43 +00:00
parent c2705bc701
commit 14f61af41c
32 changed files with 505 additions and 199 deletions
@@ -213,7 +213,8 @@ namespace Barotrauma
if (targetHull == null) { return false; }
if (maxDistance > 0)
{
if (Vector2.DistanceSquared(Character.WorldPosition, targetWorldPos) > maxDistance * maxDistance) { return false; }
// Too far from the gap.
if (Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) > maxDistance * maxDistance) { return false; }
}
if (SteeringManager is IndoorsSteeringManager pathSteering)
{
@@ -1128,9 +1128,9 @@ namespace Barotrauma
searchingNewHull = false;
}
}
if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable)
if (patrolTarget != null && pathSteering.CurrentPath is { Finished: false, Unreachable: false })
{
PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n));
pathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: PatrolNodeFilter);
return;
}
}
@@ -1570,22 +1570,22 @@ namespace Barotrauma
Vector2 toTarget = attackWorldPos - attackLimbPos;
Vector2 toTargetOffset = toTarget;
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
if (wallTarget != null && Character.Submarine == null)
if (Character.Submarine == null)
{
if (wallTarget.Structure.Submarine != null)
if (wallTarget != null)
{
Vector2 margin = CalculateMargin(wallTarget.Structure.Submarine.Velocity);
if (wallTarget.Structure.Submarine != null)
{
Vector2 margin = CalculateMargin(wallTarget.Structure.Submarine.Velocity);
toTargetOffset += margin;
}
}
else if (targetCharacter != null)
{
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
toTargetOffset += margin;
}
}
else if (targetCharacter != null)
{
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
toTargetOffset += margin;
}
else if (SelectedAiTarget.Entity is MapEntity e)
{
if (e.Submarine != null)
else if (SelectedAiTarget.Entity is MapEntity { Submarine: not null } e)
{
Vector2 margin = CalculateMargin(e.Submarine.Velocity);
toTargetOffset += margin;
@@ -1666,12 +1666,15 @@ namespace Barotrauma
// Crouch if the target is down (only humanoids), so that we can reach it.
if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2)
{
if (Math.Abs(toTarget.Y) > AttackLimb.attack.Range / 2 && Math.Abs(toTarget.X) <= AttackLimb.attack.Range)
if (targetCharacter?.CurrentHull is Hull targetHull && targetHull == Character.CurrentHull && toTarget.Y < 0)
{
humanoidAnimController.Crouch();
if (Math.Abs(toTarget.Y) > AttackLimb.attack.Range / 2 && Math.Abs(toTarget.X) <= AttackLimb.attack.Range)
{
humanoidAnimController.Crouch();
}
}
}
if (canAttack)
{
if (AttackLimb.attack.Ranged)
@@ -1706,6 +1709,10 @@ namespace Barotrauma
}
}
}
else if (wallTarget == null && Character.CurrentHull != null && targetCharacter != null)
{
canAttack = Submarine.PickBody(SimPosition, attackSimPos, collisionCategory: Physics.CollisionWall) == null;
}
}
}
Limb steeringLimb = canAttack && !AttackLimb.attack.Ranged ? AttackLimb : null;
@@ -1720,6 +1727,8 @@ namespace Barotrauma
State = AIState.Idle;
return;
}
// Note that this returns null when we don't currently use IndoorsSteeringManager, even if we are able to path!
// -> Don't use PathSteering, because that returns the reference even if we currently don't use it.
var pathSteering = SteeringManager as IndoorsSteeringManager;
if (AttackLimb != null && AttackLimb.attack.Retreat)
{
@@ -1727,58 +1736,71 @@ namespace Barotrauma
}
else
{
Vector2 steerPos = attackSimPos;
if (!Character.AnimController.SimplePhysicsEnabled)
{
// Offset so that we don't overshoot the movement
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
steerPos += offset;
}
if (pathSteering != null)
{
if (pathSteering.CurrentPath != null)
// Attack doors
if (canAttackDoors && HasValidPath())
{
// Attack doors
if (canAttackDoors)
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
{
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door is { CanBeTraversed: false } && (!Character.IsInFriendlySub || !door.HasAccess(Character)))
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door is { CanBeTraversed: false } && (!Character.IsInFriendlySub || !door.HasAccess(Character)))
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
SelectTarget(door.Item.AiTarget, currentTargetMemory.Priority);
State = AIState.Attack;
return;
}
SelectTarget(door.Item.AiTarget, currentTargetMemory.Priority);
State = AIState.Attack;
return;
}
}
}
// When pursuing, we don't want to pursue too close
float max = 300;
float margin = AttackLimb != null ? Math.Min(AttackLimb.attack.Range * 0.9f, max) : max;
if (!canAttack || distance > margin)
}
// When pursuing, we don't want to pursue too close
float max = 300;
float margin = AttackLimb != null ? Math.Min(AttackLimb.attack.Range * 0.9f, max) : max;
if ((!canAttack || distance > margin) && !IsTryingToSteerThroughGap)
{
// Steer towards the target if in the same room and swimming
// Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering.
if (Character.CurrentHull != null &&
Character.Submarine != null && !Character.Submarine.Info.IsRuin &&
(Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
// Steer towards the target if in the same room and swimming
// Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering.
if (Character.CurrentHull != null &&
Character.Submarine != null && !Character.Submarine.Info.IsRuin &&
(Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - myPos));
}
else
{
Func<PathNode, bool> nodeFilter = null;
float outsideNodePenalty = 0;
if (Character.CurrentHull != null && Character.IsInPlayerSub)
{
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos));
// Prefer not to path back outside, if we are inside player sub.
// Fixes monsters sometimes pathing from the airlock to another airlock on the other side of the sub, because the path is technically cheaper than the path through the interiors.
outsideNodePenalty = 50;
}
else
{
pathSteering.SteeringSeek(steerPos, weight: 2,
minGapWidth: minGapSize,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null),
checkVisiblity: true);
pathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2,
minGapWidth: minGapSize,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null),
nodeFilter: nodeFilter,
checkVisiblity: true,
outsideNodePenalty: outsideNodePenalty);
if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
if (pathSteering.CurrentPath != null)
{
if (pathSteering.IsPathDirty)
{
if (Character.CurrentHull is Hull hull && hull.ConnectedGaps.Any(static g => !g.IsRoomToRoom && g.Open >= 1.0f && g.ConnectedDoor != null))
{
// Reset in the airlock, because otherwise the character may be too slow to change the steering and keep moving outside.
SteeringManager.Reset();
}
// Steer towards the target
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
}
else if (pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
@@ -1787,39 +1809,42 @@ namespace Barotrauma
}
}
}
else if (!IsTryingToSteerThroughGap)
}
else if (!IsTryingToSteerThroughGap)
{
if (AttackLimb.attack.Ranged)
{
if (AttackLimb.attack.Ranged)
float dir = Character.AnimController.Dir;
if (dir > 0 && attackWorldPos.X > AttackLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackLimb.WorldPosition.X - margin)
{
float dir = Character.AnimController.Dir;
if (dir > 0 && attackWorldPos.X > AttackLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackLimb.WorldPosition.X - margin)
{
SteeringManager.Reset();
}
else
{
// Too close
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
}
SteeringManager.Reset();
}
else
{
// Close enough
SteeringManager.Reset();
// Too close
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
}
}
else
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
// Close enough
SteeringManager.Reset();
}
}
else
{
pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize);
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
}
}
else
{
Vector2 steerPos = attackSimPos;
if (!Character.AnimController.SimplePhysicsEnabled)
{
// Offset so that we don't overshoot the movement
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
steerPos += offset;
}
// Sweeping and circling doesn't work well inside
if (Character.CurrentHull == null)
{
@@ -119,10 +119,13 @@ namespace Barotrauma
steering += base.DoSteeringSeek(targetSimPos, weight);
}
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
/// <summary>
/// </summary>
/// <param name="outsideNodePenalty">Additional cost applied to outside nodes. When the character is inside an extra cost of 100 is also automatically added for outside nodes, unless the character is protected from the pressure. Used for example to prevent monsters from preferring outside nodes when they already are inside.</param>
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true, float outsideNodePenalty = 0)
{
// Have to use a variable here or resetting doesn't work.
Vector2 addition = CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
Vector2 addition = CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity, outsideNodePenalty);
steering += addition;
}
@@ -139,9 +142,9 @@ namespace Barotrauma
return null;
}
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true, float outsideNodePenalty = 0)
{
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.CurrentNode == null;
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.IsAtEndNode || currentPath.CurrentNode == null;
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
{
// If the target has moved, we need a new path.
@@ -182,7 +185,7 @@ namespace Barotrauma
Vector2 currentPos = host.SimPosition;
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility, outsideNodePenalty);
bool useNewPath = needsNewPath;
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
{
@@ -792,7 +795,7 @@ namespace Barotrauma
float? penalty = GetSingleNodePenalty(nextNode);
if (penalty == null) { return null; }
bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y;
if (!character.CanClimb)
if (!character.CanClimb && node.Waypoint.Stairs == null && nextNode.Waypoint.Stairs == null)
{
if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null && (!nextNode.Waypoint.Ladders.Item.IsInteractable(character) || character.LockHands) ||
(nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up
@@ -888,6 +891,7 @@ namespace Barotrauma
return penalty;
}
// TODO: Long and complex. Consider refactoring.
public static float smallRoomSize = 500;
public void Wander(float deltaTime, float wallAvoidDistance = 150, bool stayStillInTightSpace = true)
{
@@ -915,36 +919,92 @@ namespace Barotrauma
}
else
{
float leftDist = character.Position.X - currentHull.Rect.X;
float rightDist = currentHull.Rect.Right - character.Position.X;
if (leftDist < wallAvoidDistance && rightDist < wallAvoidDistance)
bool isVerySmallRoom = roomWidth < smallRoomSize;
Hull nextRoom = null;
if (!stayStillInTightSpace && isVerySmallRoom)
{
if (Math.Abs(rightDist - leftDist) > wallAvoidDistance / 2)
float closestDistance = 0;
// Try to steer to the next room
foreach (Gap gap in currentHull.ConnectedGaps)
{
SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
return;
}
else if (stayStillInTightSpace)
{
Reset();
return;
if (gap.Open < 1.0f) { continue; }
float feetPos = ConvertUnits.ToDisplayUnits(character.AnimController.FloorY);
float gapTop = gap.Rect.Y;
float gapBottom = gap.Rect.Y - gap.Rect.Height;
const float margin = 25;
if (character.Position.Y > gapTop || feetPos < gapBottom - margin)
{
// If the character is above or below the gap, they can't walk through it.
continue;
}
Hull room = null;
foreach (var entity in gap.linkedTo)
{
if (entity is not Hull hull) { continue; }
if (hull.Submarine != character.Submarine) { continue; }
if (hull.Rect.Width < smallRoomSize) { continue; }
if (hull != currentHull)
{
room = hull;
break;
}
}
if (room == null) { continue; }
Vector2 toGap = gap.Position - character.Position;
float distance = Math.Abs(toGap.X);
if (nextRoom == null || distance < closestDistance)
{
closestDistance = distance;
nextRoom = room;
}
}
}
if (leftDist < wallAvoidDistance)
if (nextRoom != null)
{
float speed = (wallAvoidDistance - leftDist) / wallAvoidDistance;
SteeringManual(deltaTime, Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
float toNextRoom = nextRoom.Position.X - character.Position.X;
SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(toNextRoom));
WanderAngle = 0.0f;
}
else if (rightDist < wallAvoidDistance)
{
float speed = (wallAvoidDistance - rightDist) / wallAvoidDistance;
SteeringManual(deltaTime, -Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
WanderAngle = MathHelper.Pi;
}
else
{
wander = true;
if (!stayStillInTightSpace && isVerySmallRoom)
{
// Reset regardless, because moving inside the room would look glitchy.
Reset();
return;
}
float leftDist = character.Position.X - currentHull.Rect.X;
float rightDist = currentHull.Rect.Right - character.Position.X;
if (leftDist < wallAvoidDistance && rightDist < wallAvoidDistance)
{
float diff = rightDist - leftDist;
if (Math.Abs(diff) > wallAvoidDistance / 2)
{
SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(diff));
return;
}
else if (stayStillInTightSpace)
{
Reset();
return;
}
}
if (leftDist < wallAvoidDistance)
{
float speed = (wallAvoidDistance - leftDist) / wallAvoidDistance;
SteeringManual(deltaTime, Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
WanderAngle = 0.0f;
}
else if (rightDist < wallAvoidDistance)
{
float speed = (wallAvoidDistance - rightDist) / wallAvoidDistance;
SteeringManual(deltaTime, -Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
WanderAngle = MathHelper.Pi;
}
else
{
wander = true;
}
}
}
}
@@ -170,7 +170,7 @@ namespace Barotrauma
private readonly List<PathNode> sortedNodes;
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true, float outsideNodePenalty = 0)
{
foreach (PathNode node in nodes)
{
@@ -325,7 +325,12 @@ namespace Barotrauma
#endif
return new SteeringPath(true);
}
return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
float outsideNodeCostPenalty = outsideNodePenalty;
if (ApplyPenaltyToOutsideNodes)
{
outsideNodeCostPenalty += 100;
}
return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize, outsideNodeCostPenalty);
bool IsValidStartNode(PathNode node) => IsValidNode(node, (isCharacter, start), startNodeFilter);
@@ -356,7 +361,7 @@ namespace Barotrauma
}
}
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0)
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0f, float outsideNodePenalty = 0f)
{
if (start == end)
{
@@ -398,50 +403,65 @@ namespace Barotrauma
for (int i = 0; i < currNode.connections.Count; i++)
{
PathNode nextNode = currNode.connections[i];
//a node that hasn't been searched yet
if (nextNode.state == 0)
{
nextNode.H = Vector2.Distance(nextNode.Position, end.Position);
float penalty = 0.0f;
if (GetNodePenalty != null)
switch (nextNode.state)
{
//a node that hasn't been searched yet
case 0:
{
float? nodePenalty = GetNodePenalty(currNode, nextNode);
if (nodePenalty == null)
nextNode.H = Vector2.Distance(nextNode.Position, end.Position);
float cost = CalculateNodeCost();
if (cost < float.PositiveInfinity)
{
nextNode.state = -1;
continue;
nextNode.G = cost;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
}
penalty = nodePenalty.Value;
else
{
// Set searched and invalid.
nextNode.state = -1;
}
break;
}
//node that has been searched
case 1 or -1:
{
float tempG = CalculateNodeCost();
//only use if this new route is better than the
//route the node was a part of
if (tempG < nextNode.G)
{
nextNode.G = tempG;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
}
break;
}
nextNode.G = currNode.G + currNode.distances[i] + penalty;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
}
//node that has been searched
else if (nextNode.state == 1 || nextNode.state == -1)
float CalculateNodeCost()
{
float tempG = currNode.G + currNode.distances[i];
float penalty = 0f;
if (GetNodePenalty != null)
{
float? nodePenalty = GetNodePenalty(currNode, nextNode);
if (nodePenalty == null) { continue; }
tempG += nodePenalty.Value;
if (nodePenalty.HasValue)
{
penalty += nodePenalty.Value;
}
else
{
return float.PositiveInfinity;
}
}
//only use if this new route is better than the
//route the node was a part of
if (tempG < nextNode.G)
if (currNode.Waypoint.CurrentHull == null)
{
nextNode.G = tempG;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
penalty += outsideNodePenalty;
}
return currNode.G + currNode.distances[i] + penalty;
}
}
}
@@ -2926,13 +2926,13 @@ namespace Barotrauma
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
{
newCharacter.HumanPrefab = humanPrefab;
AddToCrew(newCharacter);
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPoint);
humanPrefab.InitializeCharacter(newCharacter);
#if SERVER
newCharacter.LoadTalents();
GameMain.NetworkMember.CreateEntityEvent(newCharacter, new Character.UpdateTalentsEventData());
#endif
PostSpawnHuman(newCharacter);
});
}
}
@@ -2942,10 +2942,10 @@ namespace Barotrauma
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant);
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, characterInfo, onSpawn: newCharacter =>
{
AddToCrew(newCharacter);
newCharacter.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, spawnPoint);
newCharacter.GiveIdCardTags(spawnPoint);
newCharacter.Info.StartItemsGiven = true;
PostSpawnHuman(newCharacter);
});
}
else if (CharacterPrefab.FindBySpeciesName(args[0].ToIdentifier()) is { } prefab)
@@ -2953,7 +2953,7 @@ namespace Barotrauma
Entity.Spawner.AddCharacterToSpawnQueue(args[0].ToIdentifier(), spawnPosition, prefab.HasCharacterInfo ? new CharacterInfo(prefab.Identifier) : null);
}
void PostSpawnHuman(Character newCharacter)
void AddToCrew(Character newCharacter)
{
newCharacter.TeamID = teamType;
if (addToCrew)
@@ -218,11 +218,11 @@ namespace Barotrauma
//the first character in the CustomInteract callback is always the NPC and the 2nd the character who interacted with it
//but the TriggerAction can configure the 1st and 2nd entity in either order,
//let's make sure we pass the NPC and the interactor in the intended order
if (e1 == npc && targets2.Contains(interactor))
if (e1 == npc && ParentEvent.GetTargets(Target2Tag).Contains(interactor))
{
Trigger(npc, interactor);
}
else if (targets1.Contains(interactor) && e2 == npc)
else if (ParentEvent.GetTargets(Target1Tag).Contains(interactor) && e2 == npc)
{
Trigger(interactor, npc);
}
@@ -250,8 +250,7 @@ namespace Barotrauma
}
// Overrides the team change set in the base method.
var teamId = element.GetAttributeEnum("teamid", requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None);
var originalTeam = Level.Loaded.StartOutpost?.TeamID ?? teamId;
if (teamId != originalTeam)
if (teamId != spawnedCharacter.TeamID)
{
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId);
}
@@ -356,7 +356,7 @@ namespace Barotrauma
//that allows the NPC to fight intruders and otherwise function in the outpost if the mission is configured to spawn the hostile NPCs in a friendly outpost
if (teamId != originalTeam)
{
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId);
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId, processImmediately: true);
}
if (element.GetAttribute("color") != null)
{
@@ -542,6 +542,7 @@ namespace Barotrauma
{
currentConfig = Config.FromElement(currentConfigDoc.Root ?? throw new NullReferenceException("Config XML element is invalid: document is null."));
#if CLIENT
MainMenuScreen.DismissedNotifications = currentConfigDoc.Root.GetAttributeIdentifierArray(nameof(MainMenuScreen.DismissedNotifications), defaultValue: Array.Empty<Identifier>()).ToHashSet();
ServerListFilters.Init(currentConfigDoc.Root.GetChildElement("serverfilters"));
MultiplayerPreferences.Init(
currentConfigDoc.Root.GetChildElement("player"),
@@ -660,6 +661,8 @@ namespace Barotrauma
}
#if CLIENT
root.Add(new XAttribute(nameof(MainMenuScreen.DismissedNotifications), string.Join(',', MainMenuScreen.DismissedNotifications.Select(n => n.Value))));
XElement serverFiltersElement = new XElement("serverfilters"); root.Add(serverFiltersElement);
ServerListFilters.Instance.SaveTo(serverFiltersElement);