using Nexus.Core; namespace Nexus.Data; /// /// Classifies entities from path + component data into EntityCategory. /// Single source of truth for entity classification in the Data layer. /// public static class EntityClassifier { public static EntityCategory Classify(string? path, HashSet? 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 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; } }