Beispiel #1
0
        private void LoadLevels()
        {
            var levelsNode = m_treeView.Nodes.Add("Levels");

            foreach (var path in Directory.EnumerateDirectories(Path.Combine(m_clientRoot, "levels")))
            {
                using (var dir = new DirManager(path))
                {
                    if (dir.Exists("leveldata.xml"))
                    {
                        var n = levelsNode.Nodes.Add("level-viewer", Path.GetFileName(path), 5, 5);
                    }
                }
            }
            levelsNode.Expand();
        }
Beispiel #2
0
        // generates navmesh data for the selected levels.
        public static void GenerateAllNav(WorldIdXmlLoader worldIdXmlLoader,
                                          DirManager meshesDir, DirManager levelsDir, string levelId,
                                          bool skipExistingNav, string outputPath)
        {
            int curFolderIndex = -1;

            foreach (var pairs in worldIdXmlLoader.FolderNamesById)
            {
                curFolderIndex++;

                string clientLevelId = pairs.Key;
                string levelFolder   = pairs.Value;

                if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId)
                {
                    // skip excluded
                    continue;
                }

                string outputNavFilename = Path.Combine(outputPath, clientLevelId + ".nav");

                if (skipExistingNav && File.Exists(outputNavFilename))
                {
                    Console.WriteLine($"    ** Skipping (already exists): {clientLevelId} - {levelFolder}");
                    continue;
                }


                Console.WriteLine($"    Loading meshes for {clientLevelId} - {levelFolder}");

                var LEVEL_TIME = DateTime.Now;
                var TIMER      = DateTime.Now;
                var geoSpace   = new GeoSpace();

                // brushes
                var brushlst = LevelLoadHelper.CreateBrushLstLoader(levelsDir,
                                                                    Path.Combine(levelFolder, "brush.lst"));
                if (brushlst != null)
                {
                    var cgfMap = LevelLoadHelper.CreateBrushLstCgfLoaderMap(meshesDir, brushlst);

                    foreach (var brush in brushlst.brushEntries)
                    {
                        CgfLoader cgf;
                        if (!cgfMap.TryGetValue(brush.meshIdx, out cgf))
                        {
                            continue;
                        }

                        Matrix brushMatrix = LevelLoadHelper.GetBrushMatrix(brush);
                        AddCgfToWorld(cgf, ref brushMatrix, brush.position, geoSpace);
                    }
                }

                // objects
                var ctx = LevelLoadHelper.LoadObjectsLst(meshesDir, levelsDir, levelFolder);
                if (ctx != null)
                {
                    foreach (var o in ctx.objects)
                    {
                        CgfLoader cgf;
                        if (!ctx.cgfMap.TryGetValue(o.ObjectId, out cgf))
                        {
                            continue;
                        }

                        var xform = LevelLoadHelper.GetObjectMatrix(o);
                        AddCgfToWorld(cgf, ref xform, o.Position, geoSpace);
                    }
                }

                // terrain
                bool   loadedH32 = false;
                string h32path   = Path.Combine(levelFolder, @"terrain\land_map.h32");
                if (levelsDir.Exists(h32path))
                {
                    using (var landMapStream = levelsDir.OpenFile(h32path))
                        new H32Loader(landMapStream).LoadIntoGeoSpace(geoSpace);
                    loadedH32 = true;
                }

                Console.WriteLine("      Data load time: " + (DateTime.Now - TIMER));

                if (brushlst == null && ctx == null && !loadedH32)
                {
                    Console.WriteLine("      ** Skipping (no level data found)");
                    continue;
                }

                // build geo
                TIMER = DateTime.Now;
                geoSpace.BuildTree();
                geoSpace.Validate();
                Console.WriteLine("      Geo build time: " + (DateTime.Now - TIMER));

                // get size of level for scanning
                var   bb     = geoSpace.GetBoundingBox();
                float startX = Math.Max(0, bb.Min.X);
                float endX   = bb.Max.X;
                float startY = Math.Max(0, bb.Min.Y);
                float endY   = bb.Max.Y;
                int   top    = (int)Math.Ceiling(bb.Max.Z);
                int   bot    = Math.Max(0, (int)bb.Min.Z);

                // TODO - print bounding box
                // TODO - save log file

                if (endX <= startX || endY <= startY || top < bot)
                {
                    throw new InvalidOperationException(
                              $"unexpected level size for {clientLevelId} {levelFolder} bb: {bb}");
                }

                // compile mesh
                TIMER = DateTime.Now;
                float step           = 1f;
                var   navMeshBuilder = new NavMeshBuilder(startX, startY, bot, endX, endY, top, step);
                CompiledNavMeshSet compiledMeshSet = navMeshBuilder.ScanFloor(geoSpace);
                Console.WriteLine("      Raycast time: " + (DateTime.Now - TIMER));

                // save to file
                using (var fs = File.OpenWrite(outputNavFilename))
                    compiledMeshSet.Save(fs);

                Console.WriteLine($"      Level {clientLevelId} finished in {DateTime.Now - LEVEL_TIME}");
            }
        }
