173 lines
5.9 KiB
C#
173 lines
5.9 KiB
C#
using AcDream.Plugin.Abstractions;
|
|
|
|
namespace AcDream.Plugins.MossTank;
|
|
|
|
/// <summary>
|
|
/// Verbatim decision tree from official VTank <c>hi.cs</c> immediately before
|
|
/// its call to <c>bo.a(target,power,spell)</c>. This odd-looking table is
|
|
/// intentional: slash/pierce hybrid weapons use different charge points for
|
|
/// single, triple-strike, dual-wield and shield arrangements.
|
|
/// </summary>
|
|
internal static class AutoAttackPower
|
|
{
|
|
private const uint MeleeWeapon = 0x00000001u;
|
|
private const uint MissileWeapon = 0x00000100u;
|
|
private const uint ShieldLocation = 0x00200000u;
|
|
private const int SlashDamage = 0x0001;
|
|
private const int PierceDamage = 0x0002;
|
|
private const int TripleSlashAttack = 0x0040;
|
|
private const uint RecklessnessSkill = 50u;
|
|
|
|
public static float Resolve(
|
|
MonsterRuleActions actions,
|
|
CombatSettings settings,
|
|
ICharacterInfo character,
|
|
IReadOnlyList<PluginInventoryItem> inventory)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(actions);
|
|
ArgumentNullException.ThrowIfNull(settings);
|
|
ArgumentNullException.ThrowIfNull(character);
|
|
ArgumentNullException.ThrowIfNull(inventory);
|
|
if (!settings.AutoAttackPower)
|
|
return settings.AttackPower;
|
|
|
|
PluginInventoryItem? weapon = FindWeapon(actions, inventory);
|
|
if (weapon is not { } selected)
|
|
return settings.AttackPower;
|
|
if ((selected.ItemType & MissileWeapon) != 0u)
|
|
return ClampForRecklessness(1f, settings, character);
|
|
if ((selected.ItemType & MeleeWeapon) == 0u)
|
|
return settings.AttackPower;
|
|
|
|
int requestedDamage = RawDamage(actions.DamageType);
|
|
if (requestedDamage is not (SlashDamage or PierceDamage))
|
|
return ClampForRecklessness(1f, settings, character);
|
|
|
|
PluginInventoryItem? offhand = FindOffhand(actions, selected, inventory);
|
|
bool offhandMelee = offhand is { } held
|
|
&& (held.ItemType & MeleeWeapon) != 0u;
|
|
bool offhandShield = offhand is { } shield
|
|
&& (shield.EquippedLocation & ShieldLocation) != 0u;
|
|
bool slashPierce = (selected.DamageType & (SlashDamage | PierceDamage))
|
|
== (SlashDamage | PierceDamage);
|
|
bool tripleSlash = (selected.AttackType & TripleSlashAttack) != 0;
|
|
|
|
float power;
|
|
if (selected.WeaponType == 1 && !offhandMelee)
|
|
{
|
|
power = requestedDamage == SlashDamage && slashPierce ? 0.5f : 0f;
|
|
}
|
|
else if (requestedDamage == PierceDamage && slashPierce && !tripleSlash)
|
|
{
|
|
power = 0.2f;
|
|
}
|
|
else if (requestedDamage == PierceDamage
|
|
&& slashPierce
|
|
&& tripleSlash
|
|
&& offhandMelee)
|
|
{
|
|
power = 0.49f;
|
|
}
|
|
else if (requestedDamage != PierceDamage
|
|
|| !slashPierce
|
|
|| !tripleSlash
|
|
|| offhandShield)
|
|
{
|
|
power = 1f;
|
|
}
|
|
else
|
|
{
|
|
power = 0.2f;
|
|
}
|
|
|
|
return ClampForRecklessness(power, settings, character);
|
|
}
|
|
|
|
private static PluginInventoryItem? FindWeapon(
|
|
MonsterRuleActions actions,
|
|
IReadOnlyList<PluginInventoryItem> inventory)
|
|
{
|
|
PluginInventoryItem? equipped = null;
|
|
PluginInventoryItem? named = null;
|
|
foreach (PluginInventoryItem item in inventory)
|
|
{
|
|
if (actions.WeaponObjectId != 0u
|
|
&& item.ObjectId == actions.WeaponObjectId)
|
|
{
|
|
return item;
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(actions.WeaponName)
|
|
&& item.Name.Equals(actions.WeaponName, StringComparison.Ordinal))
|
|
{
|
|
named ??= item;
|
|
}
|
|
if (item.IsEquipped
|
|
&& (item.ItemType & (MeleeWeapon | MissileWeapon)) != 0u)
|
|
{
|
|
equipped ??= item;
|
|
}
|
|
}
|
|
return named ?? equipped;
|
|
}
|
|
|
|
private static PluginInventoryItem? FindOffhand(
|
|
MonsterRuleActions actions,
|
|
PluginInventoryItem weapon,
|
|
IReadOnlyList<PluginInventoryItem> inventory)
|
|
{
|
|
PluginInventoryItem? equipped = null;
|
|
PluginInventoryItem? named = null;
|
|
foreach (PluginInventoryItem item in inventory)
|
|
{
|
|
if (item.ObjectId == weapon.ObjectId)
|
|
continue;
|
|
if (actions.OffhandObjectId != 0u
|
|
&& item.ObjectId == actions.OffhandObjectId)
|
|
{
|
|
return item;
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(actions.OffhandName)
|
|
&& item.Name.Equals(actions.OffhandName, StringComparison.Ordinal))
|
|
{
|
|
named ??= item;
|
|
}
|
|
if (item.IsEquipped
|
|
&& ((item.ItemType & MeleeWeapon) != 0u
|
|
|| (item.EquippedLocation & ShieldLocation) != 0u))
|
|
{
|
|
equipped ??= item;
|
|
}
|
|
}
|
|
return named ?? equipped;
|
|
}
|
|
|
|
private static float ClampForRecklessness(
|
|
float power,
|
|
CombatSettings settings,
|
|
ICharacterInfo character)
|
|
{
|
|
if (!settings.UseRecklessness
|
|
|| !character.TryGetSkill(
|
|
RecklessnessSkill,
|
|
out PluginSkillInfo recklessness)
|
|
|| recklessness.Training is not (
|
|
PluginSkillTraining.Trained or PluginSkillTraining.Specialized))
|
|
{
|
|
return power;
|
|
}
|
|
return Math.Clamp(power, 0.11f, 0.9f);
|
|
}
|
|
|
|
private static int RawDamage(MonsterDamageType damage) => damage switch
|
|
{
|
|
MonsterDamageType.Slash => SlashDamage,
|
|
MonsterDamageType.Pierce => PierceDamage,
|
|
MonsterDamageType.Bludgeon => 0x0004,
|
|
MonsterDamageType.Cold => 0x0008,
|
|
MonsterDamageType.Fire => 0x0010,
|
|
MonsterDamageType.Acid => 0x0020,
|
|
MonsterDamageType.Electric => 0x0040,
|
|
MonsterDamageType.Nether => 0x0400,
|
|
_ => 0,
|
|
};
|
|
}
|