global function AddStationaryAIPosition //Add stationary positions to pending list. global function AddTestTargetPosForStationaryPositionValidation //Add test target location for validating stationary positions. global function ValidateAndFinalizePendingStationaryPositions //Runs error-checking/validation logic on stationary positions and finalizes them for use by AI. global function GetRandomStationaryPosition global function GetClosestAvailableStationaryPosition global function ClaimStationaryAIPosition global function ReleaseStationaryAIPosition global enum eStationaryAIPositionTypes { MORTAR_TITAN, MORTAR_SPECTRE, SNIPER_TITAN, LAUNCHER_REAPER } global struct StationaryAIPosition { vector origin bool inUse } global struct ArrayDistanceEntryForStationaryAIPosition { float distanceSqr StationaryAIPosition& ent vector origin } struct { array validationTestTargets table > pendingPositions table > stationaryPositions } file void function AddTestTargetPosForStationaryPositionValidation( vector origin ) { file.validationTestTargets.append( origin ) } void function AddStationaryAIPosition( vector origin, int type ) { AddPendingStationaryAIPosition_Internal( origin, type ) } void function AddStationaryAIPosition_Internal( vector origin, int type ) { StationaryAIPosition pos pos.origin = origin pos.inUse = false //Throw warnings for bad positions foreach ( vector testTarget in file.validationTestTargets ) { switch( type ) { case eStationaryAIPositionTypes.MORTAR_TITAN: if ( NavMesh_ClampPointForHullWithExtents( origin, HULL_TITAN, <100, 100, 20> ) == null ) { CodeWarning( "Mortar Titan Firing Position at " + origin + " does not have enough space to accomidate Titan, skipping." ) return } break #if MP case eStationaryAIPositionTypes.MORTAR_SPECTRE: array testLocations = MortarSpectreGetSquadFiringPositions( origin, testTarget ) foreach ( vector testLocation in testLocations ) { if ( NavMesh_ClampPointForHullWithExtents( testLocation, HULL_HUMAN, <100, 100, 20> ) == null ) { CodeWarning( "Mortar Spectre Firing Position at " + origin + " does not have enough space to accomidate squad, skipping." ) return } } break #endif //MP case eStationaryAIPositionTypes.SNIPER_TITAN: if ( NavMesh_ClampPointForHullWithExtents( origin, HULL_TITAN, <100, 100, 20> ) == null ) { CodeWarning( "Sniper Titan Firing Position at " + origin + " does not have enough space to accomidate Titan, skipping." ) return } break case eStationaryAIPositionTypes.LAUNCHER_REAPER: if ( NavMesh_ClampPointForHullWithExtents( origin, HULL_MEDIUM, <100, 100, 20> ) == null ) { CodeWarning( "Tick Launching Reaper Firing Position at " + origin + " does not have enough space to accomidate Reaper, skipping." ) return } break } } if ( !( type in file.stationaryPositions ) ) { file.stationaryPositions[ type ] <- [] } file.stationaryPositions[ type ].append( pos ) } //Function tests stationary AI positions for given type relative to given mortar target. void function AddPendingStationaryAIPosition_Internal( vector origin, int type ) { if ( !( type in file.pendingPositions ) ) file.pendingPositions[ type ] <- [] //Add position to table so we can validate and add it when all entities finish loading. file.pendingPositions[ type ].append( origin ) } void function ValidateAndFinalizePendingStationaryPositions() { Assert( file.validationTestTargets.len(), "Test targets are required to validate stationary positions. Use AddTestTargetPosForStationaryPositionValidation to add them before running validation." ) foreach ( type, origins in file.pendingPositions ) { //Make sure we have pending positions for given ai type. Assert( file.pendingPositions[ type ].len(), "Stationary Positions for type " + type + " could not be found in this map. Add Some." ) foreach ( vector origin in origins ) { AddStationaryAIPosition_Internal( origin, type ) } //Make sure we have positions for given AI type after we validate and finalize positions. Assert( file.stationaryPositions[ type ].len(), "No valid stationary positions for type " + type + " remain after validation. Adjust positions and retry." ) } } StationaryAIPosition function GetClosestAvailableStationaryPosition( vector origin, float maxDist, int type ) { array resultArray = [] float maxDistSqr = maxDist * maxDist array positions = file.stationaryPositions[type] array allResults = ArrayDistanceResultsForStationaryAIPosition( positions, origin ) allResults.sort( DistanceCompareClosestForStationaryAIPosition ) //Remove all in use stationary positions up front. array freePositions foreach ( result in allResults ) { StationaryAIPosition position = result.ent if ( position.inUse ) continue freePositions.append( result ) } //Tell us if all spots for a given AI type are taken. Assert( freePositions.len() > 0, "Could not find free mortar positions for type " + type + ", all positions are currently in use. Add more AddStationaryTitanPosition to the map." ) foreach( result in freePositions ) { StationaryAIPosition position = result.ent // if too far, throw warning and continue search beyond maxDist if ( result.distanceSqr > maxDistSqr ) { CodeWarning( "Couldn't find a mortar position within " + maxDist + " units for type " + type + " around " + origin.tostring() + " that wasn't in use. Expanding Search. Add more AddStationaryTitanPositions to the map near this point." ) } return position } unreachable } StationaryAIPosition function GetRandomStationaryPosition( vector origin, float maxDist, int type ) { array resultArray = [] array positions = file.stationaryPositions[type] //Remove all in use stationary positions up front. array freePositions foreach ( position in positions ) { if ( position.inUse ) continue freePositions.append( position ) } //Tell us if all spots for a given AI type are taken. Assert( freePositions.len() > 0, "Could not find free mortar positions for type " + type + ", all positions are currently in use. Add more AddStationaryTitanPosition to the map." ) int attemptCount = 1 while ( resultArray.len() == 0 ) { //Expand our search radius each time we reattempt our search. float maxDistSqr = ( maxDist * attemptCount ) * ( maxDist * attemptCount ) foreach( position in freePositions ) { float dist = Distance2DSqr( origin, position.origin ) if ( dist <= maxDistSqr ) resultArray.append( position ) } if ( resultArray.len() == 0 ) { CodeWarning( "Couldn't find a mortar position within " + maxDist + " units for type " + type + " around " + origin.tostring() + " that wasn't in use. Expanding Search. Add more AddStationaryTitanPositions to the map near this point." ) attemptCount += 1 } } return resultArray.getrandom() } void function ClaimStationaryAIPosition( StationaryAIPosition stationaryTitanPositions ) { Assert( stationaryTitanPositions.inUse == false ) stationaryTitanPositions.inUse = true } void function ReleaseStationaryAIPosition( StationaryAIPosition stationaryTitanPositions ) { Assert( stationaryTitanPositions.inUse == true ) stationaryTitanPositions.inUse = false } array function ArrayDistanceResultsForStationaryAIPosition( array entArray, vector origin ) { array allResults foreach ( ent in entArray ) { ArrayDistanceEntryForStationaryAIPosition entry vector entOrigin = ent.origin entry.distanceSqr = DistanceSqr( entOrigin, origin ) entry.ent = ent entry.origin = entOrigin allResults.append( entry ) } return allResults } int function DistanceCompareClosestForStationaryAIPosition( ArrayDistanceEntryForStationaryAIPosition a, ArrayDistanceEntryForStationaryAIPosition b ) { if ( a.distanceSqr > b.distanceSqr ) return 1 else if ( a.distanceSqr < b.distanceSqr ) return -1 return 0; }