Build 1.1.4.0
This commit is contained in:
@@ -1,71 +1,246 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
// TODO: This class should be refactored:
|
||||
// - Use XElement instead of XAttribute in the constructor
|
||||
// - Simplify, remove unnecessary conversions
|
||||
// - Improve the flow so that the logic is undestandable.
|
||||
// - Maybe add some test cases for the operators?
|
||||
class PropertyConditional
|
||||
/// <summary>
|
||||
/// Conditionals are used by some in-game mechanics to require one
|
||||
/// or more conditions to be met for those mechanics to be active.
|
||||
/// For example, some StatusEffects use Conditionals to only trigger
|
||||
/// if the affected character is alive.
|
||||
/// </summary>
|
||||
sealed class PropertyConditional
|
||||
{
|
||||
// TODO: Make this testable and add tests
|
||||
|
||||
/// <summary>
|
||||
/// Category of properties to check against
|
||||
/// </summary>
|
||||
public enum ConditionType
|
||||
{
|
||||
Uncertain,
|
||||
PropertyValue,
|
||||
/// <summary>
|
||||
/// Depending on what's available, check against either one
|
||||
/// of the target object's properties or the strength of an
|
||||
/// affliction.
|
||||
///
|
||||
/// The target object's available properties depend on how that
|
||||
/// object is defined in the [source code](https://github.com/Regalis11/Barotrauma).
|
||||
///
|
||||
/// This is not applicable if the element contains the attribute
|
||||
/// `SkillRequirement="true"`.
|
||||
/// </summary>
|
||||
/// <AutoDocEntryName value="Property or affliction identifier" />
|
||||
/// <example>
|
||||
/// <Conditional IsDead="true" />
|
||||
/// </example>
|
||||
PropertyValueOrAffliction,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the target character's skill with the same name as the attribute.
|
||||
///
|
||||
/// This is only applicable if the element contains the attribute
|
||||
/// `SkillRequirement="true"`.
|
||||
/// </summary>
|
||||
/// <AutoDocEntryName value="Skill identifier" />
|
||||
/// <example>
|
||||
/// <Conditional SkillRequirement="true" weapons="lt 50" />
|
||||
/// </example>
|
||||
SkillRequirement,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the name of the target.
|
||||
/// </summary>
|
||||
Name,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the species identifier of the target. Only works on characters.
|
||||
/// </summary>
|
||||
SpeciesName,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the species group of the target. Only works on characters.
|
||||
/// </summary>
|
||||
SpeciesGroup,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the target's tags. Only works on items.
|
||||
///
|
||||
/// Several tags can be checked against by using a comma-separated list.
|
||||
/// </summary>
|
||||
HasTag,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the tags of the target's active status effects.
|
||||
///
|
||||
/// Several tags can be checked against by using a comma-separated list.
|
||||
/// </summary>
|
||||
HasStatusTag,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the target's specifier tags. In the vanilla game, these are the head index
|
||||
/// and gender. See human.xml for more details.
|
||||
///
|
||||
/// Several tags can be checked against by using a comma-separated list.
|
||||
/// </summary>
|
||||
HasSpecifierTag,
|
||||
Affliction,
|
||||
|
||||
/// <summary>
|
||||
/// Check against the target's entity type.
|
||||
///
|
||||
/// The currently supported values are "character", "limb", "item", "structure" and "null".
|
||||
/// </summary>
|
||||
EntityType,
|
||||
LimbType,
|
||||
SkillRequirement
|
||||
|
||||
/// <summary>
|
||||
/// Check against the target's limb type. See <see cref="Barotrauma.LimbType"/>.
|
||||
/// </summary>
|
||||
LimbType
|
||||
}
|
||||
|
||||
public enum Comparison
|
||||
public enum LogicalOperatorType
|
||||
{
|
||||
And,
|
||||
Or
|
||||
}
|
||||
|
||||
public enum OperatorType
|
||||
/// <summary>
|
||||
/// There are several ways to compare properties to values. The comparison operator
|
||||
/// to use can be specified by placing one of the following before the value to compare
|
||||
/// against.
|
||||
/// </summary>
|
||||
public enum ComparisonOperatorType
|
||||
{
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Require that the property being checked equals the given value.
|
||||
///
|
||||
/// This is the default operator used if none is specified.
|
||||
/// </summary>
|
||||
Equals,
|
||||
|
||||
/// <summary>
|
||||
/// Require that the property being checked doesn't equal the given value.
|
||||
/// </summary>
|
||||
NotEquals,
|
||||
|
||||
/// <summary>
|
||||
/// Require that the property being checked is less than the given value.
|
||||
///
|
||||
/// This can only be used to compare with numeric object properties,
|
||||
/// affliction strengths and skill levels.
|
||||
/// </summary>
|
||||
LessThan,
|
||||
|
||||
/// <summary>
|
||||
/// Require that the property being checked is less than or equal to the given value.
|
||||
///
|
||||
/// This can only be used to compare with numeric object properties,
|
||||
/// affliction strengths and skill levels.
|
||||
/// </summary>
|
||||
LessThanEquals,
|
||||
|
||||
/// <summary>
|
||||
/// Require that the property being checked is greater than the given value.
|
||||
///
|
||||
/// This can only be used to compare with numeric object properties,
|
||||
/// affliction strengths and skill levels.
|
||||
/// </summary>
|
||||
GreaterThan,
|
||||
|
||||
/// <summary>
|
||||
/// Require that the property being checked is greater than or equal to the given value.
|
||||
///
|
||||
/// This can only be used to compare with numeric object properties,
|
||||
/// affliction strengths and skill levels.
|
||||
/// </summary>
|
||||
GreaterThanEquals
|
||||
}
|
||||
|
||||
public readonly ConditionType Type;
|
||||
public readonly OperatorType Operator;
|
||||
public readonly ComparisonOperatorType ComparisonOperator;
|
||||
public readonly Identifier AttributeName;
|
||||
public readonly string AttributeValue;
|
||||
public readonly string[] SplitAttributeValue;
|
||||
public readonly ImmutableArray<Identifier> AttributeValueAsTags;
|
||||
public readonly float? FloatValue;
|
||||
|
||||
public readonly string TargetItemComponentName;
|
||||
/// <summary>
|
||||
/// If set to the name of one of the target's ItemComponents, the conditionals defined by this element check against the properties of that component.
|
||||
/// Only works on items.
|
||||
/// </summary>
|
||||
public readonly string TargetItemComponent;
|
||||
|
||||
// Only used by attacks
|
||||
/// <summary>
|
||||
/// If set to true, the conditionals defined by this element check against the attacking character instead of the attacked character
|
||||
/// </summary>
|
||||
public readonly bool TargetSelf;
|
||||
|
||||
// Only used by conditionals targeting an item (makes the conditional check the item/character whose inventory this item is inside)
|
||||
/// <summary>
|
||||
/// If set to true, the conditionals defined by this element check against the entity containing the target.
|
||||
/// </summary>
|
||||
public readonly bool TargetContainer;
|
||||
// Only used by conditionals targeting an item. By default, containers check the parent item. This allows you to check the grandparent instead.
|
||||
|
||||
/// <summary>
|
||||
/// If this and TargetContainer are set to true, the conditionals defined by this element check against the entity containing the target's container.
|
||||
/// </summary>
|
||||
public readonly bool TargetGrandParent;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the conditionals defined by this element check against the items contained by the target. Only works with items.
|
||||
/// </summary>
|
||||
public readonly bool TargetContainedItem;
|
||||
|
||||
// Remove this after refactoring
|
||||
public static bool IsValid(XAttribute attribute)
|
||||
public static IEnumerable<PropertyConditional> FromXElement(XElement element, Predicate<XAttribute>? predicate = null)
|
||||
{
|
||||
var targetItemComponent = element.GetAttributeString(nameof(TargetItemComponent), "");
|
||||
var targetContainer = element.GetAttributeBool(nameof(TargetContainer), false);
|
||||
var targetSelf = element.GetAttributeBool(nameof(TargetSelf), false);
|
||||
var targetGrandParent = element.GetAttributeBool(nameof(TargetGrandParent), false);
|
||||
var targetContainedItem = element.GetAttributeBool(nameof(TargetContainedItem), false);
|
||||
|
||||
ConditionType? overrideConditionType = null;
|
||||
if (element.GetAttributeBool(nameof(ConditionType.SkillRequirement), false))
|
||||
{
|
||||
overrideConditionType = ConditionType.SkillRequirement;
|
||||
}
|
||||
|
||||
foreach (var attribute in element.Attributes())
|
||||
{
|
||||
if (!IsValid(attribute)) { continue; }
|
||||
if (predicate != null && !predicate(attribute)) { continue; }
|
||||
|
||||
var (comparisonOperator, attributeValueString) = ExtractComparisonOperatorFromConditionString(attribute.Value);
|
||||
if (string.IsNullOrWhiteSpace(attributeValueString))
|
||||
{
|
||||
DebugConsole.ThrowError($"Conditional attribute value is empty: {element}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var conditionType = overrideConditionType ??
|
||||
(Enum.TryParse(attribute.Name.LocalName, ignoreCase: true, out ConditionType type)
|
||||
? type
|
||||
: ConditionType.PropertyValueOrAffliction);
|
||||
|
||||
yield return new PropertyConditional(
|
||||
attributeName: attribute.NameAsIdentifier(),
|
||||
comparisonOperator: comparisonOperator,
|
||||
attributeValue: attributeValueString,
|
||||
targetItemComponent: targetItemComponent,
|
||||
targetSelf: targetSelf,
|
||||
targetContainer: targetContainer,
|
||||
targetGrandParent: targetGrandParent,
|
||||
targetContainedItem: targetContainedItem,
|
||||
conditionType: conditionType);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValid(XAttribute attribute)
|
||||
{
|
||||
switch (attribute.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -82,60 +257,63 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use XElement instead of XAttribute (how to do without breaking the existing content?)
|
||||
public PropertyConditional(XAttribute attribute)
|
||||
private PropertyConditional(
|
||||
Identifier attributeName,
|
||||
ComparisonOperatorType comparisonOperator,
|
||||
string attributeValue,
|
||||
string targetItemComponent,
|
||||
bool targetSelf,
|
||||
bool targetContainer,
|
||||
bool targetGrandParent,
|
||||
bool targetContainedItem,
|
||||
ConditionType conditionType)
|
||||
{
|
||||
AttributeName = attribute.NameAsIdentifier();
|
||||
string attributeValueString = attribute.Value;
|
||||
if (string.IsNullOrWhiteSpace(attributeValueString))
|
||||
{
|
||||
DebugConsole.ThrowError($"Conditional attribute value is empty: {attribute.Parent}");
|
||||
return;
|
||||
}
|
||||
string valueString = attributeValueString;
|
||||
string[] splitString = valueString.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitString.Length > 1) { valueString = string.Join(' ', splitString.Skip(1)); }
|
||||
Operator = GetOperatorType(splitString[0]);
|
||||
AttributeName = attributeName;
|
||||
|
||||
if (Operator == OperatorType.None)
|
||||
{
|
||||
Operator = OperatorType.Equals;
|
||||
valueString = attributeValueString;
|
||||
}
|
||||
TargetItemComponent = targetItemComponent;
|
||||
TargetSelf = targetSelf;
|
||||
TargetContainer = targetContainer;
|
||||
TargetGrandParent = targetGrandParent;
|
||||
TargetContainedItem = targetContainedItem;
|
||||
|
||||
TargetItemComponentName = attribute.Parent.GetAttributeString("targetitemcomponent", "");
|
||||
TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false);
|
||||
TargetSelf = attribute.Parent.GetAttributeBool("targetself", false);
|
||||
TargetGrandParent = attribute.Parent.GetAttributeBool("targetgrandparent", false);
|
||||
TargetContainedItem = attribute.Parent.GetAttributeBool("targetcontaineditem", false);
|
||||
Type = conditionType;
|
||||
|
||||
if (!Enum.TryParse(AttributeName.Value, true, out Type))
|
||||
{
|
||||
Type = ConditionType.Uncertain;
|
||||
}
|
||||
|
||||
if (attribute.Parent.GetAttributeBool("skillrequirement", false))
|
||||
{
|
||||
Type = ConditionType.SkillRequirement;
|
||||
}
|
||||
|
||||
AttributeValue = valueString;
|
||||
SplitAttributeValue = valueString.Split(',');
|
||||
ComparisonOperator = comparisonOperator;
|
||||
AttributeValue = attributeValue;
|
||||
AttributeValueAsTags = AttributeValue.Split(',')
|
||||
//, options: StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(s => s.ToIdentifier())
|
||||
.ToImmutableArray();
|
||||
if (float.TryParse(AttributeValue, NumberStyles.Float, CultureInfo.InvariantCulture, out float value))
|
||||
{
|
||||
FloatValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static OperatorType GetOperatorType(string op)
|
||||
public static (ComparisonOperatorType ComparisonOperator, string ConditionStr) ExtractComparisonOperatorFromConditionString(string str)
|
||||
{
|
||||
str ??= "";
|
||||
|
||||
ComparisonOperatorType op = ComparisonOperatorType.Equals;
|
||||
string conditionStr = str;
|
||||
if (str.IndexOf(' ') is var i and >= 0)
|
||||
{
|
||||
op = GetComparisonOperatorType(str[..i]);
|
||||
if (op != ComparisonOperatorType.None) { conditionStr = str[(i + 1)..]; }
|
||||
else { op = ComparisonOperatorType.Equals; }
|
||||
}
|
||||
return (op, conditionStr);
|
||||
}
|
||||
|
||||
public static ComparisonOperatorType GetComparisonOperatorType(string op)
|
||||
{
|
||||
//thanks xml for not letting me use < or > in attributes :(
|
||||
switch (op)
|
||||
switch (op.ToLowerInvariant())
|
||||
{
|
||||
case "e":
|
||||
case "eq":
|
||||
case "equals":
|
||||
return OperatorType.Equals;
|
||||
return ComparisonOperatorType.Equals;
|
||||
case "ne":
|
||||
case "neq":
|
||||
case "notequals":
|
||||
@@ -143,311 +321,280 @@ namespace Barotrauma
|
||||
case "!e":
|
||||
case "!eq":
|
||||
case "!equals":
|
||||
return OperatorType.NotEquals;
|
||||
return ComparisonOperatorType.NotEquals;
|
||||
case "gt":
|
||||
case "greaterthan":
|
||||
return OperatorType.GreaterThan;
|
||||
return ComparisonOperatorType.GreaterThan;
|
||||
case "lt":
|
||||
case "lessthan":
|
||||
return OperatorType.LessThan;
|
||||
return ComparisonOperatorType.LessThan;
|
||||
case "gte":
|
||||
case "gteq":
|
||||
case "greaterthanequals":
|
||||
return OperatorType.GreaterThanEquals;
|
||||
return ComparisonOperatorType.GreaterThanEquals;
|
||||
case "lte":
|
||||
case "lteq":
|
||||
case "lessthanequals":
|
||||
return OperatorType.LessThanEquals;
|
||||
return ComparisonOperatorType.LessThanEquals;
|
||||
default:
|
||||
return OperatorType.None;
|
||||
return ComparisonOperatorType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ComparisonOperatorIsNotEquals => ComparisonOperator == ComparisonOperatorType.NotEquals;
|
||||
|
||||
public bool Matches(ISerializableEntity target)
|
||||
public bool Matches(ISerializableEntity? target)
|
||||
{
|
||||
return Matches(target, TargetContainedItem);
|
||||
return TargetContainedItem
|
||||
? MatchesContained(target)
|
||||
: MatchesDirect(target);
|
||||
}
|
||||
|
||||
public bool Matches(ISerializableEntity target, bool checkContained)
|
||||
private bool MatchesContained(ISerializableEntity? target)
|
||||
{
|
||||
var type = Type;
|
||||
if (type == ConditionType.Uncertain)
|
||||
var containedItems = target switch
|
||||
{
|
||||
type = AfflictionPrefab.Prefabs.ContainsKey(AttributeName)
|
||||
? ConditionType.Affliction
|
||||
: ConditionType.PropertyValue;
|
||||
}
|
||||
|
||||
if (checkContained)
|
||||
Item item
|
||||
=> item.ContainedItems,
|
||||
ItemComponent ic
|
||||
=> ic.Item.ContainedItems,
|
||||
Character {Inventory: { } characterInventory}
|
||||
=> characterInventory.AllItems,
|
||||
_
|
||||
=> Enumerable.Empty<Item>()
|
||||
};
|
||||
foreach (var containedItem in containedItems)
|
||||
{
|
||||
if (target is Item item)
|
||||
{
|
||||
foreach (var containedItem in item.ContainedItems)
|
||||
{
|
||||
if (Matches(containedItem, checkContained: false)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (target is Items.Components.ItemComponent ic)
|
||||
{
|
||||
foreach (var containedItem in ic.Item.ContainedItems)
|
||||
{
|
||||
if (Matches(containedItem, checkContained: false)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (target is Character character)
|
||||
{
|
||||
if (character.Inventory == null) { return false; }
|
||||
foreach (var containedItem in character.Inventory.AllItems)
|
||||
{
|
||||
if (Matches(containedItem, checkContained: false)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (MatchesDirect(containedItem)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
private bool MatchesDirect(ISerializableEntity? target)
|
||||
{
|
||||
Character? targetChar = target as Character;
|
||||
if (target is Limb limb) { targetChar = limb.character; }
|
||||
switch (Type)
|
||||
{
|
||||
case ConditionType.PropertyValue:
|
||||
SerializableProperty property;
|
||||
if (target?.SerializableProperties == null) { return Operator == OperatorType.NotEquals; }
|
||||
if (target.SerializableProperties.TryGetValue(AttributeName, out property))
|
||||
case ConditionType.PropertyValueOrAffliction:
|
||||
// First try checking for a property belonging to the target
|
||||
if (target?.SerializableProperties != null
|
||||
&& target.SerializableProperties.TryGetValue(AttributeName, out var property))
|
||||
{
|
||||
return Matches(target, property);
|
||||
return PropertyMatchesRequirement(target, property);
|
||||
}
|
||||
return false;
|
||||
case ConditionType.Name:
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
return (Operator == OperatorType.Equals) == (target.Name == AttributeValue);
|
||||
// Then try checking for an affliction affecting the target
|
||||
if (targetChar is { CharacterHealth: { } health })
|
||||
{
|
||||
var affliction = health.GetAffliction(AttributeName.ToIdentifier());
|
||||
float afflictionStrength = affliction?.Strength ?? 0f;
|
||||
|
||||
return NumberMatchesRequirement(afflictionStrength);
|
||||
}
|
||||
return ComparisonOperatorIsNotEquals;
|
||||
case ConditionType.SkillRequirement:
|
||||
if (targetChar != null)
|
||||
{
|
||||
float skillLevel = targetChar.GetSkillLevel(AttributeName.ToIdentifier());
|
||||
|
||||
return NumberMatchesRequirement(skillLevel);
|
||||
}
|
||||
return ComparisonOperatorIsNotEquals;
|
||||
case ConditionType.HasTag:
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
return MatchesTagCondition(target);
|
||||
return ItemMatchesTagCondition(target);
|
||||
case ConditionType.HasStatusTag:
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
if (target == null) { return ComparisonOperatorIsNotEquals; }
|
||||
|
||||
// TODO: revisit this. As written, the current behavior is:
|
||||
// - ComparisonOperatorType.Equals: true when any effects have all tags
|
||||
// - ComparisonOperatorType.NotEquals: true when none of the effects have any of the tags
|
||||
int matches = 0;
|
||||
foreach (DurationListElement durationEffect in StatusEffect.DurationList)
|
||||
|
||||
foreach (var durationEffect in StatusEffect.DurationList)
|
||||
{
|
||||
if (!durationEffect.Targets.Contains(target)) { continue; }
|
||||
foreach (string tag in SplitAttributeValue)
|
||||
{
|
||||
if (durationEffect.Parent.HasTag(tag))
|
||||
{
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (StatusEffectMatchesTagCondition(durationEffect.Parent)) { matches++; }
|
||||
}
|
||||
foreach (DelayedListElement delayedEffect in DelayedEffect.DelayList)
|
||||
|
||||
foreach (var delayedEffect in DelayedEffect.DelayList)
|
||||
{
|
||||
if (!delayedEffect.Targets.Contains(target)) { continue; }
|
||||
foreach (string tag in SplitAttributeValue)
|
||||
{
|
||||
if (delayedEffect.Parent.HasTag(tag))
|
||||
{
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (StatusEffectMatchesTagCondition(delayedEffect.Parent)) { matches++; }
|
||||
}
|
||||
return Operator == OperatorType.Equals ? matches >= SplitAttributeValue.Length : matches <= 0;
|
||||
case ConditionType.HasSpecifierTag:
|
||||
{
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
if (!(target is Character { Info: { } characterInfo })) { return false; }
|
||||
|
||||
return (Operator == OperatorType.Equals) ==
|
||||
SplitAttributeValue.All(v => characterInfo.Head.Preset.TagSet.Contains(v));
|
||||
}
|
||||
case ConditionType.SpeciesName:
|
||||
{
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
if (!(target is Character targetCharacter)) { return false; }
|
||||
return (Operator == OperatorType.Equals) == (targetCharacter.SpeciesName == AttributeValue);
|
||||
}
|
||||
case ConditionType.SpeciesGroup:
|
||||
{
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
if (!(target is Character targetCharacter)) { return false; }
|
||||
return (Operator == OperatorType.Equals) == targetCharacter.Params.CompareGroup(AttributeValue.ToIdentifier());
|
||||
}
|
||||
case ConditionType.EntityType:
|
||||
switch (AttributeValue)
|
||||
{
|
||||
case "character":
|
||||
case "Character":
|
||||
return (Operator == OperatorType.Equals) == target is Character;
|
||||
case "limb":
|
||||
case "Limb":
|
||||
return (Operator == OperatorType.Equals) == target is Limb;
|
||||
case "item":
|
||||
case "Item":
|
||||
return (Operator == OperatorType.Equals) == target is Item;
|
||||
case "structure":
|
||||
case "Structure":
|
||||
return (Operator == OperatorType.Equals) == target is Structure;
|
||||
case "null":
|
||||
return (Operator == OperatorType.Equals) == (target == null);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case ConditionType.LimbType:
|
||||
{
|
||||
if (!(target is Limb limb))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return limb.type.ToString().Equals(AttributeValue, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
case ConditionType.Affliction:
|
||||
{
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
|
||||
Character targetChar = target as Character;
|
||||
if (target is Limb limb) { targetChar = limb.character; }
|
||||
if (targetChar != null)
|
||||
{
|
||||
var health = targetChar.CharacterHealth;
|
||||
if (health == null) { return false; }
|
||||
var affliction = health.GetAffliction(AttributeName.ToIdentifier());
|
||||
float afflictionStrength = affliction == null ? 0.0f : affliction.Strength;
|
||||
|
||||
return ValueMatchesRequirement(afflictionStrength);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case ConditionType.SkillRequirement:
|
||||
{
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
|
||||
if (target is Character targetChar)
|
||||
{
|
||||
float skillLevel = targetChar.GetSkillLevel(AttributeName.ToIdentifier());
|
||||
|
||||
return ValueMatchesRequirement(skillLevel);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return ComparisonOperatorIsNotEquals
|
||||
? matches >= StatusEffect.DurationList.Count + DelayedEffect.DelayList.Count
|
||||
: matches > 0;
|
||||
default:
|
||||
return false;
|
||||
bool equals = CheckOnlyEquality(target);
|
||||
return ComparisonOperatorIsNotEquals
|
||||
? !equals
|
||||
: equals;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValueMatchesRequirement(float testedValue)
|
||||
private bool CheckOnlyEquality(ISerializableEntity? target)
|
||||
{
|
||||
if (FloatValue.HasValue)
|
||||
switch (Type)
|
||||
{
|
||||
float value = FloatValue.Value;
|
||||
switch (Operator)
|
||||
case ConditionType.Name:
|
||||
if (target == null) { return false; }
|
||||
|
||||
return target.Name == AttributeValue;
|
||||
case ConditionType.HasSpecifierTag:
|
||||
{
|
||||
case OperatorType.Equals:
|
||||
return testedValue == value;
|
||||
case OperatorType.GreaterThan:
|
||||
return testedValue > value;
|
||||
case OperatorType.GreaterThanEquals:
|
||||
return testedValue >= value;
|
||||
case OperatorType.LessThan:
|
||||
return testedValue < value;
|
||||
case OperatorType.LessThanEquals:
|
||||
return testedValue <= value;
|
||||
case OperatorType.NotEquals:
|
||||
return testedValue != value;
|
||||
if (target is not Character {Info: { } characterInfo})
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return AttributeValueAsTags.All(characterInfo.Head.Preset.TagSet.Contains);
|
||||
}
|
||||
case ConditionType.SpeciesName:
|
||||
{
|
||||
if (target is not Character targetCharacter)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return targetCharacter.SpeciesName == AttributeValue;
|
||||
}
|
||||
case ConditionType.SpeciesGroup:
|
||||
{
|
||||
if (target is not Character targetCharacter)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CharacterParams.CompareGroup(AttributeValue.ToIdentifier(), targetCharacter.Params.Group);
|
||||
}
|
||||
case ConditionType.EntityType:
|
||||
return AttributeValue.ToLowerInvariant() switch
|
||||
{
|
||||
"character"
|
||||
=> target is Character,
|
||||
"limb"
|
||||
=> target is Limb,
|
||||
"item"
|
||||
=> target is Item,
|
||||
"structure"
|
||||
=> target is Structure,
|
||||
"null"
|
||||
=> target == null,
|
||||
_
|
||||
=> false
|
||||
};
|
||||
case ConditionType.LimbType:
|
||||
{
|
||||
return target is Limb limb
|
||||
&& Enum.TryParse(AttributeValue, ignoreCase: true, out LimbType attributeLimbType)
|
||||
&& attributeLimbType == limb.type;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MatchesTagCondition(ISerializableEntity target)
|
||||
private bool SufficientTagMatches(int matches)
|
||||
{
|
||||
if (!(target is Item item)) { return Operator == OperatorType.NotEquals; }
|
||||
|
||||
int matches = 0;
|
||||
foreach (string tag in SplitAttributeValue)
|
||||
{
|
||||
if (item.HasTag(tag))
|
||||
{
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
//If operator is == then it needs to match everything, otherwise if its != there must be zero matches.
|
||||
return Operator == OperatorType.Equals ? matches >= SplitAttributeValue.Length : matches <= 0;
|
||||
return ComparisonOperatorIsNotEquals
|
||||
? matches <= 0
|
||||
: matches >= AttributeValueAsTags.Length;
|
||||
}
|
||||
|
||||
public bool MatchesTagCondition(Identifier targetTag)
|
||||
private bool ItemMatchesTagCondition(ISerializableEntity? target)
|
||||
{
|
||||
if (target is not Item item) { return ComparisonOperatorIsNotEquals; }
|
||||
|
||||
int matches = 0;
|
||||
foreach (var tag in AttributeValueAsTags)
|
||||
{
|
||||
if (item.HasTag(tag)) { matches++; }
|
||||
}
|
||||
return SufficientTagMatches(matches);
|
||||
}
|
||||
|
||||
public bool TargetTagMatchesTagCondition(Identifier targetTag)
|
||||
{
|
||||
if (targetTag.IsEmpty || Type != ConditionType.HasTag) { return false; }
|
||||
|
||||
int matches = 0;
|
||||
foreach (string tag in SplitAttributeValue)
|
||||
foreach (var tag in AttributeValueAsTags)
|
||||
{
|
||||
if (targetTag == tag)
|
||||
{
|
||||
matches++;
|
||||
}
|
||||
if (targetTag == tag) { matches++; }
|
||||
}
|
||||
//If operator is == then it needs to match everything, otherwise if its != there must be zero matches.
|
||||
return Operator == OperatorType.Equals ? matches >= SplitAttributeValue.Length : matches <= 0;
|
||||
return SufficientTagMatches(matches);
|
||||
}
|
||||
|
||||
// TODO: refactor and add tests
|
||||
private bool Matches(ISerializableEntity target, SerializableProperty property)
|
||||
private bool StatusEffectMatchesTagCondition(StatusEffect statusEffect)
|
||||
{
|
||||
int matches = 0;
|
||||
foreach (var tag in AttributeValueAsTags)
|
||||
{
|
||||
if (statusEffect.HasTag(tag.Value)) { matches++; }
|
||||
}
|
||||
return SufficientTagMatches(matches);
|
||||
}
|
||||
|
||||
private bool NumberMatchesRequirement(float testedValue)
|
||||
{
|
||||
if (!FloatValue.HasValue) { return ComparisonOperatorIsNotEquals; }
|
||||
float value = FloatValue.Value;
|
||||
|
||||
return ComparisonOperator switch
|
||||
{
|
||||
ComparisonOperatorType.Equals
|
||||
=> MathUtils.NearlyEqual(testedValue, value),
|
||||
ComparisonOperatorType.NotEquals
|
||||
=> !MathUtils.NearlyEqual(testedValue, value),
|
||||
ComparisonOperatorType.GreaterThan
|
||||
=> testedValue > value,
|
||||
ComparisonOperatorType.GreaterThanEquals
|
||||
=> testedValue >= value,
|
||||
ComparisonOperatorType.LessThan
|
||||
=> testedValue < value,
|
||||
ComparisonOperatorType.LessThanEquals
|
||||
=> testedValue <= value,
|
||||
_
|
||||
=> false
|
||||
};
|
||||
}
|
||||
|
||||
private bool PropertyMatchesRequirement(ISerializableEntity target, SerializableProperty property)
|
||||
{
|
||||
Type type = property.PropertyType;
|
||||
|
||||
if (type == typeof(float) || type == typeof(int))
|
||||
{
|
||||
float floatValue = property.GetFloatValue(target);
|
||||
switch (Operator)
|
||||
{
|
||||
case OperatorType.Equals:
|
||||
return MathUtils.NearlyEqual(floatValue, FloatValue.Value);
|
||||
case OperatorType.NotEquals:
|
||||
return !MathUtils.NearlyEqual(floatValue, FloatValue.Value);
|
||||
case OperatorType.GreaterThan:
|
||||
return floatValue > FloatValue.Value;
|
||||
case OperatorType.LessThan:
|
||||
return floatValue < FloatValue.Value;
|
||||
case OperatorType.GreaterThanEquals:
|
||||
return floatValue >= FloatValue.Value;
|
||||
case OperatorType.LessThanEquals:
|
||||
return floatValue <= FloatValue.Value;
|
||||
}
|
||||
return false;
|
||||
return NumberMatchesRequirement(floatValue);
|
||||
}
|
||||
|
||||
switch (Operator)
|
||||
switch (ComparisonOperator)
|
||||
{
|
||||
case OperatorType.Equals:
|
||||
case ComparisonOperatorType.Equals:
|
||||
case ComparisonOperatorType.NotEquals:
|
||||
bool equals;
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
return property.GetBoolValue(target) == (AttributeValue == "true" || AttributeValue == "True");
|
||||
}
|
||||
var value = property.GetValue(target);
|
||||
return Equals(value, AttributeValue);
|
||||
bool attributeValueBool = AttributeValue.IsTrueString();
|
||||
equals = property.GetBoolValue(target) == attributeValueBool;
|
||||
}
|
||||
case OperatorType.NotEquals:
|
||||
else
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
return property.GetBoolValue(target) != (AttributeValue == "true" || AttributeValue == "True");
|
||||
}
|
||||
var value = property.GetValue(target);
|
||||
return !Equals(value, AttributeValue);
|
||||
equals = AreValuesEquivalent(value, AttributeValue);
|
||||
}
|
||||
case OperatorType.GreaterThan:
|
||||
case OperatorType.LessThanEquals:
|
||||
case OperatorType.LessThan:
|
||||
case OperatorType.GreaterThanEquals:
|
||||
|
||||
return ComparisonOperatorIsNotEquals
|
||||
? !equals
|
||||
: equals;
|
||||
default:
|
||||
DebugConsole.ThrowError("Couldn't compare " + AttributeValue.ToString() + " (" + AttributeValue.GetType() + ") to property \"" + property.Name + "\" (" + type + ")! "
|
||||
+ "Make sure the type of the value set in the config files matches the type of the property.");
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
static bool Equals(object value, string desiredValue)
|
||||
static bool AreValuesEquivalent(object? value, string desiredValue)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
@@ -455,7 +602,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return value.ToString().Equals(desiredValue);
|
||||
return (value.ToString() ?? "").Equals(desiredValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user