// WeatherSetupProbe — Issue #26: dump weather Setups (0x02000BA6, 0x02000588, 0x02000589) // to determine if any contain a small near-camera billboard / particle layer. using System; using System.IO; using System.Linq; using System.Numerics; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Options; using DatReaderWriter.Types; using SysEnv = System.Environment; string datDir = SysEnv.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine(SysEnv.GetFolderPath(SysEnv.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); Console.WriteLine($"datDir = {datDir}"); using var dats = new DatCollection(datDir, DatAccessType.Read); uint[] setupIds = { 0x02000BA6u, 0x02000588u, 0x02000589u }; foreach (uint sid in setupIds) ProbeSetup(dats, sid); return 0; static void ProbeSetup(DatCollection dats, uint sid) { Console.WriteLine(); Console.WriteLine($"================ Setup 0x{sid:X8} ================"); if (!dats.TryGet(sid, out var s) || s is null) { Console.WriteLine(" (NOT FOUND)"); return; } Console.WriteLine($" Flags={s.Flags} NumParts={s.NumParts}"); Console.WriteLine($" Setup Height={s.Height:F3} Radius={s.Radius:F3}"); Console.WriteLine($" SortingSphere center=({s.SortingSphere.Origin.X:F2},{s.SortingSphere.Origin.Y:F2},{s.SortingSphere.Origin.Z:F2}) r={s.SortingSphere.Radius:F3}"); Console.WriteLine($" SelectionSphere center=({s.SelectionSphere.Origin.X:F2},{s.SelectionSphere.Origin.Y:F2},{s.SelectionSphere.Origin.Z:F2}) r={s.SelectionSphere.Radius:F3}"); Console.WriteLine($" HoldingLocations={s.HoldingLocations.Count} ConnectionPoints={s.ConnectionPoints.Count} PlacementFrames={s.PlacementFrames.Count} Lights={s.Lights.Count}"); Console.WriteLine($" DefaultAnimation=0x{(uint)s.DefaultAnimation:X8} DefaultScript=0x{(uint)s.DefaultScript:X8} DefaultMotionTable=0x{(uint)s.DefaultMotionTable:X8}"); // PlacementFrames: typically Placement.Default has the per-part frames. AnimationFrame? defaultFrame = null; foreach (var kv in s.PlacementFrames) { Console.WriteLine($" Placement[{kv.Key}]: Frames={kv.Value.Frames.Count} Hooks={kv.Value.Hooks.Count}"); if (defaultFrame is null) defaultFrame = kv.Value; if (kv.Key == Placement.Default) defaultFrame = kv.Value; } for (int pi = 0; pi < s.Parts.Count; pi++) { uint partGid = (uint)s.Parts[pi]; uint parent = (s.Flags.HasFlag(SetupFlags.HasParent) && pi < s.ParentIndex.Count) ? s.ParentIndex[pi] : 0xFFFFFFFFu; Vector3 scale = (s.Flags.HasFlag(SetupFlags.HasDefaultScale) && pi < s.DefaultScale.Count) ? s.DefaultScale[pi] : new Vector3(1, 1, 1); Frame? frame = (defaultFrame is not null && pi < defaultFrame.Frames.Count) ? defaultFrame.Frames[pi] : null; string frameStr = frame is null ? "(no-frame)" : $"pos=({frame.Origin.X:F2},{frame.Origin.Y:F2},{frame.Origin.Z:F2}) ori=({frame.Orientation.W:F3},{frame.Orientation.X:F3},{frame.Orientation.Y:F3},{frame.Orientation.Z:F3})"; string parentStr = parent == 0xFFFFFFFFu ? "ROOT" : parent.ToString(); Console.WriteLine($" Part[{pi}] GfxObj=0x{partGid:X8} parent={parentStr} scale=({scale.X:F2},{scale.Y:F2},{scale.Z:F2}) {frameStr}"); DumpGfxObj(dats, partGid, " "); } } static void DumpGfxObj(DatCollection dats, uint gid, string indent) { if (!dats.TryGet(gid, out var g) || g is null) { Console.WriteLine($"{indent}GfxObj 0x{gid:X8} NOT FOUND"); return; } int triCount = g.Polygons.Count; int physTri = g.PhysicsPolygons.Count; int vertCount = g.VertexArray?.Vertices?.Count ?? 0; Vector3 mn = new(float.PositiveInfinity), mx = new(float.NegativeInfinity); if (g.VertexArray?.Vertices is not null) { foreach (var v in g.VertexArray.Vertices.Values) { mn = Vector3.Min(mn, v.Origin); mx = Vector3.Max(mx, v.Origin); } } Vector3 size = (vertCount > 0) ? (mx - mn) : Vector3.Zero; float radius = (vertCount > 0) ? size.Length() * 0.5f : 0f; string sizeTag = radius < 5 ? " < 0) { Console.WriteLine($"{indent} bbox min=({mn.X:F2},{mn.Y:F2},{mn.Z:F2}) max=({mx.X:F2},{mx.Y:F2},{mx.Z:F2}) size=({size.X:F2},{size.Y:F2},{size.Z:F2}) r~{radius:F2}{sizeTag}"); Console.WriteLine($"{indent} SortCenter=({g.SortCenter.X:F2},{g.SortCenter.Y:F2},{g.SortCenter.Z:F2})"); // billboard heuristic: 4 verts and 1-2 polygons if (vertCount <= 8 && triCount <= 4) Console.WriteLine($"{indent} >>> BILLBOARD CANDIDATE (verts<=8, tris<=4)"); } for (int si = 0; si < g.Surfaces.Count; si++) { uint surfId = (uint)g.Surfaces[si]; if (!dats.TryGet(surfId, out var surf) || surf is null) { Console.WriteLine($"{indent} Surf[{si}]=0x{surfId:X8} (not found)"); continue; } string tex = "solid"; if (surf.Type.HasFlag(SurfaceType.Base1Image) || surf.Type.HasFlag(SurfaceType.Base1ClipMap)) { uint stid = (uint)surf.OrigTextureId; if (stid != 0 && dats.TryGet(stid, out var st) && st is not null && st.Textures.Count > 0) { uint rsid = (uint)st.Textures[0]; if (dats.TryGet(rsid, out var rs) && rs is not null) tex = $"{rs.Width}x{rs.Height} {rs.Format} STex=0x{stid:X8}"; else tex = $"STex=0x{stid:X8} (rs miss)"; } else tex = $"STex=0x{stid:X8}"; } Console.WriteLine($"{indent} Surf[{si}]=0x{surfId:X8} Type={surf.Type} Translucency={surf.Translucency:F3} Lum={surf.Luminosity:F3} Diffuse={surf.Diffuse:F3} {tex}"); } }