Release v0.15.12.0
This commit is contained in:
+92
@@ -0,0 +1,92 @@
|
||||
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(AbilityObject abilityObject);
|
||||
public abstract bool MatchesCondition();
|
||||
|
||||
|
||||
// tools
|
||||
protected enum TargetType
|
||||
{
|
||||
Any = 0,
|
||||
Enemy = 1,
|
||||
Ally = 2,
|
||||
NotSelf = 3,
|
||||
Alive = 4,
|
||||
Monster = 5,
|
||||
InFriendlySubmarine = 6,
|
||||
};
|
||||
|
||||
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;
|
||||
case TargetType.InFriendlySubmarine:
|
||||
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAffliction : AbilityConditionData
|
||||
{
|
||||
private readonly string[] afflictions;
|
||||
public AbilityConditionAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction)
|
||||
{
|
||||
return afflictions.Any(a => a == affliction.Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -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 readonly 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(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is AbilityAttackData 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(abilityObject, typeof(AbilityAttackData));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityAttackResult)?.AttackResult is AttackResult attackResult)
|
||||
{
|
||||
if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; }
|
||||
|
||||
if (afflictions.Any())
|
||||
{
|
||||
if (attackResult.Afflictions == null || !afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character character)
|
||||
{
|
||||
if (!IsViableTarget(targetTypes, character)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityCharacter));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
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(AbilityObject abilityObject, Type expectedData)
|
||||
{
|
||||
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject}");
|
||||
}
|
||||
|
||||
protected abstract bool MatchesConditionSpecific(AbilityObject abilityObject);
|
||||
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(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is null) { return invert; }
|
||||
return invert ? !MatchesConditionSpecific(abilityObject) : MatchesConditionSpecific(abilityObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilitySubmarine)?.Submarine is Submarine submarine && (abilityObject as IAbilityCharacter)?.Character is Character attackingCharacter)
|
||||
{
|
||||
return submarine.TeamID == character.TeamID && character.Submarine == submarine && attackingCharacter.TeamID != character.TeamID;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilitySubmarine));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionIsAiming : AbilityConditionDataless
|
||||
{
|
||||
private enum WeaponType
|
||||
{
|
||||
Any = 0,
|
||||
Melee = 1,
|
||||
Ranged = 2
|
||||
};
|
||||
|
||||
private readonly bool hittingCountsAsAiming;
|
||||
|
||||
private readonly WeaponType weapontype;
|
||||
public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
hittingCountsAsAiming = conditionElement.GetAttributeBool("hittingcountsasaiming", false);
|
||||
switch (conditionElement.GetAttributeString("weapontype", ""))
|
||||
{
|
||||
case "melee":
|
||||
weapontype = WeaponType.Melee;
|
||||
break;
|
||||
case "ranged":
|
||||
weapontype = WeaponType.Ranged;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (character.AnimController is HumanoidAnimController animController)
|
||||
{
|
||||
foreach (Item item in character.HeldItems)
|
||||
{
|
||||
switch (weapontype)
|
||||
{
|
||||
case WeaponType.Melee:
|
||||
var meleeWeapon = item.GetComponent<MeleeWeapon>();
|
||||
if (meleeWeapon != null)
|
||||
{
|
||||
if (animController.IsAimingMelee || (meleeWeapon.Hitting && hittingCountsAsAiming)) { return true; }
|
||||
}
|
||||
break;
|
||||
case WeaponType.Ranged:
|
||||
if (animController.IsAiming && item.GetComponent<RangedWeapon>() != null) { return true; }
|
||||
break;
|
||||
default:
|
||||
if (animController.IsAiming || animController.IsAimingMelee) { return true; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItem : AbilityConditionData
|
||||
{
|
||||
private readonly string[] identifiers;
|
||||
private readonly string[] tags;
|
||||
|
||||
public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
ItemPrefab itemPrefab = null;
|
||||
if ((abilityObject as IAbilityItemPrefab)?.ItemPrefab is ItemPrefab abilityItemPrefab)
|
||||
{
|
||||
itemPrefab = abilityItemPrefab;
|
||||
}
|
||||
else if ((abilityObject as IAbilityItem)?.Item is Item abilityItem)
|
||||
{
|
||||
itemPrefab = abilityItem.Prefab;
|
||||
}
|
||||
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
if (identifiers.Any())
|
||||
{
|
||||
if (!identifiers.Any(t => itemPrefab.Identifier == t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItemOutsideSubmarine : AbilityConditionData
|
||||
{
|
||||
|
||||
public AbilityConditionItemOutsideSubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
return item.Submarine == null || item.Submarine.TeamID != character.Info.TeamID;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItem));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItemWreck : AbilityConditionData
|
||||
{
|
||||
|
||||
public AbilityConditionItemWreck(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
return item.Submarine?.Info?.IsWreck ?? false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItem));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionLocation : AbilityConditionData
|
||||
{
|
||||
private readonly bool? hasOutpost;
|
||||
private readonly string[] locationIdentifiers;
|
||||
|
||||
public AbilityConditionLocation(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
if (conditionElement.Attribute("hasoutpost") != null)
|
||||
{
|
||||
hasOutpost = conditionElement.GetAttributeBool("hasoutpost", false);
|
||||
}
|
||||
locationIdentifiers = conditionElement.GetAttributeStringArray("locationtype", new string[0]);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is IAbilityLocation abilityLocation)
|
||||
{
|
||||
if (locationIdentifiers.Any())
|
||||
{
|
||||
if (!locationIdentifiers.Contains(abilityLocation.Location.Type.Identifier)) { return false; }
|
||||
}
|
||||
if (hasOutpost.HasValue)
|
||||
{
|
||||
if (hasOutpost.Value != abilityLocation.Location.HasOutpost()) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityMission)?.Mission is Mission mission)
|
||||
{
|
||||
return mission.Prefab.Type == missionType;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityMission));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction)
|
||||
{
|
||||
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(abilityObject, typeof(IAbilityAffliction));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityString)?.String is string skillIdentifier)
|
||||
{
|
||||
return MatchesConditionSpecific(skillIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityString));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAboveVitality : AbilityConditionDataless
|
||||
{
|
||||
private readonly 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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(AbilityObject abilityObject)
|
||||
{
|
||||
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasPermanentStat : AbilityConditionDataless
|
||||
{
|
||||
private readonly string statIdentifier;
|
||||
private readonly StatTypes statType;
|
||||
private readonly float min;
|
||||
|
||||
public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
statIdentifier = conditionElement.GetAttributeString("statidentifier", string.Empty);
|
||||
if (string.IsNullOrEmpty(statIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!");
|
||||
}
|
||||
string statTypeName = conditionElement.GetAttributeString("stattype", string.Empty);
|
||||
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, characterTalent.DebugIdentifier);
|
||||
min = conditionElement.GetAttributeFloat("min", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.Info.GetSavedStatValue(statType, statIdentifier) >= min;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasSkill : AbilityConditionDataless
|
||||
{
|
||||
private readonly string skillIdentifier;
|
||||
private readonly float minValue;
|
||||
|
||||
public AbilityConditionHasSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", string.Empty);
|
||||
minValue = conditionElement.GetAttributeFloat("minvalue", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.GetSkillLevel(skillIdentifier) >= minValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasVelocity : AbilityConditionDataless
|
||||
{
|
||||
private readonly float velocity;
|
||||
|
||||
public AbilityConditionHasVelocity(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
velocity = conditionElement.GetAttributeFloat("velocity", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.AnimController.Collider.LinearVelocity.LengthSquared() > velocity * velocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionServerRandom : AbilityConditionDataless
|
||||
{
|
||||
private readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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.IsInFriendlySub) { return false; }
|
||||
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
|
||||
return currentFloodPercentage / 100 > floodPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
interface IAbilityItemPrefab
|
||||
{
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityItem
|
||||
{
|
||||
public Item Item { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityValue
|
||||
{
|
||||
public float Value { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityMission
|
||||
{
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityLocation
|
||||
{
|
||||
public Location Location { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityCharacter
|
||||
{
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityString
|
||||
{
|
||||
public string String { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityAffliction
|
||||
{
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityAttackResult
|
||||
{
|
||||
public AttackResult AttackResult { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilitySubmarine
|
||||
{
|
||||
public Submarine Submarine { get; set; }
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityObject
|
||||
{
|
||||
// kept as blank for now, as we are using a composition and only using this object to enforce parameter types
|
||||
}
|
||||
|
||||
class AbilityCharacter : AbilityObject, IAbilityCharacter
|
||||
{
|
||||
public AbilityCharacter(Character character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
class AbilityItem : AbilityObject, IAbilityItem
|
||||
{
|
||||
public AbilityItem(Item item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
public Item Item { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValue : AbilityObject, IAbilityValue
|
||||
{
|
||||
public AbilityValue(float value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
}
|
||||
|
||||
class AbilityAffliction : AbilityObject, IAbilityAffliction
|
||||
{
|
||||
public AbilityAffliction(Affliction affliction)
|
||||
{
|
||||
Affliction = affliction;
|
||||
}
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
class AbilityAfflictionCharacter : AbilityObject, IAbilityAffliction, IAbilityCharacter
|
||||
{
|
||||
public AbilityAfflictionCharacter(Affliction affliction, Character character)
|
||||
{
|
||||
Affliction = affliction;
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityValueItem(float value, ItemPrefab itemPrefab)
|
||||
{
|
||||
Value = value;
|
||||
ItemPrefab = itemPrefab;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
|
||||
class AbilityItemPrefabItem : AbilityObject, IAbilityItem, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityItemPrefabItem(Item item, ItemPrefab itemPrefab)
|
||||
{
|
||||
Item = item;
|
||||
ItemPrefab = itemPrefab;
|
||||
}
|
||||
public Item Item { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString
|
||||
{
|
||||
public AbilityValueString(float value, string abilityString)
|
||||
{
|
||||
Value = value;
|
||||
String = abilityString;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public string String { get; set; }
|
||||
}
|
||||
|
||||
class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString
|
||||
{
|
||||
public AbilityStringCharacter(string abilityString, Character character)
|
||||
{
|
||||
String = abilityString;
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public string String { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueAffliction : AbilityObject, IAbilityValue, IAbilityAffliction
|
||||
{
|
||||
public AbilityValueAffliction(float value, Affliction affliction)
|
||||
{
|
||||
Value = value;
|
||||
Affliction = affliction;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueMission : AbilityObject, IAbilityValue, IAbilityMission
|
||||
{
|
||||
public AbilityValueMission(float value, Mission mission)
|
||||
{
|
||||
Value = value;
|
||||
Mission = mission;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
{
|
||||
public AbilityLocation(Location location)
|
||||
{
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public Location Location { get; set; }
|
||||
}
|
||||
|
||||
// this is an exception class that should only be passed in this form, so classes that use it should cast into it directly
|
||||
class AbilityAttackData : AbilityObject, 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;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityApplyTreatment : AbilityObject, IAbilityCharacter, IAbilityItem
|
||||
{
|
||||
public Character Character { get; set; }
|
||||
|
||||
public Character User { get; set; }
|
||||
|
||||
public Item Item { get; set; }
|
||||
|
||||
public AbilityApplyTreatment(Character user, Character target, Item item)
|
||||
{
|
||||
Character = target;
|
||||
User = user;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityAttackResult : AbilityObject, IAbilityAttackResult
|
||||
{
|
||||
public AttackResult AttackResult { get; set; }
|
||||
|
||||
public AbilityAttackResult(AttackResult attackResult)
|
||||
{
|
||||
AttackResult = attackResult;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityCharacterSubmarine : AbilityObject, IAbilityCharacter, IAbilitySubmarine
|
||||
{
|
||||
public AbilityCharacterSubmarine(Character character, Submarine submarine)
|
||||
{
|
||||
Character = character;
|
||||
Submarine = submarine;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public Submarine Submarine { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
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 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>
|
||||
protected float EffectDeltaTime => CharacterAbilityGroup is CharacterAbilityGroupInterval abilityGroupInterval ? abilityGroupInterval.TimeSinceLastUpdate : DefaultEffectTime;
|
||||
|
||||
public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement)
|
||||
{
|
||||
CharacterAbilityGroup = characterAbilityGroup;
|
||||
CharacterTalent = characterAbilityGroup.CharacterTalent;
|
||||
Character = CharacterTalent.Character;
|
||||
RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true);
|
||||
}
|
||||
|
||||
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($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
|
||||
}
|
||||
|
||||
public void ApplyAbilityEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is null)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffect(abilityObject);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect()
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect");
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect");
|
||||
}
|
||||
|
||||
protected void LogabilityObjectMismatch()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return characterAbility;
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyForce : CharacterAbility
|
||||
{
|
||||
private readonly float force;
|
||||
private readonly float maxVelocity;
|
||||
|
||||
private readonly string afflictionIdentifier;
|
||||
|
||||
private readonly HashSet<LimbType> limbTypes = new HashSet<LimbType>();
|
||||
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
force = abilityElement.GetAttributeFloat("force", 0f);
|
||||
maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f);
|
||||
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
|
||||
|
||||
string[] limbTypesStr = abilityElement.GetAttributeStringArray("limbtypes", new string[0]);
|
||||
foreach (string limbTypeStr in limbTypesStr)
|
||||
{
|
||||
if (Enum.TryParse(limbTypeStr, out LimbType limbType))
|
||||
{
|
||||
limbTypes.Add(limbType);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
float strength = 1.0f;
|
||||
if (!string.IsNullOrEmpty(afflictionIdentifier))
|
||||
{
|
||||
Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
if (affliction == null) { return; }
|
||||
strength = affliction.Strength / affliction.Prefab.MaxStrength;
|
||||
}
|
||||
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || limb.Removed) { continue; }
|
||||
if (limbTypes.Any())
|
||||
{
|
||||
if (!limbTypes.Contains(limb.type)) { continue; }
|
||||
}
|
||||
if (Character.AnimController.TargetMovement.LengthSquared() < 0.001f) { continue; }
|
||||
limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * force * strength, maxVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
|
||||
private readonly bool nearbyCharactersAppliesToSelf;
|
||||
private readonly bool nearbyCharactersAppliesToAllies;
|
||||
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);
|
||||
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
|
||||
nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true);
|
||||
}
|
||||
|
||||
protected void ApplyEffectSpecific(Character targetCharacter)
|
||||
{
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
// currently used 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));
|
||||
if (!nearbyCharactersAppliesToSelf)
|
||||
{
|
||||
targets.RemoveAll(c => c == Character);
|
||||
}
|
||||
if (!nearbyCharactersAppliesToAllies)
|
||||
{
|
||||
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character));
|
||||
}
|
||||
statusEffect.SetUser(Character);
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.SetUser(Character);
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
statusEffect.SetUser(Character);
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter)
|
||||
{
|
||||
ApplyEffectSpecific(selectedCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
|
||||
{
|
||||
ApplyEffectSpecific(targetCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
private readonly float maxDistance = float.MaxValue;
|
||||
|
||||
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
allowSelf = abilityElement.GetAttributeBool("allowself", true);
|
||||
maxDistance = abilityElement.GetAttributeFloat("maxdistance", float.MaxValue);
|
||||
}
|
||||
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
IEnumerable<Character> chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character);
|
||||
|
||||
foreach (Character character in chosenCharacters)
|
||||
{
|
||||
if (maxDistance < float.MaxValue)
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; }
|
||||
}
|
||||
ApplyEffectSpecific(character);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+20
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as AbilityAttackData)?.Attacker is Character attacker)
|
||||
{
|
||||
ApplyEffectSpecific(attacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToLastOrderedCharacter : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
public CharacterAbilityApplyStatusEffectsToLastOrderedCharacter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (IsViableTarget(Character.LastOrderedCharacter))
|
||||
{
|
||||
ApplyEffectSpecific(Character.LastOrderedCharacter);
|
||||
}
|
||||
if (Character.HasAbilityFlag(AbilityFlags.AllowSecondOrderedTarget) && IsViableTarget(Character.SecondLastOrderedCharacter))
|
||||
{
|
||||
ApplyEffectSpecific(Character.SecondLastOrderedCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsViableTarget(Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter == null || targetCharacter.Removed) { return false; }
|
||||
if (targetCharacter == Character) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToNearestAlly : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
protected float squaredMaxDistance;
|
||||
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (crewCharacter != Character && Vector2.DistanceSquared(Character.WorldPosition, crewCharacter.WorldPosition) is float tempDistance && tempDistance < closestDistance)
|
||||
{
|
||||
closestCharacter = crewCharacter;
|
||||
closestDistance = tempDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
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 = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
|
||||
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.WorldPosition, c.WorldPosition) is float tempDistance &&
|
||||
tempDistance < squaredMaxDistance).GetRandom();
|
||||
|
||||
if (chosenCharacter == null) { return; }
|
||||
|
||||
ApplyEffectSpecific(chosenCharacter);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGainSimultaneousSkill : CharacterAbility
|
||||
{
|
||||
private string skillIdentifier;
|
||||
|
||||
public CharacterAbilityGainSimultaneousSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityValue)?.Value is float skillIncrease)
|
||||
{
|
||||
Character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveAffliction : CharacterAbility
|
||||
{
|
||||
private readonly string afflictionId;
|
||||
private readonly float strength;
|
||||
private readonly string multiplyStrengthBySkill;
|
||||
private readonly bool setValue;
|
||||
|
||||
public CharacterAbilityGiveAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
afflictionId = abilityElement.GetAttributeString("afflictionid", abilityElement.GetAttributeString("affliction", string.Empty));
|
||||
strength = abilityElement.GetAttributeFloat("strength", 0f);
|
||||
multiplyStrengthBySkill = abilityElement.GetAttributeString("multiplystrengthbyskill", string.Empty);
|
||||
setValue = abilityElement.GetAttributeBool("setvalue", false);
|
||||
|
||||
if (string.IsNullOrEmpty(afflictionId))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterAbilityGiveAffliction - affliction identifier not set.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is IAbilityCharacter character)
|
||||
{
|
||||
var afflictionPrefab = AfflictionPrefab.Prefabs.Find(a => a.Identifier.Equals(afflictionId, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\".");
|
||||
return;
|
||||
}
|
||||
float strength = this.strength;
|
||||
if (!string.IsNullOrEmpty(multiplyStrengthBySkill))
|
||||
{
|
||||
strength *= Character.GetSkillLevel(multiplyStrengthBySkill);
|
||||
}
|
||||
character.Character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(strength), allowStacking: !setValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveFlag : CharacterAbility
|
||||
{
|
||||
private readonly 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 = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.AddAbilityFlag(abilityFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveMoney : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
private readonly int amount;
|
||||
private readonly string scalingStatIdentifier;
|
||||
|
||||
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
scalingStatIdentifier = abilityElement.GetAttributeString("scalingstatidentifier", string.Empty);
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(Character targetCharacter)
|
||||
{
|
||||
float multiplier = 1f;
|
||||
if (!string.IsNullOrEmpty(scalingStatIdentifier))
|
||||
{
|
||||
multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier);
|
||||
}
|
||||
|
||||
targetCharacter.GiveMoney((int)(multiplier * amount));
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
|
||||
{
|
||||
ApplyEffectSpecific(targetCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
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 float maxValue;
|
||||
private readonly bool targetAllies;
|
||||
private readonly bool removeOnDeath;
|
||||
private readonly bool giveOnAddingFirstTime;
|
||||
private readonly bool setValue;
|
||||
|
||||
//private readonly float maximumValue;
|
||||
public override bool AllowClientSimulation => true;
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty);
|
||||
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue);
|
||||
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
|
||||
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
|
||||
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
|
||||
setValue = abilityElement.GetAttributeBool("setvalue", false);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (giveOnAddingFirstTime && addingFirstTime)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific()
|
||||
{
|
||||
if (targetAllies)
|
||||
{
|
||||
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveResistance : CharacterAbility
|
||||
{
|
||||
private readonly string resistanceId;
|
||||
private readonly float multiplier;
|
||||
|
||||
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty));
|
||||
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); // rename this to resistance for consistency
|
||||
|
||||
if (string.IsNullOrEmpty(resistanceId))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveStat : CharacterAbility
|
||||
{
|
||||
private readonly StatTypes statType;
|
||||
private readonly float value;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveTalentPoints : CharacterAbility
|
||||
{
|
||||
private readonly int amount;
|
||||
|
||||
public CharacterAbilityGiveTalentPoints(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (addingFirstTime && Character.Info != null)
|
||||
{
|
||||
Character.Info.AdditionalTalentPoints += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityIncreaseSkill : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
private readonly string skillIdentifier;
|
||||
private readonly float skillIncrease;
|
||||
|
||||
public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
|
||||
skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f);
|
||||
|
||||
if (string.IsNullOrEmpty(skillIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill.");
|
||||
}
|
||||
if (MathUtils.NearlyEqual(skillIncrease, 0))
|
||||
{
|
||||
DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character character)
|
||||
{
|
||||
ApplyEffectSpecific(character);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(Character character)
|
||||
{
|
||||
if (skillIdentifier.Equals("random"))
|
||||
{
|
||||
var skill = character.Info?.Job?.Skills?.GetRandom();
|
||||
if (skill == null) { return; }
|
||||
character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction)
|
||||
{
|
||||
foreach (string afflictionIdentifier in afflictionIdentifiers)
|
||||
{
|
||||
if (affliction.Identifier == afflictionIdentifier)
|
||||
{
|
||||
affliction.Strength *= 1 + addedMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyAttackData : CharacterAbility
|
||||
{
|
||||
private readonly List<Affliction> afflictions;
|
||||
|
||||
private readonly float addedDamageMultiplier;
|
||||
private readonly float addedPenetration;
|
||||
private readonly bool implode;
|
||||
|
||||
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);
|
||||
implode = abilityElement.GetAttributeBool("implode", false);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is AbilityAttackData attackData)
|
||||
{
|
||||
if (attackData.Afflictions == null)
|
||||
{
|
||||
attackData.Afflictions = afflictions;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackData.Afflictions.AddRange(afflictions);
|
||||
}
|
||||
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
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyFlag : CharacterAbility
|
||||
{
|
||||
private readonly AbilityFlags abilityFlag;
|
||||
|
||||
private bool lastState;
|
||||
|
||||
public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
abilityFlag = CharacterAbilityGroup.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is AbilityValueAffliction afflictionReduceAmount)
|
||||
{
|
||||
afflictionReduceAmount.Affliction.Strength -= addedAmountMultiplier * afflictionReduceAmount.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyResistance : CharacterAbility
|
||||
{
|
||||
private readonly string resistanceId;
|
||||
private readonly 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);
|
||||
|
||||
if (string.IsNullOrEmpty(resistanceId))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance);
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyStatToFlooding : CharacterAbility
|
||||
{
|
||||
private readonly StatTypes statType;
|
||||
private readonly float maxValue;
|
||||
private float lastValue = 0f;
|
||||
|
||||
public CharacterAbilityModifyStatToFlooding(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
Character.ChangeStat(statType, -lastValue);
|
||||
|
||||
if (conditionsMatched && Character.IsInFriendlySub)
|
||||
{
|
||||
float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
|
||||
lastValue = currentFloodPercentage / 100f * maxValue;
|
||||
Character.ChangeStat(statType, lastValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValue = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyStatToLevel : CharacterAbility
|
||||
{
|
||||
private readonly StatTypes statType;
|
||||
private readonly float statPerLevel;
|
||||
private readonly int maxLevel;
|
||||
private float lastValue = 0f;
|
||||
|
||||
public CharacterAbilityModifyStatToLevel(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
statPerLevel = abilityElement.GetAttributeFloat("statperlevel", 0f);
|
||||
maxLevel = abilityElement.GetAttributeInt("maxlevel", int.MaxValue);
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
Character.ChangeStat(statType, -lastValue);
|
||||
if (conditionsMatched)
|
||||
{
|
||||
int level = MathHelper.Min(Character?.Info.GetCurrentLevel() ?? 0, maxLevel);
|
||||
lastValue = statPerLevel * level;
|
||||
Character.ChangeStat(statType, lastValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValue = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyValue : CharacterAbility
|
||||
{
|
||||
private readonly float addedValue;
|
||||
private readonly float multiplyValue;
|
||||
|
||||
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
|
||||
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is IAbilityValue abilityValue)
|
||||
{
|
||||
abilityValue.Value += addedValue;
|
||||
abilityValue.Value *= multiplyValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityResetPermanentStat : CharacterAbility
|
||||
{
|
||||
private readonly string statIdentifier;
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public override bool AllowClientSimulation => true;
|
||||
|
||||
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
}
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific()
|
||||
{
|
||||
Character?.Info.ResetSavedStatValue(statIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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(removeAllAfflictions: false);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilitySpawnItemsToContainer : CharacterAbility
|
||||
{
|
||||
// currently used only for spawning items to containers
|
||||
|
||||
private readonly List<StatusEffect> statusEffects;
|
||||
private readonly List<Item> openedContainers = new List<Item>();
|
||||
private readonly float randomChance;
|
||||
private readonly bool oncePerContainer;
|
||||
|
||||
public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
randomChance = abilityElement.GetAttributeFloat("randomchance", 1f);
|
||||
oncePerContainer = abilityElement.GetAttributeBool("oncepercontainer", false);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
if (oncePerContainer)
|
||||
{
|
||||
if (openedContainers.Contains(item)) { return; }
|
||||
openedContainers.Add(item);
|
||||
}
|
||||
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
|
||||
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, item, item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityUnlockTree : CharacterAbility
|
||||
{
|
||||
public CharacterAbilityUnlockTree(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (!addingFirstTime) { return; }
|
||||
if (!TalentTree.JobTalentTrees.TryGetValue(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
|
||||
|
||||
var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab)));
|
||||
if (subTree != null)
|
||||
{
|
||||
foreach (var talentOption in subTree.TalentOptionStages)
|
||||
{
|
||||
foreach (var talent in talentOption.Talents)
|
||||
{
|
||||
if (talent == CharacterTalent.Prefab) { continue; }
|
||||
Character.GiveTalent(talent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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 float maxAddedDamageMultiplier;
|
||||
private readonly string[] tags;
|
||||
|
||||
public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f);
|
||||
maxAddedDamageMultiplier = abilityElement.GetAttributeFloat("maxaddedddamagemultiplier", float.MaxValue);
|
||||
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject 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 += Math.Min(totalAddedDamageMultiplier, maxAddedDamageMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is AbilitySkillGain abilitySkillGain && !abilitySkillGain.GainedFromApprenticeship && abilitySkillGain.Character != Character)
|
||||
{
|
||||
Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, gainedFromApprenticeship: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityAtmosMachine : CharacterAbility
|
||||
{
|
||||
private readonly float addedValue;
|
||||
private readonly float multiplyValue;
|
||||
private readonly string[] tags;
|
||||
private readonly int maxMultiplyCount;
|
||||
|
||||
public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
|
||||
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
|
||||
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is IAbilityValue abilityValue)
|
||||
{
|
||||
int multiplyCount = 0;
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Prefab.Tags.Any(t => tags.Contains(t)))
|
||||
{
|
||||
multiplyCount++;
|
||||
if (multiplyCount == maxMultiplyCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
abilityValue.Value += addedValue * multiplyCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character character)
|
||||
{
|
||||
Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityByTheBook : CharacterAbility
|
||||
{
|
||||
private readonly int moneyAmount;
|
||||
private readonly int experienceAmount;
|
||||
private readonly int max;
|
||||
|
||||
public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0);
|
||||
experienceAmount = abilityElement.GetAttributeInt("experienceamount", 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);
|
||||
foreach (Character character in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
character.Info?.GiveExperience(experienceAmount);
|
||||
}
|
||||
timesGiven++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
|
||||
private readonly int moneyPerMission;
|
||||
|
||||
private static List<Client> clientsAlreadyUsed = new List<Client>();
|
||||
|
||||
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character?.Info is CharacterInfo info)
|
||||
{
|
||||
|
||||
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityString)?.String is string skillIdentifier)
|
||||
{
|
||||
if (skillIdentifier != lastSkillIdentifier)
|
||||
{
|
||||
lastSkillIdentifier = skillIdentifier;
|
||||
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPsychoClown : CharacterAbility
|
||||
{
|
||||
private StatTypes statType;
|
||||
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);
|
||||
maxValue = abilityElement.GetAttributeFloat("maxvalue", 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 * maxValue;
|
||||
Character.ChangeStat(statType, lastValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValue = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityRegenerateLoot : CharacterAbility
|
||||
{
|
||||
// separate random chance used for the ability itself to prevent the player
|
||||
// from opening/reopening a container until it spawns loot
|
||||
private readonly float randomChance;
|
||||
|
||||
// not maintained through death, so it's possible for players to respawn and re-loot chests
|
||||
// seems like a minor issue for now
|
||||
private readonly List<Item> openedContainers = new List<Item>();
|
||||
|
||||
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
randomChance = abilityElement.GetAttributeFloat("randomchance", 1f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
if (openedContainers.Contains(item)) { return; }
|
||||
openedContainers.Add(item);
|
||||
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
|
||||
|
||||
if (item.GetComponent<ItemContainer>() is ItemContainer itemContainer)
|
||||
{
|
||||
AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
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
|
||||
{
|
||||
// this should just be its own class, misleading to inherit here
|
||||
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 || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; }
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class CharacterAbilityGroup
|
||||
{
|
||||
public CharacterTalent CharacterTalent { get; }
|
||||
public Character Character { get; }
|
||||
|
||||
// currently only used to turn off simulation if random conditions are in use
|
||||
public bool IsActive { get; private set; } = true;
|
||||
|
||||
public readonly AbilityEffectType AbilityEffectType;
|
||||
|
||||
protected int maxTriggerCount { get; }
|
||||
protected int timesTriggered = 0;
|
||||
|
||||
|
||||
// add support for OR conditions?
|
||||
protected readonly List<AbilityCondition> abilityConditions = new List<AbilityCondition>();
|
||||
|
||||
// separate dictionaries for each type of characterability?
|
||||
protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>();
|
||||
|
||||
public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup)
|
||||
{
|
||||
AbilityEffectType = abilityEffectType;
|
||||
CharacterTalent = characterTalent;
|
||||
Character = CharacterTalent.Character;
|
||||
maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
|
||||
foreach (XElement subElement in abilityElementGroup.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "abilities":
|
||||
LoadAbilities(subElement);
|
||||
break;
|
||||
case "conditions":
|
||||
LoadConditions(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateAbilityGroup(bool addingFirstTime)
|
||||
{
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
characterAbility.InitializeAbility(addingFirstTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadConditions(XElement conditionElements)
|
||||
{
|
||||
foreach (XElement conditionElement in conditionElements.Elements())
|
||||
{
|
||||
AbilityCondition newCondition = ConstructCondition(CharacterTalent, conditionElement);
|
||||
|
||||
if (newCondition == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newCondition.AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
abilityConditions.Add(newCondition);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAbility(CharacterAbility characterAbility)
|
||||
{
|
||||
if (characterAbility == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!");
|
||||
return;
|
||||
}
|
||||
|
||||
characterAbilities.Add(characterAbility);
|
||||
}
|
||||
|
||||
// XML
|
||||
private AbilityCondition ConstructCondition(CharacterTalent characterTalent, XElement conditionElement, bool errorMessages = true)
|
||||
{
|
||||
AbilityCondition newCondition = null;
|
||||
|
||||
Type conditionType;
|
||||
string type = conditionElement.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
conditionType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
|
||||
if (conditionType == null)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
object[] args = { characterTalent, conditionElement };
|
||||
|
||||
try
|
||||
{
|
||||
newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args);
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (newCondition == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null");
|
||||
return null;
|
||||
}
|
||||
|
||||
return newCondition;
|
||||
}
|
||||
|
||||
private void LoadAbilities(XElement abilityElements)
|
||||
{
|
||||
foreach (XElement abilityElementGroup in abilityElements.Elements())
|
||||
{
|
||||
AddAbility(ConstructAbility(abilityElementGroup, CharacterTalent));
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterAbility ConstructAbility(XElement abilityElement, CharacterTalent characterTalent)
|
||||
{
|
||||
CharacterAbility newAbility = CharacterAbility.Load(abilityElement, this);
|
||||
|
||||
if (newAbility == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return newAbility;
|
||||
}
|
||||
|
||||
public static List<StatusEffect> ParseStatusEffects(CharacterTalent characterTalent, XElement statusEffectElements)
|
||||
{
|
||||
if (statusEffectElements == null)
|
||||
{
|
||||
DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
foreach (XElement statusEffectElement in statusEffectElements.Elements())
|
||||
{
|
||||
var statusEffect = StatusEffect.Load(statusEffectElement, characterTalent.DebugIdentifier);
|
||||
statusEffects.Add(statusEffect);
|
||||
}
|
||||
|
||||
return statusEffects;
|
||||
}
|
||||
|
||||
public static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
|
||||
{
|
||||
if (!Enum.TryParse(statTypeString, true, out StatTypes statType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
|
||||
}
|
||||
return statType;
|
||||
}
|
||||
|
||||
public static List<Affliction> ParseAfflictions(CharacterTalent characterTalent, XElement afflictionElements)
|
||||
{
|
||||
if (afflictionElements == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Affliction> afflictions = new List<Affliction>();
|
||||
|
||||
// similar logic to affliction creation in statuseffects
|
||||
// might be worth unifying
|
||||
|
||||
foreach (XElement afflictionElement in afflictionElements.Elements())
|
||||
{
|
||||
string afflictionIdentifier = afflictionElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Affliction afflictionInstance = afflictionPrefab.Instantiate(afflictionElement.GetAttributeFloat(1.0f, "amount", "strength"));
|
||||
afflictionInstance.Probability = afflictionElement.GetAttributeFloat(1.0f, "probability");
|
||||
afflictions.Add(afflictionInstance);
|
||||
}
|
||||
|
||||
return afflictions;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupEffect : CharacterAbilityGroup
|
||||
{
|
||||
public CharacterAbilityGroupEffect(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) :
|
||||
base(abilityEffectType, characterTalent, abilityElementGroup) { }
|
||||
|
||||
public void CheckAbilityGroup(AbilityObject abilityObject)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
if (IsApplicable(abilityObject))
|
||||
{
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
if (characterAbility.IsViable())
|
||||
{
|
||||
characterAbility.ApplyAbilityEffect(abilityObject);
|
||||
}
|
||||
}
|
||||
timesTriggered++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsApplicable(AbilityObject abilityObject)
|
||||
{
|
||||
if (timesTriggered >= maxTriggerCount) { return false; }
|
||||
return abilityConditions.All(c => c.MatchesCondition(abilityObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupInterval : CharacterAbilityGroup
|
||||
{
|
||||
private float interval { get; set; }
|
||||
public float TimeSinceLastUpdate { get; private set; }
|
||||
|
||||
private float effectDelay;
|
||||
private float effectDelayTimer;
|
||||
|
||||
|
||||
public CharacterAbilityGroupInterval(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) :
|
||||
base(abilityEffectType, characterTalent, abilityElementGroup)
|
||||
{
|
||||
// too many overlapping intervals could cause hitching? maybe randomize a little
|
||||
interval = abilityElementGroup.GetAttributeFloat("interval", 0f);
|
||||
effectDelay = abilityElementGroup.GetAttributeFloat("effectdelay", 0f);
|
||||
}
|
||||
public void UpdateAbilityGroup(float deltaTime)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
TimeSinceLastUpdate += deltaTime;
|
||||
if (TimeSinceLastUpdate >= interval)
|
||||
{
|
||||
bool conditionsMatched = IsApplicable();
|
||||
effectDelayTimer = conditionsMatched ? effectDelayTimer + TimeSinceLastUpdate : 0f;
|
||||
conditionsMatched &= effectDelayTimer >= effectDelay;
|
||||
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
if (characterAbility.IsViable())
|
||||
{
|
||||
characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
|
||||
}
|
||||
}
|
||||
if (conditionsMatched)
|
||||
{
|
||||
timesTriggered++;
|
||||
}
|
||||
TimeSinceLastUpdate = 0;
|
||||
}
|
||||
}
|
||||
private bool IsApplicable()
|
||||
{
|
||||
if (timesTriggered >= maxTriggerCount) { return false; }
|
||||
return abilityConditions.All(c => c.MatchesCondition());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterTalent
|
||||
{
|
||||
public Character Character { get; }
|
||||
public string DebugIdentifier { get; }
|
||||
|
||||
public readonly TalentPrefab Prefab;
|
||||
|
||||
public bool AddedThisRound = true;
|
||||
|
||||
private readonly Dictionary<AbilityEffectType, List<CharacterAbilityGroupEffect>> characterAbilityGroupEffectDictionary = new Dictionary<AbilityEffectType, List<CharacterAbilityGroupEffect>>();
|
||||
|
||||
private readonly List<CharacterAbilityGroupInterval> characterAbilityGroupIntervals = new List<CharacterAbilityGroupInterval>();
|
||||
|
||||
// works functionally but a missing recipe is not represented on GUI side. this might be better placed in the character class itself, though it might be fine here as well
|
||||
public List<string> UnlockedRecipes { get; } = new List<string>();
|
||||
|
||||
public CharacterTalent(TalentPrefab talentPrefab, Character character)
|
||||
{
|
||||
Character = character;
|
||||
|
||||
Prefab = talentPrefab;
|
||||
XElement element = talentPrefab.ConfigElement;
|
||||
DebugIdentifier = talentPrefab.OriginalName;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "abilitygroupeffect":
|
||||
LoadAbilityGroupEffect(subElement);
|
||||
break;
|
||||
case "abilitygroupinterval":
|
||||
LoadAbilityGroupInterval(subElement);
|
||||
break;
|
||||
case "addedrecipe":
|
||||
if (subElement.GetAttributeString("itemidentifier", string.Empty) is string recipeIdentifier && recipeIdentifier != string.Empty)
|
||||
{
|
||||
UnlockedRecipes.Add(recipeIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("No recipe identifier defined for talent " + DebugIdentifier);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateTalent(float deltaTime)
|
||||
{
|
||||
foreach (var characterAbilityGroupInterval in characterAbilityGroupIntervals)
|
||||
{
|
||||
characterAbilityGroupInterval.UpdateAbilityGroup(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckTalent(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
|
||||
{
|
||||
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups))
|
||||
{
|
||||
foreach (var characterAbilityGroup in characterAbilityGroups)
|
||||
{
|
||||
characterAbilityGroup.CheckAbilityGroup(abilityObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateTalent(bool addingFirstTime)
|
||||
{
|
||||
foreach (var characterAbilityGroups in characterAbilityGroupEffectDictionary.Values)
|
||||
{
|
||||
foreach (var characterAbilityGroup in characterAbilityGroups)
|
||||
{
|
||||
characterAbilityGroup.ActivateAbilityGroup(addingFirstTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XML logic
|
||||
private void LoadAbilityGroupInterval(XElement abilityGroup)
|
||||
{
|
||||
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(AbilityEffectType.Undefined, this, abilityGroup));
|
||||
}
|
||||
|
||||
private void LoadAbilityGroupEffect(XElement abilityGroup)
|
||||
{
|
||||
AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none"));
|
||||
AddAbilityGroupEffect(new CharacterAbilityGroupEffect(abilityEffectType, this, abilityGroup), abilityEffectType);
|
||||
}
|
||||
|
||||
public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None)
|
||||
{
|
||||
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityList))
|
||||
{
|
||||
characterAbilityList.Add(characterAbilityGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<CharacterAbilityGroupEffect> characterAbilityGroups = new List<CharacterAbilityGroupEffect>();
|
||||
characterAbilityGroups.Add(characterAbilityGroup);
|
||||
characterAbilityGroupEffectDictionary.Add(abilityEffectType, characterAbilityGroups);
|
||||
}
|
||||
}
|
||||
|
||||
public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString)
|
||||
{
|
||||
if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
|
||||
}
|
||||
if (abilityEffectType == AbilityEffectType.Undefined)
|
||||
{
|
||||
DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
|
||||
}
|
||||
|
||||
return abilityEffectType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TalentPrefab : IPrefab, IDisposable, IHasUintIdentifier
|
||||
{
|
||||
public string Identifier { get; private set; }
|
||||
public string OriginalName => Identifier;
|
||||
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
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TalentPrefab(XElement element, string filePath)
|
||||
{
|
||||
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;
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
TalentPrefabs.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier that's generated by hashing the prefab's string identifier.
|
||||
/// Used to reduce the amount of bytes needed to write talent data into network messages in multiplayer.
|
||||
/// </summary>
|
||||
public uint UIntIdentifier { get; set; }
|
||||
|
||||
public static void RemoveByFile(string filePath) => TalentPrefabs.RemoveByFile(filePath);
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
DebugConsole.Log("Loading talent prefab: " + file.Path);
|
||||
RemoveByFile(file.Path);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root;
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "talent":
|
||||
TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), false);
|
||||
break;
|
||||
case "talents":
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var itemElement = element.GetChildElement("talent");
|
||||
if (itemElement != null)
|
||||
{
|
||||
TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a talent element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TalentPrefabs.Add(new TalentPrefab(element, file.Path), false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
DebugConsole.Log("Loading talent prefabs: ");
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
LoadFromFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TalentTree
|
||||
{
|
||||
public enum TalentTreeStageState
|
||||
{
|
||||
Invalid,
|
||||
Locked,
|
||||
Unlocked,
|
||||
Available,
|
||||
Highlighted
|
||||
}
|
||||
|
||||
public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>();
|
||||
|
||||
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
|
||||
|
||||
public XElement ConfigElement
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TalentTree(XElement element, string filePath)
|
||||
{
|
||||
ConfigElement = element;
|
||||
|
||||
string jobIdentifier = element.GetAttributeString("jobidentifier", "").ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrEmpty(jobIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subTreeElement in element.GetChildElements("subtree"))
|
||||
{
|
||||
TalentSubTrees.Add(new TalentSubTree(subTreeElement));
|
||||
}
|
||||
|
||||
// talents found and unlocked using the identifier wihin the talent tree, so no duplicates may occur
|
||||
HashSet<string> duplicateSet = new HashSet<string>();
|
||||
foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))))
|
||||
{
|
||||
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
|
||||
if (talentPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Talent tree for job {jobIdentifier} contains non-existent talent {talent}! Talent tree not added.");
|
||||
return;
|
||||
}
|
||||
if (!duplicateSet.Add(talent))
|
||||
{
|
||||
DebugConsole.ThrowError($"Talent tree for job {jobIdentifier} contains duplicate talent {talent}! Talent tree not added.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!JobTalentTrees.TryAdd(jobIdentifier, this))
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not add talent tree for job {jobIdentifier}! A talent tree for this job is already likely defined");
|
||||
}
|
||||
}
|
||||
|
||||
public bool TalentIsInTree(string talentIdentifier)
|
||||
{
|
||||
return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier);
|
||||
}
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
DebugConsole.Log("Loading talent tree: " + file.Path);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root;
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "talenttree":
|
||||
new TalentTree(rootElement, file.Path);
|
||||
break;
|
||||
case "talenttrees":
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var treeElement = element.GetChildElement("talenttree");
|
||||
if (treeElement != null)
|
||||
{
|
||||
new TalentTree(rootElement, file.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a talent tree element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
new TalentTree(element, file.Path);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
DebugConsole.Log("Loading talent tree: ");
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
LoadFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier)
|
||||
{
|
||||
return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
// i hate this function - markus
|
||||
public static TalentTreeStageState GetTalentOptionStageState(Character character, string subTreeIdentifier, int index, List<string> selectedTalents)
|
||||
{
|
||||
if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; }
|
||||
|
||||
if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; }
|
||||
|
||||
TalentSubTree subTree = talentTree.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier);
|
||||
|
||||
if (subTree == null) { return TalentTreeStageState.Invalid; }
|
||||
|
||||
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
|
||||
|
||||
if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier)))
|
||||
{
|
||||
return TalentTreeStageState.Unlocked;
|
||||
}
|
||||
|
||||
if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier)))
|
||||
{
|
||||
return TalentTreeStageState.Highlighted;
|
||||
}
|
||||
|
||||
bool hasTalentInLastTier = true;
|
||||
bool isLastTalentPurchased = true;
|
||||
|
||||
int lastindex = index - 1;
|
||||
if (lastindex >= 0)
|
||||
{
|
||||
TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex];
|
||||
hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent);
|
||||
isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier));
|
||||
}
|
||||
|
||||
if (!hasTalentInLastTier)
|
||||
{
|
||||
return TalentTreeStageState.Locked;
|
||||
}
|
||||
|
||||
bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0;
|
||||
|
||||
if (hasPointsForNewTalent)
|
||||
{
|
||||
return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available;
|
||||
}
|
||||
|
||||
return TalentTreeStageState.Locked;
|
||||
|
||||
bool HasTalent(TalentPrefab t)
|
||||
{
|
||||
return selectedTalents.Contains(t.Identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable<string> selectedTalents)
|
||||
{
|
||||
if (character?.Info?.Job.Prefab == null) { return false; }
|
||||
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; }
|
||||
|
||||
if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
|
||||
|
||||
foreach (var subTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
foreach (var talentOptionStage in subTree.TalentOptionStages)
|
||||
{
|
||||
bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier));
|
||||
if (!hasTalentInThisTier)
|
||||
{
|
||||
if (talentOptionStage.Talents.Any(t => t.Identifier == talentIdentifier))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<string> CheckTalentSelection(Character controlledCharacter, IEnumerable<string> selectedTalents)
|
||||
{
|
||||
List<string> viableTalents = new List<string>();
|
||||
bool canStillUnlock = true;
|
||||
// keep trying to unlock talents until none of the talents are unlockable
|
||||
while (canStillUnlock && selectedTalents.Any())
|
||||
{
|
||||
canStillUnlock = false;
|
||||
foreach (string talent in selectedTalents)
|
||||
{
|
||||
if (IsViableTalentForCharacter(controlledCharacter, talent, viableTalents))
|
||||
{
|
||||
viableTalents.Add(talent);
|
||||
canStillUnlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return viableTalents;
|
||||
}
|
||||
}
|
||||
|
||||
class TalentSubTree
|
||||
{
|
||||
public string Identifier { get; }
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
|
||||
|
||||
public TalentSubTree(XElement subTreeElement)
|
||||
{
|
||||
Identifier = subTreeElement.GetAttributeString("identifier", "");
|
||||
|
||||
DisplayName = TextManager.Get("talenttree." + Identifier, returnNull: true) ?? Identifier;
|
||||
|
||||
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
|
||||
{
|
||||
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TalentOption
|
||||
{
|
||||
public readonly List<TalentPrefab> Talents = new List<TalentPrefab>();
|
||||
|
||||
public TalentOption(XElement talentOptionsElement, string debugIdentifier)
|
||||
{
|
||||
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
|
||||
{
|
||||
string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty);
|
||||
|
||||
if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\".");
|
||||
return;
|
||||
}
|
||||
Talents.Add(TalentPrefab.TalentPrefabs[identifier]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user