v0.19.0.0 (unstable)

This commit is contained in:
Regalis11
2022-07-20 18:47:07 +03:00
parent 2e2663a175
commit 6b55adcdd9
170 changed files with 2769 additions and 1634 deletions
@@ -511,9 +511,9 @@ namespace Barotrauma
{
newDir = Direction.Left;
}
if (Character.SelectedConstruction != null)
if (Character.SelectedItem != null)
{
Character.SelectedConstruction.SecondaryUse(deltaTime, Character);
Character.SelectedItem.SecondaryUse(deltaTime, Character);
}
}
else if (AutoFaceMovement && Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater)
@@ -2148,7 +2148,7 @@ namespace Barotrauma
if (c.Removed) { continue; }
if (c.TeamID != team) { continue; }
if (c.IsIncapacitated) { continue; }
if (c.SelectedConstruction == target.Item)
if (c.SelectedItem == target.Item)
{
operatingCharacter = c;
return true;
@@ -2185,7 +2185,7 @@ namespace Barotrauma
if (c.IsIncapacitated) { continue; }
if (c.IsPlayer)
{
if (c.SelectedConstruction == target.Item)
if (c.SelectedItem == target.Item)
{
// If the other character is player, don't try to operate
other = c;
@@ -79,7 +79,8 @@ namespace Barotrauma
{
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true)
{
GetNodePenalty = GetNodePenalty
GetNodePenalty = GetNodePenalty,
GetSingleNodePenalty = GetSingleNodePenalty
};
this.canOpenDoors = canOpenDoors;
@@ -360,7 +361,7 @@ namespace Barotrauma
Ladder nextLadder = GetNextLadder();
var ladders = currentLadder ?? nextLadder;
bool useLadders = canClimb && ladders != null && steering.LengthSquared() > 0.1f && (!isDiving || steering.Y > 1);
if (useLadders && character.SelectedConstruction != ladders.Item)
if (useLadders && character.SelectedSecondaryItem != ladders.Item)
{
if (character.CanInteractWith(ladders.Item))
{
@@ -372,7 +373,7 @@ namespace Barotrauma
// Try to select the previous ladder, unless it's already selected, unless the previous ladder is not adjacent to the current ladder.
// The intention of this code is to prevent the bots from dropping from the "double ladders".
var previousLadders = currentPath.PrevNode?.Ladders;
if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item &&
if (previousLadders != null && previousLadders != ladders && character.SelectedSecondaryItem != previousLadders.Item &&
character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5)
{
previousLadders.Item.TryInteract(character, forceSelectKey: true);
@@ -382,8 +383,7 @@ namespace Barotrauma
var collider = character.AnimController.Collider;
if (character.IsClimbing && !useLadders)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
character.StopClimbing();
}
if (character.IsClimbing && useLadders)
{
@@ -402,15 +402,14 @@ namespace Barotrauma
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
bool isAboveFloor = heightFromFloor > -0.1f;
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
if (isAboveFloor && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
character.StopClimbing();
}
else if (nextLadder != null && !nextLadderSameAsCurrent)
{
// Try to change the ladder (hatches between two submarines)
if (character.SelectedConstruction != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
{
if (nextLadder.Item.TryInteract(character, forceSelectKey: true))
{
@@ -418,7 +417,7 @@ namespace Barotrauma
}
}
}
if (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10)
if (!currentPath.IsAtEndNode && (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10))
{
NextNode(!doorsChecked);
}
@@ -756,42 +755,8 @@ namespace Barotrauma
private float? GetNodePenalty(PathNode node, PathNode nextNode)
{
if (character == null) { return 0.0f; }
if (nextNode.Waypoint.isObstructed) { return null; }
float penalty = 0.0f;
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
{
var door = nextNode.Waypoint.ConnectedDoor;
if (door == null)
{
penalty = 100.0f;
}
else
{
if (!CanAccessDoor(door, button =>
{
// Ignore buttons that are on the wrong side of the door
if (door.IsHorizontal)
{
if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y))
{
return false;
}
}
else
{
if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X))
{
return false;
}
}
return true;
}))
{
return null;
}
}
}
float? penalty = GetSingleNodePenalty(nextNode);
if (penalty == null) { return null; }
bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y;
//non-humanoids can't climb up ladders
if (!(character.AnimController is HumanoidAnimController))
@@ -839,6 +804,47 @@ namespace Barotrauma
return penalty;
}
private float? GetSingleNodePenalty(PathNode node)
{
if (node.Waypoint.isObstructed) { return null; }
if (node.IsBlocked()) { return null; }
float penalty = 0.0f;
if (node.Waypoint.ConnectedGap != null && node.Waypoint.ConnectedGap.Open < 0.9f)
{
var door = node.Waypoint.ConnectedDoor;
if (door == null)
{
penalty = 100.0f;
}
else
{
if (!CanAccessDoor(door, button =>
{
// Ignore buttons that are on the wrong side of the door
if (door.IsHorizontal)
{
if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y))
{
return false;
}
}
else
{
if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X))
{
return false;
}
}
return true;
}))
{
return null;
}
}
}
return penalty;
}
public static float smallRoomSize = 500;
public void Wander(float deltaTime, float wallAvoidDistance = 150, bool stayStillInTightSpace = true)
{
@@ -14,13 +14,11 @@ namespace Barotrauma
public readonly LanguageIdentifier Language;
public readonly List<NPCConversation> Conversations;
public readonly Dictionary<Identifier, NPCPersonalityTrait> PersonalityTraits;
public NPCConversationCollection(NPCConversationsFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Language = element.GetAttributeIdentifier("language", "English").ToLanguageIdentifier();
Conversations = new List<NPCConversation>();
PersonalityTraits = new Dictionary<Identifier, NPCPersonalityTrait>();
foreach (var subElement in element.Elements())
{
Identifier elemName = new Identifier(subElement.Name.LocalName);
@@ -28,11 +26,6 @@ namespace Barotrauma
{
Conversations.Add(new NPCConversation(subElement));
}
else if (elemName == "PersonalityTrait")
{
var personalityTrait = new NPCPersonalityTrait(subElement);
PersonalityTraits.Add(personalityTrait.Name, personalityTrait);
}
}
}
@@ -361,10 +354,13 @@ namespace Barotrauma
private static float GetConversationProbability(NPCConversation conversation)
{
int index = previousConversations.IndexOf(conversation);
if (index < 0) return 10.0f;
//prefer choosing conversations with more flags (= for more specific situations) when possible
float baseProbability = MathF.Pow(conversation.Flags.Count + 1, 2);
return 1.0f - 1.0f / (index + 1);
int index = previousConversations.IndexOf(conversation);
if (index < 0) { return baseProbability * 10.0f; }
return baseProbability + 1.0f - 1.0f / (index + 1);
}
#if DEBUG
@@ -512,6 +512,23 @@ namespace Barotrauma
foreach (var weapon in weaponList)
{
float priority = weapon.CombatPriority;
if (weapon is RepairTool repairTool)
{
switch (repairTool.UsableIn)
{
case RepairTool.UseEnvironment.Air:
if (character.InWater) { continue; }
break;
case RepairTool.UseEnvironment.Water:
if (!character.InWater) { continue; }
break;
case RepairTool.UseEnvironment.None:
continue;
case RepairTool.UseEnvironment.Both:
default:
break;
}
}
if (prioritizeMelee)
{
if (weapon is MeleeWeapon)
@@ -196,10 +196,7 @@ namespace Barotrauma
character.AIController.SteeringManager.Reset();
return;
}
if (!character.IsClimbing)
{
character.SelectedConstruction = null;
}
character.SelectedItem = null;
if (Target is Entity e)
{
if (e.Removed)
@@ -647,7 +644,7 @@ namespace Barotrauma
{
if (character.IsClimbing)
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder && !PathSteering.CurrentPath.IsAtEndNode)
{
if (Target.WorldPosition.Y > character.WorldPosition.Y)
{
@@ -694,7 +691,7 @@ namespace Barotrauma
{
if (Target is Item item)
{
if (!character.IsClimbing && character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
if (character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
}
else if (Target is Character targetCharacter)
{
@@ -161,10 +161,7 @@ namespace Barotrauma
character.DeselectCharacter();
}
if (!character.IsClimbing)
{
character.SelectedConstruction = null;
}
character.SelectedItem = null;
CleanupItems(deltaTime);
@@ -310,7 +307,7 @@ namespace Barotrauma
if (character.AnimController.GetHeightFromFloor() < 0.1f)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
character.SelectedSecondaryItem = null;
}
return;
}
@@ -375,7 +372,7 @@ namespace Barotrauma
}
chairCheckTimer -= deltaTime;
if (chairCheckTimer <= 0.0f && character.SelectedConstruction == null)
if (chairCheckTimer <= 0.0f && character.SelectedSecondaryItem == null)
{
foreach (Item item in Item.ItemList)
{
@@ -205,7 +205,7 @@ namespace Barotrauma
if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false))
{
HumanAIController.FaceTarget(target.Item);
if (character.SelectedConstruction != target.Item)
if (character.SelectedItem != target.Item)
{
target.Item.TryInteract(character, forceSelectKey: true);
}
@@ -26,7 +26,7 @@ namespace Barotrauma
private bool IsRepairing() => IsRepairing(character, Item);
private readonly bool isPriority;
public static bool IsRepairing(Character character, Item item) => character.SelectedConstruction == item && item.Repairables.Any(r => r.CurrentFixer == character);
public static bool IsRepairing(Character character, Item item) => character.SelectedItem == item && item.Repairables.Any(r => r.CurrentFixer == character);
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false)
: base(character, objectiveManager, priorityModifier)
@@ -165,7 +165,7 @@ namespace Barotrauma
return;
}
}
if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false))
if (character.CanInteractWith(Item, out _, checkLinked: false))
{
waitTimer += deltaTime;
if (waitTimer < WaitTimeBeforeRepair) { return; }
@@ -184,12 +184,12 @@ namespace Barotrauma
}
if (!Abandon)
{
if (character.SelectedConstruction != Item)
if (character.SelectedItem != Item)
{
if (Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) ||
Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true))
{
character.SelectedConstruction = Item;
character.SelectedItem = Item;
}
else
{
@@ -232,8 +232,6 @@ namespace Barotrauma
previousCondition = -1;
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
{
// Don't stop in ladders, because we can't interact with other items while holding the ladders.
endNodeFilter = node => node.Waypoint.Ladders == null,
TargetName = Item.Name
};
if (repairTool != null)
@@ -67,7 +67,7 @@ namespace Barotrauma
if (!ViableForRepair(item, character, HumanAIController)) { return false; };
if (!Objectives.ContainsKey(item))
{
if (item != character.SelectedConstruction)
if (item != character.SelectedItem)
{
if (NearlyFullCondition(item)) { return false; }
}
@@ -96,7 +96,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
var selectedItem = character.SelectedConstruction;
var selectedItem = character.SelectedItem;
if (selectedItem != null && AIObjectiveRepairItem.IsRepairing(character, selectedItem) && selectedItem.ConditionPercentage < 100)
{
// Don't stop fixing until completely done
@@ -109,6 +109,8 @@ namespace Barotrauma
{
public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode);
public GetNodePenaltyHandler GetNodePenalty;
public delegate float? GetSingleNodePenaltyHandler(PathNode node);
public GetSingleNodePenaltyHandler GetSingleNodePenalty;
private readonly List<PathNode> nodes;
private readonly bool isCharacter;
@@ -282,8 +284,6 @@ namespace Barotrauma
}
//avoid stopping at a doorway
if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; }
//avoid stopping at a ladder
if (node.Waypoint.Ladders != null) { node.TempDistance *= 10.0f; }
}
//optimization: node extremely far (> 100m / 800 m) from the end position, don't try to use it as an end node
if (node.TempDistance > (InsideSubmarine ? 100.0f * 100.0f : 800.0f * 800.0f))
@@ -325,15 +325,24 @@ namespace Barotrauma
#endif
return new SteeringPath(true);
}
var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
return path;
return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true)
bool IsValidStartNode(PathNode node) => IsValidNode(node, (isCharacter, start), startNodeFilter);
bool IsValidEndNode(PathNode node) => IsValidNode(node, (isCharacter && checkVisibility, end), endNodeFilter);
bool IsValidNode(PathNode node, (bool check, Vector2 start) visibilityCheck, Func<PathNode, bool> extraFilter)
{
//if searching for a path inside the sub, make sure the waypoint is visible
if (checkVisibility && isCharacter)
if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (extraFilter != null && !extraFilter(node)) { return false; }
if (GetSingleNodePenalty != null && GetSingleNodePenalty(node) == null) { return false; }
if (node.Waypoint.ConnectedGap != null)
{
var body = Submarine.PickBody(rayStart, node.TempPosition,
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; }
}
if (visibilityCheck.check)
{
var body = Submarine.PickBody(visibilityCheck.start, node.TempPosition,
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
if (body != null)
{
@@ -344,36 +353,6 @@ namespace Barotrauma
}
return true;
}
bool IsValidStartNode(PathNode node)
{
if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (startNodeFilter != null && !startNodeFilter(node)) { return false; }
if (node.Waypoint.isObstructed) { return false; }
// Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { return false; }
if (node.IsBlocked()) { return false; }
if (node.Waypoint.ConnectedGap != null)
{
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; }
}
return true;
}
bool IsValidEndNode(PathNode node)
{
if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (endNodeFilter != null && !endNodeFilter(node)) { return false; }
if (node.Waypoint.isObstructed) { return false; }
// Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; }
if (node.IsBlocked()) { return false; }
if (node.Waypoint.ConnectedGap != null)
{
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; }
}
return true;
}
}
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0)
@@ -402,15 +381,13 @@ namespace Barotrauma
foreach (PathNode node in nodes)
{
if (node.state != 1 || node.F > dist) { continue; }
if (isCharacter && node.Waypoint.isObstructed) { continue; }
if (filter != null && !filter(node)) { continue; }
if (node.IsBlocked()) { continue; }
if (node.Waypoint.ConnectedGap != null)
{
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; }
}
}
dist = node.F;
currNode = node;
currNode = node;
}
if (currNode == null || currNode == end) { break; }
@@ -515,7 +492,4 @@ namespace Barotrauma
private bool CanFitThroughGap(Gap gap, float minWidth) => gap.IsHorizontal ? gap.RectHeight > minWidth : gap.RectWidth > minWidth;
}
}
}
@@ -115,6 +115,8 @@ namespace Barotrauma
}
}
public bool IsAtEndNode => currentIndex >= nodes.Count - 1;
public List<WayPoint> Nodes
{
get { return nodes; }
@@ -101,7 +101,7 @@ namespace Barotrauma
{
if (InWater || !CanWalk)
{
return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed);
return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed + 0.0001f);
}
else
{
@@ -134,9 +134,12 @@ namespace Barotrauma
}
}
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
public enum Animation { None, Climbing, UsingItem, Struggle, CPR, UsingItemWhileClimbing };
public Animation Anim;
public bool IsUsingItem => Anim == Animation.UsingItem || Anim == Animation.UsingItemWhileClimbing;
public bool IsClimbing => Anim == Animation.Climbing || Anim == Animation.UsingItemWhileClimbing;
public Vector2 AimSourceWorldPos
{
get
@@ -280,7 +283,7 @@ namespace Barotrauma
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
{
useItemTimer = 0.5f;
Anim = Animation.UsingConstruction;
StartUsingItem();
if (!allowMovement)
{
@@ -359,8 +362,13 @@ namespace Barotrauma
Vector2 itemPos = aim ? aimPos : holdPos;
var controller = character.SelectedConstruction?.GetComponent<Controller>();
var controller = character.SelectedItem?.GetComponent<Controller>();
bool usingController = controller != null && !controller.AllowAiming;
if (!usingController)
{
controller = character.SelectedSecondaryItem?.GetComponent<Controller>();
usingController = controller != null && !controller.AllowAiming;
}
bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f;
float itemAngle;
Holdable holdable = item.GetComponent<Holdable>();
@@ -722,5 +730,45 @@ namespace Barotrauma
CalculateArmLengths();
}
}
private void StartAnimation(Animation animation)
{
if (animation == Animation.UsingItem)
{
Anim = IsClimbing ? Animation.UsingItemWhileClimbing : Animation.UsingItem;
}
else if (animation == Animation.Climbing)
{
Anim = IsUsingItem ? Animation.UsingItemWhileClimbing : Animation.Climbing;
}
else
{
Anim = animation;
}
}
private void StopAnimation(Animation animation)
{
if (animation == Animation.UsingItem)
{
Anim = IsClimbing ? Animation.Climbing : Animation.None;
}
else if (animation == Animation.Climbing)
{
Anim = IsUsingItem ? Animation.UsingItem : Animation.None;
}
else
{
Anim = Animation.None;
}
}
public void StartUsingItem() => StartAnimation(Animation.UsingItem);
public void StartClimbing() => StartAnimation(Animation.Climbing);
public void StopUsingItem() => StopAnimation(Animation.UsingItem);
public void StopClimbing() => StopAnimation(Animation.Climbing);
}
}
@@ -237,13 +237,14 @@ namespace Barotrauma
public override void UpdateAnim(float deltaTime)
{
if (Frozen) return;
if (Frozen) { return; }
if (MainLimb == null) { return; }
levitatingCollider = !IsHanging;
ColliderIndex = Crouching && !swimming ? 1 : 0;
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false ||
character.SelectedConstruction?.GetComponent<Ladder>() != null ||
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{
Crouching = false;
@@ -354,12 +355,16 @@ namespace Barotrauma
{
ApplyTestPose();
}
else
else if (Anim != Animation.UsingItem)
{
if (Anim != Animation.UsingConstruction)
if (Anim != Animation.UsingItemWhileClimbing)
{
ResetPullJoints();
}
else
{
ResetPullJoints(l => l.IsLowerBody);
}
}
if (SimplePhysicsEnabled)
@@ -377,49 +382,53 @@ namespace Barotrauma
switch (Anim)
{
case Animation.Climbing:
case Animation.UsingItemWhileClimbing:
levitatingCollider = false;
UpdateClimbing();
UpdateUseItemTimer();
break;
case Animation.CPR:
UpdateCPR(deltaTime);
break;
case Animation.UsingConstruction:
case Animation.UsingItem:
default:
if (Anim == Animation.UsingConstruction)
{
useItemTimer -= deltaTime;
if (useItemTimer <= 0.0f) Anim = Animation.None;
}
UpdateUseItemTimer();
swimmingStateLockTimer -= deltaTime;
if (forceStanding || character.AnimController.AnimationTestPose)
{
swimming = false;
}
else
else if (swimming != inWater && swimmingStateLockTimer <= 0.0f)
{
//0.5 second delay for switching between swimming and walking
//prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth
if (swimming != inWater && swimmingStateLockTimer <= 0.0f)
{
swimming = inWater;
swimmingStateLockTimer = 0.5f;
}
swimming = inWater;
swimmingStateLockTimer = 0.5f;
}
if (swimming)
{
UpdateSwimming();
}
else
else if (character.SelectedItem == null || !(character.SelectedSecondaryItem?.GetComponent<Controller>() is { } controller) ||
!controller.ControlCharacterPose || !controller.UserInCorrectPosition)
{
UpdateStanding();
}
break;
}
void UpdateUseItemTimer()
{
if (IsUsingItem)
{
useItemTimer -= deltaTime;
if (useItemTimer <= 0.0f)
{
StopUsingItem();
}
}
}
if (Timing.TotalTime > LockFlippingUntil && TargetDir != dir && !IsStuck)
{
Flip();
@@ -841,7 +850,9 @@ namespace Barotrauma
float targetSpeed = TargetMovement.Length();
if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming)
{
if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
if (!IsUsingItem &&
!(character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) &&
!(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
{
if (rotation > 20 && rotation < 170)
{
@@ -1041,12 +1052,17 @@ namespace Barotrauma
void UpdateClimbing()
{
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
if (ladder == null || character.IsIncapacitated)
var ladder = character.SelectedSecondaryItem?.GetComponent<Ladder>();
if (character.IsIncapacitated)
{
Anim = Animation.None;
return;
}
else if (ladder == null)
{
StopClimbing();
return;
}
onGround = false;
IgnorePlatforms = true;
@@ -1209,15 +1225,21 @@ namespace Barotrauma
{
RotateHead(head);
}
else if (Anim == Animation.UsingItemWhileClimbing && character.SelectedItem is { } selectedItem)
{
Vector2 diff = (selectedItem.WorldPosition - head.WorldPosition) * Dir;
float targetRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver4 * Dir);
head.body.SmoothRotate(targetRotation, force: WalkParams.HeadTorque);
}
else
{
float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque);
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, force: WalkParams.HeadTorque);
}
if (!ladder.Item.Prefab.Triggers.Any())
if (ladder.Item.Prefab.Triggers.None())
{
character.SelectedConstruction = null;
character.SelectedSecondaryItem = null;
return;
}
@@ -1247,8 +1269,7 @@ namespace Barotrauma
if (!isClimbing)
{
Anim = Animation.None;
character.SelectedConstruction = null;
character.StopClimbing();
IgnorePlatforms = false;
}
@@ -1487,7 +1508,7 @@ namespace Barotrauma
target.AnimController.ResetPullJoints();
}
if (Anim == Animation.Climbing)
if (IsClimbing)
{
//cannot drag up ladders if the character is conscious
if (target.AllowInput && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
@@ -922,12 +922,13 @@ namespace Barotrauma
{
limb.MoveToPos(pos, amount, pullFromCenter);
}
public void ResetPullJoints()
public void ResetPullJoints(Func<Limb, bool> condition = null)
{
for (int i = 0; i < Limbs.Length; i++)
{
if (Limbs[i] == null) { continue; }
if (condition != null && !condition(Limbs[i])) { continue; }
Limbs[i].PullJointEnabled = false;
}
}
@@ -813,43 +813,65 @@ namespace Barotrauma
get { return AnimController?.Collider?.LinearVelocity.Length() ?? 0.0f; }
}
private Item _selectedConstruction;
public Item SelectedConstruction
private Item _selectedItem;
/// <summary>
/// The primary selected item. It can be any device that character interacts with. This excludes items like ladders and chairs which are assigned to <see cref="SelectedSecondaryItem"/>.
/// </summary>
public Item SelectedItem
{
get => _selectedConstruction;
get => _selectedItem;
set
{
var prevSelectedConstruction = _selectedConstruction;
_selectedConstruction = value;
var prevSelectedItem = _selectedItem;
_selectedItem = value;
#if CLIENT
HintManager.OnSetSelectedConstruction(this, prevSelectedConstruction, _selectedConstruction);
HintManager.OnSetSelectedItem(this, prevSelectedItem, _selectedItem);
if (Controlled == this)
{
if (_selectedConstruction == null)
if (_selectedItem == null)
{
GameMain.GameSession?.CrewManager?.ResetCrewList();
}
else if (_selectedConstruction.GetComponent<Ladder>() == null)
else if (!_selectedItem.IsLadder)
{
GameMain.GameSession?.CrewManager?.AutoHideCrewList();
}
}
#endif
if (prevSelectedConstruction == null && _selectedConstruction != null)
if (prevSelectedItem != null && (_selectedItem == null || _selectedItem != prevSelectedItem) && itemSelectedTime > 0)
{
double selectedDuration = Timing.TotalTime - itemSelectedTime;
if (itemSelectedDurations.ContainsKey(prevSelectedItem.Prefab))
{
itemSelectedDurations[prevSelectedItem.Prefab] += selectedDuration;
}
else
{
itemSelectedDurations.Add(prevSelectedItem.Prefab, selectedDuration);
}
itemSelectedTime = 0;
}
if (_selectedItem != null && (prevSelectedItem == null || prevSelectedItem != _selectedItem))
{
itemSelectedTime = Timing.TotalTime;
}
else if (prevSelectedConstruction != null && _selectedConstruction == null && itemSelectedTime > 0)
{
if (!itemSelectedDurations.ContainsKey(prevSelectedConstruction.Prefab))
{
itemSelectedDurations.Add(prevSelectedConstruction.Prefab, 0);
}
itemSelectedDurations[prevSelectedConstruction.Prefab] += Timing.TotalTime - itemSelectedTime;
itemSelectedTime = 0;
}
}
}
/// <summary>
/// The secondary selected item. It's an item other than a device (see <see cref="SelectedItem"/>), e.g. a ladder or a chair.
/// </summary>
public Item SelectedSecondaryItem { get; set; }
/// <summary>
/// Has the characters selected a primary or a secondary item?
/// </summary>
public bool HasSelectedAnyItem => SelectedItem != null || SelectedSecondaryItem != null;
/// <summary>
/// Is the item either the primary or the secondary selected item?
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool IsAnySelectedItem(Item item) => item == SelectedItem || item == SelectedSecondaryItem;
public bool HasSelectedAnotherSecondaryItem(Item item) => SelectedSecondaryItem != null && SelectedSecondaryItem != item;
public Item FocusedItem
{
@@ -938,7 +960,7 @@ namespace Barotrauma
{
get
{
return SelectedConstruction == null || SelectedConstruction.GetComponent<Ladder>() != null || (SelectedConstruction.GetComponent<Controller>()?.AllowAiming ?? false);
return SelectedItem == null || (SelectedItem.GetComponent<Controller>()?.AllowAiming ?? false);
}
}
@@ -1481,8 +1503,20 @@ namespace Barotrauma
public void GiveJobItems(WayPoint spawnPoint = null)
{
if (info?.Job == null) { return; }
info.Job.GiveJobItems(this, spawnPoint);
if (info == null) { return; }
if (info.HumanPrefabIds != default)
{
var humanPrefab = NPCSet.Get(info.HumanPrefabIds.NpcSetIdentifier, info.HumanPrefabIds.NpcIdentifier);
if (humanPrefab == null)
{
DebugConsole.ThrowError($"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\".");
}
else if (humanPrefab.GiveItems(this, Submarine))
{
return;
}
}
info.Job?.GiveJobItems(this, spawnPoint);
}
public void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent = false)
@@ -1524,10 +1558,17 @@ namespace Barotrauma
if (item?.GetComponent<Wearable>() is Wearable wearable &&
!Inventory.IsInLimbSlot(item, InvSlotType.Any))
{
if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue))
foreach (var allowedSlot in wearable.AllowedSlots)
{
skillLevel += skillValue;
if (allowedSlot == InvSlotType.Any) { continue; }
if (!Inventory.IsInLimbSlot(item, allowedSlot)) { continue; }
if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue))
{
skillLevel += skillValue;
break;
}
}
}
}
}
@@ -1541,7 +1582,7 @@ namespace Barotrauma
public Vector2? OverrideMovement { get; set; }
public bool ForceRun { get; set; }
public bool IsClimbing => AnimController.Anim == AnimController.Animation.Climbing;
public bool IsClimbing => AnimController.IsClimbing;
public Vector2 GetTargetMovement()
{
@@ -1771,6 +1812,11 @@ namespace Barotrauma
return speed;
}
/// <summary>
/// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping.
/// </summary>
private const float cursorFollowMargin = 40;
public void Control(float deltaTime, Camera cam)
{
ViewTarget = null;
@@ -1803,10 +1849,10 @@ namespace Barotrauma
}
if (!aiControlled &&
AnimController.Anim != AnimController.Animation.UsingConstruction &&
!AnimController.IsUsingItem &&
AnimController.Anim != AnimController.Animation.CPR &&
(GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient || Controlled == this) &&
AnimController.OnGround && !AnimController.InWater)
(AnimController.OnGround || IsClimbing) && !AnimController.InWater)
{
if (dontFollowCursor)
{
@@ -1814,13 +1860,11 @@ namespace Barotrauma
}
else
{
// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping.
float followMargin = 40;
if (CursorPosition.X < AnimController.Collider.Position.X - followMargin)
if (CursorPosition.X < AnimController.Collider.Position.X - cursorFollowMargin)
{
AnimController.TargetDir = Direction.Left;
}
else if (CursorPosition.X > AnimController.Collider.Position.X + followMargin)
else if (CursorPosition.X > AnimController.Collider.Position.X + cursorFollowMargin)
{
AnimController.TargetDir = Direction.Right;
}
@@ -1967,7 +2011,8 @@ namespace Barotrauma
}
}
if (SelectedConstruction == null || !SelectedConstruction.Prefab.DisableItemUsageWhenSelected)
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
{
foreach (Item item in HeldItems)
{
@@ -1998,24 +2043,24 @@ namespace Barotrauma
}
}
if (SelectedConstruction != null)
if (SelectedItem != null)
{
if (IsKeyDown(InputType.Aim) || !SelectedConstruction.RequireAimToSecondaryUse)
if (IsKeyDown(InputType.Aim) || !SelectedItem.RequireAimToSecondaryUse)
{
SelectedConstruction.SecondaryUse(deltaTime, this);
SelectedItem.SecondaryUse(deltaTime, this);
}
if (IsKeyDown(InputType.Use) && SelectedConstruction != null && !SelectedConstruction.IsShootable)
if (IsKeyDown(InputType.Use) && SelectedItem != null && !SelectedItem.IsShootable)
{
if (!SelectedConstruction.RequireAimToUse || IsKeyDown(InputType.Aim))
if (!SelectedItem.RequireAimToUse || IsKeyDown(InputType.Aim))
{
SelectedConstruction.Use(deltaTime, this);
SelectedItem.Use(deltaTime, this);
}
}
if (IsKeyDown(InputType.Shoot) && SelectedConstruction != null && SelectedConstruction.IsShootable)
if (IsKeyDown(InputType.Shoot) && SelectedItem != null && SelectedItem.IsShootable)
{
if (!SelectedConstruction.RequireAimToUse || IsKeyDown(InputType.Aim))
if (!SelectedItem.RequireAimToUse || IsKeyDown(InputType.Aim))
{
SelectedConstruction.Use(deltaTime, this);
SelectedItem.Use(deltaTime, this);
}
}
}
@@ -2351,15 +2396,15 @@ namespace Barotrauma
//wires are interactable if the character has selected an item the wire is connected to,
//and it's disconnected from the other end
if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item)
if (wire.Connections[0]?.Item != null && SelectedItem == wire.Connections[0].Item)
{
return wire.Connections[1] == null;
}
if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item)
if (wire.Connections[1]?.Item != null && SelectedItem == wire.Connections[1].Item)
{
return wire.Connections[0] == null;
}
if (SelectedConstruction?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)
if (SelectedItem?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)
{
return wire.Connections[0] == null && wire.Connections[1] == null;
}
@@ -2385,7 +2430,7 @@ namespace Barotrauma
Pickable pickableComponent = item.GetComponent<Pickable>();
if (pickableComponent != null && pickableComponent.Picker != this && pickableComponent.Picker != null && !pickableComponent.Picker.IsDead) { return false; }
if (SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
if (SelectedItem?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
//optimization: don't use HeldItems because it allocates memory and this method is executed very frequently
var heldItem1 = Inventory?.GetItemInLimbSlot(InvSlotType.RightHand);
if (heldItem1?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
@@ -2428,27 +2473,43 @@ namespace Barotrauma
distanceToItem = Vector2.Distance(rectIntersectionPoint, playerDistanceCheckPosition);
}
if (distanceToItem > item.InteractDistance && item.InteractDistance > 0.0f) { return false; }
float interactDistance = item.InteractDistance;
if ((SelectedSecondaryItem != null || item.IsSecondaryItem) && AnimController is HumanoidAnimController c)
{
// Use a distance slightly shorter than the arms length to keep the character in a comfortable pose
float armLength = 0.75f * ConvertUnits.ToDisplayUnits(c.ArmLength);
interactDistance = Math.Min(interactDistance, armLength);
}
if (distanceToItem > interactDistance && item.InteractDistance > 0.0f) { return false; }
Vector2 itemPosition = item.SimPosition;
if (Submarine == null && item.Submarine != null)
{
//character is outside, item inside
itemPosition += item.Submarine.SimPosition;
}
else if (Submarine != null && item.Submarine == null)
{
//character is inside, item outside
itemPosition -= Submarine.SimPosition;
}
else if (Submarine != item.Submarine)
{
//character and the item are inside different subs
itemPosition += item.Submarine.SimPosition;
itemPosition -= Submarine.SimPosition;
}
if (SelectedSecondaryItem != null && !item.IsSecondaryItem)
{
if (item.GetComponent<Controller>() is { } controller && controller.Direction != 0 && controller.Direction != AnimController.Direction) { return false; }
float threshold = ConvertUnits.ToSimUnits(cursorFollowMargin);
if (AnimController.Direction == Direction.Left && SimPosition.X + threshold < itemPosition.X) { return false; }
if (AnimController.Direction == Direction.Right && SimPosition.X - threshold > itemPosition.X) { return false; }
}
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
{
Vector2 itemPosition = item.SimPosition;
if (Submarine == null && item.Submarine != null)
{
//character is outside, item inside
itemPosition += item.Submarine.SimPosition;
}
else if (Submarine != null && item.Submarine == null)
{
//character is inside, item outside
itemPosition -= Submarine.SimPosition;
}
else if (Submarine != item.Submarine)
{
//character and the item are inside different subs
itemPosition += item.Submarine.SimPosition;
itemPosition -= Submarine.SimPosition;
}
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item && (body.UserData as ItemComponent)?.Item != item && Submarine.LastPickedFixture?.UserData as Item != item)
{
@@ -2521,7 +2582,7 @@ namespace Barotrauma
if (!CanInteract)
{
SelectedConstruction = null;
SelectedItem = SelectedSecondaryItem = null;
focusedItem = null;
if (!AllowInput)
{
@@ -2570,8 +2631,8 @@ namespace Barotrauma
AnimController.InWater :
head.InWater;
//climb ladders automatically when pressing up/down inside their trigger area
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>();
if ((SelectedConstruction == null || currentLadder != null) &&
Ladder currentLadder = SelectedSecondaryItem?.GetComponent<Ladder>();
if ((SelectedSecondaryItem == null || currentLadder != null) &&
!headInWater && Screen.Selected != GameMain.SubEditorScreen)
{
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
@@ -2613,7 +2674,7 @@ namespace Barotrauma
{
if (nearbyLadder.Select(this))
{
SelectedConstruction = nearbyLadder.Item;
SelectedSecondaryItem = nearbyLadder.Item;
}
}
}
@@ -2657,16 +2718,23 @@ namespace Barotrauma
{
FocusedCharacter.onCustomInteract(FocusedCharacter, this);
}
else if (IsKeyHit(InputType.Deselect) && SelectedConstruction != null && SelectedConstruction.GetComponent<Ladder>() == null)
else if (IsKeyHit(InputType.Deselect) && SelectedItem != null)
{
SelectedConstruction = null;
SelectedItem = null;
#if CLIENT
CharacterHealth.OpenHealthWindow = null;
#endif
}
else if (IsKeyHit(InputType.Health) && SelectedConstruction != null && SelectedConstruction.GetComponent<Ladder>() == null)
else if (IsKeyHit(InputType.Deselect) && SelectedSecondaryItem != null)
{
SelectedConstruction = null;
SelectedSecondaryItem = null;
#if CLIENT
CharacterHealth.OpenHealthWindow = null;
#endif
}
else if (IsKeyHit(InputType.Health) && (SelectedItem != null || SelectedSecondaryItem != null))
{
SelectedItem = SelectedSecondaryItem = null;
}
else if (focusedItem != null)
{
@@ -2901,7 +2969,7 @@ namespace Barotrauma
{
Stun = Math.Max(5.0f, Stun);
AnimController.ResetPullJoints();
SelectedConstruction = null;
SelectedItem = SelectedSecondaryItem = null;
return;
}
@@ -2958,7 +3026,7 @@ namespace Barotrauma
humanAnimController.Crouching = false;
}
AnimController.ResetPullJoints();
SelectedConstruction = null;
SelectedItem = SelectedSecondaryItem = null;
return;
}
@@ -2974,9 +3042,13 @@ namespace Barotrauma
DoInteractionUpdate(deltaTime, mouseSimPos);
}
if (SelectedConstruction != null && !CanInteractWith(SelectedConstruction))
if (SelectedItem != null && !CanInteractWith(SelectedItem))
{
SelectedConstruction = null;
SelectedItem = null;
}
if (SelectedSecondaryItem != null && !CanInteractWith(SelectedSecondaryItem))
{
SelectedSecondaryItem = null;
}
if (!IsDead) { LockHands = false; }
@@ -3914,7 +3986,7 @@ namespace Barotrauma
CharacterHealth.Stun = newStun;
if (newStun > 0.0f)
{
SelectedConstruction = null;
SelectedItem = SelectedSecondaryItem = null;
}
HealthUpdateInterval = 0.0f;
}
@@ -3930,77 +4002,79 @@ namespace Barotrauma
CharacterHealth.ReduceAfflictionOnAllLimbs("damage".ToIdentifier(), eatingRegen * deltaTime);
}
}
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
foreach (StatusEffect statusEffect in statusEffectList)
if (statusEffects.TryGetValue(actionType, out var statusEffectList))
{
if (statusEffect.type == ActionType.OnDamaged)
foreach (StatusEffect statusEffect in statusEffectList)
{
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
if (statusEffect.OnlyPlayerTriggered)
if (statusEffect.type == ActionType.OnDamaged)
{
if (LastAttacker == null || !LastAttacker.IsPlayer)
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
if (statusEffect.OnlyPlayerTriggered)
{
continue;
if (LastAttacker == null || !LastAttacker.IsPlayer)
{
continue;
}
}
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
statusEffect.Apply(actionType, deltaTime, this, targets);
}
else if (statusEffect.targetLimbs != null)
{
foreach (var limbType in statusEffect.targetLimbs)
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
statusEffect.Apply(actionType, deltaTime, this, targets);
}
else if (statusEffect.targetLimbs != null)
{
foreach (var limbType in statusEffect.targetLimbs)
{
// Target all matching limbs
foreach (var limb in AnimController.Limbs)
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
if (limb.IsSevered) { continue; }
if (limb.type == limbType)
// Target all matching limbs
foreach (var limb in AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
if (limb.type == limbType)
{
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
{
// Target just the first matching limb
Limb limb = AnimController.GetLimb(limbType);
if (limb != null)
{
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
{
// Target just the last matching limb
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
if (limb != null)
{
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
{
// Target just the first matching limb
Limb limb = AnimController.GetLimb(limbType);
if (limb != null)
{
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
{
// Target just the last matching limb
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
if (limb != null)
{
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
{
statusEffect.Apply(actionType, deltaTime, this, this);
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
{
statusEffect.Apply(actionType, deltaTime, this, this);
}
}
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
{
// OnDamaged is called only for the limb that is hit.
foreach (Limb limb in AnimController.Limbs)
{
limb.ApplyStatusEffects(actionType, deltaTime);
// OnDamaged is called only for the limb that is hit.
foreach (Limb limb in AnimController.Limbs)
{
limb.ApplyStatusEffects(actionType, deltaTime);
}
}
}
//OnActive effects are handled by the afflictions themselves
@@ -4075,10 +4149,12 @@ namespace Barotrauma
return;
}
#if SERVER
if (GameMain.NetworkMember is { IsServer: true })
{
GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData());
GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData(forceAfflictionData: true));
}
#endif
isDead = true;
@@ -4153,7 +4229,7 @@ namespace Barotrauma
}
}
SelectedConstruction = null;
SelectedItem = SelectedSecondaryItem = null;
SelectedCharacter = null;
AnimController.ResetPullJoints();
@@ -4896,6 +4972,12 @@ namespace Barotrauma
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
/// </summary>
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
public void StopClimbing()
{
AnimController.StopClimbing();
SelectedSecondaryItem = null;
}
}
class ActiveTeamChange
@@ -54,6 +54,15 @@ namespace Barotrauma
public struct CharacterStatusEventData : IEventData
{
public EventType EventType => EventType.Status;
#if SERVER
public bool ForceAfflictionData;
public CharacterStatusEventData(bool forceAfflictionData)
{
ForceAfflictionData = forceAfflictionData;
}
#endif
}
public struct TreatmentEventData : IEventData
@@ -261,6 +261,10 @@ namespace Barotrauma
public string Name;
public LocalizedString Title;
public (Identifier NpcSetIdentifier, Identifier NpcIdentifier) HumanPrefabIds;
public string DisplayName
{
get
@@ -649,15 +653,12 @@ namespace Barotrauma
{
Name = name;
}
else if (!npcIdentifier.IsEmpty && TextManager.Get("npctitle." + npcIdentifier) is { Loaded: true } npcTitle)
{
Name = npcTitle.Value;
}
else
{
Name = GetRandomName(randSync);
}
TryLoadNameAndTitle(npcIdentifier);
SetPersonalityTrait();
Salary = CalculateSalary();
@@ -727,7 +728,7 @@ namespace Barotrauma
}
// Used for loading the data
public CharacterInfo(XElement infoElement)
public CharacterInfo(XElement infoElement, Identifier npcIdentifier = default)
{
ID = idCounter;
idCounter++;
@@ -774,6 +775,8 @@ namespace Barotrauma
Head.FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White);
CheckColors();
TryLoadNameAndTitle(npcIdentifier);
if (string.IsNullOrEmpty(Name))
{
var nameElement = CharacterConfigElement.GetChildElement("names");
@@ -794,9 +797,21 @@ namespace Barotrauma
ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty);
if (personalityName != Identifier.Empty)
{
PersonalityTrait = NPCPersonalityTrait.Get(GameSettings.CurrentConfig.Language, personalityName);
if (NPCPersonalityTrait.Traits.TryGet(personalityName, out var trait) ||
NPCPersonalityTrait.Traits.TryGet(personalityName.Replace(" ".ToIdentifier(), Identifier.Empty), out trait))
{
PersonalityTrait = trait;
}
else
{
DebugConsole.ThrowError($"Error in CharacterInfo \"{OriginalName}\": could not find a personality trait with the identifier \"{personalityName}\".");
}
}
HumanPrefabIds = (
element.GetAttributeIdentifier("npcsetid", Identifier.Empty),
element.GetAttributeIdentifier("npcid", Identifier.Empty));
MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0);
foreach (var subElement in infoElement.Elements())
@@ -838,6 +853,19 @@ namespace Barotrauma
LoadHeadAttachments();
}
private void TryLoadNameAndTitle(Identifier npcIdentifier)
{
if (!npcIdentifier.IsEmpty)
{
Title = TextManager.Get("npctitle." + npcIdentifier);
string nameTag = "charactername." + npcIdentifier;
if (TextManager.ContainsTag(nameTag))
{
Name = TextManager.Get(nameTag).Value;
}
}
}
private List<ContentXElement> hairs;
public IReadOnlyList<ContentXElement> Hairs => hairs;
private List<ContentXElement> beards;
@@ -1292,9 +1320,16 @@ namespace Barotrauma
new XAttribute("facialhaircolor", XMLExtensions.ColorToString(Head.FacialHairColor)),
new XAttribute("startitemsgiven", StartItemsGiven),
new XAttribute("ragdoll", ragdollFileName),
new XAttribute("personality", PersonalityTrait?.Name.Value ?? ""));
new XAttribute("personality", PersonalityTrait?.Identifier ?? Identifier.Empty));
// TODO: animations?
if (HumanPrefabIds != default)
{
charElement.Add(
new XAttribute("npcsetid", HumanPrefabIds.NpcSetIdentifier),
new XAttribute("npcid", HumanPrefabIds.NpcIdentifier));
}
charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath));
if (Character != null)
@@ -1323,11 +1358,8 @@ namespace Barotrauma
}
}
charElement.Add(savedStatElement);
parentElement.Add(charElement);
parentElement?.Add(charElement);
return charElement;
}
@@ -10,26 +10,27 @@ namespace Barotrauma
public readonly Direction Direction;
public readonly Character SelectedCharacter;
public readonly Item SelectedItem;
public readonly Item SelectedItem, SelectedSecondaryItem;
public readonly AnimController.Animation Animation;
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, animation)
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, selectedSecondaryItem, animation)
{
}
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, animation)
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, selectedSecondaryItem, animation)
{
}
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None)
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None)
: base(pos, rotation, velocity, angularVelocity, ID, time)
{
Direction = dir;
SelectedCharacter = selectedCharacter;
SelectedItem = selectedItem;
SelectedSecondaryItem = selectedSecondaryItem;
Animation = animation;
}
@@ -46,7 +46,7 @@ namespace Barotrauma
[Serialize(0, IsPropertySaveable.No)]
public int MaxMoney { get; private set; }
public CorpsePrefab(ContentXElement element, CorpsesFile file) : base(element, file) { }
public CorpsePrefab(ContentXElement element, CorpsesFile file) : base(element, file, npcSetIdentifier: Identifier.Empty) { }
public static CorpsePrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(sync);
}
@@ -55,13 +55,27 @@ namespace Barotrauma
if (vitalityMultipliers != null)
{
float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f);
vitalityMultipliers.ForEach(i => VitalityMultipliers.Add(i, multiplier));
foreach (var vitalityMultiplier in vitalityMultipliers)
{
VitalityMultipliers.Add(vitalityMultiplier, multiplier);
if (AfflictionPrefab.Prefabs.None(p => p.Identifier == vitalityMultiplier))
{
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?");
}
}
}
var vitalityTypeMultipliers = subElement.GetAttributeIdentifierArray("type", null) ?? subElement.GetAttributeIdentifierArray("types", null);
if (vitalityTypeMultipliers != null)
{
float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f);
vitalityTypeMultipliers.ForEach(i => VitalityTypeMultipliers.Add(i, multiplier));
foreach (var vitalityTypeMultiplier in vitalityTypeMultipliers)
{
VitalityTypeMultipliers.Add(vitalityTypeMultiplier, multiplier);
if (AfflictionPrefab.Prefabs.None(p => p.AfflictionType == vitalityTypeMultiplier))
{
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?");
}
}
}
if (vitalityMultipliers == null && VitalityTypeMultipliers == null)
{
@@ -10,7 +10,7 @@ namespace Barotrauma
class HumanPrefab : PrefabWithUintIdentifier
{
[Serialize("any", IsPropertySaveable.No)]
public string Job { get; protected set; }
public Identifier Job { get; protected set; }
[Serialize(1f, IsPropertySaveable.No)]
public float Commonness { get; protected set; }
@@ -82,17 +82,19 @@ namespace Barotrauma
public XElement Element { get; protected set; }
public readonly Dictionary<XElement, float> ItemSets = new Dictionary<XElement, float>();
public readonly Dictionary<XElement, float> CustomNPCSets = new Dictionary<XElement, float>();
public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>();
public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>();
public HumanPrefab(ContentXElement element, ContentFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
private readonly Identifier npcSetIdentifier;
public HumanPrefab(ContentXElement element, ContentFile file, Identifier npcSetIdentifier) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
SerializableProperty.DeserializeProperties(this, element);
Job = Job.ToLowerInvariant();
Element = element;
element.GetChildElements("itemset").ForEach(e => ItemSets.Add(e, e.GetAttributeFloat("commonness", 1)));
element.GetChildElements("character").ForEach(e => CustomNPCSets.Add(e, e.GetAttributeFloat("commonness", 1)));
element.GetChildElements("itemset").ForEach(e => ItemSets.Add((e, e.GetAttributeFloat("commonness", 1))));
element.GetChildElements("character").ForEach(e => CustomCharacterInfos.Add((e, e.GetAttributeFloat("commonness", 1))));
PreferredOutpostModuleTypes = element.GetAttributeIdentifierArray("preferredoutpostmoduletypes", Array.Empty<Identifier>());
this.npcSetIdentifier = npcSetIdentifier;
}
public IEnumerable<Identifier> GetModuleFlags()
@@ -107,7 +109,7 @@ namespace Barotrauma
public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func<JobPrefab, bool> predicate = null)
{
return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate);
return !Job.IsEmpty && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate);
}
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
@@ -146,23 +148,43 @@ namespace Barotrauma
}
}
public void GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true)
public bool GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true)
{
if (ItemSets == null || !ItemSets.Any()) { return; }
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), randSync);
if (ItemSets == null || !ItemSets.Any()) { return false; }
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element;
if (spawnItems != null)
{
foreach (XElement itemElement in spawnItems.GetChildElements("item"))
{
InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents);
int amount = itemElement.GetAttributeInt("amount", 1);
for (int i = 0; i < amount; i++)
{
InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents);
}
}
}
return true;
}
public CharacterInfo GetCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced)
/// <summary>
/// Creates a character info from the human prefab. If there are custom character infos defined, those are used, otherwise a randomized info is generated.
/// </summary>
/// <param name="randSync"></param>
/// <returns></returns>
public CharacterInfo CreateCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced)
{
var characterElement = ToolBox.SelectWeightedRandom(CustomNPCSets.Keys.ToList(), CustomNPCSets.Values.ToList(), randSync);
return characterElement != null ? new CharacterInfo(characterElement) : null;
var characterElement = ToolBox.SelectWeightedRandom(CustomCharacterInfos, info => info.commonness, randSync).element;
CharacterInfo characterInfo;
if (characterElement == null)
{
characterInfo= new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: GetJobPrefab(randSync), npcIdentifier: Identifier);
}
else
{
characterInfo = new CharacterInfo(characterElement, Identifier);
}
characterInfo.HumanPrefabIds = (npcSetIdentifier, Identifier);
return characterInfo;
}
public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, Item parentItem = null, bool createNetworkEvents = true)
@@ -229,7 +251,11 @@ namespace Barotrauma
parentItem?.Combine(item, user: null);
foreach (XElement childItemElement in itemElement.Elements())
{
InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents);
int amount = childItemElement.GetAttributeInt("amount", 1);
for (int i = 0; i < amount; i++)
{
InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents);
}
}
}
@@ -19,7 +19,7 @@ namespace Barotrauma
public int Variant;
public Skill PrimarySkill { get; }
public Skill PrimarySkill { get; private set; }
public Job(JobPrefab jobPrefab) : this(jobPrefab, randSync: Rand.RandSync.Unsynced, variant: 0) { }
@@ -102,9 +102,14 @@ namespace Barotrauma
public void OverrideSkills(Dictionary<Identifier, float> newSkills)
{
skills.Clear();
foreach (var newSkill in newSkills)
foreach (var newSkillInfo in newSkills)
{
skills.Add(newSkill.Key, new Skill(newSkill.Key, newSkill.Value));
var newSkill = new Skill(newSkillInfo.Key, newSkillInfo.Value);
if (PrimarySkill != null && newSkill.Identifier == PrimarySkill.Identifier)
{
PrimarySkill = newSkill;
}
skills.Add(newSkillInfo.Key, newSkill);
}
}
@@ -79,7 +79,7 @@ namespace Barotrauma
/// </summary>
public static IReadOnlyDictionary<Identifier, float> ItemRepairPriorities => _itemRepairPriorities;
public static JobPrefab Get(string identifier)
public static JobPrefab Get(Identifier identifier)
{
if (Prefabs.ContainsKey(identifier))
{
@@ -303,6 +303,17 @@ namespace Barotrauma
public Vector2 DebugTargetPos;
public Vector2 DebugRefPos;
public bool IsLowerBody =>
type == LimbType.LeftLeg ||
type == LimbType.RightLeg ||
type == LimbType.LeftFoot ||
type == LimbType.RightFoot ||
type == LimbType.Tail ||
type == LimbType.Legs ||
type == LimbType.RightThigh ||
type == LimbType.LeftThigh ||
type == LimbType.Waist;
public bool IsSevered
{
get { return isSevered; }
@@ -5,9 +5,11 @@ using System.Xml.Linq;
namespace Barotrauma
{
class NPCPersonalityTrait
class NPCPersonalityTrait : PrefabWithUintIdentifier
{
public readonly Identifier Name;
public readonly static PrefabCollection<NPCPersonalityTrait> Traits = new PrefabCollection<NPCPersonalityTrait>();
public readonly LocalizedString DisplayName;
public readonly List<string> AllowedDialogTags;
@@ -17,43 +19,29 @@ namespace Barotrauma
get { return commonness; }
}
public static IEnumerable<NPCPersonalityTrait> GetAll(LanguageIdentifier language)
public NPCPersonalityTrait(XElement element, NPCPersonalityTraitsFile file)
: base(file, element.GetAttributeIdentifier("identifier", element.GetAttributeIdentifier("name", Identifier.Empty)))
{
if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language))
string name = element.GetAttributeString("name", null);
if (name == null)
{
DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead..");
language = TextManager.DefaultLanguage;
DisplayName = TextManager.Get("personalitytrait." + Identifier)
.Fallback(Identifier.ToString());
}
return NPCConversationCollection.Collections[language]
.SelectMany(cc => cc.PersonalityTraits.Values);
}
public static NPCPersonalityTrait Get(LanguageIdentifier language, Identifier traitName)
{
if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language))
else
{
DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead..");
language = TextManager.DefaultLanguage;
DisplayName = name;
}
return NPCConversationCollection.Collections[language]
.FirstOrDefault(cc => cc.PersonalityTraits.ContainsKey(traitName))
.PersonalityTraits[traitName];
}
public NPCPersonalityTrait(XElement element)
{
Name = element.GetAttributeIdentifier("name", "");
AllowedDialogTags = new List<string>(element.GetAttributeStringArray("alloweddialogtags", Array.Empty<string>()));
commonness = element.GetAttributeFloat("commonness", 1.0f);
}
public static NPCPersonalityTrait GetRandom(string seed)
{
#warning TODO: implement NPCPersonality content type and revise this for determinism
var rand = new MTRandom(ToolBox.StringToInt(seed));
var list = GetAll(GameSettings.CurrentConfig.Language);
return ToolBox.SelectWeightedRandom(list, t => t.commonness, rand);
return ToolBox.SelectWeightedRandom(Traits.OrderBy(t => t.UintIdentifier), t => t.commonness, rand);
}
public override void Dispose() { }
}
}
@@ -17,7 +17,7 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect()
{
if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
if (!SelectedItemHasTag(Character)) { return; }
Character closestCharacter = null;
float closestDistance = squaredMaxDistance;
@@ -31,13 +31,17 @@ namespace Barotrauma.Abilities
}
}
if (closestCharacter?.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; }
if (!SelectedItemHasTag(closestCharacter)) { return; }
if (closestDistance < squaredMaxDistance)
{
ApplyEffectSpecific(Character);
ApplyEffectSpecific(closestCharacter);
}
bool SelectedItemHasTag(Character character) =>
(character.SelectedItem != null && character.SelectedItem.HasTag(tag)) ||
(character.SelectedSecondaryItem != null && character.SelectedSecondaryItem.HasTag(tag));
}
}
}