v1.4.4.1 (Blood in the Water Update)

This commit is contained in:
Regalis11
2024-04-24 18:09:05 +03:00
parent 89b91d1c3e
commit ff1b8951a7
397 changed files with 15250 additions and 6479 deletions
@@ -12,9 +12,9 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction)
if (abilityObject is IAbilityAffliction { Affliction: Affliction affliction })
{
return afflictions.Any(a => a == affliction.Identifier);
return afflictions.Any(a => a == affliction.Identifier || a == affliction.Prefab.AfflictionType);
}
else
{
@@ -1,16 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionItemInSubmarine : AbilityConditionData
[TypePreviouslyKnownAs("AbilityConditionItemInSubmarine")]
class AbilityConditionInSubmarine : AbilityConditionData
{
private readonly SubmarineType? submarineType;
public AbilityConditionItemInSubmarine(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
public AbilityConditionInSubmarine(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
if (conditionElement.GetAttribute("submarinetype") != null)
{
submarineType = conditionElement.GetAttributeEnum<SubmarineType>("submarinetype", SubmarineType.Player);
submarineType = conditionElement.GetAttributeEnum("submarinetype", SubmarineType.Player);
}
}
@@ -30,9 +29,15 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityObject, typeof(IAbilityItem));
return false;
return MatchesCondition();
}
}
public override bool MatchesCondition()
{
if (character.Submarine is null) { return false; }
return character.Submarine?.Info?.Type == submarineType;
}
}
}
@@ -8,6 +8,7 @@ namespace Barotrauma.Abilities
{
private readonly Option<int> matchedLevel;
private readonly Option<int> minLevel;
private readonly Option<int> maxLevel;
public AbilityConditionHasLevel(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
@@ -18,23 +19,33 @@ namespace Barotrauma.Abilities
minLevel = conditionElement.GetAttributeInt("minlevel", 0) is var min and not 0
? Option<int>.Some(min)
: Option<int>.None();
maxLevel = conditionElement.GetAttributeInt("maxlevel", 0) is var max and not 0
? Option<int>.Some(max)
: Option<int>.None();
if (matchedLevel.IsNone() && minLevel.IsNone())
if (matchedLevel.IsNone() && minLevel.IsNone() && maxLevel.IsNone())
{
throw new Exception($"{nameof(AbilityConditionHasLevel)} must have either \"levelequals\" or \"minlevel\" attribute.");
throw new Exception($"{nameof(AbilityConditionHasLevel)} must have either \"levelequals\", \"minlevel\" or \"maxlevel\" attribute.");
}
}
protected override bool MatchesConditionSpecific()
{
var currentLevel = character.Info.GetCurrentLevel();
if (matchedLevel.TryUnwrap(out int match))
{
return character.Info.GetCurrentLevel() == match;
return currentLevel == match;
}
if (minLevel.TryUnwrap(out int min))
{
return character.Info.GetCurrentLevel() >= min;
return currentLevel >= min;
}
if (maxLevel.TryUnwrap(out int max))
{
return currentLevel <= max;
}
return false;
@@ -12,7 +12,9 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
return character.IsRagdolled || character.Stun > 0f || character.IsIncapacitated;
// TODO: Should we only check whether the target is ragdolling here?
// Or should we use character.IsKnockedDown instead?
return (character.IsRagdolled && !character.AnimController.IsHangingWithRope) || character.Stun > 0f || character.IsIncapacitated;
}
}
}
@@ -100,7 +100,7 @@ namespace Barotrauma.Abilities
string type = abilityElement.Name.ToString().ToLowerInvariant();
try
{
abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
abilityType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true);
if (abilityType == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")",
@@ -21,24 +21,42 @@
}
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched)
{
ApplyEffect();
}
}
protected override void ApplyEffect()
{
ApplyAfflictionToCharacter(Character);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is IAbilityCharacter character)
{
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}\".",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
float strength = this.strength;
if (!string.IsNullOrEmpty(multiplyStrengthBySkill))
{
strength *= Character.GetSkillLevel(multiplyStrengthBySkill);
}
character.Character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(strength), allowStacking: !setValue);
ApplyAfflictionToCharacter(character.Character);
}
}
private void ApplyAfflictionToCharacter(Character character)
{
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}\".",
contentPackage: CharacterTalent.Prefab.ContentPackage);
return;
}
float strength = this.strength;
if (!string.IsNullOrEmpty(multiplyStrengthBySkill))
{
strength *= Character.GetSkillLevel(multiplyStrengthBySkill);
}
character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(strength), allowStacking: !setValue);
}
}
}
@@ -15,12 +15,13 @@
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.",
contentPackage: abilityElement.ContentPackage);
}
// NOTE: The resistance value is a multiplier here, so 1.0 == 0% resistance
if (MathUtils.NearlyEqual(multiplier, 1))
{
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing.",
contentPackage: abilityElement.ContentPackage);
}
}
public override void InitializeAbility(bool addingFirstTime)
@@ -22,7 +22,7 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is not IAbilityCharacter character) { return; }
character.Character.CharacterHealth.ReduceAfflictionOnAllLimbs(afflictionId, amount);
character.Character.CharacterHealth.ReduceAfflictionOnAllLimbs(afflictionId, amount, attacker: Character);
}
}
}
@@ -0,0 +1,57 @@
#nullable enable
namespace Barotrauma.Abilities;
internal class CharacterAbilityUpgradeSubmarine : CharacterAbility
{
private readonly UpgradePrefab? upgradePrefab;
private readonly UpgradeCategory? upgradeCategory;
public readonly int level;
public override bool AllowClientSimulation => true;
public CharacterAbilityUpgradeSubmarine(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
var prefabIdentifier = abilityElement.GetAttributeIdentifier(nameof(upgradePrefab), Identifier.Empty);
var categoryIdentifier = abilityElement.GetAttributeIdentifier(nameof(upgradeCategory), Identifier.Empty);
if (UpgradePrefab.Find(prefabIdentifier) is not { } foundUpgradePrefab)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityUpgradeSubmarine)} - {nameof(upgradePrefab)} not found.",
contentPackage: abilityElement.ContentPackage);
}
else
{
upgradePrefab = foundUpgradePrefab;
}
if (UpgradeCategory.Find(categoryIdentifier) is not { } foundUpgradeCategory)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityUpgradeSubmarine)} - {nameof(upgradeCategory)} not found.",
contentPackage: abilityElement.ContentPackage);
}
else
{
upgradeCategory = foundUpgradeCategory;
}
level = abilityElement.GetAttributeInt(nameof(level), 1);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
ApplyEffectSpecific();
}
protected override void ApplyEffect()
{
ApplyEffectSpecific();
}
private void ApplyEffectSpecific()
{
if (upgradePrefab == null || upgradeCategory == null) { return; }
if (GameMain.GameSession?.Campaign?.UpgradeManager is not { } upgradeManager) { return; }
upgradeManager.AddUpgradeExternally(upgradePrefab, upgradeCategory, level);
}
}
@@ -0,0 +1,63 @@
namespace Barotrauma.Abilities;
/// <summary>
/// Hardcoded ability for the "War Stories" talent.
/// Spawns an item and sets the health multiplier to the target stat value.
///
/// The item spawned should have a default health of 1 because we set the multiplier.
/// This is because we already had existing Item.HealthMultiplier that gets synced and
/// everything but not one for setting the max health directly to some value and I didn't
/// want to add a new one just for this.
/// </summary>
internal class CharacterAbilityWarStories : CharacterAbility
{
private readonly Identifier targetStat;
private readonly float minCondition;
private readonly ItemPrefab prefab;
public CharacterAbilityWarStories(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
targetStat = abilityElement.GetAttributeIdentifier("target", Identifier.Empty);
minCondition = abilityElement.GetAttributeFloat("mincondition", 1);
if (targetStat.IsEmpty)
{
DebugConsole.ThrowError($"{nameof(CharacterAbilityWarStories)}: target stat is not defined", contentPackage: abilityElement.ContentPackage);
}
Identifier spawnedItem = abilityElement.GetAttributeIdentifier("item", Identifier.Empty);
if (!ItemPrefab.Prefabs.TryGet(spawnedItem, out prefab))
{
DebugConsole.ThrowError($"{nameof(CharacterAbilityWarStories)}: spawned item \"{spawnedItem}\" could not be found.", contentPackage: abilityElement.ContentPackage);
}
}
protected override void ApplyEffect()
{
if (prefab is null || Character is null) { return; }
float condition = Character.Info?.GetSavedStatValue(StatTypes.None, targetStat) ?? 0;
if (condition < minCondition) { return; }
if (GameMain.GameSession?.RoundEnding ?? true)
{
Item item = new(prefab, Character.WorldPosition, Character.Submarine)
{
Condition = condition,
HealthMultiplier = condition
};
Character.Inventory.TryPutItem(item, Character, item.AllowedSlots);
}
else
{
Entity.Spawner?.AddItemToSpawnQueue(prefab, Character.Inventory, condition: condition, onSpawned: item =>
{
item.HealthMultiplier = condition;
});
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
=> ApplyEffect();
}
@@ -140,7 +140,7 @@ namespace Barotrauma.Abilities
string type = conditionElement.Name.ToString().ToLowerInvariant();
try
{
conditionType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
conditionType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true);
if (conditionType == null)
{
if (errorMessages)
@@ -22,6 +22,12 @@ namespace Barotrauma
public readonly Sprite Icon;
/// <summary>
/// When set to a value the talent tooltip will display a text showing the current value of the stat and the max value.
/// For example "Progress: 37/100".
/// </summary>
public readonly Option<(Identifier PermanentStatIdentifier, int Max)> TrackedStat;
#if CLIENT
public readonly Option<Color> ColorOverride;
#endif
@@ -44,6 +50,12 @@ namespace Barotrauma
AbilityEffectsStackWithSameTalent = element.GetAttributeBool("abilityeffectsstackwithsametalent", true);
var trackedStat = element.GetAttributeIdentifier("trackedstat", Identifier.Empty);
var trackedMax = element.GetAttributeInt("trackedmax", 100);
TrackedStat = !trackedStat.IsEmpty
? Option.Some((trackedStat, trackedMax))
: Option.None;
Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", Identifier.Empty);
if (!nameIdentifier.IsEmpty)
{
@@ -82,18 +82,17 @@ namespace Barotrauma
TalentSubTree subTree = talentTree!.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier);
if (subTree is null) { return TalentStages.Invalid; }
if (!TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents))
{
return TalentStages.Locked;
}
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
if (targetTalentOption.HasEnoughTalents(character.Info))
{
return TalentStages.Unlocked;
}
if (!TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents))
{
return TalentStages.Locked;
}
if (targetTalentOption.HasSelectedTalent(selectedTalents))
{
return TalentStages.Highlighted;