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));
}
}
}
@@ -0,0 +1,12 @@
namespace Barotrauma
{
sealed class NPCPersonalityTraitsFile : GenericPrefabFile<NPCPersonalityTrait>
{
public NPCPersonalityTraitsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
protected override bool MatchesSingular(Identifier identifier) => identifier == "personalitytrait";
protected override bool MatchesPlural(Identifier identifier) => identifier == "personalitytraits";
protected override PrefabCollection<NPCPersonalityTrait> Prefabs => NPCPersonalityTrait.Traits;
protected override NPCPersonalityTrait CreatePrefab(ContentXElement element) => new NPCPersonalityTrait(element, this);
}
}
@@ -84,7 +84,9 @@ namespace Barotrauma
{
static readonly List<CoroutineHandle> Coroutines = new List<CoroutineHandle>();
public static float UnscaledDeltaTime, DeltaTime;
public static float DeltaTime { get; private set; }
public static bool Paused { get; private set; }
public static CoroutineHandle StartCoroutine(IEnumerable<CoroutineStatus> func, string name = "", bool useSeparateThread = false)
{
@@ -191,7 +193,7 @@ namespace Barotrauma
if (current != null)
{
if (current.EndsCoroutine(handle) || handle.AbortRequested) { return true; }
if (!current.CheckFinished(UnscaledDeltaTime)) { return false; }
if (!current.CheckFinished(DeltaTime)) { return false; }
}
if (!handle.Coroutine.MoveNext()) { return true; }
return false;
@@ -204,7 +206,7 @@ namespace Barotrauma
while (!handle.AbortRequested)
{
if (PerformCoroutineStep(handle)) { return; }
Thread.Sleep((int)(UnscaledDeltaTime * 1000));
Thread.Sleep((int)(DeltaTime * 1000));
}
}
catch (ThreadAbortException)
@@ -232,7 +234,7 @@ namespace Barotrauma
{
if (handle.Thread.ThreadState.HasFlag(ThreadState.Stopped))
{
if (handle.Exception!=null || handle.Coroutine.Current == CoroutineStatus.Failure)
if (handle.Exception != null || handle.Coroutine.Current == CoroutineStatus.Failure)
{
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
}
@@ -254,9 +256,9 @@ namespace Barotrauma
#endif
}
// Updating just means stepping through all the coroutines
public static void Update(float unscaledDeltaTime, float deltaTime)
public static void Update(bool paused, float deltaTime)
{
UnscaledDeltaTime = unscaledDeltaTime;
Paused = paused;
DeltaTime = deltaTime;
List<CoroutineHandle> coroutineList;
@@ -282,8 +284,8 @@ namespace Barotrauma
{
public readonly float TotalTime;
float timer;
bool ignorePause;
private float timer;
private readonly bool ignorePause;
public WaitForSeconds(float time, bool ignorePause = true)
{
@@ -295,7 +297,7 @@ namespace Barotrauma
public override bool CheckFinished(float deltaTime)
{
#if !SERVER
if (ignorePause || !GUI.PauseMenuOpen)
if (ignorePause || !CoroutineManager.Paused)
{
timer -= deltaTime;
}
@@ -798,7 +798,55 @@ namespace Barotrauma
eventPrefabs.Select(prefab => prefab.Identifier).Distinct().Select(id => id.Value).ToArray()
};
}));
commands.Add(new Command("unlockmission", "unlockmission [identifier/tag]: Unlocks a mission in a random adjacent level.", (string[] args) =>
{
if (!(GameMain.GameSession?.GameMode is CampaignMode campaign))
{
ThrowError("The unlockmission command is only usable in the campaign mode.");
return;
}
if (args.Length == 0)
{
ThrowError("Please enter the identifier or a tag of the mission you want to unlock.");
return;
}
var currentLocation = campaign.Map.CurrentLocation;
if (MissionPrefab.Prefabs.Any(p => p.Identifier == args[0]))
{
currentLocation.UnlockMissionByIdentifier(args[0].ToIdentifier());
}
else
{
currentLocation.UnlockMissionByTag(args[0].ToIdentifier());
}
if (campaign is MultiPlayerCampaign mpCampaign)
{
mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
}
}, isCheat: true, getValidArgs: () =>
{
return new[]
{
MissionPrefab.Prefabs.Select(p => p.Identifier.ToString()).ToArray()
};
}));
commands.Add(new Command("setcampaignmetadata", "setcampaignmetadata [identifier] [value]: Sets the specified campaign metadata value.", (string[] args) =>
{
if (!(GameMain.GameSession?.GameMode is CampaignMode campaign))
{
ThrowError("The setcampaignmetadata command is only usable in the campaign mode.");
return;
}
if (args.Length < 2)
{
ThrowError("Please specify an identifier and a value.");
return;
}
SetDataAction.PerformOperation(campaign.CampaignMetadata, args[0].ToIdentifier(), args[1], SetDataAction.OperationType.Set);
}, isCheat: true));
commands.Add(new Command("setskill", "setskill [all/identifier] [max/level] [character]: Set your skill level.", (string[] args) =>
{
if (args.Length < 2)
@@ -2532,9 +2580,12 @@ namespace Barotrauma
#if CLIENT
GameMain.DebugDraw = false;
GameMain.LightManager.LightingEnabled = true;
Character.DebugDrawInteract = false;
#endif
Hull.EditWater = false;
Hull.EditFire = false;
EnemyAIController.DisableEnemyAI = false;
HumanAIController.DisableCrewAI = false;
}
}
}
@@ -59,7 +59,11 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes)]
public bool ContinueConversation { get; set; }
private Character speaker;
public Character speaker
{
get;
private set;
}
private AIObjective prevIdleObjective, prevGotoObjective;
@@ -120,7 +124,7 @@ namespace Barotrauma
#else
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.InGame && c.Character != null) { ServerWrite(speaker, c); }
if (c.InGame && c.Character != null) { ServerWrite(speaker, c, interrupt); }
}
#endif
ResetSpeaker();
@@ -331,9 +335,11 @@ namespace Barotrauma
if (!TargetTag.IsEmpty)
{
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
if (!targets.Any() || IsBlockedByAnotherConversation(targets)) { return; }
if (!targets.Any() || IsBlockedByAnotherConversation(targets, BlockOtherConversationsDuration)) { return; }
}
if (targetCharacter != null && IsBlockedByAnotherConversation(targetCharacter.ToEnumerable(), 0.1f)) { return; }
if (speaker?.AIController is HumanAIController humanAI)
{
prevIdleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
@@ -118,7 +118,7 @@ namespace Barotrauma
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
{
if (newCharacter == null) { return; }
newCharacter.HumanPrefab = humanPrefab;
@@ -157,8 +157,9 @@ namespace Barotrauma
npcsOrItems.Add(item);
}
item.CampaignInteractionType = CampaignMode.InteractionType.Examine;
if (player.SelectedConstruction == item ||
player.Inventory != null && player.Inventory.Contains(item) ||
if (player.SelectedItem == item ||
player.SelectedSecondaryItem == item ||
(player.Inventory != null && player.Inventory.Contains(item)) ||
(player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
{
Trigger(e1, e2);
@@ -71,6 +71,10 @@ namespace Barotrauma
private readonly List<Event> activeEvents = new List<Event>();
private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
private readonly HashSet<EventPrefab> nonRepeatableEvents = new HashSet<EventPrefab>();
#if DEBUG && SERVER
private DateTime nextIntensityLogTime;
#endif
@@ -169,54 +173,49 @@ namespace Barotrauma
CreateEvents(additiveSet);
}
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
if (level?.LevelData != null)
{
//if the outpost is connected to a locked connection, create an event to unlock it
if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
if (level.LevelData.Type == LevelData.LevelType.Outpost)
{
var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent);
var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e =>
e.BiomeIdentifier.IsEmpty ||
e.BiomeIdentifier == level.LevelData.Biome.Identifier);
//if the outpost is connected to a locked connection, create an event to unlock it
if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
{
var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent);
var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e =>
e.BiomeIdentifier.IsEmpty ||
e.BiomeIdentifier == level.LevelData.Biome.Identifier);
var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ?
ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) :
ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand);
if (unlockPathEventPrefab != null)
{
var newEvent = unlockPathEventPrefab.CreateInstance();
newEvent.Init();
ActiveEvents.Add(newEvent);
}
else
{
//if no event that unlocks the path can be found, unlock it automatically
level.StartLocation.Connections.ForEach(c => c.Locked = false);
}
}
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
if (level.LevelData.EventHistory.Count > MaxEventHistory)
{
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
}
AddChildEvents(initialEventSet);
void AddChildEvents(EventSet eventSet)
{
if (eventSet == null) { return; }
if (eventSet.OncePerOutpost)
{
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ?
ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) :
ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand);
if (unlockPathEventPrefab != null)
{
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
{
level.LevelData.NonRepeatableEvents.Add(ep);
}
var newEvent = unlockPathEventPrefab.CreateInstance();
newEvent.Init();
ActiveEvents.Add(newEvent);
}
else
{
//if no event that unlocks the path can be found, unlock it automatically
level.StartLocation.Connections.ForEach(c => c.Locked = false);
}
}
foreach (EventSet childSet in eventSet.ChildSets)
AddChildEvents(initialEventSet);
void AddChildEvents(EventSet eventSet)
{
AddChildEvents(childSet);
if (eventSet == null) { return; }
if (eventSet.OncePerOutpost)
{
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
{
nonRepeatableEvents.Add(ep);
}
}
foreach (EventSet childSet in eventSet.ChildSets)
{
AddChildEvents(childSet);
}
}
}
}
@@ -350,13 +349,33 @@ namespace Barotrauma
selectedEvents.Clear();
activeEvents.Clear();
QueuedEvents.Clear();
finishedEvents.Clear();
nonRepeatableEvents.Clear();
preloadedSprites.ForEach(s => s.Remove());
preloadedSprites.Clear();
pathFinder = null;
}
/// <summary>
/// Registers the exhaustible events in the level as exhausted, and adds the current events to the event history
/// </summary>
public void RegisterEventHistory()
{
level.LevelData.EventsExhausted = true;
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
{
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
if (level.LevelData.EventHistory.Count > MaxEventHistory)
{
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
}
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
}
}
public void SkipEventCooldown()
{
eventCoolDown = 0.0f;
@@ -375,6 +394,8 @@ namespace Barotrauma
selectedEvents.Remove(eventSet);
if (level == null) { return; }
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
if (eventSet.Exhaustible && level.LevelData.EventsExhausted) { return; }
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
int applyCount = 1;
@@ -427,7 +448,8 @@ namespace Barotrauma
if (suitablePrefabSubsets.Any())
{
var unusedEvents = suitablePrefabSubsets.ToList();
for (int j = 0; j < eventSet.EventCount; j++)
int eventCount = eventSet.GetEventCount(level);
for (int j = 0; j < eventCount; j++)
{
if (unusedEvents.All(e => e.EventPrefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; }
EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, e => e.EventPrefabs.Max(p => CalculateCommonness(p, e.Commonness)), rand);
@@ -706,7 +728,18 @@ namespace Barotrauma
foreach (Event ev in activeEvents)
{
if (!ev.IsFinished) { ev.Update(deltaTime); }
if (!ev.IsFinished)
{
ev.Update(deltaTime);
}
else if (!finishedEvents.Contains(ev))
{
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
{
if (!level.LevelData.EventHistory.Contains(ev.Prefab)) { level.LevelData.EventHistory.Add(ev.Prefab); }
}
finishedEvents.Add(ev);
}
}
if (QueuedEvents.Count > 0)
@@ -92,7 +92,13 @@ namespace Barotrauma
public readonly bool ChooseRandom;
public readonly int EventCount = 1;
private readonly int eventCount = 1;
private readonly Dictionary<Identifier, int> overrideEventCount = new Dictionary<Identifier, int>();
/// <summary>
/// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed.
/// </summary>
public readonly bool Exhaustible;
public readonly float MinDistanceTraveled;
public readonly float MinMissionTime;
@@ -250,7 +256,8 @@ namespace Barotrauma
MaxIntensity = Math.Max(element.GetAttributeFloat("maxintensity", 100.0f), MinIntensity);
ChooseRandom = element.GetAttributeBool("chooserandom", false);
EventCount = element.GetAttributeInt("eventcount", 1);
eventCount = element.GetAttributeInt("eventcount", 1);
Exhaustible = element.GetAttributeBool("exhaustible", false);
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
@@ -288,6 +295,13 @@ namespace Barotrauma
case "eventset":
childSets.Add(new EventSet(subElement, file, this));
break;
case "overrideeventcount":
Identifier locationType = subElement.GetAttributeIdentifier("locationtype", "");
if (!overrideEventCount.ContainsKey(locationType))
{
overrideEventCount.Add(locationType, subElement.GetAttributeInt("eventcount", eventCount));
}
break;
default:
//an element with just an identifier = reference to an event prefab
if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase))
@@ -332,6 +346,12 @@ namespace Barotrauma
return OverrideCommonness.ContainsKey(key) ? OverrideCommonness[key] : DefaultCommonness;
}
public int GetEventCount(Level level)
{
if (level?.StartLocation == null || !overrideEventCount.TryGetValue(level.StartLocation.Type.Identifier, out int count)) { return eventCount; }
return count;
}
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter = null, bool fullLog = false)
{
List<string> debugLines = new List<string>();
@@ -358,7 +378,7 @@ namespace Barotrauma
var unusedEvents = thisSet.EventPrefabs.ToList();
if (unusedEvents.Any())
{
for (int i = 0; i < thisSet.EventCount; i++)
for (int i = 0; i < thisSet.eventCount; i++)
{
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab.EventPrefabs.Any(p => p != null))
@@ -95,37 +95,42 @@ namespace Barotrauma
randSync = Rand.RandSync.Unsynced;
}
//if any of the escortees have a job defined, try to use a spawnpoint designated for that job
List<HumanPrefab> humanPrefabsToSpawn = new List<HumanPrefab>();
foreach (XElement element in characterConfig.Elements())
{
int count = CalculateScalingEscortedCharacterCount(inMission: true);
var humanPrefab = GetHumanPrefabFromElement(element);
if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; }
for (int i = 0; i < count; i++)
{
humanPrefabsToSpawn.Add(humanPrefab);
}
}
var jobPrefab = humanPrefab.GetJobPrefab();
//if any of the escortees have a job defined, try to use a spawnpoint designated for that job
foreach (var humanPrefab in humanPrefabsToSpawn)
{
if (humanPrefab == null || humanPrefab.Job.IsEmpty || humanPrefab.Job == "any") { continue; }
var jobPrefab = humanPrefab.GetJobPrefab(randSync);
if (jobPrefab != null)
{
var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub);
if (jobSpecificSpawnPos != null)
if (jobSpecificSpawnPos != null)
{
explicitStayInHullPos = jobSpecificSpawnPos;
break;
}
}
}
foreach (XElement element in characterConfig.Elements())
foreach (var humanPrefab in humanPrefabsToSpawn)
{
int count = CalculateScalingEscortedCharacterCount(inMission: true);
for (int i = 0; i < count; i++)
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
if (spawnedCharacter.AIController is HumanAIController humanAI)
{
Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
if (spawnedCharacter.AIController is HumanAIController humanAI)
{
humanAI.InitMentalStateManager();
}
humanAI.InitMentalStateManager();
}
}
if (terroristChance > 0f)
{
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
@@ -511,7 +511,7 @@ namespace Barotrauma
protected Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.ServerAndClient, bool giveTags = true)
{
var characterInfo = humanPrefab.GetCharacterInfo(Rand.RandSync.ServerAndClient) ?? new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobOrJobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
characterInfo.TeamID = teamType;
if (positionToStayIn == null)
@@ -2,21 +2,19 @@
using System.Collections.Generic;
using System.Linq;
using System;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class MonsterMission : Mission
{
//string = filename, point = min,max
private readonly HashSet<(CharacterPrefab character, Point amountRange)> monsterPrefabs = new HashSet<(CharacterPrefab character, Point amountRange)>();
private readonly List<Character> monsters = new List<Character>();
private readonly List<Vector2> sonarPositions = new List<Vector2>();
private readonly List<Vector2> tempSonarPositions = new List<Vector2>();
private readonly float maxSonarMarkerDistance = 10000.0f;
private readonly Level.PositionType spawnPosType;
private Vector2? spawnPos = null;
public override IEnumerable<Vector2> SonarPositions
{
@@ -114,7 +112,16 @@ namespace Barotrauma
if (!IsClient)
{
Level.Loaded.TryGetInterestingPosition(true, spawnPosType, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
float minDistBetweenMonsterMissions = 10000;
float mindDistFromSub = Level.Loaded.Size.X * 0.3f;
var monsterMissions = GameMain.GameSession.Missions.Select(e => e as MonsterMission).Where(m => m != null && m != this && m.spawnPos.HasValue);
if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Vector2 spawnPos,
filter: p => monsterMissions.None(m => Vector2.DistanceSquared(p.Position.ToVector2(), m.spawnPos.Value) < minDistBetweenMonsterMissions * minDistBetweenMonsterMissions),
suppressWarning: true))
{
Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out spawnPos);
}
this.spawnPos = spawnPos;
foreach (var (character, amountRange) in monsterPrefabs)
{
int amount = Rand.Range(amountRange.X, amountRange.Y + 1);
@@ -123,9 +130,8 @@ namespace Barotrauma
monsters.Add(Character.Create(character.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
}
}
InitializeMonsters(monsters);
}
}
}
private void InitializeMonsters(IEnumerable<Character> monsters)
@@ -181,7 +187,7 @@ namespace Barotrauma
}
if (monsters[i].Removed || monsters[i].IsDead) { continue; }
Vector2 diff = tempSonarPositions[i] - monsters[i].Position;
Vector2 diff = tempSonarPositions[i] - monsters[i].WorldPosition;
float maxDist = maxSonarMarkerDistance;
Submarine refSub = Character.Controlled?.Submarine ?? Submarine.MainSub;
@@ -191,12 +197,12 @@ namespace Barotrauma
float subDist = Vector2.Distance(refPos, tempSonarPositions[i]) / maxDist;
maxDist = Math.Min(subDist * subDist * maxDist, maxDist);
maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].Position), maxDist);
maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].WorldPosition), maxDist);
}
if (diff.LengthSquared() > maxDist * maxDist)
{
tempSonarPositions[i] = monsters[i].Position + Vector2.Normalize(diff) * maxDist;
tempSonarPositions[i] = monsters[i].WorldPosition + Vector2.Normalize(diff) * maxDist;
}
}
@@ -125,7 +125,8 @@ namespace Barotrauma
var file = CharacterPrefab.FindBySpeciesName(SpeciesName)?.ContentFile;
if (file == null)
{
DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\"");
DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\".");
disallowed = true;
yield break;
}
else
@@ -535,10 +536,10 @@ namespace Barotrauma
Character createdCharacter = Character.Create(SpeciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true, throwErrorIfNotFound: false);
if (createdCharacter == null)
{
DebugConsole.AddWarning($"Error in MonsterEvent: failed to spawn the character \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\".");
disallowed = true;
return;
}
var eventManager = GameMain.GameSession.EventManager;
if (eventManager != null)
{
@@ -342,6 +342,21 @@ namespace Barotrauma
}
private static Implementation? loadedImplementation;
private static void ValidateEventID(string eventID)
{
#if DEBUG
string[] parts = eventID.Split(':');
if (parts.Length > 5)
{
DebugConsole.ThrowError($"Invalid GameAnalytics event id \"{eventID}\". Only 5 id parts allowed separated by ':'");
}
if (parts.Any(p => p.Length > 32))
{
DebugConsole.ThrowError($"Invalid GameAnalytics event id \"{eventID}\". Each id part separated by ':' must be 32 characters or less.");
}
#endif
}
public static void AddErrorEvent(ErrorSeverity errorSeverity, string message)
{
if (!SendUserStatistics) { return; }
@@ -368,12 +383,14 @@ namespace Barotrauma
public static void AddDesignEvent(string eventID)
{
if (!SendUserStatistics) { return; }
ValidateEventID(eventID);
loadedImplementation?.AddDesignEvent(eventID);
}
public static void AddDesignEvent(string eventID, double value)
{
if (!SendUserStatistics) { return; }
ValidateEventID(eventID);
loadedImplementation?.AddDesignEvent(eventID, value);
}
@@ -450,11 +467,11 @@ namespace Barotrauma
SetConsent(Consent.Error);
return;
}
loadedImplementation?.SetEnabledInfoLog(true);
loadedImplementation?.SetEnabledVerboseLog(true);
#if DEBUG
try
{
loadedImplementation?.SetEnabledInfoLog(true);
loadedImplementation?.SetEnabledVerboseLog(true);
}
catch (Exception e)
{
@@ -302,6 +302,10 @@ namespace Barotrauma
var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true);
foreach (PurchasedItem item in itemsToPurchase)
{
// Exchange money
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
if (!campaign.TryPurchase(client, itemValue)) { continue; }
// Add to the purchased items
var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab);
if (purchasedItem != null)
@@ -313,9 +317,6 @@ namespace Barotrauma
purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client);
itemsPurchasedFromStore.Add(purchasedItem);
}
// Exchange money
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
campaign.TryPurchase(client, itemValue);
if (GameMain.IsSingleplayer)
{
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
@@ -96,20 +96,17 @@ namespace Barotrauma
private object GetTypeOrDefault(Identifier identifier, Type type, object defaultValue)
{
object? value = GetValue(identifier);
if (value == null)
if (value != null)
{
SetValue(identifier, defaultValue);
if (value.GetType() == type)
{
return value;
}
else
{
DebugConsole.ThrowError($"Attempted to get value \"{identifier}\" as a {type} but the value is {value.GetType()}.");
}
}
else if (value.GetType() == type)
{
return value;
}
else
{
DebugConsole.ThrowError($"Attempted to get value \"{identifier}\" as a {type} but the value is {value.GetType()}.");
}
return defaultValue;
}
@@ -34,7 +34,7 @@ namespace Barotrauma
public double TotalPlayTime;
public int TotalPassedLevels;
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic, Cargo }
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Upgrade, PurchaseSub, MedicalClinic, Cargo }
public static bool BlocksInteraction(InteractionType interactionType)
{
@@ -85,7 +85,9 @@ namespace Barotrauma
public bool CheatsEnabled;
public const int HullRepairCost = 500, ItemRepairCost = 500, ShuttleReplaceCost = 1000;
public const float HullRepairCostPerDamage = 0.5f, ItemRepairCostPerRepairDuration = 1.0f;
public const int ShuttleReplaceCost = 1000;
public const int MaxHullRepairCost = 2000, MaxItemRepairCost = 2000;
protected bool wasDocked;
@@ -152,8 +154,9 @@ namespace Barotrauma
{
if (!(e.ChangedData.BalanceChanged is Some<int> { Value: var changed })) { return; }
bool isGain = changed > 0;
if (changed != 0) { return; }
bool isGain = changed > 0;
Color clr = isGain ? GUIStyle.Yellow : GUIStyle.Red;
switch (e.Owner)
@@ -260,6 +263,39 @@ namespace Barotrauma
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
}
public int GetHullRepairCost()
{
float totalDamage = 0;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineType.Player) { continue; }
if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine))
{
for (int i = 0; i < wall.SectionCount; i++)
{
totalDamage += wall.SectionDamage(i);
}
}
}
return (int)Math.Min(totalDamage * HullRepairCostPerDamage, MaxHullRepairCost);
}
public int GetItemRepairCost()
{
float totalRepairDuration = 0.0f;
foreach (Item item in Item.ItemList)
{
if (item.Submarine == null || item.Submarine.Info.Type != SubmarineType.Player) { continue; }
if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine))
{
var repairable = item.GetComponent<Repairable>();
if (repairable == null) { continue; }
totalRepairDuration += repairable.FixDurationHighSkill * (1.0f - item.Condition / item.MaxCondition);
}
}
return (int)Math.Min(totalRepairDuration * ItemRepairCostPerRepairDuration, MaxItemRepairCost);
}
public void InitCampaignData()
{
Factions = new List<Faction>();
@@ -864,7 +864,7 @@ namespace Barotrauma
double roundDuration = Timing.TotalTime - RoundStartTime;
GameAnalyticsManager.AddProgressionEvent(
success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail,
GameMode?.Name?.Value ?? "none",
GameMode?.Preset.Identifier.Value ?? "none",
roundDuration);
string eventId = "EndRound:" + (GameMode?.Preset?.Identifier.Value ?? "none") + ":";
LogEndRoundStats(eventId);
@@ -60,16 +60,6 @@ namespace Barotrauma
/// </summary>
public const bool UpgradeAlsoConnectedSubs = true;
/// <summary>
/// Prevents the player from upgrading the submarine when we are switching to a new one.
/// </summary>
/// <remarks>
/// In singleplayer we check if CampaignMode.PendingSubmarineSwitch is not null indicating we are switching submarines
/// but in multiplayer that value is not synced so we use this variable instead by setting it to false in <see cref="UpgradeManager.ClientRead"/>
/// and then set it back to true when the round ends in <see cref="MultiPlayerCampaign.End"/>
/// </remarks>
public bool CanUpgrade = true;
/// <summary>
/// This is used by the client in multiplayer, acts like a secondary PendingUpgrades list
/// but is not affected by server messages.
@@ -713,9 +703,9 @@ namespace Barotrauma
public bool CanUpgradeSub()
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return CanUpgrade; }
return Campaign.PendingSubmarineSwitch == null;
return
Campaign.PendingSubmarineSwitch == null ||
Campaign.PendingSubmarineSwitch.Name == Submarine.MainSub.Info.Name;
}
public void Save(XElement? parent)
@@ -12,7 +12,7 @@ namespace Barotrauma
Ragdoll, Health, Grab,
SelectNextCharacter,
SelectPreviousCharacter,
Voice,
Voice, RadioVoice, LocalVoice,
Deselect,
Shoot,
Command,
@@ -691,12 +691,30 @@ namespace Barotrauma.Items.Components
{
item.Drop(character);
item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false);
//the light source won't get properly updated if lighting is disabled (even though the light sprite is still drawn when lighting is disabled)
//so let's ensure the light source is up-to-date
RefreshLightSources(item);
}
AttachToWall();
}
return true;
static void RefreshLightSources(Item item)
{
item.body?.UpdateDrawPosition();
foreach (var light in item.GetComponents<LightComponent>())
{
light.SetLightSourceTransform();
}
item.GetComponent<ItemContainer>()?.SetContainedItemPositions();
foreach (var containedItem in item.ContainedItems)
{
RefreshLightSources(containedItem);
}
}
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
return true;
@@ -1,12 +1,10 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -292,7 +290,6 @@ namespace Barotrauma.Items.Components
item.body.PhysEnabled = false;
}
private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{
if (User == null || User.Removed)
@@ -419,7 +416,18 @@ namespace Barotrauma.Items.Components
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
var attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
#if CLIENT
if (attackResult.Damage > 0.0f)
{
Character.Controlled?.UpdateHUDProgressBar(targetItem,
targetItem.WorldPosition,
targetItem.Condition / targetItem.MaxCondition,
emptyColor: GUIStyle.HealthBarColorLow,
fullColor: GUIStyle.HealthBarColorHigh,
textTag: targetItem.Name);
}
#endif
}
else if (target.UserData is Holdable holdable && holdable.CanPush)
{
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
//attempting to pick does not select the item, so if it is selected at this point, another ItemComponent
//must have been selected and we should not keep deattaching (happens when for example interacting with
//an electrical component while holding both a screwdriver and a wrench).
if (picker.SelectedConstruction == item ||
if (picker.IsAnySelectedItem(item)||
picker.IsKeyDown(InputType.Aim) ||
!picker.CanInteractWith(item) ||
item.Removed || item.ParentInventory != null)
@@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components
!string.IsNullOrWhiteSpace(PickingMsg) ? PickingMsg : this is Door ? "progressbar.opening" : "progressbar.deattaching");
#endif
picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
picker.AnimController.UpdateUseItem(!picker.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
pickTimer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
@@ -208,7 +208,7 @@ namespace Barotrauma.Items.Components
{
if (picker != null)
{
picker.AnimController.Anim = AnimController.Animation.None;
picker.AnimController.StopUsingItem();
picker.PickingItem = null;
}
if (pickingCoroutine != null)
@@ -18,6 +18,7 @@ namespace Barotrauma.Items.Components
};
private readonly HashSet<Identifier> fixableEntities;
private readonly HashSet<Identifier> nonFixableEntities;
private Vector2 pickedPosition;
private float activeTimer;
@@ -135,6 +136,7 @@ namespace Barotrauma.Items.Components
}
fixableEntities = new HashSet<Identifier>();
nonFixableEntities = new HashSet<Identifier>();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -147,7 +149,16 @@ namespace Barotrauma.Items.Components
}
else
{
fixableEntities.Add(subElement.GetAttributeIdentifier("identifier", ""));
foreach (Identifier id in subElement.GetAttributeIdentifierArray("identifier", Array.Empty<Identifier>()))
{
fixableEntities.Add(id);
}
}
break;
case "nonfixable":
foreach (Identifier id in subElement.GetAttributeIdentifierArray("identifier", Array.Empty<Identifier>()))
{
nonFixableEntities.Add(id);
}
break;
}
@@ -523,6 +534,7 @@ namespace Barotrauma.Items.Components
if (sectionIndex < 0) { return false; }
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; }
if (nonFixableEntities.Contains(targetStructure.Prefab.Identifier) || nonFixableEntities.Any(t => targetStructure.Tags.Contains(t))) { return false; }
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, structure: targetStructure);
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
@@ -49,6 +49,8 @@ namespace Barotrauma.Items.Components
}
}
public readonly NamedEvent<ItemContainer> OnContainedItemsChanged = new NamedEvent<ItemContainer>();
private bool alwaysContainedItemsSpawned;
public ItemInventory Inventory;
@@ -347,6 +349,7 @@ namespace Barotrauma.Items.Components
//no need to Update() if this item has no statuseffects and no physics body
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
OnContainedItemsChanged.Invoke(this);
}
public override void Move(Vector2 amount, bool ignoreContacts = false)
@@ -360,6 +363,7 @@ namespace Barotrauma.Items.Components
//deactivate if the inventory is empty
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
OnContainedItemsChanged.Invoke(this);
}
public bool CanBeContained(Item item)
@@ -496,7 +500,7 @@ namespace Barotrauma.Items.Components
return false;
}
}
if (AutoInteractWithContained && character.SelectedConstruction == null)
if (AutoInteractWithContained && character.SelectedItem == null)
{
foreach (Item contained in Inventory.AllItems)
{
@@ -510,7 +514,15 @@ namespace Barotrauma.Items.Components
var abilityItem = new AbilityItemContainer(item);
character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem);
return base.Select(character);
if (item.ParentInventory?.Owner == character)
{
//can't select ItemContainers in the character's inventory (the inventory is drawn by hovering the cursor over the inventory slot, not as a GUIFrame)
return false;
}
else
{
return base.Select(character);
}
}
public override bool Pick(Character picker)
@@ -19,8 +19,7 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
if (character == null || character.LockHands || character.Removed || !(character.AnimController is HumanoidAnimController)) return false;
character.AnimController.Anim = AnimController.Animation.Climbing;
character.AnimController.StartClimbing();
return true;
}
@@ -36,6 +36,7 @@ namespace Barotrauma.Items.Components
private readonly List<LimbPos> limbPositions = new List<LimbPos>();
private Direction dir;
public Direction Direction => dir;
//the position where the user walks to when using the controller
//(relative to the position of the item)
@@ -128,6 +129,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, IsPropertySaveable.No, description: "If true, other items can be used simultaneously.")]
public bool IsSecondaryItem
{
get;
private set;
}
public Controller(Item item, ContentXElement element)
: base(item, element)
{
@@ -150,7 +158,7 @@ namespace Barotrauma.Items.Components
if (user == null
|| user.Removed
|| user.SelectedConstruction != item
|| !user.IsAnySelectedItem(item)
|| item.ParentInventory != null
|| !user.CanInteractWith(item)
|| (UsableIn == UseEnvironment.Water && !user.AnimController.InWater)
@@ -165,7 +173,7 @@ namespace Barotrauma.Items.Components
return;
}
user.AnimController.Anim = AnimController.Animation.UsingConstruction;
user.AnimController.StartUsingItem();
if (userPos != Vector2.Zero)
{
@@ -186,32 +194,34 @@ namespace Barotrauma.Items.Components
}
else
{
diff.Y = 0.0f;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
// Secondary items (like ladders or chairs) will control the character position over primary items
// Only control the character position if the character doesn't have another secondary item already controlling it
if (!user.HasSelectedAnotherSecondaryItem(Item))
{
if (Math.Abs(diff.X) > 20.0f)
diff.Y = 0.0f;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
{
//wait for the character to walk to the correct position
return;
if (Math.Abs(diff.X) > 20.0f)
{
//wait for the character to walk to the correct position
return;
}
else if (Math.Abs(diff.X) > 0.1f)
{
//aim to keep the collider at the correct position once close enough
user.AnimController.Collider.LinearVelocity = new Vector2(
diff.X * 0.1f,
user.AnimController.Collider.LinearVelocity.Y);
}
}
else if (Math.Abs(diff.X) > 0.1f)
{
//aim to keep the collider at the correct position once close enough
user.AnimController.Collider.LinearVelocity = new Vector2(
diff.X * 0.1f,
user.AnimController.Collider.LinearVelocity.Y);
}
}
else
{
if (Math.Abs(diff.X) > 10.0f)
else if (Math.Abs(diff.X) > 10.0f)
{
user.AnimController.TargetMovement = Vector2.Normalize(diff);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
}
user.AnimController.TargetMovement = Vector2.Zero;
}
user.AnimController.TargetMovement = Vector2.Zero;
UserInCorrectPosition = true;
}
}
@@ -220,9 +230,16 @@ namespace Barotrauma.Items.Components
if (limbPositions.Count == 0) { return; }
user.AnimController.Anim = AnimController.Animation.UsingConstruction;
user.AnimController.StartUsingItem();
user.AnimController.ResetPullJoints();
if (user.SelectedItem != null)
{
user.AnimController.ResetPullJoints(l => l.IsLowerBody);
}
else
{
user.AnimController.ResetPullJoints();
}
if (dir != 0) { user.AnimController.TargetDir = dir; }
@@ -230,7 +247,10 @@ namespace Barotrauma.Items.Components
{
Limb limb = user.AnimController.GetLimb(lb.LimbType);
if (limb == null || !limb.body.Enabled) { continue; }
// Don't move lower body limbs if there's another selected secondary item that should control them
if (limb.IsLowerBody && user.HasSelectedAnotherSecondaryItem(Item)) { continue; }
// Don't move hands if there's a selected primary item that should control them
if (!limb.IsLowerBody && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; }
if (lb.AllowUsingLimb)
{
switch (lb.LimbType)
@@ -247,12 +267,9 @@ namespace Barotrauma.Items.Components
break;
}
}
limb.Disabled = true;
Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.Position * item.Scale;
Vector2 diff = worldPosition - limb.WorldPosition;
limb.PullJointEnabled = true;
limb.PullJointWorldAnchorB = limb.SimPosition + ConvertUnits.ToSimUnits(diff);
}
@@ -266,9 +283,7 @@ namespace Barotrauma.Items.Components
{
return false;
}
if (user == null || user.Removed ||
user.SelectedConstruction != item || !user.CanInteractWith(item))
if (user == null || user.Removed || !user.IsAnySelectedItem(item) || !user.CanInteractWith(item))
{
user = null;
return false;
@@ -290,46 +305,44 @@ namespace Barotrauma.Items.Components
}
lastUsed = Timing.TotalTime;
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
return true;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
if (this.user != character)
if (user != character)
{
return false;
}
if (this.user == null || character.Removed ||
this.user.SelectedConstruction != item || !character.CanInteractWith(item))
if (user == null || character.Removed || !user.IsAnySelectedItem(item) || !character.CanInteractWith(item))
{
user = null;
return false;
}
if (character == null)
{
this.user = null;
return false;
}
if (character == null) return false;
focusTarget = GetFocusTarget();
if (focusTarget == null)
{
Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y);
Vector2 offset = character.CursorWorldPosition - centerPos;
offset.Y = -offset.Y;
targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
return false;
}
character.ViewTarget = focusTarget;
#if CLIENT
if (character == Character.Controlled && cam != null)
{
Lights.LightManager.ViewTarget = focusTarget;
cam.TargetPos = focusTarget.WorldPosition;
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected * focusTarget.OffsetOnSelectedMultiplier, deltaTime * 10.0f);
HideHUDs(true);
}
@@ -338,16 +351,12 @@ namespace Barotrauma.Items.Components
if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
{
Vector2 centerPos = new Vector2(focusTarget.WorldRect.Center.X, focusTarget.WorldRect.Center.Y);
Turret turret = focusTarget.GetComponent<Turret>();
if (turret != null)
if (focusTarget.GetComponent<Turret>() is { } turret)
{
centerPos = new Vector2(focusTarget.WorldRect.X + turret.TransformedBarrelPos.X, focusTarget.WorldRect.Y - turret.TransformedBarrelPos.Y);
}
Vector2 offset = character.CursorWorldPosition - centerPos;
offset.Y = -offset.Y;
targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
}
return true;
@@ -425,9 +434,10 @@ namespace Barotrauma.Items.Components
humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
}
if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; }
if (character.SelectedItem == item) { character.SelectedItem = null; }
if (character.SelectedSecondaryItem == item) { character.SelectedSecondaryItem = null; }
character.AnimController.Anim = AnimController.Animation.None;
character.AnimController.StopUsingItem();
if (character == Character.Controlled)
{
HideHUDs(false);
@@ -3,6 +3,7 @@ using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
@@ -62,11 +63,18 @@ namespace Barotrauma.Items.Components
inputContainer = containers[0];
outputContainer = containers[1];
#if CLIENT
Identifier eventIdentifier = new Identifier(nameof(Deconstructor));
inputContainer.OnContainedItemsChanged.RegisterOverwriteExisting(eventIdentifier, OnItemSlotsChanged);
#endif
OnItemLoadedProjSpecific();
}
partial void OnItemLoadedProjSpecific();
partial void OnItemSlotsChanged(ItemContainer container);
public override void Update(float deltaTime, Camera cam)
{
MoveInputQueue();
@@ -281,6 +289,7 @@ namespace Barotrauma.Items.Components
{
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
{
spawnedItem.SpawnedInCurrentOutpost = item.SpawnedInCurrentOutpost;
spawnedItem.StolenDuringRound = targetItem.StolenDuringRound;
spawnedItem.AllowStealing = targetItem.AllowStealing;
for (int i = 0; i < outputContainer.Capacity; i++)
@@ -556,8 +556,20 @@ namespace Barotrauma.Items.Components
const int MaxCraftingSkill = 100;
//having a higher-than-100 skill (e.g. due to talents) gives +1 quality
quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0;
quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0;
foreach (var skill in fabricatedItem.RequiredSkills)
{
//+1 quality if the character's skill level is >20% from the min requirement towards max skill
//e.g. if the skill requirement is 10 -> 28
//40 -> 52
//90 -> 92
float skillRequirement = MathHelper.Lerp(skill.Level, MaxCraftingSkill, 0.2f);
if (user.GetSkillLevel(skill.Identifier) > skillRequirement)
{
quality += 1;
}
}
return quality;
}
@@ -226,7 +226,7 @@ namespace Barotrauma.Items.Components
// (= bots turn autotemp back on when leaving the reactor)
if (LastAIUser != null)
{
if (LastAIUser.SelectedConstruction != item && LastAIUser.CanInteractWith(item))
if (LastAIUser.SelectedItem != item && LastAIUser.CanInteractWith(item))
{
AutoTemp = true;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
@@ -505,7 +505,7 @@ namespace Barotrauma.Items.Components
{
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
#if SERVER
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item)
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedItem == item)
{
GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime);
}
@@ -705,7 +705,7 @@ namespace Barotrauma.Items.Components
{
if (lastUser != null && lastUser != character && lastUser != LastAIUser)
{
if (lastUser.SelectedConstruction == item && character.IsOnPlayerTeam)
if (lastUser.SelectedItem == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogReactorTaken").Value, null, 0.0f, "reactortaken".ToIdentifier(), 10.0f);
}
@@ -301,7 +301,7 @@ namespace Barotrauma.Items.Components
float userSkill = 0.0f;
if (user != null && controlledSub != null &&
(user.SelectedConstruction == item || item.linkedTo.Contains(user.SelectedConstruction)))
(user.SelectedItem == item || item.linkedTo.Contains(user.SelectedItem)))
{
userSkill = user.GetSkillLevel("helm") / 100.0f;
}
@@ -333,7 +333,7 @@ namespace Barotrauma.Items.Components
{
showIceSpireWarning = false;
if (user != null && user.Info != null &&
user.SelectedConstruction == item &&
user.SelectedItem == item &&
controlledSub != null && controlledSub.Velocity.LengthSquared() > 0.01f)
{
IncreaseSkillLevel(user, deltaTime);
@@ -389,7 +389,7 @@ namespace Barotrauma.Items.Components
}
// if our tactical AI pilot has left, revert back to maintaining position
if (navigateTactically && (user == null || user.SelectedConstruction != item))
if (navigateTactically && (user == null || user.SelectedItem != item))
{
navigateTactically = false;
AIRamTimer = 0f;
@@ -722,7 +722,7 @@ namespace Barotrauma.Items.Components
character.AIController.SteeringManager.Reset();
if (objective.Override)
{
if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam)
if (user != character && user != null && user.SelectedItem == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogSteeringTaken").Value, null, 0.0f, "steeringtaken".ToIdentifier(), 10.0f);
}
@@ -117,9 +117,6 @@ namespace Barotrauma.Items.Components
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
public bool ExponentialRechargeSpeed { get; set; }
[Editable(minValue: 0.0f, maxValue: 10.0f, decimals: 2), Serialize(0.5f, IsPropertySaveable.Yes)]
public float RechargeAdjustSpeed { get; set; }
private float efficiency;
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, IsPropertySaveable.Yes, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
public float Efficiency
@@ -851,7 +851,7 @@ namespace Barotrauma.Items.Components
}
else if (target.Body.UserData is Limb limb)
{
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User))
{
return false;
}
@@ -872,7 +872,18 @@ namespace Barotrauma.Items.Components
if (targetItem.Removed) { return false; }
if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
{
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
#if CLIENT
if (attackResult.Damage > 0.0f)
{
Character.Controlled?.UpdateHUDProgressBar(targetItem,
targetItem.WorldPosition,
targetItem.Condition / targetItem.MaxCondition,
emptyColor: GUIStyle.HealthBarColorLow,
fullColor: GUIStyle.HealthBarColorHigh,
textTag: targetItem.Name);
}
#endif
}
}
else if (target.Body.UserData is IDamageable damageable)
@@ -47,7 +47,7 @@ namespace Barotrauma.Items.Components
private int qualityLevel;
[Editable, Serialize(0, IsPropertySaveable.Yes)]
[Editable(MinValueInt = 0, MaxValueInt = MaxQuality), Serialize(0, IsPropertySaveable.Yes)]
public int QualityLevel
{
get { return qualityLevel; }
@@ -343,7 +343,7 @@ namespace Barotrauma.Items.Components
{
CurrentFixer.CheckTalents(AbilityEffectType.OnStopTinkering);
}
CurrentFixer.AnimController.Anim = AnimController.Animation.None;
CurrentFixer.AnimController.StopUsingItem();
CurrentFixer = null;
currentRepairItem = null;
currentFixerAction = FixActions.None;
@@ -430,7 +430,7 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
if (CurrentFixer != null && (CurrentFixer.SelectedItem != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
{
StopRepairing(CurrentFixer);
return;
@@ -502,7 +502,7 @@ namespace Barotrauma.Items.Components
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete);
}
if (CurrentFixer?.SelectedConstruction == item) { CurrentFixer.SelectedConstruction = null; }
if (CurrentFixer?.SelectedItem == item) { CurrentFixer.SelectedItem = null; }
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
wasBroken = false;
StopRepairing(CurrentFixer);
@@ -179,7 +179,7 @@ namespace Barotrauma.Items.Components
{
UpdateProjSpecific(deltaTime);
if (user == null || user.SelectedConstruction != item)
if (user == null || user.SelectedItem != item)
{
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
@@ -196,7 +196,7 @@ namespace Barotrauma.Items.Components
return;
}
user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
user.AnimController.UpdateUseItem(!user.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -206,7 +206,7 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime);
public override bool Select(Character picker)
public bool CanRewire()
{
//attaching wires to items with a body is not allowed
//(signal items remove their bodies when attached to a wall)
@@ -214,6 +214,15 @@ namespace Barotrauma.Items.Components
{
return false;
}
return true;
}
public override bool Select(Character picker)
{
if (!CanRewire())
{
return false;
}
user = picker;
#if SERVER
@@ -106,11 +106,11 @@ namespace Barotrauma.Items.Components
{
case "set_text":
case "signal_in":
if (string.IsNullOrEmpty(signal.value)) { return; }
if (signal.value.Length > MaxMessageLength)
{
signal.value = signal.value.Substring(0, MaxMessageLength);
}
string inputSignal = signal.value.Replace("\\n", "\n");
ShowOnDisplay(inputSignal, addToHistory: true, TextColor);
break;
@@ -309,7 +309,7 @@ namespace Barotrauma.Items.Components
if (nodes.Count == 0) { return; }
Character user = item.ParentInventory?.Owner as Character;
editNodeDelay = (user?.SelectedConstruction == null) ? editNodeDelay - deltaTime : 0.5f;
editNodeDelay = (user?.SelectedItem == null) ? editNodeDelay - deltaTime : 0.5f;
Submarine sub = item.Submarine;
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
@@ -369,7 +369,7 @@ namespace Barotrauma.Items.Components
user.AnimController.Collider.ApplyForce(forceDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
if (diff.LengthSquared() > 50.0f * 50.0f)
{
user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length()));
user.AnimController.UpdateUseItem(!user.IsClimbing, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length()));
}
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
@@ -428,7 +428,7 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character != Character.Controlled) { return false; }
if (character.SelectedConstruction != null) { return false; }
if (character.HasSelectedAnyItem) { return false; }
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen && !PlayerInput.PrimaryMouseButtonClicked())
{
@@ -525,7 +525,7 @@ namespace Barotrauma.Items.Components
UpdateLightComponents();
}
private void UpdateLightComponents()
public void UpdateLightComponents()
{
if (lightComponents != null)
{
@@ -303,6 +303,10 @@ namespace Barotrauma
{
light.SetLightSourceTransform();
}
foreach (var turret in GetComponents<Turret>())
{
turret.UpdateLightComponents();
}
}
#endif
}
@@ -458,7 +462,7 @@ namespace Barotrauma
[Serialize(0.0f, IsPropertySaveable.No)]
/// <summary>
/// Can be used by status effects or conditionals to modify the sound range
/// Can be used by status effects or conditionals to modify the sight range
/// </summary>
public new float SightRange
{
@@ -806,6 +810,10 @@ namespace Barotrauma
}
}
public bool IsLadder { get; }
public bool IsSecondaryItem { get; }
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID, bool callOnItemLoaded = true)
: this(new Rectangle(
(int)(position.X - itemPrefab.Sprite.size.X / 2 * itemPrefab.Scale),
@@ -1013,6 +1021,9 @@ namespace Barotrauma
qualityComponent = GetComponent<Quality>();
IsLadder = GetComponent<Ladder>() != null;
IsSecondaryItem = IsLadder || GetComponent<Controller>() is { IsSecondaryItem: true };
InitProjSpecific();
if (callOnItemLoaded)
@@ -2491,16 +2502,30 @@ namespace Barotrauma
if (user != null)
{
if (user.SelectedConstruction == this)
if (user.SelectedItem == this)
{
if (user.IsKeyHit(InputType.Select) || forceSelectKey)
{
user.SelectedConstruction = null;
user.SelectedItem = null;
}
}
else if (user.SelectedSecondaryItem == this)
{
if (user.IsKeyHit(InputType.Select) || forceSelectKey)
{
user.SelectedSecondaryItem = null;
}
}
else if (selected)
{
user.SelectedConstruction = this;
if (IsSecondaryItem)
{
user.SelectedSecondaryItem = this;
}
else
{
user.SelectedItem = this;
}
}
}
@@ -3245,7 +3270,7 @@ namespace Barotrauma
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.RotationRad);
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
item.rect.Location -= (origin - oldOrigin).ToPoint();
item.rect.Location -= ((origin - oldOrigin) * scaleRelativeToPrefab).ToPoint();
}
if (item.PurchasedNewSwap && !string.IsNullOrEmpty(appliedSwap.SwappableItem?.SpawnWithId))
@@ -3433,7 +3458,8 @@ namespace Barotrauma
foreach (Character character in Character.CharacterList)
{
if (character.SelectedConstruction == this) { character.SelectedConstruction = null; }
if (character.SelectedItem == this) { character.SelectedItem = null; }
if (character.SelectedSecondaryItem == this) { character.SelectedSecondaryItem = null; }
}
Door door = GetComponent<Door>();
@@ -3128,14 +3128,14 @@ namespace Barotrauma
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
{
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter);
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning);
position = pos.ToVector2();
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
{
if (!PositionsOfInterest.Any())
{
@@ -3155,11 +3155,14 @@ namespace Barotrauma
}
if (!suitablePositions.Any())
{
string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
if (!suppressWarning)
{
string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
DebugConsole.ThrowError(errorMsg);
#endif
}
position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position;
return false;
}
@@ -57,6 +57,8 @@ namespace Barotrauma
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
public bool EventsExhausted { get; set; }
public float CrushDepth
{
get
@@ -130,6 +132,8 @@ namespace Barotrauma
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
}
@@ -238,7 +242,8 @@ namespace Barotrauma
new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)),
new XAttribute("size", XMLExtensions.PointToString(Size)),
new XAttribute("generationparams", GenerationParams.Identifier),
new XAttribute("initialdepth", InitialDepth));
new XAttribute("initialdepth", InitialDepth),
new XAttribute(nameof(EventsExhausted).ToLower(), EventsExhausted));
if (HasBeaconStation)
{
@@ -944,15 +944,20 @@ namespace Barotrauma
{
foreach (Location location in Locations)
{
location.LevelData.EventsExhausted = false;
if (location.Discovered)
{
if (furthestDiscoveredLocation == null ||
if (furthestDiscoveredLocation == null ||
location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{
furthestDiscoveredLocation = location;
}
}
}
foreach (LocationConnection connection in Connections)
{
connection.LevelData.EventsExhausted = false;
}
foreach (Location location in Locations)
{
@@ -19,7 +19,7 @@ namespace Barotrauma
public NPCSet(ContentXElement element, NPCSetsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file)).ToImmutableArray();
Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file, Identifier)).ToImmutableArray();
}
public static HumanPrefab? Get(Identifier setIdentifier, Identifier npcidentifier)
@@ -203,7 +203,7 @@ namespace Barotrauma
}
else
{
newCollection.Add(new HumanPrefab(npcElement, file));
newCollection.Add(new HumanPrefab(npcElement, file, npcSetIdentifier: from));
}
}
humanPrefabCollections.Add(newCollection);
@@ -1426,7 +1426,7 @@ namespace Barotrauma
static bool ShouldRemoveLinkedEntity(MapEntity e, bool doorInUse, PlacedModule module)
{
if (e is Item it && it.GetComponent<Ladder>() != null)
if (e is Item it && it.IsLadder)
{
if (module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Top) || module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Bottom))
{
@@ -1568,7 +1568,7 @@ namespace Barotrauma
foreach (HumanPrefab humanPrefab in humanPrefabs)
{
if (humanPrefab is null) { continue; }
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient);
var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
if (location != null && location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
{
killedCharacters.Add(humanPrefab);
@@ -1582,7 +1582,7 @@ namespace Barotrauma
{
for (int tries = 0; tries < 100; tries++)
{
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: killedCharacter.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient);
var characterInfo = killedCharacter.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
if (!location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
{
selectedCharacters.Add((killedCharacter, characterInfo));
@@ -1,126 +0,0 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class RoundEndCinematic
{
public bool Running
{
get;
private set;
}
public Camera AssignedCamera;
private float duration;
private CoroutineHandle updateCoroutine;
public RoundEndCinematic(Submarine submarine, Camera cam, float duration = 10.0f)
: this(new List<Submarine>() { submarine }, cam, duration)
{
}
public RoundEndCinematic(List<Submarine> submarines, Camera cam, float duration)
{
if (!submarines.Any(s => s != null)) return;
this.duration = duration;
AssignedCamera = cam;
Running = true;
updateCoroutine = CoroutineManager.StartCoroutine(Update(submarines, cam));
}
public void Stop()
{
CoroutineManager.StopCoroutines(updateCoroutine);
Running = false;
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
}
private IEnumerable<CoroutineStatus> Update(List<Submarine> subs, Camera cam)
{
if (!subs.Any()) yield return CoroutineStatus.Success;
#if CLIENT
Character.Controlled = null;
GameMain.LightManager.LosEnabled = false;
#endif
cam.TargetPos = Vector2.Zero;
Level.Loaded.TopBarrier.Enabled = false;
foreach (Character character in Character.CharacterList)
{
character.AnimController.Frozen = true;
foreach (Limb limb in character.AnimController.Limbs)
{
limb.body.PhysEnabled = false;
}
}
cam.TargetPos = Vector2.Zero;
float timer = 0.0f;
float initialZoom = cam.Zoom;
Vector2 initialCameraPos = cam.Position;
while (timer < duration)
{
if (Screen.Selected != GameMain.GameScreen)
{
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
Running = false;
yield return CoroutineStatus.Success;
}
Vector2 minPos = new Vector2(
subs.Min(s => s.WorldPosition.X - s.Borders.Width / 2),
subs.Min(s => s.WorldPosition.Y - s.Borders.Height / 2));
Vector2 maxPos = new Vector2(
subs.Min(s => s.WorldPosition.X + s.Borders.Width / 2),
subs.Min(s => s.WorldPosition.Y + s.Borders.Height / 2));
Vector2 cameraPos = new Vector2(
MathHelper.SmoothStep(minPos.X, maxPos.X, timer / duration),
(minPos.Y + maxPos.Y) / 2.0f);
cam.Translate(cameraPos - cam.Position);
foreach (Submarine sub in subs)
{
sub.PhysicsBody?.ResetDynamics();
}
#if CLIENT
cam.Zoom = MathHelper.SmoothStep(initialZoom, 0.5f, timer / duration);
if (timer / duration > 0.9f)
{
GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / duration) - 0.9f) * 10.0f);
}
#endif
timer += CoroutineManager.UnscaledDeltaTime;
yield return CoroutineStatus.Running;
}
Running = false;
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
yield return CoroutineStatus.Success;
}
}
}
@@ -986,14 +986,6 @@ namespace Barotrauma
subBody.Body.ResetDynamics();
subBody.Body.Enabled = false;
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (e.Submarine == this)
{
Spawner.AddEntityToRemoveQueue(e);
}
}
foreach (Character c in Character.CharacterList)
{
if (c.Submarine == this)
@@ -1,8 +1,6 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Collision;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
@@ -898,13 +896,15 @@ namespace Barotrauma
}
bool holdingOntoSomething = false;
if (c.SelectedConstruction != null)
if (c.SelectedSecondaryItem != null)
{
holdingOntoSomething =
c.SelectedConstruction.GetComponent<Ladder>() != null ||
(c.SelectedConstruction.GetComponent<Controller>()?.LimbPositions.Any() ?? false);
holdingOntoSomething = c.SelectedSecondaryItem.IsLadder ||
(c.SelectedSecondaryItem.GetComponent<Controller>()?.LimbPositions.Any() ?? false);
}
if (!holdingOntoSomething && c.SelectedItem != null)
{
holdingOntoSomething = c.SelectedItem.GetComponent<Controller>()?.LimbPositions.Any() ?? false;
}
if (!holdingOntoSomething)
{
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
@@ -19,7 +19,7 @@ namespace Barotrauma
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
public const float LadderWaypointInterval = 70.0f;
public const float LadderWaypointInterval = 55.0f;
protected SpawnType spawnType;
private string[] idCardTags;
@@ -560,21 +560,22 @@ namespace Barotrauma
stairPoints.ForEach(wp => wp.FindStairs());
}
// Ladders
foreach (Item item in Item.ItemList)
{
var ladders = item.GetComponent<Ladder>();
if (ladders == null) { continue; }
Vector2 bottomPoint = new Vector2(item.Rect.Center.X, item.Rect.Top - item.Rect.Height + 10);
List<WayPoint> ladderPoints = new List<WayPoint>
List<(WayPoint wp, bool connectHullPoints)> ladderPoints = new List<(WayPoint, bool)>
{
new WayPoint(bottomPoint, SpawnType.Path, submarine),
(new WayPoint(bottomPoint, SpawnType.Path, submarine), true)
};
List<Body> ignoredBodies = new List<Body>();
// Lowest point is only meaningful for hanging ladders inside the sub, but it shouldn't matter in other cases either.
// Start point is where the bots normally grasp the ladder when they stand on ground.
WayPoint lowestPoint = ladderPoints[0];
WayPoint lowestPoint = ladderPoints[0].wp;
WayPoint prevPoint = lowestPoint;
Vector2 prevPos = prevPoint.SimPosition;
Body ground = Submarine.PickBody(lowestPoint.SimPosition, lowestPoint.SimPosition - Vector2.UnitY, ignoredBodies,
@@ -589,7 +590,7 @@ namespace Barotrauma
if (lowestPoint == null || Math.Abs(startPoint.Position.Y - startHeight) > 40 && Hull.FindHull(nextPos) != null)
{
startPoint = new WayPoint(nextPos, SpawnType.Path, submarine);
ladderPoints.Add(startPoint);
ladderPoints.Add((startPoint, true));
if (lowestPoint != null)
{
startPoint.ConnectTo(lowestPoint);
@@ -613,18 +614,13 @@ namespace Barotrauma
}
else
{
//no door, check for walls
//no door, check for platforms/walls
pickedBody = Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, null, false,
(Fixture f) => f.Body.UserData is Structure);
}
if (pickedBody == null)
{
prevPos = Submarine.LastPickedPosition;
continue;
}
else
if (pickedBody != null)
{
ignoredBodies.Add(pickedBody);
}
@@ -632,19 +628,29 @@ namespace Barotrauma
if (pickedDoor != null)
{
WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine);
ladderPoints.Add(newPoint);
ladderPoints.Add((newPoint, true));
newPoint.ConnectedGap = pickedDoor.LinkedGap;
// TODO: Prevent the waypoint below being too close to the door
newPoint.ConnectTo(prevPoint);
prevPoint = newPoint;
prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height));
// Adjust y to prevent waypoints clamping up together
y = Math.Max(pickedDoor.Item.Position.Y, y);
}
else
{
WayPoint newPoint = new WayPoint(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor, SpawnType.Path, submarine);
ladderPoints.Add(newPoint);
Vector2 pos = pickedBody == null ? new Vector2(startPoint.Position.X, y) :
ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor;
WayPoint newPoint = new WayPoint(pos, SpawnType.Path, submarine);
ladderPoints.Add((newPoint, pickedBody != null));
newPoint.ConnectTo(prevPoint);
prevPoint = newPoint;
prevPos = ConvertUnits.ToSimUnits(newPoint.Position);
if (pickedBody != null)
{
// Adjust y to prevent waypoints clamping up together
y = Math.Max(newPoint.Position.Y, y);
}
}
}
@@ -652,33 +658,30 @@ namespace Barotrauma
if (prevPoint.rect.Y < item.Rect.Y - 40)
{
WayPoint wayPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine);
ladderPoints.Add(wayPoint);
ladderPoints.Add((wayPoint, true));
wayPoint.ConnectTo(prevPoint);
}
// Connect ladder waypoints to hull points at the right and left side
foreach (WayPoint ladderPoint in ladderPoints)
var ladderWaypoints = ladderPoints.Select(lp => lp.wp);
foreach (var ladderPoint in ladderPoints)
{
ladderPoint.Ladders = ladders;
bool isHatch = ladderPoint.ConnectedGap != null && !ladderPoint.ConnectedGap.IsRoomToRoom;
var wp = ladderPoint.wp;
wp.Ladders = ladders;
if (!ladderPoint.connectHullPoints) { continue; }
bool isHatch = wp.ConnectedGap != null && !wp.ConnectedGap.IsRoomToRoom;
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = null;
if (isHatch)
{
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderPoints);
}
else
{
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
}
WayPoint closest = isHatch ?
wp.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderWaypoints) :
wp.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderWaypoints);
if (closest == null) { continue; }
ladderPoint.ConnectTo(closest);
wp.ConnectTo(closest);
}
}
}
// Another pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders)
// Another ladder pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders)
foreach (Item item in Item.ItemList)
{
var ladders = item.GetComponent<Ladder>();
@@ -1035,12 +1038,10 @@ namespace Barotrauma
w.tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToHashSet();
string jobIdentifier = element.GetAttributeString("job", "").ToLowerInvariant();
if (!string.IsNullOrWhiteSpace(jobIdentifier))
Identifier jobIdentifier = element.GetAttributeIdentifier("job", Identifier.Empty);
if (!jobIdentifier.IsEmpty)
{
w.AssignedJob =
JobPrefab.Get(jobIdentifier) ??
JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase));
w.AssignedJob = JobPrefab.Get(jobIdentifier);
}
w.linkedToID = new List<ushort>();
@@ -810,6 +810,13 @@ namespace Barotrauma.Networking
private set;
}
[Serialize(120.0f, IsPropertySaveable.Yes)]
public float DisallowKickVoteTime
{
get;
private set;
}
[Serialize(300.0f, IsPropertySaveable.Yes)]
public float KillDisconnectedTime
{
@@ -87,10 +87,12 @@ namespace Barotrauma
GameSettings.SaveCurrentConfig();
GameMain.SoundManager.SetCategoryMuffle("default", false);
GUI.ClearMessages();
#if !DEBUG
if (GameMain.GameSession?.GameMode is TestGameMode)
{
DebugConsole.DeactivateCheats();
}
#endif
#endif
}
@@ -168,9 +170,9 @@ namespace Barotrauma
if (Character.Controlled != null)
{
if (Character.Controlled.SelectedConstruction != null && Character.Controlled.CanInteractWith(Character.Controlled.SelectedConstruction))
if (Character.Controlled.SelectedItem != null && Character.Controlled.CanInteractWith(Character.Controlled.SelectedItem))
{
Character.Controlled.SelectedConstruction.UpdateHUD(cam, Character.Controlled, (float)deltaTime);
Character.Controlled.SelectedItem.UpdateHUD(cam, Character.Controlled, (float)deltaTime);
}
if (Character.Controlled.Inventory != null)
{
@@ -19,6 +19,7 @@
{
Selected.Deselect();
#if CLIENT
GameMain.ParticleManager.ClearParticles();
GUIContextMenu.CurrentContextMenu = null;
GUI.ClearCursorWait();
//make sure any textbox in the previously selected screen doesn't stay selected
@@ -290,6 +290,8 @@ namespace Barotrauma
{ InputType.CrewOrders, Keys.C },
{ InputType.Voice, Keys.V },
{ InputType.RadioVoice, Keys.None },
{ InputType.LocalVoice, Keys.None },
{ InputType.ToggleChatMode, Keys.R },
{ InputType.Command, MouseButton.MiddleMouse },
{ InputType.PreviousFireMode, MouseButton.MouseWheelDown },
@@ -333,16 +335,15 @@ namespace Barotrauma
}
bool playerConfigContainsNewChatBinds = false;
bool playerConfigContainsRestoredVoipBinds = false;
foreach (XElement element in elements)
{
foreach (XAttribute attribute in element.Attributes())
{
if (Enum.TryParse(attribute.Name.LocalName, out InputType result))
{
if (!playerConfigContainsNewChatBinds)
{
playerConfigContainsNewChatBinds = result == InputType.ActiveChat;
}
playerConfigContainsNewChatBinds |= result == InputType.ActiveChat;
playerConfigContainsRestoredVoipBinds |= result == InputType.RadioVoice;
bindings[result] = element.GetAttributeKeyOrMouse(attribute.Name.LocalName, bindings[result]);
}
}
@@ -351,14 +352,15 @@ namespace Barotrauma
// Clear the old chat binds for configs saved before the introduction of the new chat binds
if (!playerConfigContainsNewChatBinds)
{
if (bindings.ContainsKey(InputType.Chat))
{
bindings[InputType.Chat] = Keys.None;
}
if (bindings.ContainsKey(InputType.RadioChat))
{
bindings[InputType.RadioChat] = Keys.None;
}
bindings[InputType.Chat] = Keys.None;
bindings[InputType.RadioChat] = Keys.None;
}
// Clear old VOIP binds to make sure we have no overlapping binds
if (!playerConfigContainsRestoredVoipBinds)
{
bindings[InputType.LocalVoice] = Keys.None;
bindings[InputType.RadioVoice] = Keys.None;
}
Bindings = bindings.ToImmutableDictionary();
@@ -511,6 +513,7 @@ namespace Barotrauma
if (hudScaleChanged)
{
HUDLayoutSettings.CreateAreas();
GameMain.GameSession?.HUDScaleChanged();
}
GameMain.SoundManager?.ApplySettings();
@@ -264,12 +264,36 @@ namespace Barotrauma
public string Name => $"Character Spawn Info ({SpeciesName})";
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
[Serialize(false, IsPropertySaveable.No)]
public bool TransferBuffs { get; private set; }
[Serialize(false, IsPropertySaveable.No)]
public bool TransferAfflictions { get; private set; }
[Serialize(false, IsPropertySaveable.No)]
public bool TransferInventory { get; private set; }
[Serialize("", IsPropertySaveable.No)]
public Identifier SpeciesName { get; private set; }
[Serialize(1, IsPropertySaveable.No)]
public int Count { get; private set; }
[Serialize(0, IsPropertySaveable.No)]
public int Stun { get; private set; }
[Serialize("", IsPropertySaveable.No)]
public Identifier AfflictionOnSpawn { get; private set; }
[Serialize(1, IsPropertySaveable.No)]
public int AfflictionStrength { get; private set; }
[Serialize(false, IsPropertySaveable.No)]
public bool TransferControl { get; private set; }
[Serialize(false, IsPropertySaveable.No)]
public bool RemovePreviousCharacter { get; private set; }
[Serialize(0f, IsPropertySaveable.No)]
public float Spread { get; private set; }
@@ -1596,6 +1620,64 @@ namespace Barotrauma
{
SwarmBehavior.CreateSwarm(characters.Cast<AICharacter>());
}
if (!characterSpawnInfo.AfflictionOnSpawn.IsEmpty)
{
if (!AfflictionPrefab.Prefabs.TryGet(characterSpawnInfo.AfflictionOnSpawn, out AfflictionPrefab afflictionPrefab))
{
DebugConsole.NewMessage($"Could not apply an affliction to the spawned character(s). No affliction with the identifier \"{characterSpawnInfo.AfflictionOnSpawn}\" found.", Color.Red);
return;
}
newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(characterSpawnInfo.AfflictionStrength));
}
if (characterSpawnInfo.Stun > 0)
{
newCharacter.SetStun(characterSpawnInfo.Stun);
}
foreach (var target in targets)
{
if (!(target is Character character)) { continue; }
if (characterSpawnInfo.TransferInventory && character.Inventory != null && newCharacter.Inventory != null)
{
if (character.Inventory.Capacity != newCharacter.Inventory.Capacity) { return; }
for (int i = 0; i < character.Inventory.Capacity && i < newCharacter.Inventory.Capacity; i++)
{
character.Inventory.GetItemsAt(i).ForEachMod(item => newCharacter.Inventory.TryPutItem(item, i, allowSwapping: true, allowCombine: false, user: null));
}
}
if (characterSpawnInfo.TransferBuffs || characterSpawnInfo.TransferAfflictions)
{
foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
{
if (!characterSpawnInfo.TransferAfflictions && characterSpawnInfo.TransferBuffs && affliction.Prefab.IsBuff)
{
newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, affliction.Prefab.Instantiate(affliction.Strength));
}
if (characterSpawnInfo.TransferAfflictions)
{
newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, affliction.Prefab.Instantiate(affliction.Strength));
}
}
}
if (i == characterSpawnInfo.Count) // Only perform the below actions if this is the last character being spawned.
{
if (characterSpawnInfo.TransferControl)
{
#if CLIENT
if (Character.Controlled == target)
{
Character.Controlled = newCharacter;
}
#elif SERVER
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character != target) { continue; }
GameMain.Server.SetClientCharacter(c, newCharacter);
}
#endif
}
if (characterSpawnInfo.RemovePreviousCharacter) { Entity.Spawner?.AddEntityToRemoveQueue(character); }
}
}
});
}
}
@@ -1897,7 +1979,7 @@ namespace Barotrauma
{
continue;
}
element.Parent.ApplyToProperty(target, property, n, CoroutineManager.UnscaledDeltaTime);
element.Parent.ApplyToProperty(target, property, n, CoroutineManager.DeltaTime);
}
foreach (Affliction affliction in element.Parent.Afflictions)