Skip to content
DummkopfOfHachtenduden edited this page Jan 14, 2025 · 16 revisions
// Header
1   byte    version     // expecting 1
4   uint    simpleDungeonDataOffset

//AI_NAVIGATION Data
//CRefDungeon
2   ushort  regionID
4   uint    blockCount
for (int i = 0; i < blockCount; i++)
{
    //CRefBlock
    4   uint    block.Index
    4   uint    block.CellCount     // number of refCells in this block
    4   uint    block.EdgeCount     // number of refEdges in this block

    // CellLookupTable
    // A table that when given a start and goal cell returns the index of the edge that should be used in order to reach the goal.
    for(int goalCellID = 0; goalCellID < cellCount; goalCellID++)
    {
        for(int startCellID = 0; startCellID < cellCount; startCellID++)
        {                  
            2   short   refEdgeIndex0;         // refEdge to use when pathing forwards (start cell to goal cell)
            2   short   refEdgeIndex1;         // refEdge to use when pathing backwards (goal cell to start cell)
        }
    }

    // Links
    4   uint    linkCount
    for (int i = 0; i < linkCount; i++)
    {        
        4   uint    link.ID                     // same as CellID
        2   ushort  link.CellID
        2   ushort  link.LinkedObjID            // the other block
        2   ushort  link.LinkedObjRefEdgeIndex  // a global edge of the other block
    }
}

// BlockLookupTable
// A table that when given a start and goal block returns the index of the cell that should be used as subgoal in order to reach the actual goal.
for(int goalBlockID = 0; goalBlockID < blockCount; goalBlockID++)
{
    for(int startBlockID = 0; startBlockID < blockCount; startBlockID++)
    {
        2   short   refCellID   // the memory for this is not zeroed and may contain garbage where startBlockID == goalBlockID which are invalid paths anyway.
    }
}

4   uint    int0    // expecting 0
//SimpleDungeonData
//simpleDungeonDataOffset will get you here
2   ushort  regionID    // yes twice
4   uint    blockCount
for (uint i = 0; i < blockCount; i++)
{
    4   uint    edgeCount
    for (uint i = 0; i < edgeCount; i++)
    {
        12  Vector3 edgeCenter
    }
}
4   uint    int1    // expecting 0

Pattern for ImHex

#pragma array_limit 4294967296
#pragma pattern_limit 4294967296

struct RefCell
{
    u16 RefEdgeIndex0;
    u16 RefEdgeIndex1;
};

struct RefBlockLink
{
    u32 ID;
    u16 CellID;
    u16 LinkedObjID;
    u16 LinkedObjRefEdgeIndex;
};

struct RefBlock 
{
    u32 Index;
    u32 CellCount;
    u32 EdgeCount;
    RefCell CellLookupTable[CellCount*CellCount];
    u32 LinkCount;
    RefBlockLink Links[LinkCount];
    
};

struct RefDungeon 
{
    u16 RegionID;
    u32 BlockCount;
    RefBlock Blocks[BlockCount];
    u16 BlockLookupTable[BlockCount*BlockCount];
    u32 Int0;
};


struct Vector3
{
   float X;
   float Y;
   float Z;
};

struct SimpleDungeonBlock
{
    u32 EdgeCount;
    Vector3 EdgeCenterPoints[EdgeCount];
};

struct SimpleDungeonData
{
   u16 RegionID;
   u32 BlockCount;
   SimpleDungeonBlock Blocks[BlockCount];
   u32 Int0;
};

struct AI_NAVIGATION 
{
    u8  Version;
    u32 SimpleDungeonDataOffset;
    RefDungeon refDungeon;   
    SimpleDungeonData simpleDungeonData @ SimpleDungeonDataOffset;
};

AI_NAVIGATION file @ 0;

Simplification

Some RefBlocks in AINavData may have be computed based on simplified mesh versions resulting in a mismatch of cell indices if you use the original mesh. This has been the major reason why this format took so long to figure out in the fist place. 😄 room102 comparison

The simplified mesh is a retriangulated version of the original with only the most important points and some additional Steiner points. The actual algorithm is unknown to me but you may reverse engineer it at vsro_client.sub_0044BF10. It's also possible to patch the client into dumping these as the simplification code is still included despite not being used there.

Examples

table

Unsmoothed paths generated with AINavData. full path image

Clone this wiki locally