// SkyObjectInspect — throwaway probe for the Dereth stars mystery. using System; using System.Collections.Generic; using System.IO; using System.Linq; 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); if (!dats.TryGet(0x13000000u, out var region) || region is null) { Console.Error.WriteLine("ERROR: Cannot read Region 0x13000000"); return 1; } Console.WriteLine($"Region loaded. SkyInfo.DayGroups count: {region.SkyInfo?.DayGroups?.Count ?? -1}"); var interesting = new[] { 0, 8 }; foreach (int dg in interesting) { if (region.SkyInfo?.DayGroups is null || dg >= region.SkyInfo.DayGroups.Count) continue; var group = region.SkyInfo.DayGroups[dg]; Console.WriteLine(); Console.WriteLine($"=== DayGroup[{dg}] Name=\"{group.DayName?.Value}\" Chance={group.ChanceOfOccur:F3} SkyObjs={group.SkyObjects.Count} SkyTimes={group.SkyTime.Count} ==="); for (int oi = 0; oi < group.SkyObjects.Count; oi++) { var so = group.SkyObjects[oi]; Console.WriteLine($" OI={oi}: Begin={so.BeginTime:F3} End={so.EndTime:F3} BeginAng={so.BeginAngle:F1} EndAng={so.EndAngle:F1} TexVel=({so.TexVelocityX:F3},{so.TexVelocityY:F3}) Gfx=0x{(uint)so.DefaultGfxObjectId:X8} Pes=0x{(uint)so.DefaultPesObjectId:X8} Props=0x{so.Properties:X8}"); } // Show every SkyTime's SkyObjectReplace entries — this tells us if any OI // actually changes at night. foreach (var skytime in group.SkyTime.OrderBy(s => s.Begin)) { Console.WriteLine($" [SkyTime @ Begin={skytime.Begin:F3}] Replaces={skytime.SkyObjReplace.Count}"); foreach (var r in skytime.SkyObjReplace) { Console.WriteLine($" OI={r.ObjectIndex}: Gfx=0x{(uint)r.GfxObjId:X8} Rot={r.Rotate:F2} Transp={r.Transparent:F3} Lum={r.Luminosity:F3} MaxB={r.MaxBright:F3}"); } } } // Also scan ALL DayGroups for any SkyObject with BeginTime > EndTime (wrap) // OR BeginTime in late night (>0.75) with a gfx that could be stars. Console.WriteLine(); Console.WriteLine("=== Scan: any SkyObject with night-spanning window (begin>0.7 or end<0.3 wrap-candidate) across ALL DayGroups ==="); int nFound = 0; if (region.SkyInfo?.DayGroups is not null) { for (int dg = 0; dg < region.SkyInfo.DayGroups.Count; dg++) { var group = region.SkyInfo.DayGroups[dg]; for (int oi = 0; oi < group.SkyObjects.Count; oi++) { var so = group.SkyObjects[oi]; bool wrap = so.BeginTime > so.EndTime && so.BeginTime != so.EndTime; bool late = so.BeginTime > 0.7f; bool early = so.EndTime < 0.3f && so.EndTime > 0f; if (wrap || late || early) { Console.WriteLine($" DG[{dg}]=\"{group.DayName?.Value}\" OI={oi} Begin={so.BeginTime:F3} End={so.EndTime:F3} Gfx=0x{(uint)so.DefaultGfxObjectId:X8} wrap={wrap} late={late} early={early}"); nFound++; } } } } Console.WriteLine($" (found {nFound} night-window candidates)"); // Candidate GfxObjs for Sunny. var candidateIds = new uint[] { 0x010015EEu, 0x010015EFu, 0x01001F6Au, 0x01004C36u, 0x02000714u }; foreach (uint gid in candidateIds) { Console.WriteLine(); Console.WriteLine($"=== GfxObj 0x{gid:X8} ==="); if (gid >= 0x02000000u) { if (dats.TryGet(gid, out var setup) && setup is not null) { Console.WriteLine($" [Setup] Parts={setup.Parts.Count}"); for (int pi = 0; pi < setup.Parts.Count; pi++) { uint partGid = (uint)setup.Parts[pi]; Console.WriteLine($" Part[{pi}] = GfxObj 0x{partGid:X8}"); DumpGfxObj(dats, partGid, indent: " "); } } else { Console.WriteLine(" (not a Setup or not found)"); } continue; } DumpGfxObj(dats, gid, indent: " "); } return 0; static void DumpGfxObj(DatCollection dats, uint gid, string indent) { if (!dats.TryGet(gid, out var go) || go is null) { Console.WriteLine($"{indent}(GfxObj 0x{gid:X8} not found)"); return; } Console.WriteLine($"{indent}GfxObj 0x{gid:X8}: Flags=0x{(uint)go.Flags:X8} Surfaces={go.Surfaces.Count} Polys={go.Polygons.Count} Verts={go.VertexArray?.Vertices?.Count ?? 0}"); for (int si = 0; si < go.Surfaces.Count; si++) { uint sid = (uint)go.Surfaces[si]; if (!dats.TryGet(sid, out var surf) || surf is null) { Console.WriteLine($"{indent} Surf[{si}]=0x{sid:X8} (not found)"); continue; } string texDesc = DescribeTexture(dats, surf); Console.WriteLine($"{indent} Surf[{si}]=0x{sid:X8} Type={surf.Type} Translucency={surf.Translucency:F3} Luminosity={surf.Luminosity:F3} Diffuse={surf.Diffuse:F3} Tex=[{texDesc}]"); } } static string DescribeTexture(DatCollection dats, Surface surf) { if (!(surf.Type.HasFlag(SurfaceType.Base1Image) || surf.Type.HasFlag(SurfaceType.Base1ClipMap))) return $"solid color A=0x{surf.ColorValue.Alpha:X2} R=0x{surf.ColorValue.Red:X2} G=0x{surf.ColorValue.Green:X2} B=0x{surf.ColorValue.Blue:X2}"; uint stid = (uint)surf.OrigTextureId; if (stid == 0) return "no-texture"; if (!dats.TryGet(stid, out var st) || st is null) return $"SurfaceTex 0x{stid:X8} missing"; if (st.Textures.Count == 0) return $"SurfaceTex 0x{stid:X8} empty"; uint rsid = (uint)st.Textures[0]; if (!dats.TryGet(rsid, out var rs) || rs is null) return $"RenderSurf 0x{rsid:X8} missing"; double brightRatio = ApproxBrightRatio(rs); return $"{rs.Width}x{rs.Height} {rs.Format} data={rs.SourceData.Length}B palette=0x{rs.DefaultPaletteId:X8} brightRatio~{brightRatio:F3}"; } static double ApproxBrightRatio(RenderSurface rs) { if (rs.SourceData is null || rs.SourceData.Length == 0) return 0; if (rs.Format == PixelFormat.PFID_A8R8G8B8) { int bright = 0, total = rs.SourceData.Length / 4; for (int i = 0; i + 4 <= rs.SourceData.Length; i += 4) { byte a = rs.SourceData[i]; byte r = rs.SourceData[i + 1]; byte g = rs.SourceData[i + 2]; byte b = rs.SourceData[i + 3]; if (a > 0 && (r + g + b) / 3 > 48) bright++; } return total > 0 ? (double)bright / total : 0; } if (rs.Format == PixelFormat.PFID_R8G8B8) { int bright = 0, total = rs.SourceData.Length / 3; for (int i = 0; i + 3 <= rs.SourceData.Length; i += 3) { byte r = rs.SourceData[i]; byte g = rs.SourceData[i + 1]; byte b = rs.SourceData[i + 2]; if ((r + g + b) / 3 > 48) bright++; } return total > 0 ? (double)bright / total : 0; } int nonZero = 0; for (int i = 0; i < rs.SourceData.Length; i++) if (rs.SourceData[i] != 0) nonZero++; return (double)nonZero / rs.SourceData.Length; }