Beispiel #3
0
        public void Process()
        {
            DateTime timer = DateTime.Now;

            if (!Directory.Exists(aionClientPath))
            {
                throw new FileNotFoundException($"Aion client installation path [{aionClientPath}] doesn't exist or not a folder path");
            }

            var meshesDir = new DirManager(aionClientPath,
                                           new[] { @"Levels\Common", @"objects\npc\event_object", @"objects\npc\level_object" });

            // Read world_maps.xml and WorldId.xml and find Levels to process
            Console.WriteLine("  Generating available levels list...");
            WorldIdXmlLoader worldIdXmlLoader = LoadWorldIdXml();

            Console.WriteLine("  Done.");

            var worldGeoBuilders = new List <WorldGeoFileBuilder>();

            var levelMeshData = new Dictionary <string, CgfMeshesToLoad>(); // key is level folder name

            Console.WriteLine("  Processing levels...");
            bool containsValidLevel = false;
            int  curFolderIndex     = -1;

            // Load levels dir... load all except common.
            var subdirs = Directory.EnumerateDirectories(Path.Combine(aionClientPath, "Levels"))
                          .Select(fulldir => Path.GetFileName(fulldir))
                          .Where(dir => !dir.Equals("common", StringComparison.InvariantCultureIgnoreCase));
            var levelsDir = new DirManager(Path.Combine(aionClientPath, "Levels"), subdirs);

            // special case to extract the login level...
            // login is like any other level, except it has no ID and isn't included in WorldId.xml.
            if (levelId == "login")
            {
                Console.WriteLine("*** Extracting login level ***");
                worldIdXmlLoader.FolderNamesById["login"] = "******";
            }

            if (generateGeo)
            {
                foreach (var pairs in worldIdXmlLoader.FolderNamesById)
                {
                    curFolderIndex++;

                    string clientLevelId = pairs.Key;
                    string levelFolder   = pairs.Value;

                    if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId)
                    {
                        // skip excluded
                        continue;
                    }

                    Console.WriteLine($"    [{clientLevelId}] - ({curFolderIndex}/{worldIdXmlLoader.FolderNamesById.Count}) - {levelFolder} ...");

                    Console.WriteLine("      Parsing leveldata.xml ...");

                    if (!levelsDir.Exists(Path.Combine(levelFolder, "leveldata.xml")))
                    {
                        Console.WriteLine("        leveldata.xml not found, skipping level.");
                        continue;
                    }

                    // Note: this list is referenced later by index.
                    List <string> vegetationCgfFilenames;
                    Point         mapSize;
                    using (var levelDataXml = levelsDir.OpenFile(Path.Combine(levelFolder, "leveldata.xml")))
                    {
                        var levelData = new LevelDataXmlLoader(levelDataXml);
                        vegetationCgfFilenames = levelData.VegetationCgfFilenames.Select(Util.NormalizeMeshFilename).ToList();
                        mapSize = levelData.MapWidthAndHeight;
                    }

                    var meshData = new CgfMeshesToLoad();

                    BrushLstLoader brushLst = null;
                    if (levelsDir.Exists(Path.Combine(levelFolder, "brush.lst")))
                    {
                        Console.WriteLine("      Parsing brush.lst ... ");
                        using (var stream = levelsDir.OpenFile(Path.Combine(levelFolder, "brush.lst")))
                            brushLst = new BrushLstLoader(stream);

                        // TODO - un-hardcode
                        if (brushLst.m_eventUsage[1])
                        {
                            Console.WriteLine("        * Supports event: X-Mas");
                        }
                        if (brushLst.m_eventUsage[2])
                        {
                            Console.WriteLine("        * Supports event: Halloween");
                        }
                        if (brushLst.m_eventUsage[3])
                        {
                            Console.WriteLine("        * Supports event: Brax Cafe");
                        }
                        if (brushLst.m_eventUsage[4])
                        {
                            Console.WriteLine("        * Supports event: Valentines");
                        }
                    }
                    else
                    {
                        Console.WriteLine("      brush.lst not found, skipping");
                    }


                    List <ObjectsLstItem> objectsLst = null;
                    if (levelsDir.Exists(Path.Combine(levelFolder, "objects.lst")))
                    {
                        Console.WriteLine("      Parsing objects.lst ... ");
                        using (var stream = levelsDir.OpenFile(Path.Combine(levelFolder, "objects.lst")))
                            objectsLst = ObjectsLstLoader.Load(stream, mapSize.X, mapSize.Y);
                    }
                    else
                    {
                        Console.WriteLine("      objects.lst not found, skipping");
                    }

                    // ------------------------------
                    meshData.meshFiles = new List <string>();

                    // brushes
                    if (brushLst != null)
                    {
                        meshData.meshFiles.AddRange(brushLst.brushInfoList.Select(o => o.filename));
                    }

                    // vegetation
                    meshData.meshFiles.AddRange(vegetationCgfFilenames);

                    // normalize names and dedupe. example entry: "levels/common/dark/natural/rocks/base/na_d_rockgngrass_05a.cgf"
                    meshData.meshFiles = meshData.meshFiles.Select(Util.NormalizeMeshFilename).Distinct().ToList();

                    meshData.meshUsage = new int[meshData.meshFiles.Count];
                    if (!noMesh)
                    {
                        levelMeshData.Add(levelFolder, meshData);
                    }

                    byte[] landMapH32 = null;

                    if (levelsDir.Exists(Path.Combine(levelFolder, @"terrain\land_map.h32")))
                    {
                        using (var stream = levelsDir.OpenFile(Path.Combine(levelFolder, @"terrain\land_map.h32")))
                        {
                            using (var ms = new MemoryStream())
                            {
                                stream.CopyTo(ms);
                                landMapH32 = ms.ToArray();
                            }
                        }
                    }

                    // keep track of all required cgfs
                    if (brushLst != null)
                    {
                        m_requiredCgfs.UnionWith(brushLst.brushInfoList.Select(o => Util.NormalizeMeshFilename(o.filename)));
                    }
                    if (objectsLst != null)
                    {
                        m_requiredCgfs.UnionWith(vegetationCgfFilenames.Select(Util.NormalizeMeshFilename));
                    }

                    // level data must be loaded first to find the required cgfs. then, cgfs must be loaded to determine if
                    // they contain collision data or not. then we can write the geo file minus non-collidable meshes.
                    var w = new WorldGeoFileBuilder(clientLevelId, landMapH32, brushLst, vegetationCgfFilenames, objectsLst);

                    // these will be processed after meshs
                    worldGeoBuilders.Add(w);

                    containsValidLevel = true;

                    Console.WriteLine("    Done.");
                }
                Console.WriteLine("  Done.");

                // --------------------------------------------------------------------------------
                if (!noMesh && containsValidLevel)
                {
                    Console.WriteLine("  Generating meshs.geo ...");

                    int    meshesSaved   = 0;
                    string meshesGeoFile = Path.Combine(outputPath, "meshs.geo");
                    using (var meshesGeoDataStream = new BinaryWriter(File.Open(meshesGeoFile, FileMode.Create)))
                    {
                        foreach (string s in m_requiredCgfs)
                        {
                            string cgfPath = PakUtil.NormalizeFilename(s);
                            if (!meshesDir.Exists(cgfPath))
                            {
                                Console.WriteLine("    Cgf not found: " + cgfPath);
                                continue;
                            }
                            using (var cgfStream = meshesDir.OpenFile(cgfPath))
                                AddToMeshesGeo(cgfPath, new CgfLoader(cgfStream), meshesGeoDataStream,
                                               1 /*collisionIntention=physical*/);
                            meshesSaved++;
                        }
                    }

                    Console.WriteLine("  Done. " + meshesSaved + "/" + m_requiredCgfs.Count + " meshes saved.");

                    // -----------------------------------
                    Console.WriteLine("  Writing world.geo files ...");
                    int wc = 0;
                    foreach (var w in worldGeoBuilders)
                    {
                        wc++;
                        Console.Write($"      Creating {w.ClientLevelId}.geo file [{wc}/{worldGeoBuilders.Count}]... ");

                        w.CreateWorldGeoFile(outputPath, noH32, m_loadedCgfs, m_emptyCgfs);
                        Console.WriteLine("   Done.");
                    }
                    Console.WriteLine("  Done.");
                }

                // ------------------------------------

                /*VERBOSE Console.WriteLine("  Check meshes that were not found ...");
                 *          foreach (var levelMeshes in levelMeshData)
                 *          {
                 *                  Console.WriteLine("    " + levelMeshes.Key);
                 *                  CgfMeshesToLoad brushLstMeshData = levelMeshes.Value;
                 *                  for (int i = 0; i < brushLstMeshData.meshFiles.Count; i++)
                 *                  {
                 *                          if (brushLstMeshData.meshUsage[i] == 0 && !m_emptyCgfs.Contains(Util.NormalizeMeshFilename(brushLstMeshData.meshFiles[i])))
                 *                          {
                 *                                  Console.WriteLine("      " + brushLstMeshData.meshFiles[i]);
                 *                          }
                 *                  }
                 *          }*/
                Console.WriteLine("  Done.");
            }

            // --------------------------------------------------------------------------------
            if (generateDoors)
            {
                Console.WriteLine("Generating door mesh data...");

                // Writes 2 files:
                // - door data, pairing level+entityid to position
                // - door mesh file, containing start and end variations of each door model.

                // Original AL stores models in data\geo\model\static_doors as .cga files.
                // These are not actually .cga files, but instead are the same format as .geo files.
                // These meshes only contained a single state.
                //
                // This following code generates door data in a new format:
                // - Each door model is loaded twice, in the start and end positions.
                // - Each model has _start or _end appended to the mesh name.
                // - All unique door meshes are stored in doors.geo, following the same format as
                //   meshes.geo. Meshes should be identified by original name + _start or _end.
                // - All door instances are stored in doors.dat and define world placement.
                //   static_doors.xml is still necessary as it provides unique info such as key item id.

                var doorModelNames = new HashSet <string>();

                using (var doorsDat = new BinaryWriter(File.Open(Path.Combine(outputPath, "doors.dat"), FileMode.Create)))
                {
                    doorsDat.Write(0x524F4F44);

                    // collect unique doors
                    foreach (var pairs in worldIdXmlLoader.FolderNamesById)
                    {
                        curFolderIndex++;

                        string clientLevelId = pairs.Key;
                        string levelFolder   = pairs.Value;

                        if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId)
                        {
                            // skip excluded
                            continue;
                        }

                        Console.WriteLine($"    [{clientLevelId}] - ({curFolderIndex}/{worldIdXmlLoader.FolderNamesById.Count}) - {levelFolder} ...");

                        List <DoorInfo> doorInfos;
                        try
                        {
                            doorInfos = DoorLoader.LoadDoorInfosForLevel(levelsDir, levelFolder);
                        }
                        catch
                        {
                            Console.WriteLine("        Level folder not found.");
                            continue;
                        }

                        foreach (var door in doorInfos)
                        {
                            string meshName = PakUtil.NormalizeFilename(door.object_AnimatedModel);
                            doorModelNames.Add(meshName);

                            doorsDat.Write((short)meshName.Length);
                            byte[] meshFileNameBytes = Encoding.ASCII.GetBytes(meshName);
                            doorsDat.Write(meshFileNameBytes);
                            doorsDat.Write(int.Parse(clientLevelId));
                            doorsDat.Write(door.EntityId);
                            doorsDat.Write(door.Pos.X);
                            doorsDat.Write(door.Pos.Y);
                            doorsDat.Write(door.Pos.Z);
                            // TODO investigate dir / use_dir. value correlates to z angle...
                            doorsDat.Write(door.Angles.X);
                            doorsDat.Write(door.Angles.Y);
                            doorsDat.Write(door.Angles.Z);
                        }
                    }
                }

                m_loadedCgfs = new HashSet <string>();
                m_emptyCgfs  = new HashSet <string>();

                // generate door meshes.
                // a door model may be referenced many times, so models are saved separately from instances.
                int    meshesSaved  = 0;
                string doorsGeoFile = Path.Combine(outputPath, "doors.geo");
                using (var doorMeshesGeoDataStream = new BinaryWriter(File.Open(doorsGeoFile, FileMode.Create)))
                {
                    foreach (var cgfPath in doorModelNames.OrderBy(o => o))
                    {
                        if (!meshesDir.Exists(cgfPath))
                        {
                            Console.WriteLine("    Door Cgf/Cga not found: " + cgfPath);
                            continue;
                        }

                        // TODO - parameterize m_loadedCgfs and m_emptyCgfs...
                        using (var cgfStream = meshesDir.OpenFile(cgfPath))
                        {
                            var cgfFirstState  = new CgfLoader(cgfStream);
                            var cgfSecondState = cgfFirstState.CloneAtTime(999999);

                            byte collisionIntention = 1 + (1 << 4); // physical + door
                            AddToMeshesGeo(cgfPath + "_start", cgfFirstState, doorMeshesGeoDataStream, collisionIntention);
                            AddToMeshesGeo(cgfPath + "_end", cgfSecondState, doorMeshesGeoDataStream, collisionIntention);
                        }
                        meshesSaved++;
                    }
                }

                // doors should have collision data, otherwise they won't be very effective.
                foreach (var empty in m_emptyCgfs)
                {
                    Console.WriteLine("Warning: door has no collision data: " + empty);
                }
                Console.WriteLine("Door meshes: " + meshesSaved);
            }

            // --------------------------------------------------------------------------------
            if (generateNav)
            {
                Console.WriteLine("  Generating NavMesh .nav data...");
                var start = DateTime.Now;
                NavMeshProcessor.GenerateAllNav(worldIdXmlLoader, meshesDir, levelsDir,
                                                levelId, skipExistingNav, outputPath);
                Console.WriteLine("    NavMesh processing time: " + (DateTime.Now - start));
            }

            TimeSpan timerEnd = DateTime.Now - timer;

            Console.WriteLine("  Processing time: " + timerEnd);
        }