// Dump self-targeted beneficial spells from portal.dat's SpellTable so buff // selection can be built on what the data actually says rather than on // remembered spell names. using AcDream.Content; using AcDream.Core.Spells; using DatReaderWriter; using DatReaderWriter.Options; using SysEnv = System.Environment; string datDir = SysEnv.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine(SysEnv.GetFolderPath(SysEnv.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); MagicCatalog catalog = MagicCatalog.Load(adapter); SpellTable table = catalog.SpellTable; Console.WriteLine($"spells loaded: {table.Count}"); if (args.Length > 0 && args[0] == "--cursors") { // Resolve the retail global-cursor enum table (6) to DAT surface ids, the // same walk RetailCursorResolver does: portal master map -> table 6 -> id. uint masterDid = (uint)dats.Portal.Header.MasterMapId; if (!dats.Portal.TryGet(masterDid, out var master) || master is null) throw new InvalidOperationException("no master enum map"); if (!master.ClientEnumToID.TryGetValue(6u, out uint cursorMapDid)) throw new InvalidOperationException("no cursor enum table 6"); if (!dats.Portal.TryGet(cursorMapDid, out var cursorMap) || cursorMap is null) throw new InvalidOperationException("cursor map missing"); foreach (var kv in cursorMap.ClientEnumToID.OrderBy(k => k.Key)) Console.WriteLine($"cursorEnum 0x{kv.Key:X2} -> surface 0x{kv.Value:X8}"); return; } if (args.Length > 0 && args[0] == "--flags") { string want = args.Length > 1 ? args[1].ToLowerInvariant() : "bane"; foreach (uint id in table.SpellIds.OrderBy(i => i)) { if (!table.TryGet(id, out var m)) continue; if (!m.Name.ToLowerInvariant().Contains(want)) continue; if (m.Generation != 1 && !m.Name.Contains(" I", StringComparison.Ordinal)) continue; Console.WriteLine( $"0x{m.SpellId:X4} fam{m.Family,-5} gen{m.Generation,-3} " + $"flags=0x{m.Flags:X4} mask=0x{m.TargetMask:X4} " + $"self={m.IsSelfTargeted,-5} unt={m.IsUntargeted,-5} ben={m.IsBeneficial,-5} " + $"school={m.School,-20} {m.Name,-28} | {m.Description}"); } return; } if (args.Length > 0 && args[0] == "--desc") { foreach (uint id in table.SpellIds.OrderBy(i => i)) { if (!table.TryGet(id, out var m)) continue; if (!m.IsSelfTargeted || !m.IsBeneficial || m.IsDebuff) continue; if (m.Generation != 1) continue; // one representative per line Console.WriteLine($"fam {m.Family,-5} 0x{m.SpellId:X4} {m.Name,-40} | {m.Description}"); } return; } if (args.Length > 0 && args[0] == "--skills") { var skillTable = dats.Get(0x0E000004u) ?? throw new InvalidOperationException("SkillTable 0x0E000004 missing"); Console.WriteLine($"skills: {skillTable.Skills.Count}"); foreach (var kv in skillTable.Skills.OrderBy(k => (uint)k.Key)) Console.WriteLine($" {(uint)kv.Key,-4} {kv.Value.Name}"); return; } if (args.Length > 0 && args[0] == "--enum") { string outPath = args.Length > 1 ? args[1] : Path.Combine("src", "AcDream.Plugins.MossTank", "SpellId.g.cs"); SpellDump.EmitEnum.Run(table, outPath, "AcDream.Plugins.MossTank", "SpellId"); return; } string filter = args.Length > 0 ? args[0].ToLowerInvariant() : ""; var rows = table.SpellIds .Select(id => table.TryGet(id, out var m) ? m : null) .Where(s => s is not null)! .Select(s => s!) .Where(s => s.IsSelfTargeted && s.IsBeneficial && !s.IsDebuff) .Where(s => filter.Length == 0 || s.Name.ToLowerInvariant().Contains(filter)) .OrderBy(s => s.Family).ThenBy(s => s.Generation) .ToList(); Console.WriteLine($"self-targeted beneficial: {rows.Count}\n"); Console.WriteLine($"{"family",-8} {"gen",-4} {"id",-8} {"school",-12} {"mana",-5} {"dur",-8} name"); foreach (var s in rows) { Console.WriteLine( $"{s.Family,-8} {s.Generation,-4} 0x{s.SpellId:X4} {s.School,-12} {s.ManaCost,-5} {s.Duration,-8:F0} {s.Name}"); }