Unstable 0.1500.1.0 (BaroDev edition)

This commit is contained in:
Markus Isberg
2021-09-03 21:56:31 +09:00
parent 501e02c026
commit e7b7c1a748
143 changed files with 2928 additions and 1356 deletions
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly string itemIdentifier;
private readonly string[] tags;
private WeaponType weapontype;
private readonly WeaponType weapontype;
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
@@ -33,7 +33,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is AttackData attackData)
if (abilityData is AbilityAttackData attackData)
{
Item item = attackData?.SourceAttack?.SourceItem;
@@ -71,7 +71,7 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false;
}
}
@@ -30,7 +30,7 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false;
}
}
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -1,27 +0,0 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHandsomeStranger : AbilityConditionData
{
string skillIdentifier;
public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is string skillIdentifier)
{
return this.skillIdentifier == skillIdentifier;
}
else
{
LogAbilityConditionError(abilityData, typeof(string));
return false;
}
}
}
}
@@ -0,0 +1,54 @@
using Barotrauma.Items.Components;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionIsAiming : AbilityConditionDataless
{
private enum WeaponType
{
Any = 0,
Melee = 1,
Ranged = 2
};
private WeaponType weapontype;
public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
switch (conditionElement.GetAttributeString("weapontype", ""))
{
case "melee":
weapontype = WeaponType.Melee;
break;
case "ranged":
weapontype = WeaponType.Ranged;
break;
}
}
protected override bool MatchesConditionSpecific()
{
bool aimingCorrectItem = false;
if (character.AnimController is HumanoidAnimController animController)
{
foreach (Item item in character.HeldItems)
{
switch (weapontype)
{
case WeaponType.Melee:
aimingCorrectItem |= item.GetComponent<MeleeWeapon>() != null && animController.IsAimingMelee;
break;
case WeaponType.Ranged:
aimingCorrectItem |= item.GetComponent<RangedWeapon>() != null && animController.IsAiming;
break;
default:
aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee;
break;
}
}
}
return aimingCorrectItem;
}
}
}
@@ -22,10 +22,9 @@ namespace Barotrauma.Abilities
{
item = tempItem.Prefab;
}
// this and other instances of this type of casting will be refactored
else if (abilityData is (ItemPrefab itemPrefab, object _))
else if (abilityData is IAbilityItemPrefab abilityItemPrefab)
{
item = itemPrefab;
item = abilityItemPrefab.ItemPrefab;
}
if (item != null)
@@ -15,17 +15,17 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is (Affliction affliction, float reduceAmount))
if (abilityData is IAbilityAffliction abilityAffliction)
{
if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; }
if (allowedTypes.Find(c => c == abilityAffliction.Affliction.Prefab.AfflictionType) == null) { return false; }
if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; }
if (!string.IsNullOrEmpty(identifier) && abilityAffliction.Affliction.Prefab.Identifier != identifier) { return false; }
return true;
}
else
{
LogAbilityConditionError(abilityData, typeof((Affliction, float)));
LogAbilityConditionError(abilityData, typeof(IAbilityAffliction));
return false;
}
}
@@ -0,0 +1,32 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionSkill : AbilityConditionData
{
private readonly string skillIdentifier;
public AbilityConditionSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
}
private bool MatchesConditionSpecific(string skillIdentifier)
{
return this.skillIdentifier == skillIdentifier;
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if ((abilityData as string ?? (abilityData as IAbilityString)?.String) is string skillIdentifier)
{
return MatchesConditionSpecific(skillIdentifier);
}
else
{
LogAbilityConditionError(abilityData, typeof(string));
return false;
}
}
}
}
@@ -1,11 +1,10 @@
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAboveVitality : AbilityConditionDataless
{
float vitalityPercentage;
private readonly float vitalityPercentage;
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
@@ -0,0 +1,26 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionCoauthor : AbilityConditionDataless
{
private readonly string jobIdentifier;
public AbilityConditionCoauthor(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
jobIdentifier = conditionElement.GetAttributeString("jobidentifier", string.Empty);
}
protected override bool MatchesConditionSpecific()
{
if (character.SelectedCharacter is Character otherCharacter)
{
if (!otherCharacter.HasJob(jobIdentifier)) { return false; }
if (!(character.SelectedBy == otherCharacter)) { return false; }
return true;
}
return false;
}
}
}
@@ -1,8 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -0,0 +1,23 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasPermanentStat : AbilityConditionDataless
{
private readonly StatTypes statType;
private readonly float min;
public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
statType = CharacterAbilityGroup.ParseStatType(conditionElement.GetAttributeString("stattype", ""), characterTalent.DebugIdentifier);
min = conditionElement.GetAttributeFloat("min", 0f);
}
protected override bool MatchesConditionSpecific()
{
// should consider decoupling this from stat values entirely
return character.Info.GetSavedStatValue(statType) >= min;
}
}
}
@@ -0,0 +1,31 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasStatusTag : AbilityConditionDataless
{
private readonly string tag;
public AbilityConditionHasStatusTag(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
tag = conditionElement.GetAttributeString("tag", "");
if (string.IsNullOrEmpty(tag))
{
DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.");
}
}
protected override bool MatchesConditionSpecific()
{
if (!string.IsNullOrEmpty(tag))
{
return
StatusEffect.DurationList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag)) ||
DelayedEffect.DelayList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag));
}
return false;
}
}
}
@@ -0,0 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInFriendlySubmarine : AbilityConditionDataless
{
public AbilityConditionInFriendlySubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.Submarine?.TeamID == character.TeamID;
}
}
}
@@ -0,0 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInHull : AbilityConditionDataless
{
public AbilityConditionInHull(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.CurrentHull != null;
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionLevelsBehindHighest : AbilityConditionDataless
{
private readonly int levelsBehind;
public AbilityConditionLevelsBehindHighest(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
levelsBehind = conditionElement.GetAttributeInt("levelsbehind", 0);
}
protected override bool MatchesConditionSpecific()
{
return Character.GetFriendlyCrew(character).Where(c => c.Info != null && (c.Info.GetCurrentLevel() - character.Info.GetCurrentLevel() >= levelsBehind)).Any();
}
}
}
@@ -24,13 +24,13 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is (Mission mission, AbilityValue missionAbilityValue))
if (abilityData is IAbilityMission abilityMission)
{
return mission.Prefab.Type == missionType;
return abilityMission.Mission.Prefab.Type == missionType;
}
else
{
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue)));
LogAbilityConditionError(abilityData, typeof(IAbilityMission));
return false;
}
}
@@ -1,14 +1,10 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionServerRandom : AbilityConditionDataless
{
private float randomChance = 0f;
private readonly float randomChance = 0f;
public override bool AllowClientSimulation => false;
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
@@ -0,0 +1,32 @@
namespace Barotrauma.Abilities
{
interface IAbilityItemPrefab
{
public ItemPrefab ItemPrefab { get; set; }
}
interface IAbilityValue
{
public float Value { get; set; }
}
interface IAbilityMission
{
public Mission Mission { get; set; }
}
interface IAbilityCharacter
{
public Character Character { get; set; }
}
interface IAbilityString
{
public string String { get; set; }
}
interface IAbilityAffliction
{
public Affliction Affliction { get; set; }
}
}
@@ -0,0 +1,85 @@
using System.Collections.Generic;
namespace Barotrauma.Abilities
{
class AbilityValue : IAbilityValue
{
public AbilityValue(float value)
{
Value = value;
}
public float Value { get; set; }
}
class AbilityValueItem : IAbilityValue, IAbilityItemPrefab
{
public AbilityValueItem(float value, ItemPrefab itemPrefab)
{
Value = value;
ItemPrefab = itemPrefab;
}
public float Value { get; set; }
public ItemPrefab ItemPrefab { get; set; }
}
class AbilityValueString : IAbilityValue, IAbilityString
{
public AbilityValueString(float value, string abilityString)
{
Value = value;
String = abilityString;
}
public float Value { get; set; }
public string String { get; set; }
}
class AbilityStringCharacter : IAbilityCharacter, IAbilityString
{
public AbilityStringCharacter(string abilityString, Character character)
{
String = abilityString;
Character = character;
}
public Character Character { get; set; }
public string String { get; set; }
}
class AbilityValueAffliction : IAbilityValue, IAbilityAffliction
{
public AbilityValueAffliction(float value, Affliction affliction)
{
Value = value;
Affliction = affliction;
}
public float Value { get; set; }
public Affliction Affliction { get; set; }
}
class AbilityValueMission : IAbilityValue, IAbilityMission
{
public AbilityValueMission(float value, Mission mission)
{
Value = value;
Mission = mission;
}
public float Value { get; set; }
public Mission Mission { get; set; }
}
class AbilityAttackData : IAbilityCharacter
{
public float DamageMultiplier { get; set; } = 1f;
public float AddedPenetration { get; set; } = 0f;
public List<Affliction> Afflictions { get; set; }
public Attack SourceAttack { get; }
public Character Character { get; set; }
public Character Attacker { get; set; }
public AbilityAttackData(Attack sourceAttack, Character character)
{
SourceAttack = sourceAttack;
Character = character;
}
}
}
@@ -12,12 +12,16 @@ namespace Barotrauma.Abilities
public CharacterTalent CharacterTalent { get; }
public Character Character { get; }
public virtual bool RequiresAlive => true;
public bool RequiresAlive { get; }
public virtual bool AllowClientSimulation => false;
public virtual bool AppliesEffectOnIntervalUpdate => false;
private const float DefaultEffectTime = 1.0f;
// currently resets if the character dies. would need to be stored in a dictionary of sorts to maintain through death
/// <summary>
/// Used primarily for StatusEffects. Default to constant outside interval abilities.
/// </summary>
@@ -28,6 +32,7 @@ namespace Barotrauma.Abilities
CharacterAbilityGroup = characterAbilityGroup;
CharacterTalent = characterAbilityGroup.CharacterTalent;
Character = CharacterTalent.Character;
RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true);
}
public bool IsViable()
@@ -132,11 +137,5 @@ namespace Barotrauma.Abilities
}
return flagType;
}
public static float DistanceToSquaredDistance(float distance)
{
return distance * distance;
}
}
}
@@ -10,16 +10,41 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects;
private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
}
protected void ApplyEffectSpecific(Character targetCharacter)
{
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
// currently used this to spawn items on the targeted character
statusEffect.SetUser(targetCharacter);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.This))
{
statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character);
}
else
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
}
}
@@ -30,13 +55,17 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Character targetCharacter)
if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter)
{
ApplyEffectSpecific(selectedCharacter);
}
else if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
else
else
{
ApplyEffect();
ApplyEffect();
}
}
}
@@ -0,0 +1,30 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects
{
private readonly bool allowSelf;
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
protected override void ApplyEffect()
{
IEnumerable<Character> chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character);
foreach (Character character in chosenCharacters)
{
ApplyEffectSpecific(character);
}
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToAttacker : CharacterAbilityApplyStatusEffects
{
public CharacterAbilityApplyStatusEffectsToAttacker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect(object abilityData)
{
if ((abilityData as AbilityAttackData)?.Attacker is Character attacker)
{
ApplyEffectSpecific(attacker);
}
}
}
}
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -10,7 +9,7 @@ namespace Barotrauma.Abilities
protected float squaredMaxDistance;
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
}
protected override void ApplyEffect()
@@ -20,7 +19,7 @@ namespace Barotrauma.Abilities
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
{
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
if (crewCharacter != Character && Vector2.DistanceSquared(Character.WorldPosition, crewCharacter.WorldPosition) is float tempDistance && tempDistance < closestDistance)
{
closestCharacter = crewCharacter;
closestDistance = tempDistance;
@@ -1,5 +1,6 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -16,7 +17,7 @@ namespace Barotrauma.Abilities
public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true);
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
@@ -26,9 +27,9 @@ namespace Barotrauma.Abilities
Character chosenCharacter = null;
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
(allowSelf ||c != Character) &&
(allowSelf || c != Character) &&
(allowDifferentSub || c.Submarine == Character.Submarine) &&
Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance &&
Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) is float tempDistance &&
tempDistance < squaredMaxDistance).GetRandom();
if (chosenCharacter == null) { return; }
@@ -7,16 +7,41 @@ namespace Barotrauma.Abilities
{
public override bool AppliesEffectOnIntervalUpdate => true;
private int amount;
private readonly int amount;
private StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
scalingStatType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("scalingstattype", "None"), CharacterTalent.DebugIdentifier);
}
private void ApplyEffectSpecific(Character targetCharacter)
{
float multiplier = 1f;
if (scalingStatType != StatTypes.None)
{
multiplier = 0 + Character.Info.GetSavedStatValue(scalingStatType);
}
targetCharacter.GiveMoney((int)(multiplier * amount));
}
protected override void ApplyEffect(object abilityData)
{
if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
else
{
ApplyEffectSpecific(Character);
}
}
protected override void ApplyEffect()
{
Character.GiveMoney(amount);
ApplyEffectSpecific(Character);
}
}
}
@@ -8,8 +8,10 @@ namespace Barotrauma.Abilities
private readonly string statIdentifier;
private readonly StatTypes statType;
private readonly float value;
private readonly float maxValue;
private readonly bool targetAllies;
private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
//private readonly float maximumValue;
public override bool AppliesEffectOnIntervalUpdate => true;
@@ -19,8 +21,10 @@ namespace Barotrauma.Abilities
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue);
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
}
@@ -38,11 +42,11 @@ namespace Barotrauma.Abilities
{
if (targetAllies)
{
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath));
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue));
}
else
{
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue);
}
}
}
@@ -4,18 +4,23 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
private readonly string resistanceId;
private readonly float multiplier;
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (string.IsNullOrEmpty(resistanceId))
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
}
}
public override void InitializeAbility(bool addingFirstTime)
{
Character.ChangeAbilityResistance(resistanceId, resistance);
Character.ChangeAbilityResistance(resistanceId, multiplier);
}
}
}
@@ -4,10 +4,9 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveStat : CharacterAbility
{
private StatTypes statType;
private float value;
private readonly StatTypes statType;
private readonly float value;
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
@@ -7,8 +7,9 @@ namespace Barotrauma.Abilities
{
private readonly List<Affliction> afflictions;
float addedDamageMultiplier;
float addedPenetration;
private readonly float addedDamageMultiplier;
private readonly float addedPenetration;
private readonly bool implode;
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -18,11 +19,12 @@ namespace Barotrauma.Abilities
}
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
implode = abilityElement.GetAttributeBool("implode", false);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AttackData attackData)
if (abilityData is AbilityAttackData attackData)
{
if (attackData.Afflictions == null)
{
@@ -34,6 +36,13 @@ namespace Barotrauma.Abilities
}
attackData.DamageMultiplier += addedDamageMultiplier;
attackData.AddedPenetration += addedPenetration;
if (implode)
{
// might have issues, as the method used to be private and only used for pressure death
attackData.Character?.Implode();
}
}
else
{
@@ -4,8 +4,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityModifyResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
private readonly string resistanceId;
private readonly float resistance;
bool lastState;
// should probably be split to different classes
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
if (string.IsNullOrEmpty(resistanceId))
{
DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set.");
}
}
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
@@ -0,0 +1,52 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToSkill : CharacterAbility
{
private readonly StatTypes statType;
private readonly float maxValue;
private readonly string skillIdentifier;
private readonly bool useAll;
private float lastValue = 0f;
public CharacterAbilityModifyStatToSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", string.Empty);
useAll = skillIdentifier == "all";
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched)
{
float skillTotal = 0f;
if (useAll && Character.Info?.Job != null)
{
foreach (Skill skill in Character.Info.Job.Skills)
{
skillTotal += Character.GetSkillLevel(skill.Identifier);
}
skillTotal /= Character.Info.Job.Skills.Count;
}
else
{
skillTotal = Character.GetSkillLevel(skillIdentifier);
}
lastValue = skillTotal / 100f * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -5,42 +5,21 @@ namespace Barotrauma.Abilities
class CharacterAbilityModifyValue : CharacterAbility
{
private float addedValue;
private float multiplierValue;
private float multiplyValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AbilityValue abilityValue)
if (abilityData is IAbilityValue abilityValue)
{
ApplyEffectSpecific(abilityValue);
abilityValue.Value += addedValue;
abilityValue.Value *= multiplyValue;
}
else if (abilityData is (object _, AbilityValue tupleAbilityValue))
{
ApplyEffectSpecific(tupleAbilityValue);
}
}
private void ApplyEffectSpecific(AbilityValue abilityValue)
{
abilityValue.Value += addedValue;
abilityValue.Value *= multiplierValue;
}
}
// this seems like a real silly way to have to pass values by reference into these same interfaces
// if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead
class AbilityValue
{
public float Value { get; set; }
public AbilityValue(float value)
{
Value = value;
}
}
}
@@ -5,7 +5,7 @@ namespace Barotrauma.Abilities
class CharacterAbilityResetPermanentStat : CharacterAbility
{
private readonly string statIdentifier;
public override bool RequiresAlive => false;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityRevive : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityRevive(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
private void ApplyEffectSpecific()
{
Character.Revive();
}
protected override void ApplyEffect()
{
ApplyEffectSpecific();
}
protected override void ApplyEffect(object abilityData)
{
ApplyEffectSpecific();
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityAlienHoarder : CharacterAbility
{
private readonly float addedDamageMultiplierPerItem;
private readonly int maxAmount;
private readonly string[] tags;
public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f);
maxAmount = abilityElement.GetAttributeInt("maxamount", 0);
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AbilityAttackData attackData)
{
float totalAddedDamageMultiplier = 0f;
foreach (Item item in Character.Inventory.AllItems)
{
if (tags.Any(t => item.Prefab.Tags.Any(p => t == p)))
{
totalAddedDamageMultiplier += addedDamageMultiplierPerItem;
}
}
attackData.DamageMultiplier += addedDamageMultiplierPerItem;
}
else
{
LogAbilityDataMismatch();
}
}
}
}
@@ -12,9 +12,9 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(object abilityData)
{
if (abilityData is (string skillIdentifier, Character character) && character != Character)
if (abilityData is AbilityStringCharacter abilityStringCharacter && abilityStringCharacter.Character != Character)
{
character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f);
Character.Info?.IncreaseSkillLevel(abilityStringCharacter.String, 1.0f, abilityStringCharacter.Character.Position + Vector2.UnitY * 175.0f);
}
}
}
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityByTheBook : CharacterAbility
{
private int moneyAmount;
private int max;
public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0);
max = abilityElement.GetAttributeInt("max", 0);
}
protected override void ApplyEffect()
{
IEnumerable<Character> enemyCharacters = Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.None);
int timesGiven = 0;
foreach (Character enemyCharacter in enemyCharacters)
{
if (!enemyCharacter.IsHuman) { continue; }
if (enemyCharacter.Submarine == null || enemyCharacter.Submarine != Submarine.MainSub) { continue; }
if (enemyCharacter.IsDead) { continue; }
if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount);
timesGiven++;
}
}
}
}
@@ -9,45 +9,22 @@ namespace Barotrauma.Abilities
class CharacterAbilityInsurancePolicy : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public override bool RequiresAlive => false;
private readonly int moneyPerLevel;
private bool hasOccurred = false;
private readonly int moneyPerMission;
private static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0);
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
}
protected override void ApplyEffect()
{
if (Character?.Info is CharacterInfo info && !hasOccurred)
if (Character?.Info is CharacterInfo info)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
{
if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; }
}
}
Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel());
hasOccurred = true;
// this is an ugly way to do this, but this effect should not occur more than once per round for a client
// this seemed like the simplest way to do it since characters are instantiated from scratch each time
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
{
if (client.Character == Character)
{
clientsAlreadyUsed.Add(client);
}
}
}
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
}
}
}
@@ -5,14 +5,14 @@ namespace Barotrauma.Abilities
class CharacterAbilityPsychoClown : CharacterAbility
{
private StatTypes statType;
private float value;
private float maxValue;
private string afflictionIdentifier;
private float lastValue = 0f;
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
}
@@ -32,7 +32,7 @@ namespace Barotrauma.Abilities
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
}
lastValue = afflictionStrength * value;
lastValue = afflictionStrength * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
@@ -8,6 +8,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityRegenerateLoot : CharacterAbility
{
// not maintained through death, so it's possible for players to respawn and re-loot chests
// seems like a minor issue for now
List<Item> openedContainers = new List<Item>();
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
@@ -10,20 +10,20 @@ namespace Barotrauma.Abilities
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsReset;
private int maxEnemyCount;
private float squaredDistance;
private readonly int maxEnemyCount;
private readonly float squaredDistance;
public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset"));
maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0);
squaredDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("distance", 0));
squaredDistance = MathF.Pow(abilityElement.GetAttributeFloat("distance", 0), 2);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
int numberOfEnemiesInRange = Character.CharacterList.Where(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) < squaredDistance).Count();
int numberOfEnemiesInRange = Character.CharacterList.Count(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) < squaredDistance);
foreach (var statusEffect in statusEffectsReset)
{
@@ -8,6 +8,7 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
{
// this should just be its own class, misleading to inherit here
private string tag;
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -14,6 +14,10 @@ namespace Barotrauma.Abilities
// currently only used to turn off simulation if random conditions are in use
public bool IsActive { get; private set; } = true;
protected int maxTriggerCount { get; }
protected int timesTriggered = 0;
// add support for OR conditions?
protected readonly List<AbilityCondition> abilityConditions = new List<AbilityCondition>();
@@ -24,7 +28,7 @@ namespace Barotrauma.Abilities
{
CharacterTalent = characterTalent;
Character = CharacterTalent.Character;
maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
foreach (XElement subElement in abilityElementGroup.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -27,6 +27,7 @@ namespace Barotrauma.Abilities
private bool IsApplicable(object abilityData)
{
if (timesTriggered >= maxTriggerCount) { return false; }
return abilityConditions.All(c => c.MatchesCondition(abilityData));
}
}
@@ -14,6 +14,7 @@ namespace Barotrauma.Abilities
private float effectDelay;
private float effectDelayTimer;
public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup)
{
// too many overlapping intervals could cause hitching? maybe randomize a little
@@ -42,6 +43,7 @@ namespace Barotrauma.Abilities
}
private bool IsApplicable()
{
if (timesTriggered >= maxTriggerCount) { return false; }
return abilityConditions.All(c => c.MatchesCondition());
}
}
@@ -111,8 +111,7 @@ namespace Barotrauma
public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString)
{
AbilityEffectType abilityEffectType = AbilityEffectType.Undefined;
if (!Enum.TryParse(abilityEffectTypeString, true, out abilityEffectType))
if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType))
{
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
}
@@ -1,7 +1,5 @@
using Microsoft.Xna.Framework;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
@@ -13,6 +11,12 @@ namespace Barotrauma
public ContentPackage ContentPackage { get; private set; }
public string FilePath { get; private set; }
public string DisplayName { get; private set; }
public string Description { get; private set; }
public readonly Sprite Icon;
public static readonly PrefabCollection<TalentPrefab> TalentPrefabs = new PrefabCollection<TalentPrefab>();
public XElement ConfigElement
@@ -26,7 +30,51 @@ namespace Barotrauma
FilePath = filePath;
ConfigElement = element;
Identifier = element.GetAttributeString("identifier", "noidentifier");
DisplayName = TextManager.Get("talentname." + Identifier, returnNull: true) ?? Identifier;
this.CalculatePrefabUIntIdentifier(TalentPrefabs);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "icon":
Icon = new Sprite(subElement);
break;
case "description":
string tempDescription = Description;
TextManager.ConstructDescription(ref tempDescription, subElement);
Description = tempDescription;
break;
}
}
if (string.IsNullOrEmpty(Description))
{
if (element.Attribute("description") != null)
{
string description = element.GetAttributeString("description", string.Empty);
Description = TextManager.Get(description, returnNull: true) ?? description;
}
else
{
Description = TextManager.Get("talentdescription." + Identifier, returnNull: true) ?? string.Empty;
}
}
#if DEBUG
if (!TextManager.ContainsTag("talentname." + Identifier))
{
DebugConsole.AddWarning($"Name for the talent \"{Identifier}\" not found in the text files.");
}
if (string.IsNullOrEmpty(Description))
{
DebugConsole.AddWarning($"Description for the talent \"{Identifier}\" not configured");
}
if (Description.Contains('['))
{
DebugConsole.ThrowError($"Description for the talent \"{Identifier}\" contains brackets - was some variable not replaced correctly? ({Description})");
}
#endif
}
private bool disposed = false;
@@ -94,9 +142,6 @@ namespace Barotrauma
{
LoadFromFile(file);
}
}
}
}
@@ -14,6 +14,7 @@ namespace Barotrauma
private static HashSet<string> subtreeTalents = new HashSet<string>();
private const string PlaceholderTalent = "placeholder";
public XElement ConfigElement
{
get;
@@ -41,6 +42,7 @@ namespace Barotrauma
HashSet<string> duplicateSet = new HashSet<string>();
foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))))
{
if (talent == PlaceholderTalent) { continue; }
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null)
{
@@ -118,6 +120,7 @@ namespace Barotrauma
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable<string> selectedTalents)
{
if (talentIdentifier == PlaceholderTalent) { return false; }
if (character?.Info?.Job.Prefab == null) { return false; }
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; }
@@ -178,7 +181,7 @@ namespace Barotrauma
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
{
TalentOptionStages.Add(new TalentOption(talentOptionsElement));
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
}
}
@@ -186,32 +189,19 @@ namespace Barotrauma
class TalentOption
{
public readonly List<Talent> Talents = new List<Talent>();
public readonly List<TalentPrefab> Talents = new List<TalentPrefab>();
public TalentOption(XElement talentOptionsElement)
public TalentOption(XElement talentOptionsElement, string debugIdentifier)
{
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
{
Talents.Add(new Talent(talentOptionElement));
}
}
}
class Talent
{
public readonly string Identifier;
public readonly Sprite Icon;
public Talent(XElement talentOptionElement)
{
Identifier = talentOptionElement.GetAttributeString("identifier", "");
foreach (XElement subElement in talentOptionElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty);
if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier))
{
case "icon":
Icon = new Sprite(subElement);
break;
DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\".");
return;
}
Talents.Add(TalentPrefab.TalentPrefabs[identifier]);
}
}
}