using System; using System.Collections.Generic; namespace Oni.Totoro { internal class AnimationDatReader { private readonly Animation animation = new Animation(); private readonly InstanceDescriptor tram; private readonly BinaryReader dat; #region private class DatExtent private class DatExtent { public int Frame; public readonly AttackExtent Extent = new AttackExtent(); } #endregion #region private class DatExtentInfo private class DatExtentInfo { public float MaxHorizontal; public float MinY = 1e09f; public float MaxY = -1e09f; public readonly DatExtentInfoFrame FirstExtent = new DatExtentInfoFrame(); public readonly DatExtentInfoFrame MaxExtent = new DatExtentInfoFrame(); } #endregion #region private class DatExtentInfoFrame private class DatExtentInfoFrame { public int Frame = -1; public int Attack; public int AttackOffset; public Vector2 Location; public float Height; public float Length; public float MinY; public float MaxY; public float Angle; } #endregion private AnimationDatReader(InstanceDescriptor tram, BinaryReader dat) { this.tram = tram; this.dat = dat; } public static Animation Read(InstanceDescriptor tram) { using (var dat = tram.OpenRead()) { var reader = new AnimationDatReader(tram, dat); reader.ReadAnimation(); return reader.animation; } } private void ReadAnimation() { dat.Skip(4); int heightOffset = dat.ReadInt32(); int velocityOffset = dat.ReadInt32(); int attackOffset = dat.ReadInt32(); int damageOffset = dat.ReadInt32(); int motionBlurOffset = dat.ReadInt32(); int shortcutOffset = dat.ReadInt32(); ReadOptionalThrowInfo(); int footstepOffset = dat.ReadInt32(); int particleOffset = dat.ReadInt32(); int positionOffset = dat.ReadInt32(); int rotationOffset = dat.ReadInt32(); int soundOffset = dat.ReadInt32(); animation.Flags = (AnimationFlags)dat.ReadInt32(); var directAnimations = dat.ReadLinkArray(2); for (int i = 0; i < directAnimations.Length; i++) animation.DirectAnimations[i] = (directAnimations[i] != null ? directAnimations[i].FullName : null); animation.OverlayUsedBones = (BoneMask)dat.ReadInt32(); animation.OverlayReplacedBones = (BoneMask)dat.ReadInt32(); animation.FinalRotation = dat.ReadSingle(); animation.Direction = (Direction)dat.ReadUInt16(); animation.Vocalization = dat.ReadUInt16(); var extents = ReadExtentInfo(); animation.Impact = dat.ReadString(16); animation.HardPause = dat.ReadUInt16(); animation.SoftPause = dat.ReadUInt16(); int soundCount = dat.ReadInt32(); dat.Skip(6); int fps = dat.ReadUInt16(); animation.FrameSize = dat.ReadUInt16(); animation.Type = (AnimationType)dat.ReadUInt16(); animation.AimingType = (AnimationType)dat.ReadUInt16(); animation.FromState = (AnimationState)dat.ReadUInt16(); animation.ToState = (AnimationState)dat.ReadUInt16(); int boneCount = dat.ReadUInt16(); int frameCount = dat.ReadUInt16(); int duration = dat.ReadInt16(); animation.Varient = (AnimationVarient)dat.ReadUInt16(); dat.Skip(2); animation.AtomicStart = dat.ReadUInt16(); animation.AtomicEnd = dat.ReadUInt16(); animation.InterpolationEnd = dat.ReadUInt16(); animation.InterpolationMax = dat.ReadUInt16(); animation.ActionFrame = dat.ReadUInt16(); animation.FirstLevelAvailable = dat.ReadUInt16(); animation.InvulnerableStart = dat.ReadByte(); animation.InvulnerableEnd = dat.ReadByte(); int attackCount = dat.ReadByte(); int damageCount = dat.ReadByte(); int motionBlurCount = dat.ReadByte(); int shortcutCount = dat.ReadByte(); int footstepCount = dat.ReadByte(); int particleCount = dat.ReadByte(); ReadRawArray(heightOffset, frameCount, animation.Heights, r => r.ReadSingle()); ReadRawArray(velocityOffset, frameCount, animation.Velocities, r => r.ReadVector2()); ReadRotations(rotationOffset, boneCount, frameCount); ReadRawArray(positionOffset, frameCount, animation.Positions, ReadPosition); ReadRawArray(shortcutOffset, shortcutCount, animation.Shortcuts, ReadShortcut); ReadRawArray(damageOffset, damageCount, animation.SelfDamage, ReadDamage); ReadRawArray(particleOffset, particleCount, animation.Particles, ReadParticle); ReadRawArray(footstepOffset, footstepCount, animation.Footsteps, ReadFootstep); ReadRawArray(soundOffset, soundCount, animation.Sounds, ReadSound); ReadRawArray(motionBlurOffset, motionBlurCount, animation.MotionBlur, ReadMotionBlur); ReadRawArray(attackOffset, attackCount, animation.Attacks, ReadAttack); foreach (var attack in animation.Attacks) { for (int i = attack.Start; i <= attack.End; i++) { var extent = extents.FirstOrDefault(e => e.Frame == i); if (extent != null) attack.Extents.Add(extent.Extent); } } } private void ReadRotations(int offset, int boneCount, int frameCount) { using (var raw = tram.GetRawReader(offset)) { int basePosition = raw.Position; var boneOffsets = raw.ReadUInt16Array(boneCount); foreach (int boneOffset in boneOffsets) { raw.Position = basePosition + boneOffset; var keys = new List(); int time = 0; do { var key = new KeyFrame(); if (animation.FrameSize == 6) { key.Rotation.X = raw.ReadInt16() * 180.0f / 32767.5f; key.Rotation.Y = raw.ReadInt16() * 180.0f / 32767.5f; key.Rotation.Z = raw.ReadInt16() * 180.0f / 32767.5f; } else if (animation.FrameSize == 16) { key.Rotation = raw.ReadQuaternion().ToVector4(); } if (time == frameCount - 1) key.Duration = 1; else key.Duration = raw.ReadByte(); time += key.Duration; keys.Add(key); } while (time < frameCount); animation.Rotations.Add(keys); } } } private List ReadExtentInfo() { var info = new DatExtentInfo { MaxHorizontal = dat.ReadSingle(), MinY = dat.ReadSingle(), MaxY = dat.ReadSingle() }; for (int i = 0; i < animation.AttackRing.Length; i++) animation.AttackRing[i] = dat.ReadSingle(); info.FirstExtent.Frame = dat.ReadUInt16(); info.FirstExtent.Attack = dat.ReadByte(); info.FirstExtent.AttackOffset = dat.ReadByte(); info.FirstExtent.Location = dat.ReadVector2(); info.FirstExtent.Height = dat.ReadSingle(); info.FirstExtent.Length = dat.ReadSingle(); info.FirstExtent.MinY = dat.ReadSingle(); info.FirstExtent.MaxY = dat.ReadSingle(); info.FirstExtent.Angle = dat.ReadSingle(); info.MaxExtent.Frame = dat.ReadUInt16(); info.MaxExtent.Attack = dat.ReadByte(); info.MaxExtent.AttackOffset = dat.ReadByte(); info.MaxExtent.Location = dat.ReadVector2(); info.MaxExtent.Height = dat.ReadSingle(); info.MaxExtent.Length = dat.ReadSingle(); info.MaxExtent.MinY = dat.ReadSingle(); info.MaxExtent.MaxY = dat.ReadSingle(); info.MaxExtent.Angle = dat.ReadSingle(); dat.Skip(4); int extentCount = dat.ReadInt32(); int extentOffset = dat.ReadInt32(); var extents = new List(); ReadRawArray(extentOffset, extentCount, extents, ReadExtent); foreach (var datExtent in extents) { var attackExtent = datExtent.Extent; if (datExtent.Frame == info.FirstExtent.Frame) { attackExtent.Angle = MathHelper.ToDegrees(info.FirstExtent.Angle); attackExtent.Length = info.FirstExtent.Length; attackExtent.MinY = info.FirstExtent.MinY; attackExtent.MaxY = info.FirstExtent.MaxY; } else if (datExtent.Frame == info.MaxExtent.Frame) { attackExtent.Angle = MathHelper.ToDegrees(info.MaxExtent.Angle); attackExtent.Length = info.MaxExtent.Length; attackExtent.MinY = info.MaxExtent.MinY; attackExtent.MaxY = info.MaxExtent.MaxY; } if (Math.Abs(attackExtent.MinY - info.MinY) < 0.01f) attackExtent.MinY = info.MinY; if (Math.Abs(attackExtent.MaxY - info.MaxY) < 0.01f) attackExtent.MaxY = info.MaxY; } return extents; } private void ReadOptionalThrowInfo() { int offset = dat.ReadInt32(); if (offset != 0) { using (var raw = tram.GetRawReader(offset)) animation.ThrowSource = ReadThrowInfo(raw); } } private ThrowInfo ReadThrowInfo(BinaryReader raw) { return new ThrowInfo { Position = raw.ReadVector3(), Angle = raw.ReadSingle(), Distance = raw.ReadSingle(), Type = (AnimationType)raw.ReadUInt16() }; } private Shortcut ReadShortcut(BinaryReader raw) { return new Shortcut { FromState = (AnimationState)raw.ReadUInt16(), Length = raw.ReadUInt16(), ReplaceAtomic = (raw.ReadInt32() != 0) }; } private Footstep ReadFootstep(BinaryReader raw) { return new Footstep { Frame = raw.ReadUInt16(), Type = (FootstepType)raw.ReadUInt16() }; } private Sound ReadSound(BinaryReader raw) { return new Sound { Name = raw.ReadString(32), Start = raw.ReadUInt16() }; } private MotionBlur ReadMotionBlur(BinaryReader raw) { var motionBlur = new MotionBlur { Bones = (BoneMask)raw.ReadInt32(), Start = raw.ReadUInt16(), End = raw.ReadUInt16(), Lifetime = raw.ReadByte(), Alpha = raw.ReadByte(), Interval = raw.ReadByte() }; raw.Skip(1); return motionBlur; } private Particle ReadParticle(BinaryReader raw) { return new Particle { Start = raw.ReadUInt16(), End = raw.ReadUInt16(), Bone = (Bone)raw.ReadInt32(), Name = raw.ReadString(16) }; } private Damage ReadDamage(BinaryReader raw) { return new Damage { Points = raw.ReadUInt16(), Frame = raw.ReadUInt16() }; } private Position ReadPosition(BinaryReader raw) { return new Position { X = raw.ReadInt16() * 0.01f, Z = raw.ReadInt16() * 0.01f, Height = raw.ReadUInt16() * 0.01f, YOffset = raw.ReadInt16() * 0.01f }; } private Attack ReadAttack(BinaryReader raw) { var attack = new Attack { Bones = (BoneMask)raw.ReadInt32(), Knockback = raw.ReadSingle(), Flags = (AttackFlags)raw.ReadInt32(), HitPoints = raw.ReadInt16(), Start = raw.ReadUInt16(), End = raw.ReadUInt16(), HitType = (AnimationType)raw.ReadUInt16(), HitLength = raw.ReadUInt16(), StunLength = raw.ReadUInt16(), StaggerLength = raw.ReadUInt16() }; raw.Skip(6); return attack; } private DatExtent ReadExtent(BinaryReader raw) { var extent = new DatExtent(); extent.Frame = raw.ReadInt16(); extent.Extent.Angle = raw.ReadUInt16() * 360.0f / 65535.0f; extent.Extent.Length = (raw.ReadUInt32() & 0xffffu) * 0.01f; extent.Extent.MinY = raw.ReadInt16() * 0.01f; extent.Extent.MaxY = raw.ReadInt16() * 0.01f; return extent; } private void ReadRawArray(int offset, int count, List list, Func readElement) { if (offset == 0 || count == 0) return; using (var raw = tram.GetRawReader(offset)) { for (int i = 0; i < count; i++) list.Add(readElement(raw)); } } } }