Item I (slice-1 fix round, finishing item 1). CaptureMatchesDeclaredSettingTypeAndValue's ~1.5e-7 float-round-trip tolerance was masking a real cause, not a real value change: every tDouble-declared distance setting whose live field was actually a float (CombatSettings.MaximumRange/MinimumRange/ ApproachDistance/RingDistance/ArcRange/TargetSelectAngleRange/ PetCustomRange/CollisionProjectileRadius/CollisionStepDistance/ SpellRangeFudge, Looting.CorpseApproachRange/CorpseMinimumApproachRange, VitalPlan.HelperHealthDistance/HelperStaminaDistance/HelperManaDistance) lost precision below float's ~7-significant-digit guarantee on every load, since a loaded .usd's real value is a double. - All 15 fields widened from float to double, matching their declared tDouble type. Every physics/combat call site that genuinely needs a float (IProjectileAutomation.EvaluatePath/EvaluatePathWithDiagnostics, ICombatAutomation.CaptureHostileTargets/CaptureCorpses, the fellow-distance Lowest() helper) now casts explicitly at that one use site (CombatController.cs, Looting.cs, VitalRecharge.cs, MossTankCommands.cs, PetAutomation.cs) instead of the field itself being narrowed everywhere it's stored. - MossTankProfileStore's JSON DTOs (InventoryProfileDocument. CorpseApproachRange, CombatProfileDocument.MaximumRange/ ApproachDistance/TargetSelectAngleRange/ArcRange/RingDistance/ PetCustomRange) widened to match, so MossTank's own persisted profiles keep full precision too — their Apply()-side Math.Clamp calls needed no changes (the float literal bounds like 2f/100f already widen to the double overload implicitly). - VtankSettingsProfileSerializer.Apply's `(float)(cell.AsDouble() * 240d)` casts and `cell.AsFloat()` calls for these 15 settings are now plain `cell.AsDouble()` / `cell.AsDouble() * 240d` — no narrowing at all. - ValuesEqual's "d"/"f" branch dropped DoublesEqual/FloatRoundTripTolerance entirely: `a.AsDouble() == b.AsDouble()`, exact, matching every other tag. CaptureMatchesDeclaredSettingTypeAndValue's own test-side tolerance (a second, independently-tolerant comparison) removed the same way — all 135 catalog names now pass under Assert.Equal(exact) with zero special-casing. Full MossTank suite: 581/581 (no count change — this is a precision fix, not new coverage; CaptureMatchesDeclaredSettingTypeAndValue's own 135 cases already existed and now pass exactly instead of within tolerance). App.Tests (Plugin|LaunchOptions filter): 84/84. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
173 lines
6.6 KiB
C#
173 lines
6.6 KiB
C#
using AcDream.Plugin.Abstractions;
|
|
|
|
namespace AcDream.Plugins.MossTank;
|
|
|
|
internal enum TargetSelectionMethod
|
|
{
|
|
Range,
|
|
Angle,
|
|
Both,
|
|
}
|
|
|
|
internal enum DebuffEachFirst
|
|
{
|
|
One = 1,
|
|
Priority = 2,
|
|
All = 3,
|
|
}
|
|
|
|
internal enum DebuffSelectionMethod
|
|
{
|
|
SpellLevel = 1,
|
|
Skill = 2,
|
|
}
|
|
|
|
/// <summary>
|
|
/// VTank's real 3-way "UseArcs" enum
|
|
/// (<c>refs/vtank/decompiled/hi.cs:515-538</c>, switch on
|
|
/// <c>f3.f("UseArcs")</c>): <c>No</c> always picks the direct-shape spell,
|
|
/// <c>AtRange</c> picks the arc shape only once the target is at or beyond
|
|
/// <c>ArcRange</c>, and <c>Yes</c> always picks the arc shape regardless of
|
|
/// distance. A prior port collapsed this onto a single bool (effectively
|
|
/// only distinguishing "No" from "AtRange"), which cannot represent "Yes"
|
|
/// at all — see <see cref="AttackSpellCatalog"/>.
|
|
/// </summary>
|
|
internal enum UseArcsMode
|
|
{
|
|
No = 1,
|
|
AtRange = 2,
|
|
Yes = 3,
|
|
}
|
|
|
|
internal enum PetRangeMode
|
|
{
|
|
AttackDistance = 0,
|
|
Custom = 1,
|
|
}
|
|
|
|
internal enum ConsumableCategory
|
|
{
|
|
Other,
|
|
HealthKit,
|
|
HealthFood,
|
|
StaminaKit,
|
|
StaminaFood,
|
|
ManaKit,
|
|
ManaFood,
|
|
Pea,
|
|
AllPeas,
|
|
Lockpick,
|
|
}
|
|
|
|
internal sealed class CombatSettings
|
|
{
|
|
/// <summary>
|
|
/// VTank's EnableCombat profile option. This is deliberately separate
|
|
/// from the panel's Run Macro state: a running macro may navigate, loot,
|
|
/// buff, or execute Meta rules while combat itself is disabled.
|
|
/// </summary>
|
|
public bool Enabled { get; set; } = true;
|
|
/// <summary>VTank's hunt-cast skill margin.</summary>
|
|
public int HuntSkillExcessOverDifficulty { get; set; } = 25;
|
|
// Declared tDouble in VTank's own Settings table (VtankOptionCatalog):
|
|
// double, not float, so a loaded .usd round-trips this setting exactly
|
|
// instead of losing precision below float's ~7-digit guarantee (item I,
|
|
// slice-1 fix round). Physics/combat consumers that want a float cast
|
|
// at their own use site.
|
|
public double MaximumRange { get; set; } = 5d;
|
|
/// <summary>
|
|
/// Monsters nearer than this are not valid attack targets. VTank applies
|
|
/// this before priority and angle/range ranking.
|
|
/// </summary>
|
|
public double MinimumRange { get; set; }
|
|
/// <summary>
|
|
/// VTank's Approach Distance. Zero disables monster approach; otherwise
|
|
/// navigation may close a selected target from this range down to
|
|
/// <see cref="MaximumRange"/>.
|
|
/// </summary>
|
|
public double ApproachDistance { get; set; }
|
|
public bool IdlePeaceMode { get; set; }
|
|
public bool StopMacroOnDeath { get; set; } = true;
|
|
public bool JumpOutWandCasting { get; set; }
|
|
public bool DoJiggle { get; set; }
|
|
public TargetSelectionMethod SelectionMethod { get; set; } =
|
|
TargetSelectionMethod.Both;
|
|
public double TargetSelectAngleRange { get; set; } = 5d;
|
|
public bool TargetLock { get; set; }
|
|
public PluginAttackHeight AttackHeight { get; set; } =
|
|
PluginAttackHeight.Medium;
|
|
public float AttackPower { get; set; } = 0.5f;
|
|
public bool AutoAttackPower { get; set; } = true;
|
|
public bool UseRecklessness { get; set; } = true;
|
|
public double ScanIntervalSeconds { get; set; } = 0.25;
|
|
public DebuffEachFirst DebuffEachFirst { get; set; } = DebuffEachFirst.One;
|
|
public DebuffSelectionMethod DebuffSelectionMethod { get; set; } =
|
|
DebuffSelectionMethod.Skill;
|
|
public double DebuffPrecastSeconds { get; set; } = 5d;
|
|
public bool SwitchWandsToDebuff { get; set; }
|
|
public UseArcsMode UseArcs { get; set; } = UseArcsMode.AtRange;
|
|
public double SpellRangeFudge { get; set; } = 1d;
|
|
public bool UseBreakableTurnTo { get; set; } = true;
|
|
public bool UseProjectileAwareness { get; set; } = true;
|
|
public double CollisionProjectileRadius { get; set; } = 0.4d;
|
|
public double CollisionStepDistance { get; set; } = 0.7d;
|
|
public bool ShowCollisionDebug { get; set; }
|
|
public int MaximumCollisionChecksPerTick { get; set; } = 500;
|
|
public double ArcRange { get; set; } = 5d;
|
|
public double RingDistance { get; set; } = 5d;
|
|
public int MinimumRingTargets { get; set; } = 4;
|
|
public bool DeleteGhostMonsters { get; set; } = true;
|
|
public int GhostMonsterSpellAttemptCount { get; set; } = 200;
|
|
public int BlacklistMonsterAttemptCount { get; set; } = 4;
|
|
public double BlacklistMonsterTimeoutSeconds { get; set; } = 120d;
|
|
public bool DeleteGhostMonstersByHealthTracker { get; set; } = true;
|
|
public double GhostDeleteHealthTrackerSeconds { get; set; } = 30d;
|
|
public bool SummonPets { get; set; } = true;
|
|
public PetRangeMode PetRangeMode { get; set; } = PetRangeMode.AttackDistance;
|
|
public double PetCustomRange { get; set; } = 5d;
|
|
public int PetMonsterDensity { get; set; } = 1;
|
|
public int PetRefillCountIdle { get; set; } = 3;
|
|
public int PetRefillCountNormal { get; set; } = 1;
|
|
public bool AllowDebuffFallback { get; set; }
|
|
public int UseSpecialAmmo { get; set; }
|
|
public bool WhoYouGonnaCall { get; set; } = true;
|
|
public bool AutoFellowManagement { get; set; } = true;
|
|
public string BlacklistedSpellComponents { get; set; } = string.Empty;
|
|
/// <summary>
|
|
/// Runtime object ids resolved from VTank's Items profile. Debuff lenses
|
|
/// and cast-on-strike weapons are never taken from arbitrary inventory.
|
|
/// </summary>
|
|
public ISet<uint> CombatItemObjectIds { get; } = new HashSet<uint>();
|
|
public ISet<string> CombatItemNames { get; } =
|
|
new HashSet<string>(StringComparer.Ordinal);
|
|
/// <summary>Exact names enabled in VTank's Consumables profile.</summary>
|
|
public ISet<string> ConsumableNames { get; } =
|
|
new HashSet<string>(StringComparer.Ordinal);
|
|
public IDictionary<string, ConsumableCategory> ConsumableCategories { get; } =
|
|
new Dictionary<string, ConsumableCategory>(StringComparer.Ordinal);
|
|
public IList<MonsterRule> Rules { get; } =
|
|
new List<MonsterRule> { new("DEFAULT", 0) };
|
|
|
|
public ResolvedMonsterRule ResolveRule(PluginCombatTarget target)
|
|
{
|
|
var context = new MonsterExpressionContext(
|
|
target.Name,
|
|
target.WeenieClassId,
|
|
target.SpeciesName,
|
|
target.MaximumHealth,
|
|
target.Distance,
|
|
target.HasShield,
|
|
MetaState,
|
|
ResolveSetting);
|
|
return MonsterRuleResolver.Resolve(Rules, context);
|
|
}
|
|
|
|
public string MetaState { get; set; } = "Default";
|
|
public IDictionary<string, MonsterValue> DynamicSettings { get; } =
|
|
new Dictionary<string, MonsterValue>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
private MonsterValue? ResolveSetting(string name) =>
|
|
DynamicSettings.TryGetValue(name, out MonsterValue value)
|
|
? value
|
|
: null;
|
|
}
|