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