poe2-bot/src/Nexus.Data/EntityClassifier.cs
2026-03-06 14:37:05 -05:00

90 lines
3.6 KiB
C#

using Nexus.Core;
namespace Nexus.Data;
/// <summary>
/// Classifies entities from path + component data into EntityCategory.
/// Single source of truth for entity classification in the Data layer.
/// </summary>
public static class EntityClassifier
{
public static EntityCategory Classify(string? path, HashSet<string>? components)
{
var baseCategory = ClassifyFromPath(path);
if (components is { Count: > 0 })
return ReclassifyFromComponents(baseCategory, components);
return baseCategory;
}
private static EntityCategory ClassifyFromPath(string? path)
{
if (path is null) return EntityCategory.Unknown;
var firstSlash = path.IndexOf('/');
if (firstSlash < 0) return EntityCategory.Unknown;
var secondSlash = path.IndexOf('/', firstSlash + 1);
var segment = secondSlash > 0
? path[(firstSlash + 1)..secondSlash]
: path[(firstSlash + 1)..];
switch (segment)
{
case "Characters":
return EntityCategory.Player;
case "Monsters":
if (path.Contains("/Critters/", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Critter;
if (path.Contains("/NPC/", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/TownNPC/", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Npc;
return EntityCategory.Monster;
case "NPC":
return EntityCategory.Npc;
case "Effects":
return EntityCategory.Effect;
case "MiscellaneousObjects":
if (path.Contains("Doodad", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Doodad;
if (path.Contains("/Chest", StringComparison.OrdinalIgnoreCase) ||
path.Contains("/Stash", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Chest;
if (path.Contains("/Shrine", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Shrine;
if (path.Contains("/Portal", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Portal;
return EntityCategory.MiscObject;
case "Terrain":
if (path.Contains("/Doodad", StringComparison.OrdinalIgnoreCase))
return EntityCategory.Doodad;
return EntityCategory.Terrain;
case "Items":
return EntityCategory.WorldItem;
default:
return EntityCategory.Unknown;
}
}
private static EntityCategory ReclassifyFromComponents(EntityCategory baseCategory, HashSet<string> components)
{
// Priority order matching ExileCore's ParseType logic
if (components.Contains("Monster")) return EntityCategory.Monster;
if (components.Contains("Chest")) return EntityCategory.Chest;
if (components.Contains("Shrine")) return EntityCategory.Shrine;
if (components.Contains("Waypoint")) return EntityCategory.Waypoint;
if (components.Contains("AreaTransition")) return EntityCategory.AreaTransition;
if (components.Contains("Portal")) return EntityCategory.Portal;
if (components.Contains("TownPortal")) return EntityCategory.TownPortal;
if (components.Contains("NPC")) return EntityCategory.Npc;
if (components.Contains("Player")) return EntityCategory.Player;
// Don't override path-based classification for Effects/Terrain/etc.
return baseCategory;
}
}