// What does retail actually put in the animations of STATIC ANIMATING objects? // // A Static object whose Setup declares a DefaultAnimation or DefaultScript // joins CPhysics::static_animating_objects (CPhysicsObj::InitDefaults // @0x00513A7B) and is driven every frame by animate_static_object // @0x00513DF0. This walks every Setup in portal.dat, follows those defaults, // and tallies the hook types they carry -- so "which hooks must be honoured // for this class of object to behave" is read from the data, not assumed. using AcDream.Content; 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"); using var dats = new DatCollection(datDir, DatAccessType.Read); var animHookCounts = new Dictionary(); var scriptHookCounts = new Dictionary(); var omegaSetups = new List<(uint Setup, uint Anim)>(); int setups = 0, withAnim = 0, withScript = 0; foreach (uint id in dats.Portal.Tree.Select(e => e.Id).Where(i => (i >> 24) == 0x02)) { if (!dats.Portal.TryGet(id, out var setup) || setup is null) continue; setups++; uint animId = setup.DefaultAnimation?.DataId ?? 0u; uint scriptId = setup.DefaultScript?.DataId ?? 0u; if (animId != 0) withAnim++; if (scriptId != 0) withScript++; if (animId != 0 && dats.Portal.TryGet(animId, out var anim) && anim is not null) { bool omega = false; foreach (var frame in anim.PartFrames) foreach (var hook in frame.Hooks) { animHookCounts[hook.HookType] = animHookCounts.GetValueOrDefault(hook.HookType) + 1; if (hook.HookType == AnimationHookType.SetOmega) omega = true; } if (omega) omegaSetups.Add((id, animId)); } if (scriptId != 0 && dats.Portal.TryGet(scriptId, out var script) && script is not null) { foreach (var d in script.ScriptData) { AnimationHookType t = d.Hook.HookType; scriptHookCounts[t] = scriptHookCounts.GetValueOrDefault(t) + 1; } } } // Every animation in the dat, not just the Setup defaults: tells us whether // SetOmega is exclusively a static-animating-scenery mechanism or whether // creatures use it too (which would widen where the hook must be honoured). int allAnims = 0, animsWithOmega = 0; foreach (uint id in dats.Portal.Tree.Select(e => e.Id).Where(i => (i >> 24) == 0x03)) { if (!dats.Portal.TryGet(id, out var a2) || a2 is null) continue; allAnims++; bool has = false; foreach (var fr in a2.PartFrames) foreach (var h in fr.Hooks) if (h.HookType == AnimationHookType.SetOmega) has = true; if (has) animsWithOmega++; } Console.WriteLine($"ALL animations={allAnims} containingSetOmega={animsWithOmega}"); Console.WriteLine($"setups={setups} withDefaultAnimation={withAnim} withDefaultScript={withScript}"); Console.WriteLine("\n-- hook types in DefaultAnimation --"); foreach (var kv in animHookCounts.OrderByDescending(k => k.Value)) Console.WriteLine($" {kv.Key,-28} {kv.Value}"); Console.WriteLine("\n-- hook types in DefaultScript --"); foreach (var kv in scriptHookCounts.OrderByDescending(k => k.Value)) Console.WriteLine($" {kv.Key,-28} {kv.Value}"); Console.WriteLine($"\n-- setups whose DefaultAnimation carries SetOmega: {omegaSetups.Count} --"); foreach (var (s, a) in omegaSetups) { Console.Write($" setup 0x{s:X8} -> anim 0x{a:X8}"); if (dats.Portal.TryGet(s, out var su) && su is not null) { Console.Write($" parts={su.Parts.Count}"); // How far the authored part frames sit from the setup origin: a mesh // offset from the point it spins about traces a CIRCLE under grotate. double maxR = 0; foreach (var f in su.PlacementFrames.Values) foreach (var af in f.Frames) maxR = Math.Max(maxR, Math.Sqrt( af.Origin.X * af.Origin.X + af.Origin.Y * af.Origin.Y)); Console.Write($" maxPartRadius={maxR:0.###}m"); } if (dats.Portal.TryGet(a, out var an) && an is not null) foreach (var fr in an.PartFrames) foreach (var h in fr.Hooks) if (h is SetOmegaHook so) Console.Write($" omega=({so.Axis.X:0.###},{so.Axis.Y:0.###},{so.Axis.Z:0.###})"); Console.WriteLine(); }