diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp index 7a57339af..7c17268aa 100644 --- a/regamedll/dlls/bot/cs_bot_manager.cpp +++ b/regamedll/dlls/bot/cs_bot_manager.cpp @@ -36,6 +36,10 @@ bool CCSBotManager::m_isLearningMap = false; bool CCSBotManager::m_isAnalysisRequested = false; NavEditCmdType CCSBotManager::m_editCmd = EDIT_NONE; +int randomSpawnsCount = 0; +RandomSpawnStruct g_randomSpawns[MAX_RANDOM_SPAWNS]; + + CCSBotManager::CCSBotManager() { m_flNextCVarCheck = 0.0f; @@ -1096,6 +1100,125 @@ class CollectOverlappingAreas CCSBotManager::Zone *m_zone; }; +inline bool IsValidArea(CNavArea *area) +{ + ShortestPathCost cost; + bool bNotOrphaned; + + // check that we can path from the nav area to a ct spawner to confirm it isn't orphaned. + CBaseEntity *CTSpawn = UTIL_FindEntityByClassname(nullptr, "info_player_start"); + + if (CTSpawn) + { + CNavArea *CTSpawnArea = TheNavAreaGrid.GetNearestNavArea(&CTSpawn->pev->origin); + bNotOrphaned = NavAreaBuildPath(area, CTSpawnArea, nullptr, cost); + + if (bNotOrphaned) + return true; + } + + // double check that we can path from the nav area to a t spawner to confirm it isn't orphaned. + CBaseEntity *TSpawn = UTIL_FindEntityByClassname(nullptr, "info_player_deathmatch"); + + if (TSpawn) + { + CNavArea *TSpawnArea = TheNavAreaGrid.GetNearestNavArea(&TSpawn->pev->origin); + bNotOrphaned = NavAreaBuildPath(area, TSpawnArea, nullptr, cost); + + if (bNotOrphaned) + return true; + } + + return false; +} + +void GetDMSpawnPositions() +{ + const float MIN_AREA_SIZE = 64.0f; + const float ANGLE_STEP = 45.0f; + + randomSpawnsCount = 0; + Q_memset(g_randomSpawns, 0, MAX_RANDOM_SPAWNS); + + for (NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); iter++) + { + if (randomSpawnsCount >= MAX_RANDOM_SPAWNS) + break; + + CNavArea *area = *iter; + + if (!area) + continue; + + // without attributes + /* + if (area->GetAttributes()) + continue; + */ + + if (UTIL_PointContents(*area->GetCenter()) != CONTENTS_EMPTY) + continue; + + if (area->GetSizeX() < MIN_AREA_SIZE || area->GetSizeY() < MIN_AREA_SIZE) + continue; + + if (!IsValidArea(area)) + continue; + + Vector vecOrigin = *area->GetCenter() + Vector(0, 0, HalfHumanHeight + 5); + + if (IsFreeSpace(vecOrigin, human_hull)) + { + Vector vecTempAngle; + float bestAngleYaw; + float bestDistance = 0.0f; + float fDistance; + + // CONSOLE_ECHO("==============================\n"); + + for (float AngleYaw = 0.0f; AngleYaw <= 360.0f; AngleYaw += ANGLE_STEP) + { + TraceResult tr; + + vecTempAngle.y = AngleYaw; + UTIL_MakeVectors(vecTempAngle); + + Vector vecEnd(vecOrigin + gpGlobals->v_forward * 8192); + UTIL_TraceLine(vecOrigin, vecEnd, ignore_monsters, nullptr, &tr); + + fDistance = (vecOrigin - tr.vecEndPos).Length(); + + if (fDistance > bestDistance) + { + bestDistance = fDistance; + bestAngleYaw = vecTempAngle.y; + } + + // CONSOLE_ECHO("Current: %0.f | Distance: %0.2f \n", vecTempAngle.y, fDistance); + } + + if (bestDistance < 100.0f) + { + continue; + } + + { + vecTempAngle.y = bestAngleYaw; + g_randomSpawns[randomSpawnsCount].vecAngle = vecTempAngle; + // CONSOLE_ECHO("Area %i | Best angle %0.2f | Distance: %0.2f\n", area->GetID(), bestvecAngle, bestDistance); + } + + g_randomSpawns[randomSpawnsCount].vecOrigin = vecOrigin; + g_randomSpawns[randomSpawnsCount].area = area; + randomSpawnsCount++; + // CONSOLE_ECHO("Add spawn. at x:%f y:%f z:%f\n", vecOrigin.x, vecOrigin.y, vecOrigin.z); + } + } + + CONSOLE_ECHO("Tatal random spawns: %i.\n", randomSpawnsCount); +} + + // Search the map entities to determine the game scenario and define important zones. void CCSBotManager::ValidateMapData() { @@ -1112,6 +1235,10 @@ void CCSBotManager::ValidateMapData() CONSOLE_ECHO("Navigation map loaded.\n"); +#ifdef REGAMEDLL_ADD + GetDMSpawnPositions(); +#endif + m_zoneCount = 0; m_gameScenario = SCENARIO_DEATHMATCH; diff --git a/regamedll/dlls/bot/cs_bot_manager.h b/regamedll/dlls/bot/cs_bot_manager.h index 1faea5461..2165262d4 100644 --- a/regamedll/dlls/bot/cs_bot_manager.h +++ b/regamedll/dlls/bot/cs_bot_manager.h @@ -268,3 +268,25 @@ inline bool AreBotsAllowed() } void PrintAllEntities(); + + +#define MAX_RANDOM_SPAWNS 128 + + +struct RandomSpawnStruct +{ + Vector vecOrigin; + Vector vecAngle; + CNavArea *area; +}; + +extern RandomSpawnStruct g_randomSpawns[MAX_RANDOM_SPAWNS]; +extern int randomSpawnsCount; + +inline bool IsFreeSpace(Vector vecOrigin, int iHullNumber, edict_t *pSkipEnt = nullptr) +{ + TraceResult trace; + UTIL_TraceHull(vecOrigin, vecOrigin, dont_ignore_monsters, iHullNumber, pSkipEnt, &trace); + + return (!trace.fStartSolid && !trace.fAllSolid && trace.fInOpen); +} diff --git a/regamedll/dlls/game.cpp b/regamedll/dlls/game.cpp index f9d62d6b8..915012165 100644 --- a/regamedll/dlls/game.cpp +++ b/regamedll/dlls/game.cpp @@ -148,6 +148,7 @@ cvar_t ff_damage_reduction_other = { "ff_damage_reduction_other", cvar_t radio_timeout = { "mp_radio_timeout", "1.5", FCVAR_SERVER, 1.5f, nullptr }; cvar_t radio_maxinround = { "mp_radio_maxinround", "60", FCVAR_SERVER, 60.0f, nullptr }; cvar_t falldamage = { "mp_falldamage", "1", FCVAR_SERVER, 1.0f, nullptr }; +cvar_t randomspawn = { "mp_randomspawn", "0", FCVAR_SERVER, 0.0f, nullptr }; void GameDLL_Version_f() @@ -362,7 +363,8 @@ void EXT_FUNC GameDLLInit() CVAR_REGISTER(&radio_timeout); CVAR_REGISTER(&radio_maxinround); CVAR_REGISTER(&falldamage); - + CVAR_REGISTER(&randomspawn); + // print version CONSOLE_ECHO("ReGameDLL version: " APP_VERSION "\n"); diff --git a/regamedll/dlls/game.h b/regamedll/dlls/game.h index 6884a41c4..4d1fccfa4 100644 --- a/regamedll/dlls/game.h +++ b/regamedll/dlls/game.h @@ -174,6 +174,7 @@ extern cvar_t ff_damage_reduction_other; extern cvar_t radio_timeout; extern cvar_t radio_maxinround; extern cvar_t falldamage; +extern cvar_t randomspawn; #endif diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index 460f5d247..c125fa272 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -28,6 +28,8 @@ RewardAccount CHalfLifeMultiplay::m_rgRewardAccountRules_default[] = { REWARD_TOOK_HOSTAGE, // RR_TOOK_HOSTAGE }; +int lastSpawn[MAX_CLIENTS]; + bool IsBotSpeaking() { for (int i = 1; i <= gpGlobals->maxClients; i++) @@ -428,6 +430,7 @@ CHalfLifeMultiplay::CHalfLifeMultiplay() } Q_memset(m_iMapVotes, 0, sizeof(m_iMapVotes)); + Q_memset(lastSpawn, 0, MAX_CLIENTS); m_iLastPick = 1; m_bMapHasEscapeZone = false; @@ -4235,6 +4238,61 @@ int CHalfLifeMultiplay::DeadPlayerAmmo(CBasePlayer *pPlayer) return GR_PLR_DROP_AMMO_ACTIVE; } +#ifdef REGAMEDLL_ADD +inline bool PlayersInRadius(CBasePlayer *pPlayer, Vector vecOrigin) +{ + CBaseEntity *pEntity = nullptr; + const float flRadius = randomSpawnsCount * 4; + + while ((pEntity = UTIL_FindEntityInSphere(pEntity, vecOrigin, flRadius))) + { + if (pEntity != pPlayer && pEntity->IsPlayer() && pEntity->IsAlive()) + return true; + } + + return false; +} + +bool RandomSpawn(CBasePlayer *pPlayer) +{ + if (!pPlayer || randomSpawnsCount <= 0 || TheNavAreaList.empty()) + { + return false; + } + + const float flRadius = randomSpawnsCount * 6; + const int MAX_ATTEMPTS = 10; + + int last = lastSpawn[pPlayer->entindex() - 1]; + + for (int attempts = 0; attempts < MAX_ATTEMPTS; attempts++) + { + int which = RANDOM_LONG(0, randomSpawnsCount - 1); + + if (last != which && (g_randomSpawns[last].vecOrigin - g_randomSpawns[which].vecOrigin).Length() > flRadius + && IsFreeSpace(g_randomSpawns[which].vecOrigin, human_hull, pPlayer->edict()) && !PlayersInRadius(pPlayer, g_randomSpawns[which].vecOrigin)) + { + UTIL_SetOrigin(pPlayer->pev, g_randomSpawns[which].vecOrigin); + + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = g_randomSpawns[which].vecAngle; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = 1; + + lastSpawn[pPlayer->entindex() - 1] = which; + + // CONSOLE_ECHO("Area %i | Angle %0.1f\n", g_randomSpawns[which].cur_area->GetID(), pPlayer->pev->angles.y); + + return true; + } + } + + return false; +} + +#endif // REGAMEDLL_ADD + LINK_HOOK_CLASS_CUSTOM_CHAIN(edict_t *, CHalfLifeMultiplay, CSGameRules, GetPlayerSpawnSpot, (CBasePlayer *pPlayer), pPlayer) edict_t *EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GetPlayerSpawnSpot)(CBasePlayer *pPlayer) @@ -4248,6 +4306,12 @@ edict_t *EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GetPlayerSpawnSpot)(CBasePlayer { FireTargets(STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0); } +#ifdef REGAMEDLL_ADD + if (randomspawn.value && pPlayer->pev->deadflag == DEAD_NO && RandomSpawn(pPlayer)) + { + return pPlayer->edict(); + } +#endif } return pentSpawnSpot;