Build 1.1.4.0

This commit is contained in:
Markus Isberg
2023-03-31 18:40:44 +03:00
parent efba17e0ff
commit 9470edead3
483 changed files with 17487 additions and 8548 deletions
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,11 +15,15 @@ namespace Barotrauma
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
public float PendingAdditionStrength { get; set; }
public float AdditionStrength { get; set; }
public float PendingGrainEffectStrength { get; set; }
public float GrainEffectStrength { get; set; }
private float fluctuationTimer;
private AfflictionPrefab.Effect activeEffect;
private float prevActiveEffectStrength;
protected bool activeEffectDirty = true;
protected float _strength;
[Serialize(0f, IsPropertySaveable.Yes), Editable]
@@ -42,10 +47,11 @@ namespace Barotrauma
float newValue = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength);
if (newValue > _strength)
{
PendingAdditionStrength = Prefab.GrainBurst;
PendingGrainEffectStrength = Prefab.GrainBurst;
Duration = Prefab.Duration;
}
_strength = newValue;
activeEffectDirty = true;
}
}
@@ -68,8 +74,7 @@ namespace Barotrauma
public float DamagePerSecondTimer;
public float PreviousVitalityDecrease;
public float StrengthDiminishMultiplier = 1.0f;
public Affliction MultiplierSource;
public (float Value, Affliction Source) StrengthDiminishMultiplier = (1.0f, null);
public readonly Dictionary<AfflictionPrefab.PeriodicEffect, float> PeriodicEffectTimers = new Dictionary<AfflictionPrefab.PeriodicEffect, float>();
@@ -95,7 +100,7 @@ namespace Barotrauma
prefab?.ReloadSoundsIfNeeded();
#endif
Prefab = prefab;
PendingAdditionStrength = Prefab.GrainBurst;
PendingGrainEffectStrength = Prefab.GrainBurst;
_strength = strength;
Identifier = prefab.Identifier;
@@ -147,7 +152,16 @@ namespace Barotrauma
MathHelper.Clamp((int)Math.Floor(strength / maxStrength * strengthTexts.Length), 0, strengthTexts.Length - 1)];
}
public AfflictionPrefab.Effect GetActiveEffect() => Prefab.GetActiveEffect(Strength);
public AfflictionPrefab.Effect GetActiveEffect()
{
if (activeEffectDirty)
{
activeEffect = Prefab.GetActiveEffect(_strength);
prevActiveEffectStrength = _strength;
activeEffectDirty = false;
}
return activeEffect;
}
public float GetVitalityDecrease(CharacterHealth characterHealth)
{
@@ -158,14 +172,14 @@ namespace Barotrauma
{
if (strength < Prefab.ActivationThreshold) { return 0.0f; }
strength = MathHelper.Clamp(strength, 0.0f, Prefab.MaxStrength);
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(strength);
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxStrength - currentEffect.MinStrength <= 0.0f) { return 0.0f; }
float currVitalityDecrease = MathHelper.Lerp(
currentEffect.MinVitalityDecrease,
currentEffect.MaxVitalityDecrease,
(strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
if (currentEffect.MultiplyByMaxVitality)
{
@@ -186,11 +200,11 @@ namespace Barotrauma
float amount = MathHelper.Lerp(
currentEffect.MinGrainStrength,
currentEffect.MaxGrainStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
currentEffect.GetStrengthFactor(this)) * GetScreenEffectFluctuation(currentEffect);
if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
if (Prefab.GrainBurst > 0 && GrainEffectStrength > amount)
{
return Math.Min(AdditionStrength, 1.0f);
return Math.Min(GrainEffectStrength, 1.0f);
}
return amount;
@@ -206,7 +220,7 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinScreenDistort,
currentEffect.MaxScreenDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
currentEffect.GetStrengthFactor(this)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetRadialDistortStrength()
@@ -219,7 +233,7 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinRadialDistort,
currentEffect.MaxRadialDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
currentEffect.GetStrengthFactor(this)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetChromaticAberrationStrength()
@@ -232,7 +246,7 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinChromaticAberration,
currentEffect.MaxChromaticAberration,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
currentEffect.GetStrengthFactor(this)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetAfflictionOverlayMultiplier()
@@ -247,7 +261,7 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinAfflictionOverlayAlphaMultiplier,
currentEffect.MaxAfflictionOverlayAlphaMultiplier,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
}
public Color GetFaceTint()
@@ -259,7 +273,7 @@ namespace Barotrauma
return Color.Lerp(
currentEffect.MinFaceTint,
currentEffect.MaxFaceTint,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
}
public Color GetBodyTint()
@@ -271,7 +285,7 @@ namespace Barotrauma
return Color.Lerp(
currentEffect.MinBodyTint,
currentEffect.MaxBodyTint,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
}
public float GetScreenBlurStrength()
@@ -284,7 +298,7 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinScreenBlur,
currentEffect.MaxScreenBlur,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
currentEffect.GetStrengthFactor(this)) * GetScreenEffectFluctuation(currentEffect);
}
private float GetScreenEffectFluctuation(AfflictionPrefab.Effect currentEffect)
@@ -302,7 +316,7 @@ namespace Barotrauma
float amount = MathHelper.Lerp(
currentEffect.MinSkillMultiplier,
currentEffect.MaxSkillMultiplier,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
return amount;
}
@@ -333,7 +347,7 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinResistance,
currentEffect.MaxResistance,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
}
public float GetSpeedMultiplier()
@@ -344,26 +358,21 @@ namespace Barotrauma
return MathHelper.Lerp(
currentEffect.MinSpeedMultiplier,
currentEffect.MaxSpeedMultiplier,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
}
public float GetStatValue(StatTypes statType)
{
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return 0.0f; }
if (GetViableEffect() is not AfflictionPrefab.Effect currentEffect) { return 0.0f; }
if (currentEffect.AfflictionStatValues.TryGetValue(statType, out var value))
{
return MathHelper.Lerp(
value.minValue,
value.maxValue,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
}
return 0.0f;
if (!currentEffect.AfflictionStatValues.TryGetValue(statType, out var appliedStat)) { return 0.0f; }
return MathHelper.Lerp(appliedStat.MinValue, appliedStat.MaxValue, currentEffect.GetStrengthFactor(this));
}
public bool HasFlag(AbilityFlags flagType)
{
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return false; }
if (GetViableEffect() is not AfflictionPrefab.Effect currentEffect) { return false; }
return currentEffect.AfflictionAbilityFlags.HasFlag(flagType);
}
@@ -401,13 +410,16 @@ namespace Barotrauma
fluctuationTimer += deltaTime * currentEffect.ScreenEffectFluctuationFrequency;
fluctuationTimer %= 1.0f;
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
if (currentEffect.StrengthChange < 0) // Only apply StrengthDiminish.Multiplier if affliction is being weakened
{
float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier)
: characterHealth.Character.GetStatValue(StatTypes.DebuffDurationMultiplier)));
float stat = characterHealth.Character.GetStatValue(
Prefab.IsBuff
? StatTypes.BuffDurationMultiplier
: StatTypes.DebuffDurationMultiplier);
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier * durationMultiplier;
float durationMultiplier = 1f / (1f + stat);
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier.Value * durationMultiplier;
}
else if (currentEffect.StrengthChange > 0) // Reduce strengthening of afflictions if resistant
{
@@ -415,6 +427,7 @@ namespace Barotrauma
}
// Don't use the property, because it's virtual and some afflictions like husk overload it for external use.
_strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength);
activeEffectDirty |= !MathUtils.NearlyEqual(prevActiveEffectStrength, _strength);
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
{
@@ -426,14 +439,14 @@ namespace Barotrauma
{
amount /= Prefab.GrainBurst;
}
if (PendingAdditionStrength >= 0)
if (PendingGrainEffectStrength >= 0)
{
AdditionStrength += amount;
PendingAdditionStrength -= deltaTime;
GrainEffectStrength += amount;
PendingGrainEffectStrength -= deltaTime;
}
else if (AdditionStrength > 0)
else if (GrainEffectStrength > 0)
{
AdditionStrength -= amount;
GrainEffectStrength -= amount;
}
}
@@ -442,7 +455,10 @@ namespace Barotrauma
var currentEffect = GetActiveEffect();
if (currentEffect != null)
{
currentEffect.StatusEffects.ForEach(se => ApplyStatusEffect(type, se, deltaTime, characterHealth, targetLimb));
foreach (var statusEffect in currentEffect.StatusEffects)
{
ApplyStatusEffect(type, statusEffect, deltaTime, characterHealth, targetLimb);
}
}
}
@@ -481,6 +497,7 @@ namespace Barotrauma
{
_nonClampedStrength = strength;
_strength = _nonClampedStrength;
activeEffectDirty |= !MathUtils.NearlyEqual(_strength, prevActiveEffectStrength);
}
public bool ShouldShowIcon(Character afflictedCharacter)
@@ -1,5 +1,8 @@
namespace Barotrauma
{
/// <summary>
/// A special affliction type that increases the character's Bloodloss affliction with a rate relative to the strength of the bleeding.
/// </summary>
class AfflictionBleeding : Affliction
{
public AfflictionBleeding(AfflictionPrefab prefab, float strength) :
@@ -7,6 +7,10 @@ using Microsoft.Xna.Framework;
namespace Barotrauma
{
/// <summary>
/// A special affliction type that gradually makes the character turn into another type of character.
/// See <see cref="AfflictionPrefabHusk"/> for more details.
/// </summary>
partial class AfflictionHusk : Affliction
{
public enum InfectionState
@@ -22,7 +26,7 @@ namespace Barotrauma
private Character character;
private bool stun = true;
private bool stun = false;
private readonly List<Affliction> huskInfection = new List<Affliction>();
@@ -43,6 +47,7 @@ namespace Barotrauma
DeactivateHusk();
highestStrength = 0;
}
activeEffectDirty = true;
}
}
private float highestStrength;
@@ -62,6 +67,7 @@ namespace Barotrauma
private float DormantThreshold => HuskPrefab.DormantThreshold;
private float ActiveThreshold => HuskPrefab.ActiveThreshold;
private float TransitionThreshold => HuskPrefab.TransitionThreshold;
private float TransformThresholdOnDeath => HuskPrefab.TransformThresholdOnDeath;
public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength)
@@ -216,7 +222,8 @@ namespace Barotrauma
private void DeactivateHusk()
{
if (character?.AnimController == null || character.Removed) { return; }
if (Prefab is AfflictionPrefabHusk { NeedsAir: false })
if (Prefab is AfflictionPrefabHusk { NeedsAir: false } &&
!character.CharacterHealth.GetAllAfflictions().Any(a => a != this && a.Prefab is AfflictionPrefabHusk { NeedsAir: false }))
{
character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
}
@@ -6,6 +6,7 @@ using System.Reflection;
using System.Xml.Linq;
using Barotrauma.Extensions;
using System.Collections.Immutable;
using Barotrauma.Items.Components;
namespace Barotrauma
{
@@ -56,8 +57,77 @@ namespace Barotrauma
public override void Dispose() { }
}
/// <summary>
/// AfflictionPrefabHusk is a special type of affliction that has added functionality for husk infection.
/// </summary>
class AfflictionPrefabHusk : AfflictionPrefab
{
// Use any of these to define which limb the appendage is attached to.
// If multiple are defined, the order of preference is: id, name, type.
public readonly int AttachLimbId;
public readonly string AttachLimbName;
public readonly LimbType AttachLimbType;
/// <summary>
/// The minimum strength at which husk infection will be in the dormant stage.
/// It must be less than or equal to ActiveThreshold.
/// </summary>
public readonly float DormantThreshold;
/// <summary>
/// The minimum strength at which husk infection will be in the active stage.
/// It must be greater than or equal to DormantThreshold and less than or equal to TransitionThreshold.
/// </summary>
public readonly float ActiveThreshold;
/// <summary>
/// The minimum strength at which husk infection will be in its final stage.
/// It must be greater than or equal to ActiveThreshold.
/// </summary>
public readonly float TransitionThreshold;
/// <summary>
/// The minimum strength the affliction must have for the affected character
/// to transform into a husk upon death.
/// </summary>
public readonly float TransformThresholdOnDeath;
/// <summary>
/// The species of husk to convert the affected character to
/// once husk infection reaches its final stage.
/// </summary>
public readonly Identifier HuskedSpeciesName;
/// <summary>
/// If set to true, all buffs are transferred to the converted
/// character after husk transformation is complete.
/// </summary>
public readonly bool TransferBuffs;
/// <summary>
/// If set to true, the affected player will see on-screen messages describing husk infection symptoms
/// and affected bots will speak about their current husk infection stage.
/// </summary>
public readonly bool SendMessages;
/// <summary>
/// If set to true, affected characters will have their speech impeded once the affliction
/// reaches the dormant stage.
/// </summary>
public readonly bool CauseSpeechImpediment;
/// <summary>
/// If not set to true, affected characters will no longer require air
/// once the affliction reaches the active stage.
/// </summary>
public readonly bool NeedsAir;
/// <summary>
/// If set to true, affected players will retain control of their character
/// after transforming into a husk.
/// </summary>
public readonly bool ControlHusk;
public AfflictionPrefabHusk(ContentXElement element, AfflictionsFile file, Type type = null) : base(element, file, type)
{
HuskedSpeciesName = element.GetAttributeIdentifier("huskedspeciesname", Identifier.Empty);
@@ -68,7 +138,6 @@ namespace Barotrauma
}
// Remove "[speciesname]" for backward support (we don't use it anymore)
HuskedSpeciesName = HuskedSpeciesName.Remove("[speciesname]").ToIdentifier();
TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true);
if (TargetSpecies.Length == 0)
{
DebugConsole.NewMessage($"No 'targets' defined for the husk affliction ({Identifier}) in {element}", Color.Orange);
@@ -79,7 +148,7 @@ namespace Barotrauma
{
AttachLimbId = attachElement.GetAttributeInt("id", -1);
AttachLimbName = attachElement.GetAttributeString("name", null);
AttachLimbType = Enum.TryParse(attachElement.GetAttributeString("type", "none"), true, out LimbType limbType) ? limbType : LimbType.None;
AttachLimbType = attachElement.GetAttributeEnum("type", LimbType.None);
}
else
{
@@ -97,175 +166,282 @@ namespace Barotrauma
DormantThreshold = element.GetAttributeFloat("dormantthreshold", MaxStrength * 0.5f);
ActiveThreshold = element.GetAttributeFloat("activethreshold", MaxStrength * 0.75f);
TransitionThreshold = element.GetAttributeFloat("transitionthreshold", MaxStrength);
if (DormantThreshold > ActiveThreshold)
{
DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(DormantThreshold)} is greater than {nameof(ActiveThreshold)} ({DormantThreshold} > {ActiveThreshold})");
}
if (ActiveThreshold > TransitionThreshold)
{
DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(ActiveThreshold)} is greater than {nameof(TransitionThreshold)} ({ActiveThreshold} > {TransitionThreshold})");
}
TransformThresholdOnDeath = element.GetAttributeFloat("transformthresholdondeath", ActiveThreshold);
}
// Use any of these to define which limb the appendage is attached to.
// If multiple are defined, the order of preference is: id, name, type.
public readonly int AttachLimbId;
public readonly string AttachLimbName;
public readonly LimbType AttachLimbType;
public float ActiveThreshold, DormantThreshold, TransitionThreshold;
public float TransformThresholdOnDeath;
public readonly Identifier HuskedSpeciesName;
public readonly Identifier[] TargetSpecies;
public readonly bool TransferBuffs;
public readonly bool SendMessages;
public readonly bool CauseSpeechImpediment;
public readonly bool NeedsAir;
public readonly bool ControlHusk;
}
/// <summary>
/// AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character.
/// There are multiple sub-types of afflictions such as AfflictionPrefabHusk, AfflictionPsychosis and AfflictionBleeding that can be used for additional functionality.
///
/// When defining a new affliction, the type will be determined by the element name.
/// </summary>
/// <example>
/// <code language="xml">
/// <Afflictions>
/// <!-- Defines a regular affliction. -->
/// <Affliction identifier="mycoolaffliction1" />
///
/// <!-- Defines an AfflictionPrefabHusk affliction. -->
/// <AfflictionPrefabHusk identifier="mycoolaffliction2"/>
///
/// <!-- Defines an AfflictionBleeding affliction. -->
/// <AfflictionBleeding identifier="mycoolaffliction3"/>
/// </Afflictions>
/// </code>
/// </example>
class AfflictionPrefab : PrefabWithUintIdentifier
{
public class Effect
/// <summary>
/// Effects are the primary way to add functionality to afflictions.
/// </summary>
/// <doc>
/// <Ignore type="SubElement" identifier="AbilityFlag" />
/// <SubElement identifier="abilityflag" type="AppliedAbilityFlag">
/// Enables the specified flag on the character as long as the effect is active.
/// </SubElement>
/// <Type identifier="AppliedAbilityFlag">
/// <Summary>
/// Flag that will be enabled for the character as long as the effect is active.
/// <example>
/// <code language="xml">
/// <Effect minstrength="0" maxstrength="100">
/// <!-- Grants pressure immunity to the character while the effect is active. -->
/// <AbilityFlag flagtype="ImmuneToPressure" />
/// </Effect>
/// </code>
/// </example>
/// </Summary>
/// <Field identifier="FlagType" type="AbilityFlags" defaultValue="None">
/// Which ability flag to enable.
/// </Field>
/// </Type>
/// </doc>
public sealed class Effect
{
//this effect is applied when the strength is within this range
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Minimum affliction strength required for this effect to be active.")]
public float MinStrength { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Maximum affliction strength for which this effect will be active.")]
public float MaxStrength { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of vitality that is lost at this effect's lowest strength.")]
public float MinVitalityDecrease { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of vitality that is lost at this effect's highest strength.")]
public float MaxVitalityDecrease { get; private set; }
//how much the strength of the affliction changes per second
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "How much the affliction's strength changes every second while this effect is active.")]
public float StrengthChange { get; private set; }
[Serialize(false, IsPropertySaveable.No)]
[Serialize(false, IsPropertySaveable.No, description:
"If set to true, MinVitalityDecrease and MaxVitalityDecrease represent a fraction of the affected character's maximum " +
"vilatily, with 1 meaning 100%, instead of the same amount for all species.")]
public bool MultiplyByMaxVitality { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Blur effect strength at this effect's lowest strength.")]
public float MinScreenBlur { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Blur effect strength at this effect's highest strength.")]
public float MaxScreenBlur { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Generic distortion effect strength at this effect's lowest strength.")]
public float MinScreenDistort { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Generic distortion effect strength at this effect's highest strength.")]
public float MaxScreenDistort { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Radial distortion effect strength at this effect's lowest strength.")]
public float MinRadialDistort { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Radial distortion effect strength at this effect's highest strength.")]
public float MaxRadialDistort { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Chromatic aberration effect strength at this effect's lowest strength.")]
public float MinChromaticAberration { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Chromatic aberration effect strength at this effect's highest strength.")]
public float MaxChromaticAberration { get; private set; }
[Serialize("255,255,255,255", IsPropertySaveable.No)]
[Serialize("255,255,255,255", IsPropertySaveable.No, description: "Radiation grain effect color.")]
public Color GrainColor { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Radiation grain effect strength at this effect's lowest strength.")]
public float MinGrainStrength { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description: "Radiation grain effect strength at this effect's highest strength.")]
public float MaxGrainStrength { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No, description:
"The maximum rate of fluctuation to apply to visual effects caused by this affliction effect. " +
"Effective fluctuation is proportional to the affliction's current strength.")]
public float ScreenEffectFluctuationFrequency { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description:
"Multiplier for the affliction overlay's opacity at this effect's lowest strength. " +
"See the list of elements for more details.")]
public float MinAfflictionOverlayAlphaMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description:
"Multiplier for the affliction overlay's opacity at this effect's highest strength. " +
"See the list of elements for more details.")]
public float MaxAfflictionOverlayAlphaMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description:
"Multiplier for every buff's decay rate at this effect's lowest strength. " +
"Only applies to afflictions of class BuffDurationIncrease.")]
public float MinBuffMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description:
"Multiplier for every buff's decay rate at this effect's highest strength. " +
"Only applies to afflictions of class BuffDurationIncrease.")]
public float MaxBuffMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to the affected character's speed at this effect's lowest strength.")]
public float MinSpeedMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to the affected character's speed at this effect's highest strength.")]
public float MaxSpeedMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to all of the affected character's skill levels at this effect's lowest strength.")]
public float MinSkillMultiplier { get; private set; }
[Serialize(1.0f, IsPropertySaveable.No)]
[Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to all of the affected character's skill levels at this effect's highest strength.")]
public float MaxSkillMultiplier { get; private set; }
private readonly Identifier[] resistanceFor;
public IReadOnlyList<Identifier> ResistanceFor => resistanceFor;
/// <summary>
/// A list of identifiers of afflictions that the affected character will be
/// resistant to when this effect is active.
/// </summary>
public readonly ImmutableArray<Identifier> ResistanceFor;
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No,
description: "The amount of resistance to the afflictions specified by ResistanceFor to apply at this effect's lowest strength.")]
public float MinResistance { get; private set; }
[Serialize(0.0f, IsPropertySaveable.No)]
[Serialize(0.0f, IsPropertySaveable.No,
description: "The amount of resistance to the afflictions specified by ResistanceFor to apply at this effect's highest strength.")]
public float MaxResistance { get; private set; }
[Serialize("", IsPropertySaveable.No)]
[Serialize("", IsPropertySaveable.No, description: "Identifier used by AI to determine conversation lines to say when this effect is active.")]
public Identifier DialogFlag { get; private set; }
[Serialize("", IsPropertySaveable.No)]
[Serialize("", IsPropertySaveable.No, description: "Tag that enemy AI may use to target the affected character when this effect is active.")]
public Identifier Tag { get; private set; }
[Serialize("0,0,0,0", IsPropertySaveable.No)]
[Serialize("0,0,0,0", IsPropertySaveable.No,
description: "Color to tint the affected character's face with at this effect's lowest strength. The alpha channel is used to determine how much to tint the character's face.")]
public Color MinFaceTint { get; private set; }
[Serialize("0,0,0,0", IsPropertySaveable.No)]
[Serialize("0,0,0,0", IsPropertySaveable.No,
description: "Color to tint the affected character's face with at this effect's highest strength. The alpha channel is used to determine how much to tint the character's face.")]
public Color MaxFaceTint { get; private set; }
[Serialize("0,0,0,0", IsPropertySaveable.No)]
[Serialize("0,0,0,0", IsPropertySaveable.No,
description: "Color to tint the affected character's entire body with at this effect's lowest strength. The alpha channel is used to determine how much to tint the character.")]
public Color MinBodyTint { get; private set; }
[Serialize("0,0,0,0", IsPropertySaveable.No)]
[Serialize("0,0,0,0", IsPropertySaveable.No,
description: "Color to tint the affected character's entire body with at this effect's highest strength. The alpha channel is used to determine how much to tint the character.")]
public Color MaxBodyTint { get; private set; }
/// <summary>
/// Prevents AfflictionHusks with the specified identifier(s) from transforming the character into an AI-controlled character
/// StatType that will be applied to the affected character when the effect is active that is proportional to the effect's strength.
/// </summary>
public Identifier[] BlockTransformation { get; private set; }
/// <example>
/// <code language="xml">
/// <Effect minstrength="0" maxstrength="100">
/// <!-- Walking speed will be increased by 10% at strength 0, 20% at 50 and 30% at 100 -->
/// <StatValue stattype="WalkingSpeed" minvalue="0.1" maxvalue="0.3" />
/// <!-- Maximum health will be increased by 20% regardless of the effect strength -->
/// <StatValue stattype="MaximumHealthMultiplier" value="0.2" />
/// </Effect>
/// </code>
/// </example>
public readonly struct AppliedStatValue
{
/// <summary>
/// Which StatType to apply
/// </summary>
public readonly StatTypes StatType;
public readonly Dictionary<StatTypes, (float minValue, float maxValue)> AfflictionStatValues = new Dictionary<StatTypes, (float minValue, float maxValue)>();
public AbilityFlags AfflictionAbilityFlags;
/// <summary>
/// Minimum value to apply
/// </summary>
public readonly float MinValue;
/// <summary>
/// Minimum value to apply
/// </summary>
public readonly float MaxValue;
/// <summary>
/// Constant value to apply, will be ignored if MinValue or MaxValue are set
/// </summary>
private readonly float Value;
public AppliedStatValue(ContentXElement element)
{
Value = element.GetAttributeFloat("value", 0.0f);
StatType = element.GetAttributeEnum("stattype", StatTypes.None);
MinValue = element.GetAttributeFloat("minvalue", Value);
MaxValue = element.GetAttributeFloat("maxvalue", Value);
}
}
/// <summary>
/// Prevents AfflictionHusks with the specified identifier(s) from transforming the character into an AI-controlled character.
/// </summary>
public readonly ImmutableArray<Identifier> BlockTransformation;
/// <summary>
/// StatType that will be applied to the affected character when the effect is active that is proportional to the effect's strength.
/// </summary>
public readonly ImmutableDictionary<StatTypes, AppliedStatValue> AfflictionStatValues;
public readonly AbilityFlags AfflictionAbilityFlags;
//statuseffects applied on the character when the affliction is active
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
public readonly ImmutableArray<StatusEffect> StatusEffects;
public Effect(ContentXElement element, string parentDebugName)
{
SerializableProperty.DeserializeProperties(this, element);
resistanceFor = element.GetAttributeIdentifierArray("resistancefor", Array.Empty<Identifier>());
BlockTransformation = element.GetAttributeIdentifierArray("blocktransformation", Array.Empty<Identifier>());
ResistanceFor = element.GetAttributeIdentifierArray("resistancefor", Array.Empty<Identifier>())!.ToImmutableArray();
BlockTransformation = element.GetAttributeIdentifierArray("blocktransformation", Array.Empty<Identifier>())!.ToImmutableArray();
var afflictionStatValues = new Dictionary<StatTypes, AppliedStatValue>();
var statusEffects = new List<StatusEffect>();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "statuseffect":
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
statusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
break;
case "statvalue":
var statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), parentDebugName);
float defaultValue = subElement.GetAttributeFloat("value", 0f);
float minValue = subElement.GetAttributeFloat("minvalue", defaultValue);
float maxValue = subElement.GetAttributeFloat("maxvalue", defaultValue);
AfflictionStatValues.TryAdd(statType, (minValue, maxValue));
var newStatValue = new AppliedStatValue(subElement);
afflictionStatValues.Add(newStatValue.StatType, newStatValue);
break;
case "abilityflag":
var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName);
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", "")}\".");
continue;
}
AfflictionAbilityFlags |= flagType;
break;
case "affliction":
@@ -273,21 +449,71 @@ namespace Barotrauma
break;
}
}
AfflictionStatValues = afflictionStatValues.ToImmutableDictionary();
StatusEffects = statusEffects.ToImmutableArray();
}
/// <summary>
/// Returns 0 if affliction.Strength is MinStrength,
/// 1 if affliction.Strength is MaxStrength
/// </summary>
public float GetStrengthFactor(Affliction affliction)
=> MathUtils.InverseLerp(
MinStrength,
MaxStrength,
affliction.Strength);
}
public class Description
/// <summary>
/// Description element can be used to define descriptions for the affliction that are shown at specific conditions.
/// For example a description that only shows to other players or only at certain strength levels.
/// </summary>
/// <doc>
/// <Field identifier="Text" type="string" defaultValue="&quot;&quot;">
/// Raw text for the description.
/// </Field>
/// </doc>
public sealed class Description
{
public enum TargetType
{
/// <summary>
/// Everyone can see the description.
/// </summary>
Any,
/// <summary>
/// Only the affected character can see the description.
/// </summary>
Self,
/// <summary>
/// The affected character cannot see the description but others can.
/// </summary>
OtherCharacter
}
/// <summary>
/// Raw text for the description.
/// </summary>
public readonly LocalizedString Text;
/// <summary>
/// Text tag used to set the text from the localization files.
/// </summary>
public readonly Identifier TextTag;
public readonly float MinStrength, MaxStrength;
/// <summary>
/// Minimum strength required for the description to be shown.
/// </summary>
public readonly float MinStrength;
/// <summary>
/// Maximum strength required for the description to be shown.
/// </summary>
public readonly float MaxStrength;
/// <summary>
/// Who can see the description.
/// </summary>
public readonly TargetType Target;
public Description(ContentXElement element, AfflictionPrefab affliction)
@@ -317,7 +543,23 @@ namespace Barotrauma
}
}
public class PeriodicEffect
/// <summary>
/// PeriodicEffect applies StatusEffects to the character periodically.
/// </summary>
/// <doc>
/// <SubElement identifier="StatusEffect" type="StatusEffect" />
/// <Field identifier="Interval" type="float" defaultValue="1.0">
/// How often the status effect is applied in seconds.
/// Setting this attribute will set both the min and max interval to the specified value.
/// </Field>
/// <Field identifier="MinInterval" type="float" defaultValue="1.0">
/// Minimum interval between applying the status effect in seconds.
/// </Field>
/// <Field identifier="MaxInterval" type="float" defaultValue="1.0">
/// Maximum interval between applying the status effect in seconds.
/// </Field>
/// </doc>
public sealed class PeriodicEffect
{
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
public readonly float MinInterval, MaxInterval;
@@ -344,65 +586,151 @@ namespace Barotrauma
}
}
public static readonly Identifier DamageType = "damage".ToIdentifier();
public static readonly Identifier BurnType = "burn".ToIdentifier();
public static readonly Identifier BleedingType = "bleeding".ToIdentifier();
public static readonly Identifier ParalysisType = "paralysis".ToIdentifier();
public static readonly Identifier PoisonType = "poison".ToIdentifier();
public static readonly Identifier StunType = "stun".ToIdentifier();
public static readonly Identifier EMPType = "emp".ToIdentifier();
public static readonly Identifier SpaceHerpesType = "spaceherpes".ToIdentifier();
public static readonly Identifier AlienInfectedType = "alieninfected".ToIdentifier();
public static readonly Identifier InvertControlsType = "invertcontrols".ToIdentifier();
public static readonly Identifier HuskInfectionType = "huskinfection".ToIdentifier();
public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"];
public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"];
public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"];
public static AfflictionPrefab Bleeding => Prefabs["bleeding"];
public static AfflictionPrefab Burn => Prefabs["burn"];
public static AfflictionPrefab Bleeding => Prefabs[BleedingType];
public static AfflictionPrefab Burn => Prefabs[BurnType];
public static AfflictionPrefab OxygenLow => Prefabs["oxygenlow"];
public static AfflictionPrefab Bloodloss => Prefabs["bloodloss"];
public static AfflictionPrefab Pressure => Prefabs["pressure"];
public static AfflictionPrefab Stun => Prefabs["stun"];
public static AfflictionPrefab Stun => Prefabs[StunType];
public static AfflictionPrefab RadiationSickness => Prefabs["radiationsickness"];
public static readonly PrefabCollection<AfflictionPrefab> Prefabs = new PrefabCollection<AfflictionPrefab>();
public override void Dispose() { }
public static IEnumerable<AfflictionPrefab> List => Prefabs;
// Arbitrary string that is used to identify the type of the affliction.
public readonly Identifier AfflictionType;
public override void Dispose() { }
private readonly ContentXElement configElement;
//Does the affliction affect a specific limb or the whole character
public readonly bool LimbSpecific;
//If not a limb-specific affliction, which limb is the indicator shown on in the health menu
//(e.g. mental health problems on head, lack of oxygen on torso...)
public readonly LimbType IndicatorLimb;
public readonly LocalizedString Name;
public readonly Identifier TranslationIdentifier;
public readonly bool IsBuff;
public readonly bool AffectMachines;
public readonly bool HealableInMedicalClinic;
public readonly float HealCostMultiplier;
public readonly int BaseHealCost;
public readonly bool ShowBarInHealthMenu;
public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription;
private readonly LocalizedString defaultDescription;
public readonly ImmutableList<Description> Descriptions;
/// <summary>
/// Arbitrary string that is used to identify the type of the affliction.
/// </summary>
public readonly Identifier AfflictionType;
/// <summary>
/// If set to true, the affliction affects individual limbs. Otherwise, it affects the whole character.
/// </summary>
public readonly bool LimbSpecific;
/// <summary>
/// If the affliction doesn't affect individual limbs, this attribute determines
/// where the game will render the affliction's indicator when viewed in the
/// in-game health UI.
///
/// For example, the psychosis indicator is rendered on the head, and low oxygen
/// is rendered on the torso.
/// </summary>
public readonly LimbType IndicatorLimb;
/// <summary>
/// Can be set to the identifier of another affliction to make this affliction
/// reuse the same name and description.
/// </summary>
public readonly Identifier TranslationIdentifier;
/// <summary>
/// If set to true, the game will recognize this affliction as a buff.
/// This means, among other things, that bots won't attempt to treat it,
/// and the health UI will render the affected limb in green rather than red.
/// </summary>
public readonly bool IsBuff;
/// <summary>
/// If set to true, this affliction can affect characters that are marked as
/// machines, such as the Fractal Guardian.
/// </summary>
public readonly bool AffectMachines;
/// <summary>
/// If set to true, this affliction can be healed at the medical clinic.
/// </summary>
/// <doc>
/// <override type="DefaultValue">
/// false if the affliction is a buff or has the type "geneticmaterialbuff" or "geneticmaterialdebuff", true otherwise.
/// </override>
/// </doc>
public readonly bool HealableInMedicalClinic;
/// <summary>
/// How much each unit of this affliction's strength will add
/// to the cost of healing at the medical clinic.
/// </summary>
public readonly float HealCostMultiplier;
/// <summary>
/// The minimum cost of healing this affliction at the medical clinic.
/// </summary>
public readonly int BaseHealCost;
/// <summary>
/// If set to false, the health UI will not show the strength of the affliction
/// as a bar under its indicator.
/// </summary>
public readonly bool ShowBarInHealthMenu;
/// <summary>
/// If set to true, this affliction's icon will be hidden from the HUD
/// after 5 seconds.
/// </summary>
public readonly bool HideIconAfterDelay;
//how high the strength has to be for the affliction to take affect
/// <summary>
/// How high the strength has to be for the affliction to take effect
/// </summary>
public readonly float ActivationThreshold = 0.0f;
//how high the strength has to be for the affliction icon to be shown in the UI
/// <summary>
/// How high the strength has to be for the affliction icon to be shown in the UI
/// </summary>
public readonly float ShowIconThreshold = 0.05f;
//how high the strength has to be for the affliction icon to be shown to others with a health scanner or via the health interface
/// <summary>
/// How high the strength has to be for the affliction icon to be shown to others with a health scanner or via the health interface
/// </summary>
public readonly float ShowIconToOthersThreshold = 0.05f;
/// <summary>
/// The maximum strength this affliction can have.
/// </summary>
public readonly float MaxStrength = 100.0f;
/// <summary>
/// The strength of the radiation grain effect to apply
/// when the strength of this affliction increases.
/// </summary>
public readonly float GrainBurst;
//how high the strength has to be for the affliction icon to be shown with a health scanner
/// <summary>
/// How high the strength has to be for the affliction icon to be shown with a health scanner
/// </summary>
public readonly float ShowInHealthScannerThreshold = 0.05f;
//how strong the affliction needs to be before bots attempt to treat it
/// <summary>
/// How strong the affliction needs to be before bots attempt to treat it
/// </summary>
public readonly float TreatmentThreshold = 5.0f;
/// <summary>
@@ -411,25 +739,57 @@ namespace Barotrauma
public ImmutableHashSet<Identifier> IgnoreTreatmentIfAfflictedBy;
/// <summary>
/// The affliction is automatically removed after this time. 0 = unlimited
/// The duration of the affliction, in seconds. If set to 0, the affliction does not expire.
/// </summary>
public readonly float Duration;
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
/// <summary>
/// How much karma changes when a player applies this affliction to someone (per strength of the affliction)
/// </summary>
public float KarmaChangeOnApplied;
/// <summary>
/// Opacity of the burn effect (darker tint) on limbs affected by this affliction. 1 = full strength.
/// </summary>
public readonly float BurnOverlayAlpha;
/// <summary>
/// Opacity of the bloody damage overlay on limbs affected by this affliction. 1 = full strength.
/// </summary>
public readonly float DamageOverlayAlpha;
//steam achievement given when the affliction is removed from the controlled character
/// <summary>
/// Steam achievement given when the controlled character receives the affliction
/// </summary>
public readonly Identifier AchievementOnReceived;
/// <summary>
/// Steam achievement given when the affliction is removed from the controlled character
/// </summary>
public readonly Identifier AchievementOnRemoved;
public readonly Sprite Icon;
/// <summary>
/// A gradient that defines which color to render this affliction's icon
/// with, based on the affliction's current strength.
/// </summary>
public readonly Color[] IconColors;
public readonly Sprite AfflictionOverlay;
/// <summary>
/// If set to true and the affliction has an AfflictionOverlay element,
/// the overlay's opacity will be strictly proportional to its strength.
/// Otherwise, the overlay's opacity will be determined based on its
/// activation threshold and effects.
/// </summary>
public readonly bool AfflictionOverlayAlphaIsLinear;
/// <summary>
/// If set to true, this affliction will not persist between rounds.
/// </summary>
public readonly bool ResetBetweenRounds;
/// <summary>
/// Should damage particles be emitted when a character receives this affliction? Only relevant if the affliction is of the type "bleeding" or "damage".
/// </summary>
public readonly bool DamageParticles;
/// <summary>
@@ -444,7 +804,20 @@ namespace Barotrauma
/// </summary>
public readonly float WeaponsSkillGain;
/// <summary>
/// A list of species this affliction is allowed to affect.
/// </summary>
public Identifier[] TargetSpecies { get; protected set; }
/// <summary>
/// Effects to apply at various strength levels.
/// Only one effect can be applied at any given moment, so their ranges should be defined with no overlap.
/// </summary>
private readonly List<Effect> effects = new List<Effect>();
/// <summary>
/// PeriodicEffect applies StatusEffects to the character periodically.
/// </summary>
private readonly List<PeriodicEffect> periodicEffects = new List<PeriodicEffect>();
public IEnumerable<Effect> Effects => effects;
@@ -453,7 +826,17 @@ namespace Barotrauma
private readonly ConstructorInfo constructor;
public readonly bool ResetBetweenRounds;
/// <summary>
/// Icon that's used in UI to represent this affliction.
/// </summary>
public readonly Sprite Icon;
/// <summary>
/// A sprite that covers the affected player's entire screen when this affliction is active.
/// Its opacity is controlled by the active effect's MinAfflictionOverlayAlphaMultiplier
/// and MaxAfflictionOverlayAlphaMultiplier
/// </summary>
public readonly Sprite AfflictionOverlay;
public IEnumerable<KeyValuePair<Identifier, float>> TreatmentSuitability
{
@@ -481,7 +864,7 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(fallbackName))
{
Name = Name.Fallback(fallbackName);
}
}
defaultDescription = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
string fallbackDescription = element.GetAttributeString("description", "");
if (!string.IsNullOrEmpty(fallbackDescription))
@@ -536,13 +919,22 @@ namespace Barotrauma
KarmaChangeOnApplied = element.GetAttributeFloat(nameof(KarmaChangeOnApplied), 0.0f);
CauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}").Fallback(element.GetAttributeString("causeofdeathdescription", ""));
SelfCauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}").Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
CauseOfDeathDescription =
TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}")
.Fallback(TextManager.Get(element.GetAttributeString("causeofdeathdescription", "")))
.Fallback(element.GetAttributeString("causeofdeathdescription", ""));
SelfCauseOfDeathDescription =
TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}")
.Fallback(TextManager.Get(element.GetAttributeString("selfcauseofdeathdescription", "")))
.Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false);
AchievementOnReceived = element.GetAttributeIdentifier(nameof(AchievementOnReceived), "");
AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), "");
TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true);
ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
DamageParticles = element.GetAttributeBool(nameof(DamageParticles), true);
@@ -1,5 +1,8 @@
namespace Barotrauma
{
/// <summary>
/// A special affliction type that makes the character see and hear things that aren't there.
/// </summary>
partial class AfflictionPsychosis : Affliction
{
@@ -1,11 +1,12 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
/// <summary>
/// A special affliction type that periodically inverts the character's controls and stuns the character.
/// The frequency and duration of the effects increases the higher the strength of the affliction is.
/// </summary>
class AfflictionSpaceHerpes : Affliction
{
private float invertControlsCooldown = 60.0f;
@@ -3,6 +3,10 @@ using System;
namespace Barotrauma
{
/// <summary>
/// A special affliction type that increases the duration of buffs (afflictions of the type "buff"). The increase is defined using the
/// <see cref="AfflictionPrefab.Effect.MinBuffMultiplier"/> and <see cref="AfflictionPrefab.Effect.MaxBuffMultiplier"/> attributes of the affliction effect.
/// </summary>
class BuffDurationIncrease : Affliction
{
public BuffDurationIncrease(AfflictionPrefab prefab, float strength) : base(prefab, strength)
@@ -20,9 +24,9 @@ namespace Barotrauma
{
foreach (Affliction affliction in afflictions)
{
if (!affliction.Prefab.IsBuff || affliction == this || affliction.MultiplierSource != this) { continue; }
affliction.MultiplierSource = null;
affliction.StrengthDiminishMultiplier = 1f;
if (!affliction.Prefab.IsBuff || affliction == this || affliction.StrengthDiminishMultiplier.Source != this) { continue; }
affliction.StrengthDiminishMultiplier.Source = null;
affliction.StrengthDiminishMultiplier.Value = 1f;
}
}
else
@@ -31,10 +35,10 @@ namespace Barotrauma
{
if (!affliction.Prefab.IsBuff || affliction == this) { continue; }
float multiplier = GetDiminishMultiplier();
if (affliction.StrengthDiminishMultiplier < multiplier && affliction.MultiplierSource != this) { continue; }
if (affliction.StrengthDiminishMultiplier.Value < multiplier && affliction.StrengthDiminishMultiplier.Source != this) { continue; }
affliction.MultiplierSource = this;
affliction.StrengthDiminishMultiplier = multiplier;
affliction.StrengthDiminishMultiplier.Source = this;
affliction.StrengthDiminishMultiplier.Value = multiplier;
}
}
}
@@ -48,7 +52,7 @@ namespace Barotrauma
float multiplier = MathHelper.Lerp(
currentEffect.MinBuffMultiplier,
currentEffect.MaxBuffMultiplier,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.GetStrengthFactor(this));
return 1.0f / Math.Max(multiplier, 0.001f);
}
}
@@ -184,7 +184,7 @@ namespace Barotrauma
}
}
public Color DefaultFaceTint = Color.TransparentBlack;
public static readonly Color DefaultFaceTint = Color.TransparentBlack;
public Color FaceTint
{
@@ -339,12 +339,12 @@ namespace Barotrauma
return null;
}
public T GetAffliction<T>(string identifier, bool allowLimbAfflictions = true) where T : Affliction
public T GetAffliction<T>(Identifier identifier, bool allowLimbAfflictions = true) where T : Affliction
{
return GetAffliction(identifier, allowLimbAfflictions) as T;
}
public Affliction GetAffliction(string identifier, Limb limb)
public Affliction GetAffliction(Identifier identifier, Limb limb)
{
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
{
@@ -380,7 +380,7 @@ namespace Barotrauma
/// <param name="limb">The limb the affliction is attached to</param>
/// <param name="requireLimbSpecific">Does the affliction have to be attached to only the specific limb.
/// Most monsters for example don't have separate healths for different limbs, essentially meaning that every affliction is applied to every limb.</param>
public float GetAfflictionStrength(string afflictionType, Limb limb, bool requireLimbSpecific)
public float GetAfflictionStrength(Identifier afflictionType, Limb limb, bool requireLimbSpecific)
{
if (requireLimbSpecific && limbHealths.Count == 1) { return 0.0f; }
@@ -401,7 +401,7 @@ namespace Barotrauma
return strength;
}
public float GetAfflictionStrength(string afflictionType, bool allowLimbAfflictions = true)
public float GetAfflictionStrength(Identifier afflictionType, bool allowLimbAfflictions = true)
{
float strength = 0.0f;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
@@ -449,7 +449,11 @@ namespace Barotrauma
var affliction = kvp.Key;
resistance += affliction.GetResistance(afflictionPrefab.Identifier);
}
return 1 - ((1 - resistance) * Character.GetAbilityResistance(afflictionPrefab));
resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(afflictionPrefab));
if (resistance > 1f) { resistance = 1f; }
return resistance;
}
public float GetStatValue(StatTypes statType)
@@ -483,16 +487,19 @@ namespace Barotrauma
ReduceMatchingAfflictions(amount, treatmentAction);
}
public void ReduceAfflictionOnAllLimbs(Identifier affliction, float amount, ActionType? treatmentAction = null)
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null)
{
if (affliction.IsEmpty) { throw new ArgumentException($"{nameof(affliction)} is empty"); }
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
matchingAfflictions.Clear();
matchingAfflictions.AddRange(afflictions.Keys);
matchingAfflictions.RemoveAll(a =>
a.Prefab.Identifier != affliction &&
a.Prefab.AfflictionType != affliction);
foreach (var affliction in afflictions)
{
if (affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType)
{
matchingAfflictions.Add(affliction.Key);
}
}
ReduceMatchingAfflictions(amount, treatmentAction);
}
@@ -509,18 +516,21 @@ namespace Barotrauma
ReduceMatchingAfflictions(amount, treatmentAction);
}
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier affliction, float amount, ActionType? treatmentAction = null)
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null)
{
if (affliction.IsEmpty) { throw new ArgumentException($"{nameof(affliction)} is empty"); }
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
matchingAfflictions.Clear();
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
matchingAfflictions.RemoveAll(a =>
a.Prefab.Identifier != affliction &&
a.Prefab.AfflictionType != affliction);
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
foreach (var affliction in afflictions)
{
if ((affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType) &&
affliction.Value == targetLimbHealth)
{
matchingAfflictions.Add(affliction.Key);
}
}
ReduceMatchingAfflictions(amount, treatmentAction);
}
@@ -622,7 +632,7 @@ namespace Barotrauma
KillIfOutOfVitality();
}
public float GetLimbDamage(Limb limb, string afflictionType = null)
public float GetLimbDamage(Limb limb, Identifier afflictionType)
{
float damageStrength;
if (limb.IsSevered)
@@ -635,16 +645,16 @@ namespace Barotrauma
// Therefore with e.g. 80 health, the max damage per limb would be 40.
// Having at least 40 damage on both legs would cause maximum limping.
float max = MaxVitality / 2;
if (string.IsNullOrEmpty(afflictionType))
if (afflictionType.IsEmpty)
{
float damage = GetAfflictionStrength("damage", limb, true);
float bleeding = GetAfflictionStrength("bleeding", limb, true);
float burn = GetAfflictionStrength("burn", limb, true);
float damage = GetAfflictionStrength(AfflictionPrefab.DamageType, limb, true);
float bleeding = GetAfflictionStrength(AfflictionPrefab.BleedingType, limb, true);
float burn = GetAfflictionStrength(AfflictionPrefab.BurnType, limb, true);
damageStrength = Math.Min(damage + bleeding + burn, max);
}
else
{
damageStrength = Math.Min(GetAfflictionStrength("damage", limb, true), max);
damageStrength = Math.Min(GetAfflictionStrength(afflictionType, limb, true), max);
}
return damageStrength / max;
}
@@ -701,22 +711,17 @@ namespace Barotrauma
if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun")
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == AfflictionPrefab.StunType)
{
if (Character.EmpVulnerability <= 0 || GetAfflictionStrength("emp", allowLimbAfflictions: false) <= 0)
{
return;
}
}
if (Character.Params.Health.PoisonImmunity && (newAffliction.Prefab.AfflictionType == "poison" || newAffliction.Prefab.AfflictionType == "paralysis")) { return; }
if (Character.EmpVulnerability <= 0 && newAffliction.Prefab.AfflictionType == "emp") { return; }
if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
{
if (huskPrefab.TargetSpecies.None(s => s == Character.SpeciesName))
if (Character.EmpVulnerability <= 0 || GetAfflictionStrength(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
{
return;
}
}
if (Character.Params.Health.PoisonImmunity &&
(newAffliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType || newAffliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)) { return; }
if (Character.EmpVulnerability <= 0 && newAffliction.Prefab.AfflictionType == AfflictionPrefab.EMPType) { return; }
if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s == Character.SpeciesName)) { return; }
Affliction existingAffliction = null;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
@@ -753,7 +758,9 @@ namespace Barotrauma
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
newAffliction.Source);
afflictions.Add(copyAffliction, limbHealth);
SteamAchievementManager.OnAfflictionReceived(copyAffliction, Character);
MedicalClinic.OnAfflictionCountChanged(Character);
Character.HealthUpdateInterval = 0.0f;
CalculateVitality();
@@ -826,10 +833,16 @@ namespace Barotrauma
}
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
}
foreach (var affliction in afflictionsToRemove)
{
afflictions.Remove(affliction);
}
}
if (afflictionsToRemove.Count is not 0)
{
MedicalClinic.OnAfflictionCountChanged(Character);
}
}
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed));
@@ -883,6 +896,11 @@ namespace Barotrauma
}
}
/// <summary>
/// 0-1.
/// </summary>
public float OxygenLowResistance => !Character.NeedsOxygen ? 1 : GetResistance(oxygenLowAffliction.Prefab);
private void UpdateOxygen(float deltaTime)
{
if (!Character.NeedsOxygen)
@@ -1137,16 +1155,14 @@ namespace Barotrauma
}
}
public IEnumerable<Identifier> GetActiveAfflictionTags() => GetActiveAfflictionTags(afflictions.Keys);
private readonly HashSet<Identifier> afflictionTags = new HashSet<Identifier>();
public IEnumerable<Identifier> GetActiveAfflictionTags(IEnumerable<Affliction> afflictions)
public IEnumerable<Identifier> GetActiveAfflictionTags()
{
afflictionTags.Clear();
foreach (Affliction affliction in afflictions)
foreach (Affliction affliction in afflictions.Keys)
{
var currentEffect = affliction.GetActiveEffect();
if (currentEffect != null && !currentEffect.Tag.IsEmpty)
if (currentEffect is { Tag.IsEmpty: false })
{
afflictionTags.Add(currentEffect.Tag);
}