From f2e85df45733ced77f5512af5776147e6d153e4a Mon Sep 17 00:00:00 2001 From: wincent Date: Sat, 7 Mar 2020 23:26:39 +0100 Subject: [PATCH] World creation: * Generate Luz and Lvl files from xml. * Filter out cdclient for zone entries. --- InfectedRose.Database/Table.cs | 1 + .../InfectedRose.Interface.csproj | 13 +++ InfectedRose.Interface/Program.cs | 87 +++++++++++++++++++ InfectedRose.Interface/ZoneId.cs | 40 +++++++++ InfectedRose.Luz/LuzFile.cs | 20 ++--- InfectedRose.Lvl/LevelSkyConfig.cs | 12 +-- InfectedRose.Lvl/LvlFile.cs | 8 ++ InfectedRose.Lvl/ParticleStruct.cs | 1 - InfectedRose.World/InfectedRose.World.csproj | 13 +++ InfectedRose.World/Scene.cs | 74 ++++++++++++++++ InfectedRose.World/Terrain.cs | 14 +++ InfectedRose.World/WorldObject.cs | 42 +++++++++ InfectedRose.World/Zone.cs | 81 +++++++++++++++++ InfectedRose.sln | 12 +++ 14 files changed, 401 insertions(+), 17 deletions(-) create mode 100644 InfectedRose.Interface/InfectedRose.Interface.csproj create mode 100644 InfectedRose.Interface/Program.cs create mode 100644 InfectedRose.Interface/ZoneId.cs create mode 100644 InfectedRose.World/InfectedRose.World.csproj create mode 100644 InfectedRose.World/Scene.cs create mode 100644 InfectedRose.World/Terrain.cs create mode 100644 InfectedRose.World/WorldObject.cs create mode 100644 InfectedRose.World/Zone.cs diff --git a/InfectedRose.Database/Table.cs b/InfectedRose.Database/Table.cs index 7a39497..37a3576 100644 --- a/InfectedRose.Database/Table.cs +++ b/InfectedRose.Database/Table.cs @@ -394,6 +394,7 @@ private static int GetKey(object key) { var index = key switch { + uint keyUint => (int) keyUint, int keyInt => keyInt, string val => (int) Hash(val.Select(k => (byte) k).ToArray()), FdbString keyStr => (int) Hash(keyStr.Value.Select(k => (byte) k).ToArray()), diff --git a/InfectedRose.Interface/InfectedRose.Interface.csproj b/InfectedRose.Interface/InfectedRose.Interface.csproj new file mode 100644 index 0000000..978e420 --- /dev/null +++ b/InfectedRose.Interface/InfectedRose.Interface.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + diff --git a/InfectedRose.Interface/Program.cs b/InfectedRose.Interface/Program.cs new file mode 100644 index 0000000..b3ce398 --- /dev/null +++ b/InfectedRose.Interface/Program.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using System.Xml.Serialization; +using InfectedRose.Database; +using InfectedRose.Database.Concepts.Tables; +using InfectedRose.World; + +namespace InfectedRose.Interface +{ + internal static class Program + { + private static async Task Main(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine($"Usage: {Path.GetFileName(Assembly.GetEntryAssembly()?.Location)} "); + + Environment.Exit(1); + + return; + } + + var serializer = new XmlSerializer(typeof(Zone)); + + var zone = new Zone(); + + if (File.Exists(args[0])) + { + await using var stream = File.OpenRead(args[0]); + + zone = (Zone) serializer.Deserialize(stream); + } + else + { + await using var stream = File.Create(args[0]); + + serializer.Serialize(stream, zone); + + Console.WriteLine("Created xml file"); + + Environment.Exit(1); + + return; + } + + await zone.SaveAsync(Path.GetDirectoryName(args[0])); + + var database = await AccessDatabase.OpenAsync(args[1]); + + var zones = database["ZoneTable"]; + + foreach (var row in zones) + { + if (Enum.IsDefined(typeof(ZoneId), (ushort) row.Key)) continue; + + zones.Remove(row); + } + + var entry = new ZoneTableTable(zones.Create(zone.Id)); + + entry.zoneName = zone.Name; + entry.zoneID = (int) zone.Id; + entry.ghostdistance = zone.GhostDistance; + entry.scriptID = -1; + entry.locStatus = 0; + entry.DisplayDescription = zone.Description; + + foreach (var table in database) + { + table.Recalculate(); + } + + var pad = 0; + + await database.SaveAsync(args[1], i => + { + if (++pad != 10000) return; + + Console.WriteLine(i); + + pad = 0; + }); + } + } +} \ No newline at end of file diff --git a/InfectedRose.Interface/ZoneId.cs b/InfectedRose.Interface/ZoneId.cs new file mode 100644 index 0000000..1f81434 --- /dev/null +++ b/InfectedRose.Interface/ZoneId.cs @@ -0,0 +1,40 @@ +namespace InfectedRose.Interface +{ + public enum ZoneId : ushort + { + VentureExplorerCinematic, + VentureExplorer = 1000, + ReturnToVentureExplorer, + AvantGardens = 1100, + AvantGardensSurvival, + SpiderQueenBattle, + BlockYard = 1150, + AvantGrove, + NimbusStation = 1200, + PetCove, + VertigoLoopRacetrack = 1203, + BattleOfNimbusStation, + NimbusRock = 1250, + NimbusIsle, + FrostBurgh = 1260, + GnarledForest = 1300, + CanyonCove = 1302, + KeelhaulCanyon, + ChanteyShantey = 1350, + ForbiddenValley = 1400, + ForbiddenValleyDragon = 1402, + DragonmawChasm, + RavenBluff = 1450, + Starbase3001 = 1600, + DeepFreeze, + RobotCity, + MoonBase, + Portabello = 1604, + LegoClub = 1700, + CruxPrime = 1800, + NexusTower = 1900, + Ninjago = 2000, + FrakjawBattle, + NimbusStationWinterRacetrack = 1261 + } +} \ No newline at end of file diff --git a/InfectedRose.Luz/LuzFile.cs b/InfectedRose.Luz/LuzFile.cs index 4451b42..b91823d 100644 --- a/InfectedRose.Luz/LuzFile.cs +++ b/InfectedRose.Luz/LuzFile.cs @@ -27,11 +27,11 @@ public class LuzFile : IConstruct public string TerrainDescription { get; set; } - public LuzSceneTransition[] Transitions { get; set; } - - public uint PathFormatVersion { get; set; } - - public LuzPathData[] PathData { get; set; } + public LuzSceneTransition[] Transitions { get; set; } = new LuzSceneTransition[0]; + + public uint PathFormatVersion { get; set; } = 18; + + public LuzPathData[] PathData { get; set; } = new LuzPathData[0]; public void Serialize(BitWriter writer) { @@ -97,7 +97,9 @@ public void Serialize(BitWriter writer) private void WritePaths(BitWriter writer) { - writer.BaseStream.Position += 4; + using var token = new LengthToken(writer); + + token.Allocate(); writer.Write(PathFormatVersion); @@ -119,9 +121,9 @@ private void WritePaths(BitWriter writer) writer.Write((uint) pathData.Waypoints.Length); - foreach (var waypoint in pathData.Waypoints) + foreach (var wayPoint in pathData.Waypoints) { - waypoint.Serialize(writer); + wayPoint.Serialize(writer); } } } @@ -188,8 +190,6 @@ public void Deserialize(BitReader reader) var version = reader.Read(); var name = reader.ReadNiString(true, true); var type = (PathType) reader.Read(); - - //Console.WriteLine($"[{i}:{name}] -> [{type}]"); PathData[i] = type switch { diff --git a/InfectedRose.Lvl/LevelSkyConfig.cs b/InfectedRose.Lvl/LevelSkyConfig.cs index be794f6..c6a50d7 100644 --- a/InfectedRose.Lvl/LevelSkyConfig.cs +++ b/InfectedRose.Lvl/LevelSkyConfig.cs @@ -6,17 +6,17 @@ namespace InfectedRose.Lvl { public class LevelSkyConfig : ChunkBase { - public IdStruct[] Identifiers { get; set; } + public IdStruct[] Identifiers { get; set; } = new IdStruct[0]; public string[] Skybox { get; set; } = new string[6]; - - public float[] UnknownFloatArray0 { get; set; } + + public float[] UnknownFloatArray0 { get; set; } = new float[0]; public float[] UnknownFloatArray1 { get; set; } = new float[3]; public float[] UnknownFloatArray2 { get; set; } = new float[3]; - - public byte[] UnknownSectionData { get; set; } + + public byte[] UnknownSectionData { get; set; } = new byte[0]; public override uint ChunkType => 2000; @@ -66,7 +66,7 @@ private byte[] WriteSkySection() for (var i = 0; i < 6; i++) { - writer.WriteNiString(Skybox[i]); + writer.WriteNiString(Skybox[i] ?? ""); } return stream.ToArray(); diff --git a/InfectedRose.Lvl/LvlFile.cs b/InfectedRose.Lvl/LvlFile.cs index 3ee3938..55375ef 100644 --- a/InfectedRose.Lvl/LvlFile.cs +++ b/InfectedRose.Lvl/LvlFile.cs @@ -22,6 +22,8 @@ public class LvlFile : IConstruct public LevelEnvironmentConfig LevelEnvironmentConfig { get; set; } private static readonly byte[] ChunkHeader = "CHNK".Select(c => (byte) c).ToArray(); + + private ushort _index; public void Serialize(BitWriter writer) { @@ -44,6 +46,8 @@ public void Serialize(BitWriter writer) private void SerializeNew(BitWriter writer) { + _index = 0; + SerializeChunk(writer, LevelInfo); SerializeChunk(writer, LevelSkyConfig); @@ -75,6 +79,10 @@ private void SerializeChunk(BitWriter writer, ChunkBase chunkBase) { if (chunkBase == default) return; + _index++; + + chunkBase.Index = _index; + using var token = new LengthToken(writer); switch (chunkBase) diff --git a/InfectedRose.Lvl/ParticleStruct.cs b/InfectedRose.Lvl/ParticleStruct.cs index 30de78f..ae08893 100644 --- a/InfectedRose.Lvl/ParticleStruct.cs +++ b/InfectedRose.Lvl/ParticleStruct.cs @@ -1,4 +1,3 @@ -using System; using System.Numerics; using InfectedRose.Core; using RakDotNet.IO; diff --git a/InfectedRose.World/InfectedRose.World.csproj b/InfectedRose.World/InfectedRose.World.csproj new file mode 100644 index 0000000..ed762b5 --- /dev/null +++ b/InfectedRose.World/InfectedRose.World.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + + + + + + + + + diff --git a/InfectedRose.World/Scene.cs b/InfectedRose.World/Scene.cs new file mode 100644 index 0000000..eef7cce --- /dev/null +++ b/InfectedRose.World/Scene.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Serialization; +using InfectedRose.Luz; +using InfectedRose.Lvl; +using RakDotNet.IO; + +namespace InfectedRose.World +{ + [XmlRoot] + public class Scene + { + [XmlAttribute] public string Name { get; set; } + + [XmlAttribute] public uint Id { get; set; } + + [XmlAttribute] public uint Layer { get; set; } + + [XmlAttribute] public uint Revision { get; set; } + + [XmlElement] public string SkyBox { get; set; } + + [XmlElement("Object")] public List Objects { get; set; } + + public LuzScene LuzScene => new LuzScene + { + FileName = $"{Name.Replace(" ", "_")}.lvl", + SceneName = Name, + SceneId = Id, + LayerId = Layer + }; + + public async Task SaveAsync(string path) + { + var scene = LuzScene; + + var file = new LvlFile + { + LvlVersion = 0x26 + }; + + if (Objects?.Count > 0) + { + file.LevelObjects = new LevelObjects(file.LvlVersion) + { + Templates = Objects.Select(o => o.Template).ToArray() + }; + } + + if (!string.IsNullOrWhiteSpace(SkyBox)) + { + var skyConfig = new LevelSkyConfig + { + Skybox = {[0] = SkyBox} + }; + + file.LevelSkyConfig = skyConfig; + } + + file.LevelInfo = new LevelInfo + { + RevisionNumber = Revision + }; + + await using var stream = File.Create(Path.Combine(path, scene.FileName)); + + using var writer = new BitWriter(stream); + + file.Serialize(writer); + } + } +} \ No newline at end of file diff --git a/InfectedRose.World/Terrain.cs b/InfectedRose.World/Terrain.cs new file mode 100644 index 0000000..e4f4142 --- /dev/null +++ b/InfectedRose.World/Terrain.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; + +namespace InfectedRose.World +{ + [XmlRoot] + public class Terrain + { + [XmlElement] public string FileName { get; set; } = "terrain.raw"; + + [XmlElement] public string Name { get; set; } = "terrain"; + + [XmlElement] public string Description { get; set; } = "terrain file"; + } +} \ No newline at end of file diff --git a/InfectedRose.World/WorldObject.cs b/InfectedRose.World/WorldObject.cs new file mode 100644 index 0000000..f95be53 --- /dev/null +++ b/InfectedRose.World/WorldObject.cs @@ -0,0 +1,42 @@ +using System; +using System.Numerics; +using System.Xml.Serialization; +using InfectedRose.Lvl; + +namespace InfectedRose.World +{ + [XmlRoot("Object")] + public class WorldObject + { + [XmlAttribute] public int Lot { get; set; } + + [XmlAttribute] public uint AssetType { get; set; } + + [XmlElement] public Vector3 Position { get; set; } + + [XmlElement] public Quaternion Rotation { get; set; } + + [XmlElement] public float Scale { get; set; } + + [XmlElement] public string Info { get; set; } + + public LevelObjectTemplate Template + { + get + { + var random = new Random(); + + return new LevelObjectTemplate + { + Lot = Lot, + AssetType = AssetType, + Position = Position, + Rotation = Rotation, + LegoInfo = LegoDataDictionary.FromString(Info), + Scale = Scale, + ObjectId = (ulong) random.Next(default, int.MaxValue) + }; + } + } + } +} \ No newline at end of file diff --git a/InfectedRose.World/Zone.cs b/InfectedRose.World/Zone.cs new file mode 100644 index 0000000..2237951 --- /dev/null +++ b/InfectedRose.World/Zone.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using System.Xml.Serialization; +using InfectedRose.Luz; +using RakDotNet.IO; + +namespace InfectedRose.World +{ + [XmlRoot("Zone")] + public class Zone + { + [XmlElement] public string Name { get; set; } = "my_maps/zone/zone.luz"; + + [XmlElement] public uint Id { get; set; } = 0; + + [XmlElement] public uint Revision { get; set; } = 1; + + [XmlElement] public float GhostDistance { get; set; } = 500; + + [XmlElement] public string Description { get; set; } = "amazing zone"; + + [XmlElement("Scene")] public List Scenes { get; set; } = new List + { + new Scene + { + Id = 0, + Layer = 0, + Name = "global scene", + Objects = new List + { + new WorldObject + { + AssetType = 1, + Lot = 31, + Scale = 1, + Info = "", + Rotation = Quaternion.Identity + } + }, + Revision = 1, + SkyBox = "SkyBox" + } + }; + + [XmlElement] public Vector3 SpawnPosition { get; set; } + + [XmlElement] public Quaternion SpawnRotation { get; set; } = Quaternion.Identity; + + [XmlElement] public Terrain Terrain { get; set; } = new Terrain(); + + public async Task SaveAsync(string path) + { + var file = new LuzFile + { + TerrainFile = Terrain.Name, + TerrainFileName = Terrain.FileName, + TerrainDescription = Terrain.Description, + Version = 0x26, + SpawnPoint = SpawnPosition, + SpawnRotation = SpawnRotation, + WorldId = Id, + Scenes = Scenes.Select(s => s.LuzScene).ToArray(), + RevisionNumber = Revision + }; + + await using var stream = File.Create(Path.Combine(path, $"{Path.GetFileName(Name)}")); + + using var writer = new BitWriter(stream); + + file.Serialize(writer); + + foreach (var scene in Scenes) + { + await scene.SaveAsync(path); + } + } + } +} \ No newline at end of file diff --git a/InfectedRose.sln b/InfectedRose.sln index f50d439..3fe30d8 100644 --- a/InfectedRose.sln +++ b/InfectedRose.sln @@ -22,6 +22,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Builder", "Inf EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Pipeline", "InfectedRose.Pipeline\InfectedRose.Pipeline.csproj", "{281871E0-DB69-4CA3-BB94-00B37DE9772C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.World", "InfectedRose.World\InfectedRose.World.csproj", "{B6809E62-6FC6-4445-ABB6-302021E7B91A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Interface", "InfectedRose.Interface\InfectedRose.Interface.csproj", "{1CD2D01F-218B-47A2-AE66-A673F48F84AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,5 +88,13 @@ Global {281871E0-DB69-4CA3-BB94-00B37DE9772C}.Debug|Any CPU.Build.0 = Debug|Any CPU {281871E0-DB69-4CA3-BB94-00B37DE9772C}.Release|Any CPU.ActiveCfg = Release|Any CPU {281871E0-DB69-4CA3-BB94-00B37DE9772C}.Release|Any CPU.Build.0 = Release|Any CPU + {B6809E62-6FC6-4445-ABB6-302021E7B91A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6809E62-6FC6-4445-ABB6-302021E7B91A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6809E62-6FC6-4445-ABB6-302021E7B91A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6809E62-6FC6-4445-ABB6-302021E7B91A}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal