diff --git a/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj b/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj
index a9474e639..92abd08a0 100644
--- a/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj
+++ b/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj
@@ -24,5 +24,6 @@
+
diff --git a/src/AcDream.Plugins.MossTank/VitalPlan.cs b/src/AcDream.Plugins.MossTank/VitalPlan.cs
index dd081f4f5..0b0f5d51d 100644
--- a/src/AcDream.Plugins.MossTank/VitalPlan.cs
+++ b/src/AcDream.Plugins.MossTank/VitalPlan.cs
@@ -48,12 +48,17 @@ public sealed class VitalSettings
public string RechargeHandlerSet { get; set; } = "RechargeHandlerSet";
///
/// The real VTank RechargeHandlerSet nested table (5 columns:
- /// Vital/HandlerString/MinPercent/MaxPercent/Stance), parsed from a
- /// loaded .usd profile by .
- /// Empty until a profile with this table is loaded;
- /// falls back to its hand-ported default ordering when this is empty.
+ /// Vital/HandlerString/MinPercent/MaxPercent/Stance). Defaults to VTank's
+ /// own shipped 26-row table (),
+ /// not an independently hand-typed replica; loading a real .usd
+ /// profile via replaces it
+ /// with that profile's own table. 's
+ /// hand-ported switch-based ordering is retained only as a defensive
+ /// fallback for the case where every row happens to have been cleared
+ /// explicitly and none match a given (vital, stance, percent) band.
///
- public IReadOnlyList RechargeHandlerRows { get; set; } = [];
+ public IReadOnlyList RechargeHandlerRows { get; set; } =
+ VtankDefaultSettingsDatabase.DefaultRechargeHandlerRows;
public bool UseKitsInMagicMode { get; set; } = true;
public bool GoToPeaceModeToUseKits { get; set; }
public int MinimumHealKitSuccessChance { get; set; } = 95;
diff --git a/src/AcDream.Plugins.MossTank/VitalRecharge.cs b/src/AcDream.Plugins.MossTank/VitalRecharge.cs
index 7da3695cb..ae863a71c 100644
--- a/src/AcDream.Plugins.MossTank/VitalRecharge.cs
+++ b/src/AcDream.Plugins.MossTank/VitalRecharge.cs
@@ -245,6 +245,18 @@ internal static class VitalRechargePlanner
return true;
}
+ ///
+ /// is ,
+ /// which defaults to VTank's own shipped 26-row table
+ /// ()
+ /// rather than an empty list, so is the
+ /// path every ordinary call takes. The hardcoded defaults switch
+ /// below is retained ONLY as a defensive net for a caller that
+ /// deliberately passes an empty/null row set or an unrecognized
+ /// (vital, stance, percent) band the real table happens not to cover —
+ /// not as the primary source of truth it used to be before the real
+ /// table became parseable.
+ ///
internal static IReadOnlyList Handlers(
VitalKind vital,
bool magicMode,
diff --git a/src/AcDream.Plugins.MossTank/VtankDefaultSettings.usd b/src/AcDream.Plugins.MossTank/VtankDefaultSettings.usd
new file mode 100644
index 000000000..1ac328544
--- /dev/null
+++ b/src/AcDream.Plugins.MossTank/VtankDefaultSettings.usd
@@ -0,0 +1,2270 @@
+10
+AntiExtraBuffSpells
+1
+ExemplarId
+n
+0
+AssistItems
+2
+Object
+Type
+n
+n
+0
+BuffedItems
+2
+Object
+Spell
+n
+n
+0
+ExtraBuffSpells
+1
+ExemplarId
+n
+0
+GemFoodItems
+2
+Name
+Spell
+n
+n
+2
+s
+Asheron's Benediction
+i
+3810
+s
+Blackmoor's Favor
+i
+3811
+ItemUseSpecifiers
+2
+Object
+Uses
+y
+n
+0
+MyMonsters
+21
+MonsterName
+AttackPriority
+DamageType
+WeaponToUse
+Imperil
+Vuln
+Yield
+GravityW
+Attack
+Ring
+Broadside
+Fester
+WeakeningCurse
+FesteringCurse
+Corruption
+DestructiveCurse
+Corrosion
+Streak
+SecondaryVuln
+SecondaryEquip
+PetDamageType
+y
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+1
+s
+
+i
+1
+i
+8
+i
+-1
+b
+False
+b
+False
+b
+False
+b
+False
+b
+True
+b
+False
+b
+False
+b
+False
+b
+False
+b
+False
+b
+False
+b
+False
+b
+False
+b
+True
+i
+98
+i
+0
+i
+101
+Settings
+4
+Setting
+Value
+Description
+SettingType
+y
+n
+n
+n
+137
+s
+EnableLooting
+b
+False
+s
+
+i
+1
+s
+EnableNav
+b
+False
+s
+
+i
+1
+s
+EnableBuffing
+b
+True
+s
+
+i
+1
+s
+EnableCombat
+b
+True
+s
+
+i
+1
+s
+SpellDiffExcessThreshold-Hunt
+i
+25
+s
+
+i
+3
+s
+SpellDiffExcessThreshold-Buff
+i
+5
+s
+
+i
+3
+s
+ArrowheadFletchDiffExcessThreshold
+i
+10
+s
+
+i
+3
+s
+Recharge-Norm-HitP
+i
+75
+s
+
+i
+3
+s
+Recharge-Norm-Stam
+i
+50
+s
+
+i
+3
+s
+Recharge-Norm-Mana
+i
+50
+s
+
+i
+3
+s
+Recharge-NoTarg-HitP
+i
+1
+s
+
+i
+3
+s
+Recharge-NoTarg-Stam
+i
+1
+s
+
+i
+3
+s
+Recharge-NoTarg-Mana
+i
+1
+s
+
+i
+3
+s
+Recharge-Helper-HitP
+i
+20
+s
+
+i
+3
+s
+Recharge-Helper-Stam
+i
+1
+s
+
+i
+3
+s
+Recharge-Helper-Mana
+i
+1
+s
+
+i
+3
+s
+DoHelp
+b
+True
+s
+If true, allies are healed/restamed/given mana. The fellowship window must be open to help fellows.
+i
+1
+s
+AttackDistance
+d
+0.0208333333333333
+s
+
+i
+2
+s
+AttackMinimumDistance
+d
+0
+s
+
+i
+2
+s
+ApproachDistance
+d
+0
+s
+
+i
+2
+s
+RingDistance
+d
+0.0208333333333333
+s
+
+i
+2
+s
+CorpseApproachRange-Max
+d
+0
+s
+
+i
+2
+s
+CorpseApproachRange-Min
+d
+0.014
+s
+
+i
+2
+s
+NavCloseStopRange
+d
+0.00833333333333333
+s
+
+i
+2
+s
+NavFarStopRange
+d
+999999
+s
+
+i
+2
+s
+UsePortalDistance
+d
+0.0166666666666667
+s
+
+i
+2
+s
+HelperDistanceHitP
+d
+0.310416666666667
+s
+
+i
+2
+s
+HelperDistanceStam
+d
+0.310416666666667
+s
+
+i
+2
+s
+HelperDistanceMana
+d
+0.166666666666667
+s
+
+i
+2
+s
+MinimumRingTargets
+i
+4
+s
+
+i
+3
+s
+DefaultMeleeAttackHeight
+i
+2
+s
+
+i
+3
+s
+CastDispelSelf
+b
+False
+s
+Casts dispel life magic self VII when the macro has a vuln on it.
+i
+1
+s
+UseDispelItems
+b
+False
+s
+Uses either a Gem of Stillness or a Condensed Dispel Potion when the macro has a vuln on it.
+i
+1
+s
+AutoCram
+b
+False
+s
+Moves items from the main pack to side packs.
+i
+1
+s
+AutoStack
+b
+True
+s
+Stacks stackable items.
+i
+1
+s
+ReadUnknownScrolls
+b
+True
+s
+Loots and reads unknown scrolls (looting must be enabled).
+i
+1
+s
+UseDispelDrum
+b
+False
+s
+
+i
+1
+s
+SwitchWandsToDebuff
+b
+False
+s
+If enabled, the macro forces a wand switch to the appropriately selected wand for the monster, even to debuff it. Without this setting, wands are only switched when a war attack is required.
+i
+1
+s
+AutoCraftItems
+b
+True
+s
+When enabled, the macro crafts known components that result in items in the gems/food panel, if your character has none. For instance, this allows the macro to create blue kits out of bobo comps if "Plentiful Healing Kit" is listed in the gems/food panel.
+i
+1
+s
+UseHealersHeart
+b
+True
+s
+When set to true and you have a healer's heart in your wand list, the macro uses it for helper.
+i
+1
+s
+JumpOutWandCasting
+b
+False
+s
+For the healer's heart, when enabled, the macro jumps right after using the heart. This causes the heal to go out immediately and your character to have to stand there doing nothing until the animation timer is up.
+i
+1
+s
+LootAllCorpses
+b
+False
+s
+Causes the macro to loot corpses killed by neither you nor your fellows.
+i
+1
+s
+LootFellowCorpses
+b
+False
+s
+Causes the macro to loot corpses killed by your fellows.
+i
+1
+s
+DoJiggle
+b
+False
+s
+Secret spell dodge mode.
+i
+1
+s
+RandomHelperBuffs
+b
+False
+s
+Casts buffs on nearby players randomly.
+i
+1
+s
+RandomHelperIntervalSeconds
+d
+5
+s
+
+i
+2
+s
+IdlePeaceMode
+b
+False
+s
+Loots, navigates, and salvages in peace mode.
+i
+1
+s
+TargetLock
+b
+False
+s
+Causes the macro to attack the selected monster first, as long as it is a valid target.
+i
+1
+s
+StopMacroOnDeath
+b
+True
+s
+Causes the macro to become disabled when the character dies.
+i
+1
+s
+UseArcs
+i
+1
+s
+Arc war spells are used rather than bolts, if both are known for the best level the macro is able to cast.
+i
+6
+s
+ArcRange
+d
+0.0208333333333333
+s
+
+i
+2
+s
+TargetSelectMethod
+i
+3
+s
+Selects how targets are given preference. When set to 'both', nearby targets are chosen by angle and distant targets are chosen in order of distance.
+i
+6
+s
+TargetSelectAngleRange
+d
+0.0208333333333333
+s
+Above this range, when target select is 'both', targets are selected by range.
+i
+2
+s
+IdleBuffTopoff
+b
+False
+s
+When the macro is idle, it recasts buffs.
+i
+1
+s
+IdleBuffTopoffTimeSeconds
+i
+1200
+s
+
+i
+3
+s
+RebuffTimeRemainingSeconds
+i
+300
+s
+
+i
+3
+s
+RefillWornMana
+b
+True
+s
+Uses mana charge types specified in the gems/food panel to refill worn items.
+i
+1
+s
+RefillWornMana-Item-ManaPercent
+i
+33
+s
+
+i
+3
+s
+BuffProfile-Prots
+s
+ALFCBPS
+s
+
+i
+5
+s
+BuffProfile-Banes
+s
+ALFCBPS
+s
+
+i
+5
+s
+BuffProfile_Prots
+i
+2
+s
+Specifies which self prots are cast.
+i
+6
+s
+BuffProfile_Banes
+i
+2
+s
+Specifies which self banes are cast.
+i
+6
+s
+DebuffEachFirst
+i
+1
+s
+Cast debuffs for each valid target before beginning attack. One: debuffs one monster then attacks it. Priority: debuffs all monsters of the same priority, then attacks. All: debuffs all monsters before attacking regardless of priority.
+i
+6
+s
+AutoAttackPower
+b
+True
+s
+When true, the macro sets melee attack power automatically based on weapon damage type.
+i
+1
+s
+LootPriorityBoost
+b
+False
+s
+When true, corpses are looted before attacking monsters.
+i
+1
+s
+CorpseCacheTimeoutMinutes
+d
+60
+s
+The time in minutes after which a corpse will be removed from the corpse cache.
+i
+2
+s
+CorpseItemAppearanceTimeoutSeconds
+d
+6
+s
+The time in seconds the macro will wait for all items on a corpse to appear.
+i
+2
+s
+CorpseItemIDTimeoutSeconds
+d
+60
+s
+The time in seconds the macro will wait for all needed items on a corpse to be ID'd.
+i
+2
+s
+DebuffSelectionMethod
+i
+2
+s
+Selects what the macro prefers when looking for a way to cast debuffs. Select 'Skill' when fighting monsters with high magic defense.
+i
+6
+s
+ManaStoneLootCount
+i
+4
+s
+The maximum number of usable mana stones the macro will try to keep in your inventory when looting.
+i
+3
+s
+ManaTankMinimumMana
+i
+1000
+s
+The minimum mana required for an item to be used to fill mana stones.
+i
+3
+s
+SplitPeas
+b
+True
+s
+Specifies if the macro should automatically split peas. Components you would like to keep supplied by splitting should be added to your consumables list.
+i
+1
+s
+SpellCompMin-Critical
+i
+4
+s
+The minimum number of each type of spell component. If you have fewer, a pea will be split. 'Critical' splitting is higher priority than healing.
+i
+3
+s
+SpellCompMin-Normal
+i
+20
+s
+The minimum number of each type of spell component. If you have fewer, a pea will be split. 'Normal' splitting has a lower priority than normal buffing.
+i
+3
+s
+SpellCompMin-Idle
+i
+20
+s
+The minimum number of each type of spell component. If you have fewer, a pea will be split. 'Idle' splitting is only done while idle.
+i
+3
+s
+RechargeBoostTimeSeconds
+d
+5
+s
+Maximum time for the recharge boost effect to last. Boost causes the macro to prefer to recharge once it switches to magic mode to recharge.
+i
+2
+s
+RechargeBoostAmount
+i
+40
+s
+Number of h/s/m points used for recharge boost. Boost causes the macro to prefer to recharge once it switches to magic mode to recharge.
+i
+3
+s
+UseSpecialAmmo
+i
+0
+s
+Specifies whether the macro should craft and use special ammunition types. 'Raider' indicates Raider Lightning ammo, and 'SCurrency' indicates ammo from Colosseum, Graveyard, and Paradox-touched Olthoi Infested Area.
+i
+6
+s
+OpenDoors
+b
+False
+s
+
+i
+1
+s
+DoorIDRange
+d
+0.0833333333333333
+s
+The range at which the macro will ID doors, when opening.
+i
+2
+s
+DoorOpenRange
+d
+0.0166666666666667
+s
+The range at which the macro will attempt to open a door.
+i
+2
+s
+DoorLockpickDiffExcessThreshold
+i
+-50
+s
+The number of lockpick skill points above a door's pick difficulty that is required for a door to be considered pickable.
+i
+3
+s
+ManaChargesWhenOff
+b
+True
+s
+When true, mana charges and stones are used to keep equipped mana filled even when the macro is disabled.
+i
+1
+s
+AutoFellowManagement
+b
+True
+s
+When true, automatically recruits players on request, and manages waiting lists and votes when acting as the fellowship leader.
+i
+1
+s
+MinimumHealKitSuccessChance
+i
+95
+s
+The lowest chance of success, in percent, that the macro will accept when attempting a heal with a health kit.
+i
+3
+s
+UseKitsInMagicMode
+b
+True
+s
+When true, the macro uses available healing kits to heal when in magic mode (with a higher priority than spells).
+i
+1
+s
+StaminaToHealthMultiplier
+d
+1.9
+s
+For a Stamina to Health spell to be used, it must be this much better than a normal healing spell.
+i
+2
+s
+ManaToHealthMultiplier
+d
+2.8
+s
+For a Mana to Health spell to be used, it must be this much better than a normal healing spell.
+i
+2
+s
+NavPriorityBoost
+b
+False
+s
+Causes the macro to navigate before attacking.
+i
+1
+s
+DeleteGhostMonsters
+b
+True
+s
+Attempts to detect when monsters appear to be there but really do not exist, and signals the client to delete them.
+i
+1
+s
+GhostMonsterSpellAttemptCount
+i
+200
+s
+The number of times the macro must attempt to cast a spell on a monster, without the spell starting, before that monster is marked as a ghost.
+i
+3
+s
+WhoYouGonnaCall
+b
+True
+s
+
+i
+1
+s
+BlacklistMonsterAttemptCount
+i
+4
+s
+The number of successful attacks that must miss a target before it is blacklisted.
+i
+3
+s
+BlacklistMonsterTimeoutSeconds
+i
+120
+s
+The amount of time a monster is blacklisted for, for being unhittable.
+i
+3
+s
+CombineSalvage
+b
+True
+s
+When true and looting is enabled, salvage bags are combined according to the ranges specified by the current loot profile.
+i
+1
+s
+LootOnlyRareCorpses
+b
+False
+s
+When true, only rare-generating corpses are looted.
+i
+1
+s
+DeleteGhostMonstersByHPTracker
+b
+True
+s
+An alternate method to force the deletion of ghost monsters which works for melee, missile and mage attacks.
+i
+1
+s
+GhostDeleteHPTrackerSeconds
+i
+30
+s
+The number of seconds that must pass with no receieved health updates for a target before it is considered a ghost.
+i
+3
+s
+GoToPeaceModeToUseKits
+b
+False
+s
+Causes the macro to drop to peace mode before using a healing kit.
+i
+1
+s
+UseRecklessness
+b
+True
+s
+When true, attacks using recklessness if available.
+i
+1
+s
+DebuffPrecastSeconds
+i
+5
+s
+The number of seconds before a target's debuff is set to expire before the macro will want to recast it. Does not apply to DoT spells.
+i
+3
+s
+ClearLevelBoostFlagOnCast
+b
+True
+s
+When true, RechargeBoost applies only to the first spell cast after switching to magic mode.
+i
+1
+s
+IdleCraftCount_HealthKits
+i
+2
+s
+The number of health kits of each type to craft up to when idle.
+i
+3
+s
+IdleCraftCount_StamKits
+i
+2
+s
+The number of stamina kits of each type to craft up to when idle.
+i
+3
+s
+IdleCraftCount_ManaKits
+i
+2
+s
+The number of mana kits of each type to craft up to when idle.
+i
+3
+s
+IdleCraftCount_HealthFood
+i
+15
+s
+The number of health foods/potions of each type to craft up to when idle.
+i
+3
+s
+IdleCraftCount_StamFood
+i
+15
+s
+The number of stamina foods/potions of each type to craft up to when idle.
+i
+3
+s
+IdleCraftCount_ManaFood
+i
+15
+s
+The number of mana foods/potions of each type to craft up to when idle.
+i
+3
+s
+BuffCastRecast_Seconds
+i
+30
+s
+After casting a single buff, the time until rebuffing every other buff is reduced by BuffCastRecast_Seconds for BuffCastRecastReset_Seconds.
+i
+3
+s
+BuffCastRecastReset_Seconds
+i
+30
+s
+After casting a single buff, the time until rebuffing every other buff is reduced by BuffCastRecast_Seconds for BuffCastRecastReset_Seconds.
+i
+3
+s
+EnableMeta
+b
+False
+s
+
+i
+1
+s
+BlacklistedSpellComps
+s
+
+s
+A list of spell components which will never be used to cast a spell.
+i
+5
+s
+DropToPeaceModeRetryCount
+i
+34
+s
+When trying to switch weapons, the macro will try repeatedly to drop to peace mode. If it fails this many times, assume the client is bugged and try to use a wand.
+i
+3
+s
+FollowAroundCorners
+b
+True
+s
+When following a player, follows their path if true. Otherwise, the macro always tries to run directly to where they are now.
+i
+1
+s
+BlacklistCorpseOpenAttemptCount
+i
+30
+s
+The number of times the macro must attempt to open a corpse before it is blacklisted.
+i
+3
+s
+BlacklistCorpseOpenTimeoutSeconds
+i
+200
+s
+The number of seconds an unopenable corpse is blacklisted for.
+i
+3
+s
+SummonPets
+b
+True
+s
+When enabled, the macro summons combat pets if available and listed in items.
+i
+1
+s
+PetRangeMode
+i
+0
+s
+Determines what monster range is used when summoning a combat pet.
+i
+6
+s
+PetCustomRange
+d
+0.0208333333333333
+s
+
+i
+2
+s
+PetRefillCount-Idle
+i
+3
+s
+If a pet summon item has this many charges or fewer, it will be refilled when idle.
+i
+3
+s
+PetRefillCount-Normal
+i
+1
+s
+If a pet summon item has this many charges or fewer, it will be refilled in combat.
+i
+3
+s
+CorpseOpenTimeoutSeconds
+d
+1.5
+s
+Time after trying to open a corpse before the macro will retry.
+i
+2
+s
+PetMonsterDensity
+i
+1
+s
+The minimum number of allowed pet targets within pet range before a pet will be summoned.
+i
+3
+s
+CorpseLootItemMaxAttempts
+i
+20
+s
+After trying to loot a particular item off a corpse this number of times, the item will be blacklisted.
+i
+3
+s
+FastCastBuffs
+b
+False
+s
+Attempt to fastcast buff spells. May move the character during casting.
+i
+1
+s
+UseBreakableTurnTo
+b
+True
+s
+When casting a spell on a target, allows breaking out during turning if another action intervenes.
+i
+1
+s
+UseProjectileAwareness
+b
+True
+s
+Checks if arcs, bolts, and arrows will hit a target or strike a wall before firing. May cause lag on slower computers.
+i
+1
+s
+CollisionProjectileRadius
+d
+0.4
+s
+The radius of the test object to use for collision checks.
+i
+2
+s
+CollisionStepDistance
+d
+0.7
+s
+The distance to move the test object during each collision check. Must be less than 2x CollisionProjectileRadius or solid walls may not be seen.
+i
+2
+s
+ShowCollisionDebug
+b
+False
+s
+
+i
+1
+s
+MaximumCollisionChecksPerTick
+i
+500
+s
+The maximum number of occluded targets the macro will attempt to evaluate.
+i
+3
+s
+SpellRangeFudge
+d
+1
+s
+Number of meters to subtract from the calculated range of spells, to account for positional inaccuracy.
+i
+2
+s
+BuffWithUntrained-Item
+i
+80
+s
+Above this character level, untrained Item Enchantment will not be used for buffing.
+i
+3
+s
+BuffWithUntrained-Creature
+i
+80
+s
+Above this character level, untrained Creature Enchantment will not be used for buffing.
+i
+3
+s
+BuffWithUntrained-Life
+i
+80
+s
+Above this character level, untrained Life Magic will not be used for buffing.
+i
+3
+s
+AllowDebuffFallback
+b
+False
+s
+When true, if a debuff target is blocked by a wall then a nonprojectile debuff will be used instead (such as a spell).
+i
+1
+s
+RechargeHandlerSet
+TABLE
+5
+Vital
+HandlerString
+MinPercent
+MaxPercent
+Stance
+n
+n
+n
+n
+n
+26
+i
+1
+s
+Stamina to Health
+i
+0
+i
+15
+i
+1
+i
+1
+s
+Mana to Health
+i
+0
+i
+15
+i
+1
+i
+1
+s
+Regular Spell
+i
+0
+i
+15
+i
+1
+i
+1
+s
+Recharge With Food
+i
+0
+i
+15
+i
+1
+i
+1
+s
+Kit Recharge
+i
+0
+i
+100
+i
+1
+i
+1
+s
+Stamina to Health
+i
+0
+i
+100
+i
+1
+i
+1
+s
+Mana to Health
+i
+0
+i
+100
+i
+1
+i
+1
+s
+Regular Spell
+i
+0
+i
+100
+i
+1
+i
+1
+s
+Recharge With Food
+i
+0
+i
+100
+i
+1
+i
+2
+s
+Kit Recharge
+i
+0
+i
+100
+i
+1
+i
+2
+s
+Regular Spell
+i
+0
+i
+100
+i
+1
+i
+2
+s
+Recharge With Food
+i
+0
+i
+100
+i
+1
+i
+3
+s
+Kit Recharge
+i
+0
+i
+100
+i
+1
+i
+3
+s
+Recharge With Food
+i
+0
+i
+100
+i
+1
+i
+3
+s
+Regular Spell
+i
+0
+i
+100
+i
+1
+i
+1
+s
+Recharge With Food
+i
+0
+i
+15
+i
+2
+i
+1
+s
+Kit Recharge
+i
+0
+i
+100
+i
+2
+i
+1
+s
+Recharge With Food
+i
+0
+i
+100
+i
+2
+i
+1
+s
+Stamina to Health
+i
+0
+i
+10
+i
+2
+i
+1
+s
+Regular Spell
+i
+0
+i
+100
+i
+2
+i
+2
+s
+Kit Recharge
+i
+0
+i
+100
+i
+2
+i
+2
+s
+Recharge With Food
+i
+0
+i
+100
+i
+2
+i
+2
+s
+Regular Spell
+i
+0
+i
+100
+i
+2
+i
+3
+s
+Kit Recharge
+i
+0
+i
+100
+i
+2
+i
+3
+s
+Recharge With Food
+i
+0
+i
+100
+i
+2
+i
+3
+s
+Regular Spell
+i
+0
+i
+100
+i
+2
+s
+
+i
+7
+SettingsCategories
+2
+Setting
+Categories
+y
+n
+136
+s
+EnableLooting
+i
+256
+s
+EnableNav
+i
+32
+s
+EnableBuffing
+i
+64
+s
+EnableCombat
+i
+12
+s
+SpellDiffExcessThreshold-Hunt
+i
+8
+s
+SpellDiffExcessThreshold-Buff
+i
+64
+s
+ArrowheadFletchDiffExcessThreshold
+i
+128
+s
+Recharge-Norm-HitP
+i
+2
+s
+Recharge-Norm-Stam
+i
+2
+s
+Recharge-Norm-Mana
+i
+2
+s
+Recharge-NoTarg-HitP
+i
+2
+s
+Recharge-NoTarg-Stam
+i
+2
+s
+Recharge-NoTarg-Mana
+i
+2
+s
+Recharge-Helper-HitP
+i
+2
+s
+Recharge-Helper-Stam
+i
+2
+s
+Recharge-Helper-Mana
+i
+2
+s
+DoHelp
+i
+2
+s
+AttackDistance
+i
+16
+s
+AttackMinimumDistance
+i
+16
+s
+ApproachDistance
+i
+48
+s
+RingDistance
+i
+24
+s
+CorpseApproachRange-Max
+i
+48
+s
+CorpseApproachRange-Min
+i
+48
+s
+NavCloseStopRange
+i
+48
+s
+NavFarStopRange
+i
+48
+s
+UsePortalDistance
+i
+48
+s
+HelperDistanceHitP
+i
+18
+s
+HelperDistanceStam
+i
+18
+s
+HelperDistanceMana
+i
+18
+s
+MinimumRingTargets
+i
+8
+s
+DefaultMeleeAttackHeight
+i
+4
+s
+CastDispelSelf
+i
+2
+s
+UseDispelItems
+i
+2
+s
+AutoCram
+i
+1
+s
+AutoStack
+i
+1
+s
+ReadUnknownScrolls
+i
+256
+s
+UseDispelDrum
+i
+2
+s
+SwitchWandsToDebuff
+i
+8
+s
+AutoCraftItems
+i
+128
+s
+UseHealersHeart
+i
+2
+s
+JumpOutWandCasting
+i
+10
+s
+LootAllCorpses
+i
+256
+s
+LootFellowCorpses
+i
+256
+s
+DoJiggle
+i
+8
+s
+RandomHelperBuffs
+i
+1
+s
+RandomHelperIntervalSeconds
+i
+1
+s
+IdlePeaceMode
+i
+1
+s
+TargetLock
+i
+12
+s
+StopMacroOnDeath
+i
+1
+s
+UseArcs
+i
+8
+s
+ArcRange
+i
+24
+s
+TargetSelectMethod
+i
+12
+s
+TargetSelectAngleRange
+i
+28
+s
+IdleBuffTopoff
+i
+64
+s
+IdleBuffTopoffTimeSeconds
+i
+64
+s
+RebuffTimeRemainingSeconds
+i
+64
+s
+RefillWornMana
+i
+1
+s
+RefillWornMana-Item-ManaPercent
+i
+1
+s
+BuffProfile-Prots
+i
+64
+s
+BuffProfile-Banes
+i
+64
+s
+BuffProfile_Prots
+i
+64
+s
+BuffProfile_Banes
+i
+64
+s
+DebuffEachFirst
+i
+12
+s
+AutoAttackPower
+i
+4
+s
+LootPriorityBoost
+i
+256
+s
+CorpseCacheTimeoutMinutes
+i
+32
+s
+CorpseItemAppearanceTimeoutSeconds
+i
+256
+s
+CorpseItemIDTimeoutSeconds
+i
+256
+s
+DebuffSelectionMethod
+i
+12
+s
+ManaStoneLootCount
+i
+257
+s
+ManaTankMinimumMana
+i
+257
+s
+SplitPeas
+i
+128
+s
+SpellCompMin-Critical
+i
+128
+s
+SpellCompMin-Normal
+i
+128
+s
+SpellCompMin-Idle
+i
+128
+s
+RechargeBoostTimeSeconds
+i
+2
+s
+RechargeBoostAmount
+i
+2
+s
+UseSpecialAmmo
+i
+128
+s
+OpenDoors
+i
+32
+s
+DoorIDRange
+i
+48
+s
+DoorOpenRange
+i
+48
+s
+DoorLockpickDiffExcessThreshold
+i
+160
+s
+ManaChargesWhenOff
+i
+1
+s
+AutoFellowManagement
+i
+1
+s
+MinimumHealKitSuccessChance
+i
+2
+s
+UseKitsInMagicMode
+i
+2
+s
+StaminaToHealthMultiplier
+i
+2
+s
+ManaToHealthMultiplier
+i
+2
+s
+NavPriorityBoost
+i
+32
+s
+DeleteGhostMonsters
+i
+1
+s
+GhostMonsterSpellAttemptCount
+i
+1
+s
+WhoYouGonnaCall
+i
+1
+s
+BlacklistMonsterAttemptCount
+i
+1
+s
+BlacklistMonsterTimeoutSeconds
+i
+1
+s
+CombineSalvage
+i
+256
+s
+LootOnlyRareCorpses
+i
+256
+s
+DeleteGhostMonstersByHPTracker
+i
+13
+s
+GhostDeleteHPTrackerSeconds
+i
+13
+s
+GoToPeaceModeToUseKits
+i
+3
+s
+UseRecklessness
+i
+4
+s
+DebuffPrecastSeconds
+i
+8
+s
+ClearLevelBoostFlagOnCast
+i
+2
+s
+IdleCraftCount_HealthKits
+i
+130
+s
+IdleCraftCount_StamKits
+i
+130
+s
+IdleCraftCount_ManaKits
+i
+130
+s
+IdleCraftCount_HealthFood
+i
+130
+s
+IdleCraftCount_StamFood
+i
+130
+s
+IdleCraftCount_ManaFood
+i
+130
+s
+BuffCastRecast_Seconds
+i
+64
+s
+BuffCastRecastReset_Seconds
+i
+64
+s
+EnableMeta
+i
+1
+s
+BlacklistedSpellComps
+i
+74
+s
+DropToPeaceModeRetryCount
+i
+13
+s
+FollowAroundCorners
+i
+32
+s
+BlacklistCorpseOpenAttemptCount
+i
+257
+s
+BlacklistCorpseOpenTimeoutSeconds
+i
+257
+s
+SummonPets
+i
+12
+s
+PetRangeMode
+i
+28
+s
+PetCustomRange
+i
+28
+s
+PetRefillCount-Idle
+i
+12
+s
+PetRefillCount-Normal
+i
+12
+s
+CorpseOpenTimeoutSeconds
+i
+256
+s
+PetMonsterDensity
+i
+12
+s
+CorpseLootItemMaxAttempts
+i
+256
+s
+FastCastBuffs
+i
+64
+s
+UseBreakableTurnTo
+i
+8
+s
+UseProjectileAwareness
+i
+12
+s
+CollisionProjectileRadius
+i
+12
+s
+CollisionStepDistance
+i
+12
+s
+ShowCollisionDebug
+i
+12
+s
+MaximumCollisionChecksPerTick
+i
+12
+s
+SpellRangeFudge
+i
+8
+s
+BuffWithUntrained-Item
+i
+64
+s
+BuffWithUntrained-Creature
+i
+64
+s
+BuffWithUntrained-Life
+i
+64
+s
+AllowDebuffFallback
+i
+12
+SettingsEnumInfo
+3
+Setting
+Value
+EnumValue
+n
+n
+n
+33
+s
+UseArcs
+i
+1
+s
+No
+s
+UseArcs
+i
+2
+s
+At Range
+s
+UseArcs
+i
+3
+s
+Yes
+s
+TargetSelectMethod
+i
+1
+s
+By range
+s
+TargetSelectMethod
+i
+2
+s
+By angle
+s
+TargetSelectMethod
+i
+3
+s
+Both
+s
+BuffProfile_Prots
+i
+1
+s
+Custom
+s
+BuffProfile_Prots
+i
+2
+s
+All
+s
+BuffProfile_Prots
+i
+3
+s
+None
+s
+BuffProfile_Prots
+i
+4
+s
+B
+s
+BuffProfile_Prots
+i
+5
+s
+BPS
+s
+BuffProfile_Prots
+i
+6
+s
+BPSA
+s
+BuffProfile_Prots
+i
+7
+s
+ALFC
+s
+BuffProfile_Prots
+i
+8
+s
+BPSAC
+s
+BuffProfile_Banes
+i
+1
+s
+Custom
+s
+BuffProfile_Banes
+i
+2
+s
+All
+s
+BuffProfile_Banes
+i
+3
+s
+None
+s
+BuffProfile_Banes
+i
+4
+s
+B
+s
+BuffProfile_Banes
+i
+5
+s
+BPS
+s
+BuffProfile_Banes
+i
+6
+s
+BPSA
+s
+BuffProfile_Banes
+i
+7
+s
+BPSAC
+s
+BuffProfile_Banes
+i
+8
+s
+ALFC
+s
+DebuffEachFirst
+i
+1
+s
+One
+s
+DebuffEachFirst
+i
+2
+s
+Priority
+s
+DebuffEachFirst
+i
+3
+s
+All
+s
+DebuffSelectionMethod
+i
+1
+s
+SpellLevel
+s
+DebuffSelectionMethod
+i
+2
+s
+Skill
+s
+UseSpecialAmmo
+i
+0
+s
+None
+s
+UseSpecialAmmo
+i
+1
+s
+Raider
+s
+UseSpecialAmmo
+i
+2
+s
+SCurrency
+s
+UseSpecialAmmo
+i
+3
+s
+Both
+s
+PetRangeMode
+i
+0
+s
+AttackDistance
+s
+PetRangeMode
+i
+1
+s
+PetCustomRange
diff --git a/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs b/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs
new file mode 100644
index 000000000..c739de778
--- /dev/null
+++ b/src/AcDream.Plugins.MossTank/VtankDefaultSettingsDatabase.cs
@@ -0,0 +1,67 @@
+using System.Reflection;
+
+namespace AcDream.Plugins.MossTank;
+
+///
+/// The complete .usd shipped by VTank as
+/// uTank2.Resources.defaultsettings.usd — every table (Settings,
+/// MyMonsters, GemFoodItems, ExtraBuffSpells, AntiExtraBuffSpells,
+/// ItemUseSpecifiers, SettingsCategories, SettingsEnumInfo, AssistItems,
+/// BuffedItems, RechargeHandlerSet, …), byte-for-byte, not a hand-typed
+/// C# replica. seeds
+/// a brand-new profile from this document (every table, every row's real
+/// Description/SettingType) and
+/// 's default RechargeHandlerRows is
+/// parsed from its RechargeHandlerSet table — both replace a prior
+/// port's independently hand-maintained defaults, which could (and did:
+/// see the 8fa70c3e4 "26 rows, not 24" fix) silently drift from what VTank
+/// itself ships.
+///
+internal static class VtankDefaultSettingsDatabase
+{
+ private const string ResourceSuffix = ".VtankDefaultSettings.usd";
+ private static readonly Lazy RawText = new(LoadText);
+ private static readonly Lazy DefaultRows = new(LoadDefaultRows);
+
+ /// A fresh, independently mutable parse of the embedded document — every call gets its own object graph.
+ public static VtankDatabase Parse() => VtankDatabase.Parse(RawText.Value);
+
+ /// VTank's own shipped RechargeHandlerSet rows (26, in file order).
+ public static IReadOnlyList DefaultRechargeHandlerRows => DefaultRows.Value;
+
+ private static string LoadText()
+ {
+ Assembly assembly = typeof(VtankDefaultSettingsDatabase).Assembly;
+ string resource = assembly.GetManifestResourceNames().Single(
+ static name => name.EndsWith(ResourceSuffix, StringComparison.Ordinal));
+ using Stream stream = assembly.GetManifestResourceStream(resource)
+ ?? throw new InvalidOperationException(
+ "The embedded VTank default settings (.usd) document is missing.");
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+
+ private static RechargeHandlerRow[] LoadDefaultRows()
+ {
+ VtankDatabase database = VtankDatabase.Parse(RawText.Value);
+ VtankTable? settings = database.Find("Settings");
+ if (settings is null)
+ return [];
+ int nameColumn = settings.ColumnIndex("Setting");
+ int valueColumn = settings.ColumnIndex("Value");
+ if (nameColumn < 0 || valueColumn < 0)
+ return [];
+ foreach (VtankRow row in settings.Rows)
+ {
+ if (!row.Cells[nameColumn].AsString().Equals(
+ "RechargeHandlerSet", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+ VtankCell cell = row.Cells[valueColumn];
+ if (cell.Tag == "TABLE" && cell.Table is { } table)
+ return VtankSettingsProfileSerializer.ParseRechargeHandlerSet(table);
+ }
+ return [];
+ }
+}
diff --git a/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs b/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs
index 543b8bfc8..4e3df1b3b 100644
--- a/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs
+++ b/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs
@@ -102,38 +102,28 @@ internal static class VtankSettingsProfileSerializer
return document.Render();
}
- /// Builds a brand-new database (Settings table only) seeded from .
+ ///
+ /// Builds a brand-new database seeded from VTank's own shipped
+ /// defaultsettings.usd ()
+ /// — every table (MyMonsters, GemFoodItems, ExtraBuffSpells,
+ /// AntiExtraBuffSpells, ItemUseSpecifiers, SettingsCategories,
+ /// SettingsEnumInfo, AssistItems, BuffedItems, RechargeHandlerSet, …),
+ /// not a hand-typed 4-column Settings-only replica: a prior version of
+ /// this method built only the Settings table, with an empty
+ /// Description and a hardcoded SettingType of 1 (Bool) for every row
+ /// regardless of the setting's real declared type.
+ /// then rewrites every Settings row's Value cell from
+ /// 's current values, exactly as it would for
+ /// an existing profile being re-saved untouched.
+ ///
public static VtankDatabase CreateNew(AllSettings source)
{
- var table = new VtankTable();
- table.ColumnNames.AddRange(["Setting", "Value", "Description", "SettingType"]);
- table.IndexFlags.AddRange([true, false, false, false]);
- foreach (string name in VtankOptionCatalog.Names)
- {
- VtankCell value = Capture(name, source) ?? DefaultCell(name);
- var row = new VtankRow();
- row.Cells.Add(VtankCell.String(name));
- row.Cells.Add(value);
- row.Cells.Add(VtankCell.String(string.Empty));
- row.Cells.Add(VtankCell.Int(1));
- table.Rows.Add(row);
- }
- var database = new VtankDatabase();
- database.Tables.Add((SettingsTable, table));
+ ArgumentNullException.ThrowIfNull(source);
+ VtankDatabase database = VtankDefaultSettingsDatabase.Parse();
+ _ = Save(database, source);
return database;
}
- private static VtankCell DefaultCell(string name)
- {
- MonsterValue value = VtankOptionCatalog.Default(name);
- return value.Kind switch
- {
- MonsterValueKind.Boolean => VtankCell.Bool(value.Boolean),
- MonsterValueKind.Text => VtankCell.String(value.Text),
- _ => VtankCell.Double(value.Number),
- };
- }
-
///
/// Converts a live numeric setting value to the exact
/// shape its declared SettingType
@@ -550,21 +540,14 @@ internal static class VtankSettingsProfileSerializer
return rows;
}
- internal static VtankTable RenderRechargeHandlerSet(IReadOnlyList rows)
- {
- var table = new VtankTable();
- table.ColumnNames.AddRange(["Vital", "HandlerString", "MinPercent", "MaxPercent", "Stance"]);
- table.IndexFlags.AddRange([false, false, false, false, false]);
- foreach (RechargeHandlerRow row in rows)
- {
- var vtankRow = new VtankRow();
- vtankRow.Cells.Add(VtankCell.Int(row.Vital));
- vtankRow.Cells.Add(VtankCell.String(row.HandlerString));
- vtankRow.Cells.Add(VtankCell.Int(row.MinPercent));
- vtankRow.Cells.Add(VtankCell.Int(row.MaxPercent));
- vtankRow.Cells.Add(VtankCell.Int(row.Stance));
- table.Rows.Add(vtankRow);
- }
- return table;
- }
+ // No RenderRechargeHandlerSet/write-back helper exists here deliberately:
+ // real VTank has NO save path for this table at all (docs/research/
+ // vtank-kb/01-settings-and-profiles.md section 2, row 137 — tCustom has
+ // no case in the Advanced Options editor's display/edit switch or in
+ // "/vt opt set"; the table is owned by a dedicated cRechargeManager
+ // object, not any control that persists through the generic Settings
+ // save path). Writing it back would be a MossTank invention, not a
+ // retail port. A prior version of this file had an unused
+ // RenderRechargeHandlerSet helper for exactly that invention; it was
+ // deleted rather than wired in for this reason.
}
diff --git a/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs b/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs
index da92912b9..87d93c686 100644
--- a/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs
+++ b/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs
@@ -202,6 +202,47 @@ public sealed class VtankSettingsProfileSerializerTests
static row => row.HandlerString == "Kit Recharge");
}
+ // Item D (slice-1 fix round): CreateNew used to build ONLY a 4-column
+ // Settings table (empty Description, hardcoded SettingType 1/Bool for
+ // every row) — a hand-typed replica missing the other nine tables VTank
+ // ships (MyMonsters, GemFoodItems, ExtraBuffSpells, AntiExtraBuffSpells,
+ // ItemUseSpecifiers, SettingsCategories, SettingsEnumInfo, AssistItems,
+ // BuffedItems). It now seeds from the embedded defaultsettings.usd, so
+ // its table set must match the real fixture's exactly.
+ [Fact]
+ public void CreateNewHasTheSameTableSetAsTheDefaultFixture()
+ {
+ string fixtureText = File.ReadAllText(
+ Path.Combine(FixturesRoot, "defaultsettings.usd"));
+ VtankDatabase fixture = VtankDatabase.Parse(fixtureText);
+
+ VtankDatabase created = VtankSettingsProfileSerializer.CreateNew(NewSettings());
+
+ string[] expectedTables = [.. fixture.Tables
+ .Select(static entry => entry.Name)
+ .OrderBy(static name => name, StringComparer.Ordinal)];
+ string[] actualTables = [.. created.Tables
+ .Select(static entry => entry.Name)
+ .OrderBy(static name => name, StringComparer.Ordinal)];
+ Assert.Equal(expectedTables, actualTables);
+ Assert.True(expectedTables.Length > 1, "the fixture itself must carry more than one table.");
+
+ // Every row's Description/SettingType survive from the embedded
+ // document (not the prior blanket Description="" / SettingType=1).
+ VtankTable? settings = created.Find("Settings");
+ Assert.NotNull(settings);
+ int descriptionColumn = settings!.ColumnIndex("Description");
+ int typeColumn = settings.ColumnIndex("SettingType");
+ Assert.True(descriptionColumn >= 0);
+ Assert.True(typeColumn >= 0);
+ Assert.Contains(
+ settings.Rows,
+ row => row.Cells[descriptionColumn].AsString().Length > 0);
+ Assert.Contains(
+ settings.Rows,
+ row => row.Cells[typeColumn].AsInt() != (int)VtankSettingValueType.Bool);
+ }
+
[Fact]
public void UnknownSettingsRowIsPreservedVerbatim()
{