Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StatusEffect Recursive Heirarchy Search #14606

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,7 @@ public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 wor
}
if (target is Entity targetEntity)
{
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (effect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
effect.AddNearbyTargets(worldPosition, targets);
Expand All @@ -592,7 +591,7 @@ public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 wor
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
targets.Clear();
targets.AddRange(attacker.Inventory.AllItems);
targets.AddRange(effect.GetContainedItems(attacker));
if (additionalEffectType != ActionType.OnEating)
{
effect.Apply(conditionalEffectType, deltaTime, attacker, targets);
Expand Down Expand Up @@ -668,8 +667,7 @@ public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2
effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targets);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (effect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
effect.AddNearbyTargets(worldPosition, targets);
Expand All @@ -679,7 +677,7 @@ public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
targets.Clear();
targets.AddRange(attacker.Inventory.AllItems);
targets.AddRange(effect.GetContainedItems(attacker));
effect.Apply(conditionalEffectType, deltaTime, attacker, targets);
effect.Apply(ActionType.OnUse, deltaTime, attacker, targets);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4673,8 +4673,7 @@ public void ApplyStatusEffects(ActionType actionType, float deltaTime)
}
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
statusEffect.AddNearbyTargets(WorldPosition, targets);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,7 @@ public void ApplyStatusEffect(ActionType type, StatusEffect statusEffect, float
{
statusEffect.Apply(type, deltaTime, characterHealth.Character, targets: characterHealth.Character.AnimController.Limbs);
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
statusEffect.AddNearbyTargets(characterHealth.Character.WorldPosition, targets);
Expand Down
3 changes: 1 addition & 2 deletions Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1279,8 +1279,7 @@ public void ApplyStatusEffects(ActionType actionType, float deltaTime)
}
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
statusEffect.AddNearbyTargets(WorldPosition, targets);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Barotrauma.Extensions
{
public static class GenericExtensions
{
public static IEnumerable<T> SelectManyRecursive<T>(this T source, Func<T, T> selector)
{
IEnumerable<T> result = new List<T>();
T child = selector.Invoke(source);

while (child != null)
{
result.Append(child);
child = selector.Invoke(child);
}
return result;
}

public static IEnumerable<T> SelectManyRecursive<T>(this T source, Func<T, T> selector, int maxDepth, int minDepth = 1)
{
minDepth = Math.Max(minDepth, 1);
maxDepth = Math.Max(maxDepth, minDepth);

IEnumerable<T> result = new List<T>();
T child = selector.Invoke(source);

int depth = 1;
while (child != null && depth <= maxDepth)
{
if (depth >= minDepth) { result.Append(child); }
child = selector.Invoke(child);
depth++;
}
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,22 @@ public static IEnumerable<T> ToEnumerable<T>(this T item)
// source: https://stackoverflow.com/questions/19237868/get-all-children-to-one-list-recursive-c-sharp
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
var result = source.SelectMany(selector);
if (!result.Any())
IEnumerable<T> result = source.SelectMany(selector);
return result.Any() ? result.Concat(result.SelectManyRecursive(selector)) : result;
}

public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector, int maxDepth, int minDepth = 1)
{
minDepth = Math.Max(minDepth, 1);
maxDepth = Math.Max(maxDepth, minDepth);

return RecursiveSearch(source, selector, minDepth, maxDepth, 1);

static IEnumerable<T> RecursiveSearch(IEnumerable<T> source, Func<T, IEnumerable<T>> selector, int minDepth, int maxDepth, int depth)
{
return result;
IEnumerable<T> result = source.SelectMany(selector);
return result.Any() && depth <= maxDepth ? depth < minDepth ? RecursiveSearch(result, selector, minDepth, maxDepth, depth + 1) : result.Concat(RecursiveSearch(result, selector, minDepth, maxDepth, depth + 1)) : result;
}
return result.Concat(result.SelectManyRecursive(selector));
}

public static void AddIfNotNull<T>(this IList<T> source, T value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,14 +673,13 @@ void Inject(Item item)
}
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
targets.AddRange(contained.AllPropertyObjects);
targets.AddRange(effect.GetContainedItems(contained, true).SelectMany(item => item.AllPropertyObjects));
}
if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
{
targets.Add(character);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (effect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
effect.AddNearbyTargets(item.WorldPosition, targets);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1111,8 +1111,7 @@ private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal,
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (effect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
Expand Down
20 changes: 8 additions & 12 deletions Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1979,26 +1979,19 @@ public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaT

if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
foreach (Item containedItem in ContainedItems)
foreach (Item containedItem in effect.GetContainedItems(this))
{
if (effect.TargetIdentifiers != null &&
!effect.TargetIdentifiers.Contains(((MapEntity)containedItem).Prefab.Identifier) &&
!effect.TargetIdentifiers.Any(id => containedItem.HasTag(id)))
if (effect.TargetIdentifiers != null && !effect.TargetIdentifiers.Contains(containedItem.Prefab.Identifier) && !effect.TargetIdentifiers.Any(containedItem.HasTag))
{
continue;
}

if (effect.TargetSlot > -1)
{
if (!OwnInventory.GetItemsAt(effect.TargetSlot).Contains(containedItem)) { continue; }
}

hasTargets = true;
targets.Add(containedItem);
}
}

if (effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters) || effect.HasTargetType(StatusEffect.TargetType.NearbyItems))
if (effect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
effect.AddNearbyTargets(WorldPosition, targets);
if (targets.Count > 0)
Expand Down Expand Up @@ -2058,8 +2051,11 @@ public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaT
targets.Add(limb);
}

if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.AddRange(Container.AllPropertyObjects); }

if (effect.HasTargetType(StatusEffect.TargetType.Parent))
{
targets.AddRange(effect.GetParentItems(this).SelectMany(item => item.AllPropertyObjects));
}

effect.Apply(type, deltaTime, this, targets, worldPosition);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -662,16 +662,9 @@ public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2
if (triggerer is Character character)
{
effect.Apply(effect.type, deltaTime, triggerer, character, position);
if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
foreach (Item item in character.Inventory.AllItemsMod)
{
if (item.ContainedItems == null) { continue; }
foreach (Item containedItem in item.ContainedItems)
{
effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
}
}
effect.Apply(effect.type, deltaTime, triggerer, effect.GetContainedItems(character).SelectMany(item => item.AllPropertyObjects).ToList(), position);
}
}
else if (triggerer is Item item)
Expand All @@ -682,7 +675,7 @@ public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2
{
effect.Apply(effect.type, deltaTime, sub, Array.Empty<ISerializableEntity>(), position);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
if (effect.HasTargetType(StatusEffect.TargetType.NearbyEntities))
{
targets.Clear();
effect.AddNearbyTargets(worldPosition, targets);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ public enum TargetType
/// <summary>
/// Last limb of the character the effect is being used on.
/// </summary>
LastLimb = 1024
LastLimb = 1024,
/// <summary>
/// Same as NearbyCharacters and NearbyItems.
/// </summary>
NearbyEntities = NearbyCharacters | NearbyItems
}

/// <summary>
Expand Down Expand Up @@ -531,6 +535,15 @@ public AITrigger(XElement element)
/// </summary>
public int TargetSlot = -1;

/// <summary>
/// Whether to search recursively when finding parent/child items.
/// </summary>
private readonly bool RecursiveSearch;
/// <summary>
/// Controls the depth at which the recursive search will take place. Requires <see cref="RecursiveSearch"/>.
/// </summary>
private readonly int MinSearchDepth, MaxSearchDepth;

private readonly List<RelatedItem> requiredItems;

public readonly ImmutableArray<(Identifier propertyName, object value)> PropertyEffects;
Expand Down Expand Up @@ -807,6 +820,9 @@ protected StatusEffect(ContentXElement element, string parentDebugName)

TargetItemComponent = element.GetAttributeString("targetitemcomponent", string.Empty);
TargetSlot = element.GetAttributeInt("targetslot", -1);
RecursiveSearch = element.GetAttributeBool("recursive", element.GetAttributeBool("recursivesearch", false));
MinSearchDepth = element.GetAttributeInt("minsearchdepth", 1);
MaxSearchDepth = element.GetAttributeInt("maxsearchdepth", int.MaxValue);

Range = element.GetAttributeFloat("range", 0.0f);
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
Expand Down Expand Up @@ -880,6 +896,8 @@ protected StatusEffect(ContentXElement element, string parentDebugName)
case "targetlimb":
case "delay":
case "interval":
case "recursivesearch":
case "recursive":
//aliases for fields we're already reading above, and which shouldn't be interpreted as values we're trying to set
break;
case "allowedafflictions":
Expand All @@ -903,7 +921,7 @@ protected StatusEffect(ContentXElement element, string parentDebugName)
DebugConsole.ThrowError($"Error in StatusEffect ({parentDebugName}): sounds should be defined as child elements of the StatusEffect, not as attributes.", contentPackage: element.ContentPackage);
break;
case "range":
if (!HasTargetType(TargetType.NearbyCharacters) && !HasTargetType(TargetType.NearbyItems))
if (!HasTargetType(TargetType.NearbyEntities))
{
propertyAttributes.Add(attribute);
}
Expand Down Expand Up @@ -1267,6 +1285,40 @@ bool CheckDistance(ISpatialEntity e)
}
}

public List<Item> GetContainedItems(Character character) => (TargetSlot > -1 ? character.Inventory.GetItemsAt(TargetSlot) : character.Inventory.AllItems).SelectMany(item => GetContainedItems(item, true)).ToList();

public List<Item> GetContainedItems(Item item, bool isChild = false)
{
List<Item> targets = new List<Item>();
IEnumerable<Item> children = isChild ? new List<Item> { item } : (TargetSlot > -1 ? item.OwnInventory.GetItemsAt(TargetSlot) : item.ContainedItems);

if (HasTargetType(TargetType.Contained))
{
targets.AddRange(children);
if (RecursiveSearch)
{
targets.AddRange(children.SelectManyRecursive(item => item.ContainedItems, MinSearchDepth, MaxSearchDepth));
}
}
return targets;
}

public List<Item> GetParentItems(Item item)
{
List<Item> targets = new List<Item>();
Item parent = item.Container;

if (HasTargetType(TargetType.Parent) && parent != null)
{
targets.Add(parent);
if (RecursiveSearch)
{
targets.AddRange(parent.SelectManyRecursive(item => item.Container, MaxSearchDepth, MinSearchDepth));
}
}
return targets;
}

public bool HasRequiredConditions(IReadOnlyList<ISerializableEntity> targets)
{
return HasRequiredConditions(targets, propertyConditionals);
Expand Down