/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../../../idlib/precompiled.h" #pragma hdrstop #include "AASBuild_local.h" #define BFL_PATCH 0x1000 //=============================================================== // // idAASBuild // //=============================================================== /* ============ idAASBuild::idAASBuild ============ */ idAASBuild::idAASBuild( void ) { file = NULL; procNodes = NULL; numProcNodes = 0; numGravitationalSubdivisions = 0; numMergedLeafNodes = 0; numLedgeSubdivisions = 0; ledgeMap = NULL; } /* ============ idAASBuild::~idAASBuild ============ */ idAASBuild::~idAASBuild( void ) { Shutdown(); } /* ================ idAASBuild::Shutdown ================ */ void idAASBuild::Shutdown( void ) { aasSettings = NULL; if ( file ) { delete file; file = NULL; } DeleteProcBSP(); numGravitationalSubdivisions = 0; numMergedLeafNodes = 0; numLedgeSubdivisions = 0; ledgeList.Clear(); if ( ledgeMap ) { delete ledgeMap; ledgeMap = NULL; } } /* ================ idAASBuild::ParseProcNodes ================ */ void idAASBuild::ParseProcNodes( idLexer *src ) { int i; src->ExpectTokenString( "{" ); idAASBuild::numProcNodes = src->ParseInt(); if ( idAASBuild::numProcNodes < 0 ) { src->Error( "idAASBuild::ParseProcNodes: bad numProcNodes" ); } idAASBuild::procNodes = (aasProcNode_t *)Mem_ClearedAlloc( idAASBuild::numProcNodes * sizeof( aasProcNode_t ) ); for ( i = 0; i < idAASBuild::numProcNodes; i++ ) { aasProcNode_t *node; node = &(idAASBuild::procNodes[i]); src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); node->children[0] = src->ParseInt(); node->children[1] = src->ParseInt(); } src->ExpectTokenString( "}" ); } /* ================ idAASBuild::LoadProcBSP ================ */ bool idAASBuild::LoadProcBSP( const char *name, ID_TIME_T minFileTime ) { idStr fileName; idToken token; idLexer *src; // load it fileName = name; fileName.SetFileExtension( PROC_FILE_EXT ); src = new idLexer( fileName, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); if ( !src->IsLoaded() ) { common->Warning("idAASBuild::LoadProcBSP: couldn't load %s", fileName.c_str() ); delete src; return false; } // if the file is too old if ( src->GetFileTime() < minFileTime ) { delete src; return false; } if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) { common->Warning( "idAASBuild::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID ); delete src; return false; } // parse the file while ( 1 ) { if ( !src->ReadToken( &token ) ) { break; } if ( token == "model" ) { src->SkipBracedSection(); continue; } if ( token == "shadowModel" ) { src->SkipBracedSection(); continue; } if ( token == "interAreaPortals" ) { src->SkipBracedSection(); continue; } if ( token == "nodes" ) { idAASBuild::ParseProcNodes( src ); break; } src->Error( "idAASBuild::LoadProcBSP: bad token \"%s\"", token.c_str() ); } delete src; return true; } /* ============ idAASBuild::DeleteProcBSP ============ */ void idAASBuild::DeleteProcBSP( void ) { if ( procNodes ) { Mem_Free( procNodes ); procNodes = NULL; } numProcNodes = 0; } /* ============ idAASBuild::ChoppedAwayByProcBSP ============ */ bool idAASBuild::ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) { int res; idFixedWinding back; aasProcNode_t *node; float dist; do { node = idAASBuild::procNodes + nodeNum; dist = node->plane.Normal() * origin + node->plane[3]; if ( dist > radius ) { res = SIDE_FRONT; } else if ( dist < -radius ) { res = SIDE_BACK; } else { res = w->Split( &back, node->plane, ON_EPSILON ); } if ( res == SIDE_FRONT ) { nodeNum = node->children[0]; } else if ( res == SIDE_BACK ) { nodeNum = node->children[1]; } else if ( res == SIDE_ON ) { // continue with the side the winding faces if ( node->plane.Normal() * normal > 0.0f ) { nodeNum = node->children[0]; } else { nodeNum = node->children[1]; } } else { // if either node is not solid if ( node->children[0] < 0 || node->children[1] < 0 ) { return false; } // only recurse if the node is not solid if ( node->children[1] > 0 ) { if ( !idAASBuild::ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) { return false; } } nodeNum = node->children[0]; } } while ( nodeNum > 0 ); if ( nodeNum < 0 ) { return false; } return true; } /* ============ idAASBuild::ClipBrushSidesWithProcBSP ============ */ void idAASBuild::ClipBrushSidesWithProcBSP( idBrushList &brushList ) { int i, clippedSides; idBrush *brush; idFixedWinding neww; idBounds bounds; float radius; idVec3 origin; // if the .proc file has no BSP tree if ( idAASBuild::procNodes == NULL ) { return; } clippedSides = 0; for ( brush = brushList.Head(); brush; brush = brush->Next() ) { for ( i = 0; i < brush->GetNumSides(); i++ ) { if ( !brush->GetSide(i)->GetWinding() ) { continue; } // make a local copy of the winding neww = *brush->GetSide(i)->GetWinding(); neww.GetBounds( bounds ); origin = (bounds[1] - bounds[0]) * 0.5f; radius = origin.Length() + ON_EPSILON; origin = bounds[0] + origin; if ( ChoppedAwayByProcBSP( 0, &neww, brush->GetSide(i)->GetPlane().Normal(), origin, radius ) ) { brush->GetSide(i)->SetFlag( SFL_USED_SPLITTER ); clippedSides++; } } } common->Printf( "%6d brush sides clipped\n", clippedSides ); } /* ============ idAASBuild::ContentsForAAS ============ */ int idAASBuild::ContentsForAAS( int contents ) { int c; if ( contents & ( CONTENTS_SOLID|CONTENTS_AAS_SOLID|CONTENTS_MONSTERCLIP ) ) { return AREACONTENTS_SOLID; } c = 0; if ( contents & CONTENTS_WATER ) { c |= AREACONTENTS_WATER; } if ( contents & CONTENTS_AREAPORTAL ) { c |= AREACONTENTS_CLUSTERPORTAL; } if ( contents & CONTENTS_AAS_OBSTACLE ) { c |= AREACONTENTS_OBSTACLE; } return c; } /* ============ idAASBuild::AddBrushForMapBrush ============ */ idBrushList idAASBuild::AddBrushesForMapBrush( const idMapBrush *mapBrush, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) { int contents, i; idMapBrushSide *mapSide; const idMaterial *mat; idList sideList; idBrush *brush; idPlane plane; contents = 0; for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { mapSide = mapBrush->GetSide(i); mat = declManager->FindMaterial( mapSide->GetMaterial() ); contents |= mat->GetContentFlags(); plane = mapSide->GetPlane(); plane.FixDegeneracies( DEGENERATE_DIST_EPSILON ); sideList.Append( new idBrushSide( plane, -1 ) ); } contents = ContentsForAAS( contents ); if ( !contents ) { for ( i = 0; i < sideList.Num(); i++ ) { delete sideList[i]; } return brushList; } brush = new idBrush(); brush->SetContents( contents ); if ( !brush->FromSides( sideList ) ) { common->Warning( "brush primitive %d on entity %d is degenerate", primitiveNum, entityNum ); delete brush; return brushList; } brush->SetEntityNum( entityNum ); brush->SetPrimitiveNum( primitiveNum ); brush->Transform( origin, axis ); brushList.AddToTail( brush ); return brushList; } /* ============ idAASBuild::AddBrushesForPatch ============ */ idBrushList idAASBuild::AddBrushesForMapPatch( const idMapPatch *mapPatch, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) { int i, j, contents, validBrushes; float dot; int v1, v2, v3, v4; idFixedWinding w; idPlane plane; idVec3 d1, d2; idBrush *brush; idSurface_Patch mesh; const idMaterial *mat; mat = declManager->FindMaterial( mapPatch->GetMaterial() ); contents = ContentsForAAS( mat->GetContentFlags() ); if ( !contents ) { return brushList; } mesh = idSurface_Patch( *mapPatch ); // if the patch has an explicit number of subdivisions use it to avoid cracks if ( mapPatch->GetExplicitlySubdivided() ) { mesh.SubdivideExplicit( mapPatch->GetHorzSubdivisions(), mapPatch->GetVertSubdivisions(), false, true ); } else { mesh.Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false ); } validBrushes = 0; for ( i = 0; i < mesh.GetWidth() - 1; i++ ) { for ( j = 0; j < mesh.GetHeight() - 1; j++ ) { v1 = j * mesh.GetWidth() + i; v2 = v1 + 1; v3 = v1 + mesh.GetWidth() + 1; v4 = v1 + mesh.GetWidth(); d1 = mesh[v2].xyz - mesh[v1].xyz; d2 = mesh[v3].xyz - mesh[v1].xyz; plane.SetNormal( d1.Cross(d2) ); if ( plane.Normalize() != 0.0f ) { plane.FitThroughPoint( mesh[v1].xyz ); dot = plane.Distance( mesh[v4].xyz ); // if we can turn it into a quad if ( idMath::Fabs(dot) < 0.1f ) { w.Clear(); w += mesh[v1].xyz; w += mesh[v2].xyz; w += mesh[v3].xyz; w += mesh[v4].xyz; brush = new idBrush(); brush->SetContents( contents ); if ( brush->FromWinding( w, plane ) ) { brush->SetEntityNum( entityNum ); brush->SetPrimitiveNum( primitiveNum ); brush->SetFlag( BFL_PATCH ); brush->Transform( origin, axis ); brushList.AddToTail( brush ); validBrushes++; } else { delete brush; } continue; } else { // create one of the triangles w.Clear(); w += mesh[v1].xyz; w += mesh[v2].xyz; w += mesh[v3].xyz; brush = new idBrush(); brush->SetContents( contents ); if ( brush->FromWinding( w, plane ) ) { brush->SetEntityNum( entityNum ); brush->SetPrimitiveNum( primitiveNum ); brush->SetFlag( BFL_PATCH ); brush->Transform( origin, axis ); brushList.AddToTail( brush ); validBrushes++; } else { delete brush; } } } // create the other triangle d1 = mesh[v3].xyz - mesh[v1].xyz; d2 = mesh[v4].xyz - mesh[v1].xyz; plane.SetNormal( d1.Cross(d2) ); if ( plane.Normalize() != 0.0f ) { plane.FitThroughPoint( mesh[v1].xyz ); w.Clear(); w += mesh[v1].xyz; w += mesh[v3].xyz; w += mesh[v4].xyz; brush = new idBrush(); brush->SetContents( contents ); if ( brush->FromWinding( w, plane ) ) { brush->SetEntityNum( entityNum ); brush->SetPrimitiveNum( primitiveNum ); brush->SetFlag( BFL_PATCH ); brush->Transform( origin, axis ); brushList.AddToTail( brush ); validBrushes++; } else { delete brush; } } } } if ( !validBrushes ) { common->Warning( "patch primitive %d on entity %d is completely degenerate", primitiveNum, entityNum ); } return brushList; } /* ============ idAASBuild::AddBrushesForMapEntity ============ */ idBrushList idAASBuild::AddBrushesForMapEntity( const idMapEntity *mapEnt, int entityNum, idBrushList brushList ) { int i; idVec3 origin; idMat3 axis; if ( mapEnt->GetNumPrimitives() < 1 ) { return brushList; } mapEnt->epairs.GetVector( "origin", "0 0 0", origin ); if ( !mapEnt->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { float angle = mapEnt->epairs.GetFloat( "angle" ); if ( angle != 0.0f ) { axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); } else { axis.Identity(); } } for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) { idMapPrimitive *mapPrim; mapPrim = mapEnt->GetPrimitive(i); if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { brushList = AddBrushesForMapBrush( static_cast(mapPrim), origin, axis, entityNum, i, brushList ); continue; } if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) { if ( aasSettings->usePatches ) { brushList = AddBrushesForMapPatch( static_cast(mapPrim), origin, axis, entityNum, i, brushList ); } continue; } } return brushList; } /* ============ idAASBuild::AddBrushesForMapFile ============ */ idBrushList idAASBuild::AddBrushesForMapFile( const idMapFile * mapFile, idBrushList brushList ) { int i; common->Printf( "[Brush Load]\n" ); brushList = AddBrushesForMapEntity( mapFile->GetEntity( 0 ), 0, brushList ); for ( i = 1; i < mapFile->GetNumEntities(); i++ ) { const char *classname = mapFile->GetEntity( i )->epairs.GetString( "classname" ); if ( idStr::Icmp( classname, "func_aas_obstacle" ) == 0 ) { brushList = AddBrushesForMapEntity( mapFile->GetEntity( i ), i, brushList ); } } common->Printf( "%6d brushes\n", brushList.Num() ); return brushList; } /* ============ idAASBuild::CheckForEntities ============ */ bool idAASBuild::CheckForEntities( const idMapFile *mapFile, idStrList &entityClassNames ) const { int i; idStr classname; com_editors |= EDITOR_AAS; for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { if ( !mapFile->GetEntity(i)->epairs.GetString( "classname", "", classname ) ) { continue; } if ( aasSettings->ValidEntity( classname ) ) { entityClassNames.AddUnique( classname ); } } com_editors &= ~EDITOR_AAS; return ( entityClassNames.Num() != 0 ); } /* ============ MergeAllowed ============ */ bool MergeAllowed( idBrush *b1, idBrush *b2 ) { return ( b1->GetContents() == b2->GetContents() && !( ( b1->GetFlags() | b2->GetFlags() ) & BFL_PATCH ) ); } /* ============ ExpandedChopAllowed ============ */ bool ExpandedChopAllowed( idBrush *b1, idBrush *b2 ) { return ( b1->GetContents() == b2->GetContents() ); } /* ============ ExpandedMergeAllowed ============ */ bool ExpandedMergeAllowed( idBrush *b1, idBrush *b2 ) { return ( b1->GetContents() == b2->GetContents() ); } /* ============ idAASBuild::ChangeMultipleBoundingBoxContents ============ */ void idAASBuild::ChangeMultipleBoundingBoxContents_r( idBrushBSPNode *node, int mask ) { while( node ) { if ( !( node->GetContents() & mask ) ) { node->SetContents( node->GetContents() & ~AREACONTENTS_SOLID ); } ChangeMultipleBoundingBoxContents_r( node->GetChild( 0 ), mask ); node = node->GetChild( 1 ); } } /* ============ idAASBuild::Build ============ */ bool idAASBuild::Build( const idStr &fileName, const idAASSettings *settings ) { int i, bit, mask, startTime; idMapFile * mapFile; idBrushList brushList; idList expandedBrushes; idBrush *b; idBrushBSP bsp; idStr name; idAASReach reach; idAASCluster cluster; idStrList entityClassNames; startTime = Sys_Milliseconds(); Shutdown(); aasSettings = settings; name = fileName; name.SetFileExtension( "map" ); mapFile = new idMapFile; if ( !mapFile->Parse( name ) ) { delete mapFile; common->Error( "Couldn't load map file: '%s'", name.c_str() ); return false; } // check if this map has any entities that use this AAS file if ( !CheckForEntities( mapFile, entityClassNames ) ) { delete mapFile; common->Printf( "no entities in map that use %s\n", settings->fileExtension.c_str() ); return true; } // load map file brushes brushList = AddBrushesForMapFile( mapFile, brushList ); // if empty map if ( brushList.Num() == 0 ) { delete mapFile; common->Error( "%s is empty", name.c_str() ); return false; } // merge as many brushes as possible before expansion brushList.Merge( MergeAllowed ); // if there is a .proc file newer than the .map file if ( LoadProcBSP( fileName, mapFile->GetFileTime() ) ) { ClipBrushSidesWithProcBSP( brushList ); DeleteProcBSP(); } // make copies of the brush list expandedBrushes.Append( &brushList ); for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) { expandedBrushes.Append( brushList.Copy() ); } // expand brushes for the axial bounding boxes mask = AREACONTENTS_SOLID; for ( i = 0; i < expandedBrushes.Num(); i++ ) { for ( b = expandedBrushes[i]->Head(); b; b = b->Next() ) { b->ExpandForAxialBox( aasSettings->boundingBoxes[i] ); bit = 1 << ( i + AREACONTENTS_BBOX_BIT ); mask |= bit; b->SetContents( b->GetContents() | bit ); } } // move all brushes back into the original list for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) { brushList.AddToTail( *expandedBrushes[i] ); delete expandedBrushes[i]; } if ( aasSettings->writeBrushMap ) { bsp.WriteBrushMap( fileName, "_" + aasSettings->fileExtension, AREACONTENTS_SOLID ); } // build BSP tree from brushes bsp.Build( brushList, AREACONTENTS_SOLID, ExpandedChopAllowed, ExpandedMergeAllowed ); // only solid nodes with all bits set for all bounding boxes need to stay solid ChangeMultipleBoundingBoxContents_r( bsp.GetRootNode(), mask ); // portalize the bsp tree bsp.Portalize(); // remove subspaces not reachable by entities if ( !bsp.RemoveOutside( mapFile, AREACONTENTS_SOLID, entityClassNames ) ) { bsp.LeakFile( name ); delete mapFile; common->Printf( "%s has no outside", name.c_str() ); return false; } // gravitational subdivision GravitationalSubdivision( bsp ); // merge portals where possible bsp.MergePortals( AREACONTENTS_SOLID ); // melt portal windings bsp.MeltPortals( AREACONTENTS_SOLID ); if ( aasSettings->writeBrushMap ) { WriteLedgeMap( fileName, "_" + aasSettings->fileExtension + "_ledge" ); } // ledge subdivisions LedgeSubdivision( bsp ); // merge leaf nodes MergeLeafNodes( bsp ); // merge portals where possible bsp.MergePortals( AREACONTENTS_SOLID ); // melt portal windings bsp.MeltPortals( AREACONTENTS_SOLID ); // store the file from the bsp tree StoreFile( bsp ); file->settings = *aasSettings; // calculate reachability reach.Build( mapFile, file ); // build clusters cluster.Build( file ); // optimize the file if ( !aasSettings->noOptimize ) { file->Optimize(); } // write the file name.SetFileExtension( aasSettings->fileExtension ); file->Write( name, mapFile->GetGeometryCRC() ); // delete the map file delete mapFile; common->Printf( "%6d seconds to create AAS\n", (Sys_Milliseconds() - startTime) / 1000 ); return true; } /* ============ idAASBuild::BuildReachability ============ */ bool idAASBuild::BuildReachability( const idStr &fileName, const idAASSettings *settings ) { int startTime; idMapFile * mapFile; idStr name; idAASReach reach; idAASCluster cluster; startTime = Sys_Milliseconds(); aasSettings = settings; name = fileName; name.SetFileExtension( "map" ); mapFile = new idMapFile; if ( !mapFile->Parse( name ) ) { delete mapFile; common->Error( "Couldn't load map file: '%s'", name.c_str() ); return false; } file = new idAASFileLocal(); name.SetFileExtension( aasSettings->fileExtension ); if ( !file->Load( name, 0 ) ) { delete mapFile; common->Error( "Couldn't load AAS file: '%s'", name.c_str() ); return false; } file->settings = *aasSettings; // calculate reachability reach.Build( mapFile, file ); // build clusters cluster.Build( file ); // write the file file->Write( name, mapFile->GetGeometryCRC() ); // delete the map file delete mapFile; common->Printf( "%6d seconds to calculate reachability\n", (Sys_Milliseconds() - startTime) / 1000 ); return true; } /* ============ ParseOptions ============ */ int ParseOptions( const idCmdArgs &args, idAASSettings &settings ) { int i; idStr str; for ( i = 1; i < args.Argc(); i++ ) { str = args.Argv( i ); str.StripLeading( '-' ); if ( str.Icmp( "usePatches" ) == 0 ) { settings.usePatches = true; common->Printf( "usePatches = true\n" ); } else if ( str.Icmp( "writeBrushMap" ) == 0 ) { settings.writeBrushMap = true; common->Printf( "writeBrushMap = true\n" ); } else if ( str.Icmp( "playerFlood" ) == 0 ) { settings.playerFlood = true; common->Printf( "playerFlood = true\n" ); } else if ( str.Icmp( "noOptimize" ) == 0 ) { settings.noOptimize = true; common->Printf( "noOptimize = true\n" ); } } return args.Argc() - 1; } /* ============ RunAAS_f ============ */ void RunAAS_f( const idCmdArgs &args ) { int i; idAASBuild aas; idAASSettings settings; idStr mapName; if ( args.Argc() <= 1 ) { common->Printf( "runAAS [options] \n" "options:\n" " -usePatches = use bezier patches for collision detection.\n" " -writeBrushMap = write a brush map with the AAS geometry.\n" " -playerFlood = use player spawn points as valid AAS positions.\n" ); return; } common->ClearWarnings( "compiling AAS" ); common->SetRefreshOnPrint( true ); // get the aas settings definitions const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false ); if ( !dict ) { common->Error( "Unable to find entityDef for 'aas_types'" ); } const idKeyValue *kv = dict->MatchPrefix( "type" ); while( kv != NULL ) { const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false ); if ( !settingsDict ) { common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() ); } else { settings.FromDict( kv->GetValue(), settingsDict ); i = ParseOptions( args, settings ); mapName = args.Argv(i); mapName.BackSlashesToSlashes(); if ( mapName.Icmpn( "maps/", 4 ) != 0 ) { mapName = "maps/" + mapName; } aas.Build( mapName, &settings ); } kv = dict->MatchPrefix( "type", kv ); if ( kv ) { common->Printf( "=======================================================\n" ); } } common->SetRefreshOnPrint( false ); common->PrintWarnings(); } /* ============ RunAASDir_f ============ */ void RunAASDir_f( const idCmdArgs &args ) { int i; idAASBuild aas; idAASSettings settings; idFileList *mapFiles; if ( args.Argc() <= 1 ) { common->Printf( "runAASDir \n" ); return; } common->ClearWarnings( "compiling AAS" ); common->SetRefreshOnPrint( true ); // get the aas settings definitions const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false ); if ( !dict ) { common->Error( "Unable to find entityDef for 'aas_types'" ); } // scan for .map files mapFiles = fileSystem->ListFiles( idStr("maps/") + args.Argv(1), ".map" ); // create AAS files for all the .map files for ( i = 0; i < mapFiles->GetNumFiles(); i++ ) { if ( i ) { common->Printf( "=======================================================\n" ); } const idKeyValue *kv = dict->MatchPrefix( "type" ); while( kv != NULL ) { const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false ); if ( !settingsDict ) { common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() ); } else { settings.FromDict( kv->GetValue(), settingsDict ); aas.Build( idStr( "maps/" ) + args.Argv( 1 ) + "/" + mapFiles->GetFile( i ), &settings ); } kv = dict->MatchPrefix( "type", kv ); if ( kv ) { common->Printf( "=======================================================\n" ); } } } fileSystem->FreeFileList( mapFiles ); common->SetRefreshOnPrint( false ); common->PrintWarnings(); } /* ============ RunReach_f ============ */ void RunReach_f( const idCmdArgs &args ) { int i; idAASBuild aas; idAASSettings settings; if ( args.Argc() <= 1 ) { common->Printf( "runReach [options] \n" ); return; } common->ClearWarnings( "calculating AAS reachability" ); common->SetRefreshOnPrint( true ); // get the aas settings definitions const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false ); if ( !dict ) { common->Error( "Unable to find entityDef for 'aas_types'" ); } const idKeyValue *kv = dict->MatchPrefix( "type" ); while( kv != NULL ) { const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false ); if ( !settingsDict ) { common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() ); } else { settings.FromDict( kv->GetValue(), settingsDict ); i = ParseOptions( args, settings ); aas.BuildReachability( idStr("maps/") + args.Argv(i), &settings ); } kv = dict->MatchPrefix( "type", kv ); if ( kv ) { common->Printf( "=======================================================\n" ); } } common->SetRefreshOnPrint( false ); common->PrintWarnings(); }