//============ Copyright (c) Valve Corporation, All rights reserved. ============ // // // //=============================================================================== #include "asw_npcs.h" #include "asw_mission_chooser.h" #include "TileSource/MapLayout.h" #include "TileSource/Room.h" #include "TileSource/RoomTemplate.h" #include "KeyValues.h" #include "asw_spawn_selection.h" #include "layout_system/tilegen_layout_system.h" ConVar asw_encounters_distance_min( "asw_encounters_distance_min", "900", FCVAR_CHEAT, "Min distance between edges of encounter circles" ); ConVar asw_encounter_radius_min( "asw_encounter_radius_min", "384.0f", FCVAR_CHEAT ); ConVar asw_encounter_radius_max( "asw_encounter_radius_max", "512.0f", FCVAR_CHEAT ); // TODO: Parameters exposed to level designer? void CASWMissionChooserNPCs::InitFixedSpawns( CLayoutSystem *pLayoutSystem, CMapLayout *pLayout ) { // init the spawn set for this mission KeyValues *pGenerationOptions = pLayout->GetGenerationOptions(); if ( !pGenerationOptions ) { Warning( "Error placed fixed alien spawns, no generation options in this layout." ); return; } bool bChosenSpawnSet = false; const char *szNamedSpawnSet = pGenerationOptions->GetString( "AlienSpawnSet" ); if ( szNamedSpawnSet && szNamedSpawnSet[0] ) { bChosenSpawnSet = SpawnSelection()->SetCurrentSpawnSet( szNamedSpawnSet ); } if ( !bChosenSpawnSet ) { SpawnSelection()->SetCurrentSpawnSet( pGenerationOptions->GetInt( "Difficulty", 5 ) ); } // if we have any rooms with the alien encounter tag, then just use those for fixed spawn locations bool bAlienEncounterTag = false; int iRooms = pLayout->m_PlacedRooms.Count(); for ( int i = 0; i < iRooms; i++ ) { CRoom *pRoom = pLayout->m_PlacedRooms[i]; if ( pRoom && pRoom->m_pRoomTemplate && pRoom->m_pRoomTemplate->HasTag( "AlienEncounter" ) ) { bAlienEncounterTag = true; CASW_Encounter *pEncounter = new CASW_Encounter(); // pick a random spot in this room Vector vecWorldMins, vecWorldMaxs; pRoom->GetWorldBounds( &vecWorldMins, &vecWorldMaxs ); Vector vecPos = vecWorldMins + pLayoutSystem->GetRandomFloat( 0, 1 ) * ( vecWorldMaxs - vecWorldMins ); vecPos.z = 0; pEncounter->SetEncounterPosition( vecPos ); pEncounter->SetEncounterRadius( pLayoutSystem->GetRandomFloat( asw_encounter_radius_min.GetFloat(), asw_encounter_radius_max.GetFloat() ) ); // add spawn defs // TODO: more spawns in bigger rooms? or rooms with higher weights? int iSpawnsPerEncounter = pLayoutSystem->GetRandomInt( CurrentSpawnSet()->GetMinSpawnsPerEncounter(), CurrentSpawnSet()->GetMaxSpawnsPerEncounter() ); for ( int i = 0; i < iSpawnsPerEncounter; i++ ) { CASW_Spawn_Definition* pSpawnDef = CurrentSpawnSet()->GetSpawnDef( ASW_NPC_SPAWN_TYPE_FIXED ); if ( !pSpawnDef ) continue; pEncounter->AddSpawnDef( pSpawnDef ); } pLayout->m_Encounters.AddToTail( pEncounter ); } } if ( bAlienEncounterTag ) { pLayout->MarkEncounterRooms(); return; } // find area of the mission int iTotalArea = 0; for ( int i = 0; i < iRooms; i++ ) { CRoom *pRoom = pLayout->m_PlacedRooms[i]; iTotalArea += ( pRoom->m_pRoomTemplate->GetTilesX() * ASW_TILE_SIZE ) * ( pRoom->m_pRoomTemplate->GetTilesY() * ASW_TILE_SIZE ); } // decide how many encounters we want int iEncounters = pLayoutSystem->GetRandomInt( CurrentSpawnSet()->GetMinEncounters(), CurrentSpawnSet()->GetMaxEncounters() ); // distance between encounters //float flMinDistance = asw_encounters_distance_min.GetFloat(); // randomly pick rooms for the encounters to be in, using the room weights CUtlVector candidates; float flTotalWeight = 0; for ( int i = 0; i < iRooms; i++ ) { CRoom *pRoom = pLayout->m_PlacedRooms[i]; if ( pRoom->GetSpawnWeight() > 0 && !pRoom->m_pRoomTemplate->IsEscapeRoom() && !pRoom->m_pRoomTemplate->IsStartRoom() && !pRoom->m_pRoomTemplate->IsBorderRoom() ) { flTotalWeight += pRoom->GetSpawnWeight(); candidates.AddToTail( pRoom ); } } for ( int e = 0; e < iEncounters; e++ ) { float flChosen = pLayoutSystem->GetRandomFloat( 0, flTotalWeight ); CRoom *pChosenRoom = NULL; for ( int i = 0; i < candidates.Count(); i++ ) { CRoom *pRoom = candidates[i]; flChosen -= pRoom->GetSpawnWeight(); if ( flChosen <= 0 ) { pChosenRoom = pRoom; break; } } if ( !pChosenRoom ) continue; CASW_Encounter *pEncounter = new CASW_Encounter(); // pick a random spot in this room Vector vecWorldMins, vecWorldMaxs; pChosenRoom->GetWorldBounds( &vecWorldMins, &vecWorldMaxs ); //Vector vecPos = vecWorldMins + pLayoutSystem->GetRandomFloat( 0, 1 ) * ( vecWorldMaxs - vecWorldMins ); Vector vecPos = ( vecWorldMins + vecWorldMaxs ) * 0.5f; // center of the room vecPos.z = 0; pEncounter->SetEncounterPosition( vecPos ); pEncounter->SetEncounterRadius( pLayoutSystem->GetRandomFloat( asw_encounter_radius_min.GetFloat(), asw_encounter_radius_max.GetFloat() ) ); // add spawn defs // TODO: more spawns in bigger rooms? or rooms with higher weights? int iSpawnsPerEncounter = pLayoutSystem->GetRandomInt( CurrentSpawnSet()->GetMinSpawnsPerEncounter(), CurrentSpawnSet()->GetMaxSpawnsPerEncounter() ); for ( int i = 0; i < iSpawnsPerEncounter; i++ ) { CASW_Spawn_Definition* pSpawnDef = CurrentSpawnSet()->GetSpawnDef( ASW_NPC_SPAWN_TYPE_FIXED ); if ( !pSpawnDef ) continue; pEncounter->AddSpawnDef( pSpawnDef ); } pLayout->m_Encounters.AddToTail( pEncounter ); } PushEncountersApart( pLayout ); pLayout->MarkEncounterRooms(); } void CASWMissionChooserNPCs::PushEncountersApart( CMapLayout *pLayout ) { // get a list of valid rooms int iRooms = pLayout->m_PlacedRooms.Count(); CUtlVector candidates; float flTotalWeight = 0; for ( int i = 0; i < iRooms; i++ ) { CRoom *pRoom = pLayout->m_PlacedRooms[i]; if ( pRoom->GetSpawnWeight() > 0 && !pRoom->m_pRoomTemplate->IsEscapeRoom() && !pRoom->m_pRoomTemplate->IsStartRoom() && !pRoom->m_pRoomTemplate->IsBorderRoom() ) { // skip 1x1 rooms if ( pRoom->m_pRoomTemplate->GetTilesX() * pRoom->m_pRoomTemplate->GetTilesY() <= 1 ) continue; flTotalWeight += pRoom->GetSpawnWeight(); candidates.AddToTail( pRoom ); } } // push the encounters apart int iSteps = 6; for ( int i = 0; i < iSteps; i++ ) { int iEncounters = pLayout->m_Encounters.Count(); for ( int e = 0; e < iEncounters; e++ ) { Vector vecPush = vec3_origin; CASW_Encounter *pEncounter = pLayout->m_Encounters[ e ]; // accumulate a force from all other nearby encounters for ( int k = 0; k < iEncounters; k++ ) { if ( e == k ) continue; CASW_Encounter *pOther = pLayout->m_Encounters[ k ]; Vector dir = pEncounter->GetEncounterPosition() - pOther->GetEncounterPosition(); // direction from other to us float dist = dir.NormalizeInPlace(); float flMinDistance = asw_encounters_distance_min.GetFloat() + pEncounter->GetEncounterRadius() + pOther->GetEncounterRadius(); if ( dist < flMinDistance ) { float flPush = flMinDistance - dist; // push us exactly out of the min vecPush += dir * flPush; } } pEncounter->SetEncounterPosition( pEncounter->GetEncounterPosition() + vecPush ); // clamp encounters to room bounds CRoom *pClosestRoom = NULL; float flClosestDist = 65535.0f; bool bWithinARoom = false; Vector pos = pEncounter->GetEncounterPosition(); for ( int k = 0; k < candidates.Count(); k++ ) { CRoom *pRoom = candidates[k]; Vector vecMins, vecMaxs; pRoom->GetWorldBounds( &vecMins, &vecMaxs ); if ( pos.x >= vecMins.x && pos.x <= vecMaxs.x && pos.y >= vecMins.y && pos.y <= vecMaxs.y ) { bWithinARoom = true; break; } Vector vecMiddle = ( vecMins + vecMaxs ) * 0.5f; float flDist = vecMiddle.DistTo( pos ); if ( flDist < flClosestDist ) { flClosestDist = flDist; pClosestRoom = pRoom; } } // if encounter wasn't in any room, then clamp to the closest if ( !bWithinARoom && pClosestRoom ) { Vector vecMins, vecMaxs; pClosestRoom->GetWorldBounds( &vecMins, &vecMaxs ); pos.x = clamp( pos.x, vecMins.x, vecMaxs.x ); pos.y = clamp( pos.y, vecMins.y, vecMaxs.y ); pEncounter->SetEncounterPosition( pos ); } /* // put encounter in the center of its room for ( int k = 0; k < candidates.Count(); k++ ) { CRoom *pRoom = candidates[k]; Vector vecMins, vecMaxs; pRoom->GetWorldBounds( &vecMins, &vecMaxs ); if ( pos.x >= vecMins.x && pos.x <= vecMaxs.x && pos.y >= vecMins.y && pos.y <= vecMaxs.y ) { Vector mid = ( vecMins + vecMaxs ) * 0.5f; mid.z = 0; pEncounter->SetEncounterPosition ( mid ); break; } } */ } } }