acdream/src/AcDream.Plugins.MossTank/VtankDamageDatabase.cs

289 lines
6.6 KiB
C#

using AcDream.Plugin.Abstractions;
namespace AcDream.Plugins.MossTank;
/// <summary>
/// The ordered monster-element preferences from VTank's official
/// GameInfoDB. Name overrides win over CreatureType, matching e0.d(name).
/// Unknown targets retain VTank's final 0..6 element fallback order.
/// </summary>
internal static class VtankDamageDatabase
{
private static readonly MonsterDamageType[] Fallback =
[
MonsterDamageType.Pierce,
MonsterDamageType.Bludgeon,
MonsterDamageType.Slash,
MonsterDamageType.Acid,
MonsterDamageType.Electric,
MonsterDamageType.Cold,
MonsterDamageType.Fire,
];
private static readonly Dictionary<string, MonsterDamageType[]> Overrides =
ParseNames(OverrideData);
private static readonly Dictionary<int, MonsterDamageType[]> Species =
ParseSpecies(SpeciesData);
public static IReadOnlyList<MonsterDamageType> Preferences(
in PluginCombatTarget target)
{
if (!string.IsNullOrWhiteSpace(target.Name)
&& Overrides.TryGetValue(target.Name, out MonsterDamageType[]? exact))
{
return exact;
}
// Zero is also the plugin contract's "not appraised" sentinel.
if (target.SpeciesId != 0
&& Species.TryGetValue(target.SpeciesId, out MonsterDamageType[]? species))
{
return species;
}
return Fallback;
}
public static int PreferenceIndex(
in PluginCombatTarget target,
MonsterDamageType damage)
{
IReadOnlyList<MonsterDamageType> preferences = Preferences(target);
for (int i = 0; i < preferences.Count; i++)
{
if (preferences[i] == damage)
return i;
}
for (int i = 0; i < Fallback.Length; i++)
{
if (Fallback[i] == damage)
return preferences.Count + i;
}
return int.MaxValue;
}
private static Dictionary<string, MonsterDamageType[]> ParseNames(
string data)
{
var result = new Dictionary<string, MonsterDamageType[]>(
StringComparer.OrdinalIgnoreCase);
foreach (ReadOnlySpan<char> line in data.AsSpan().EnumerateLines())
{
int separator = line.IndexOf('|');
if (separator <= 0)
continue;
result[line[..separator].ToString()] = ParseElements(
line[(separator + 1)..]);
}
return result;
}
private static Dictionary<int, MonsterDamageType[]> ParseSpecies(
string data)
{
var result = new Dictionary<int, MonsterDamageType[]>();
foreach (ReadOnlySpan<char> line in data.AsSpan().EnumerateLines())
{
int separator = line.IndexOf('|');
if (separator <= 0
|| !int.TryParse(line[..separator], out int species))
{
continue;
}
result[species] = ParseElements(line[(separator + 1)..]);
}
return result;
}
private static MonsterDamageType[] ParseElements(ReadOnlySpan<char> text)
{
var result = new List<MonsterDamageType>(7);
foreach (Range range in text.Split(';'))
{
if (!int.TryParse(text[range], out int raw))
continue;
MonsterDamageType mapped = raw switch
{
0 => MonsterDamageType.Pierce,
1 => MonsterDamageType.Bludgeon,
2 => MonsterDamageType.Slash,
3 => MonsterDamageType.Acid,
4 => MonsterDamageType.Electric,
5 => MonsterDamageType.Cold,
6 => MonsterDamageType.Fire,
_ => MonsterDamageType.None,
};
if (mapped != MonsterDamageType.None && !result.Contains(mapped))
result.Add(mapped);
}
return [.. result];
}
private const string OverrideData = """
Magma Golem|5;1;0;2
Mist Golem|5;4;3;6;2;0;1
Nubilous Golem|4;5;3;2;0;1
Plasma Golem|4;5;3;2;0;1
Vapor Golem|5;4;15;4;3;6;2;0;1
Damaged Glacial Golem|6;1;0;2
Fractured Glacial Golem|6;1;0;2
Tanada Nanjou Shou-jen|3;4;6;5
Disgraced Nanjou Shou-jen|3;4;6;5
Magma Golem Exarch|5;1;0;2
Pillar of Fire|5;2;0
Infused Blood Golem|5;3;4;1;0;6;2
Infused Empyrean Blood Golem|5;3;4;2;6;0;1
Sapphire Golem|0;3;1;5;6;4;2
High Priestess Xik Minru|1;2;0
Contained Rift|2;1;0
Ebon Rift|2;1;0
Fallen Rift|2;1;0
Narrow Rift|2;1;0
Quiddity Rift|2;1;0
Shallow Rift|2;1;0
Tenebrous Rift|2;1;0
Umbral Rift|2;1;0
Unstable Rift|2;1;0
Aqueous Golem|4;6;5;3;2;1;0
Wave Golem|4;6;5;3;2;1;0
Unstable Magma Golem|5;1;0;2
Behemoth of Tenkarrdun|5;1;0;2
Small Magma Golem|5;1;0;2
Atlan's Crafting Golem|5;1;0;2
Bur Lizk|5;4;0;2
Dust Golem|5;4;6;3;0;1;2
Ancient Magma Golem|5;1;0;2
Frozen Ice Golem|6;1;0;2
Frozen Glacial Golem|6;1;0;2
Forge Golem|5;4;3;1;0;2
Frozen Gearknight|6
Diaphanous Nephol Golem|5;4;3;6;2;0;1
Tenuous Nephol Golem|5;4;3;6;2;0;1
Turbid Nephol Golem|5;4;3;6;2;0;1
Wall of Ice|6;1;0;2;3;4
Scold|5;1;0;2
Scold Chunk|5;1;0;2
Scold Lump|5;1;0;2
Freezing Mist Golem|5;4;3;6;2;0;1
Frost Golem|6;4;5;3
Elite Guardian|5;6
Enraged Ancient Soul|6;3;1;4;5;2;0
Mudmouth|6;4;3;5;1;0;2
Fiery Defender|5;2;4;1;0;3
Follower of Deewain|4;3;1;0;5;2;6
Chilled Defender|4;3;6;1;2;0;5
Charged Defender|5;3;4;2;1;0
Iron Golem Samurai|3;4;5;6;2;1;0
Clay Golem Samurai|1;5;6;4;3;2;0
Bronze Golem Samurai|4;3;5;6;2;1;0
Spectral Nanjou Shou-jen|1;6;2;3;4;5;0
Spectral Samurai|5;4;3;2;1;6;0
Spectral Claw Master|1;6;2;3;4;5;0
""";
private const string SpeciesData = """
0|2
1|1;0;2;5;6;4;3
2|4;6;5;2;0;1;3
3|6;1;2;3;5;0;4
4|6;1;4;3;2;5;0
5|4;3;2;0;1;5;6
6|2;0;5;3;1;4;6
7|0;2;1;6;3;5;4
8|6;0;1;5;3;2;4
9|1;2;0;3;6;5;4
10|1;4;2;0;5;3;6
11|2
12|2
13|1;3;0;5;6;4;2
14|6;3;2;1;4;0;5
15|2;0;1
16|5;2;4;3;0;1;6
17|2;0;3;1;5;4;6
18|2
19|6;0;1;2;3;5;4
20|2;0;1;3;4;5
21|0;4;6;3
22|6;2;1;4;0;3;5
23|6;0;1;3;2;5;4
24|6;3;2
25|2
26|5;2;0;6
27|0;2;1
28|5;3;0
29|4;5;2
30|1;2;0
31|6;0;1;2;3;4;5
32|5;1;3
33|2;0;1
34|2;0;1
35|1;0;2
36|2;6;0
37|2
38|5;2;0
39|6;2;1
40|2
41|2
42|3;2;0
43|2
44|2;0;1
45|2;5;3
46|6;0;2;1;3;4;5
47|1;0;2
48|5;2;0;3;1;6;4
49|6;2;0;1
50|2;6;1
51|2;1;0
52|6;2;1;0
53|5;1;2;4;6;3;0
54|5;2;0;1
55|1;5;4;2;6;3;0
56|5;1;3
57|2;0;5;3;1;4;6
58|2;0;5;3;1;4;6
59|5;2;0;3;1;6;4
60|4;2;0
61|6;2;0
62|2;0;1
63|3;4;6;5;1;2;0
64|2
65|2
66|2
67|2
68|2
69|2
70|4;3;0;2;1
71|1;5;4;0;2;6;3
72|2
73|2
74|2
75|5;0;2;6;1;4;3
76|2
77|6;2;0;1;3;4;5
78|4;6;5;2;3;0;1
79|2;0;6;5;4;3;1
80|6;2;1;0
81|1;0;6;2;3;4;5
82|2;1;0
83|4;2;0;1
84|1;2;0
85|2
86|2;0;1
87|2
88|1;0;2
89|0;1;2
90|2
91|2
92|1;0;2
93|2
94|2
95|6;2;1;0
96|2
-1|1;0;2;3;4;5;6
97|6
98|2;0;1
99|3;4;1;0;6;5;2
100|6;3;2;0;1;5;4
101|3;1;6;0;2;4;5
""";
}