Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -69,21 +69,20 @@ namespace Barotrauma.Abilities
switch (targetType)
{
case TargetType.Enemy:
return !HumanAIController.IsFriendly(character, targetCharacter);
return !HumanAIController.IsFriendly(character, targetCharacter, onlySameTeam: false);
case TargetType.Ally:
return HumanAIController.IsFriendly(character, targetCharacter);
return HumanAIController.IsFriendly(character, targetCharacter, onlySameTeam: true);
case TargetType.NotSelf:
return targetCharacter != character;
case TargetType.Alive:
return !targetCharacter.IsDead;
case TargetType.Monster:
return !targetCharacter.IsHuman;
return !targetCharacter.IsHuman && !targetCharacter.IsPet;
case TargetType.InFriendlySubmarine:
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
default:
return true;
}
}
}
}
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -7,31 +6,13 @@ namespace Barotrauma.Abilities
{
class AbilityConditionMission : AbilityConditionData
{
private readonly ImmutableHashSet<MissionType> missionType;
private readonly ImmutableHashSet<Identifier> missionType;
private readonly bool isAffiliated;
public AbilityConditionMission(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
string[] missionTypeStrings = conditionElement.GetAttributeStringArray("missiontype", new []{ "None" })!;
HashSet<MissionType> missionTypes = new HashSet<MissionType>();
missionType = conditionElement.GetAttributeIdentifierImmutableHashSet("missiontype", ImmutableHashSet<Identifier>.Empty)!;
isAffiliated = conditionElement.GetAttributeBool("isaffiliated", false);
foreach (string missionTypeString in missionTypeStrings)
{
if (!Enum.TryParse(missionTypeString, out MissionType parsedMission) || parsedMission is MissionType.None)
{
if (!isAffiliated)
{
DebugConsole.ThrowError($"Error in AbilityConditionMission \"{characterTalent.DebugIdentifier}\" - \"{missionTypeString}\" is not a valid mission type.",
contentPackage: conditionElement.ContentPackage);
}
continue;
}
missionTypes.Add(parsedMission);
}
missionType = missionTypes.ToImmutableHashSet();
}
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
@@ -8,7 +8,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
foreach (Character c in GameSession.GetSessionCrewCharacters(CharacterType.Both))
foreach (Character c in Character.GetFriendlyCrew(character))
{
if (!c.IsDead && c.IsUnconscious)
{
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -14,8 +13,8 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
IEnumerable<Character> crewmembers = Character.GetFriendlyCrew(character);
int differentCrewAmount = crewmembers.Select(c => c.Info?.Job?.Prefab.Identifier).Distinct().Count();
IEnumerable<Character> crewMembers = Character.GetFriendlyCrew(character);
int differentCrewAmount = crewMembers.Select(c => c.Info.Job?.Prefab.Identifier).Distinct().Count();
return differentCrewAmount >= amount;
}
}
@@ -1,18 +1,13 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionHasSkill : AbilityConditionDataless
{
private readonly string skillIdentifier;
private readonly Identifier skillIdentifier;
private readonly float minValue;
public AbilityConditionHasSkill(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", string.Empty);
skillIdentifier = conditionElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
minValue = conditionElement.GetAttributeFloat("minvalue", 0f);
}
@@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Linq;
namespace Barotrauma.Abilities
{
@@ -14,7 +12,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
return Character.GetFriendlyCrew(character).Where(c => c.Info != null && (c.Info.GetCurrentLevel() - character.Info.GetCurrentLevel() >= levelsBehind)).Any();
return Character.GetFriendlyCrew(character).Any(c => c.Info.GetCurrentLevel() - character.Info.GetCurrentLevel() >= levelsBehind);
}
}
}
@@ -9,7 +9,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesCharacter(Character character)
{
int ownLevel = character.Info.GetCurrentLevel();
foreach (Character otherCharacter in GameSession.GetSessionCrewCharacters(CharacterType.Both))
foreach (Character otherCharacter in Character.GetFriendlyCrew(character))
{
if (otherCharacter == character) { continue; }
if (otherCharacter.Info.GetCurrentLevel() < ownLevel) { return false; }
@@ -13,13 +13,15 @@
{
if (GameMain.GameSession == null) { return false; }
foreach (Character character in GameMain.GameSession.Casualties)
foreach (Character deadCharacter in GameMain.GameSession.Casualties)
{
if (assistantsDontCount && character.Info?.Job?.Prefab.Identifier == "assistant")
if (deadCharacter.TeamID != character.TeamID) { continue; }
if (assistantsDontCount && deadCharacter.Info?.Job?.Prefab.Identifier == "assistant")
{
continue;
}
if (character.CauseOfDeath != null && character.CauseOfDeath.Type != CauseOfDeathType.Disconnected)
if (deadCharacter.CauseOfDeath != null && deadCharacter.CauseOfDeath.Type != CauseOfDeathType.Disconnected)
{
return false;
}
@@ -14,7 +14,8 @@
float waterVolume = 0.0f, totalVolume = 0.0f;
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine != character.Submarine) { continue; }
if (hull.Submarine is not { } hullSubmarine) { continue; }
if (hullSubmarine != character.Submarine || hullSubmarine.TeamID != character.TeamID) { continue; }
waterVolume += hull.WaterVolume;
totalVolume += hull.Volume;
}
@@ -100,7 +100,7 @@ namespace Barotrauma.Abilities
string type = abilityElement.Name.ToString().ToLowerInvariant();
try
{
abilityType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true);
abilityType = ReflectionUtils.GetTypeWithBackwardsCompatibility(ToolBox.BarotraumaAssembly, "Barotrauma.Abilities", type, false, true);
if (abilityType == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")",
@@ -19,6 +19,13 @@ namespace Barotrauma.Abilities
private bool effectBeingApplied;
/// <summary>
/// Should the character who has the ability be marked as the "user" of the status effect?
/// Means that e.g. enemies will consider damage from the effect to be coming from the character with the ability, and that the character will gain skills if the effect e.g. heals someone.
/// </summary>
private readonly bool setUser;
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
@@ -27,6 +34,7 @@ namespace Barotrauma.Abilities
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true);
nearbyCharactersAppliesToEnemies = abilityElement.GetAttributeBool("nearbycharactersappliestoenemies", true);
setUser = abilityElement.GetAttributeBool("setuser", true);
}
protected void ApplyEffectSpecific(Character targetCharacter, Limb targetLimb = null)
@@ -44,7 +52,7 @@ namespace Barotrauma.Abilities
if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
// currently used to spawn items on the targeted character
statusEffect.SetUser(targetCharacter);
if (setUser) { statusEffect.SetUser(targetCharacter); }
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
@@ -57,28 +65,28 @@ namespace Barotrauma.Abilities
}
if (!nearbyCharactersAppliesToAllies)
{
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character));
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character, onlySameTeam: true));
}
if (!nearbyCharactersAppliesToEnemies)
{
targets.RemoveAll(c => c is Character otherCharacter && !HumanAIController.IsFriendly(otherCharacter, Character));
}
statusEffect.SetUser(Character);
if (setUser) { statusEffect.SetUser(Character); }
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb) && targetLimb != null)
{
statusEffect.SetUser(Character);
if (setUser) { statusEffect.SetUser(Character); }
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetLimb);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
{
statusEffect.SetUser(Character);
if (setUser) { statusEffect.SetUser(Character); }
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
else
{
statusEffect.SetUser(Character);
if (setUser) { statusEffect.SetUser(Character); }
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character);
}
}
@@ -26,9 +26,9 @@ namespace Barotrauma.Abilities
return;
}
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
foreach (Character character in Character.GetFriendlyCrew(Character))
{
JobPrefab? characterJob = character.Info?.Job?.Prefab;
JobPrefab? characterJob = character.Info.Job?.Prefab;
if (characterJob is null) { continue; }
switch (characterJob.Identifier == apprenticeJob.Identifier)
@@ -30,7 +30,7 @@
foreach (Character otherCharacter in Character.GetFriendlyCrew(Character))
{
if (otherCharacter == Character) { continue; }
otherCharacter.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true);
otherCharacter.Info.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true);
}
}
else
@@ -4,14 +4,14 @@
{
private readonly Identifier afflictionId;
private readonly float strength;
private readonly string multiplyStrengthBySkill;
private readonly Identifier multiplyStrengthBySkill;
private readonly bool setValue;
public CharacterAbilityGiveAffliction(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
afflictionId = abilityElement.GetAttributeIdentifier("afflictionid", abilityElement.GetAttributeIdentifier("affliction", Identifier.Empty));
strength = abilityElement.GetAttributeFloat("strength", 0f);
multiplyStrengthBySkill = abilityElement.GetAttributeString("multiplystrengthbyskill", string.Empty);
multiplyStrengthBySkill = abilityElement.GetAttributeIdentifier("multiplystrengthbyskill", Identifier.Empty);
setValue = abilityElement.GetAttributeBool("setvalue", false);
if (afflictionId.IsEmpty)
@@ -52,7 +52,7 @@
return;
}
float strength = this.strength;
if (!string.IsNullOrEmpty(multiplyStrengthBySkill))
if (!multiplyStrengthBySkill.IsEmpty)
{
strength *= Character.GetSkillLevel(multiplyStrengthBySkill);
}
@@ -75,7 +75,7 @@ namespace Barotrauma.Abilities
{
foreach (Character c in Character.GetFriendlyCrew(Character))
{
c?.Info?.ChangeSavedStatValue(statType, value, identifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
c.Info.ChangeSavedStatValue(statType, value, identifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
}
}
else
@@ -20,9 +20,8 @@ namespace Barotrauma.Abilities
{
if (!addingFirstTime) { return; }
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
foreach (Character character in Character.GetFriendlyCrew(Character))
{
if (character.Info is null) { return; }
character.Info.AdditionalTalentPoints += amount;
}
}
@@ -6,7 +6,7 @@ namespace Barotrauma.Abilities
{
private readonly StatTypes statType;
private readonly float maxValue;
private readonly string skillIdentifier;
private readonly Identifier skillIdentifier;
private readonly bool useAll;
private float lastValue = 0f;
public override bool AllowClientSimulation => true;
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", string.Empty);
skillIdentifier = abilityElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
useAll = skillIdentifier == "all";
}
@@ -5,6 +5,8 @@
private readonly float addedValue;
private readonly float multiplyValue;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
namespace Barotrauma.Abilities
{
@@ -19,10 +19,30 @@ namespace Barotrauma.Abilities
}
}
protected override void ApplyEffect()
{
ApplyEffectToCharacter(Character);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is not IAbilityCharacter character) { return; }
character.Character.CharacterHealth.ReduceAfflictionOnAllLimbs(afflictionId, amount, attacker: Character);
if (abilityObject is IAbilityCharacter characterData)
{
ApplyEffectToCharacter(characterData.Character);
}
}
private void ApplyEffectToCharacter(Character character)
{
character?.CharacterHealth.ReduceAfflictionOnAllLimbs(afflictionId, amount, attacker: Character);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched)
{
ApplyEffect();
}
}
}
}
@@ -6,6 +6,7 @@ internal class CharacterAbilityUpgradeSubmarine : CharacterAbility
private readonly UpgradePrefab? upgradePrefab;
private readonly UpgradeCategory? upgradeCategory;
public readonly int level;
private readonly bool giveOnAddingFirstTime;
public override bool AllowClientSimulation => true;
@@ -13,7 +14,8 @@ internal class CharacterAbilityUpgradeSubmarine : CharacterAbility
{
var prefabIdentifier = abilityElement.GetAttributeIdentifier(nameof(upgradePrefab), Identifier.Empty);
var categoryIdentifier = abilityElement.GetAttributeIdentifier(nameof(upgradeCategory), Identifier.Empty);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
if (UpgradePrefab.Find(prefabIdentifier) is not { } foundUpgradePrefab)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityUpgradeSubmarine)} - {nameof(upgradePrefab)} not found.",
@@ -47,6 +49,14 @@ internal class CharacterAbilityUpgradeSubmarine : CharacterAbility
ApplyEffectSpecific();
}
public override void InitializeAbility(bool addingFirstTime)
{
if (addingFirstTime && giveOnAddingFirstTime)
{
ApplyEffectSpecific();
}
}
private void ApplyEffectSpecific()
{
if (upgradePrefab == null || upgradeCategory == null) { return; }
@@ -24,14 +24,18 @@ namespace Barotrauma.Abilities
foreach (Character enemyCharacter in enemyCharacters)
{
if (!enemyCharacter.IsHuman) { continue; }
if (enemyCharacter.Submarine == null || enemyCharacter.Submarine != Submarine.MainSub) { continue; }
if (enemyCharacter.Submarine == null ||
(Submarine.MainSub != null && enemyCharacter.Submarine != Submarine.MainSub))
{
continue;
}
if (enemyCharacter.IsDead) { continue; }
if (!enemyCharacter.LockHands) { continue; }
Character.GiveMoney(moneyAmount);
GameAnalyticsManager.AddMoneyGainedEvent(moneyAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier.Value);
foreach (Character character in Character.GetFriendlyCrew(Character))
{
character.Info?.GiveExperience(experienceAmount);
character.Info.GiveExperience(experienceAmount);
}
timesGiven++;
if (max > 0 && timesGiven >= max) { break; }
@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System;
using Barotrauma.Extensions;
@@ -31,7 +31,7 @@ namespace Barotrauma.Abilities
if (!TalentTree.JobTalentTrees.TryGet(apprentice.Identifier, out TalentTree? talentTree)) { return; }
ImmutableHashSet<Character> characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
var characters = Character.GetFriendlyCrew(Character);
HashSet<ImmutableHashSet<Identifier>> talentsTrees = new HashSet<ImmutableHashSet<Identifier>>();
foreach (TalentSubTree subTree in talentTree.TalentSubTrees)
@@ -60,13 +60,14 @@ namespace Barotrauma.Abilities
talentsTrees.Add(identifiers.ToImmutableHashSet());
}
ImmutableHashSet<Identifier> selectedTalentTree = talentsTrees.GetRandomUnsynced();
foreach (Identifier identifier in selectedTalentTree)
ImmutableHashSet<Identifier>? selectedTalentTree = talentsTrees.GetRandomUnsynced();
if (selectedTalentTree != null)
{
if (Character.HasTalent(identifier)) { continue; }
Character.GiveTalent(identifier);
foreach (Identifier identifier in selectedTalentTree)
{
if (Character.HasTalent(identifier)) { continue; }
Character.GiveTalent(identifier);
}
}
static bool IsShowCaseTalent(Identifier identifier, TalentOption option)
@@ -12,14 +12,20 @@
internal class CharacterAbilityWarStories : CharacterAbility
{
private readonly Identifier targetStat;
private readonly float minCondition;
private readonly float normalQualityThreshold;
private readonly float goodQualityThreshold;
private readonly float excellentQualityThreshold;
private readonly float masterworkQualityThreshold;
private readonly ItemPrefab prefab;
public CharacterAbilityWarStories(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
targetStat = abilityElement.GetAttributeIdentifier("target", Identifier.Empty);
minCondition = abilityElement.GetAttributeFloat("mincondition", 1);
normalQualityThreshold = abilityElement.GetAttributeFloat("normalqualitythreshold", 4);
goodQualityThreshold = abilityElement.GetAttributeFloat("goodqualitythreshold", 10);
excellentQualityThreshold = abilityElement.GetAttributeFloat("excellentqualitythreshold", 20);
masterworkQualityThreshold = abilityElement.GetAttributeFloat("masterworkqualitythreshold", 30);
if (targetStat.IsEmpty)
{
@@ -37,23 +43,28 @@ internal class CharacterAbilityWarStories : CharacterAbility
{
if (prefab is null || Character is null) { return; }
float condition = Character.Info?.GetSavedStatValue(StatTypes.None, targetStat) ?? 0;
if (condition < minCondition) { return; }
float statValue = Character.Info?.GetSavedStatValue(StatTypes.None, targetStat) ?? 0;
if (statValue < normalQualityThreshold) { return; }
int quality = 0;
if (statValue >= masterworkQualityThreshold) { quality = 3; }
else if (statValue >= excellentQualityThreshold) { quality = 2; }
else if (statValue >= goodQualityThreshold) { quality = 1; }
if (GameMain.GameSession?.RoundEnding ?? true)
{
Item item = new(prefab, Character.WorldPosition, Character.Submarine)
{
Condition = condition,
HealthMultiplier = condition
Quality = quality,
};
Character.Inventory.TryPutItem(item, Character, item.AllowedSlots);
}
else
{
Entity.Spawner?.AddItemToSpawnQueue(prefab, Character.Inventory, condition: condition, onSpawned: item =>
Entity.Spawner?.AddItemToSpawnQueue(prefab, Character.Inventory, quality: quality, onSpawned: item =>
{
item.HealthMultiplier = condition;
item.Quality = quality;
});
}
}
@@ -140,7 +140,7 @@ namespace Barotrauma.Abilities
string type = conditionElement.Name.ToString().ToLowerInvariant();
try
{
conditionType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true);
conditionType = ReflectionUtils.GetTypeWithBackwardsCompatibility(ToolBox.BarotraumaAssembly, "Barotrauma.Abilities", type, false, true);
if (conditionType == null)
{
if (errorMessages)
@@ -22,6 +22,11 @@ namespace Barotrauma
public readonly Sprite Icon;
/// <summary>
/// When set to true, this talent will not be visible in the "Extra Talents" panel if it is not part of the character's job talent tree.
/// </summary>
public readonly bool IsHiddenExtraTalent;
/// <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".
@@ -62,6 +67,8 @@ namespace Barotrauma
DisplayName = TextManager.Get(nameIdentifier).Fallback(Identifier.Value);
}
IsHiddenExtraTalent = element.GetAttributeBool("ishiddenextratalent", false);
Description = string.Empty;
#if CLIENT
@@ -131,7 +131,7 @@ namespace Barotrauma
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; }
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
if (IsTalentLocked(talentIdentifier)) { return false; }
if (IsTalentLocked(talentIdentifier, Character.GetFriendlyCrew(character))) { return false; }
if (character.Info.GetUnlockedTalentsInTree().Contains(talentIdentifier))
{
@@ -163,10 +163,8 @@ namespace Barotrauma
return false;
}
public static bool IsTalentLocked(Identifier talentIdentifier, ImmutableHashSet<Character> characterList = null)
public static bool IsTalentLocked(Identifier talentIdentifier, IEnumerable<Character> characterList)
{
characterList ??= GameSession.GetSessionCrewCharacters(CharacterType.Both);
foreach (Character c in characterList)
{
if (c.Info.GetSavedStatValue(StatTypes.LockedTalents, talentIdentifier) >= 1) { return true; }