namespace AcDream.Plugin.Abstractions; /// How far a character has taken a skill. public enum PluginSkillTraining { Unknown = 0, Untrained, Trained, Specialized, } /// /// One spell, as much of it as a plugin needs to make its own decisions. /// /// /// Deliberately data, not policy. The host publishes what the spell /// table says; the plugin decides what to cast and when. That line is the whole /// architectural point of this surface — a Virindi-Tank-class engine belongs in /// plugin-land, built on host primitives, exactly as VTank itself was built on /// Decal's. /// /// /// Retail's stacking bucket, and the correct identity for a duration /// buff line. It is NOT a safe identity in general: the instantaneous vital /// transfers share a family per source vital, so family 89 holds both /// "Stamina to Health" and "Stamina to Mana". Group by family only after /// filtering to duration buffs. /// /// /// Retail's spell Generation — the roman-numeral level. Higher is /// stronger within a family. /// /// /// Skill id of the magic school that casts this spell, so a plugin can weigh /// the character's skill in that school against . /// /// /// Retail's own spell description. Load-bearing rather than cosmetic: the /// client's spell table carries no link between a spell and the stat it /// raises — that arrives from the server with the enchantment — but the /// description states it in words ("Increases the caster's Life Magic skill by /// 10 points"), so a plugin can derive the mapping from shipped data instead of /// hard-coding one. /// public readonly record struct PluginSpellInfo( uint SpellId, string Name, uint Family, int Tier, int Difficulty, int ManaCost, float DurationSeconds, uint School, string Description, bool IsSelfTargeted, bool IsBeneficial); /// One enchantment currently in force on the local player. public readonly record struct PluginActiveEnchantment( uint SpellId, uint Family, int Tier, double SecondsRemaining); /// One of the character's skills, named from the retail skill table. public readonly record struct PluginSkillInfo( uint SkillId, string Name, PluginSkillTraining Training, uint Current); /// One primary attribute. is 0..5. public readonly record struct PluginAttributeInfo( int Kind, string Name, uint Current); /// Why a cast would or would not be accepted right now. public enum PluginCastGate { /// No live session, or the surface is not bound yet. Unavailable = 0, Ready, NotKnown, /// A cast is already in flight. Busy, /// The host rejected it for a reason not modelled here. Refused, } /// Local-player reads a plugin needs to decide what to cast. public interface ICharacterInfo { bool IsInWorld { get; } uint CurrentHealth { get; } uint MaxHealth { get; } uint CurrentStamina { get; } uint MaxStamina { get; } uint CurrentMana { get; } uint MaxMana { get; } /// Skills the character has, with training state and current level. IReadOnlyList Skills { get; } /// The six primary attributes. IReadOnlyList Attributes { get; } /// /// Enchantments in force on the local player. Snapshot semantics: the list /// is rebuilt by the host, never mutated in place under a reader. /// IReadOnlyList ActiveEnchantments { get; } bool TryGetSkill(uint skillId, out PluginSkillInfo skill); } /// Spell-table data, filtered to what the local character knows. public interface ISpellCatalog { /// /// Every spell in the character's spellbook that targets self and is /// beneficial. /// IReadOnlyList KnownSelfBuffs { get; } bool TryGet(uint spellId, out PluginSpellInfo info); } /// Casting, with a preflight so a plugin need not guess. public interface IMagicCommands { bool IsCasting { get; } PluginCastGate EvaluateGate(uint spellId); /// /// Request a cast. Returns whether the request was accepted for dispatch — /// not whether the spell ultimately lands, which the server decides. /// bool Cast(uint spellId); } /// /// The automation surface: reads, spell data, and commands, grouped so /// grows by one member rather than several. /// public interface IAutomationSurface { /// /// on hosts that never bind a live session, and /// while a graphical host is between sessions — including at character /// select, which is what lets a plugin panel stay hidden until there is a /// character to act on. /// bool IsAvailable { get; } ICharacterInfo Character { get; } ISpellCatalog Spells { get; } IMagicCommands Magic { get; } } /// /// BCL-only inert surface for hosts with no live session. Every read is empty /// and every command refuses, so a plugin can keep one code path. /// public sealed class NoOpAutomationSurface : IAutomationSurface, ICharacterInfo, ISpellCatalog, IMagicCommands { public static NoOpAutomationSurface Instance { get; } = new(); private NoOpAutomationSurface() { } public bool IsAvailable => false; public ICharacterInfo Character => this; public ISpellCatalog Spells => this; public IMagicCommands Magic => this; public bool IsInWorld => false; public uint CurrentHealth => 0; public uint MaxHealth => 0; public uint CurrentStamina => 0; public uint MaxStamina => 0; public uint CurrentMana => 0; public uint MaxMana => 0; public IReadOnlyList Skills { get; } = Array.Empty(); public IReadOnlyList Attributes { get; } = Array.Empty(); public IReadOnlyList ActiveEnchantments { get; } = Array.Empty(); public IReadOnlyList KnownSelfBuffs { get; } = Array.Empty(); public bool TryGetSkill(uint skillId, out PluginSkillInfo skill) { skill = default; return false; } public bool TryGet(uint spellId, out PluginSpellInfo info) { info = default; return false; } public bool IsCasting => false; public PluginCastGate EvaluateGate(uint spellId) => PluginCastGate.Unavailable; public bool Cast(uint spellId) => false; }