3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Globalization;
42 PropertyValueOrAffliction,
233 foreach (var attribute
in element.Attributes())
235 if (!IsValid(attribute)) {
continue; }
236 if (predicate !=
null && !predicate(attribute)) {
continue; }
238 (
ComparisonOperatorType comparisonOperator,
string attributeValueString) = ExtractComparisonOperatorFromConditionString(attribute.Value);
239 if (
string.IsNullOrWhiteSpace(attributeValueString))
241 DebugConsole.ThrowError($
"Conditional attribute value is empty: {element}", contentPackage: element.ContentPackage);
246 (Enum.TryParse(attribute.Name.LocalName, ignoreCase:
true, out
ConditionType type) ? type :
ConditionType.PropertyValueOrAffliction);
249 attributeName: attribute.NameAsIdentifier(),
250 comparisonOperator: comparisonOperator,
251 attributeValue: attributeValueString,
252 targetItemComponent: targetItemComponent,
253 itemComponentComparison: itemComponentComparison,
254 targetSelf: targetSelf,
255 targetContainer: targetContainer,
256 targetGrandParent: targetGrandParent,
257 targetContainedItem: targetContainedItem,
258 conditionType: conditionType);
262 private static bool IsValid(XAttribute attribute)
264 switch (attribute.Name.ToString().ToLowerInvariant())
266 case "targetitemcomponent":
268 case "targetcontainer":
269 case "targetgrandparent":
270 case "targetcontaineditem":
271 case "skillrequirement":
279 private PropertyConditional(
280 Identifier attributeName,
282 string attributeValue,
283 string targetItemComponent,
286 bool targetContainer,
287 bool targetGrandParent,
288 bool targetContainedItem,
300 Type = conditionType;
305 .Select(s => s.ToIdentifier())
307 if (
float.TryParse(
AttributeValue, NumberStyles.Float, CultureInfo.InvariantCulture, out
float value))
314 cachedHostilityValue = hostilityValue;
323 string conditionStr = str;
324 if (str.IndexOf(
' ') is var i and >= 0)
330 return (op, conditionStr);
336 switch (op.ToLowerInvariant())
358 case "greaterthanequals":
362 case "lessthanequals":
374 ? MatchesContained(target)
375 : MatchesDirect(target);
380 var containedItems = target
switch
389 => Enumerable.Empty<
Item>()
391 foreach (var containedItem
in containedItems)
393 if (MatchesDirect(containedItem)) {
return true; }
398 private bool MatchesDirect(ISerializableEntity? target)
401 if (target is Limb limb) { targetChar = limb.character; }
409 if (targetChar is { CharacterHealth: { } health })
412 float afflictionStrength = affliction?.Strength ?? 0f;
414 return NumberMatchesRequirement(afflictionStrength);
418 else if (target?.SerializableProperties !=
null
419 && target.SerializableProperties.TryGetValue(
AttributeName, out var property))
421 return PropertyMatchesRequirement(target, property);
423 else if (targetChar?.SerializableProperties !=
null
424 && targetChar.SerializableProperties.TryGetValue(
AttributeName, out var characterProperty))
426 return PropertyMatchesRequirement(targetChar, characterProperty);
428 return ComparisonOperatorIsNotEquals;
430 if (targetChar !=
null)
432 float skillLevel = targetChar.GetSkillLevel(
AttributeName.ToIdentifier());
434 return NumberMatchesRequirement(skillLevel);
436 return ComparisonOperatorIsNotEquals;
438 if (targetChar !=
null)
440 return CheckMatchingTags(targetChar.Params.HasTag);
442 if (target is Item item)
444 return CheckMatchingTags(item.HasTag);
446 return ComparisonOperatorIsNotEquals;
448 if (target ==
null) {
return ComparisonOperatorIsNotEquals; }
450 int numTagsFound = 0;
453 bool tagFound =
false;
454 foreach (var durationEffect
in StatusEffect.DurationList)
456 if (!durationEffect.Targets.Contains(target)) {
continue; }
457 if (durationEffect.Parent.HasTag(tag))
465 foreach (var delayedEffect
in DelayedEffect.DelayList)
467 if (!delayedEffect.Targets.Contains(target)) {
continue; }
468 if (delayedEffect.Parent.HasTag(tag))
480 return ComparisonOperatorIsNotEquals
484 if (Level.Loaded is { } level)
486 return NumberMatchesRequirement(level.Difficulty);
490 if (GameMain.GameSession?.Campaign is CampaignMode campaign)
492 return Compare(campaign.Settings.WorldHostility, cachedHostilityValue,
ComparisonOperator);
496 bool equals = CheckOnlyEquality(target);
497 return ComparisonOperatorIsNotEquals
503 private bool CheckOnlyEquality(ISerializableEntity? target)
508 if (target ==
null) {
return false; }
513 if (target is not Character {Info: { } characterInfo})
522 if (target is Character targetCharacter)
526 else if (target is Limb targetLimb)
534 if (target is Character targetCharacter)
536 return CharacterParams.CompareGroup(
AttributeValue.ToIdentifier(), targetCharacter.Params.Group);
538 else if (target is Limb targetLimb)
540 return CharacterParams.CompareGroup(
AttributeValue.ToIdentifier(), targetLimb.character.Params.Group);
562 return target is Limb limb
563 && Enum.TryParse(
AttributeValue, ignoreCase:
true, out LimbType attributeLimbType)
564 && attributeLimbType == limb.type;
570 private bool SufficientTagMatches(
int matches)
572 return ComparisonOperatorIsNotEquals
577 private bool CheckMatchingTags(Func<Identifier, bool> predicate)
582 if (predicate(tag)) { matches++; }
584 return SufficientTagMatches(matches);
590 return CheckMatchingTags(targetTag.Equals);
593 private bool NumberMatchesRequirement(
float testedValue)
595 if (!
FloatValue.HasValue) {
return ComparisonOperatorIsNotEquals; }
600 private bool PropertyMatchesRequirement(ISerializableEntity target, SerializableProperty property)
602 Type type =
property.PropertyType;
604 if (type == typeof(
float) || type == typeof(
int))
606 float floatValue =
property.GetFloatValue(target);
607 return NumberMatchesRequirement(floatValue);
615 if (type == typeof(
bool))
618 equals =
property.GetBoolValue(target) == attributeValueBool;
622 var value =
property.GetValue(target);
626 return ComparisonOperatorIsNotEquals
630 DebugConsole.ThrowError(
"Couldn't compare " +
AttributeValue.ToString() +
" (" +
AttributeValue.GetType() +
") to property \"" + property.Name +
"\" (" + type +
")! "
631 +
"Make sure the type of the value set in the config files matches the type of the property.");
635 static bool AreValuesEquivalent(
object? value,
string desiredValue)
639 return desiredValue.Equals(
"null", StringComparison.OrdinalIgnoreCase);
643 return (value.ToString() ??
"").Equals(desiredValue);
653 return MathUtils.NearlyEqual(val1, val2);
663 return !MathUtils.NearlyEqual(val1, val2);
671 return comparisonOperator
switch
678 _ => leftValue.CompareTo(rightValue) == 0,
688 if (conditionalElements.None()) {
return default; }
689 List<PropertyConditional> conditionals =
new();
694 var logicalOperator = element.GetAttributeEnum(
"comparison", defaultOperatorType);
700 if (conditionals ==
null) {
return true; }
701 if (conditionals.None()) {
return true; }
702 switch (logicalOperator)
705 foreach (var conditional
in conditionals)
707 if (!conditional.Matches(conditionalTarget))
716 foreach (var conditional
in conditionals)
718 if (conditional.Matches(conditionalTarget))
727 throw new NotSupportedException();
string? GetAttributeString(string key, string? def)
IEnumerable< ContentXElement > GetChildElements(string name)
bool GetAttributeBool(string key, bool def)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
IEnumerable< Item > ContainedItems
The base class for components holding the different functionalities of the item
Bundles up a bunch of conditionals with a logical operator.
readonly LogicalOperatorType LogicalOperator
LogicalComparison(IEnumerable< PropertyConditional > conditionals, LogicalOperatorType logicalOperator)
readonly ImmutableArray< PropertyConditional > Conditionals
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
readonly ComparisonOperatorType ComparisonOperator
bool Matches(ISerializableEntity? target)
static bool CheckConditionals(ISerializableEntity conditionalTarget, IEnumerable< PropertyConditional > conditionals, LogicalOperatorType logicalOperator)
readonly bool TargetContainer
If set to true, the conditionals defined by this element check against the entity containing the targ...
bool TargetTagMatchesTagCondition(Identifier targetTag)
static ComparisonOperatorType GetComparisonOperatorType(string op)
readonly string AttributeValue
readonly bool TargetGrandParent
If this and TargetContainer are set to true, the conditionals defined by this element check against t...
readonly string TargetItemComponent
If set to the name of one of the target's ItemComponents, the conditionals defined by this element ch...
static bool Compare< T >(T leftValue, T rightValue, ComparisonOperatorType comparisonOperator)
static ? LogicalComparison LoadConditionals(ContentXElement element, LogicalOperatorType defaultOperatorType=LogicalOperatorType.And)
Seeks for child elements of name "conditional" and bundles them with an attribute of name "comparison...
ComparisonOperatorType
There are several ways to compare properties to values. The comparison operator to use can be specifi...
readonly ImmutableArray< Identifier > AttributeValueAsTags
readonly LogicalOperatorType ItemComponentComparison
When targeting item components, should we require them all to match the conditional or any (default).
readonly bool TargetContainedItem
If set to true, the conditionals defined by this element check against the items contained by the tar...
static bool CompareFloat(float val1, float val2, ComparisonOperatorType op)
readonly? float FloatValue
readonly bool TargetSelf
If set to true, the conditionals defined by this element check against the attacking character instea...
readonly ConditionType Type
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
readonly Identifier AttributeName
static ComparisonOperatorType ComparisonOperator
ConditionType
Category of properties to check against
@ Character
Characters only
@ Structure
Structures and hulls, but also items (for backwards support)!