132 lines
3.9 KiB
C#
132 lines
3.9 KiB
C#
namespace Roboto.Memory;
|
|
|
|
public enum EntityType
|
|
{
|
|
Unknown,
|
|
Player,
|
|
Monster,
|
|
Npc,
|
|
Effect,
|
|
WorldItem,
|
|
MiscellaneousObject,
|
|
Terrain,
|
|
Critter,
|
|
Chest,
|
|
Shrine,
|
|
Portal,
|
|
TownPortal,
|
|
Waypoint,
|
|
AreaTransition,
|
|
Door,
|
|
Doodad,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raw entity data read from process memory. No business logic or classification —
|
|
/// type classification lives in EntityReader (Memory-internal) and EntityClassifier (Data layer).
|
|
/// </summary>
|
|
public class Entity
|
|
{
|
|
public nint Address { get; }
|
|
public uint Id { get; }
|
|
public string? Path { get; }
|
|
public string? Metadata { get; }
|
|
|
|
// Position (from Render component)
|
|
public bool HasPosition { get; internal set; }
|
|
public float X { get; internal set; }
|
|
public float Y { get; internal set; }
|
|
public float Z { get; internal set; }
|
|
|
|
// Vitals (from Life component — only populated when explicitly read)
|
|
public bool HasVitals { get; internal set; }
|
|
public int LifeCurrent { get; internal set; }
|
|
public int LifeTotal { get; internal set; }
|
|
public int ManaCurrent { get; internal set; }
|
|
public int ManaTotal { get; internal set; }
|
|
public int EsCurrent { get; internal set; }
|
|
public int EsTotal { get; internal set; }
|
|
|
|
// Component info
|
|
public int ComponentCount { get; internal set; }
|
|
public HashSet<string>? Components { get; internal set; }
|
|
|
|
// Component-based properties (populated by GameMemoryReader)
|
|
public bool IsTargetable { get; internal set; }
|
|
public bool IsOpened { get; internal set; }
|
|
public bool IsAvailable { get; internal set; }
|
|
public int Rarity { get; internal set; }
|
|
|
|
// Mods (from Mods component)
|
|
public List<string>? ModNames { get; internal set; }
|
|
|
|
// AreaTransition destination (raw area ID, e.g. "G1_4")
|
|
public string? TransitionName { get; internal set; }
|
|
public int TransitionState { get; internal set; } = -1;
|
|
|
|
// Action state (from Actor component)
|
|
public short ActionId { get; internal set; }
|
|
public bool IsAttacking { get; internal set; }
|
|
public bool IsMoving { get; internal set; }
|
|
|
|
// Classification (set by EntityReader)
|
|
public EntityType Type { get; internal set; }
|
|
|
|
// Derived properties
|
|
public bool IsAlive => HasVitals && LifeCurrent > 0;
|
|
public bool IsDead => HasVitals && LifeCurrent <= 0;
|
|
public bool HasComponent(string name) => Components?.Contains(name) == true;
|
|
|
|
/// <summary>
|
|
/// Grid-plane distance to another point (ignores Z).
|
|
/// </summary>
|
|
public float DistanceTo(float px, float py)
|
|
{
|
|
var dx = X - px;
|
|
var dy = Y - py;
|
|
return MathF.Sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grid-plane distance to another entity.
|
|
/// </summary>
|
|
public float DistanceTo(Entity other) => DistanceTo(other.X, other.Y);
|
|
|
|
/// <summary>
|
|
/// Short category string derived from path (e.g. "Monsters", "Effects", "NPC").
|
|
/// </summary>
|
|
public string PathCategory
|
|
{
|
|
get
|
|
{
|
|
if (Path is null) return "?";
|
|
var parts = Path.Split('/');
|
|
return parts.Length >= 2 ? parts[1] : "?";
|
|
}
|
|
}
|
|
|
|
internal Entity(nint address, uint id, string? path)
|
|
{
|
|
Address = address;
|
|
Id = id;
|
|
Path = path;
|
|
Metadata = ExtractMetadata(path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Strips the "@N" instance suffix from the path.
|
|
/// "Metadata/Monsters/Wolves/RottenWolf1_@2" → "Metadata/Monsters/Wolves/RottenWolf1_"
|
|
/// </summary>
|
|
private static string? ExtractMetadata(string? path)
|
|
{
|
|
if (path is null) return null;
|
|
var atIndex = path.LastIndexOf('@');
|
|
return atIndex > 0 ? path[..atIndex] : path;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var pos = HasPosition ? $"({X:F0},{Y:F0})" : "no pos";
|
|
return $"[{Id}] {Type} {Path ?? "?"} {pos}";
|
|
}
|
|
}
|