Unstable 0.1500.0.0
This commit is contained in:
+89
@@ -0,0 +1,89 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityCondition
|
||||
{
|
||||
protected CharacterTalent characterTalent;
|
||||
protected Character character;
|
||||
protected bool invert;
|
||||
|
||||
public virtual bool AllowClientSimulation => true;
|
||||
|
||||
public AbilityCondition(CharacterTalent characterTalent, XElement conditionElement)
|
||||
{
|
||||
this.characterTalent = characterTalent;
|
||||
character = characterTalent.Character;
|
||||
invert = conditionElement.GetAttributeBool("invert", false);
|
||||
}
|
||||
public abstract bool MatchesCondition(object abilityData);
|
||||
public abstract bool MatchesCondition();
|
||||
|
||||
|
||||
// tools
|
||||
protected enum TargetType
|
||||
{
|
||||
Any = 0,
|
||||
Enemy = 1,
|
||||
Ally = 2,
|
||||
NotSelf = 3,
|
||||
Alive = 4,
|
||||
Monster = 5,
|
||||
};
|
||||
|
||||
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
|
||||
{
|
||||
List<TargetType> targetTypes = new List<TargetType>();
|
||||
foreach (string targetTypeString in targetTypeStrings)
|
||||
{
|
||||
TargetType targetType = TargetType.Any;
|
||||
if (!Enum.TryParse(targetTypeString, true, out targetType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
|
||||
}
|
||||
targetTypes.Add(targetType);
|
||||
}
|
||||
return targetTypes;
|
||||
}
|
||||
|
||||
protected bool IsViableTarget(IEnumerable<TargetType> targetTypes, Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter == null) { return false; }
|
||||
|
||||
bool isViable = true;
|
||||
foreach (TargetType targetType in targetTypes)
|
||||
{
|
||||
if (!IsViableTarget(targetType, targetCharacter))
|
||||
{
|
||||
isViable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isViable;
|
||||
}
|
||||
|
||||
private bool IsViableTarget(TargetType targetType, Character targetCharacter)
|
||||
{
|
||||
switch (targetType)
|
||||
{
|
||||
case TargetType.Enemy:
|
||||
return !HumanAIController.IsFriendly(character, targetCharacter);
|
||||
case TargetType.Ally:
|
||||
return HumanAIController.IsFriendly(character, targetCharacter);
|
||||
case TargetType.NotSelf:
|
||||
return targetCharacter != character;
|
||||
case TargetType.Alive:
|
||||
return !targetCharacter.IsDead;
|
||||
case TargetType.Monster:
|
||||
return !targetCharacter.IsHuman;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+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 WeaponType weapontype;
|
||||
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
|
||||
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
|
||||
switch (conditionElement.GetAttributeString("weapontype", ""))
|
||||
{
|
||||
case "melee":
|
||||
weapontype = WeaponType.Melee;
|
||||
break;
|
||||
case "ranged":
|
||||
weapontype = WeaponType.Ranged;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is AttackData attackData)
|
||||
{
|
||||
Item item = attackData?.SourceAttack?.SourceItem;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Source Item was not found in {this} for talent {characterTalent.DebugIdentifier}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(itemIdentifier))
|
||||
{
|
||||
if (item.prefab.Identifier != itemIdentifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tags.Any())
|
||||
{
|
||||
if (!tags.All(t => item.HasTag(t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (weapontype)
|
||||
{
|
||||
case WeaponType.Melee:
|
||||
return item.GetComponent<MeleeWeapon>() != null;
|
||||
case WeaponType.Ranged:
|
||||
return item.GetComponent<RangedWeapon>() != null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(AttackData));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is AttackResult attackResult)
|
||||
{
|
||||
if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; }
|
||||
|
||||
if (afflictions.Any())
|
||||
{
|
||||
if (!afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(AttackData));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is Character character)
|
||||
{
|
||||
if (!IsViableTarget(targetTypes, character)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Character));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityConditionData : AbilityCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Some conditions rely on specific ability data that is integrally connected to the AbilityEffectType.
|
||||
/// This is done in order to avoid having to create duplicate ability behavior, such as if an ability needs to trigger
|
||||
/// a common ability effect but in specific circumstances. These conditions could also be partially replaced by
|
||||
/// more explicit AbilityEffectType enums, but this would introduce bloat and overhead to integral game logic
|
||||
/// when instead said logic can be made to only run when required using these conditions.
|
||||
///
|
||||
/// These conditions will return an error if used outside their limited intended use.
|
||||
/// </summary>
|
||||
public AbilityConditionData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected void LogAbilityConditionError<T>(T abilityData, Type expectedData)
|
||||
{
|
||||
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityData}");
|
||||
}
|
||||
|
||||
protected abstract bool MatchesConditionSpecific(object abilityData);
|
||||
public override bool MatchesCondition()
|
||||
{
|
||||
DebugConsole.ThrowError("Used data-reliant ability condition in a state-based ability! This is not allowed.");
|
||||
return false;
|
||||
}
|
||||
public override bool MatchesCondition(object abilityData)
|
||||
{
|
||||
if (abilityData is null) { return invert; }
|
||||
return invert ? !MatchesConditionSpecific(abilityData) : MatchesConditionSpecific(abilityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is Submarine submarine)
|
||||
{
|
||||
return submarine.TeamID == character.TeamID && character.Submarine == submarine;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Submarine));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHandsomeStranger : AbilityConditionData
|
||||
{
|
||||
string skillIdentifier;
|
||||
|
||||
public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is string skillIdentifier)
|
||||
{
|
||||
return this.skillIdentifier == skillIdentifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(string));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItem : AbilityConditionData
|
||||
{
|
||||
private readonly string identifier;
|
||||
private readonly string[] tags;
|
||||
|
||||
public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant();
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
ItemPrefab item = null;
|
||||
if (abilityData is Item tempItem)
|
||||
{
|
||||
item = tempItem.Prefab;
|
||||
}
|
||||
// this and other instances of this type of casting will be refactored
|
||||
else if (abilityData is (ItemPrefab itemPrefab, object _))
|
||||
{
|
||||
item = itemPrefab;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
if (item.Identifier != identifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return tags.Any(t => item.Tags.Any(p => t == p));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Item));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is (Affliction affliction, float reduceAmount))
|
||||
{
|
||||
if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; }
|
||||
|
||||
if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof((Affliction, float)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionScavenger : AbilityConditionData
|
||||
{
|
||||
public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is Item item)
|
||||
{
|
||||
return item.Submarine != character.Submarine;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Item));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAboveVitality : AbilityConditionDataless
|
||||
{
|
||||
float vitalityPercentage;
|
||||
|
||||
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.HealthPercentage / 100f > vitalityPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityConditionDataless : AbilityCondition
|
||||
{
|
||||
public AbilityConditionDataless(CharacterTalent characterTalent, XElement conditionElement) : base (characterTalent, conditionElement) { }
|
||||
|
||||
protected abstract bool MatchesConditionSpecific();
|
||||
public override bool MatchesCondition()
|
||||
{
|
||||
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
|
||||
}
|
||||
|
||||
public override bool MatchesCondition(object abilityData)
|
||||
{
|
||||
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
+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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is (Mission mission, AbilityValue missionAbilityValue))
|
||||
{
|
||||
return mission.Prefab.Type == missionType;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionServerRandom : AbilityConditionDataless
|
||||
{
|
||||
private float randomChance = 0f;
|
||||
public override bool AllowClientSimulation => false;
|
||||
|
||||
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
randomChance = conditionElement.GetAttributeFloat("randomchance", 1f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return randomChance >= Rand.Range(0f, 1f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.Submarine == null || character.Submarine.TeamID != character.TeamID) { return false; }
|
||||
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
|
||||
return currentFloodPercentage / 100 > floodPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class CharacterAbility
|
||||
{
|
||||
public CharacterAbilityGroup CharacterAbilityGroup { get; }
|
||||
public CharacterTalent CharacterTalent { get; }
|
||||
public Character Character { get; }
|
||||
|
||||
public virtual bool RequiresAlive => true;
|
||||
public virtual bool AllowClientSimulation => false;
|
||||
public virtual bool AppliesEffectOnIntervalUpdate => false;
|
||||
|
||||
private const float DefaultEffectTime = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Used primarily for StatusEffects. Default to constant outside interval abilities.
|
||||
/// </summary>
|
||||
protected float EffectDeltaTime => CharacterAbilityGroup is CharacterAbilityGroupInterval abilityGroupInterval ? abilityGroupInterval.TimeSinceLastUpdate : DefaultEffectTime;
|
||||
|
||||
public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement)
|
||||
{
|
||||
CharacterAbilityGroup = characterAbilityGroup;
|
||||
CharacterTalent = characterAbilityGroup.CharacterTalent;
|
||||
Character = CharacterTalent.Character;
|
||||
}
|
||||
|
||||
public bool IsViable()
|
||||
{
|
||||
if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
if (RequiresAlive && Character.IsDead) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void InitializeAbility(bool addingFirstTime) { }
|
||||
|
||||
public virtual void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
// may need a separate Update for changing state on non-interval-based abilities
|
||||
if (AppliesEffectOnIntervalUpdate)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyState(conditionsMatched, timeSinceLastUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
|
||||
}
|
||||
|
||||
public void ApplyAbilityEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is null)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffect(abilityData);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect()
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect");
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect(object abilityData)
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect");
|
||||
}
|
||||
|
||||
protected void LogAbilityDataMismatch()
|
||||
{
|
||||
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type.");
|
||||
}
|
||||
|
||||
// XML
|
||||
public static CharacterAbility Load(XElement abilityElement, CharacterAbilityGroup characterAbilityGroup, bool errorMessages = true)
|
||||
{
|
||||
Type abilityType;
|
||||
string type = abilityElement.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
|
||||
if (abilityType == null)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
object[] args = { characterAbilityGroup, abilityElement };
|
||||
CharacterAbility characterAbility;
|
||||
|
||||
try
|
||||
{
|
||||
characterAbility = (CharacterAbility)Activator.CreateInstance(abilityType, args);
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException);
|
||||
return null;
|
||||
}
|
||||
|
||||
DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier);
|
||||
return characterAbility;
|
||||
}
|
||||
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
|
||||
{
|
||||
AbilityFlags flagType = AbilityFlags.None;
|
||||
if (!Enum.TryParse(flagTypeString, true, out flagType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
|
||||
}
|
||||
return flagType;
|
||||
}
|
||||
|
||||
public static float DistanceToSquaredDistance(float distance)
|
||||
{
|
||||
return distance * distance;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyForce : CharacterAbility
|
||||
{
|
||||
private readonly float impulseStrength;
|
||||
private readonly float maxVelocity;
|
||||
|
||||
private readonly string afflictionIdentifier;
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
impulseStrength = abilityElement.GetAttributeFloat("impulsestrength", 0f);
|
||||
maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f);
|
||||
|
||||
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
|
||||
if (affliction == null) { return; }
|
||||
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * impulseStrength * (affliction.Strength / affliction.Prefab.MaxStrength), maxVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffects : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public override bool AllowClientSimulation => true;
|
||||
|
||||
protected readonly List<StatusEffect> statusEffects;
|
||||
|
||||
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
}
|
||||
|
||||
protected void ApplyEffectSpecific(Character targetCharacter)
|
||||
{
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character targetCharacter)
|
||||
{
|
||||
ApplyEffectSpecific(targetCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToNearestAlly : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
protected float squaredMaxDistance;
|
||||
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
|
||||
{
|
||||
closestCharacter = crewCharacter;
|
||||
closestDistance = tempDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToRandomAlly : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
private readonly float squaredMaxDistance;
|
||||
private readonly bool allowDifferentSub;
|
||||
private readonly bool allowSelf;
|
||||
|
||||
public override bool AllowClientSimulation => false;
|
||||
|
||||
public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
|
||||
allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true);
|
||||
allowSelf = abilityElement.GetAttributeBool("allowself", true);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character chosenCharacter = null;
|
||||
|
||||
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
|
||||
(allowSelf ||c != Character) &&
|
||||
(allowDifferentSub || c.Submarine == Character.Submarine) &&
|
||||
Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance &&
|
||||
tempDistance < squaredMaxDistance).GetRandom();
|
||||
|
||||
if (chosenCharacter == null) { return; }
|
||||
|
||||
ApplyEffectSpecific(chosenCharacter);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveFlag : CharacterAbility
|
||||
{
|
||||
private AbilityFlags abilityFlag;
|
||||
|
||||
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
|
||||
public CharacterAbilityGiveFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.AddAbilityFlag(abilityFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveMissionCount : CharacterAbility
|
||||
{
|
||||
private readonly int amount;
|
||||
|
||||
public CharacterAbilityGiveMissionCount(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (!addingFirstTime) { return; }
|
||||
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
|
||||
campaign.Settings.AddedMissionCount += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveMoney : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
private int amount;
|
||||
|
||||
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character.GiveMoney(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGivePermanentStat : CharacterAbility
|
||||
{
|
||||
private readonly string statIdentifier;
|
||||
private readonly StatTypes statType;
|
||||
private readonly float value;
|
||||
private readonly bool targetAllies;
|
||||
private readonly bool removeOnDeath;
|
||||
//private readonly float maximumValue;
|
||||
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
|
||||
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
|
||||
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific()
|
||||
{
|
||||
if (targetAllies)
|
||||
{
|
||||
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath));
|
||||
}
|
||||
else
|
||||
{
|
||||
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveResistance : CharacterAbility
|
||||
{
|
||||
private string resistanceId;
|
||||
private float resistance;
|
||||
|
||||
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
|
||||
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, resistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveStat : CharacterAbility
|
||||
{
|
||||
private StatTypes statType;
|
||||
private float value;
|
||||
|
||||
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
|
||||
public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.ChangeStat(statType, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityIncreaseSkill : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
private string skillIdentifier;
|
||||
private float skillIncrease;
|
||||
|
||||
public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
|
||||
skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character character)
|
||||
{
|
||||
ApplyEffectSpecific(character);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(Character character)
|
||||
{
|
||||
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is Affliction affliction)
|
||||
{
|
||||
foreach (string afflictionIdentifier in afflictionIdentifiers)
|
||||
{
|
||||
if (affliction.Identifier == afflictionIdentifier)
|
||||
{
|
||||
affliction.Strength *= 1 + addedMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityDataMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyAttackData : CharacterAbility
|
||||
{
|
||||
private readonly List<Affliction> afflictions;
|
||||
|
||||
float addedDamageMultiplier;
|
||||
float addedPenetration;
|
||||
|
||||
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
if (abilityElement.GetChildElement("afflictions") is XElement afflictionElements)
|
||||
{
|
||||
afflictions = CharacterAbilityGroup.ParseAfflictions(CharacterTalent, afflictionElements);
|
||||
}
|
||||
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
|
||||
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is AttackData attackData)
|
||||
{
|
||||
if (attackData.Afflictions == null)
|
||||
{
|
||||
attackData.Afflictions = afflictions;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackData.Afflictions.AddRange(afflictions);
|
||||
}
|
||||
attackData.DamageMultiplier += addedDamageMultiplier;
|
||||
attackData.AddedPenetration += addedPenetration;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityDataMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyFlag : CharacterAbility
|
||||
{
|
||||
private AbilityFlags abilityFlag;
|
||||
|
||||
private bool lastState;
|
||||
|
||||
public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
{
|
||||
Character.AddAbilityFlag(abilityFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
Character.RemoveAbilityFlag(abilityFlag);
|
||||
}
|
||||
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is (Affliction affliction, float reduceAmount))
|
||||
{
|
||||
affliction.Strength -= addedAmountMultiplier * reduceAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityDataMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyResistance : CharacterAbility
|
||||
{
|
||||
private string resistanceId;
|
||||
private float resistance;
|
||||
bool lastState;
|
||||
|
||||
// should probably be split to different classes
|
||||
public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
|
||||
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
|
||||
}
|
||||
|
||||
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance);
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyValue : CharacterAbility
|
||||
{
|
||||
private float addedValue;
|
||||
private float multiplierValue;
|
||||
|
||||
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
|
||||
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is AbilityValue abilityValue)
|
||||
{
|
||||
ApplyEffectSpecific(abilityValue);
|
||||
}
|
||||
else if (abilityData is (object _, AbilityValue tupleAbilityValue))
|
||||
{
|
||||
ApplyEffectSpecific(tupleAbilityValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(AbilityValue abilityValue)
|
||||
{
|
||||
abilityValue.Value += addedValue;
|
||||
abilityValue.Value *= multiplierValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// this seems like a real silly way to have to pass values by reference into these same interfaces
|
||||
// if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead
|
||||
class AbilityValue
|
||||
{
|
||||
public float Value { get; set; }
|
||||
public AbilityValue(float value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityResetPermanentStat : CharacterAbility
|
||||
{
|
||||
private readonly string statIdentifier;
|
||||
public override bool RequiresAlive => false;
|
||||
|
||||
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
}
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific()
|
||||
{
|
||||
Character?.Info.ResetSavedStatValue(statIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is (string skillIdentifier, Character character) && character != Character)
|
||||
{
|
||||
character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is Character character)
|
||||
{
|
||||
Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityIndustrialRevolution : CharacterAbility
|
||||
{
|
||||
float addedFabricationSpeed;
|
||||
|
||||
public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f);
|
||||
}
|
||||
|
||||
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
{
|
||||
// not necessarily the cleanest or performant way, but at least this shouldn't break anything.
|
||||
// must be done every frame in order to work.
|
||||
if (Character.SelectedConstruction?.GetComponent<Fabricator>() is Fabricator fabricator && fabricator.IsActive)
|
||||
{
|
||||
fabricator.FabricationSpeedMultiplier += addedFabricationSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityInsurancePolicy : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public override bool RequiresAlive => false;
|
||||
|
||||
private readonly int moneyPerLevel;
|
||||
private bool hasOccurred = false;
|
||||
|
||||
private static List<Client> clientsAlreadyUsed = new List<Client>();
|
||||
|
||||
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character?.Info is CharacterInfo info && !hasOccurred)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
|
||||
{
|
||||
if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel());
|
||||
hasOccurred = true;
|
||||
|
||||
// this is an ugly way to do this, but this effect should not occur more than once per round for a client
|
||||
// this seemed like the simplest way to do it since characters are instantiated from scratch each time
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
|
||||
{
|
||||
if (client.Character == Character)
|
||||
{
|
||||
clientsAlreadyUsed.Add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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(object abilityData)
|
||||
{
|
||||
if (abilityData is string skillIdentifier)
|
||||
{
|
||||
if (skillIdentifier != lastSkillIdentifier)
|
||||
{
|
||||
lastSkillIdentifier = skillIdentifier;
|
||||
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, Character.Position + Vector2.UnitY * 175.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPsychoClown : CharacterAbility
|
||||
{
|
||||
private StatTypes statType;
|
||||
private float value;
|
||||
private string afflictionIdentifier;
|
||||
private float lastValue = 0f;
|
||||
|
||||
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
// managing state this way seems liable to cause bugs, maybe instead create abstraction to reset these values more safely
|
||||
// talents cannot be removed while in active play because of the lack of this, for example
|
||||
Character.ChangeStat(statType, -lastValue);
|
||||
|
||||
if (conditionsMatched)
|
||||
{
|
||||
var affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
|
||||
float afflictionStrength = 0f;
|
||||
if (affliction != null)
|
||||
{
|
||||
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
|
||||
}
|
||||
|
||||
lastValue = afflictionStrength * value;
|
||||
Character.ChangeStat(statType, lastValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValue = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityRegenerateLoot : CharacterAbility
|
||||
{
|
||||
List<Item> openedContainers = new List<Item>();
|
||||
|
||||
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Item item && !openedContainers.Contains(item))
|
||||
{
|
||||
openedContainers.Add(item);
|
||||
|
||||
if (item.GetComponent<ItemContainer>() is ItemContainer itemContainer)
|
||||
{
|
||||
AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityStonewall : CharacterAbility
|
||||
{
|
||||
private readonly List<StatusEffect> statusEffects;
|
||||
private readonly List<StatusEffect> statusEffectsReset;
|
||||
private int maxEnemyCount;
|
||||
private float squaredDistance;
|
||||
|
||||
public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset"));
|
||||
maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0);
|
||||
squaredDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("distance", 0));
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
int numberOfEnemiesInRange = Character.CharacterList.Where(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) < squaredDistance).Count();
|
||||
|
||||
foreach (var statusEffect in statusEffectsReset)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character);
|
||||
}
|
||||
|
||||
if (conditionsMatched && numberOfEnemiesInRange > 0)
|
||||
{
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
|
||||
{
|
||||
private string tag;
|
||||
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
tag = abilityElement.GetAttributeString("tag", "");
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
|
||||
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
|
||||
{
|
||||
closestCharacter = crewCharacter;
|
||||
closestDistance = tempDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityTaskmaster : CharacterAbility
|
||||
{
|
||||
private readonly List<StatusEffect> statusEffects;
|
||||
private readonly List<StatusEffect> statusEffectsRemove;
|
||||
|
||||
private Character lastCharacter;
|
||||
|
||||
public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove"));
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter == Character) { return; }
|
||||
|
||||
foreach (var statusEffect in statusEffectsRemove)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter);
|
||||
}
|
||||
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
|
||||
}
|
||||
|
||||
lastCharacter = targetCharacter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
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;
|
||||
|
||||
// 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(CharacterTalent characterTalent, XElement abilityElementGroup)
|
||||
{
|
||||
CharacterTalent = characterTalent;
|
||||
Character = CharacterTalent.Character;
|
||||
|
||||
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)
|
||||
{
|
||||
StatTypes statType;
|
||||
if (!Enum.TryParse(statTypeString, true, out 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupEffect : CharacterAbilityGroup
|
||||
{
|
||||
public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { }
|
||||
|
||||
public void CheckAbilityGroup(object abilityData)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
if (IsApplicable(abilityData))
|
||||
{
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
if (characterAbility.IsViable())
|
||||
{
|
||||
characterAbility.ApplyAbilityEffect(abilityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsApplicable(object abilityData)
|
||||
{
|
||||
return abilityConditions.All(c => c.MatchesCondition(abilityData));
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
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(CharacterTalent characterTalent, XElement abilityElementGroup) : base(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);
|
||||
}
|
||||
}
|
||||
TimeSinceLastUpdate = 0;
|
||||
}
|
||||
}
|
||||
private bool IsApplicable()
|
||||
{
|
||||
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;
|
||||
|
||||
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, object abilityData)
|
||||
{
|
||||
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups))
|
||||
{
|
||||
foreach (var characterAbilityGroup in characterAbilityGroups)
|
||||
{
|
||||
characterAbilityGroup.CheckAbilityGroup(abilityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
string name = abilityGroup.Name.ToString().ToLowerInvariant();
|
||||
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, abilityGroup));
|
||||
}
|
||||
|
||||
private void LoadAbilityGroupEffect(XElement abilityGroup)
|
||||
{
|
||||
AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none"));
|
||||
AddAbilityGroupEffect(new CharacterAbilityGroupEffect(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)
|
||||
{
|
||||
AbilityEffectType abilityEffectType = AbilityEffectType.Undefined;
|
||||
if (!Enum.TryParse(abilityEffectTypeString, true, out 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,102 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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 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");
|
||||
this.CalculatePrefabUIntIdentifier(TalentPrefabs);
|
||||
}
|
||||
|
||||
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,219 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TalentTree
|
||||
{
|
||||
public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>();
|
||||
|
||||
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
|
||||
|
||||
private static HashSet<string> subtreeTalents = new HashSet<string>();
|
||||
|
||||
public XElement ConfigElement
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TalentTree(XElement element, string filePath)
|
||||
{
|
||||
ConfigElement = element;
|
||||
|
||||
string jobIdentifier = element.GetAttributeString("jobidentifier", "");
|
||||
|
||||
if (string.IsNullOrEmpty(jobIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("No job defined for talent tree!");
|
||||
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 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.ToString()}' 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>());
|
||||
}
|
||||
|
||||
|
||||
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 readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
|
||||
|
||||
public TalentSubTree(XElement subTreeElement)
|
||||
{
|
||||
Identifier = subTreeElement.GetAttributeString("identifier", "");
|
||||
|
||||
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
|
||||
{
|
||||
TalentOptionStages.Add(new TalentOption(talentOptionsElement));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TalentOption
|
||||
{
|
||||
public readonly List<Talent> Talents = new List<Talent>();
|
||||
|
||||
public TalentOption(XElement talentOptionsElement)
|
||||
{
|
||||
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
|
||||
{
|
||||
Talents.Add(new Talent(talentOptionElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Talent
|
||||
{
|
||||
public readonly string Identifier;
|
||||
public readonly Sprite Icon;
|
||||
public Talent(XElement talentOptionElement)
|
||||
{
|
||||
Identifier = talentOptionElement.GetAttributeString("identifier", "");
|
||||
foreach (XElement subElement in talentOptionElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "icon":
|
||||
Icon = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user