Unstable 0.1300.0.3

This commit is contained in:
Markus Isberg
2021-03-25 15:40:24 +02:00
parent 874616027b
commit 58c50a235d
136 changed files with 2486 additions and 1008 deletions
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -18,8 +19,11 @@ namespace Barotrauma
private readonly Alignment? cameraEndPos;
private readonly float? startZoom;
private readonly float? endZoom;
public readonly float Duration;
public readonly float WaitDuration;
public readonly float PanDuration;
public readonly bool FadeOut;
public readonly bool LosFadeIn;
private readonly CoroutineHandle updateCoroutine;
@@ -28,10 +32,12 @@ namespace Barotrauma
public bool AllowInterrupt = false;
public bool RemoveControlFromCharacter = true;
public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, float duration = 10.0f, float? startZoom = null, float? endZoom = null)
public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null)
{
Duration = duration;
WaitDuration = waitDuration;
PanDuration = panDuration;
FadeOut = fadeOut;
LosFadeIn = losFadeIn;
this.cameraStartPos = cameraStartPos;
this.cameraEndPos = cameraEndPos;
this.startZoom = startZoom;
@@ -77,9 +83,12 @@ namespace Barotrauma
Vector2 initialCameraPos = cam.Position;
Vector2? initialTargetPos = targetEntity?.WorldPosition;
float timer = 0.0f;
while (timer < Duration)
float timer = -WaitDuration;
while (timer < PanDuration)
{
float clampedTimer = Math.Max(timer, 0f);
if (Screen.Selected != GameMain.GameScreen)
{
yield return new WaitForSeconds(0.1f);
@@ -136,14 +145,20 @@ namespace Barotrauma
MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
prevControlled?.WorldPosition ?? targetEntity.WorldPosition;
Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, timer / Duration);
Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration);
cam.Translate(cameraPos - cam.Position);
#if CLIENT
cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, timer / Duration);
if (timer / Duration > 0.9f)
cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, clampedTimer / PanDuration);
if (clampedTimer / PanDuration > 0.9f)
{
if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / Duration) - 0.9f) * 10.0f); }
if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((clampedTimer / PanDuration) - 0.9f) * 10.0f); }
}
if (LosFadeIn && clampedTimer / PanDuration > 0.8f)
{
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f;
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
GameMain.LightManager.LosEnabled = true;
}
#endif
timer += CoroutineManager.UnscaledDeltaTime;
@@ -158,6 +173,7 @@ namespace Barotrauma
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
#endif
if (prevControlled != null && !prevControlled.Removed)
@@ -172,6 +172,15 @@ namespace Barotrauma
}
}
/// <summary>
/// The monster won't try to damage these submarines
/// </summary>
public HashSet<Submarine> UnattackableSubmarines
{
get;
private set;
} = new HashSet<Submarine>();
public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack);
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter);
@@ -2150,7 +2159,13 @@ namespace Barotrauma
else
{
// Ignore all structures, items, and hulls inside wrecks and beacons
if (aiTarget.Entity.Submarine != null && (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon)) { continue; }
if (aiTarget.Entity.Submarine != null)
{
if (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon || UnattackableSubmarines.Contains(aiTarget.Entity.Submarine))
{
continue;
}
}
if (aiTarget.Entity is Hull hull)
{
// Ignore the target if it's a room and the character is already inside a sub
@@ -2439,13 +2454,23 @@ namespace Barotrauma
}
}
}
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid)
// Don't target characters that are outside of the allowed zone, unless chasing or escaping.
switch (targetParams.State)
{
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
{
continue;
}
case AIState.Escape:
case AIState.Avoid:
break;
default:
if (targetParams.State == AIState.Attack)
{
if (State == targetParams.State && SelectedAiTarget == aiTarget) { break; }
}
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
{
continue;
}
break;
}
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist);
@@ -1012,11 +1012,6 @@ namespace Barotrauma
}
else
{
bool allowOffensive = HasItem(attacker, "handlocker", out _, requireEquipped: true);
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
{
cumulativeDamage = 100;
}
// Don't react to minor (accidental) dmg done by characters that are in the same team
if (cumulativeDamage < 10)
{
@@ -1027,7 +1022,7 @@ namespace Barotrauma
}
else
{
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 20, allowOffensive: allowOffensive), attacker, GetReactionTime() * 2);
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 50), attacker, GetReactionTime() * 2);
}
}
}
@@ -1056,7 +1051,7 @@ namespace Barotrauma
if (!otherHumanAI.IsFriendly(Character)) { continue; }
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
if (!isWitnessing && !CheckReportRange(Character, otherCharacter, ReportRange)) { continue; }
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing);
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing, dmgThreshold: attacker.TeamID == Character.TeamID ? 50 : 10);
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
}
@@ -1099,6 +1094,15 @@ namespace Barotrauma
}
else
{
if (c.AIController is HumanAIController humanAI && humanAI.ObjectiveManager.GetActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
{
// Already targeting the attacker -> treat as a more serious threat.
cumulativeDamage *= 2;
}
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
{
cumulativeDamage = 100;
}
if (cumulativeDamage > dmgThreshold)
{
if (c.IsSecurity)
@@ -1154,7 +1158,7 @@ namespace Barotrauma
HoldPosition =
Character.Info?.Job?.Prefab.Identifier == "watchman" ||
Character.CurrentHull == null ||
Character.IsOnPlayerTeam && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
abortCondition = abortCondition,
allowHoldFire = allowHoldFire,
};
@@ -1841,15 +1845,14 @@ namespace Barotrauma
if (character == null) { continue; }
if (c == character) { continue; }
if (c.IsDead || c.IsIncapacitated) { continue; }
if (c.SelectedConstruction != target.Item) { continue; }
if (!IsFriendly(character, c, onlySameTeam: true)) { continue; }
operatingCharacter = c;
// If the other character is player, don't try to operate
if (c.IsPlayer) { return true; }
if (c.AIController is HumanAIController controllingHumanAi)
{
Item otherTarget = controllingHumanAi.objectiveManager.GetActiveObjective<AIObjectiveOperateItem>()?.Component.Item ?? c.SelectedConstruction;
if (otherTarget != target.Item) { continue; }
// If the other character is player, don't try to operate
if (c.IsPlayer) { return true; }
// If the other character is ordered to operate the item, let him do it
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
{
@@ -1874,8 +1877,7 @@ namespace Barotrauma
}
else
{
// Shouldn't go here, unless we allow non-humans to operate items
return false;
return c.SelectedConstruction == target.Item;
}
}
@@ -178,7 +178,7 @@ namespace Barotrauma
{
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f &&
speaker?.CurrentHull != null &&
speaker.TeamID == CharacterTeamType.FriendlyNPC &&
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
{
currentFlags.Add("EnterOutpost");
@@ -188,6 +188,11 @@ namespace Barotrauma
{
currentFlags.Add("Casual");
}
if (GameMain.GameSession.IsCurrentLocationRadiated())
{
currentFlags.Add("InRadiation");
}
}
if (speaker != null)
@@ -50,6 +50,11 @@ namespace Barotrauma
float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
float max = AIObjectiveManager.LowestOrderPriority - reduction;
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
if (decontainObjective == null)
{
// Halve the priority until there's a decontain objective (a valid container was found).
Priority /= 2;
}
}
return Priority;
}
@@ -126,5 +131,13 @@ namespace Barotrauma
itemIndex = 0;
decontainObjective = null;
}
public void DropTarget()
{
if (item != null && character.HasItem(item))
{
item.Drop(character);
}
}
}
}
@@ -2,6 +2,7 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Barotrauma
{
@@ -29,7 +30,21 @@ namespace Barotrauma
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
}
protected override float TargetEvaluation() => Targets.Any() ? (objectiveManager.IsOrder(this) ? objectiveManager.GetOrderPriority(this) : AIObjectiveManager.RunPriority - 1) : 0;
protected override float TargetEvaluation()
{
if (Targets.None()) { return 0; }
if (objectiveManager.IsOrder(this))
{
float prio = objectiveManager.GetOrderPriority(this);
if (subObjectives.All(so => so.SubObjectives.None()))
{
// If none of the subobjectives have subobjectives, no valid container was found. In this case, let's reduce the priority below the run threshold.
prio = Math.Min(prio, AIObjectiveManager.RunPriority - 1);
}
return prio;
}
return AIObjectiveManager.RunPriority - 0.5f;
}
protected override bool Filter(Item target)
{
@@ -65,10 +80,10 @@ namespace Barotrauma
return true;
}
public static bool IsValidContainer(Item item, Character character) =>
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
public static bool IsValidContainer(Item item, Character character, bool allowUnloading = true) =>
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && allowUnloading && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
public static bool IsValidTarget(Item item, Character character, bool checkInventory)
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
{
if (item == null) { return false; }
if (item.IgnoreByAI) { return false; }
@@ -76,7 +91,7 @@ namespace Barotrauma
if (item.SpawnedInOutpost) { return false; }
if (item.ParentInventory != null)
{
if (item.Container == null || !IsValidContainer(item.Container, character)) { return false; }
if (item.Container == null || !IsValidContainer(item.Container, character, allowUnloading)) { return false; }
}
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
var pickable = item.GetComponent<Pickable>();
@@ -127,5 +142,17 @@ namespace Barotrauma
}
return canEquip;
}
public override void OnDeselected()
{
base.OnDeselected();
foreach (var subObjective in SubObjectives)
{
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
{
cleanUpObjective.DropTarget();
}
}
}
}
}
@@ -457,7 +457,7 @@ namespace Barotrauma
priority /= 2;
}
}
if (Enemy.Stun > 1)
if (Enemy.IsKnockedDown)
{
// Enemy is stunned, reduce the priority of stunner weapons.
Attack attack = GetAttackDefinition(weapon);
@@ -730,11 +730,12 @@ namespace Barotrauma
{
IgnoreIfTargetDead = true,
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = Enemy.DisplayName
TargetName = Enemy.DisplayName,
AlwaysUseEuclideanDistance = false
},
onAbandon: () => Abandon = true);
if (followTargetObjective == null) { return; }
if (Mode == CombatMode.Arrest && Enemy.Stun > 2)
if (Mode == CombatMode.Arrest && (Enemy.Stun > 1 || Enemy.IsKnockedDown))
{
if (HumanAIController.HasItem(character, "handlocker", out _))
{
@@ -742,8 +743,8 @@ namespace Barotrauma
{
arrestingRegistered = true;
followTargetObjective.Completed += OnArrestTargetReached;
followTargetObjective.CloseEnough = 100;
}
followTargetObjective.CloseEnough = 100;
}
else
{
@@ -759,7 +760,7 @@ namespace Barotrauma
SteeringManager.Reset();
}
}
if (followTargetObjective != null)
if (!arrestingRegistered && followTargetObjective != null)
{
followTargetObjective.CloseEnough =
WeaponComponent is RangedWeapon ? 1000 :
@@ -782,7 +783,7 @@ namespace Barotrauma
private void OnArrestTargetReached()
{
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
{
var handCuffs = matchingItems.First();
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
@@ -802,8 +803,8 @@ namespace Barotrauma
}
}
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
IsCompleted = true;
}
IsCompleted = true;
}
/// <summary>
@@ -937,7 +938,14 @@ namespace Barotrauma
return;
}
if (reloadTimer > 0) { return; }
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
if (Mode == CombatMode.Arrest)
{
// If the target is arrested or if it's stunned and we can't lock the target up, consider the objective done.
if (Enemy.IsKnockedDown && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false) || HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true))
{
IsCompleted = true;
}
}
if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
if (WeaponComponent is MeleeWeapon meleeWeapon)
@@ -1017,6 +1025,8 @@ namespace Barotrauma
private void UseWeapon(float deltaTime)
{
// Never allow to attack characters with deadly weapons while trying to arrest.
if (Mode == CombatMode.Arrest && isLethalWeapon) { return; }
float reloadTime = 0;
if (WeaponComponent is RangedWeapon rangedWeapon)
{
@@ -1038,10 +1048,16 @@ namespace Barotrauma
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
}
private bool ShouldUnequipWeapon =>
Weapon != null &&
character.Submarine != null &&
character.Submarine.TeamID == character.TeamID &&
Character.CharacterList.None(c => c.Submarine == character.Submarine && HumanAIController.IsActive(c) && !HumanAIController.IsFriendly(character, c) && HumanAIController.VisibleHulls.Contains(c.CurrentHull));
protected override void OnCompleted()
{
base.OnCompleted();
if (Weapon != null)
if (ShouldUnequipWeapon)
{
Unequip();
}
@@ -1051,7 +1067,7 @@ namespace Barotrauma
protected override void OnAbandon()
{
base.OnAbandon();
if (Weapon != null)
if (ShouldUnequipWeapon)
{
Unequip();
}
@@ -38,7 +38,7 @@ namespace Barotrauma
Priority = 0;
Abandon = true;
}
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
{
Priority = 0;
Abandon = true;
@@ -10,6 +10,8 @@ namespace Barotrauma
{
public override string DebugTag => "get item";
public override bool AbandonWhenCannotCompleteSubjectives => false;
private readonly bool equip;
public HashSet<Item> ignoredItems = new HashSet<Item>();
@@ -225,18 +227,8 @@ namespace Barotrauma
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
{
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
abortCondition = obj =>
{
bool abort = targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget;
if (abort)
{
// Fail silently if someone takes the suit.
obj.speakIfFails = false;
}
return abort;
},
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
abortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
SpeakIfFails = false
};
},
onAbandon: () =>
@@ -263,13 +255,18 @@ namespace Barotrauma
if (targetItem == null)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item was defined.", Color.Red);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
#endif
Abandon = true;
}
return;
}
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.followControlledCharacter);
bool hasCalledPathFinder = false;
int itemsPerFrame = (int)priority;
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
@@ -310,8 +307,18 @@ namespace Barotrauma
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
itemPriority *= distanceFactor;
itemPriority *= item.Condition / item.MaxCondition;
//ignore if the item has a lower priority than the currently selected one
// Ignore if the item has a lower priority than the currently selected one
if (itemPriority < currItemPriority) { continue; }
if (!hasCalledPathFinder && PathSteering != null && checkPath)
{
// While following the player, let's ensure that there's a valid path to the target before accepting it.
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
// Only allow one path find call per frame.
hasCalledPathFinder = true;
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable) { continue; }
}
currItemPriority = itemPriority;
targetItem = item;
moveToTarget = rootInventoryOwner ?? item;
@@ -326,7 +333,7 @@ namespace Barotrauma
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
#endif
Abandon = true;
}
@@ -345,7 +352,7 @@ namespace Barotrauma
else
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
#endif
Abandon = true;
}
@@ -393,11 +400,30 @@ namespace Barotrauma
/// </summary>
private void ResetInternal()
{
goToObjective = null;
RemoveSubObjective(ref goToObjective);
targetItem = originalTarget;
moveToTarget = targetItem?.GetRootInventoryOwner();
isDoneSeeking = false;
currSearchIndex = 0;
currItemPriority = 0;
}
protected override void OnAbandon()
{
base.OnAbandon();
if (moveToTarget == null) { return; }
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
#endif
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null)
{
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
if (msg != null)
{
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
}
}
}
}
}
@@ -30,7 +30,7 @@ namespace Barotrauma
public bool followControlledCharacter;
public bool mimic;
public bool speakIfFails = true;
public bool SpeakIfFails { get; set; } = true;
public float extraDistanceWhileSwimming;
public float extraDistanceOutsideSub;
@@ -67,6 +67,8 @@ namespace Barotrauma
public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; }
public bool AlwaysUseEuclideanDistance { get; set; } = true;
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
public override bool AllowOutsideSubmarine => AllowGoingOutside;
@@ -88,12 +90,7 @@ namespace Barotrauma
Abandon = !isOrder;
return Priority;
}
if (followControlledCharacter && Character.Controlled == null)
{
Priority = 0;
Abandon = !isOrder;
}
if (Target is Entity e && e.Removed)
if (Target == null || Target is Entity e && e.Removed)
{
Priority = 0;
Abandon = !isOrder;
@@ -147,11 +144,10 @@ namespace Barotrauma
private void SpeakCannotReach()
{
if (!character.IsOnPlayerTeam) { return; }
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
#endif
if (objectiveManager.HasOrders() && DialogueIdentifier != null && speakIfFails)
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null && DialogueIdentifier != null && SpeakIfFails)
{
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
if (msg != null)
@@ -165,12 +161,15 @@ namespace Barotrauma
{
if (followControlledCharacter)
{
if (Character.Controlled == null || !HumanAIController.IsFriendly(Character.Controlled))
if (Character.Controlled != null && HumanAIController.IsFriendly(Character.Controlled))
{
Target = Character.Controlled;
}
if (Target == null)
{
Abandon = true;
return;
}
Target = Character.Controlled;
}
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
{
@@ -260,6 +259,7 @@ namespace Barotrauma
}
if (needsEquipment)
{
SteeringManager.Reset();
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
@@ -288,9 +288,14 @@ namespace Barotrauma
}
}
}
float maxGapDistance = 500;
Character targetCharacter = Target as Character;
if (character.AnimController.InWater)
{
if (character.CurrentHull == null)
if (character.CurrentHull == null ||
followControlledCharacter &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{
if (seekGapsTimer > 0)
{
@@ -298,7 +303,7 @@ namespace Barotrauma
}
else
{
SeekGaps(maxDistance: 500);
SeekGaps(maxGapDistance);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
if (TargetGap != null)
{
@@ -327,7 +332,7 @@ namespace Barotrauma
}
if (TargetGap != null)
{
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, TargetGap.FlowTargetHull.WorldPosition, deltaTime))
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, followControlledCharacter ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
@@ -347,7 +352,7 @@ namespace Barotrauma
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
(targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
// Currently equipped scooter
@@ -528,17 +533,24 @@ namespace Barotrauma
{
Gap selectedGap = null;
float selectedDistance = -1;
Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition);
foreach (Gap gap in Gap.GapList)
{
if (gap.Open < 1) { continue; }
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition);
if (distance > maxDistance * maxDistance) { continue; }
if (selectedGap == null || distance < selectedDistance)
if (gap.Submarine == null) { continue; }
if (!followControlledCharacter)
{
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
}
Vector2 toGap = gap.WorldPosition - character.WorldPosition;
if (Vector2.Dot(Vector2.Normalize(toGap), toTargetNormalized) < 0) { continue; }
float squaredDistance = toGap.LengthSquared();
if (squaredDistance > maxDistance * maxDistance) { continue; }
if (selectedGap == null || squaredDistance < selectedDistance)
{
selectedGap = gap;
selectedDistance = distance;
selectedDistance = squaredDistance;
}
}
TargetGap = selectedGap;
@@ -555,7 +567,7 @@ namespace Barotrauma
//otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; }
}
if (!character.AnimController.InWater)
if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
{
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
if (yDiff > CloseEnough) { return false; }
@@ -495,7 +495,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true) && !ignoredItems.Contains(item))
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
{
itemsToClean.Add(item);
}
@@ -540,5 +540,17 @@ namespace Barotrauma
ignoredItems.Clear();
autonomousObjectiveRetryTimer = 10;
}
public override void OnDeselected()
{
base.OnDeselected();
foreach (var subObjective in SubObjectives)
{
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
{
cleanUpObjective.DropTarget();
}
}
}
}
}
@@ -445,7 +445,7 @@ namespace Barotrauma
extraDistanceWhileSwimming = 100,
AllowGoingOutside = true,
IgnoreIfTargetDead = true,
followControlledCharacter = orderGiver == character,
followControlledCharacter = true,
mimic = true,
DialogueIdentifier = "dialogcannotreachplace"
};
@@ -69,10 +69,9 @@ namespace Barotrauma
{
if (!isOrder)
{
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC ||
HumanAIController.IsTrueForAnyCrewMember(c =>
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC)
{
// The reactor was previously operated by a player -> ignore.
Priority = 0;
return Priority;
}
@@ -89,7 +88,6 @@ namespace Barotrauma
case "powerup":
// Check that we don't already have another order that is targeting the same item.
// Without this the autonomous objective will tell the bot to turn the reactor on again.
if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective)))
{
Priority = 0;
@@ -177,9 +175,9 @@ namespace Barotrauma
}
if (operateTarget != null)
{
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
{
// Another crew member is already targeting this entity.
// Another crew member is already targeting this entity (leak).
Abandon = true;
return;
}
@@ -471,13 +471,15 @@ namespace Barotrauma
}
}
private double pressureProtectionLastSet;
private float pressureProtection;
public float PressureProtection
{
get { return pressureProtection; }
set
{
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f);
pressureProtection = Math.Max(value, 0.0f);
pressureProtectionLastSet = Timing.TotalTime;
}
}
@@ -528,11 +530,10 @@ namespace Barotrauma
public float Stun
{
get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; }
get { return IsRagdolled ? 1.0f : CharacterHealth.Stun; }
set
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
SetStun(value, true);
}
}
@@ -697,7 +698,7 @@ namespace Barotrauma
{
if (!canBeDragged) { return false; }
if (Removed || !AnimController.Draggable) { return false; }
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated || IsPet;
return IsKnockedDown || LockHands || IsPet;
}
set { canBeDragged = value; }
}
@@ -715,7 +716,7 @@ namespace Barotrauma
}
else
{
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated;
return IsKnockedDown || LockHands;
}
}
set { canInventoryBeAccessed = value; }
@@ -1016,7 +1017,7 @@ namespace Barotrauma
{
// Get the non husked name and find the ragdoll with it
var matchingAffliction = AfflictionPrefab.List
.Where(p => p.AfflictionType == "huskinfection")
.Where(p => p is AfflictionPrefabHusk)
.Select(p => p as AfflictionPrefabHusk)
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase)));
string nonHuskedSpeciesName = string.Empty;
@@ -1052,7 +1053,7 @@ namespace Barotrauma
else
{
AnimController = new FishAnimController(this, seed, ragdollParams as FishRagdollParams);
PressureProtection = 100.0f;
PressureProtection = int.MaxValue;
}
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
@@ -1271,7 +1272,13 @@ namespace Barotrauma
public float GetSkillLevel(string skillIdentifier)
{
return (Info == null || Info.Job == null) ? 0.0f : Info.Job.GetSkillLevel(skillIdentifier);
if (Info?.Job == null) { return 0.0f; }
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
{
skillLevel *= affliction.GetSkillMultiplier();
}
return skillLevel;
}
// TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner
@@ -2466,11 +2473,8 @@ namespace Barotrauma
if (NeedsAir)
{
bool protectedFromPressure = PressureProtection > 0.0f;
//cannot be protected from pressure when below crush depth
protectedFromPressure = protectedFromPressure && WorldPosition.Y > CharacterHealth.CrushDepth;
//implode if not protected from pressure, and either outside or in a high-pressure hull
if (!protectedFromPressure &&
if (!IsProtectedFromPressure() &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f))
{
if (CharacterHealth.PressureKillDelay <= 0.0f)
@@ -2656,7 +2660,10 @@ namespace Barotrauma
{
if (NeedsAir)
{
PressureProtection -= deltaTime * 100.0f;
if (Timing.TotalTime > pressureProtectionLastSet + 0.1)
{
PressureProtection = 0.0f;
}
}
if (NeedsWater)
{
@@ -3281,7 +3288,7 @@ namespace Barotrauma
GameMain.Config.RecentlyEncounteredCreatures.Add(other.SpeciesName);
}
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1)
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true)
{
if (Removed) { return new AttackResult(); }
@@ -3327,7 +3334,7 @@ namespace Barotrauma
bool wasDead = IsDead;
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier);
CharacterHealth.ApplyDamage(hitLimb, attackResult);
CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking);
if (attacker != this)
{
OnAttacked?.Invoke(attacker, attackResult);
@@ -3382,6 +3389,12 @@ namespace Barotrauma
}
}
/// <summary>
/// Is the character knocked down regardless whether the technical state is dead, unconcious, paralyzed, or stunned.
/// With stunning, the parameter uses a half a second delay before the character is treated as knocked down. The purpose of this is to ignore minor stunning. If you don't want to to ignore any stun, use the Stun property.
/// </summary>
public bool IsKnockedDown => IsDead || IsIncapacitated || CharacterHealth.StunTimer > 0.5f;
public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
@@ -3391,7 +3404,7 @@ namespace Barotrauma
{
AnimController.ResetPullJoints();
}
CharacterHealth.StunTimer = newStun;
CharacterHealth.Stun = newStun;
if (newStun > 0.0f)
{
SelectedConstruction = null;
@@ -3964,5 +3977,10 @@ namespace Barotrauma
public bool IsWatchman => HasJob("watchman");
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
public bool IsProtectedFromPressure()
{
return PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 0.0f);
}
}
}
@@ -961,6 +961,20 @@ namespace Barotrauma
public void Rename(string newName)
{
if (string.IsNullOrEmpty(newName)) { return; }
// Replace the name tag of any existing id cards or duffel bags
foreach (var item in Item.ItemList)
{
if (item.Prefab.Identifier != "idcard" && !item.Tags.Contains("despawncontainer")) { continue; }
foreach (var tag in item.Tags.Split(','))
{
var splitTag = tag.Split(":");
if (splitTag.Length < 2) { continue; }
if (splitTag[0] != "name") { continue; }
if (splitTag[1] != Name) { continue; }
item.ReplaceTag(tag, $"name:{newName}");
break;
}
}
Name = newName;
}
@@ -14,6 +14,9 @@ namespace Barotrauma
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
public float PendingAdditionStrenght { get; set; }
public float AdditionStrength { get; set; }
protected float _strength;
[Serialize(0f, true), Editable]
@@ -26,7 +29,12 @@ namespace Barotrauma
{
_nonClampedStrength = value;
}
_strength = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength);
float newValue = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength);
if (newValue > _strength)
{
PendingAdditionStrenght = Prefab.GrainBurst;
}
_strength = newValue;
}
}
@@ -56,6 +64,7 @@ namespace Barotrauma
public Affliction(AfflictionPrefab prefab, float strength)
{
Prefab = prefab;
PendingAdditionStrenght = Prefab.GrainBurst;
_strength = strength;
Identifier = prefab?.Identifier;
@@ -109,18 +118,25 @@ namespace Barotrauma
if (currentEffect == null) { return 0.0f; }
if (MathUtils.NearlyEqual(currentEffect.MaxGrainStrength, 0f)) { return 0.0f; }
return MathHelper.Lerp(
float amount = MathHelper.Lerp(
currentEffect.MinGrainStrength,
currentEffect.MaxGrainStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
{
return AdditionStrength;
}
return amount;
}
public float GetScreenDistortStrength()
{
if (Strength < Prefab.ActivationThreshold) return 0.0f;
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f;
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength <= 0.0f) return 0.0f;
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinScreenDistortStrength,
@@ -130,10 +146,10 @@ namespace Barotrauma
public float GetRadialDistortStrength()
{
if (Strength < Prefab.ActivationThreshold) return 0.0f;
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f;
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength <= 0.0f) return 0.0f;
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinRadialDistortStrength,
@@ -143,10 +159,10 @@ namespace Barotrauma
public float GetChromaticAberrationStrength()
{
if (Strength < Prefab.ActivationThreshold) return 0.0f;
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f;
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength <= 0.0f) return 0.0f;
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinChromaticAberrationStrength,
@@ -156,10 +172,10 @@ namespace Barotrauma
public float GetScreenBlurStrength()
{
if (Strength < Prefab.ActivationThreshold) return 0.0f;
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f;
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength <= 0.0f) return 0.0f;
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinScreenBlurStrength,
@@ -167,6 +183,20 @@ namespace Barotrauma
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
}
public float GetSkillMultiplier()
{
if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) { return 1.0f; }
float amount = MathHelper.Lerp(
currentEffect.MinSkillMultiplier,
currentEffect.MaxSkillMultiplier,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
return amount;
}
public void CalculateDamagePerSecond(float currentVitalityDecrease)
{
DamagePerSecond = Math.Max(DamagePerSecond, currentVitalityDecrease - PreviousVitalityDecrease);
@@ -245,6 +275,21 @@ namespace Barotrauma
{
ApplyStatusEffect(statusEffect, deltaTime, characterHealth, targetLimb);
}
float amount = deltaTime;
if (Prefab.GrainBurst > 0)
{
amount /= Prefab.GrainBurst;
}
if (PendingAdditionStrenght >= 0)
{
AdditionStrength += amount;
PendingAdditionStrenght -= deltaTime;
}
else if (AdditionStrength > 0)
{
AdditionStrength -= amount;
}
}
public void ApplyStatusEffect(StatusEffect statusEffect, float deltaTime, CharacterHealth characterHealth, Limb targetLimb)
@@ -267,7 +312,7 @@ namespace Barotrauma
{
var targets = new List<ISerializableEntity>();
statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets);
statusEffect.Apply(ActionType.OnActive, deltaTime, targetLimb.character, targets);
statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, targets);
}
}
@@ -134,6 +134,8 @@ namespace Barotrauma
public float MinSpeedMultiplier, MaxSpeedMultiplier;
public float MinBuffMultiplier, MaxBuffMultiplier;
public float MinSkillMultiplier, MaxSkillMultiplier;
public float MinResistance, MaxResistance;
public string ResistanceFor;
public string DialogFlag;
@@ -172,6 +174,9 @@ namespace Barotrauma
MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f);
MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength);
MinSkillMultiplier = element.GetAttributeFloat("minskillmultiplier", 1.0f);
MaxSkillMultiplier = element.GetAttributeFloat("maxskillmultiplier", 1.0f);
ResistanceFor = element.GetAttributeString("resistancefor", "");
MinResistance = element.GetAttributeFloat("minresistance", 0.0f);
MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f);
@@ -292,6 +297,8 @@ namespace Barotrauma
public readonly float ShowIconToOthersThreshold = 0.05f;
public readonly float MaxStrength = 100.0f;
public readonly float GrainBurst;
//how high the strength has to be for the affliction icon to be shown with a health scanner
public readonly float ShowInHealthScannerThreshold = 0.05f;
@@ -451,6 +458,7 @@ namespace Barotrauma
prefab = new AfflictionPrefab(sourceElement, file.Path, typeof(AfflictionBleeding));
break;
case "huskinfection":
case "alieninfection":
prefab = new AfflictionPrefabHusk(sourceElement, file.Path, typeof(AfflictionHusk));
break;
case "cprsettings":
@@ -521,7 +529,7 @@ namespace Barotrauma
if (prefab != null)
{
loadedAfflictions.Add((prefab, element));
loadedAfflictions.Add((prefab, sourceElement));
Prefabs.Add(prefab, isOverride);
prefab.CalculatePrefabUIntIdentifier(Prefabs);
}
@@ -582,6 +590,7 @@ namespace Barotrauma
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLower(), 0.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
@@ -190,12 +190,14 @@ namespace Barotrauma
set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public float StunTimer
public float Stun
{
get { return stunAffliction.Strength; }
set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); }
}
public float StunTimer { get; private set; }
public Affliction PressureAffliction
{
get { return pressureAffliction; }
@@ -488,7 +490,7 @@ namespace Barotrauma
CalculateVitality();
}
public void ApplyDamage(Limb hitLimb, AttackResult attackResult)
public void ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking = true)
{
if (Unkillable || Character.GodMode) { return; }
if (hitLimb.HealthIndex < 0 || hitLimb.HealthIndex >= limbHealths.Count)
@@ -502,11 +504,11 @@ namespace Barotrauma
{
if (newAffliction.Prefab.LimbSpecific)
{
AddLimbAffliction(hitLimb, newAffliction);
AddLimbAffliction(hitLimb, newAffliction, allowStacking);
}
else
{
AddAffliction(newAffliction);
AddAffliction(newAffliction, allowStacking);
}
}
}
@@ -573,7 +575,7 @@ namespace Barotrauma
CalculateVitality();
}
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
private void AddLimbAffliction(Limb limb, Affliction newAffliction, bool allowStacking = true)
{
if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; }
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
@@ -582,10 +584,10 @@ namespace Barotrauma
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
return;
}
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction);
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction, allowStacking);
}
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction)
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true)
{
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
@@ -594,7 +596,15 @@ namespace Barotrauma
{
if (newAffliction.Prefab == affliction.Prefab)
{
affliction.Strength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
if (allowStacking)
{
// Add the existing strength
newStrength += affliction.Strength;
}
newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength);
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
affliction.Strength = newStrength;
affliction.Source = newAffliction.Source;
CalculateVitality();
if (Vitality <= MinVitality)
@@ -624,13 +634,12 @@ namespace Barotrauma
#endif
}
private void AddAffliction(Affliction newAffliction)
private void AddAffliction(Affliction newAffliction, bool allowStacking = true)
{
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (newAffliction.Prefab.AfflictionType == "huskinfection")
if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
{
var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk;
if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase)))
{
return;
@@ -640,7 +649,13 @@ namespace Barotrauma
{
if (newAffliction.Prefab == affliction.Prefab)
{
float newStrength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
if (allowStacking)
{
// Add the existing strength
newStrength += affliction.Strength;
}
newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength);
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
affliction.Strength = newStrength;
affliction.Source = newAffliction.Source;
@@ -676,6 +691,8 @@ namespace Barotrauma
{
UpdateOxygen(deltaTime);
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
for (int i = 0; i < limbHealths.Count; i++)
{
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
@@ -795,6 +812,13 @@ namespace Barotrauma
Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease);
}
#if CLIENT
if (IsUnconscious)
{
HintManager.OnCharacterUnconscious(Character);
}
#endif
}
private void Kill()
@@ -79,6 +79,9 @@ namespace Barotrauma
[Serialize(0f, true), Editable]
public float SonarDisruption { get; set; }
[Serialize(0f, true), Editable]
public float DistantSonarRange { get; set; }
[Serialize(25000f, true, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)]
public float DisableDistance { get; set; }
@@ -609,7 +609,7 @@ namespace Barotrauma
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
{
if (args.Length < 2) return;
if (args.Length < 2) { return; }
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a =>
a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
@@ -626,9 +626,19 @@ namespace Barotrauma
return;
}
Character targetCharacter = (args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray());
bool relativeStrength = false;
if (args.Length > 2)
{
bool.TryParse(args[2], out relativeStrength);
}
Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray());
if (targetCharacter != null)
{
if (relativeStrength)
{
afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength;
}
targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength));
}
},
@@ -734,19 +744,19 @@ namespace Barotrauma
List<EventPrefab> eventPrefabs = EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).ToList();
if (GameMain.GameSession?.EventManager != null && args.Length > 0)
{
EventPrefab newEvent = eventPrefabs.Find(prefab => string.Equals(prefab.Identifier, args[0], StringComparison.InvariantCultureIgnoreCase));
EventPrefab eventPrefab = eventPrefabs.Find(prefab => string.Equals(prefab.Identifier, args[0], StringComparison.InvariantCultureIgnoreCase));
if (newEvent != null)
if (eventPrefab != null)
{
var @event = newEvent.CreateInstance();
var newEvent = eventPrefab.CreateInstance();
if (newEvent == null)
{
NewMessage($"Could not initialize event {args[0]} because level did not meet requirements");
return;
}
GameMain.GameSession.EventManager.ActiveEvents.Add(@event);
@event.Init(true);
NewMessage($"Initialized event {newEvent.Identifier}", Color.Aqua);
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
newEvent.Init(true);
NewMessage($"Initialized event {eventPrefab.Identifier}", Color.Aqua);
return;
}
@@ -1002,7 +1012,7 @@ namespace Barotrauma
}
else
{
ThrowError("Could not set location reputation ({args[0]} is not a valid reputation value).");
ThrowError($"Could not set location reputation ({args[0]} is not a valid reputation value).");
}
}
else
@@ -1010,6 +1020,41 @@ namespace Barotrauma
ThrowError("Could not set location reputation (no active campaign).");
}
}, null, true));
commands.Add(new Command("setreputation", "setreputation [faction] [value]: Set the reputation of a cation to the specified value.", (string[] args) =>
{
if (args.Length < 2)
{
ThrowError("Insufficient arguments (expected 2)");
return;
}
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
if (campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase)) is { } faction)
{
if (float.TryParse(args[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation))
{
faction.Reputation.Value = reputation;
}
else
{
ThrowError($"Could not set faction reputation ({args[1]} is not a valid reputation value).");
}
}
else
{
ThrowError($"Could not set faction reputation (faction {args[0]} not found).");
}
}
else
{
ThrowError("Could not set faction reputation (no active campaign).");
}
}, () =>
{
return new[] { FactionPrefab.Prefabs.Select(f => f.Identifier).ToArray() };
}, true));
commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) =>
{
@@ -1133,6 +1178,56 @@ namespace Barotrauma
UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray()
};
}, true));
commands.Add(new Command("maxupgrades", "maxupgrades [category] [prefab]: Maxes out all upgrades or only specific one if given arguments.", args =>
{
UpgradeManager upgradeManager = GameMain.GameSession?.Campaign?.UpgradeManager;
if (upgradeManager == null)
{
ThrowError("This command can only be used in campaign.");
return;
}
string categoryIdentifier = null;
string prefabIdentifier = null;
switch (args.Length)
{
case 1:
categoryIdentifier = args[0];
break;
case 2:
categoryIdentifier = args[0];
prefabIdentifier = args[1];
break;
}
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
if (!string.IsNullOrWhiteSpace(categoryIdentifier) && !category.Identifier.Equals(categoryIdentifier, StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
{
if (!prefab.UpgradeCategories.Contains(category)) { continue; }
if (!string.IsNullOrWhiteSpace(prefabIdentifier) && !prefab.Identifier.Equals(prefabIdentifier, StringComparison.OrdinalIgnoreCase)) { continue; }
int targetLevel = prefab.MaxLevel - upgradeManager.GetRealUpgradeLevel(prefab, category);
for (int i = 0; i < targetLevel; i++)
{
upgradeManager.PurchaseUpgrade(prefab, category, force: true);
}
NewMessage($"Upgraded {category.Identifier}.{prefab.Identifier} by {targetLevel} levels.", Color.DarkGreen);
}
}
NewMessage($"Start a new round to apply the upgrades.", Color.Lime);
}, () =>
{
return new[]
{
UpgradeCategory.Categories.Select(c => c.Identifier).Distinct().ToArray(),
UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray()
};
}, true));
commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) =>
{
@@ -253,8 +253,8 @@ namespace Barotrauma
};
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false).ToList();
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false).ToList();
if (moduleFlags != null && moduleFlags.Any())
{
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList();
@@ -303,6 +303,12 @@ namespace Barotrauma
return potentialSpawnPoints.GetRandom();
}
//avoid using waypoints if there's any actual spawnpoints available
if (validSpawnPoints.Any(wp => wp.SpawnType != SpawnType.Path))
{
validSpawnPoints = validSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
}
//if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags
if (spawnpointTags == null || !spawnpointTags.Any())
{
@@ -6,12 +6,17 @@ namespace Barotrauma
{
class TagAction : EventAction
{
public enum SubType { Any= 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
[Serialize("", true)]
public string Criteria { get; set; }
[Serialize("", true)]
public string Tag { get; set; }
[Serialize(SubType.Any, true)]
public SubType SubmarineType { get; set; }
[Serialize(true, true)]
public bool IgnoreIncapacitatedCharacters { get; set; }
@@ -63,17 +68,37 @@ namespace Barotrauma
private void TagStructuresByIdentifier(string identifier)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
}
private void TagItemsByIdentifier(string identifier)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
}
private void TagItemsByTag(string tag)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.HasTag(tag));
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.HasTag(tag));
}
private bool SubmarineTypeMatches(Submarine sub)
{
if (SubmarineType == SubType.Any) { return true; }
if (sub == null) { return false; }
switch (sub.Info.Type)
{
case Barotrauma.SubmarineType.Player:
return SubmarineType.HasFlag(SubType.Player);
case Barotrauma.SubmarineType.Outpost:
case Barotrauma.SubmarineType.OutpostModule:
return SubmarineType.HasFlag(SubType.Outpost);
case Barotrauma.SubmarineType.Wreck:
return SubmarineType.HasFlag(SubType.Wreck);
case Barotrauma.SubmarineType.BeaconStation:
return SubmarineType.HasFlag(SubType.BeaconStation);
default:
return false;
}
}
public override void Update(float deltaTime)
@@ -113,7 +138,7 @@ namespace Barotrauma
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()})";
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()}, Sub: {SubmarineType.ColorizeObject()})";
}
}
}
@@ -134,7 +134,7 @@ namespace Barotrauma
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
{
//if the outpost is connected to a locked connection, create an event to unlock it
if (level.StartLocation?.Connections.Any(c => c.Locked) ?? false)
if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
{
var unlockPathPrefabs = EventSet.PrefabList.FindAll(e => e.UnlockPathEvent);
var unlockPathPrefabsForBiome = unlockPathPrefabs.FindAll(e =>
@@ -166,11 +166,14 @@ namespace Barotrauma
void AddChildEvents(EventSet eventSet)
{
if (eventSet == null) { return; }
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First))
if (eventSet.OncePerOutpost)
{
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First))
{
level.LevelData.NonRepeatableEvents.Add(ep);
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
{
level.LevelData.NonRepeatableEvents.Add(ep);
}
}
}
foreach (EventSet childSet in eventSet.ChildSets)
@@ -373,6 +376,8 @@ namespace Barotrauma
private void CreateEvents(EventSet eventSet, Random rand)
{
if (level == null) { return; }
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
int applyCount = 1;
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
if (eventSet.PerRuin)
@@ -86,6 +86,7 @@ namespace Barotrauma
public readonly bool IgnoreCoolDown;
public readonly bool PerRuin, PerCave, PerWreck;
public readonly bool DisableInHuntingGrounds;
public readonly bool OncePerOutpost;
@@ -142,9 +143,10 @@ namespace Barotrauma
PerRuin = element.GetAttributeBool("perruin", false);
PerCave = element.GetAttributeBool("percave", false);
PerWreck = element.GetAttributeBool("perwreck", false);
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
OncePerOutpost = element.GetAttributeBool("perwreck", false);
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
Commonness[""] = 1.0f;
@@ -54,10 +54,6 @@ namespace Barotrauma
requireRescue.Clear();
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
if (submarine.Info.Type == SubmarineType.Outpost)
{
submarine.TeamID = CharacterTeamType.None;
}
if (!IsClient)
{
InitCharacters(submarine);
@@ -148,6 +144,10 @@ namespace Barotrauma
}
humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos);
humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false);
if (spawnPos is WayPoint wp)
{
spawnedCharacter.GiveIdCardTags(wp);
}
if (element.GetAttributeBool("requirekill", false))
{
requireKill.Add(spawnedCharacter);
@@ -175,6 +175,15 @@ namespace Barotrauma
{
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
}
if (submarine != null && spawnedCharacter.AIController is EnemyAIController enemyAi)
{
enemyAi.UnattackableSubmarines.Add(submarine);
enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
foreach (Submarine sub in Submarine.MainSub.DockedTo)
{
enemyAi.UnattackableSubmarines.Add(sub);
}
}
}
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic;
namespace Barotrauma
@@ -99,18 +100,18 @@ namespace Barotrauma
}
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
subs[0].TeamID = CharacterTeamType.Team1; subs[1].TeamID = CharacterTeamType.Team2;
subs[0].NeutralizeBallast(); subs[1].NeutralizeBallast();
subs[0].NeutralizeBallast();
subs[0].TeamID = CharacterTeamType.Team1;
subs[0].DockedTo.ForEach(s => s.TeamID = CharacterTeamType.Team1);
subs[1].NeutralizeBallast();
subs[1].TeamID = CharacterTeamType.Team2;
subs[1].DockedTo.ForEach(s => s.TeamID = CharacterTeamType.Team2);
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
subs[1].FlipX();
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
foreach (Submarine submarine in Submarine.Loaded)
{
//hide all subs from sonar to make sneak attacks possible
submarine.ShowSonarMarker = false;
}
}
public override void End()
@@ -107,6 +107,11 @@ namespace Barotrauma
}
public readonly Location[] Locations;
public int? Difficulty
{
get { return Prefab.Difficulty; }
}
public Mission(MissionPrefab prefab, Location[] locations)
{
@@ -186,6 +191,9 @@ namespace Barotrauma
public void Start(Level level)
{
#if CLIENT
shownMessages.Clear();
#endif
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
{
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false))
@@ -72,6 +72,8 @@ namespace Barotrauma
public readonly List<Tuple<string, object, SetDataAction.OperationType>> DataRewards = new List<Tuple<string, object, SetDataAction.OperationType>>();
public readonly int Commonness;
public readonly int? Difficulty;
public const int MinDifficulty = 1, MaxDifficulty = 4;
public readonly int Reward;
@@ -156,6 +158,11 @@ namespace Barotrauma
AllowRetry = element.GetAttributeBool("allowretry", false);
IsSideObjective = element.GetAttributeBool("sideobjective", false);
Commonness = element.GetAttributeInt("commonness", 1);
if (element.GetAttribute("difficulty") != null)
{
int difficulty = element.GetAttributeInt("difficulty", MinDifficulty);
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
}
SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully");
FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? "";
@@ -228,7 +228,7 @@ namespace Barotrauma
}
GiveReward();
completed = true;
if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase)))
if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase) || t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)))
{
level.LevelData.HasHuntingGrounds = false;
}
@@ -310,7 +310,7 @@ namespace Barotrauma
{
foreach (Character npc in Character.CharacterList)
{
if (npc.TeamID != CharacterTeamType.FriendlyNPC || npc.CurrentHull == null || npc.IsIncapacitated) { continue; }
if ((npc.TeamID != CharacterTeamType.FriendlyNPC && npc.TeamID != CharacterTeamType.None) || npc.CurrentHull == null || npc.IsIncapacitated) { continue; }
if (npc.AIController is HumanAIController humanAI && (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()))
{
continue;
@@ -327,10 +327,12 @@ namespace Barotrauma
{
if (npc.TeamID == CharacterTeamType.None)
{
dialogFlags.Remove("OutpostNPC");
dialogFlags.Add("Bandit");
}
else if (npc.TeamID == CharacterTeamType.FriendlyNPC)
{
dialogFlags.Remove("OutpostNPC");
dialogFlags.Add("Hostage");
}
}
@@ -144,7 +144,7 @@ namespace Barotrauma
new XAttribute("value", valueStr),
new XAttribute("type", value?.GetType())));
}
#if DEBUG || UNSTABLE
#if DEBUG
DebugConsole.Log(element.ToString());
#endif
modeElement.Add(element);
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -258,22 +259,32 @@ namespace Barotrauma
if (levelData.HasBeaconStation && !levelData.IsBeaconActive)
{
var beaconMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase)));
if (beaconMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type))
var beaconMissionPrefabs = MissionPrefab.List.FindAll(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase)));
if (beaconMissionPrefabs.Any())
{
extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
var beaconMissionPrefab = beaconMissionPrefabs.GetRandom(rand);
if (!Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type))
{
extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
}
}
}
if (levelData.HasHuntingGrounds)
{
var huntingGroundsMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)));
if (huntingGroundsMissionPrefab == null)
var huntingGroundsMissionPrefabs = MissionPrefab.List.FindAll(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)));
if (!huntingGroundsMissionPrefabs.Any())
{
DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggroundsnoreward\" found.");
}
else if (!Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type))
else
{
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
var huntingGroundsMissionPrefab = huntingGroundsMissionPrefabs.GetRandom(rand);
if (!Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type))
{
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
}
}
}
}
@@ -524,11 +535,13 @@ namespace Barotrauma
takenItems.Add(item);
}
}
map.CurrentLocation.RegisterTakenItems(takenItems);
map.CurrentLocation.AddToStock(CargoManager.SoldItems);
CargoManager.ClearSoldItemsProjSpecific();
map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems);
if (map != null && CargoManager != null)
{
map.CurrentLocation.RegisterTakenItems(takenItems);
map.CurrentLocation.AddToStock(CargoManager.SoldItems);
CargoManager.ClearSoldItemsProjSpecific();
map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems);
}
if (GameMain.NetworkMember == null)
{
CargoManager.ClearItemsInBuyCrate();
@@ -538,11 +551,11 @@ namespace Barotrauma
{
if (GameMain.NetworkMember.IsServer)
{
CargoManager.ClearItemsInBuyCrate();
CargoManager?.ClearItemsInBuyCrate();
}
else if (GameMain.NetworkMember.IsClient)
{
CargoManager.ClearItemsInSellCrate();
CargoManager?.ClearItemsInSellCrate();
}
}
@@ -601,7 +614,11 @@ namespace Barotrauma
}
foreach (Location location in Map.Locations)
{
location.ChangeType(location.OriginalType);
if (location.Type != location.OriginalType)
{
location.ChangeType(location.OriginalType);
location.PendingLocationTypeChange = null;
}
location.CreateStore(force: true);
location.ClearMissions();
location.Discovered = false;
@@ -425,6 +425,7 @@ namespace Barotrauma
#if CLIENT
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode;
#endif
LevelData = level?.LevelData;
@@ -104,7 +104,8 @@ namespace Barotrauma
/// </remarks>
/// <param name="prefab"></param>
/// <param name="category"></param>
public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category)
/// <param name="force"></param>
public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category, bool force = false)
{
if (!CanUpgradeSub())
{
@@ -136,6 +137,11 @@ namespace Barotrauma
});
}
if (force)
{
price = 0;
}
if (Campaign.Money > price)
{
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
@@ -154,7 +160,7 @@ namespace Barotrauma
PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category);
#if CLIENT
DebugLog($"CLIENT: Purchased level {GetUpgradeLevel(prefab, category) + 1} {category.Name}.{prefab.Name} for ${price}", GUI.Style.Orange);
DebugLog($"CLIENT: Purchased level {GetUpgradeLevel(prefab, category) + 1} {category.Name}.{prefab.Name} for {price}", GUI.Style.Orange);
#endif
if (upgrade == null)
@@ -689,7 +695,7 @@ namespace Barotrauma
public static void DebugLog(string msg, Color? color = null)
{
#if UNSTABLE || DEBUG
#if DEBUG
DebugConsole.NewMessage(msg, color ?? Color.GreenYellow);
#else
DebugConsole.Log(msg);
@@ -150,6 +150,44 @@ namespace Barotrauma
return false;
}
public override void RemoveItem(Item item)
{
RemoveItem(item, tryEquipFromSameStack: false);
}
public void RemoveItem(Item item, bool tryEquipFromSameStack)
{
if (!Contains(item)) { return; }
bool wasEquipped = character.HasEquippedItem(item);
var indices = FindIndices(item);
base.RemoveItem(item);
#if CLIENT
CreateSlots();
#endif
//if the item was equipped and there are more items in the same stack, equip one of those items
if (tryEquipFromSameStack && wasEquipped)
{
int limbSlot = indices.Find(j => SlotTypes[j] != InvSlotType.Any);
foreach (int i in indices)
{
var itemInSameSlot = GetItemAt(i);
if (itemInSameSlot != null)
{
if (TryPutItem(itemInSameSlot, limbSlot, allowSwapping: false, allowCombine: false, character))
{
#if CLIENT
visualSlots[i].ShowBorderHighlight(GUI.Style.Green, 0.1f, 0.412f);
#endif
}
break;
}
}
}
}
/// <summary>
/// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot
/// </summary>
@@ -165,6 +203,19 @@ namespace Barotrauma
}
}
if (allowedSlots != null && !allowedSlots.Contains(InvSlotType.Any))
{
int slot = FindLimbSlot(allowedSlots.First());
if (slot > -1 && slots[slot].Items.Any(it => it != item) && slots[slot].First().Prefab.AllowDroppingOnSwap)
{
foreach (Item existingItem in slots[slot].Items.ToList())
{
existingItem.Drop(user);
if (existingItem.ParentInventory != null) { existingItem.ParentInventory.RemoveItem(existingItem); }
}
}
}
return TryPutItem(item, user, allowedSlots, createNetworkEvent);
}
@@ -62,6 +62,7 @@ namespace Barotrauma.Items.Components
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
Attack = new Attack(subElement, item.Name + ", MeleeWeapon");
Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent());
}
item.IsShootable = true;
// TODO: should define this in xml if we have melee weapons that don't require aim to use
@@ -175,7 +175,7 @@ namespace Barotrauma.Items.Components
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale;
foreach (Character character in Character.CharacterList)
{
if (character.Submarine != null || !character.Enabled || character.Removed) { continue; }
if (!character.Enabled || character.Removed) { continue; }
float distSqr = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
if (distSqr > scaledDamageRange * scaledDamageRange) { continue; }
character.LastDamageSource = item;
@@ -121,11 +121,10 @@ namespace Barotrauma.Items.Components
inputContainer = containers[0];
outputContainer = containers[1];
foreach (var recipe in fabricationRecipes)
{
int ingredientCount = recipe.RequiredItems.Sum(it => it.Amount);
if (ingredientCount > inputContainer.Capacity)
if (recipe.RequiredItems.Count > inputContainer.Capacity)
{
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + recipe.TargetItem.Name + "\"!");
}
@@ -581,10 +581,9 @@ namespace Barotrauma.Items.Components
return false;
}
aiUpdateTimer = AIUpdateInterval;
// load more fuel if the current maximum output is only 50% of the current load
// or if the fuel rod is (almost) deplenished
float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2);
float minCondition = fuelConsumptionRate * MathUtils.Pow2((degreeOfSuccess - refuelLimit) * 2);
if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
{
bool outOfFuel = false;
@@ -360,7 +360,7 @@ namespace Barotrauma.Items.Components
//other junction boxes don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && !(ic is RelayComponent) && connection.Name.Contains("signal")) { continue; }
ic.ReceiveSignal(signal, connection);
ic.ReceiveSignal(signal, recipient);
}
foreach (StatusEffect effect in recipient.Effects)
@@ -65,10 +65,10 @@ namespace Barotrauma.Items.Components
}
base.IsActive = true;
InitProjSpecific(element);
InitProjSpecific();
}
partial void InitProjSpecific(XElement element);
partial void InitProjSpecific();
private bool linksInitialized;
public override void OnMapLoaded()
@@ -62,7 +62,7 @@ namespace Barotrauma.Items.Components
{
case "signal_in":
string signalOut = (signal.value == TargetSignal) ? Output : FalseOutput;
if (string.IsNullOrWhiteSpace(signalOut)) { return; }
if (string.IsNullOrEmpty(signalOut)) { return; }
signal.value = signalOut;
item.SendSignal(signal, "signal_out");
break;
@@ -83,7 +83,8 @@ namespace Barotrauma.Items.Components
fireInRange = IsFireInRange();
fireCheckTimer = FireCheckInterval;
}
item.SendSignal(fireInRange ? Output : FalseOutput, "signal_out");
string signalOut = fireInRange ? Output : FalseOutput;
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); }
}
}
}
@@ -42,7 +42,7 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element);
partial void ShowOnDisplay(string input);
partial void ShowOnDisplay(string input, bool addToHistory = true);
public override void ReceiveSignal(Signal signal, Connection connection)
{
@@ -58,13 +58,18 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
bool isSubEditor = false;
#if CLIENT
isSubEditor = Screen.Selected != GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode;
#endif
base.OnItemLoaded();
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
{
ShowOnDisplay(DisplayedWelcomeMessage);
ShowOnDisplay(DisplayedWelcomeMessage, addToHistory: !isSubEditor);
DisplayedWelcomeMessage = "";
//remove welcome message if a game session is running so it doesn't reappear on successive rounds
if (GameMain.GameSession != null)
if (GameMain.GameSession != null && !isSubEditor)
{
welcomeMessage = null;
}
@@ -102,6 +102,13 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, true, "If enabled, this wire will use the sprite depth instead of a constant depth.")]
public bool UseSpriteDepth
{
get;
set;
}
public Wire(Item item, XElement element)
: base(item, element)
{
@@ -436,23 +436,28 @@ namespace Barotrauma.Items.Components
Projectile launchedProjectile = null;
for (int i = 0; i < ProjectileCount; i++)
{
foreach (MapEntity e in item.linkedTo)
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Any())
{
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) { continue; }
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
ItemContainer projectileContainer = projectiles.First().Item.Container?.GetComponent<ItemContainer>();
projectileContainer?.Item.Use(deltaTime, null);
}
else
{
foreach (MapEntity e in item.linkedTo)
{
linkedItem.Use(deltaTime, null);
var repairable = linkedItem.GetComponent<Repairable>();
if (repairable != null && failedLaunchAttempts < 2)
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) { continue; }
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
linkedItem.Use(deltaTime, null);
projectiles = GetLoadedProjectiles(true);
if (projectiles.Any()) { break; }
}
}
}
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
{
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
@@ -471,7 +476,6 @@ namespace Barotrauma.Items.Components
}
failedLaunchAttempts = 0;
launchedProjectile = projectiles.FirstOrDefault();
if (!ignorePower)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
@@ -492,6 +496,15 @@ namespace Barotrauma.Items.Components
}
}
if (launchedProjectile?.Item.Container != null)
{
var repairable = launchedProjectile?.Item.Container.GetComponent<Repairable>();
if (repairable != null)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
}
}
if (launchedProjectile != null || LaunchWithoutProjectile)
{
Launch(launchedProjectile?.Item, character);
@@ -663,13 +663,29 @@ namespace Barotrauma
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(),user, CharacterInventory.anySlot, createNetworkEvent))
&&
stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
if (!swapSuccessful && existingItems.Count == 1 && existingItems[0].Prefab.AllowDroppingOnSwap)
{
existingItems[0].Drop(user, createNetworkEvent);
swapSuccessful = stackedItems.Distinct().Any(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
#if CLIENT
if (swapSuccessful)
{
SoundPlayer.PlayUISound(GUISoundType.DropItem);
if (otherInventory.visualSlots != null && otherIndex > -1)
{
otherInventory.visualSlots[otherIndex].ShowBorderHighlight(Color.Transparent, 0.1f, 0.1f);
}
}
#endif
}
}
//if the item in the slot can be moved to the slot of the moved item
if (swapSuccessful)
{
System.Diagnostics.Debug.Assert(slots[index].Contains(item), "Something when wrong when swapping items, item is not present in the inventory.");
System.Diagnostics.Debug.Assert(otherInventory.Contains(existingItems.FirstOrDefault()), "Something when wrong when swapping items, item is not present in the other inventory.");
System.Diagnostics.Debug.Assert(!existingItems.Any(it => !it.Prefab.AllowDroppingOnSwap && !otherInventory.Contains(it)), "Something when wrong when swapping items, item is not present in the other inventory.");
#if CLIENT
if (visualSlots != null)
{
@@ -2807,7 +2807,14 @@ namespace Barotrauma
if (parentInventory != null)
{
parentInventory.RemoveItem(this);
if (parentInventory is CharacterInventory characterInventory)
{
characterInventory.RemoveItem(this, tryEquipFromSameStack: true);
}
else
{
parentInventory.RemoveItem(this);
}
parentInventory = null;
}
@@ -517,6 +517,9 @@ namespace Barotrauma
set { maxStackSize = MathHelper.Clamp(value, 1, Inventory.MaxStackSize); }
}
[Serialize(false, false)]
public bool AllowDroppingOnSwap { get; private set; }
public Vector2 Size => size;
public bool CanBeBought => (DefaultPrice != null && DefaultPrice.CanBeBought) || (locationPrices != null && locationPrices.Any(p => p.Value.CanBeBought));
@@ -87,7 +87,7 @@ namespace Barotrauma.MapCreatures.Behavior
internal partial class BallastFloraBehavior : ISerializableEntity
{
#if DEBUG || UNSTABLE
#if DEBUG
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
#endif
@@ -60,7 +60,7 @@ namespace Barotrauma.MapCreatures.Behavior
protected virtual void Grow()
{
List<BallastFloraBranch> newTiles = GrowRandomly();
#if DEBUG || UNSTABLE
#if DEBUG
Behavior.debugSearchLines.Clear();
#endif
if (newTiles.Any(TryScanTargets)) { return; }
@@ -135,7 +135,7 @@ namespace Barotrauma.MapCreatures.Behavior
Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position);
#if DEBUG || UNSTABLE
#if DEBUG
Tuple<Vector2, Vector2> debugLine1 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(topLeft), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos - diameter));
Tuple<Vector2, Vector2> debugLine2 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(bottomRight), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos + diameter));
Behavior.debugSearchLines.Add(debugLine2);
@@ -27,6 +27,7 @@ namespace Barotrauma
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
private bool playTinnitus;
private bool applyFireEffects;
private string[] ignoreFireEffectsForTags;
private bool ignoreCover;
private bool onlyInside;
private bool onlyOutside;
@@ -53,6 +54,7 @@ namespace Barotrauma
smoke = true;
flames = true;
underwaterBubble = true;
ignoreFireEffectsForTags = new string[0];
}
public Explosion(XElement element, string parentDebugName)
@@ -70,6 +72,8 @@ namespace Barotrauma
playTinnitus = element.GetAttributeBool("playtinnitus", true);
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames);
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", new string[0], convertToLowerInvariant: true);
ignoreCover = element.GetAttributeBool("ignorecover", false);
onlyInside = element.GetAttributeBool("onlyinside", false);
onlyOutside = element.GetAttributeBool("onlyoutside", false);
@@ -192,7 +196,7 @@ namespace Barotrauma
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
if (dist > Attack.Range) { continue; }
if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof)
if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
{
//don't apply OnFire effects if the item is inside a fireproof container
//(or if it's inside a container that's inside a fireproof container, etc)
@@ -479,8 +479,8 @@ namespace Barotrauma
minMainPathWidth, parentTunnel: null);
Tunnels.Add(mainPath);
Tunnel startPath = null, endPath = null;
if (Mirrored ? !HasEndOutpost() : !HasStartOutpost())
Tunnel startPath = null, endPath = null, endHole = null;
if (GenerationParams.StartPosition.Y < 0.5f && (Mirrored ? !HasEndOutpost() : !HasStartOutpost()))
{
startPath = new Tunnel(
TunnelType.SidePath,
@@ -488,7 +488,11 @@ namespace Barotrauma
minWidth / 2, parentTunnel: mainPath);
Tunnels.Add(startPath);
}
if (Mirrored ? !HasStartOutpost() : !HasEndOutpost())
else
{
startExitPosition = StartPosition;
}
if (GenerationParams.EndPosition.Y < 0.5f && (Mirrored ? !HasStartOutpost() : !HasEndOutpost()))
{
endPath = new Tunnel(
TunnelType.SidePath,
@@ -496,12 +500,51 @@ namespace Barotrauma
minWidth / 2, parentTunnel: mainPath);
Tunnels.Add(endPath);
}
else
{
endExitPosition = EndPosition;
}
if (GenerationParams.CreateHoleNextToEnd)
{
if (Mirrored)
{
endHole = new Tunnel(
TunnelType.SidePath,
new List<Point>() { startPosition, startExitPosition.ToPoint(), new Point(0, Size.Y) },
minWidth / 2, parentTunnel: mainPath);
}
else
{
endHole = new Tunnel(
TunnelType.SidePath,
new List<Point>() { endPosition, endExitPosition.ToPoint(), Size },
minWidth / 2, parentTunnel: mainPath);
}
Tunnels.Add(endHole);
}
//create a tunnel from the lowest point in the main path to the abyss
//to ensure there's a way to the abyss in all levels
if (GenerationParams.CreateHoleToAbyss)
{
Point lowestPoint = mainPath.Nodes.First();
foreach (var pathNode in mainPath.Nodes)
{
if (pathNode.Y < lowestPoint.Y) { lowestPoint = pathNode; }
}
var abyssTunnel = new Tunnel(
TunnelType.SidePath,
new List<Point>() { lowestPoint, new Point(lowestPoint.X, 0) },
minWidth / 2, parentTunnel: mainPath);
Tunnels.Add(abyssTunnel);
}
int sideTunnelCount = Rand.Range(GenerationParams.SideTunnelCount.X, GenerationParams.SideTunnelCount.Y + 1, Rand.RandSync.Server);
for (int j = 0; j < sideTunnelCount; j++)
{
if (mainPath.Nodes.Count < 4) { break; }
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath);
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole);
Tunnel tunnelToBranchOff = validTunnels[Rand.Int(validTunnels.Count, Rand.RandSync.Server)];
if (tunnelToBranchOff == null) { tunnelToBranchOff = mainPath; }
@@ -634,13 +677,16 @@ namespace Barotrauma
CaveGenerator.GeneratePath(tunnel, this);
if (tunnel.Type == TunnelType.MainPath || tunnel.Type == TunnelType.SidePath)
{
var distinctCells = tunnel.Cells.Distinct().ToList();
for (int i = 2; i < distinctCells.Count; i += 3)
if (tunnel != startPath && tunnel != endPath && tunnel != endHole)
{
PositionsOfInterest.Add(new InterestingPosition(
new Point((int)distinctCells[i].Site.Coord.X, (int)distinctCells[i].Site.Coord.Y),
tunnel.Type == TunnelType.MainPath ? PositionType.MainPath : PositionType.SidePath,
Caves.Find(cave => cave.Tunnels.Contains(tunnel))));
var distinctCells = tunnel.Cells.Distinct().ToList();
for (int i = 2; i < distinctCells.Count; i += 3)
{
PositionsOfInterest.Add(new InterestingPosition(
new Point((int)distinctCells[i].Site.Coord.X, (int)distinctCells[i].Site.Coord.Y),
tunnel.Type == TunnelType.MainPath ? PositionType.MainPath : PositionType.SidePath,
Caves.Find(cave => cave.Tunnels.Contains(tunnel))));
}
}
}
GenerateWaypoints(tunnel, parentTunnel: tunnel.ParentTunnel);
@@ -685,7 +731,10 @@ namespace Barotrauma
var potentialIslands = new List<VoronoiCell>();
foreach (var cell in pathCells)
{
if (GetDistToTunnel(cell.Center, mainPath) < minMainPathWidth) { continue; }
if (GetDistToTunnel(cell.Center, mainPath) < minMainPathWidth ||
(startPath != null && GetDistToTunnel(cell.Center, startPath) < minMainPathWidth) ||
(endPath != null && GetDistToTunnel(cell.Center, endPath) < minMainPathWidth) ||
(endHole != null && GetDistToTunnel(cell.Center, endHole) < minMainPathWidth)) { continue; }
if (cell.Edges.Any(e => e.AdjacentCell(cell)?.CellType != CellType.Path || e.NextToCave)) { continue; }
potentialIslands.Add(cell);
}
@@ -1230,18 +1279,6 @@ namespace Barotrauma
List<VoronoiCell> toBeRemoved = new List<VoronoiCell>();
foreach (VoronoiCell cell in cells)
{
if (GenerationParams.CreateHoleNextToEnd)
{
if ((!Mirrored && cell.Center.X > endPosition.X) || (Mirrored && cell.Center.X < StartPosition.X))
{
if (cell.Edges.Any(e => e.Point1.Y > Size.Y - submarineSize || e.Point2.Y > Size.Y - submarineSize))
{
toBeRemoved.Add(cell);
continue;
}
}
}
if (cell.Edges.Any(e => e.NextToCave)) { continue; }
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > holeProbability) { continue; }
if (!limits.Contains(cell.Site.Coord.X, cell.Site.Coord.Y)) { continue; }
@@ -1644,7 +1681,7 @@ namespace Barotrauma
Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2);
int radius = Math.Max(caveSize.X, caveSize.Y) / 2;
var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.2f, asCloseAsPossible: true, allowedArea);
var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.5f, asCloseAsPossible: true, allowedArea);
GenerateCave(caveParams, parentTunnel, cavePos, caveSize);
@@ -2633,7 +2670,7 @@ namespace Barotrauma
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(startPos),
ConvertUnits.ToSimUnits(endPos),
ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body),
ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)),
Physics.CollisionLevel | Physics.CollisionWall) != null)
{
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall;
@@ -3027,6 +3064,7 @@ namespace Barotrauma
else if (type == SubmarineType.BeaconStation)
{
sub.ShowSonarMarker = false;
sub.DockedTo.ForEach(s => s.ShowSonarMarker = false);
sub.PhysicsBody.FarseerBody.BodyType = BodyType.Static;
sub.TeamID = CharacterTeamType.None;
}
@@ -3472,13 +3510,22 @@ namespace Barotrauma
if ((i == 0) == !Mirrored)
{
StartOutpost = outpost;
if (StartLocation != null) { outpost.Info.Name = StartLocation.Name; }
if (StartLocation != null)
{
outpost.TeamID = StartLocation.Type.OutpostTeam;
outpost.Info.Name = StartLocation.Name;
}
}
else
{
EndOutpost = outpost;
if (EndLocation != null) { outpost.Info.Name = EndLocation.Name; }
if (EndLocation != null)
{
outpost.TeamID = EndLocation.Type.OutpostTeam;
outpost.Info.Name = EndLocation.Name;
}
}
}
}
@@ -3514,20 +3561,28 @@ namespace Barotrauma
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
Item reactorItem = beaconItems.Find(it => it.GetComponent<Reactor>() != null);
Reactor reactorComponent = reactorItem.GetComponent<Reactor>();
ItemContainer reactorContainer = reactorItem.GetComponent<ItemContainer>();
Repairable repairable = reactorItem.GetComponent<Repairable>();
reactorComponent.FuelConsumptionRate = 0.0f;
if (repairable != null)
Reactor reactorComponent = null;
ItemContainer reactorContainer = null;
if (reactorItem != null)
{
repairable.DeteriorationSpeed = 0.0f;
reactorComponent = reactorItem.GetComponent<Reactor>();
reactorComponent.FuelConsumptionRate = 0.0f;
reactorContainer = reactorItem.GetComponent<ItemContainer>();
Repairable repairable = reactorItem.GetComponent<Repairable>();
if (repairable != null)
{
if (repairable != null)
{
repairable.DeteriorationSpeed = 0.0f;
}
}
}
if (LevelData.IsBeaconActive)
{
if (reactorContainer.Inventory.IsEmpty())
if (reactorContainer != null && reactorContainer.Inventory.IsEmpty())
{
ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItems[0].Identifiers[0]];
Entity.Spawner.AddToSpawnQueue(
Spawner.AddToSpawnQueue(
fuelPrefab, reactorContainer.Inventory,
onSpawned: (it) => reactorComponent.PowerUpImmediately());
}
@@ -3544,7 +3599,7 @@ namespace Barotrauma
foreach (Item item in reactorContainer.Inventory.AllItems)
{
if (item.NonInteractable) { continue; }
Entity.Spawner.AddToRemoveQueue(item);
Spawner.AddToRemoveQueue(item);
}
//remove wires
@@ -3563,7 +3618,18 @@ namespace Barotrauma
}
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
{
Entity.Spawner.AddToRemoveQueue(item);
foreach (Connection connection in wire.Connections)
{
if (connection != null)
{
connection.ConnectionPanel.DisconnectedWires.Add(wire);
wire.RemoveConnection(connection.Item);
#if SERVER
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
wire.CreateNetworkEvent();
#endif
}
}
}
}
@@ -3573,7 +3639,7 @@ namespace Barotrauma
if (item.NonInteractable) { continue; }
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
{
item.Condition *= Rand.Range(0.2f, 0.6f, Rand.RandSync.Unsynced);
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
}
}
@@ -3617,6 +3683,9 @@ namespace Barotrauma
pathPoints.Shuffle(Rand.RandSync.Unsynced);
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
corpsePoints.Shuffle(Rand.RandSync.Unsynced);
if (!corpsePoints.Any() && !pathPoints.Any()) { continue; }
int spawnCounter = 0;
for (int j = 0; j < corpseCount; j++)
{
@@ -3706,7 +3775,15 @@ namespace Barotrauma
/// </summary>
public float GetRealWorldDepth(float worldPositionY)
{
return (-(worldPositionY - GenerationParams.Height) + LevelData.InitialDepth) * Physics.DisplayToRealWorldRatio;
if (GameMain.GameSession?.Campaign == null)
{
//ensure the levels aren't too deep to traverse in non-campaign modes where you don't have the option to upgrade/switch the sub
return (-(worldPositionY - GenerationParams.Height) + 80000.0f) * Physics.DisplayToRealWorldRatio;
}
else
{
return (-(worldPositionY - GenerationParams.Height) + LevelData.InitialDepth) * Physics.DisplayToRealWorldRatio;
}
}
public void DebugSetStartLocation(Location newStartLocation)
@@ -145,8 +145,11 @@ namespace Barotrauma
var rand = new MTRandom(ToolBox.StringToInt(Seed));
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
//minimum difficulty of the level before hunting grounds can appear
float huntingGroundsDifficultyThreshold = 25;
//probability of hunting grounds appearing in 100% difficulty levels
float maxHuntingGroundsProbability = 0.3f;
HasHuntingGrounds = rand.NextDouble() < Difficulty / 100.0f * maxHuntingGroundsProbability;
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
IsBeaconActive = false;
@@ -181,6 +181,13 @@ namespace Barotrauma
set;
}
[Serialize(true, true, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
public bool CreateHoleToAbyss
{
get;
set;
}
[Serialize(1000, true, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
public int LevelObjectAmount
{
@@ -361,6 +361,11 @@ namespace Barotrauma
if (subElement.Attribute("index") != null)
{
int locationTypeChangeIndex = subElement.GetAttributeInt("index", 0);
if (locationTypeChangeIndex < 0 || locationTypeChangeIndex >= Type.CanChangeTo.Count)
{
DebugConsole.AddWarning($"Failed to activate a location type change in the location \"{Name}\". Location index out of bounds ({locationTypeChangeIndex}).");
continue;
}
PendingLocationTypeChange = (Type.CanChangeTo[locationTypeChangeIndex], timer, null);
}
else
@@ -1059,12 +1064,21 @@ namespace Barotrauma
if (PendingLocationTypeChange.Value.parentMission != null)
{
changeElement.Add(new XAttribute("missionidentifier", PendingLocationTypeChange.Value.parentMission.Identifier));
locationElement.Add(changeElement);
}
else
{
changeElement.Add(new XAttribute("index", Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange)));
int index = Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange);
changeElement.Add(new XAttribute("index", index));
if (index == -1)
{
DebugConsole.AddWarning($"Invalid location type change in the location \"{Name}\". Unknown type change ({PendingLocationTypeChange.Value.typeChange.ChangeToType}).");
}
else
{
locationElement.Add(changeElement);
}
}
locationElement.Add(changeElement);
}
if (LocationTypeChangeCooldown > 0)
@@ -27,6 +27,8 @@ namespace Barotrauma
public readonly float BeaconStationChance;
public readonly CharacterTeamType OutpostTeam;
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
public readonly List<string> MissionIdentifiers = new List<string>();
@@ -90,6 +92,9 @@ namespace Barotrauma
ReplaceInRadiation = element.GetAttributeString(nameof(ReplaceInRadiation).ToLower(), "");
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
Enum.TryParse(teamStr, out OutpostTeam);
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
try
{
@@ -316,20 +316,13 @@ namespace Barotrauma
if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; }
}
}
HashSet<Location> connectedLocations = new HashSet<Location>();
foreach (LocationConnection connection in Connections)
{
connection.Locations[0].Connections.Add(connection);
connection.Locations[1].Connections.Add(connection);
connectedLocations.Add(connection.Locations[0]);
connectedLocations.Add(connection.Locations[1]);
}
//remove orphans
Locations.RemoveAll(c => !connectedLocations.Contains(c));
//remove locations that are too close to each other
float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance;
for (int i = Locations.Count - 1; i >= 0; i--)
@@ -443,6 +436,9 @@ namespace Barotrauma
}
}
//remove orphans
Locations.RemoveAll(l => !Connections.Any(c => c.Locations.Contains(l)));
foreach (LocationConnection connection in Connections)
{
connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
@@ -654,10 +650,12 @@ namespace Barotrauma
CurrentLocation.CreateStore();
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.CampaignMetadata is { } metadata)
if (GameMain.GameSession is { Campaign: { CampaignMetadata: { } metadata } })
{
metadata.SetValue("campaign.location.id", CurrentLocationIndex);
metadata.SetValue("campaign.location.name", CurrentLocation.Name);
metadata.SetValue("campaign.location.biome", CurrentLocation.Biome?.Identifier ?? "null");
metadata.SetValue("campaign.location.type", CurrentLocation.Type?.Identifier ?? "null");
}
}
@@ -185,10 +185,6 @@ namespace Barotrauma
{
return -WorldPosition.Y * Physics.DisplayToRealWorldRatio;
}
else if (GameMain.GameSession?.Campaign == null)
{
return (-(WorldPosition.Y - Level.Loaded.GenerationParams.Height) + 80000.0f) * Physics.DisplayToRealWorldRatio;
}
return Level.Loaded.GetRealWorldDepth(WorldPosition.Y);
}
}
@@ -254,7 +250,7 @@ namespace Barotrauma
get
{
if (Level.Loaded == null || subBody == null) { return false; }
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth & RealWorldDepth > RealWorldCrushDepth;
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth && RealWorldDepth > RealWorldCrushDepth;
}
}
@@ -324,6 +320,7 @@ namespace Barotrauma
{
Info.Type = SubmarineType.Wreck;
ShowSonarMarker = false;
DockedTo.ForEach(s => s.ShowSonarMarker = false);
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
TeamID = CharacterTeamType.None;
@@ -450,12 +450,21 @@ namespace Barotrauma
if (GameMain.GameSession?.GameMode is TestGameMode) { return; }
#endif
if (Level.Loaded == null) { return; }
float submarineDepth = submarine.RealWorldDepth;
if (!Submarine.AtDamageDepth) { return; }
//camera shake and sounds start playing 500 meters before crush depth
float depthEffectThreshold = 500.0f;
if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth - depthEffectThreshold && Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth - depthEffectThreshold)
{
return;
}
depthDamageTimer -= deltaTime;
if (depthDamageTimer > 0.0f) { return; }
#if CLIENT
SoundPlayer.PlayDamageSound("pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f);
#endif
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) { continue; }
@@ -463,12 +472,14 @@ namespace Barotrauma
float wallCrushDepth = wall.CrushDepth;
if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; }
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
if (pastCrushDepth < 0) { return; }
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
if (pastCrushDepth > 0)
{
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
}
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(pastCrushDepth * 0.001f, 50.0f));
}
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Clamp(pastCrushDepth * 0.001f, 1.0f, 50.0f));
}
}
depthDamageTimer = 10.0f;
@@ -30,7 +30,8 @@ namespace Barotrauma.Networking
ERROR, //tell the server that an error occurred
CREW,
READY_CHECK
READY_CHECK,
READY_TO_SPAWN
}
enum ClientNetObject
@@ -24,7 +24,7 @@ namespace Barotrauma.Networking
//items created during respawn
//any respawn items left in the shuttle are removed when the shuttle despawns
private List<Item> respawnItems = new List<Item>();
private readonly List<Item> respawnItems = new List<Item>();
public bool UsingShuttle
{
@@ -713,7 +713,7 @@ namespace Barotrauma
public void SetPrevTransform(Vector2 simPosition, float rotation)
{
#if DEBUG || UNSTABLE
#if DEBUG
if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return; }
if (!IsValidValue(rotation, "rotation")) { return; }
#endif
@@ -756,12 +756,12 @@ namespace Barotrauma
Vector2 vel = FarseerBody.LinearVelocity;
Vector2 deltaPos = simPosition - (Vector2)pullPos;
#if DEBUG
if (deltaPos.LengthSquared() > 100.0f * 100.0f)
{
#if DEBUG || UNSTABLE
DebugConsole.ThrowError("Attempted to move a physics body to an invalid position.\n" + Environment.StackTrace.CleanupStackTrace());
#endif
}
#endif
deltaPos *= force;
ApplyLinearImpulse((deltaPos - vel * 0.5f) * FarseerBody.Mass, (Vector2)pullPos);
}
@@ -1057,12 +1057,11 @@ namespace Barotrauma
foreach (Affliction affliction in Afflictions)
{
if (Rand.Value(Rand.RandSync.Unsynced) > affliction.Probability) { continue; }
Affliction multipliedAffliction = affliction;
if (!disableDeltaTime)
Affliction newAffliction = affliction;
if (!disableDeltaTime && !setValue)
{
multipliedAffliction = affliction.CreateMultiplied(deltaTime);
newAffliction = affliction.CreateMultiplied(deltaTime);
}
if (target is Character character)
{
if (character.Removed) { continue; }
@@ -1072,7 +1071,7 @@ namespace Barotrauma
if (limb.Removed) { continue; }
if (limb.IsSevered) { continue; }
if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; }
AttackResult result = limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true);
//only apply non-limb-specific afflictions to the first limb
if (!affliction.Prefab.LimbSpecific) { break; }
@@ -1082,7 +1081,7 @@ namespace Barotrauma
{
if (limb.IsSevered) { continue; }
if (limb.character.Removed || limb.Removed) { continue; }
AttackResult result = limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true);
}
}
@@ -1383,7 +1382,7 @@ namespace Barotrauma
foreach (Affliction affliction in element.Parent.Afflictions)
{
Affliction multipliedAffliction = affliction;
if (!element.Parent.disableDeltaTime) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); }
if (!element.Parent.disableDeltaTime && !element.Parent.setValue) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); }
if (target is Character character)
{