// WeatherEnumerator — Issue #26 probe. // Iterates ALL DayGroups in Dereth (Region 0x13000000), dumps every SkyObject // flagged with the weather bit (Properties & 0x04), and records the bounding // box of every unique GfxObj used as a weather visual. using System; using System.Collections.Generic; 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; const uint WEATHER_BIT = 0x04; 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); if (!dats.TryGet(0x13000000u, out var region) || region is null) { Console.Error.WriteLine("ERROR: Cannot read Region 0x13000000"); return 1; } var dayGroups = region.SkyInfo?.DayGroups; if (dayGroups is null) { Console.Error.WriteLine("No DayGroups"); return 1; } Console.WriteLine($"Region loaded. {dayGroups.Count} DayGroups."); Console.WriteLine(); // Collect unique weather GfxObjIds across all DayGroups (incl. SkyTime replaces). var weatherGfxIds = new HashSet(); int totalWeatherSkyObjs = 0; for (int dg = 0; dg < dayGroups.Count; dg++) { var group = dayGroups[dg]; string name = group.DayName?.Value ?? "(null)"; int weatherCount = group.SkyObjects.Count(so => (so.Properties & WEATHER_BIT) != 0); Console.WriteLine($"=== DayGroup[{dg}] \"{name}\" Chance={group.ChanceOfOccur:F3} SkyObjects={group.SkyObjects.Count} WeatherSkyObjs={weatherCount} ==="); for (int oi = 0; oi < group.SkyObjects.Count; oi++) { var so = group.SkyObjects[oi]; if ((so.Properties & WEATHER_BIT) == 0) continue; totalWeatherSkyObjs++; uint gfx = (uint)so.DefaultGfxObjectId; uint pes = (uint)so.DefaultPesObjectId; Console.WriteLine($" WEATHER OI={oi} Begin={so.BeginTime:F3} End={so.EndTime:F3} BeginAng={so.BeginAngle:F1} EndAng={so.EndAngle:F1}"); Console.WriteLine($" TexVel=({so.TexVelocityX:F4},{so.TexVelocityY:F4}) Gfx=0x{gfx:X8} Pes=0x{pes:X8} Props=0x{so.Properties:X8}"); if (gfx != 0) weatherGfxIds.Add(gfx); } // Also scan SkyObjReplace entries for weather slots (replaces never carry // their own Properties, but the same OI's weather bit means the replace // is also a weather visual — record the gfx). var weatherIndices = new HashSet( Enumerable.Range(0, group.SkyObjects.Count) .Where(i => (group.SkyObjects[i].Properties & WEATHER_BIT) != 0)); foreach (var st in group.SkyTime) { foreach (var r in st.SkyObjReplace) { if (!weatherIndices.Contains((int)r.ObjectIndex)) continue; uint gfx = (uint)r.GfxObjId; if (gfx != 0 && weatherGfxIds.Add(gfx)) Console.WriteLine($" WEATHER REPLACE SkyTime.Begin={st.Begin:F3} OI={r.ObjectIndex} Gfx=0x{gfx:X8}"); } } Console.WriteLine(); } Console.WriteLine($"Total weather SkyObjects across all DayGroups: {totalWeatherSkyObjs}"); Console.WriteLine($"Unique weather GfxObjIds: {weatherGfxIds.Count}"); Console.WriteLine(); Console.WriteLine("=== Bounding boxes of unique weather GfxObjs ==="); Console.WriteLine("GfxObjId | Verts | minX minY minZ | maxX maxY maxZ | sizeX sizeY sizeZ | radius"); Console.WriteLine(new string('-', 130)); foreach (uint gid in weatherGfxIds.OrderBy(x => x)) { DumpBounds(dats, gid); } return 0; static void DumpBounds(DatCollection dats, uint gid) { if (gid >= 0x02000000u) { if (!dats.TryGet(gid, out var setup) || setup is null) { Console.WriteLine($"0x{gid:X8} | (Setup not found)"); return; } Console.WriteLine($"0x{gid:X8} | Setup with {setup.Parts.Count} part(s):"); foreach (var p in setup.Parts) DumpGfxBounds(dats, (uint)p, indent: " "); return; } DumpGfxBounds(dats, gid, indent: ""); } static void DumpGfxBounds(DatCollection dats, uint gid, string indent) { if (!dats.TryGet(gid, out var go) || go is null) { Console.WriteLine($"{indent}0x{gid:X8} | (GfxObj not found)"); return; } var verts = go.VertexArray?.Vertices; if (verts is null || verts.Count == 0) { Console.WriteLine($"{indent}0x{gid:X8} | 0 verts (no geometry)"); return; } Vector3 mn = new(float.MaxValue), mx = new(float.MinValue); foreach (var kv in verts) { var v = kv.Value; var p = new Vector3(v.Origin.X, v.Origin.Y, v.Origin.Z); mn = Vector3.Min(mn, p); mx = Vector3.Max(mx, p); } var size = mx - mn; float radius = Math.Max(size.X, Math.Max(size.Y, size.Z)) * 0.5f; Console.WriteLine( $"{indent}0x{gid:X8} | {verts.Count,5} | " + $"{mn.X,8:F2} {mn.Y,8:F2} {mn.Z,8:F2} | " + $"{mx.X,8:F2} {mx.Y,8:F2} {mx.Z,8:F2} | " + $"{size.X,8:F2} {size.Y,8:F2} {size.Z,8:F2} | {radius,7:F2}"); }