Unstable 0.1500.0.0

This commit is contained in:
Markus Isberg
2021-08-26 21:08:21 +09:00
parent 265a2e7ab3
commit 501e02c026
245 changed files with 9775 additions and 2034 deletions
@@ -0,0 +1,89 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
abstract class AbilityCondition
{
protected CharacterTalent characterTalent;
protected Character character;
protected bool invert;
public virtual bool AllowClientSimulation => true;
public AbilityCondition(CharacterTalent characterTalent, XElement conditionElement)
{
this.characterTalent = characterTalent;
character = characterTalent.Character;
invert = conditionElement.GetAttributeBool("invert", false);
}
public abstract bool MatchesCondition(object abilityData);
public abstract bool MatchesCondition();
// tools
protected enum TargetType
{
Any = 0,
Enemy = 1,
Ally = 2,
NotSelf = 3,
Alive = 4,
Monster = 5,
};
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
{
List<TargetType> targetTypes = new List<TargetType>();
foreach (string targetTypeString in targetTypeStrings)
{
TargetType targetType = TargetType.Any;
if (!Enum.TryParse(targetTypeString, true, out targetType))
{
DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
}
targetTypes.Add(targetType);
}
return targetTypes;
}
protected bool IsViableTarget(IEnumerable<TargetType> targetTypes, Character targetCharacter)
{
if (targetCharacter == null) { return false; }
bool isViable = true;
foreach (TargetType targetType in targetTypes)
{
if (!IsViableTarget(targetType, targetCharacter))
{
isViable = false;
break;
}
}
return isViable;
}
private bool IsViableTarget(TargetType targetType, Character targetCharacter)
{
switch (targetType)
{
case TargetType.Enemy:
return !HumanAIController.IsFriendly(character, targetCharacter);
case TargetType.Ally:
return HumanAIController.IsFriendly(character, targetCharacter);
case TargetType.NotSelf:
return targetCharacter != character;
case TargetType.Alive:
return !targetCharacter.IsDead;
case TargetType.Monster:
return !targetCharacter.IsHuman;
default:
return true;
}
}
}
}
@@ -0,0 +1,79 @@
using Barotrauma.Items.Components;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAttackData : AbilityConditionData
{
private enum WeaponType
{
Any = 0,
Melee = 1,
Ranged = 2
};
private readonly string itemIdentifier;
private readonly string[] tags;
private WeaponType weapontype;
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
switch (conditionElement.GetAttributeString("weapontype", ""))
{
case "melee":
weapontype = WeaponType.Melee;
break;
case "ranged":
weapontype = WeaponType.Ranged;
break;
}
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is AttackData attackData)
{
Item item = attackData?.SourceAttack?.SourceItem;
if (item == null)
{
DebugConsole.AddWarning($"Source Item was not found in {this} for talent {characterTalent.DebugIdentifier}!");
return false;
}
if (!string.IsNullOrEmpty(itemIdentifier))
{
if (item.prefab.Identifier != itemIdentifier)
{
return false;
}
}
if (tags.Any())
{
if (!tags.All(t => item.HasTag(t)))
{
return false;
}
}
switch (weapontype)
{
case WeaponType.Melee:
return item.GetComponent<MeleeWeapon>() != null;
case WeaponType.Ranged:
return item.GetComponent<RangedWeapon>() != null;
}
return true;
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
return false;
}
}
}
}
@@ -0,0 +1,38 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAttackResult : AbilityConditionData
{
private readonly List<TargetType> targetTypes;
private readonly string[] afflictions;
public AbilityConditionAttackResult(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true);
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is AttackResult attackResult)
{
if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; }
if (afflictions.Any())
{
if (!afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; }
}
return true;
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
return false;
}
}
}
}
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionCharacter : AbilityConditionData
{
private readonly List<TargetType> targetTypes;
public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is Character character)
{
if (!IsViableTarget(targetTypes, character)) { return false; }
return true;
}
else
{
LogAbilityConditionError(abilityData, typeof(Character));
return false;
}
}
}
}
@@ -0,0 +1,39 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
abstract class AbilityConditionData : AbilityCondition
{
/// <summary>
/// Some conditions rely on specific ability data that is integrally connected to the AbilityEffectType.
/// This is done in order to avoid having to create duplicate ability behavior, such as if an ability needs to trigger
/// a common ability effect but in specific circumstances. These conditions could also be partially replaced by
/// more explicit AbilityEffectType enums, but this would introduce bloat and overhead to integral game logic
/// when instead said logic can be made to only run when required using these conditions.
///
/// These conditions will return an error if used outside their limited intended use.
/// </summary>
public AbilityConditionData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected void LogAbilityConditionError<T>(T abilityData, Type expectedData)
{
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityData}");
}
protected abstract bool MatchesConditionSpecific(object abilityData);
public override bool MatchesCondition()
{
DebugConsole.ThrowError("Used data-reliant ability condition in a state-based ability! This is not allowed.");
return false;
}
public override bool MatchesCondition(object abilityData)
{
if (abilityData is null) { return invert; }
return invert ? !MatchesConditionSpecific(abilityData) : MatchesConditionSpecific(abilityData);
}
}
}
@@ -0,0 +1,22 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionEvasiveManeuvers : AbilityConditionData
{
public AbilityConditionEvasiveManeuvers(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is Submarine submarine)
{
return submarine.TeamID == character.TeamID && character.Submarine == submarine;
}
else
{
LogAbilityConditionError(abilityData, typeof(Submarine));
return false;
}
}
}
}
@@ -0,0 +1,27 @@
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,50 @@
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionItem : AbilityConditionData
{
private readonly string identifier;
private readonly string[] tags;
public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant();
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
}
protected override bool MatchesConditionSpecific(object abilityData)
{
ItemPrefab item = null;
if (abilityData is Item tempItem)
{
item = tempItem.Prefab;
}
// this and other instances of this type of casting will be refactored
else if (abilityData is (ItemPrefab itemPrefab, object _))
{
item = itemPrefab;
}
if (item != null)
{
if (!string.IsNullOrEmpty(identifier))
{
if (item.Identifier != identifier)
{
return false;
}
}
return tags.Any(t => item.Tags.Any(p => t == p));
}
else
{
LogAbilityConditionError(abilityData, typeof(Item));
return false;
}
}
}
}
@@ -0,0 +1,33 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionReduceAffliction : AbilityConditionData
{
private readonly string[] allowedTypes;
private readonly string identifier;
public AbilityConditionReduceAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
allowedTypes = conditionElement.GetAttributeStringArray("allowedtypes", new string[0], convertToLowerInvariant: true);
identifier = conditionElement.GetAttributeString("identifier", "");
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is (Affliction affliction, float reduceAmount))
{
if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; }
if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; }
return true;
}
else
{
LogAbilityConditionError(abilityData, typeof((Affliction, float)));
return false;
}
}
}
}
@@ -0,0 +1,22 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionScavenger : AbilityConditionData
{
public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is Item item)
{
return item.Submarine != character.Submarine;
}
else
{
LogAbilityConditionError(abilityData, typeof(Item));
return false;
}
}
}
}
@@ -0,0 +1,20 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAboveVitality : AbilityConditionDataless
{
float vitalityPercentage;
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f);
}
protected override bool MatchesConditionSpecific()
{
return character.HealthPercentage / 100f > vitalityPercentage;
}
}
}
@@ -0,0 +1,19 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAlliesAboveVitality : AbilityConditionDataless
{
float vitalityPercentage;
public AbilityConditionAlliesAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f);
}
protected override bool MatchesConditionSpecific()
{
return Character.GetFriendlyCrew(character).All(c => c.HealthPercentage / 100f >= vitalityPercentage);
}
}
}
@@ -0,0 +1,18 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionCrouched : AbilityConditionDataless
{
public AbilityConditionCrouched(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
}
protected override bool MatchesConditionSpecific()
{
return character.AnimController is HumanoidAnimController humanoidAnimController && humanoidAnimController.Crouching;
}
}
}
@@ -0,0 +1,24 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
abstract class AbilityConditionDataless : AbilityCondition
{
public AbilityConditionDataless(CharacterTalent characterTalent, XElement conditionElement) : base (characterTalent, conditionElement) { }
protected abstract bool MatchesConditionSpecific();
public override bool MatchesCondition()
{
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
}
public override bool MatchesCondition(object abilityData)
{
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
}
}
}
@@ -0,0 +1,31 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasAffliction : AbilityConditionDataless
{
private string afflictionIdentifier;
private float minimumPercentage;
public AbilityConditionHasAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
afflictionIdentifier = conditionElement.GetAttributeString("afflictionidentifier", "");
minimumPercentage = conditionElement.GetAttributeFloat("minimumpercentage", 0f);
}
protected override bool MatchesConditionSpecific()
{
if (!string.IsNullOrEmpty(afflictionIdentifier))
{
var affliction = character.CharacterHealth.GetAffliction(afflictionIdentifier);
if (affliction == null) { return false; }
return minimumPercentage <= affliction.Strength / affliction.Prefab.MaxStrength;
}
return false;
}
}
}
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasDifferentJobs : AbilityConditionDataless
{
private readonly int amount;
public AbilityConditionHasDifferentJobs(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
amount = conditionElement.GetAttributeInt("amount", 0);
}
protected override bool MatchesConditionSpecific()
{
IEnumerable<Character> crewmembers = Character.GetFriendlyCrew(character);
int differentCrewAmount = crewmembers.Select(c => c.Info?.Job?.Prefab.Identifier).Distinct().Count();
return differentCrewAmount >= amount;
}
}
}
@@ -0,0 +1,57 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasItem : AbilityConditionDataless
{
// not used for anything atm, will be used for clown subclass
private readonly string[] tags;
private InvSlotType? invSlotType;
bool requireAll;
private List<Item> items = new List<Item>();
public AbilityConditionHasItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
requireAll = conditionElement.GetAttributeBool("requireall", false);
//this.invSlotType = invSlotType;
}
protected override bool MatchesConditionSpecific()
{
items.Clear();
if (tags.Any())
{
foreach (string tag in tags)
{
// there is a better method, should use that instead
if (character.GetEquippedItem(tag, invSlotType) is Item foundItem)
{
items.Add(foundItem);
}
}
}
else
{
if (character.GetEquippedItem(null, invSlotType) is Item foundItem)
{
items.Add(foundItem);
}
}
if (requireAll)
{
return (items.Count >= tags.Count());
}
else
{
return items.Any();
}
}
}
}
@@ -0,0 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInWater : AbilityConditionDataless
{
public AbilityConditionInWater(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.InWater;
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionMission : AbilityConditionData
{
private readonly MissionType missionType;
public AbilityConditionMission(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
string missionTypeString = conditionElement.GetAttributeString("missiontype", "None");
if (!Enum.TryParse(missionTypeString, out missionType))
{
DebugConsole.ThrowError("Error in AbilityConditionMission \"" + characterTalent.DebugIdentifier + "\" - \"" + missionTypeString + "\" is not a valid mission type.");
return;
}
if (missionType == MissionType.None)
{
DebugConsole.ThrowError("Error in AbilityConditionMission \"" + characterTalent.DebugIdentifier + "\" - mission type cannot be none.");
return;
}
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is (Mission mission, AbilityValue missionAbilityValue))
{
return mission.Prefab.Type == missionType;
}
else
{
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue)));
return false;
}
}
}
}
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionNoCrewDied : AbilityConditionDataless
{
public AbilityConditionNoCrewDied(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
}
protected override bool MatchesConditionSpecific()
{
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
return !campaign.CrewHasDied;
}
return true;
}
}
}
@@ -0,0 +1,17 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionOnMission : AbilityConditionDataless
{
public AbilityConditionOnMission(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
}
protected override bool MatchesConditionSpecific()
{
return Level.Loaded?.Type != LevelData.LevelType.Outpost;
}
}
}
@@ -0,0 +1,18 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionRagdolled : AbilityConditionDataless
{
public AbilityConditionRagdolled(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
}
protected override bool MatchesConditionSpecific()
{
return character.IsRagdolled || character.Stun > 0f || character.IsIncapacitated;
}
}
}
@@ -0,0 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionRunning : AbilityConditionDataless
{
public AbilityConditionRunning(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.AnimController is HumanoidAnimController animController && animController.IsMovingFast;
}
}
}
@@ -0,0 +1,24 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionServerRandom : AbilityConditionDataless
{
private float randomChance = 0f;
public override bool AllowClientSimulation => false;
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
randomChance = conditionElement.GetAttributeFloat("randomchance", 1f);
}
protected override bool MatchesConditionSpecific()
{
return randomChance >= Rand.Range(0f, 1f, Rand.RandSync.Unsynced);
}
}
}
@@ -0,0 +1,21 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionShipFlooded : AbilityConditionDataless
{
private readonly float floodPercentage;
public AbilityConditionShipFlooded(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
floodPercentage = conditionElement.GetAttributeFloat("floodpercentage", 0f);
}
protected override bool MatchesConditionSpecific()
{
if (character.Submarine == null || character.Submarine.TeamID != character.TeamID) { return false; }
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
return currentFloodPercentage / 100 > floodPercentage;
}
}
}
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
abstract class CharacterAbility
{
public CharacterAbilityGroup CharacterAbilityGroup { get; }
public CharacterTalent CharacterTalent { get; }
public Character Character { get; }
public virtual bool RequiresAlive => true;
public virtual bool AllowClientSimulation => false;
public virtual bool AppliesEffectOnIntervalUpdate => false;
private const float DefaultEffectTime = 1.0f;
/// <summary>
/// Used primarily for StatusEffects. Default to constant outside interval abilities.
/// </summary>
protected float EffectDeltaTime => CharacterAbilityGroup is CharacterAbilityGroupInterval abilityGroupInterval ? abilityGroupInterval.TimeSinceLastUpdate : DefaultEffectTime;
public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement)
{
CharacterAbilityGroup = characterAbilityGroup;
CharacterTalent = characterAbilityGroup.CharacterTalent;
Character = CharacterTalent.Character;
}
public bool IsViable()
{
if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
if (RequiresAlive && Character.IsDead) { return false; }
return true;
}
public virtual void InitializeAbility(bool addingFirstTime) { }
public virtual void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
{
// may need a separate Update for changing state on non-interval-based abilities
if (AppliesEffectOnIntervalUpdate)
{
if (conditionsMatched)
{
ApplyEffect();
}
}
else
{
VerifyState(conditionsMatched, timeSinceLastUpdate);
}
}
protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
}
public void ApplyAbilityEffect(object abilityData)
{
if (abilityData is null)
{
ApplyEffect();
}
else
{
ApplyEffect(abilityData);
}
}
protected virtual void ApplyEffect()
{
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect");
}
protected virtual void ApplyEffect(object abilityData)
{
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect");
}
protected void LogAbilityDataMismatch()
{
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type.");
}
// XML
public static CharacterAbility Load(XElement abilityElement, CharacterAbilityGroup characterAbilityGroup, bool errorMessages = true)
{
Type abilityType;
string type = abilityElement.Name.ToString().ToLowerInvariant();
try
{
abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
if (abilityType == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")");
return null;
}
}
catch (Exception e)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e);
return null;
}
object[] args = { characterAbilityGroup, abilityElement };
CharacterAbility characterAbility;
try
{
characterAbility = (CharacterAbility)Activator.CreateInstance(abilityType, args);
}
catch (TargetInvocationException e)
{
DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException);
return null;
}
DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier);
return characterAbility;
}
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
{
AbilityFlags flagType = AbilityFlags.None;
if (!Enum.TryParse(flagTypeString, true, out flagType))
{
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
}
return flagType;
}
public static float DistanceToSquaredDistance(float distance)
{
return distance * distance;
}
}
}
@@ -0,0 +1,34 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyForce : CharacterAbility
{
private readonly float impulseStrength;
private readonly float maxVelocity;
private readonly string afflictionIdentifier;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
impulseStrength = abilityElement.GetAttributeFloat("impulsestrength", 0f);
maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
}
protected override void ApplyEffect()
{
Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
if (affliction == null) { return; }
foreach (Limb limb in Character.AnimController.Limbs)
{
limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * impulseStrength * (affliction.Strength / affliction.Prefab.MaxStrength), maxVelocity);
}
}
}
}
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffects : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public override bool AllowClientSimulation => true;
protected readonly List<StatusEffect> statusEffects;
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
}
protected void ApplyEffectSpecific(Character targetCharacter)
{
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
}
protected override void ApplyEffect()
{
ApplyEffectSpecific(Character);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
else
{
ApplyEffect();
}
}
}
}
@@ -0,0 +1,36 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToNearestAlly : CharacterAbilityApplyStatusEffects
{
protected float squaredMaxDistance;
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
}
protected override void ApplyEffect()
{
Character closestCharacter = null;
float closestDistance = float.MaxValue;
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
{
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
{
closestCharacter = crewCharacter;
closestDistance = tempDistance;
}
}
if (closestDistance < squaredMaxDistance)
{
ApplyEffectSpecific(closestCharacter);
}
}
}
}
@@ -0,0 +1,40 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToRandomAlly : CharacterAbilityApplyStatusEffects
{
private readonly float squaredMaxDistance;
private readonly bool allowDifferentSub;
private readonly bool allowSelf;
public override bool AllowClientSimulation => false;
public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true);
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
protected override void ApplyEffect()
{
Character chosenCharacter = null;
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
(allowSelf ||c != Character) &&
(allowDifferentSub || c.Submarine == Character.Submarine) &&
Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance &&
tempDistance < squaredMaxDistance).GetRandom();
if (chosenCharacter == null) { return; }
ApplyEffectSpecific(chosenCharacter);
}
}
}
@@ -0,0 +1,20 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveFlag : CharacterAbility
{
private AbilityFlags abilityFlag;
// 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 CharacterAbilityGiveFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
}
public override void InitializeAbility(bool addingFirstTime)
{
Character.AddAbilityFlag(abilityFlag);
}
}
}
@@ -0,0 +1,21 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveMissionCount : CharacterAbility
{
private readonly int amount;
public CharacterAbilityGiveMissionCount(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
}
public override void InitializeAbility(bool addingFirstTime)
{
if (!addingFirstTime) { return; }
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
campaign.Settings.AddedMissionCount += amount;
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveMoney : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
private int amount;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
}
protected override void ApplyEffect()
{
Character.GiveMoney(amount);
}
}
}
@@ -0,0 +1,49 @@
using Barotrauma.Extensions;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGivePermanentStat : CharacterAbility
{
private readonly string statIdentifier;
private readonly StatTypes statType;
private readonly float value;
private readonly bool targetAllies;
private readonly bool removeOnDeath;
//private readonly float maximumValue;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
}
protected override void ApplyEffect(object abilityData)
{
ApplyEffectSpecific();
}
protected override void ApplyEffect()
{
ApplyEffectSpecific();
}
private void ApplyEffectSpecific()
{
if (targetAllies)
{
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath));
}
else
{
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
}
}
}
}
@@ -0,0 +1,21 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
}
public override void InitializeAbility(bool addingFirstTime)
{
Character.ChangeAbilityResistance(resistanceId, resistance);
}
}
}
@@ -0,0 +1,22 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveStat : CharacterAbility
{
private StatTypes statType;
private 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);
value = abilityElement.GetAttributeFloat("value", 0f);
}
public override void InitializeAbility(bool addingFirstTime)
{
Character.ChangeStat(statType, value);
}
}
}
@@ -0,0 +1,41 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityIncreaseSkill : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
private string skillIdentifier;
private float skillIncrease;
public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f);
}
protected override void ApplyEffect()
{
ApplyEffectSpecific(Character);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Character character)
{
ApplyEffectSpecific(character);
}
else
{
ApplyEffectSpecific(Character);
}
}
private void ApplyEffectSpecific(Character character)
{
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f);
}
}
}
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyAffliction : CharacterAbility
{
private readonly string[] afflictionIdentifiers;
private readonly float addedMultiplier;
public CharacterAbilityModifyAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
afflictionIdentifiers = abilityElement.GetAttributeStringArray("afflictionidentifiers", new string[0], convertToLowerInvariant: true);
addedMultiplier = abilityElement.GetAttributeFloat("addedmultiplier", 0f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Affliction affliction)
{
foreach (string afflictionIdentifier in afflictionIdentifiers)
{
if (affliction.Identifier == afflictionIdentifier)
{
affliction.Strength *= 1 + addedMultiplier;
}
}
}
else
{
LogAbilityDataMismatch();
}
}
}
}
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyAttackData : CharacterAbility
{
private readonly List<Affliction> afflictions;
float addedDamageMultiplier;
float addedPenetration;
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
if (abilityElement.GetChildElement("afflictions") is XElement afflictionElements)
{
afflictions = CharacterAbilityGroup.ParseAfflictions(CharacterTalent, afflictionElements);
}
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AttackData attackData)
{
if (attackData.Afflictions == null)
{
attackData.Afflictions = afflictions;
}
else
{
attackData.Afflictions.AddRange(afflictions);
}
attackData.DamageMultiplier += addedDamageMultiplier;
attackData.AddedPenetration += addedPenetration;
}
else
{
LogAbilityDataMismatch();
}
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyFlag : CharacterAbility
{
private AbilityFlags abilityFlag;
private bool lastState;
public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched != lastState)
{
if (conditionsMatched)
{
Character.AddAbilityFlag(abilityFlag);
}
else
{
Character.RemoveAbilityFlag(abilityFlag);
}
lastState = conditionsMatched;
}
}
}
}
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyReduceAffliction : CharacterAbility
{
float addedAmountMultiplier;
public CharacterAbilityModifyReduceAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedAmountMultiplier = abilityElement.GetAttributeFloat("addedamountmultiplier", 0f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is (Affliction affliction, float reduceAmount))
{
affliction.Strength -= addedAmountMultiplier * reduceAmount;
}
else
{
LogAbilityDataMismatch();
}
}
}
}
@@ -0,0 +1,27 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
bool lastState;
// should probably be split to different classes
public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
}
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched != lastState)
{
Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance);
lastState = conditionsMatched;
}
}
}
}
@@ -0,0 +1,26 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStat : CharacterAbility
{
private readonly StatTypes statType;
private readonly float value;
bool lastState;
public CharacterAbilityModifyStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched != lastState)
{
Character.ChangeStat(statType, conditionsMatched ? value : -value);
lastState = conditionsMatched;
}
}
}
}
@@ -0,0 +1,46 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyValue : CharacterAbility
{
private float addedValue;
private float multiplierValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AbilityValue abilityValue)
{
ApplyEffectSpecific(abilityValue);
}
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;
}
}
}
@@ -0,0 +1,46 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityPutItem : CharacterAbility
{
private readonly string itemIdentifier;
private readonly int amount;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityPutItem(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
itemIdentifier = abilityElement.GetAttributeString("itemidentifier", "");
amount = abilityElement.GetAttributeInt("amount", 1);
}
protected override void ApplyEffect()
{
if (string.IsNullOrEmpty(itemIdentifier))
{
DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined.");
return;
}
ItemPrefab itemPrefab = ItemPrefab.Find(null, itemIdentifier);
if (itemPrefab == null)
{
DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found.");
return;
}
for (int i = 0; i < amount; i++)
{
if (GameMain.GameSession?.RoundEnding ?? true)
{
Item item = new Item(itemPrefab, Character.WorldPosition, Character.Submarine);
Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any });
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, Character.Inventory);
}
}
}
}
}
@@ -0,0 +1,29 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityResetPermanentStat : CharacterAbility
{
private readonly string statIdentifier;
public override bool RequiresAlive => false;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
}
protected override void ApplyEffect(object abilityData)
{
ApplyEffectSpecific();
}
protected override void ApplyEffect()
{
ApplyEffectSpecific();
}
private void ApplyEffectSpecific()
{
Character?.Info.ResetSavedStatValue(statIdentifier);
}
}
}
@@ -0,0 +1,21 @@
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApprenticeship : CharacterAbility
{
public CharacterAbilityApprenticeship(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is (string skillIdentifier, Character character) && character != Character)
{
character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f);
}
}
}
}
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityBountyHunter : CharacterAbility
{
private float vitalityPercentage;
public CharacterAbilityBountyHunter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
vitalityPercentage = abilityElement.GetAttributeFloat("vitalitypercentage", 0f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Character character)
{
Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality));
}
}
}
}
@@ -0,0 +1,30 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityIndustrialRevolution : CharacterAbility
{
float addedFabricationSpeed;
public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f);
}
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched)
{
// not necessarily the cleanest or performant way, but at least this shouldn't break anything.
// must be done every frame in order to work.
if (Character.SelectedConstruction?.GetComponent<Fabricator>() is Fabricator fabricator && fabricator.IsActive)
{
fabricator.FabricationSpeedMultiplier += addedFabricationSpeed;
}
}
}
}
}
@@ -0,0 +1,54 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
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 static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0);
}
protected override void ApplyEffect()
{
if (Character?.Info is CharacterInfo info && !hasOccurred)
{
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);
}
}
}
}
}
}
}
@@ -0,0 +1,26 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityMultitasker : CharacterAbility
{
private string lastSkillIdentifier;
public CharacterAbilityMultitasker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is string skillIdentifier)
{
if (skillIdentifier != lastSkillIdentifier)
{
lastSkillIdentifier = skillIdentifier;
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, Character.Position + Vector2.UnitY * 175.0f);
}
}
}
}
}
@@ -0,0 +1,44 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityPsychoClown : CharacterAbility
{
private StatTypes statType;
private float value;
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);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
// managing state this way seems liable to cause bugs, maybe instead create abstraction to reset these values more safely
// talents cannot be removed while in active play because of the lack of this, for example
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched)
{
var affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
float afflictionStrength = 0f;
if (affliction != null)
{
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
}
lastValue = afflictionStrength * value;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -0,0 +1,30 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityRegenerateLoot : CharacterAbility
{
List<Item> openedContainers = new List<Item>();
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Item item && !openedContainers.Contains(item))
{
openedContainers.Add(item);
if (item.GetComponent<ItemContainer>() is ItemContainer itemContainer)
{
AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer);
}
}
}
}
}
@@ -0,0 +1,42 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityStonewall : CharacterAbility
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsReset;
private int maxEnemyCount;
private 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));
}
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();
foreach (var statusEffect in statusEffectsReset)
{
statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character);
}
if (conditionsMatched && numberOfEnemiesInRange > 0)
{
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character);
}
}
}
}
}
@@ -0,0 +1,42 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
{
private string tag;
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
tag = abilityElement.GetAttributeString("tag", "");
}
protected override void ApplyEffect()
{
if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
Character closestCharacter = null;
float closestDistance = float.MaxValue;
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
{
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
{
closestCharacter = crewCharacter;
closestDistance = tempDistance;
}
}
if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
if (closestDistance < squaredMaxDistance)
{
ApplyEffectSpecific(Character);
ApplyEffectSpecific(closestCharacter);
}
}
}
}
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityTaskmaster : CharacterAbility
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsRemove;
private Character lastCharacter;
public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove"));
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Character targetCharacter)
{
if (targetCharacter == Character) { return; }
foreach (var statusEffect in statusEffectsRemove)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter);
}
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
lastCharacter = targetCharacter;
}
}
}
}