2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 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.
26 ===========================================================================
29 #include "../../../idlib/precompiled.h"
32 #include "AASBuild_local.h"
34 #define BFL_PATCH 0x1000
36 //===============================================================
40 //===============================================================
44 idAASBuild::idAASBuild
47 idAASBuild::idAASBuild( void ) {
51 numGravitationalSubdivisions = 0;
52 numMergedLeafNodes = 0;
53 numLedgeSubdivisions = 0;
59 idAASBuild::~idAASBuild
62 idAASBuild::~idAASBuild( void ) {
71 void idAASBuild::Shutdown( void ) {
78 numGravitationalSubdivisions = 0;
79 numMergedLeafNodes = 0;
80 numLedgeSubdivisions = 0;
90 idAASBuild::ParseProcNodes
93 void idAASBuild::ParseProcNodes( idLexer *src ) {
96 src->ExpectTokenString( "{" );
98 idAASBuild::numProcNodes = src->ParseInt();
99 if ( idAASBuild::numProcNodes < 0 ) {
100 src->Error( "idAASBuild::ParseProcNodes: bad numProcNodes" );
102 idAASBuild::procNodes = (aasProcNode_t *)Mem_ClearedAlloc( idAASBuild::numProcNodes * sizeof( aasProcNode_t ) );
104 for ( i = 0; i < idAASBuild::numProcNodes; i++ ) {
107 node = &(idAASBuild::procNodes[i]);
109 src->Parse1DMatrix( 4, node->plane.ToFloatPtr() );
110 node->children[0] = src->ParseInt();
111 node->children[1] = src->ParseInt();
114 src->ExpectTokenString( "}" );
119 idAASBuild::LoadProcBSP
122 bool idAASBuild::LoadProcBSP( const char *name, ID_TIME_T minFileTime ) {
129 fileName.SetFileExtension( PROC_FILE_EXT );
130 src = new idLexer( fileName, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
131 if ( !src->IsLoaded() ) {
132 common->Warning("idAASBuild::LoadProcBSP: couldn't load %s", fileName.c_str() );
137 // if the file is too old
138 if ( src->GetFileTime() < minFileTime ) {
143 if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) {
144 common->Warning( "idAASBuild::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID );
151 if ( !src->ReadToken( &token ) ) {
155 if ( token == "model" ) {
156 src->SkipBracedSection();
160 if ( token == "shadowModel" ) {
161 src->SkipBracedSection();
165 if ( token == "interAreaPortals" ) {
166 src->SkipBracedSection();
170 if ( token == "nodes" ) {
171 idAASBuild::ParseProcNodes( src );
175 src->Error( "idAASBuild::LoadProcBSP: bad token \"%s\"", token.c_str() );
185 idAASBuild::DeleteProcBSP
188 void idAASBuild::DeleteProcBSP( void ) {
190 Mem_Free( procNodes );
198 idAASBuild::ChoppedAwayByProcBSP
201 bool idAASBuild::ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) {
208 node = idAASBuild::procNodes + nodeNum;
209 dist = node->plane.Normal() * origin + node->plane[3];
210 if ( dist > radius ) {
213 else if ( dist < -radius ) {
217 res = w->Split( &back, node->plane, ON_EPSILON );
219 if ( res == SIDE_FRONT ) {
220 nodeNum = node->children[0];
222 else if ( res == SIDE_BACK ) {
223 nodeNum = node->children[1];
225 else if ( res == SIDE_ON ) {
226 // continue with the side the winding faces
227 if ( node->plane.Normal() * normal > 0.0f ) {
228 nodeNum = node->children[0];
231 nodeNum = node->children[1];
235 // if either node is not solid
236 if ( node->children[0] < 0 || node->children[1] < 0 ) {
239 // only recurse if the node is not solid
240 if ( node->children[1] > 0 ) {
241 if ( !idAASBuild::ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) {
245 nodeNum = node->children[0];
247 } while ( nodeNum > 0 );
256 idAASBuild::ClipBrushSidesWithProcBSP
259 void idAASBuild::ClipBrushSidesWithProcBSP( idBrushList &brushList ) {
267 // if the .proc file has no BSP tree
268 if ( idAASBuild::procNodes == NULL ) {
273 for ( brush = brushList.Head(); brush; brush = brush->Next() ) {
274 for ( i = 0; i < brush->GetNumSides(); i++ ) {
276 if ( !brush->GetSide(i)->GetWinding() ) {
280 // make a local copy of the winding
281 neww = *brush->GetSide(i)->GetWinding();
282 neww.GetBounds( bounds );
283 origin = (bounds[1] - bounds[0]) * 0.5f;
284 radius = origin.Length() + ON_EPSILON;
285 origin = bounds[0] + origin;
287 if ( ChoppedAwayByProcBSP( 0, &neww, brush->GetSide(i)->GetPlane().Normal(), origin, radius ) ) {
288 brush->GetSide(i)->SetFlag( SFL_USED_SPLITTER );
294 common->Printf( "%6d brush sides clipped\n", clippedSides );
299 idAASBuild::ContentsForAAS
302 int idAASBuild::ContentsForAAS( int contents ) {
305 if ( contents & ( CONTENTS_SOLID|CONTENTS_AAS_SOLID|CONTENTS_MONSTERCLIP ) ) {
306 return AREACONTENTS_SOLID;
309 if ( contents & CONTENTS_WATER ) {
310 c |= AREACONTENTS_WATER;
312 if ( contents & CONTENTS_AREAPORTAL ) {
313 c |= AREACONTENTS_CLUSTERPORTAL;
315 if ( contents & CONTENTS_AAS_OBSTACLE ) {
316 c |= AREACONTENTS_OBSTACLE;
323 idAASBuild::AddBrushForMapBrush
326 idBrushList idAASBuild::AddBrushesForMapBrush( const idMapBrush *mapBrush, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) {
328 idMapBrushSide *mapSide;
329 const idMaterial *mat;
330 idList<idBrushSide *> sideList;
335 for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
336 mapSide = mapBrush->GetSide(i);
337 mat = declManager->FindMaterial( mapSide->GetMaterial() );
338 contents |= mat->GetContentFlags();
339 plane = mapSide->GetPlane();
340 plane.FixDegeneracies( DEGENERATE_DIST_EPSILON );
341 sideList.Append( new idBrushSide( plane, -1 ) );
344 contents = ContentsForAAS( contents );
346 for ( i = 0; i < sideList.Num(); i++ ) {
352 brush = new idBrush();
353 brush->SetContents( contents );
355 if ( !brush->FromSides( sideList ) ) {
356 common->Warning( "brush primitive %d on entity %d is degenerate", primitiveNum, entityNum );
361 brush->SetEntityNum( entityNum );
362 brush->SetPrimitiveNum( primitiveNum );
363 brush->Transform( origin, axis );
364 brushList.AddToTail( brush );
371 idAASBuild::AddBrushesForPatch
374 idBrushList idAASBuild::AddBrushesForMapPatch( const idMapPatch *mapPatch, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) {
375 int i, j, contents, validBrushes;
382 idSurface_Patch mesh;
383 const idMaterial *mat;
385 mat = declManager->FindMaterial( mapPatch->GetMaterial() );
386 contents = ContentsForAAS( mat->GetContentFlags() );
392 mesh = idSurface_Patch( *mapPatch );
394 // if the patch has an explicit number of subdivisions use it to avoid cracks
395 if ( mapPatch->GetExplicitlySubdivided() ) {
396 mesh.SubdivideExplicit( mapPatch->GetHorzSubdivisions(), mapPatch->GetVertSubdivisions(), false, true );
398 mesh.Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false );
403 for ( i = 0; i < mesh.GetWidth() - 1; i++ ) {
404 for ( j = 0; j < mesh.GetHeight() - 1; j++ ) {
406 v1 = j * mesh.GetWidth() + i;
408 v3 = v1 + mesh.GetWidth() + 1;
409 v4 = v1 + mesh.GetWidth();
411 d1 = mesh[v2].xyz - mesh[v1].xyz;
412 d2 = mesh[v3].xyz - mesh[v1].xyz;
413 plane.SetNormal( d1.Cross(d2) );
414 if ( plane.Normalize() != 0.0f ) {
415 plane.FitThroughPoint( mesh[v1].xyz );
416 dot = plane.Distance( mesh[v4].xyz );
417 // if we can turn it into a quad
418 if ( idMath::Fabs(dot) < 0.1f ) {
425 brush = new idBrush();
426 brush->SetContents( contents );
427 if ( brush->FromWinding( w, plane ) ) {
428 brush->SetEntityNum( entityNum );
429 brush->SetPrimitiveNum( primitiveNum );
430 brush->SetFlag( BFL_PATCH );
431 brush->Transform( origin, axis );
432 brushList.AddToTail( brush );
441 // create one of the triangles
447 brush = new idBrush();
448 brush->SetContents( contents );
449 if ( brush->FromWinding( w, plane ) ) {
450 brush->SetEntityNum( entityNum );
451 brush->SetPrimitiveNum( primitiveNum );
452 brush->SetFlag( BFL_PATCH );
453 brush->Transform( origin, axis );
454 brushList.AddToTail( brush );
462 // create the other triangle
463 d1 = mesh[v3].xyz - mesh[v1].xyz;
464 d2 = mesh[v4].xyz - mesh[v1].xyz;
465 plane.SetNormal( d1.Cross(d2) );
466 if ( plane.Normalize() != 0.0f ) {
467 plane.FitThroughPoint( mesh[v1].xyz );
474 brush = new idBrush();
475 brush->SetContents( contents );
476 if ( brush->FromWinding( w, plane ) ) {
477 brush->SetEntityNum( entityNum );
478 brush->SetPrimitiveNum( primitiveNum );
479 brush->SetFlag( BFL_PATCH );
480 brush->Transform( origin, axis );
481 brushList.AddToTail( brush );
491 if ( !validBrushes ) {
492 common->Warning( "patch primitive %d on entity %d is completely degenerate", primitiveNum, entityNum );
500 idAASBuild::AddBrushesForMapEntity
503 idBrushList idAASBuild::AddBrushesForMapEntity( const idMapEntity *mapEnt, int entityNum, idBrushList brushList ) {
508 if ( mapEnt->GetNumPrimitives() < 1 ) {
512 mapEnt->epairs.GetVector( "origin", "0 0 0", origin );
513 if ( !mapEnt->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) {
514 float angle = mapEnt->epairs.GetFloat( "angle" );
515 if ( angle != 0.0f ) {
516 axis = idAngles( 0.0f, angle, 0.0f ).ToMat3();
522 for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
523 idMapPrimitive *mapPrim;
525 mapPrim = mapEnt->GetPrimitive(i);
526 if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
527 brushList = AddBrushesForMapBrush( static_cast<idMapBrush*>(mapPrim), origin, axis, entityNum, i, brushList );
530 if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
531 if ( aasSettings->usePatches ) {
532 brushList = AddBrushesForMapPatch( static_cast<idMapPatch*>(mapPrim), origin, axis, entityNum, i, brushList );
543 idAASBuild::AddBrushesForMapFile
546 idBrushList idAASBuild::AddBrushesForMapFile( const idMapFile * mapFile, idBrushList brushList ) {
549 common->Printf( "[Brush Load]\n" );
551 brushList = AddBrushesForMapEntity( mapFile->GetEntity( 0 ), 0, brushList );
553 for ( i = 1; i < mapFile->GetNumEntities(); i++ ) {
554 const char *classname = mapFile->GetEntity( i )->epairs.GetString( "classname" );
556 if ( idStr::Icmp( classname, "func_aas_obstacle" ) == 0 ) {
557 brushList = AddBrushesForMapEntity( mapFile->GetEntity( i ), i, brushList );
561 common->Printf( "%6d brushes\n", brushList.Num() );
568 idAASBuild::CheckForEntities
571 bool idAASBuild::CheckForEntities( const idMapFile *mapFile, idStrList &entityClassNames ) const {
575 com_editors |= EDITOR_AAS;
577 for ( i = 0; i < mapFile->GetNumEntities(); i++ ) {
578 if ( !mapFile->GetEntity(i)->epairs.GetString( "classname", "", classname ) ) {
582 if ( aasSettings->ValidEntity( classname ) ) {
583 entityClassNames.AddUnique( classname );
587 com_editors &= ~EDITOR_AAS;
589 return ( entityClassNames.Num() != 0 );
597 bool MergeAllowed( idBrush *b1, idBrush *b2 ) {
598 return ( b1->GetContents() == b2->GetContents() && !( ( b1->GetFlags() | b2->GetFlags() ) & BFL_PATCH ) );
606 bool ExpandedChopAllowed( idBrush *b1, idBrush *b2 ) {
607 return ( b1->GetContents() == b2->GetContents() );
615 bool ExpandedMergeAllowed( idBrush *b1, idBrush *b2 ) {
616 return ( b1->GetContents() == b2->GetContents() );
621 idAASBuild::ChangeMultipleBoundingBoxContents
624 void idAASBuild::ChangeMultipleBoundingBoxContents_r( idBrushBSPNode *node, int mask ) {
626 if ( !( node->GetContents() & mask ) ) {
627 node->SetContents( node->GetContents() & ~AREACONTENTS_SOLID );
629 ChangeMultipleBoundingBoxContents_r( node->GetChild( 0 ), mask );
630 node = node->GetChild( 1 );
639 bool idAASBuild::Build( const idStr &fileName, const idAASSettings *settings ) {
640 int i, bit, mask, startTime;
642 idBrushList brushList;
643 idList<idBrushList*> expandedBrushes;
648 idAASCluster cluster;
649 idStrList entityClassNames;
651 startTime = Sys_Milliseconds();
655 aasSettings = settings;
658 name.SetFileExtension( "map" );
660 mapFile = new idMapFile;
661 if ( !mapFile->Parse( name ) ) {
663 common->Error( "Couldn't load map file: '%s'", name.c_str() );
667 // check if this map has any entities that use this AAS file
668 if ( !CheckForEntities( mapFile, entityClassNames ) ) {
670 common->Printf( "no entities in map that use %s\n", settings->fileExtension.c_str() );
674 // load map file brushes
675 brushList = AddBrushesForMapFile( mapFile, brushList );
678 if ( brushList.Num() == 0 ) {
680 common->Error( "%s is empty", name.c_str() );
684 // merge as many brushes as possible before expansion
685 brushList.Merge( MergeAllowed );
687 // if there is a .proc file newer than the .map file
688 if ( LoadProcBSP( fileName, mapFile->GetFileTime() ) ) {
689 ClipBrushSidesWithProcBSP( brushList );
693 // make copies of the brush list
694 expandedBrushes.Append( &brushList );
695 for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) {
696 expandedBrushes.Append( brushList.Copy() );
699 // expand brushes for the axial bounding boxes
700 mask = AREACONTENTS_SOLID;
701 for ( i = 0; i < expandedBrushes.Num(); i++ ) {
702 for ( b = expandedBrushes[i]->Head(); b; b = b->Next() ) {
703 b->ExpandForAxialBox( aasSettings->boundingBoxes[i] );
704 bit = 1 << ( i + AREACONTENTS_BBOX_BIT );
706 b->SetContents( b->GetContents() | bit );
710 // move all brushes back into the original list
711 for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) {
712 brushList.AddToTail( *expandedBrushes[i] );
713 delete expandedBrushes[i];
716 if ( aasSettings->writeBrushMap ) {
717 bsp.WriteBrushMap( fileName, "_" + aasSettings->fileExtension, AREACONTENTS_SOLID );
720 // build BSP tree from brushes
721 bsp.Build( brushList, AREACONTENTS_SOLID, ExpandedChopAllowed, ExpandedMergeAllowed );
723 // only solid nodes with all bits set for all bounding boxes need to stay solid
724 ChangeMultipleBoundingBoxContents_r( bsp.GetRootNode(), mask );
726 // portalize the bsp tree
729 // remove subspaces not reachable by entities
730 if ( !bsp.RemoveOutside( mapFile, AREACONTENTS_SOLID, entityClassNames ) ) {
731 bsp.LeakFile( name );
733 common->Printf( "%s has no outside", name.c_str() );
737 // gravitational subdivision
738 GravitationalSubdivision( bsp );
740 // merge portals where possible
741 bsp.MergePortals( AREACONTENTS_SOLID );
743 // melt portal windings
744 bsp.MeltPortals( AREACONTENTS_SOLID );
746 if ( aasSettings->writeBrushMap ) {
747 WriteLedgeMap( fileName, "_" + aasSettings->fileExtension + "_ledge" );
750 // ledge subdivisions
751 LedgeSubdivision( bsp );
754 MergeLeafNodes( bsp );
756 // merge portals where possible
757 bsp.MergePortals( AREACONTENTS_SOLID );
759 // melt portal windings
760 bsp.MeltPortals( AREACONTENTS_SOLID );
762 // store the file from the bsp tree
764 file->settings = *aasSettings;
766 // calculate reachability
767 reach.Build( mapFile, file );
770 cluster.Build( file );
773 if ( !aasSettings->noOptimize ) {
778 name.SetFileExtension( aasSettings->fileExtension );
779 file->Write( name, mapFile->GetGeometryCRC() );
781 // delete the map file
784 common->Printf( "%6d seconds to create AAS\n", (Sys_Milliseconds() - startTime) / 1000 );
791 idAASBuild::BuildReachability
794 bool idAASBuild::BuildReachability( const idStr &fileName, const idAASSettings *settings ) {
799 idAASCluster cluster;
801 startTime = Sys_Milliseconds();
803 aasSettings = settings;
806 name.SetFileExtension( "map" );
808 mapFile = new idMapFile;
809 if ( !mapFile->Parse( name ) ) {
811 common->Error( "Couldn't load map file: '%s'", name.c_str() );
815 file = new idAASFileLocal();
817 name.SetFileExtension( aasSettings->fileExtension );
818 if ( !file->Load( name, 0 ) ) {
820 common->Error( "Couldn't load AAS file: '%s'", name.c_str() );
824 file->settings = *aasSettings;
826 // calculate reachability
827 reach.Build( mapFile, file );
830 cluster.Build( file );
833 file->Write( name, mapFile->GetGeometryCRC() );
835 // delete the map file
838 common->Printf( "%6d seconds to calculate reachability\n", (Sys_Milliseconds() - startTime) / 1000 );
848 int ParseOptions( const idCmdArgs &args, idAASSettings &settings ) {
852 for ( i = 1; i < args.Argc(); i++ ) {
854 str = args.Argv( i );
855 str.StripLeading( '-' );
857 if ( str.Icmp( "usePatches" ) == 0 ) {
858 settings.usePatches = true;
859 common->Printf( "usePatches = true\n" );
860 } else if ( str.Icmp( "writeBrushMap" ) == 0 ) {
861 settings.writeBrushMap = true;
862 common->Printf( "writeBrushMap = true\n" );
863 } else if ( str.Icmp( "playerFlood" ) == 0 ) {
864 settings.playerFlood = true;
865 common->Printf( "playerFlood = true\n" );
866 } else if ( str.Icmp( "noOptimize" ) == 0 ) {
867 settings.noOptimize = true;
868 common->Printf( "noOptimize = true\n" );
871 return args.Argc() - 1;
879 void RunAAS_f( const idCmdArgs &args ) {
882 idAASSettings settings;
885 if ( args.Argc() <= 1 ) {
886 common->Printf( "runAAS [options] <mapfile>\n"
888 " -usePatches = use bezier patches for collision detection.\n"
889 " -writeBrushMap = write a brush map with the AAS geometry.\n"
890 " -playerFlood = use player spawn points as valid AAS positions.\n" );
894 common->ClearWarnings( "compiling AAS" );
896 common->SetRefreshOnPrint( true );
898 // get the aas settings definitions
899 const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
901 common->Error( "Unable to find entityDef for 'aas_types'" );
904 const idKeyValue *kv = dict->MatchPrefix( "type" );
905 while( kv != NULL ) {
906 const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
907 if ( !settingsDict ) {
908 common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
910 settings.FromDict( kv->GetValue(), settingsDict );
911 i = ParseOptions( args, settings );
912 mapName = args.Argv(i);
913 mapName.BackSlashesToSlashes();
914 if ( mapName.Icmpn( "maps/", 4 ) != 0 ) {
915 mapName = "maps/" + mapName;
917 aas.Build( mapName, &settings );
920 kv = dict->MatchPrefix( "type", kv );
922 common->Printf( "=======================================================\n" );
925 common->SetRefreshOnPrint( false );
926 common->PrintWarnings();
934 void RunAASDir_f( const idCmdArgs &args ) {
937 idAASSettings settings;
938 idFileList *mapFiles;
940 if ( args.Argc() <= 1 ) {
941 common->Printf( "runAASDir <folder>\n" );
945 common->ClearWarnings( "compiling AAS" );
947 common->SetRefreshOnPrint( true );
949 // get the aas settings definitions
950 const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
952 common->Error( "Unable to find entityDef for 'aas_types'" );
955 // scan for .map files
956 mapFiles = fileSystem->ListFiles( idStr("maps/") + args.Argv(1), ".map" );
958 // create AAS files for all the .map files
959 for ( i = 0; i < mapFiles->GetNumFiles(); i++ ) {
961 common->Printf( "=======================================================\n" );
964 const idKeyValue *kv = dict->MatchPrefix( "type" );
965 while( kv != NULL ) {
966 const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
967 if ( !settingsDict ) {
968 common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
970 settings.FromDict( kv->GetValue(), settingsDict );
971 aas.Build( idStr( "maps/" ) + args.Argv( 1 ) + "/" + mapFiles->GetFile( i ), &settings );
974 kv = dict->MatchPrefix( "type", kv );
976 common->Printf( "=======================================================\n" );
981 fileSystem->FreeFileList( mapFiles );
983 common->SetRefreshOnPrint( false );
984 common->PrintWarnings();
992 void RunReach_f( const idCmdArgs &args ) {
995 idAASSettings settings;
997 if ( args.Argc() <= 1 ) {
998 common->Printf( "runReach [options] <mapfile>\n" );
1002 common->ClearWarnings( "calculating AAS reachability" );
1004 common->SetRefreshOnPrint( true );
1006 // get the aas settings definitions
1007 const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
1009 common->Error( "Unable to find entityDef for 'aas_types'" );
1012 const idKeyValue *kv = dict->MatchPrefix( "type" );
1013 while( kv != NULL ) {
1014 const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
1015 if ( !settingsDict ) {
1016 common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
1018 settings.FromDict( kv->GetValue(), settingsDict );
1019 i = ParseOptions( args, settings );
1020 aas.BuildReachability( idStr("maps/") + args.Argv(i), &settings );
1023 kv = dict->MatchPrefix( "type", kv );
1025 common->Printf( "=======================================================\n" );
1029 common->SetRefreshOnPrint( false );
1030 common->PrintWarnings();