Unstable 1.2.1.0

This commit is contained in:
Markus Isberg
2023-11-10 17:45:19 +02:00
parent 2ea58c58a7
commit 8a2e2ea0ae
268 changed files with 4076 additions and 1843 deletions
@@ -262,7 +262,8 @@ namespace Barotrauma
if (aiElements.Count == 0)
{
DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found.");
DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found.",
contentPackage: c.Prefab?.ContentPackage);
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, false, false);
return;
@@ -330,7 +331,8 @@ namespace Barotrauma
_aiParams = Character.Params.AI;
if (_aiParams == null)
{
DebugConsole.ThrowError($"No AI Params defined for {Character.SpeciesName}. AI disabled.");
DebugConsole.ThrowError($"No AI Params defined for {Character.SpeciesName}. AI disabled.",
contentPackage: Character.Prefab.ContentPackage);
Enabled = false;
_aiParams = new CharacterParams.AIParams(null, Character.Params);
}
@@ -2503,7 +2505,8 @@ namespace Barotrauma
Limb mouthLimb = Character.AnimController.GetLimb(LimbType.Head);
if (mouthLimb == null)
{
DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (No head limb defined)");
DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (No head limb defined)",
contentPackage: Character.Prefab.ContentPackage);
State = AIState.Idle;
return;
}
@@ -2540,7 +2543,11 @@ namespace Barotrauma
item.body.LinearVelocity -= velocity * 0.25f;
bool wasBroken = item.Condition <= 0.0f;
item.LastEatenTime = (float)Timing.TotalTimeUnpaused;
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
item.AddDamage(Character,
item.WorldPosition,
new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed),
impulseDirection: Vector2.Zero,
deltaTime);
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
if (item.Condition <= 0.0f)
{
@@ -167,10 +167,6 @@ namespace Barotrauma
public HumanAIController(Character c) : base(c)
{
if (!c.IsHuman)
{
throw new Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
}
insideSteering = new IndoorsSteeringManager(this, true, false);
outsideSteering = new SteeringManager(this);
objectiveManager = new AIObjectiveManager(c);
@@ -1800,7 +1796,7 @@ namespace Barotrauma
if (!TriggerSecurity(otherHumanAI, combatMode))
{
// Else call the others
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
{
if (!TriggerSecurity(security.AIController as HumanAIController, combatMode))
{
@@ -1861,16 +1857,11 @@ namespace Barotrauma
}
if (!someoneSpoke)
{
if (!item.StolenDuringRound &&
Level.Loaded?.Type == LevelData.LevelType.Outpost &&
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
if (!item.StolenDuringRound)
{
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss);
ApplyStealingReputationLoss(item);
item.StolenDuringRound = true;
}
item.StolenDuringRound = true;
otherCharacter.Speak(TextManager.Get("dialogstealwarning").Value, null, Rand.Range(0.5f, 1.0f), "thief".ToIdentifier(), 10.0f);
someoneSpoke = true;
#if CLIENT
@@ -1881,7 +1872,7 @@ namespace Barotrauma
if (!TriggerSecurity(otherHumanAI))
{
// Else call the others
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition)))
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition)))
{
if (TriggerSecurity(security.AIController as HumanAIController))
{
@@ -1902,6 +1893,10 @@ namespace Barotrauma
if (humanAI == null) { return false; }
if (!humanAI.Character.IsSecurity) { return false; }
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
if (humanAI.ObjectiveManager.GetObjective<AIObjectiveFindThieves>() is { } findThieves)
{
findThieves.InspectEveryone();
}
humanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, thief, delay: GetReactionTime(),
abortCondition: obj => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null,
onAbort: () =>
@@ -1919,6 +1914,18 @@ namespace Barotrauma
}
}
public static void ApplyStealingReputationLoss(Item item)
{
if (Level.Loaded?.Type == LevelData.LevelType.Outpost &&
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
{
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss);
}
}
// 0.225 - 0.375
private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f);
@@ -197,17 +197,6 @@ namespace Barotrauma
}
}
/// <summary>
/// This method allows multiple subobjectives of same type. Use with caution.
/// </summary>
public void AddSubObjectiveInQueue(AIObjective objective)
{
if (!subObjectives.Contains(objective))
{
subObjectives.Add(objective);
}
}
public void RemoveSubObjective<T>(ref T objective) where T : AIObjective
{
if (objective != null)
@@ -0,0 +1,160 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveCheckStolenItems : AIObjective
{
public override Identifier Identifier { get; set; } = "check stolen items".ToIdentifier();
public override bool AllowOutsideSubmarine => false;
public override bool AllowInAnySub => false;
public float FindStolenItemsProbability = 1.0f;
enum State
{
GotoTarget,
Inspect,
Warn,
Done
}
private float inspectDelay;
private float warnDelay;
private State currentState;
public readonly Character TargetCharacter;
private AIObjectiveGoTo? goToObjective;
private readonly List<Item> stolenItems = new List<Item>();
public AIObjectiveCheckStolenItems(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1) :
base(character, objectiveManager, priorityModifier)
{
TargetCharacter = targetCharacter;
inspectDelay = 5.0f;
warnDelay = 5.0f;
}
public override bool IsLoop
{
get => false;
set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
}
protected override bool CheckObjectiveSpecific() => false;
protected override float GetPriority()
{
if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this))
{
Priority = objectiveManager.GetOrderPriority(this);
}
else
{
Priority = AIObjectiveManager.LowestOrderPriority - 1;
}
return Priority;
}
public void ForceComplete()
{
IsCompleted = true;
}
protected override void Act(float deltaTime)
{
switch (currentState)
{
case State.GotoTarget:
TryAddSubObjective(ref goToObjective,
constructor: () =>
{
return new AIObjectiveGoTo(TargetCharacter, character, objectiveManager, repeat: false)
{
SpeakIfFails = false
};
},
onCompleted: () =>
{
RemoveSubObjective(ref goToObjective);
currentState = State.Inspect;
stolenItems.Clear();
TargetCharacter.Inventory.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true, stolenItems);
character.Speak(TextManager.Get("dialogcheckstolenitems").Value);
},
onAbandon: () =>
{
Abandon = true;
});
break;
case State.Inspect:
Inspect(deltaTime);
break;
case State.Warn:
Warn(deltaTime);
break;
}
}
private void Inspect(float deltaTime)
{
if (inspectDelay > 0.0f)
{
character.SelectCharacter(TargetCharacter);
inspectDelay -= deltaTime;
return;
}
if (stolenItems.Any() &&
Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) < FindStolenItemsProbability)
{
character.Speak(TextManager.Get("dialogcheckstolenitems.warn").Value);
currentState = State.Warn;
}
else
{
character.Speak(TextManager.Get("dialogcheckstolenitems.nostolenitems").Value);
currentState = State.Done;
IsCompleted = true;
}
character.DeselectCharacter();
}
private void Warn(float deltaTime)
{
if (warnDelay > 0.0f)
{
warnDelay -= deltaTime;
return;
}
var stolenItemsOnCharacter = stolenItems.Where(it => it.GetRootInventoryOwner() == TargetCharacter);
if (stolenItemsOnCharacter.Any())
{
character.Speak(TextManager.Get("dialogcheckstolenitems.arrest").Value);
HumanAIController.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, TargetCharacter);
foreach (var stolenItem in stolenItemsOnCharacter)
{
HumanAIController.ApplyStealingReputationLoss(stolenItem);
}
}
else
{
character.Speak(TextManager.Get("dialogcheckstolenitems.comply").Value);
}
foreach (var item in stolenItems)
{
HumanAIController.ObjectiveManager.AddObjective(new AIObjectiveGetItem(character, item, objectiveManager, equip: false)
{
BasePriority = 10
});
}
currentState = State.Done;
IsCompleted = true;
}
}
}
@@ -0,0 +1,152 @@
#nullable enable
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveFindThieves : AIObjectiveLoop<Character>
{
public override Identifier Identifier { get; set; } = "find thieves".ToIdentifier();
protected override float IgnoreListClearInterval => 30;
public override bool IgnoreUnsafeHulls => true;
protected override float TargetUpdateTimeMultiplier => 1.0f;
const float DefaultInspectDistance = 200.0f;
/// <summary>
/// How close the NPC must be to the target to the inspect them? You can use high values to make the NPC
/// systematically go through targets no matter where they are, and low values to check targets they happen to come across.
/// </summary>
public float InspectDistance = DefaultInspectDistance;
private float? overrideInspectProbability;
/// <summary>
/// Chance of inspecting a valid target. The NPC won't try to inspect that target again for <see cref="inspectionInterval"/>
/// regardless if the target is inspected or not.
/// </summary>
public float InspectProbability
{
get
{
if (overrideInspectProbability.HasValue)
{
return overrideInspectProbability.Value;
}
if (GameMain.GameSession?.Campaign is { } campaign)
{
if (campaign.Map?.CurrentLocation?.Reputation is { } reputation)
{
return MathHelper.Lerp(
campaign.Settings.MaxStolenItemInspectionProbability,
campaign.Settings.MinStolenItemInspectionProbability,
reputation.NormalizedValue);
}
}
return 0.2f;
}
}
/// <summary>
/// When did the character last inspect whether some other character has stolen items on them?
/// </summary>
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
private readonly float inspectionInterval = 120.0f;
public AIObjectiveFindThieves(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
protected override bool Filter(Character target)
{
if (!IsValidTarget(target, character)) { return false; }
if (Vector2.DistanceSquared(target.WorldPosition, character.WorldPosition) > InspectDistance * InspectDistance) { return false; }
if (lastInspectionTimes.TryGetValue(target, out double lastInspectionTime))
{
if (Timing.TotalTime < lastInspectionTime + inspectionInterval)
{
return false;
}
}
return true;
}
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override float TargetEvaluation()
{
return subObjectives.Any() ? 50 : 0;
}
public void InspectEveryone()
{
lastInspectionTimes.Clear();
overrideInspectProbability = 1.0f;
InspectDistance = DefaultInspectDistance * 2;
}
protected override AIObjective ObjectiveConstructor(Character target)
{
var checkStolenItemsObjective = new AIObjectiveCheckStolenItems(character, target, objectiveManager);
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) >= InspectProbability)
{
checkStolenItemsObjective.ForceComplete();
lastInspectionTimes[target] = Timing.TotalTime;
}
return checkStolenItemsObjective;
}
private float checkVisibleStolenItemsTimer;
private const float CheckVisibleStolenItemsInterval = 5.0f;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (checkVisibleStolenItemsTimer > 0.0f)
{
checkVisibleStolenItemsTimer -= deltaTime;
return;
}
foreach (var target in Character.CharacterList)
{
if (!IsValidTarget(target, character)) { continue; }
//if we spot someone wearing or holding stolen items, immediately check them (with 100% chance of spotting the stolen items)
if (target.Inventory.AllItems.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing && target.HasEquippedItem(it)) &&
character.CanSeeTarget(target))
{
AIObjectiveCheckStolenItems? existingObjective =
objectiveManager.GetActiveObjectives<AIObjectiveCheckStolenItems>().FirstOrDefault(o => o.TargetCharacter == target);
if (existingObjective == null)
{
objectiveManager.AddObjective(new AIObjectiveCheckStolenItems(character, target, objectiveManager));
lastInspectionTimes[target] = Timing.TotalTime;
}
}
}
checkVisibleStolenItemsTimer = CheckVisibleStolenItemsInterval;
}
private bool IsValidTarget(Character target, Character character)
{
if (target == null || target.Removed) { return false; }
if (target.IsIncapacitated) { return false; }
if (target == character) { return false; }
if (target.Submarine == null) { return false; }
if (character.Submarine == null) { return false; }
if (target.CurrentHull == null) { return false; }
if (target.Submarine != character.Submarine) { return false; }
//only player's crew can steal, ignore other teams
if (!target.IsOnPlayerTeam) { return false; }
if (target.IsArrested) { return false; }
return true;
}
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
{
lastInspectionTimes[target] = Timing.TotalTime;
}
}
}
@@ -178,7 +178,7 @@ namespace Barotrauma
if (!objectiveManager.IsOrder(this))
{
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater || this is AIObjectiveFindThieves;
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
}
if (!Filter(target)) { continue; }
@@ -142,6 +142,7 @@ namespace Barotrauma
prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
}
AddObjective(newIdleObjective);
int objectiveCount = Objectives.Count;
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
{
@@ -549,6 +550,9 @@ namespace Barotrauma
case "escapehandcuffs":
newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
break;
case "findthieves":
newObjective = new AIObjectiveFindThieves(character, this, priorityModifier: priorityModifier);
break;
case "prepareforexpedition":
newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
{
@@ -441,7 +441,8 @@ namespace Barotrauma
}
catch (NotImplementedException e)
{
DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}");
DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}",
contentPackage: ContentPackage);
return null;
}
}
@@ -552,7 +552,8 @@ namespace Barotrauma
#if DEBUG
if (handlePos[i].LengthSquared() > ArmLength)
{
DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)");
DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)",
item.Prefab.ContentPackage);
}
#endif
HandIK(
@@ -150,7 +150,7 @@ namespace Barotrauma
private readonly float movementLerp;
private float cprAnimTimer,cprPump;
private float cprAnimTimer, cprPumpTimer;
private float fallingProneAnimTimer;
const float FallingProneAnimDuration = 1.0f;
@@ -243,14 +243,17 @@ namespace Barotrauma
if (MainLimb == null) { return; }
levitatingCollider = !IsHanging;
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
if (onGround && character.CanMove)
{
Crouching = false;
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{
Crouching = false;
}
ColliderIndex = Crouching && !swimming ? 1 : 0;
}
ColliderIndex = Crouching && !swimming ? 1 : 0;
//stun (= disable the animations) if the ragdoll receives a large enough impact
if (strongestImpact > 0.0f)
@@ -276,7 +279,7 @@ namespace Barotrauma
if (!character.CanMove)
{
if (fallingProneAnimTimer < FallingProneAnimDuration)
if (fallingProneAnimTimer < FallingProneAnimDuration && onGround)
{
fallingProneAnimTimer += deltaTime;
UpdateFallingProne(1.0f);
@@ -285,7 +288,12 @@ namespace Barotrauma
Collider.FarseerBody.FixedRotation = false;
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
Collider.Enabled = false;
if (Collider.Enabled)
{
//deactivating the collider -> make the main limb inherit the collider's velocity because it'll control the movement now
MainLimb.body.LinearVelocity = Collider.LinearVelocity;
Collider.Enabled = false;
}
Collider.LinearVelocity = MainLimb.LinearVelocity;
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
//reset pull joints to prevent the character from "hanging" mid-air if pull joints had been active when the character was still moving
@@ -386,6 +394,12 @@ namespace Barotrauma
DragCharacter(character.SelectedCharacter, deltaTime);
}
if (Anim != Animation.CPR)
{
cprAnimTimer = 0.0f;
cprPumpTimer = 0.0f;
}
switch (Anim)
{
case Animation.Climbing:
@@ -648,14 +662,6 @@ namespace Barotrauma
if (!onGround)
{
Vector2 move = torso.PullJointWorldAnchorB - torso.SimPosition;
foreach (Limb limb in Limbs)
{
if (limb.IsSevered) { continue; }
MoveLimb(limb, limb.SimPosition + move, 15.0f, true);
}
return;
}
@@ -1318,14 +1324,14 @@ namespace Barotrauma
}
}
void UpdateFallingProne(float strength)
void UpdateFallingProne(float strength, bool moveHands = true, bool moveTorso = true, bool moveLegs = true)
{
if (strength <= 0.0f) { return; }
Limb head = GetLimb(LimbType.Head);
Limb torso = GetLimb(LimbType.Torso);
if (head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
if (moveHands && head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
{
//if the head is moving, try to protect it with the hands
Limb leftHand = GetLimb(LimbType.LeftHand);
@@ -1347,7 +1353,7 @@ namespace Barotrauma
//make the torso tip over
//otherwise it tends to just drop straight down, pinning the characters legs in a weird pose
if (!InWater)
if (moveTorso && !InWater)
{
//prefer tipping over in the same direction the torso is rotating
//or moving
@@ -1358,27 +1364,30 @@ namespace Barotrauma
}
//attempt to make legs stay in a straight line with the torso to prevent the character from doing a split
for (int i = 0; i < 2; i++)
if (moveLegs)
{
var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh);
if (thigh == null) { continue; }
if (thigh.IsSevered) { continue; }
float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation));
float diff = torso.Rotation - thigh.Rotation;
if (MathUtils.IsValid(diff))
for (int i = 0; i < 2; i++)
{
float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f;
thigh.body.ApplyTorque(thighTorque * strength);
}
var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh);
if (thigh == null) { continue; }
if (thigh.IsSevered) { continue; }
float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation));
float diff = torso.Rotation - thigh.Rotation;
if (MathUtils.IsValid(diff))
{
float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f;
thigh.body.ApplyTorque(thighTorque * strength);
}
var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg);
if (leg == null || leg.IsSevered) { continue; }
float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation));
diff = torso.Rotation - leg.Rotation;
if (MathUtils.IsValid(diff))
{
float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f;
leg.body.ApplyTorque(legTorque * strength);
var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg);
if (leg == null || leg.IsSevered) { continue; }
float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation));
diff = torso.Rotation - leg.Rotation;
if (MathUtils.IsValid(diff))
{
float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f;
leg.body.ApplyTorque(legTorque * strength);
}
}
}
}
@@ -1398,7 +1407,8 @@ namespace Barotrauma
Crouching = true;
Vector2 diff = target.SimPosition - character.SimPosition;
Vector2 offset = Vector2.UnitX * -Dir * 0.75f;
Vector2 diff = (target.SimPosition + offset) - character.SimPosition;
Limb targetHead = target.AnimController.GetLimb(LimbType.Head);
Limb targetTorso = target.AnimController.GetLimb(LimbType.Torso);
if (targetTorso == null)
@@ -1412,7 +1422,23 @@ namespace Barotrauma
Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition;
targetMovement = new Vector2(diff.X, 0.0f);
const float CloseEnough = 0.1f;
if (Math.Abs(targetMovement.X) < CloseEnough)
{
targetMovement.X = 0.0f;
}
TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left;
//if the target's in some weird pose, we may not be able to flip it so it's facing up,
//so let's only try it once so we don't end up constantly flipping it
if (cprAnimTimer <= 0.0f && target.AnimController.Direction == TargetDir)
{
target.AnimController.Flip();
}
(target.AnimController as HumanoidAnimController)?.UpdateFallingProne(strength: 1.0f, moveHands: false, moveTorso: false);
head.Disabled = true;
torso.Disabled = true;
UpdateStanding();
@@ -1443,73 +1469,64 @@ namespace Barotrauma
}
}
//pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17)
if (cprAnimTimer > 15.0f && targetHead != null && head != null)
//Serverside code
if (GameMain.NetworkMember is not { IsClient: true })
{
float yPos = (float)Math.Sin(cprAnimTimer) * 0.2f;
head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.3f + yPos);
if (target.Oxygen < -10.0f)
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
}
if (targetHead != null && head != null)
{
head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f);
head.PullJointEnabled = true;
torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.2f));
torso.PullJointEnabled = true;
//Serverside code
if (GameMain.NetworkMember is not { IsClient: true })
{
if (target.Oxygen < -10.0f)
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
}
}
else
torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f));
torso.PullJointEnabled = true;
if (cprPumpTimer >= 1)
{
if (targetHead != null && head != null)
torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
cprPumpTimer = 0;
if (skill < CPRSettings.Active.DamageSkillThreshold)
{
head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f);
head.PullJointEnabled = true;
target.LastDamageSource = null;
target.DamageLimb(
targetTorso.WorldPosition, targetTorso,
new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) },
stun: 0.0f,
playSound: true,
attackImpulse: Vector2.Zero,
attacker: null);
}
torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f));
torso.PullJointEnabled = true;
if (cprPump >= 1)
//need to CPR for at least a couple of seconds before the target can be revived
//(reviving the target when the CPR has barely started looks strange)
if (cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
{
torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
cprPump = 0;
float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax);
reviveChance *= 1f + cprBoost;
if (skill < CPRSettings.Active.DamageSkillThreshold)
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance)
{
target.LastDamageSource = null;
target.DamageLimb(
targetTorso.WorldPosition, targetTorso,
new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) },
0.0f, true, 0.0f, attacker: null);
}
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
{
float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax);
reviveChance *= 1f + cprBoost;
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance)
{
//increase oxygen and clamp it above zero
// -> the character should be revived if there are no major afflictions in addition to lack of oxygen
target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f);
}
//increase oxygen and clamp it above zero
// -> the character should be revived if there are no major afflictions in addition to lack of oxygen
target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f);
}
}
cprPump += deltaTime;
}
cprAnimTimer = (cprAnimTimer + deltaTime) % 17;
cprPumpTimer += deltaTime;
cprAnimTimer += deltaTime;
//got the character back into a non-critical state, increase medical skill
//BUT only if it has been more than 10 seconds since the character revived someone
@@ -437,7 +437,18 @@ namespace Barotrauma
foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
{
if (!inEditor && huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; }
AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty), huskAppendage, ragdoll: this);
Identifier afflictionIdentifier = huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty);
if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab affliction) ||
affliction is not AfflictionPrefabHusk matchingAffliction)
{
DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!",
contentPackage: huskAppendage.ContentPackage);
}
else
{
AfflictionHusk.AttachHuskAppendage(character, matchingAffliction, huskAppendage, ragdoll: this);
}
}
}
}
@@ -1,11 +1,12 @@
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma
{
{
public enum HitDetection
{
Distance,
@@ -391,7 +392,8 @@ namespace Barotrauma
element.GetAttribute("burndamage") != null ||
element.GetAttribute("bleedingdamage") != null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. <Affliction identifier=\"internaldamage\" strength=\"10\" />).");
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. <Affliction identifier=\"internaldamage\" strength=\"10\" />).",
contentPackage: element.ContentPackage);
}
//if level wall damage is not defined, default to the structure damage
@@ -414,12 +416,14 @@ namespace Barotrauma
AfflictionPrefab afflictionPrefab;
if (subElement.GetAttribute("name") != null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names.");
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names.",
contentPackage: element.ContentPackage);
string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, System.StringComparison.OrdinalIgnoreCase));
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.",
contentPackage: element.ContentPackage);
continue;
}
}
@@ -428,7 +432,8 @@ namespace Barotrauma
Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out afflictionPrefab))
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.");
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.",
contentPackage: element.ContentPackage);
continue;
}
}
@@ -441,7 +446,7 @@ namespace Barotrauma
}
partial void InitProjSpecific(ContentXElement element);
public void ReloadAfflictions(XElement element, string parentDebugName)
public void ReloadAfflictions(ContentXElement element, string parentDebugName)
{
Afflictions.Clear();
foreach (var subElement in element.GetChildElements("affliction"))
@@ -450,13 +455,14 @@ namespace Barotrauma
Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab afflictionPrefab))
{
DebugConsole.ThrowError($"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\".");
DebugConsole.ThrowError($"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\".",
contentPackage: element.ContentPackage);
continue;
}
affliction = afflictionPrefab.Instantiate(0.0f);
affliction.Deserialize(subElement);
//backwards compatibility
if (subElement.Attribute("amount") != null && subElement.Attribute("strength") == null)
if (subElement.GetAttribute("amount") != null && subElement.GetAttribute("strength") == null)
{
affliction.Strength = subElement.GetAttributeFloat("amount", 0.0f);
}
@@ -465,7 +471,7 @@ namespace Barotrauma
}
}
public void Serialize(XElement element)
public void Serialize(ContentXElement element)
{
SerializableProperty.SerializeProperties(this, element, true);
foreach (var affliction in Afflictions)
@@ -477,7 +483,7 @@ namespace Barotrauma
}
}
public void Deserialize(XElement element, string parentDebugName)
public void Deserialize(ContentXElement element, string parentDebugName)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
ReloadAfflictions(element, parentDebugName);
@@ -497,8 +503,9 @@ namespace Barotrauma
SetUser(attacker);
DamageParticles(deltaTime, worldPosition);
var attackResult = target?.AddDamage(attacker, worldPosition, this, deltaTime, playSound) ?? new AttackResult();
Vector2 impulseDirection = GetImpulseDirection(target as ISpatialEntity, worldPosition, SourceItem);
var attackResult = target?.AddDamage(attacker, worldPosition, this, impulseDirection, deltaTime, playSound) ?? new AttackResult();
var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure;
var additionalEffectType = ActionType.OnUse;
if (targetCharacter != null && targetCharacter.IsDead)
@@ -606,7 +613,7 @@ namespace Barotrauma
float penetration = Penetration;
RangedWeapon weapon =
SourceItem?.GetComponent<RangedWeapon>() ??
SourceItem?.GetComponent<RangedWeapon>() ??
SourceItem?.GetComponent<Projectile>()?.Launcher?.GetComponent<RangedWeapon>();
float? penetrationValue = weapon?.Penetration;
if (penetrationValue.HasValue)
@@ -614,7 +621,8 @@ namespace Barotrauma
penetration += penetrationValue.Value;
}
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration);
Vector2 impulseDirection = GetImpulseDirection(targetLimb, worldPosition, SourceItem);
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, impulseDirection, playSound, targetLimb, penetration);
var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure;
foreach (StatusEffect effect in statusEffects)
@@ -666,6 +674,34 @@ namespace Barotrauma
return attackResult;
}
private Vector2 GetImpulseDirection(ISpatialEntity target, Vector2 sourceWorldPosition, Item sourceItem)
{
Vector2 impulseDirection = Vector2.Zero;
if (target != null)
{
impulseDirection = target.WorldPosition - sourceWorldPosition;
}
if (sourceItem?.body != null && sourceItem.body.Enabled && sourceItem.body.LinearVelocity.LengthSquared() > 0.0f)
{
impulseDirection = sourceItem.body.LinearVelocity;
}
else
{
var projectileComponent = sourceItem?.GetComponent<Projectile>();
if (projectileComponent != null)
{
impulseDirection = new Vector2(MathF.Cos(SourceItem.Rotation), MathF.Sin(SourceItem.Rotation));
}
}
if (impulseDirection.LengthSquared() > 0.0001f)
{
impulseDirection = Vector2.Normalize(impulseDirection);
}
return impulseDirection;
}
public float AttackTimer { get; private set; }
public float CoolDownTimer { get; set; }
public float CurrentRandomCoolDown { get; private set; }
@@ -1098,6 +1098,15 @@ namespace Barotrauma
set { CharacterHealth.Unkillable = value; }
}
/// <summary>
/// Is the health interface available on this character? Can be used by status effects
/// </summary>
public bool UseHealthWindow
{
get { return CharacterHealth.UseHealthWindow; }
set { CharacterHealth.UseHealthWindow = value; }
}
public CampaignMode.InteractionType CampaignInteractionType;
public Identifier MerchantIdentifier;
@@ -1223,19 +1232,15 @@ namespace Barotrauma
public static Character Create(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool spawnInitialItems = true)
{
Character newCharacter = null;
if (prefab.Identifier != CharacterPrefab.HumanSpeciesName)
if (prefab.Identifier != CharacterPrefab.HumanSpeciesName || hasAi)
{
var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems);
var ai = new EnemyAIController(aiCharacter, seed);
var ai = (prefab.Identifier == CharacterPrefab.HumanSpeciesName || aiCharacter.Params.UseHumanAI) ?
new HumanAIController(aiCharacter) as AIController :
new EnemyAIController(aiCharacter, seed);
aiCharacter.SetAI(ai);
newCharacter = aiCharacter;
}
else if (hasAi)
{
var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems);
var ai = new HumanAIController(aiCharacter);
aiCharacter.SetAI(ai);
newCharacter = aiCharacter;
newCharacter = aiCharacter;
}
else
{
@@ -1282,7 +1287,8 @@ namespace Barotrauma
{
if (!VariantOf.IsEmpty)
{
DebugConsole.ThrowError("The variant system does not yet support humans, sorry. It does support other humanoids though!");
DebugConsole.ThrowError("The variant system does not yet support humans, sorry. It does support other humanoids though!",
contentPackage: Prefab.ContentPackage);
}
if (characterInfo == null)
{
@@ -1406,7 +1412,8 @@ namespace Barotrauma
if (matchingAffliction == null || nonHuskedSpeciesName.IsEmpty)
{
DebugConsole.ThrowError($"Cannot find a husk infection that matches {speciesName}! Please make sure that the speciesname is added as 'targets' in the husk affliction prefab definition!\n"
+ "Note that all the infected speciesnames and files must stick the following pattern: [nonhuskedspeciesname][huskedspeciesname]. E.g. Humanhusk, Crawlerhusk, or Humancustomhusk, or Crawlerzombie. Not \"Customhumanhusk!\" or \"Zombiecrawler\"");
+ "Note that all the infected speciesnames and files must stick the following pattern: [nonhuskedspeciesname][huskedspeciesname]. E.g. Humanhusk, Crawlerhusk, or Humancustomhusk, or Crawlerzombie. Not \"Customhumanhusk!\" or \"Zombiecrawler\"",
contentPackage: Prefab.ContentPackage);
// Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg.
nonHuskedSpeciesName = IsHumanoid ? CharacterPrefab.HumanSpeciesName : "crawler".ToIdentifier();
speciesName = nonHuskedSpeciesName;
@@ -2407,6 +2414,11 @@ namespace Barotrauma
}
else if (body.UserData is Item item)
{
if (item.GetComponent<Door>() is { HasWindow: true } door)
{
if (door.IsPositionOnWindow(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition))) { return false; }
}
return item != target;
}
return true;
@@ -2768,9 +2780,17 @@ namespace Barotrauma
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
{
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item && (body.UserData as ItemComponent)?.Item != item && Submarine.LastPickedFixture?.UserData as Item != item)
{
return false;
if (body != null)
{
var otherItem = body.UserData as Item ?? (body.UserData as ItemComponent)?.Item;
if (otherItem != item &&
(body.UserData as ItemComponent)?.Item != item &&
/*allow interacting through open doors (e.g. duct blocks' colliders stay active despite being open)*/
otherItem?.GetComponent<Door>() is not { IsOpen: true } &&
Submarine.LastPickedFixture?.UserData as Item != item)
{
return false;
}
}
}
@@ -2797,7 +2817,12 @@ namespace Barotrauma
public void DeselectCharacter()
{
if (SelectedCharacter == null) { return; }
SelectedCharacter.AnimController?.ResetPullJoints();
if (!SelectedCharacter.AllowInput)
{
//we cannot reset the pull joints if the target is conscious (moving on its own),
//that'd interfere with its animations
SelectedCharacter.AnimController?.ResetPullJoints();
}
SelectedCharacter = null;
}
@@ -3301,10 +3326,7 @@ namespace Barotrauma
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.2f; }
}
if (IsRagdolled)
{
SetInput(InputType.Ragdoll, false, true);
}
SetInput(InputType.Ragdoll, false, IsRagdolled);
}
if (!wasRagdolled && IsRagdolled)
{
@@ -3980,15 +4002,15 @@ namespace Barotrauma
CharacterHealth.SetAllDamage(damageAmount, bleedingDamageAmount, burnDamageAmount);
}
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true)
{
return ApplyAttack(attacker, worldPosition, attack, deltaTime, playSound, null);
return ApplyAttack(attacker, worldPosition, attack, deltaTime, impulseDirection, playSound);
}
/// <summary>
/// Apply the specified attack to this character. If the targetLimb is not specified, the limb closest to worldPosition will receive the damage.
/// </summary>
public AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false, Limb targetLimb = null, float penetration = 0f)
public AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, Vector2 impulseDirection, bool playSound = false, Limb targetLimb = null, float penetration = 0f)
{
if (Removed)
{
@@ -4000,7 +4022,16 @@ namespace Barotrauma
Limb limbHit = targetLimb;
float attackImpulse = attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier * deltaTime;
float impulseMagnitude = (attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier) * deltaTime;
Vector2 attackImpulse = Vector2.Zero;
if (Math.Abs(impulseMagnitude) > 0.0f)
{
impulseDirection = impulseDirection.LengthSquared() > 0.0001f ?
Vector2.Normalize(impulseDirection) :
Vector2.UnitX;
attackImpulse = impulseDirection * impulseMagnitude;
}
AbilityAttackData attackData = new AbilityAttackData(attack, this, attacker);
IEnumerable<Affliction> attackAfflictions;
@@ -4129,12 +4160,12 @@ namespace Barotrauma
}
}
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null, float damageMultiplier = 1f)
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, Vector2? attackImpulse = null, Character attacker = null, float damageMultiplier = 1f)
{
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker, damageMultiplier: damageMultiplier);
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse ?? Vector2.Zero, out _, attacker, damageMultiplier: damageMultiplier);
}
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1)
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, Vector2 attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1)
{
hitLimb = null;
@@ -4167,7 +4198,7 @@ namespace Barotrauma
CreatureMetrics.RecordKill(target.SpeciesName);
}
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, float penetration = 0f, bool shouldImplode = false)
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false)
{
if (Removed) { return new AttackResult(); }
@@ -4200,18 +4231,17 @@ namespace Barotrauma
}
Vector2 dir = hitLimb.WorldPosition - worldPosition;
if (Math.Abs(attackImpulse) > 0.0f)
if (attackImpulse.LengthSquared() > 0.0f)
{
Vector2 diff = dir;
if (diff == Vector2.Zero) { diff = Rand.Vector(1.0f); }
Vector2 impulse = Vector2.Normalize(diff) * attackImpulse;
Vector2 hitPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff);
hitLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
hitLimb.body.ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
var mainLimb = hitLimb.character.AnimController.MainLimb;
if (hitLimb != mainLimb)
{
// Always add force to mainlimb
mainLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
mainLimb.body.ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
bool wasDead = IsDead;
@@ -4656,7 +4686,7 @@ namespace Barotrauma
}
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log);
public void Revive(bool removeAfflictions = true)
public void Revive(bool removeAfflictions = true, bool createNetworkEvent = false)
{
if (Removed)
{
@@ -4705,7 +4735,11 @@ namespace Barotrauma
limb.IsSevered = false;
}
GameMain.GameSession?.ReviveCharacter(this);
GameMain.GameSession?.ReviveCharacter(this);
if (createNetworkEvent && GameMain.NetworkMember is { IsServer: true })
{
GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData());
}
}
public override void Remove()
@@ -4986,7 +5020,9 @@ namespace Barotrauma
float maxDistance = 1000f;
foreach (var hull in adjacentHulls)
{
if (hull.ConnectedGaps.Any(g => g.Open > 0.9f && g.linkedTo.Contains(CurrentHull) &&
if (hull.ConnectedGaps.Any(g =>
(g.Open > 0.9f || g.ConnectedDoor is { HasWindow: true }) &&
g.linkedTo.Contains(CurrentHull) &&
Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2)))
{
if (Vector2.DistanceSquared(hull.WorldPosition, WorldPosition) < Math.Pow(maxDistance, 2))
@@ -5005,7 +5041,7 @@ namespace Barotrauma
else
{
if (h.ConnectedGaps.Any(g =>
g.Open > 0.9f &&
(g.Open > 0.9f || g.ConnectedDoor is { HasWindow: true }) &&
Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2) &&
CanSeeTarget(g)))
{
@@ -5027,7 +5063,7 @@ namespace Barotrauma
public bool IsEngineer => HasJob("engineer");
public bool IsMechanic => HasJob("mechanic");
public bool IsMedic => HasJob("medicaldoctor");
public bool IsSecurity => HasJob("securityofficer") || HasJob("vipsecurityofficer");
public bool IsSecurity => HasJob("securityofficer") || HasJob("vipsecurityofficer") || HasJob("outpostsecurityofficer");
public bool IsAssistant => HasJob("assistant");
public bool IsWatchman => HasJob("watchman");
public bool IsVip => HasJob("prisoner");
@@ -767,7 +767,7 @@ namespace Barotrauma
}
// Used for loading the data
public CharacterInfo(XElement infoElement, Identifier npcIdentifier = default)
public CharacterInfo(ContentXElement infoElement, Identifier npcIdentifier = default)
{
ID = idCounter;
idCounter++;
@@ -1311,12 +1311,12 @@ namespace Barotrauma
OnExperienceChanged(prevAmount, ExperiencePoints);
}
const int BaseExperienceRequired = -50;
const int BaseExperienceRequired = 450;
const int AddedExperienceRequiredPerLevel = 500;
public int GetTotalTalentPoints()
{
return GetCurrentLevel() + AdditionalTalentPoints - 1;
return GetCurrentLevel() + AdditionalTalentPoints;
}
public int GetAvailableTalentPoints()
@@ -1342,16 +1342,19 @@ namespace Barotrauma
return experienceRequired + ExperienceRequiredPerLevel(level);
}
/// <summary>
/// How much more experience does the character need to reach the specified level?
/// </summary>
public int GetExperienceRequiredForLevel(int level)
{
int currentLevel = GetCurrentLevel(out int experienceRequired);
int currentLevel = GetCurrentLevel();
if (currentLevel >= level) { return 0; }
int required = experienceRequired;
for (int i = currentLevel + 1; i <= level; i++)
int required = 0;
for (int i = 0; i < level; i++)
{
required += ExperienceRequiredPerLevel(i);
}
return required;
return required - ExperiencePoints;
}
public int GetCurrentLevel()
@@ -1361,7 +1364,7 @@ namespace Barotrauma
private int GetCurrentLevel(out int experienceRequired)
{
int level = 1;
int level = 0;
experienceRequired = 0;
while (experienceRequired + ExperienceRequiredPerLevel(level) <= ExperiencePoints)
{
@@ -95,7 +95,8 @@ namespace Barotrauma
name = ParseName(mainElement, file);
if (name == Identifier.Empty)
{
DebugConsole.ThrowError($"No species name defined for: {file.Path}");
DebugConsole.ThrowError($"No species name defined for: {file.Path}",
contentPackage: file.ContentPackage);
return false;
}
return true;
@@ -75,7 +75,8 @@ namespace Barotrauma
HuskPrefab = prefab as AfflictionPrefabHusk;
if (HuskPrefab == null)
{
DebugConsole.ThrowError("Error in husk affliction definition: the prefab is of wrong type!");
DebugConsole.ThrowError("Error in husk affliction definition: the prefab is of wrong type!",
contentPackage: prefab.ContentPackage);
}
}
@@ -197,7 +198,7 @@ namespace Barotrauma
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / limbCount));
character.LastDamageSource = null;
float force = applyForce ? random * 0.5f * limb.Mass : 0;
character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, Rand.Vector(force));
}
}
@@ -205,7 +206,7 @@ namespace Barotrauma
{
if (huskAppendage == null && character.Params.UseHuskAppendage)
{
huskAppendage = AttachHuskAppendage(character, Prefab.Identifier);
huskAppendage = AttachHuskAppendage(character, Prefab as AfflictionPrefabHusk);
}
if (Prefab is AfflictionPrefabHusk { NeedsAir: false })
@@ -285,13 +286,14 @@ namespace Barotrauma
if (prefab == null)
{
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.",
contentPackage: Prefab.ContentPackage);
yield return CoroutineStatus.Success;
}
XElement parentElement = new XElement("CharacterInfo");
XElement infoElement = character.Info?.Save(parentElement);
CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement);
CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(new ContentXElement(Prefab.ContentPackage, infoElement));
if (huskCharacterInfo != null)
{
@@ -371,31 +373,28 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
public static List<Limb> AttachHuskAppendage(Character character, Identifier afflictionIdentifier, ContentXElement appendageDefinition = null, Ragdoll ragdoll = null)
public static List<Limb> AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, ContentXElement appendageDefinition = null, Ragdoll ragdoll = null)
{
var appendage = new List<Limb>();
if (!(AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier) is AfflictionPrefabHusk matchingAffliction))
{
DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!");
return appendage;
}
Identifier nonhuskedSpeciesName = GetNonHuskedSpeciesName(character.SpeciesName, matchingAffliction);
Identifier huskedSpeciesName = GetHuskedSpeciesName(nonhuskedSpeciesName, matchingAffliction);
CharacterPrefab huskPrefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
if (huskPrefab?.ConfigElement == null)
{
DebugConsole.ThrowError($"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!");
DebugConsole.ThrowError($"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!",
contentPackage: matchingAffliction.ContentPackage);
return appendage;
}
var mainElement = huskPrefab.ConfigElement;
var element = appendageDefinition;
if (element == null)
{
element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeIdentifier("affliction", Identifier.Empty) == afflictionIdentifier);
element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeIdentifier("affliction", Identifier.Empty) == matchingAffliction.Identifier);
}
if (element == null)
{
DebugConsole.ThrowError($"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{afflictionIdentifier}'!");
DebugConsole.ThrowError($"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{matchingAffliction.Identifier}'!",
contentPackage: matchingAffliction.ContentPackage);
return appendage;
}
ContentPath pathToAppendage = element.GetAttributeContentPath("path") ?? ContentPath.Empty;
@@ -170,11 +170,13 @@ namespace Barotrauma
if (DormantThreshold > ActiveThreshold)
{
DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(DormantThreshold)} is greater than {nameof(ActiveThreshold)} ({DormantThreshold} > {ActiveThreshold})");
DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(DormantThreshold)} is greater than {nameof(ActiveThreshold)} ({DormantThreshold} > {ActiveThreshold})",
contentPackage: element.ContentPackage);
}
if (ActiveThreshold > TransitionThreshold)
{
DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(ActiveThreshold)} is greater than {nameof(TransitionThreshold)} ({ActiveThreshold} > {TransitionThreshold})");
DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(ActiveThreshold)} is greater than {nameof(TransitionThreshold)} ({ActiveThreshold} > {TransitionThreshold})",
contentPackage: element.ContentPackage);
}
TransformThresholdOnDeath = element.GetAttributeFloat("transformthresholdondeath", ActiveThreshold);
@@ -440,13 +442,15 @@ namespace Barotrauma
AbilityFlags flagType = subElement.GetAttributeEnum("flagtype", AbilityFlags.None);
if (flagType is AbilityFlags.None)
{
DebugConsole.ThrowError($"Error in affliction \"{parentDebugName}\" - invalid ability flag type \"{subElement.GetAttributeString("flagtype", "")}\".");
DebugConsole.ThrowError($"Error in affliction \"{parentDebugName}\" - invalid ability flag type \"{subElement.GetAttributeString("flagtype", "")}\".",
contentPackage: element.ContentPackage);
continue;
}
AfflictionAbilityFlags |= flagType;
break;
case "affliction":
DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.");
DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.",
contentPackage: element.ContentPackage);
break;
}
}
@@ -537,14 +541,16 @@ namespace Barotrauma
}
else if (TextTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.");
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.",
contentPackage: element.ContentPackage);
}
MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f);
MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
if (MinStrength >= MaxStrength)
{
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.");
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.",
contentPackage: element.ContentPackage);
}
Target = element.GetAttributeEnum(nameof(Target), TargetType.Any);
}
@@ -953,7 +959,8 @@ namespace Barotrauma
AfflictionOverlay = new Sprite(subElement);
break;
case "statvalue":
DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.");
DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.",
contentPackage: element.ContentPackage);
break;
case "effect":
case "periodiceffect":
@@ -962,7 +969,8 @@ namespace Barotrauma
descriptions.Add(new Description(subElement, this));
break;
default:
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})");
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})",
contentPackage: element.ContentPackage);
break;
}
}
@@ -1046,7 +1054,8 @@ namespace Barotrauma
var b = effects[j];
if (a.MinStrength < b.MaxStrength && b.MinStrength < a.MaxStrength)
{
DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work.");
DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work.",
ContentPackage);
}
}
}
@@ -49,7 +49,8 @@ namespace Barotrauma
case "vitalitymultiplier":
if (subElement.GetAttribute("name") != null)
{
DebugConsole.ThrowError("Error in character health config (" + characterHealth.Character.Name + ") - define vitality multipliers using affliction identifiers or types instead of names.");
DebugConsole.ThrowError("Error in character health config (" + characterHealth.Character.Name + ") - define vitality multipliers using affliction identifiers or types instead of names.",
contentPackage: element.ContentPackage);
continue;
}
var vitalityMultipliers = subElement.GetAttributeIdentifierArray("identifier", null) ?? subElement.GetAttributeIdentifierArray("identifiers", null);
@@ -61,7 +62,8 @@ namespace Barotrauma
VitalityMultipliers.Add(vitalityMultiplier, multiplier);
if (AfflictionPrefab.Prefabs.None(p => p.Identifier == vitalityMultiplier))
{
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?");
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?",
contentPackage: element.ContentPackage);
}
}
}
@@ -74,13 +76,15 @@ namespace Barotrauma
VitalityTypeMultipliers.Add(vitalityTypeMultiplier, multiplier);
if (AfflictionPrefab.Prefabs.None(p => p.AfflictionType == vitalityTypeMultiplier))
{
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?");
DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?",
contentPackage: element.ContentPackage);
}
}
}
if (vitalityMultipliers == null && VitalityTypeMultipliers == null)
{
DebugConsole.ThrowError($"Error in character health config {characterHealth.Character.Name}: affliction identifier(s) or type(s) not defined in the \"VitalityMultiplier\" elements!");
DebugConsole.ThrowError($"Error in character health config {characterHealth.Character.Name}: affliction identifier(s) or type(s) not defined in the \"VitalityMultiplier\" elements!",
contentPackage: element.ContentPackage);
}
break;
}
@@ -148,11 +152,6 @@ namespace Barotrauma
return minVitality;
}
return vitality;
}
private set
{
vitality = value;
}
}
@@ -254,7 +253,7 @@ namespace Barotrauma
public CharacterHealth(Character character)
{
this.Character = character;
Vitality = 100.0f;
vitality = 100.0f;
DoesBleed = true;
UseHealthWindow = false;
@@ -271,7 +270,7 @@ namespace Barotrauma
this.Character = character;
InitIrremovableAfflictions();
Vitality = UnmodifiedMaxVitality;
vitality = UnmodifiedMaxVitality;
minVitality = character.IsHuman ? -100.0f : 0.0f;
@@ -971,7 +970,7 @@ namespace Barotrauma
public void CalculateVitality()
{
Vitality = MaxVitality;
vitality = MaxVitality;
IsParalyzed = false;
if (Unkillable || Character.GodMode) { return; }
@@ -984,7 +983,7 @@ namespace Barotrauma
{
vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
}
Vitality -= vitalityDecrease;
vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease);
if (affliction.Strength >= affliction.Prefab.MaxStrength &&
@@ -79,12 +79,13 @@ namespace Barotrauma
public ref readonly ImmutableArray<Identifier> ParsedAfflictionTypes => ref parsedAfflictionTypes;
public DamageModifier(XElement element, string parentDebugName, bool checkErrors = true)
public DamageModifier(ContentXElement element, string parentDebugName, bool checkErrors = true)
{
Deserialize(element);
if (element.Attribute("afflictionnames") != null)
if (element.GetAttribute("afflictionnames") != null)
{
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.",
contentPackage: element.ContentPackage);
}
if (checkErrors)
{
@@ -108,12 +109,12 @@ namespace Barotrauma
}
}
static void createWarningOrError(string msg)
void createWarningOrError(string msg)
{
#if DEBUG
DebugConsole.ThrowError(msg);
DebugConsole.ThrowError(msg, contentPackage: element.ContentPackage);
#else
DebugConsole.AddWarning(msg);
DebugConsole.AddWarning(msg, contentPackage: element.ContentPackage);
#endif
}
}
@@ -117,8 +117,8 @@ namespace Barotrauma
public XElement Element { get; protected set; }
public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>();
public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>();
public readonly List<(ContentXElement element, float commonness)> ItemSets = new List<(ContentXElement element, float commonness)>();
public readonly List<(ContentXElement element, float commonness)> CustomCharacterInfos = new List<(ContentXElement element, float commonness)>();
public readonly Identifier NpcSetIdentifier;
@@ -196,7 +196,7 @@ namespace Barotrauma
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element;
if (spawnItems != null)
{
foreach (XElement itemElement in spawnItems.GetChildElements("item"))
foreach (ContentXElement itemElement in spawnItems.GetChildElements("item"))
{
int amount = itemElement.GetAttributeInt("amount", 1);
for (int i = 0; i < amount; i++)
@@ -239,14 +239,15 @@ namespace Barotrauma
return characterInfo;
}
public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, WayPoint spawnPoint = null, Item parentItem = null, bool createNetworkEvents = true)
public static void InitializeItem(Character character, ContentXElement itemElement, Submarine submarine, HumanPrefab humanPrefab, WayPoint spawnPoint = null, Item parentItem = null, bool createNetworkEvents = true)
{
ItemPrefab itemPrefab;
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier.ToIdentifier()) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Tried to spawn \"" + humanPrefab?.Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found.");
DebugConsole.ThrowError("Tried to spawn \"" + humanPrefab?.Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found.",
contentPackage: itemElement?.ContentPackage);
return;
}
Item item = new Item(itemPrefab, character.Position, null);
@@ -301,7 +302,7 @@ namespace Barotrauma
wifiComponent.TeamID = character.TeamID;
}
parentItem?.Combine(item, user: null);
foreach (XElement childItemElement in itemElement.Elements())
foreach (ContentXElement childItemElement in itemElement.Elements())
{
int amount = childItemElement.GetAttributeInt("amount", 1);
for (int i = 0; i < amount; i++)
@@ -47,13 +47,14 @@ namespace Barotrauma
}
}
public Job(XElement element)
public Job(ContentXElement element)
{
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
JobPrefab p;
if (!JobPrefab.Prefabs.ContainsKey(identifier))
{
DebugConsole.ThrowError($"Could not find the job {identifier}. Giving the character a random job.");
DebugConsole.ThrowError($"Could not find the job {identifier}. Giving the character a random job.",
contentPackage: element.ContentPackage);
p = JobPrefab.Random(Rand.RandSync.Unsynced);
}
else
@@ -43,7 +43,8 @@ namespace Barotrauma
Priority = element.GetAttributeFloat("priority", -1f);
if (Priority < 0)
{
DebugConsole.AddWarning($"The 'priority' attribute is missing from the the item repair priorities definition in {element} of {file.Path}.");
DebugConsole.AddWarning($"The 'priority' attribute is missing from the the item repair priorities definition in {element} of {file.Path}.",
ContentPackage);
}
}
@@ -266,7 +266,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {a} at {selectedFile} of type {animType} for the character {speciesName}");
DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {a} at {selectedFile} of type {animType} for the character {speciesName}",
contentPackage: characterPrefab.ContentPackage);
}
return a;
}
@@ -63,7 +63,8 @@ namespace Barotrauma
{
if (!character.AnimController.CanWalk)
{
DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!");
DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!",
contentPackage: character.Prefab.ContentPackage);
return false;
}
return true;
@@ -50,6 +50,9 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable]
public bool NeedsWater { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Note: non-humans with a human AI aren't fully supported. Enabling this on a non-human character may lead to issues.")]
public bool UseHumanAI { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable]
public bool IsMachine { get; set; }
@@ -556,7 +559,8 @@ namespace Barotrauma
DebugConsole.AddWarning($"Character \"{character.SpeciesName}\" has a negative crush depth. "+
"Previously the crush depths were defined as display units (e.g. -30000 would correspond to 300 meters below the level), "+
"but now they're in meters (e.g. 3000 would correspond to a depth of 3000 meters displayed on the nav terminal). "+
$"Changing the crush depth from {CrushDepth} to {newCrushDepth}.");
$"Changing the crush depth from {CrushDepth} to {newCrushDepth}.",
element.ContentPackage);
CrushDepth = newCrushDepth;
}
}
@@ -708,7 +712,8 @@ namespace Barotrauma
if (HasTag(tag))
{
target = null;
DebugConsole.AddWarning($"Trying to add multiple targets with the same tag ('{tag}') defined! Only the first will be used!");
DebugConsole.AddWarning($"Trying to add multiple targets with the same tag ('{tag}') defined! Only the first will be used!",
targetElement.ContentPackage);
return false;
}
else
@@ -84,12 +84,14 @@ namespace Barotrauma
doc = XMLExtensions.TryLoadXml(Path);
if (doc == null)
{
DebugConsole.ThrowError("[EditableParams] The document is null! Failed to load the parameters.");
DebugConsole.ThrowError("[EditableParams] The document is null! Failed to load the parameters.",
contentPackage: file.ContentPackage);
return false;
}
if (MainElement == null)
{
DebugConsole.ThrowError("[EditableParams] The main element is null! Failed to load the parameters.");
DebugConsole.ThrowError("[EditableParams] The main element is null! Failed to load the parameters.",
contentPackage: file.ContentPackage);
return false;
}
IsLoaded = Deserialize(MainElement);
@@ -106,7 +106,8 @@ namespace Barotrauma
CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier == speciesName && (contentPackage == null || p.ContentFile.ContentPackage == contentPackage));
if (prefab?.ConfigElement == null)
{
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}' (content package {contentPackage?.Name ?? "null"})");
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'",
contentPackage: contentPackage);
return string.Empty;
}
return GetFolder(prefab.ConfigElement, prefab.ContentFile.Path.Value);
@@ -183,7 +184,8 @@ namespace Barotrauma
}
if (error != null)
{
DebugConsole.ThrowError(error);
DebugConsole.ThrowError(error,
contentPackage: prefab?.ContentPackage);
}
}
if (selectedFile == null)
@@ -444,7 +446,8 @@ namespace Barotrauma
{
if (source.MainElement == null)
{
DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!");
DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!",
contentPackage: source.MainElement?.ContentPackage);
return;
}
Deserialize(source.MainElement, alsoChildren: false);
@@ -453,7 +456,8 @@ namespace Barotrauma
// TODO: cannot currently undo joint/limb deletion.
if (sourceSubParams.Count != subParams.Count)
{
DebugConsole.ThrowError("[RagdollParams] The count of the sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.");
DebugConsole.ThrowError("[RagdollParams] The count of the sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.",
contentPackage: source.MainElement?.ContentPackage);
return;
}
for (int i = 0; i < subParams.Count; i++)
@@ -461,7 +465,8 @@ namespace Barotrauma
var subSubParams = subParams[i].SubParams;
if (subSubParams.Count != sourceSubParams[i].SubParams.Count)
{
DebugConsole.ThrowError("[RagdollParams] The count of the sub sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.");
DebugConsole.ThrowError("[RagdollParams] The count of the sub sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.",
contentPackage: source.MainElement?.ContentPackage);
return;
}
subParams[i].Deserialize(sourceSubParams[i].Element, recursive: false);
@@ -890,14 +895,14 @@ namespace Barotrauma
#if CLIENT
public DecorativeSprite DecorativeSprite { get; private set; }
public override bool Deserialize(XElement element = null, bool recursive = true)
public override bool Deserialize(ContentXElement element = null, bool recursive = true)
{
base.Deserialize(element, recursive);
DecorativeSprite.SerializableProperties = SerializableProperty.DeserializeProperties(DecorativeSprite, element ?? Element);
return SerializableProperties != null;
}
public override bool Serialize(XElement element = null, bool recursive = true)
public override bool Serialize(ContentXElement element = null, bool recursive = true)
{
base.Serialize(element, recursive);
SerializableProperty.SerializeProperties(DecorativeSprite, element ?? Element);
@@ -985,7 +990,8 @@ namespace Barotrauma
deformation = new PositionalDeformationParams(deformationElement);
break;
default:
DebugConsole.ThrowError($"SpriteDeformationParams not implemented: '{typeName}'");
DebugConsole.ThrowError($"SpriteDeformationParams not implemented: '{typeName}'",
contentPackage: element.ContentPackage);
break;
}
if (deformation != null)
@@ -1000,14 +1006,14 @@ namespace Barotrauma
#if CLIENT
public Dictionary<SpriteDeformationParams, XElement> Deformations { get; private set; }
public override bool Deserialize(XElement element = null, bool recursive = true)
public override bool Deserialize(ContentXElement element = null, bool recursive = true)
{
base.Deserialize(element, recursive);
Deformations.ForEach(d => d.Key.SerializableProperties = SerializableProperty.DeserializeProperties(d.Key, d.Value));
return SerializableProperties != null;
}
public override bool Serialize(XElement element = null, bool recursive = true)
public override bool Serialize(ContentXElement element = null, bool recursive = true)
{
base.Serialize(element, recursive);
Deformations.ForEach(d => SerializableProperty.SerializeProperties(d.Key, d.Value));
@@ -1098,14 +1104,14 @@ namespace Barotrauma
}
#if CLIENT
public override bool Deserialize(XElement element = null, bool recursive = true)
public override bool Deserialize(ContentXElement element = null, bool recursive = true)
{
base.Deserialize(element, recursive);
LightSource.Deserialize(element ?? Element);
return SerializableProperties != null;
}
public override bool Serialize(XElement element = null, bool recursive = true)
public override bool Serialize(ContentXElement element = null, bool recursive = true)
{
base.Serialize(element, recursive);
LightSource.Serialize(element ?? Element);
@@ -1130,14 +1136,14 @@ namespace Barotrauma
Attack = new Attack(element, ragdoll.SpeciesName.Value);
}
public override bool Deserialize(XElement element = null, bool recursive = true)
public override bool Deserialize(ContentXElement element = null, bool recursive = true)
{
base.Deserialize(element, recursive);
Attack.Deserialize(element ?? Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null");
return SerializableProperties != null;
}
public override bool Serialize(XElement element = null, bool recursive = true)
public override bool Serialize(ContentXElement element = null, bool recursive = true)
{
base.Serialize(element, recursive);
Attack.Serialize(element ?? Element);
@@ -1182,14 +1188,14 @@ namespace Barotrauma
DamageModifier = new DamageModifier(element, ragdoll.SpeciesName.Value);
}
public override bool Deserialize(XElement element = null, bool recursive = true)
public override bool Deserialize(ContentXElement element = null, bool recursive = true)
{
base.Deserialize(element, recursive);
DamageModifier.Deserialize(element ?? Element);
return SerializableProperties != null;
}
public override bool Serialize(XElement element = null, bool recursive = true)
public override bool Serialize(ContentXElement element = null, bool recursive = true)
{
base.Serialize(element, recursive);
DamageModifier.Serialize(element ?? Element);
@@ -1218,7 +1224,7 @@ namespace Barotrauma
public virtual string Name { get; set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
public ContentXElement Element { get; set; }
public XElement OriginalElement { get; protected set; }
public ContentXElement OriginalElement { get; protected set; }
public List<SubParam> SubParams { get; set; } = new List<SubParam>();
public RagdollParams Ragdoll { get; private set; }
@@ -1230,14 +1236,14 @@ namespace Barotrauma
public SubParam(ContentXElement element, RagdollParams ragdoll)
{
Element = element;
OriginalElement = new XElement(element);
OriginalElement = new ContentXElement(element.ContentPackage, element);
Ragdoll = ragdoll;
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
public virtual bool Deserialize(XElement element = null, bool recursive = true)
public virtual bool Deserialize(ContentXElement element = null, bool recursive = true)
{
element = element ?? Element;
element ??= Element;
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (recursive)
{
@@ -1246,9 +1252,9 @@ namespace Barotrauma
return SerializableProperties != null;
}
public virtual bool Serialize(XElement element = null, bool recursive = true)
public virtual bool Serialize(ContentXElement element = null, bool recursive = true)
{
element = element ?? Element;
element ??= Element;
SerializableProperty.SerializeProperties(this, element, true);
if (recursive)
{
@@ -13,7 +13,7 @@ namespace Barotrauma.Abilities
public AbilityCondition(CharacterTalent characterTalent, ContentXElement conditionElement)
{
this.characterTalent = characterTalent;
this.characterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent));
character = characterTalent.Character;
invert = conditionElement.GetAttributeBool("invert", false);
}
@@ -40,7 +40,8 @@ namespace Barotrauma.Abilities
{
if (!Enum.TryParse(targetTypeString, true, out TargetType targetType))
{
DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")",
contentPackage: characterTalent.Prefab.ContentPackage);
}
targetTypes.Add(targetType);
}
@@ -1,7 +1,4 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Linq;
namespace Barotrauma.Abilities
{
@@ -34,7 +34,8 @@ namespace Barotrauma.Abilities
string weaponTypeStr = conditionElement.GetAttributeString("weapontype", "Any");
if (!Enum.TryParse(weaponTypeStr, ignoreCase: true, out weapontype))
{
DebugConsole.ThrowError($"Error in talent \"{characterTalent.DebugIdentifier}\": \"{weaponTypeStr}\" is not a valid weapon type.");
DebugConsole.ThrowError($"Error in talent \"{characterTalent.DebugIdentifier}\": \"{weaponTypeStr}\" is not a valid weapon type.",
contentPackage: conditionElement.ContentPackage);
}
}
@@ -17,7 +17,7 @@ namespace Barotrauma.Abilities
conditionElement.GetAttributeStringArray("targettypes",
conditionElement.GetAttributeStringArray("targettype", Array.Empty<string>())));
foreach (XElement subElement in conditionElement.Elements())
foreach (ContentXElement subElement in conditionElement.Elements())
{
if (subElement.NameAsIdentifier() == "conditional")
{
@@ -27,7 +27,8 @@ namespace Barotrauma.Abilities
if (!targetTypes.Any() && !conditionals.Any())
{
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No target types or conditionals defined - the condition will match any character.");
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No target types or conditionals defined - the condition will match any character.",
contentPackage: conditionElement.ContentPackage);
}
}
@@ -17,13 +17,15 @@ namespace Barotrauma.Abilities
protected void LogAbilityConditionError(AbilityObject abilityObject, Type expectedData)
{
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject} in talent {characterTalent.DebugIdentifier}");
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject} in talent {characterTalent.DebugIdentifier}",
contentPackage: characterTalent.Prefab.ContentPackage);
}
protected abstract bool MatchesConditionSpecific(AbilityObject abilityObject);
public override bool MatchesCondition()
{
DebugConsole.ThrowError($"Used data-reliant ability condition in a state-based ability in talent {characterTalent.DebugIdentifier}! This is not allowed.");
DebugConsole.ThrowError($"Used data-reliant ability condition in a state-based ability in talent {characterTalent.DebugIdentifier}! This is not allowed.",
contentPackage: characterTalent.Prefab.ContentPackage);
return false;
}
public override bool MatchesCondition(AbilityObject abilityObject)
@@ -19,7 +19,8 @@ namespace Barotrauma.Abilities
if (identifiers.None() && tags.None() && category == MapEntityCategory.None)
{
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined.");
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined.",
contentPackage: conditionElement.ContentPackage);
}
}
@@ -22,7 +22,8 @@ namespace Barotrauma.Abilities
{
if (!isAffiliated)
{
DebugConsole.ThrowError($"Error in AbilityConditionMission \"{characterTalent.DebugIdentifier}\" - \"{missionTypeString}\" is not a valid mission type.");
DebugConsole.ThrowError($"Error in AbilityConditionMission \"{characterTalent.DebugIdentifier}\" - \"{missionTypeString}\" is not a valid mission type.",
contentPackage: conditionElement.ContentPackage);
}
continue;
}
@@ -12,7 +12,8 @@
statIdentifier = conditionElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
if (statIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!");
DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!",
contentPackage: conditionElement.ContentPackage);
}
string statTypeName = conditionElement.GetAttributeString("stattype", string.Empty);
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, characterTalent.DebugIdentifier);
@@ -13,7 +13,8 @@ namespace Barotrauma.Abilities
tag = conditionElement.GetAttributeIdentifier("tag", Identifier.Empty);
if (tag.IsEmpty)
{
DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.");
DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.",
characterTalent.Prefab.ContentPackage);
}
}
@@ -26,7 +26,7 @@ namespace Barotrauma.Abilities
public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement)
{
CharacterAbilityGroup = characterAbilityGroup;
CharacterAbilityGroup = characterAbilityGroup ?? throw new ArgumentNullException(nameof(characterAbilityGroup));
CharacterTalent = characterAbilityGroup.CharacterTalent;
Character = CharacterTalent.Character;
RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true);
@@ -59,7 +59,8 @@ namespace Barotrauma.Abilities
protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.",
contentPackage: CharacterTalent.Prefab.ContentPackage);
}
public void ApplyAbilityEffect(AbilityObject abilityObject)
@@ -76,17 +77,20 @@ namespace Barotrauma.Abilities
protected virtual void ApplyEffect()
{
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect in talent {CharacterTalent.DebugIdentifier}");
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect in talent {CharacterTalent.DebugIdentifier}",
CharacterTalent.Prefab.ContentPackage);
}
protected virtual void ApplyEffect(AbilityObject abilityObject)
{
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}");
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}",
CharacterTalent.Prefab.ContentPackage);
}
protected void LogAbilityObjectMismatch()
{
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}");
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}",
contentPackage: CharacterTalent.Prefab.ContentPackage);
}
// XML
@@ -99,13 +103,18 @@ namespace Barotrauma.Abilities
abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
if (abilityType == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")");
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")",
contentPackage: abilityElement.ContentPackage);
return null;
}
}
catch (Exception e)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e);
if (errorMessages)
{
DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e,
contentPackage: abilityElement.ContentPackage);
}
return null;
}
@@ -118,7 +127,8 @@ namespace Barotrauma.Abilities
}
catch (TargetInvocationException e)
{
DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException);
DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException,
contentPackage: abilityElement.ContentPackage);
return null;
}
@@ -30,7 +30,8 @@ namespace Barotrauma.Abilities
}
else
{
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type.");
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type.",
contentPackage: abilityElement.ContentPackage);
}
}
}
@@ -21,7 +21,8 @@ namespace Barotrauma.Abilities
JobPrefab? apprenticeJob = GetApprenticeJob(Character, jobPrefabList);
if (apprenticeJob is null)
{
DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}");
DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
@@ -14,7 +14,8 @@
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
if (skillIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -16,7 +16,8 @@
if (afflictionId.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -27,7 +28,8 @@
var afflictionPrefab = AfflictionPrefab.Prefabs.Find(a => a.Identifier == afflictionId);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\".");
DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\".",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
float strength = this.strength;
@@ -14,23 +14,25 @@ internal sealed class CharacterAbilityGiveExperience : CharacterAbility
if (amount == 0 && level == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - no exp amount or level defined in {nameof(CharacterAbilityGiveExperience)}.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - no exp amount or level defined in {nameof(CharacterAbilityGiveExperience)}.",
contentPackage: abilityElement.ContentPackage);
}
if (amount > 0 && level > 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - {nameof(CharacterAbilityGiveExperience)} defines both an exp amount and a level.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - {nameof(CharacterAbilityGiveExperience)} defines both an exp amount and a level.",
contentPackage: abilityElement.ContentPackage);
}
}
private void ApplyEffectSpecific(Character targetCharacter)
{
if (amount != 0)
{
targetCharacter.Info?.GiveExperience(amount);
}
if (level > 0)
{
targetCharacter.Info?.GiveExperience(targetCharacter.Info.GetExperienceRequiredForLevel(level));
targetCharacter.Info?.GiveExperience(targetCharacter.Info.GetExperienceRequiredForLevel(level) + amount);
}
else if (amount != 0)
{
targetCharacter.Info?.GiveExperience(amount);
}
}
@@ -14,7 +14,8 @@
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -28,7 +28,8 @@
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
if (statIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined.");
DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined.",
contentPackage: abilityElement.ContentPackage);
}
string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty);
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier);
@@ -13,11 +13,13 @@ namespace Barotrauma.Abilities
amount = abilityElement.GetAttributeFloat("amount", 0f);
if (factionIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined.",
contentPackage: abilityElement.ContentPackage);
}
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -12,11 +12,13 @@
if (resistanceId.IsEmpty)
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.",
contentPackage: abilityElement.ContentPackage);
}
if (MathUtils.NearlyEqual(multiplier, 1))
{
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing.");
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -9,7 +9,8 @@
amount = abilityElement.GetAttributeInt("amount", 0);
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -11,7 +11,8 @@ namespace Barotrauma.Abilities
amount = abilityElement.GetAttributeInt("amount", 0);
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -16,11 +16,13 @@ namespace Barotrauma.Abilities
if (skillIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill.");
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill.",
contentPackage: abilityElement.ContentPackage);
}
if (MathUtils.NearlyEqual(skillIncrease, 0))
{
DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0.");
DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -8,7 +8,8 @@ namespace Barotrauma.Abilities
identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
if (identifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -15,11 +15,13 @@
if (resistanceId.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}.",
contentPackage: abilityElement.ContentPackage);
}
if (MathUtils.NearlyEqual(multiplier, 1.0f))
{
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing.");
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -11,7 +11,8 @@
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
if (MathUtils.NearlyEqual(addedValue, 0.0f) && MathUtils.NearlyEqual(multiplyValue, 1.0f))
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -11,7 +11,8 @@
amount = abilityElement.GetAttributeInt("amount", 1);
if (itemIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - itemIdentifier not defined.");
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - itemIdentifier not defined.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -19,14 +20,16 @@
{
if (itemIdentifier.IsEmpty)
{
DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined.");
DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined.",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
ItemPrefab itemPrefab = ItemPrefab.Find(null, itemIdentifier);
if (itemPrefab == null)
{
DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found.");
DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found.",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
for (int i = 0; i < amount; i++)
@@ -14,7 +14,8 @@ namespace Barotrauma.Abilities
if (afflictionId.IsEmpty)
{
DebugConsole.ThrowError($"Error in {nameof(CharacterAbilityReduceAffliction)} - affliction identifier not set.");
DebugConsole.ThrowError($"Error in {nameof(CharacterAbilityReduceAffliction)} - affliction identifier not set.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -12,7 +12,8 @@ namespace Barotrauma.Abilities
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
if (statIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty.",
contentPackage: abilityElement.ContentPackage);
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
@@ -13,7 +13,8 @@ namespace Barotrauma.Abilities
value = abilityElement.GetAttributeInt("value", 0);
if (identifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty.",
contentPackage: abilityElement.ContentPackage);
}
}
@@ -24,7 +24,8 @@ namespace Barotrauma.Abilities
JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet());
if (apprentice is null)
{
DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}");
DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
@@ -31,7 +31,7 @@ namespace Barotrauma.Abilities
public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, ContentXElement abilityElementGroup)
{
AbilityEffectType = abilityEffectType;
CharacterTalent = characterTalent;
CharacterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent));
Character = CharacterTalent.Character;
maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
foreach (var subElement in abilityElementGroup.Elements())
@@ -55,7 +55,8 @@ namespace Barotrauma.Abilities
case AbilityEffectType.OnDieToCharacter:
if (characterAbilities.Any(a => a.RequiresAlive))
{
DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute.");
DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute.",
characterTalent.Prefab.ContentPackage);
}
break;
}
@@ -90,7 +91,8 @@ namespace Barotrauma.Abilities
if (newCondition == null)
{
DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!");
DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!",
contentPackage: conditionElement.ContentPackage);
return;
}
@@ -107,7 +109,8 @@ namespace Barotrauma.Abilities
{
if (characterAbility == null)
{
DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!");
DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
@@ -118,7 +121,8 @@ namespace Barotrauma.Abilities
{
if (characterAbility == null)
{
DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!");
DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
@@ -135,13 +139,21 @@ namespace Barotrauma.Abilities
conditionType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
if (conditionType == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")");
if (errorMessages)
{
DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")",
contentPackage: characterTalent.Prefab.ContentPackage);
}
return null;
}
}
catch (Exception e)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e);
if (errorMessages)
{
DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e,
contentPackage: characterTalent.Prefab.ContentPackage);
}
return null;
}
@@ -154,13 +166,15 @@ namespace Barotrauma.Abilities
}
catch (TargetInvocationException e)
{
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException);
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException,
contentPackage: characterTalent.Prefab.ContentPackage);
return null;
}
if (newCondition == null)
{
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null");
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null",
contentPackage: characterTalent.Prefab.ContentPackage);
return null;
}
@@ -189,7 +203,8 @@ namespace Barotrauma.Abilities
if (newAbility == null)
{
DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!");
DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!",
contentPackage: characterTalent.Prefab.ContentPackage);
return null;
}
@@ -200,7 +215,8 @@ namespace Barotrauma.Abilities
{
if (statusEffectElements == null)
{
DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier);
DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier,
contentPackage: characterTalent.Prefab.ContentPackage);
return null;
}
@@ -233,7 +249,8 @@ namespace Barotrauma.Abilities
{
if (afflictionElements == null)
{
DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier);
DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier,
contentPackage: characterTalent.Prefab.ContentPackage);
return null;
}
@@ -248,7 +265,8 @@ namespace Barotrauma.Abilities
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.");
DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.",
contentPackage: characterTalent.Prefab.ContentPackage);
continue;
}
@@ -23,9 +23,8 @@ namespace Barotrauma
public CharacterTalent(TalentPrefab talentPrefab, Character character)
{
Character = character;
Prefab = talentPrefab;
Character = character ?? throw new ArgumentNullException(nameof(character));
Prefab = talentPrefab ?? throw new ArgumentNullException(nameof(talentPrefab));
var element = talentPrefab.ConfigElement;
DebugIdentifier = talentPrefab.OriginalName;
@@ -46,7 +45,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"No recipe identifier defined for talent {DebugIdentifier}");
DebugConsole.ThrowError($"No recipe identifier defined for talent {DebugIdentifier}",
contentPackage: element.ContentPackage);
}
break;
case "addedstoreitem":
@@ -56,7 +56,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"No store item identifier defined for talent {DebugIdentifier}");
DebugConsole.ThrowError($"No store item identifier defined for talent {DebugIdentifier}",
contentPackage: element.ContentPackage);
}
break;
}
@@ -146,11 +147,13 @@ namespace Barotrauma
{
if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType))
{
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")",
contentPackage: characterTalent?.Prefab?.ContentPackage);
}
if (abilityEffectType == AbilityEffectType.Undefined)
{
DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")",
contentPackage: characterTalent?.Prefab?.ContentPackage);
}
return abilityEffectType;
@@ -84,7 +84,8 @@ namespace Barotrauma
}
catch (Exception e)
{
DebugConsole.ThrowError($"Error while loading talent migration for talent \"{Identifier}\".", e);
DebugConsole.ThrowError($"Error while loading talent migration for talent \"{Identifier}\".", e,
element?.ContentPackage);
}
}
break;
@@ -37,7 +37,8 @@ namespace Barotrauma
if (Identifier.IsEmpty)
{
DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!");
DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!",
contentPackage: element.ContentPackage);
return;
}
@@ -304,7 +305,8 @@ namespace Barotrauma
if (RequiredTalents > MaxChosenTalents)
{
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents.");
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents.",
contentPackage: talentOptionsElement.ContentPackage);
}
HashSet<Identifier> identifiers = new HashSet<Identifier>();
@@ -333,11 +335,13 @@ namespace Barotrauma
if (RequiredTalents > talentIdentifiers.Count)
{
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage.");
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage.",
contentPackage: talentOptionsElement.ContentPackage);
}
if (MaxChosenTalents > talentIdentifiers.Count)
{
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents.");
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents.",
contentPackage: talentOptionsElement.ContentPackage);
}
}
}