using System; using System.Collections.Generic; using System.IO; using System.Xml; using Oni.Metadata; using Oni.Xml; namespace Oni.Totoro { internal class AnimationXmlReader { private const string ns = ""; private static readonly char[] emptyChars = new char[0]; private XmlReader xml; private string basePath; private Animation animation; private AnimationDaeReader daeReader; private AnimationXmlReader() { } public static Animation Read(XmlReader xml, string baseDir) { var reader = new AnimationXmlReader { xml = xml, basePath = baseDir, animation = new Animation() }; var animation = reader.Read(); animation.ValidateFrames(); return animation; } private Animation Read() { animation.Name = xml.GetAttribute("Name"); xml.ReadStartElement("Animation", ns); if (xml.IsStartElement("DaeImport") || xml.IsStartElement("Import")) ImportDaeAnimation(); xml.ReadStartElement("Lookup"); animation.Type = MetaEnum.Parse(xml.ReadElementContentAsString("Type", ns)); animation.AimingType = MetaEnum.Parse(xml.ReadElementContentAsString("AimingType", ns)); animation.FromState = MetaEnum.Parse(xml.ReadElementContentAsString("FromState", ns)); animation.ToState = MetaEnum.Parse(xml.ReadElementContentAsString("ToState", ns)); animation.Varient = MetaEnum.Parse(xml.ReadElementContentAsString("Varient", ns)); animation.FirstLevelAvailable = xml.ReadElementContentAsInt("FirstLevel", ns); ReadRawArray("Shortcuts", animation.Shortcuts, Read); xml.ReadEndElement(); animation.Flags = MetaEnum.Parse(xml.ReadElementContentAsString("Flags", ns)); xml.ReadStartElement("Atomic", ns); animation.AtomicStart = xml.ReadElementContentAsInt("Start", ns); animation.AtomicEnd = xml.ReadElementContentAsInt("End", ns); xml.ReadEndElement(); xml.ReadStartElement("Invulnerable", ns); animation.InvulnerableStart = xml.ReadElementContentAsInt("Start", ns); animation.InvulnerableEnd = xml.ReadElementContentAsInt("End", ns); xml.ReadEndElement(); xml.ReadStartElement("Overlay", ns); animation.OverlayUsedBones = MetaEnum.Parse(xml.ReadElementContentAsString("UsedBones", ns)); animation.OverlayReplacedBones = MetaEnum.Parse(xml.ReadElementContentAsString("ReplacedBones", ns)); xml.ReadEndElement(); xml.ReadStartElement("DirectAnimations", ns); animation.DirectAnimations[0] = xml.ReadElementContentAsString("Link", ns); animation.DirectAnimations[1] = xml.ReadElementContentAsString("Link", ns); xml.ReadEndElement(); xml.ReadStartElement("Pause"); animation.HardPause = xml.ReadElementContentAsInt("Hard", ns); animation.SoftPause = xml.ReadElementContentAsInt("Soft", ns); xml.ReadEndElement(); xml.ReadStartElement("Interpolation", ns); animation.InterpolationEnd = xml.ReadElementContentAsInt("End", ns); animation.InterpolationMax = xml.ReadElementContentAsInt("Max", ns); xml.ReadEndElement(); animation.FinalRotation = MathHelper.ToRadians(xml.ReadElementContentAsFloat("FinalRotation", ns)); animation.Direction = MetaEnum.Parse(xml.ReadElementContentAsString("Direction", ns)); animation.Vocalization = xml.ReadElementContentAsInt("Vocalization", ns); animation.ActionFrame = xml.ReadElementContentAsInt("ActionFrame", ns); animation.Impact = xml.ReadElementContentAsString("Impact", ns); ReadRawArray("Particle", animation.Particles, Read); ReadRawArray("MotionBlur", animation.MotionBlur, Read); ReadRawArray("Footsteps", animation.Footsteps, Read); ReadRawArray("Sounds", animation.Sounds, Read); if (daeReader == null) { ReadHeights(); ReadVelocities(); ReadRotations(); ReadPositions(); } ReadThrowInfo(); ReadRawArray("SelfDamage", animation.SelfDamage, Read); if (xml.IsStartElement("Attacks")) { ReadRawArray("Attacks", animation.Attacks, Read); ReadAttackRing(); } xml.ReadEndElement(); if (daeReader != null) { daeReader.Read(animation); } return animation; } private void ReadVelocities() { if (!xml.IsStartElement("Velocities")) return; if (xml.SkipEmpty()) return; xml.ReadStartElement(); while (xml.IsStartElement()) animation.Velocities.Add(xml.ReadElementContentAsVector2()); xml.ReadEndElement(); } private void ReadPositions() { var xz = new Vector2(); if (xml.IsStartElement("PositionOffset")) { xml.ReadStartElement(); xz.X = xml.ReadElementContentAsFloat("X", ns); xz.Y = xml.ReadElementContentAsFloat("Z", ns); xml.ReadEndElement(); } ReadRawArray("Positions", animation.Positions, ReadPosition); for (int i = 0; i < animation.Positions.Count; i++) { var position = animation.Positions[i]; position.X = xz.X; position.Z = xz.Y; xz += animation.Velocities[i]; } } private void ReadRotations() { var rotations = animation.Rotations; xml.ReadStartElement("Rotations"); while (xml.IsStartElement()) { xml.ReadStartElement("Bone"); var keys = new List(); int count = 0; while (xml.IsStartElement()) { string name = xml.LocalName; string[] tokens = xml.ReadElementContentAsString().Split(emptyChars, StringSplitOptions.RemoveEmptyEntries); var key = new KeyFrame(); key.Duration = XmlConvert.ToByte(tokens[0]); switch (name) { case "EKey": animation.FrameSize = 6; key.Rotation.X = XmlConvert.ToSingle(tokens[1]); key.Rotation.Y = XmlConvert.ToSingle(tokens[2]); key.Rotation.Z = XmlConvert.ToSingle(tokens[3]); break; case "QKey": animation.FrameSize = 16; key.Rotation.X = XmlConvert.ToSingle(tokens[1]); key.Rotation.Y = XmlConvert.ToSingle(tokens[2]); key.Rotation.Z = XmlConvert.ToSingle(tokens[3]); key.Rotation.W = -XmlConvert.ToSingle(tokens[4]); break; default: throw new InvalidDataException(string.Format("Unknonw animation key type '{0}'", name)); } count += key.Duration; keys.Add(key); } if (count != animation.Velocities.Count) throw new InvalidDataException("bad number of frames"); rotations.Add(keys); xml.ReadEndElement(); } xml.ReadEndElement(); } private void ReadHeights() { if (!xml.IsStartElement("Heights")) return; if (xml.SkipEmpty()) return; xml.ReadStartElement(); while (xml.IsStartElement()) animation.Heights.Add(xml.ReadElementContentAsFloat("Height", ns)); xml.ReadEndElement(); } private void ReadThrowInfo() { if (!xml.IsStartElement("ThrowSource")) return; if (xml.SkipEmpty()) return; animation.ThrowSource = new ThrowInfo(); xml.ReadStartElement("ThrowSource"); xml.ReadStartElement("TargetAdjustment"); animation.ThrowSource.Position = xml.ReadElementContentAsVector3("Position"); animation.ThrowSource.Angle = xml.ReadElementContentAsFloat("Angle", ns); xml.ReadEndElement(); animation.ThrowSource.Distance = xml.ReadElementContentAsFloat("Distance", ns); animation.ThrowSource.Type = MetaEnum.Parse(xml.ReadElementContentAsString("TargetType", ns)); xml.ReadEndElement(); } private void ReadAttackRing() { if (!xml.IsStartElement("AttackRing") && !xml.IsStartElement("HorizontalExtents")) return; if (animation.Attacks.Count == 0) { Console.Error.WriteLine("Warning: AttackRing found but no attacks are present, ignoring"); xml.Skip(); return; } xml.ReadStartElement(); for (int i = 0; i < 36; i++) animation.AttackRing[i] = xml.ReadElementContentAsFloat(); xml.ReadEndElement(); } private void ReadPosition(Position position) { xml.ReadStartElement("Position"); position.Height = xml.ReadElementContentAsFloat("Height", ns); position.YOffset = xml.ReadElementContentAsFloat("YOffset", ns); xml.ReadEndElement(); } private void Read(Particle particle) { xml.ReadStartElement("Particle"); particle.Start = xml.ReadElementContentAsInt("Start", ns); particle.End = xml.ReadElementContentAsInt("End", ns); particle.Bone = MetaEnum.Parse(xml.ReadElementContentAsString("Bone", ns)); particle.Name = xml.ReadElementContentAsString("Name", ns); xml.ReadEndElement(); } private void Read(Sound sound) { xml.ReadStartElement("Sound"); sound.Name = xml.ReadElementContentAsString("Name", ns); sound.Start = xml.ReadElementContentAsInt("Start", ns); xml.ReadEndElement(); } private void Read(Shortcut shortcut) { xml.ReadStartElement("Shortcut"); shortcut.FromState = MetaEnum.Parse(xml.ReadElementContentAsString("FromState", ns)); shortcut.Length = xml.ReadElementContentAsInt("Length", ns); shortcut.ReplaceAtomic = (xml.ReadElementContentAsString("ReplaceAtomic", ns) == "yes"); xml.ReadEndElement(); } private void Read(Footstep footstep) { xml.ReadStartElement("Footstep"); string frame = xml.GetAttribute("Frame"); if (frame != null) { footstep.Frame = XmlConvert.ToInt32(frame); footstep.Type = MetaEnum.Parse(xml.GetAttribute("Type")); } else { footstep.Frame = xml.ReadElementContentAsInt("Frame", ns); footstep.Type = MetaEnum.Parse(xml.ReadElementContentAsString("Type", ns)); } xml.ReadEndElement(); } private void Read(Damage damage) { xml.ReadStartElement("Damage"); damage.Points = xml.ReadElementContentAsInt("Points", ns); damage.Frame = xml.ReadElementContentAsInt("Frame", ns); xml.ReadEndElement(); } private void Read(MotionBlur d) { xml.ReadStartElement("MotionBlur"); d.Bones = MetaEnum.Parse(xml.ReadElementContentAsString("Bones", ns)); d.Start = xml.ReadElementContentAsInt("Start", ns); d.End = xml.ReadElementContentAsInt("End", ns); d.Lifetime = xml.ReadElementContentAsInt("Lifetime", ns); d.Alpha = xml.ReadElementContentAsInt("Alpha", ns); d.Interval = xml.ReadElementContentAsInt("Interval", ns); xml.ReadEndElement(); } private void Read(Attack attack) { xml.ReadStartElement("Attack"); attack.Start = xml.ReadElementContentAsInt("Start", ns); attack.End = xml.ReadElementContentAsInt("End", ns); attack.Bones = MetaEnum.Parse(xml.ReadElementContentAsString("Bones", ns)); attack.Flags = MetaEnum.Parse(xml.ReadElementContentAsString("Flags", ns)); attack.Knockback = xml.ReadElementContentAsFloat("Knockback", ns); attack.HitPoints = xml.ReadElementContentAsInt("HitPoints", ns); attack.HitType = MetaEnum.Parse(xml.ReadElementContentAsString("HitType", ns)); attack.HitLength = xml.ReadElementContentAsInt("HitLength", ns); attack.StunLength = xml.ReadElementContentAsInt("StunLength", ns); attack.StaggerLength = xml.ReadElementContentAsInt("StaggerLength", ns); if (xml.IsStartElement("Extents")) { ReadRawArray("Extents", attack.Extents, Read); if (attack.Extents.Count != attack.End - attack.Start + 1) Console.Error.WriteLine("Error: Attack starting at frame {0} has an incorrect number of extents ({1})", attack.Start, attack.Extents.Count); } xml.ReadEndElement(); } private void Read(AttackExtent extent) { xml.ReadStartElement("Extent"); extent.Angle = xml.ReadElementContentAsFloat("Angle", ns); extent.Length = xml.ReadElementContentAsFloat("Length", ns); extent.MinY = xml.ReadElementContentAsFloat("MinY", ns); extent.MaxY = xml.ReadElementContentAsFloat("MaxY", ns); xml.ReadEndElement(); } private void ReadRawArray(string name, List list, Action elementReader) where T : new() { if (xml.SkipEmpty()) return; xml.ReadStartElement(); while (xml.IsStartElement()) { T t = new T(); elementReader(t); list.Add(t); } xml.ReadEndElement(); } private void ImportDaeAnimation() { string filePath = xml.GetAttribute("Path"); bool empty = xml.SkipEmpty(); if (!empty) { xml.ReadStartElement(); if (filePath == null) filePath = xml.ReadElementContentAsString("Path", ns); } filePath = Path.Combine(basePath, filePath); if (!File.Exists(filePath)) { Console.Error.WriteLine("Could not find animation import source file '{0}'", filePath); return; } Console.WriteLine("Importing {0}", filePath); daeReader = new AnimationDaeReader(); daeReader.Scene = Dae.Reader.ReadFile(filePath); if (!empty) { if (xml.IsStartElement("Start")) daeReader.StartFrame = xml.ReadElementContentAsInt("Start", ns); if (xml.IsStartElement("End")) daeReader.EndFrame = xml.ReadElementContentAsInt("End", ns); xml.ReadEndElement(); } } } }