Files
finder2e_foundry_converter/converters/npc.cs
T
2026-06-18 12:04:36 -05:00

729 lines
32 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
namespace finder2e_foundry_converter.Converters
{
public struct Result<T>
{
public T Value { get; }
public string? Error { get; }
public bool IsSuccess => Error == null;
private Result(T value, string? error)
{
Value = value;
Error = error;
}
public static Result<T> Success(T value) => new Result<T>(value, null);
public static Result<T> Failure(string error) => new Result<T>(default!, error);
}
public static class NpcConverter
{
private static Dictionary<string, object> ParseToml(string content)
{
var root = new Dictionary<string, object>();
var currentTable = root;
var lines = content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
foreach (var rawLine in lines)
{
var line = rawLine.Trim();
if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
continue;
if (line.StartsWith("[") && line.EndsWith("]"))
{
var section = line.Substring(1, line.Length - 2).Trim();
var parts = section.Split('.');
currentTable = root;
foreach (var p in parts)
{
var part = p.Trim().Trim('"');
if (!currentTable.TryGetValue(part, out var next) || !(next is Dictionary<string, object>))
{
var newTable = new Dictionary<string, object>();
currentTable[part] = newTable;
currentTable = newTable;
}
else
{
currentTable = (Dictionary<string, object>)next;
}
}
}
else if (line.Contains("="))
{
var idx = line.IndexOf('=');
var key = line.Substring(0, idx).Trim().Trim('"');
var valStr = line.Substring(idx + 1).Trim();
if (string.IsNullOrEmpty(key)) continue;
currentTable[key] = ParseTomlValue(valStr);
}
}
return root;
}
private static object ParseTomlValue(string valStr)
{
if (valStr.StartsWith("{") && valStr.EndsWith("}"))
{
var inlineTable = new Dictionary<string, object>();
var content = valStr.Substring(1, valStr.Length - 2).Trim();
if (!string.IsNullOrEmpty(content))
{
var pairs = content.Split(',');
foreach (var pair in pairs)
{
var idx = pair.IndexOf('=');
if (idx >= 0)
{
var k = pair.Substring(0, idx).Trim().Trim('"');
var vStr = pair.Substring(idx + 1).Trim();
inlineTable[k] = ParseTomlValue(vStr);
}
}
}
return inlineTable;
}
if (valStr.StartsWith("\"") && valStr.EndsWith("\""))
{
return valStr.Substring(1, valStr.Length - 2);
}
if (long.TryParse(valStr, out var num))
{
return num;
}
return valStr;
}
private static Result<Dictionary<string, object>> LoadTableDocument(string normalizedSystem)
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config", $"{normalizedSystem}_tables.toml");
try
{
if (!File.Exists(path))
{
// Fallback attempt: sometimes config might be in working directory during dev
var alternativePath = Path.Combine(Directory.GetCurrentDirectory(), "config", $"{normalizedSystem}_tables.toml");
if (File.Exists(alternativePath))
{
path = alternativePath;
}
}
string rawContents = File.ReadAllText(path);
try
{
var document = ParseToml(rawContents);
return Result<Dictionary<string, object>>.Success(document);
}
catch (Exception ex)
{
return Result<Dictionary<string, object>>.Failure($"failed to parse {path}: {ex.Message}");
}
}
catch (Exception ex)
{
return Result<Dictionary<string, object>>.Failure($"failed to read {path}: {ex.Message}");
}
}
private static object? Navigate(Dictionary<string, object> doc, params string[] path)
{
object current = doc;
foreach (var key in path)
{
if (current is Dictionary<string, object> dict)
{
if (!dict.TryGetValue(key, out var next))
return null;
current = next;
}
else
{
return null;
}
}
return current;
}
public static Result<int> AbilityScoreModifier(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<int>.Failure($"unsupported ability modifier tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "ability_modifiers", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no ability modifier entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"ability modifier value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
if (value is string text && text == "n/a")
{
return Result<int>.Failure($"ability modifier tier `{normalizedTier}` is unavailable for level `{level}`");
}
return Result<int>.Failure($"invalid ability modifier value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> PerceptionModifier(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low" && normalizedTier != "terrible")
{
return Result<int>.Failure($"unsupported perception tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, `low`, or `terrible`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "perception", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no perception entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"perception value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
return Result<int>.Failure($"invalid perception value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> SkillModifier(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<int>.Failure($"unsupported skill tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "skills", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no skill entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"skill value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
if (value is Dictionary<string, object> range && normalizedTier == "low")
{
if (!range.TryGetValue("minimum", out var minObj) || minObj is not long minLong)
{
return Result<int>.Failure($"missing skill minimum for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (!range.TryGetValue("maximum", out var maxObj) || maxObj is not long maxLong)
{
return Result<int>.Failure($"missing skill maximum for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (minLong < int.MinValue || minLong > int.MaxValue)
{
return Result<int>.Failure($"skill minimum `{minLong}` is out of range for i32");
}
if (maxLong < int.MinValue || maxLong > int.MaxValue)
{
return Result<int>.Failure($"skill maximum `{maxLong}` is out of range for i32");
}
int minimum = (int)minLong;
int maximum = (int)maxLong;
if (minimum > maximum)
{
return Result<int>.Failure($"invalid skill range for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
return Result<int>.Success(Random.Shared.Next(minimum, maximum + 1));
}
return Result<int>.Failure($"invalid skill value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> Ac(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<int>.Failure($"unsupported armor class tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "armor_class", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no armor class entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"armor class value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
return Result<int>.Failure($"invalid armor class value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> SavingThrow(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low" && normalizedTier != "terrible")
{
return Result<int>.Failure($"unsupported saving throw tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, `low`, or `terrible`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "saving_throws", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no saving throw entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"saving throw value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
return Result<int>.Failure($"invalid saving throw value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> Hp(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<int>.Failure($"unsupported hit point tier `{normalizedTier}`; expected `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "hit_points", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no hit point entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is Dictionary<string, object> range)
{
if (!range.TryGetValue("minimum", out var minObj) || minObj is not long minLong)
{
return Result<int>.Failure($"missing hit point minimum for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (!range.TryGetValue("maximum", out var maxObj) || maxObj is not long maxLong)
{
return Result<int>.Failure($"missing hit point maximum for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (minLong < int.MinValue || minLong > int.MaxValue)
{
return Result<int>.Failure($"hit point minimum `{minLong}` is out of range for i32");
}
if (maxLong < int.MinValue || maxLong > int.MaxValue)
{
return Result<int>.Failure($"hit point maximum `{maxLong}` is out of range for i32");
}
int minimum = (int)minLong;
int maximum = (int)maxLong;
if (minimum > maximum)
{
return Result<int>.Failure($"invalid hit point range for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
return Result<int>.Success(Random.Shared.Next(minimum, maximum + 1));
}
return Result<int>.Failure($"invalid hit point value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> ResistanceOrWeakness(int level, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var levelTable = Navigate(docResult.Value!, "resistances_and_weaknesses", "levels", level.ToString());
if (levelTable == null || levelTable is not Dictionary<string, object> range)
{
return Result<int>.Failure($"no resistance/weakness entry found for system `{normalizedSystem}`, level `{level}`");
}
if (!range.TryGetValue("minimum", out var minObj) || minObj is not long minLong)
{
return Result<int>.Failure($"missing resistance/weakness minimum for system `{normalizedSystem}`, level `{level}`");
}
if (!range.TryGetValue("maximum", out var maxObj) || maxObj is not long maxLong)
{
return Result<int>.Failure($"missing resistance/weakness maximum for system `{normalizedSystem}`, level `{level}`");
}
if (minLong < int.MinValue || minLong > int.MaxValue)
{
return Result<int>.Failure($"resistance/weakness minimum `{minLong}` is out of range for i32");
}
if (maxLong < int.MinValue || maxLong > int.MaxValue)
{
return Result<int>.Failure($"resistance/weakness maximum `{maxLong}` is out of range for i32");
}
int minimum = (int)minLong;
int maximum = (int)maxLong;
if (minimum > maximum)
{
return Result<int>.Failure($"invalid resistance/weakness range for system `{normalizedSystem}`, level `{level}`");
}
return Result<int>.Success(Random.Shared.Next(minimum, maximum + 1));
}
public static Result<int> StrikeAttackBonus(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<int>.Failure($"unsupported strike attack bonus tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "strike_attack_bonus", "levels", level.ToString(), normalizedTier);
if (value == null)
{
return Result<int>.Failure($"no strike attack bonus entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"strike attack bonus value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
return Result<int>.Failure($"invalid strike attack bonus value for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<string> StrikeDamageRoll(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<string>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<string>.Failure($"unsupported strike damage tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<string>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "strike_damage", "levels", level.ToString(), normalizedTier);
if (value == null || value is not Dictionary<string, object> damageTable)
{
return Result<string>.Failure($"no strike damage entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (damageTable.TryGetValue("dice", out var diceObj) && diceObj is string dice)
{
return Result<string>.Success(dice);
}
return Result<string>.Failure($"invalid strike damage roll for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<int> StrikeDamageAverage(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
if (normalizedTier != "extreme" && normalizedTier != "high" && normalizedTier != "moderate" && normalizedTier != "low")
{
return Result<int>.Failure($"unsupported strike damage tier `{normalizedTier}`; expected `extreme`, `high`, `moderate`, or `low`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "strike_damage", "levels", level.ToString(), normalizedTier);
if (value == null || value is not Dictionary<string, object> damageTable)
{
return Result<int>.Failure($"no strike damage entry found for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
if (damageTable.TryGetValue("average", out var avgObj) && avgObj is long average)
{
if (average < int.MinValue || average > int.MaxValue)
{
return Result<int>.Failure($"strike damage average `{average}` is out of range for i32");
}
return Result<int>.Success((int)average);
}
return Result<int>.Failure($"invalid strike damage average for system `{normalizedSystem}`, level `{level}`, tier `{normalizedTier}`");
}
public static Result<string> AreaDamageRoll(int level, bool unlimited, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string column = unlimited ? "unlimited" : "limited";
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<string>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<string>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "area_damage", "levels", level.ToString(), column);
if (value == null || value is not Dictionary<string, object> damageTable)
{
return Result<string>.Failure($"no area damage entry found for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
if (damageTable.TryGetValue("dice", out var diceObj) && diceObj is string dice)
{
return Result<string>.Success(dice);
}
return Result<string>.Failure($"invalid area damage roll for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
public static Result<int> AreaDamageAverage(int level, bool unlimited, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string column = unlimited ? "unlimited" : "limited";
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "area_damage", "levels", level.ToString(), column);
if (value == null || value is not Dictionary<string, object> damageTable)
{
return Result<int>.Failure($"no area damage entry found for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
if (damageTable.TryGetValue("average", out var avgObj) && avgObj is long average)
{
if (average < int.MinValue || average > int.MaxValue)
{
return Result<int>.Failure($"area damage average `{average}` is out of range for i32");
}
return Result<int>.Success((int)average);
}
return Result<int>.Failure($"invalid area damage average for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
public static Result<int> SpellDc(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
string column = normalizedTier switch
{
"extreme" => "extreme_dc",
"high" => "high_dc",
"moderate" => "moderate_dc",
_ => null!
};
if (column == null)
{
return Result<int>.Failure($"unsupported spell dc tier `{normalizedTier}`; expected `extreme`, `high`, or `moderate`");
}
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "spellcasting", "levels", level.ToString(), column);
if (value == null)
{
return Result<int>.Failure($"no spell dc entry found for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"spell dc value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
return Result<int>.Failure($"invalid spell dc value for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
public static Result<int> SpellAttackBonus(int level, string modifierTier, string system)
{
string normalizedSystem = system.Trim().ToLowerInvariant();
string normalizedTier = modifierTier.Trim().ToLowerInvariant();
string column = normalizedTier switch
{
"extreme" => "extreme_spell_attack",
"high" => "high_spell_attack",
"moderate" => "moderate_spell_attack",
_ => null!
};
if (column == null)
{
return Result<int>.Failure($"unsupported spell attack bonus tier `{normalizedTier}`; expected `extreme`, `high`, or `moderate`");
}
if (normalizedSystem != "pf2e" && normalizedSystem != "sf2e")
{
return Result<int>.Failure($"unsupported game system `{normalizedSystem}`; expected `pf2e` or `sf2e`");
}
var docResult = LoadTableDocument(normalizedSystem);
if (!docResult.IsSuccess) return Result<int>.Failure(docResult.Error!);
var value = Navigate(docResult.Value!, "spellcasting", "levels", level.ToString(), column);
if (value == null)
{
return Result<int>.Failure($"no spell attack bonus entry found for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
if (value is long number)
{
if (number < int.MinValue || number > int.MaxValue)
{
return Result<int>.Failure($"spell attack bonus value `{number}` is out of range for i32");
}
return Result<int>.Success((int)number);
}
return Result<int>.Failure($"invalid spell attack bonus value for system `{normalizedSystem}`, level `{level}`, column `{column}`");
}
}
}