feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -20,6 +20,13 @@ internal sealed class MossTankPanel
/// </summary>
private const double StallTimeoutSeconds = 30.0;
/// <summary>
/// The retained markup host reads label bindings while drawing. Coverage
/// walks the complete known-buff table, so it belongs on the update side
/// and only needs to refresh at human-readable cadence.
/// </summary>
private const double CoverageRefreshIntervalSeconds = 1.0;
private readonly IPluginHost _host;
private readonly BuffSettings _buffSettings = new();
private readonly VitalSettings _vitalSettings = new();
@ -34,6 +41,18 @@ internal sealed class MossTankPanel
private double _sinceProgress;
private int _castThisPass;
private string _status = "Idle.";
private string _vitals = string.Empty;
private string _coverage = string.Empty;
private bool _vitalsInitialized;
private uint _currentHealth;
private uint _maxHealth;
private uint _currentStamina;
private uint _maxStamina;
private uint _currentMana;
private uint _maxMana;
private IReadOnlyList<PluginSpellInfo>? _coverageSpellSnapshot;
private int _coverageBuffLineCount;
private double _coverageRefreshRemaining;
/// <summary>
/// What the player had selected before the pass, so targeting yourself for
@ -59,42 +78,10 @@ internal sealed class MossTankPanel
public string Status => _status;
/// <summary>Vitals line, using the same numbers the character panel shows.</summary>
public string Vitals
{
get
{
ICharacterInfo character = _host.Automation.Character;
if (!_host.Automation.IsAvailable)
return string.Empty;
return $"Health {character.CurrentHealth}/{character.MaxHealth}"
+ $" Stam {character.CurrentStamina}/{character.MaxStamina}"
+ $" Mana {character.CurrentMana}/{character.MaxMana}";
}
}
public string Vitals => _vitals;
/// <summary>What a buff pass would cover, named from the retail tables.</summary>
public string Coverage
{
get
{
IAutomationSurface automation = _host.Automation;
if (!automation.IsAvailable)
return string.Empty;
int trained = 0;
foreach (PluginSkillInfo skill in automation.Character.Skills)
{
if (skill.Training is PluginSkillTraining.Trained
or PluginSkillTraining.Specialized)
{
trained++;
}
}
return $"{automation.Character.Attributes.Count} attributes, "
+ $"{trained} trained skills, "
+ $"{BuffProfile.Build(automation.Spells.KnownSelfBuffs).Count} buff lines";
}
}
public string Coverage => _coverage;
// ── settings bindings ─────────────────────────────────────────────────
// Adjuster buttons rather than typed entry: buttons are a proven primitive
@ -251,6 +238,8 @@ internal sealed class MossTankPanel
/// <summary>Driven by <see cref="IEvents.Tick"/> on the host update thread.</summary>
public void OnTick(double elapsedSeconds)
{
RefreshDisplayBindings(elapsedSeconds);
if (!_running)
return;
@ -295,6 +284,74 @@ internal sealed class MossTankPanel
_queueIndex++;
}
private void RefreshDisplayBindings(double elapsedSeconds)
{
IAutomationSurface automation = _host.Automation;
if (!automation.IsAvailable)
{
_vitals = string.Empty;
_coverage = string.Empty;
_vitalsInitialized = false;
_coverageSpellSnapshot = null;
_coverageBuffLineCount = 0;
_coverageRefreshRemaining = 0.0;
return;
}
ICharacterInfo character = automation.Character;
uint currentHealth = character.CurrentHealth;
uint maxHealth = character.MaxHealth;
uint currentStamina = character.CurrentStamina;
uint maxStamina = character.MaxStamina;
uint currentMana = character.CurrentMana;
uint maxMana = character.MaxMana;
if (!_vitalsInitialized
|| currentHealth != _currentHealth
|| maxHealth != _maxHealth
|| currentStamina != _currentStamina
|| maxStamina != _maxStamina
|| currentMana != _currentMana
|| maxMana != _maxMana)
{
_currentHealth = currentHealth;
_maxHealth = maxHealth;
_currentStamina = currentStamina;
_maxStamina = maxStamina;
_currentMana = currentMana;
_maxMana = maxMana;
_vitals = $"Health {currentHealth}/{maxHealth}"
+ $" Stam {currentStamina}/{maxStamina}"
+ $" Mana {currentMana}/{maxMana}";
_vitalsInitialized = true;
}
IReadOnlyList<PluginSpellInfo> spells = automation.Spells.KnownSelfBuffs;
bool spellbookChanged = !ReferenceEquals(spells, _coverageSpellSnapshot);
_coverageRefreshRemaining -= Math.Max(0.0, elapsedSeconds);
if (!spellbookChanged && _coverageRefreshRemaining > 0.0)
return;
if (spellbookChanged)
{
_coverageSpellSnapshot = spells;
_coverageBuffLineCount = BuffProfile.Build(spells).Count;
}
int trained = 0;
foreach (PluginSkillInfo skill in character.Skills)
{
if (skill.Training is PluginSkillTraining.Trained
or PluginSkillTraining.Specialized)
{
trained++;
}
}
_coverage = $"{character.Attributes.Count} attributes, "
+ $"{trained} trained skills, "
+ $"{_coverageBuffLineCount} buff lines";
_coverageRefreshRemaining = CoverageRefreshIntervalSeconds;
}
private bool TryVitalUpkeep(IAutomationSurface automation)
{
VitalAction action = VitalPlan.Decide(automation.Character, _vitalSettings);