using System; using System.Collections.Generic; namespace Oni.Level { internal class LevelDatWriter { private readonly Importer importer; private readonly DatLevel level; public class DatLevel { public string name; public string skyName; public readonly List physics = new List(); public readonly List particles = new List(); public readonly List characters = new List(); public readonly List corpses = new List(); public Akira.PolygonMesh model; } private LevelDatWriter(Importer importer, DatLevel level) { this.importer = importer; this.level = level; } public static void Write(Importer importer, DatLevel level) { var writer = new LevelDatWriter(importer, level); writer.WriteONLV(); } private void WriteONLV() { var onlv = importer.CreateInstance(TemplateTag.ONLV, level.name); var oboa = importer.CreateInstance(TemplateTag.OBOA); var aisa = importer.CreateInstance(TemplateTag.AISA); var onoa = importer.CreateInstance(TemplateTag.ONOA); var envp = importer.CreateInstance(TemplateTag.ENVP); var crsa = importer.CreateInstance(TemplateTag.CRSA); var onsk = importer.CreateInstance(TemplateTag.ONSK, level.skyName); var akev = importer.CreateInstance(TemplateTag.AKEV, level.name); using (var writer = onlv.OpenWrite()) { writer.Write(level.name, 64); writer.Write(akev); writer.Write(oboa); writer.Write(0); writer.Write(0); writer.Write(0); writer.Write(onsk); writer.Write(0.0f); writer.Write(aisa); writer.Write(0); writer.Write(0); writer.Write(0); writer.Write(onoa); writer.Write(envp); writer.Skip(644); writer.Write(crsa); } WriteOBOA(oboa); WriteAISA(aisa); WriteONOA(onoa); WriteENVP(envp, level.particles); WriteCRSA(crsa, level.corpses); } private void WriteOBOA(ImporterDescriptor oboa) { var objects = level.physics; var m3ga = new ImporterDescriptor[objects.Count]; var oban = new ImporterDescriptor[objects.Count]; var envp = new ImporterDescriptor[objects.Count]; for (int i = 0; i < objects.Count; i++) { var obj = objects[i]; var m3gms = new List(); foreach (var geom in obj.Geometries) { if (geom is string) m3gms.Add(importer.CreateInstance(TemplateTag.M3GM, (string)geom)); else m3gms.Add(Motoko.GeometryDatWriter.Write((Motoko.Geometry)geom, importer.ImporterFile)); } m3ga[i] = importer.CreateInstance(TemplateTag.M3GA); WriteM3GA(m3ga[i], m3gms); if (obj.Animation != null) oban[i] = importer.CreateInstance(TemplateTag.OBAN, obj.Animation.Name); if (obj.Particles.Count > 0) { envp[i] = importer.CreateInstance(TemplateTag.ENVP); WriteENVP(envp[i], obj.Particles); } } int unused = 32; using (var writer = oboa.OpenWrite(22)) { writer.WriteUInt16(objects.Count + unused); for (int i = 0; i != objects.Count; i++) { var obj = objects[i]; writer.Write(m3ga[i]); writer.Write(oban[i]); writer.Write(envp[i]); writer.Write((uint)(obj.Flags | Physics.ObjectSetupFlags.InUse)); //writer.Write(obj.DoorGunkIndex); writer.Write(0); writer.Write(obj.DoorScriptId); writer.Write((uint)obj.PhysicsType); writer.Write(obj.ScriptId); writer.Write(obj.Position); writer.Write(obj.Orientation); writer.Write(obj.Scale); writer.WriteMatrix4x3(obj.Origin); writer.Write(obj.Name, 64); writer.Write(obj.FileName, 64); } writer.Skip(unused * 240); } } private void WriteM3GA(ImporterDescriptor m3ga, ICollection geometries) { using (var writer = m3ga.OpenWrite(20)) { writer.Write(geometries.Count); writer.Write(geometries); } } private void WriteAISA(ImporterDescriptor aisa) { var characterClasses = new Dictionary(StringComparer.Ordinal); var weaponClasses = new Dictionary(StringComparer.Ordinal); foreach (var chr in level.characters) { if (!characterClasses.ContainsKey(chr.className)) characterClasses.Add(chr.className, importer.CreateInstance(TemplateTag.ONCC, chr.className)); if (!string.IsNullOrEmpty(chr.weaponClassName) && !weaponClasses.ContainsKey(chr.weaponClassName)) weaponClasses.Add(chr.weaponClassName, importer.CreateInstance(TemplateTag.ONWC, chr.weaponClassName)); } using (var writer = aisa.OpenWrite(22)) { writer.WriteUInt16(level.characters.Count); foreach (var chr in level.characters) { ImporterDescriptor characterClass, weaponClass; characterClasses.TryGetValue(chr.className, out characterClass); if (!string.IsNullOrEmpty(chr.weaponClassName)) weaponClasses.TryGetValue(chr.weaponClassName, out weaponClass); else weaponClass = null; writer.Write(chr.name, 32); writer.WriteInt16(chr.scriptId); writer.WriteInt16(chr.flagId); writer.WriteUInt16((ushort)chr.flags); writer.WriteUInt16((ushort)chr.team); writer.Write(characterClass); writer.Skip(36); writer.Write(chr.onSpawn, 32); writer.Write(chr.onDeath, 32); writer.Write(chr.onSeenEnemy, 32); writer.Write(chr.onAlarmed, 32); writer.Write(chr.onHurt, 32); writer.Write(chr.onDefeated, 32); writer.Write(chr.onOutOfAmmo, 32); writer.Write(chr.onNoPath, 32); writer.Write(weaponClass); writer.WriteInt16(chr.ammo); writer.Skip(10); } } } private void WriteONOA(ImporterDescriptor onoa) { var map = new Dictionary>(); int pi = 0; foreach (var poly in level.model.Polygons) { if (poly.ObjectId > 0) { List indices; int objectId = poly.ObjectType << 24 | poly.ObjectId; if (!map.TryGetValue(objectId, out indices)) { indices = new List(); map[objectId] = indices; } indices.Add(pi); } pi++; } var elt = new List>(); foreach (var pair in map) { var idxa = importer.CreateInstance(TemplateTag.IDXA); elt.Add(new KeyValuePair(pair.Key, idxa)); using (var idxaWriter = idxa.OpenWrite(20)) { idxaWriter.Write(pair.Value.Count); idxaWriter.Write(pair.Value.ToArray()); } } using (var writer = onoa.OpenWrite(20)) { writer.Write(elt.Count); foreach (var e in elt) { writer.Write(e.Key); writer.Write(e.Value); } } } private void WriteENVP(ImporterDescriptor envp, List particles) { using (var writer = envp.OpenWrite(22)) { writer.WriteUInt16(particles.Count); foreach (var particle in particles) { writer.Write(particle.ParticleClass, 64); writer.Write(particle.Tag, 48); writer.WriteMatrix4x3(particle.Matrix); writer.Write(particle.DecalScale); writer.Write((ushort)particle.Flags); writer.Skip(38); } } } private void WriteCRSA(ImporterDescriptor crsa, List corpses) { // // Ensure that there are at least 20 corpses // while (corpses.Count < 20) corpses.Add(new Corpse()); int fixedCount = corpses.Count(c => c.IsFixed); int usedCount = corpses.Count(c => c.IsUsed); // // Ensure that there are at least 5 unused corpses // so new corpses can be created at runtime // while (corpses.Count - usedCount < 5) corpses.Add(new Corpse()); corpses.Sort((x, y) => x.Order.CompareTo(y.Order)); var onccDesriptors = new Dictionary(); using (var writer = crsa.OpenWrite(12)) { writer.Write(fixedCount); writer.Write(usedCount); writer.Write(corpses.Count); foreach (var corpse in corpses) { writer.Write(corpse.FileName ?? "", 32); writer.Skip(128); if (corpse.IsUsed) { ImporterDescriptor oncc = null; if (!string.IsNullOrEmpty(corpse.CharacterClass)) { if (!onccDesriptors.TryGetValue(corpse.CharacterClass, out oncc)) { oncc = importer.CreateInstance(TemplateTag.ONCC, corpse.CharacterClass); onccDesriptors.Add(corpse.CharacterClass, oncc); } } writer.Write(oncc); foreach (var transform in corpse.Transforms) writer.WriteMatrix4x3(transform); writer.Write(corpse.BoundingBox); } else { writer.Skip(940); } } } } } }