using AcDream.App.Rendering; using AcDream.App.UI; using AcDream.Content.Vfx; using AcDream.Core.World; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Options; using Xunit.Sdk; namespace AcDream.App.Tests.Rendering; public sealed class PortalTunnelAssetTests { [Fact] public void InstalledDat_ResolvesRetailPortalSetupAndAnimation() { string? datDir = ResolveDatDir(); if (datDir is null) throw SkipException.ForSkip( "Installed client_portal.dat is required for the portal-space asset gate."); using var dats = new DatCollection(datDir, DatAccessType.Read); uint setupDid = RetailDataIdResolver.Resolve( dats, PortalTunnelPresentation.SetupClientEnum, PortalTunnelPresentation.ClientEnumCategory); uint animationDid = RetailDataIdResolver.Resolve( dats, PortalTunnelPresentation.AnimationClientEnum, PortalTunnelPresentation.ClientEnumCategory); Assert.NotEqual(0u, setupDid); Assert.NotEqual(0u, animationDid); Assert.Equal(0x02000306u, setupDid); Assert.Equal(0x030005ACu, animationDid); Setup setup = Assert.IsType(dats.Get(setupDid)); Animation animation = Assert.IsType( new RetailAnimationLoader(dats).LoadAnimation(animationDid)); Assert.NotEmpty(setup.Parts); Assert.True( animation.PartFrames.Count >= TeleportAnimSequencer.TunnelEndFrame, $"Retail tunnel timing samples frame {TeleportAnimSequencer.TunnelEndFrame}, " + $"but animation 0x{animationDid:X8} has only {animation.PartFrames.Count} frames."); Assert.All( setup.Parts, part => Assert.Equal(0x01u, ((uint)part >> 24) & 0xFFu)); Assert.Equal(0u, (uint)setup.DefaultAnimation); Assert.Equal(0u, (uint)setup.DefaultScript); Assert.Equal(0u, (uint)setup.DefaultScriptTable); var hook = Assert.Single(animation.PartFrames.SelectMany(frame => frame.Hooks)); Assert.Equal(DatReaderWriter.Enums.AnimationHookType.SoundTweaked, hook.HookType); } [Fact] public void PortalTunnelCamera_UsesRetailEyeAndRotatesAroundAcUpAxis() { var camera = new PortalTunnelCamera { DirectionDegrees = 0f, Aspect = 1f, }; Assert.True(System.Numerics.Matrix4x4.Invert(camera.View, out var world)); Assert.Equal(PortalTunnelCamera.RetailEye.X, world.Translation.X, precision: 5); Assert.Equal(PortalTunnelCamera.RetailEye.Y, world.Translation.Y, precision: 5); Assert.Equal(PortalTunnelCamera.RetailEye.Z, world.Translation.Z, precision: 5); camera.DirectionDegrees = 90f; Assert.True(System.Numerics.Matrix4x4.Invert(camera.View, out var rotatedWorld)); System.Numerics.Vector3 up = System.Numerics.Vector3.Normalize( System.Numerics.Vector3.TransformNormal(System.Numerics.Vector3.UnitY, rotatedWorld)); Assert.Equal(0f, up.X, precision: 5); Assert.Equal(0f, up.Y, precision: 5); Assert.Equal(1f, up.Z, precision: 5); } [Theory] [InlineData(45f)] [InlineData(60f)] [InlineData(90f)] public void PortalTunnelCamera_UsesActiveSmartBoxFieldOfView(float degrees) { float expected = degrees * (MathF.PI / 180f); var smartBoxProjection = System.Numerics.Matrix4x4.CreatePerspectiveFieldOfView( expected, 16f / 9f, 0.1f, 5000f); var camera = new PortalTunnelCamera(); camera.UseSmartBoxFov(smartBoxProjection); Assert.Equal(expected, camera.FovRadians, precision: 5); } private static string? ResolveDatDir() { string? configured = System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); if (!string.IsNullOrWhiteSpace(configured) && File.Exists(Path.Combine(configured, "client_portal.dat"))) return configured; const string installed = @"C:\Turbine\Asheron's Call"; if (File.Exists(Path.Combine(installed, "client_portal.dat"))) return installed; string conventional = Path.Combine( System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); return File.Exists(Path.Combine(conventional, "client_portal.dat")) ? conventional : null; } }