Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Randomspawn #1013

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
| mp_ammo_respawn_time | 20 | 0.0 | - | The respawn time for ammunition. |
| mp_vote_flags | km | 0 | - | Vote systems enabled in server.<br/>`0` voting disabled<br/>`k` votekick enabled via `vote` command<br/>`m` votemap enabled via `votemap` command |
| mp_votemap_min_time | 180 | 0.0 | - | Minimum seconds that must elapse on map before `votemap` command can be used. |
| mp_randomspawn | 0 | 0 | 1 | Random player spawns<br/>`0` disabled <br/>`1` enabled<br/>`NOTE`: Navigation `maps/.nav` file required |

</details>

Expand Down
11 changes: 10 additions & 1 deletion dist/game.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ mp_give_c4_frags "3"
// Default value: "1.0"
mp_hostages_rescued_ratio "1.0"

// Legacy func_vehicle behavior when blocked by another entity.
// Legacy func_vehicle behavior when blocked by another entity.
// New one is more useful for playing multiplayer.
//
// 0 - New behavior
Expand Down Expand Up @@ -620,3 +620,12 @@ mp_vote_flags "km"
//
// Default value: "180"
mp_votemap_min_time "180"

// Random player spawns
// 0 - disabled (default behaviour)
// 1 - enabled
//
// NOTE: Navigation "maps/.nav" file required
//
// Default value: "0"
mp_randomspawn "0"
160 changes: 160 additions & 0 deletions regamedll/dlls/bot/cs_bot_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,166 @@ class CollectOverlappingAreas
CCSBotManager::Zone *m_zone;
};

#ifdef REGAMEDLL_ADD
LINK_ENTITY_TO_CLASS(info_spawn_point, CPointEntity, CCSPointEntity)
Vaqtincha marked this conversation as resolved.
Show resolved Hide resolved

inline bool IsFreeSpace(Vector vecOrigin, int iHullNumber, edict_t *pSkipEnt = nullptr)
{
if (UTIL_PointContents(vecOrigin) != CONTENTS_EMPTY)
return false;

TraceResult trace;
UTIL_TraceHull(vecOrigin, vecOrigin, dont_ignore_monsters, iHullNumber, pSkipEnt, &trace);

return (!trace.fStartSolid && !trace.fAllSolid && trace.fInOpen);
}

inline bool pointInRadius(Vector vecOrigin, float radius)
{
CBaseEntity *pEntity = nullptr;
while ((pEntity = UTIL_FindEntityInSphere(pEntity, vecOrigin, radius)))
{
if (FClassnameIs(pEntity->edict(), "info_spawn_point"))
return true;
}

return false;
}

// a simple algorithm that searches for the farthest point (so that the player does not look at the wall)
inline Vector GetBestAngle(const Vector &vecStart)
{
const float ANGLE_STEP = 30.0f;
float bestAngle = 0.0f;
float bestDistance = 0.0f;
Vector vecBestAngle = Vector(0, -1, 0);

for (float angleYaw = 0.0f; angleYaw <= 360.0f; angleYaw += ANGLE_STEP)
{
TraceResult tr;
UTIL_MakeVectors(Vector(0, angleYaw, 0));

Vector vecEnd(vecStart + gpGlobals->v_forward * 8192);
UTIL_TraceLine(vecStart, vecEnd, ignore_monsters, nullptr, &tr);

float distance = (vecStart - tr.vecEndPos).Length();

if (distance > bestDistance)
{
bestDistance = distance;
vecBestAngle.y = angleYaw;
}
}

return vecBestAngle;
}

// this function from leaked csgo sources 2020y
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 GetSpawnPositions()
{
const float MIN_AREA_SIZE = 32.0f;
const int MAX_SPAWNS_POINTS = 128;
const float MAX_SLOPE = 0.85f;

int totalSpawns = 0;

for (NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); iter++)
{
if (totalSpawns >= MAX_SPAWNS_POINTS)
break;

CNavArea *area = *iter;

if (!area)
continue;

// ignore small areas
if (area->GetSizeX() < MIN_AREA_SIZE || area->GetSizeY() < MIN_AREA_SIZE)
continue;

// ignore areas jump, crouch etc
if (area->GetAttributes())
continue;

if (area->GetAreaSlope() < MAX_SLOPE)
{
//CONSOLE_ECHO("Skip area slope: %0.3f\n", area->GetAreaSlope());
continue;
}

Vector vecOrigin = *area->GetCenter() + Vector(0, 0, HalfHumanHeight + 5);

if (!IsFreeSpace(vecOrigin, human_hull))
{
//CONSOLE_ECHO("No free space!\n");
continue;
}

if (pointInRadius(vecOrigin, 128.0f))
continue;

if (!IsValidArea(area))
continue;

Vector bestAngle = GetBestAngle(vecOrigin);

if (bestAngle.y != -1)
{
CBaseEntity* pPoint = CBaseEntity::Create("info_spawn_point", vecOrigin, bestAngle, nullptr);

if (pPoint)
{
totalSpawns++;
//CONSOLE_ECHO("Add spawn at x:%f y:%f z:%f with angle %0.1f slope %0.3f \n", vecOrigin.x, vecOrigin.y, vecOrigin.z, bestAngle.y, area->GetAreaSlope());

// use only for debugging
if (randomspawn.value > 1.0f)
{
SET_MODEL(ENT(pPoint->pev), "models/player.mdl");
pPoint->pev->sequence = ACT_IDLE;
pPoint->pev->rendermode = kRenderTransAdd;
pPoint->pev->renderamt = 150.0f;
}
}
}
}

CONSOLE_ECHO("Total spawns points: %i\n", totalSpawns);
}
#endif

// Search the map entities to determine the game scenario and define important zones.
void CCSBotManager::ValidateMapData()
{
Expand Down
3 changes: 3 additions & 0 deletions regamedll/dlls/bot/cs_bot_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,6 @@ inline bool AreBotsAllowed()
}

void PrintAllEntities();
#ifdef REGAMEDLL_ADD
void GetSpawnPositions();
#endif
6 changes: 4 additions & 2 deletions regamedll/dlls/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3814,8 +3814,10 @@ void EXT_FUNC ServerActivate(edict_t *pEdictList, int edictCount, int clientMax)
#ifdef REGAMEDLL_ADD
CSGameRules()->ServerActivate();

if (location_area_info.value)
LoadNavigationMap();
if (LoadNavigationMap() == NAV_OK)
{
GetSpawnPositions();
}
#endif
}

Expand Down
3 changes: 3 additions & 0 deletions regamedll/dlls/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ cvar_t ammo_respawn_time = { "mp_ammo_respawn_time", "20", FCVAR_SERVER, 2
cvar_t vote_flags = { "mp_vote_flags", "km", 0, 0.0f, nullptr };
cvar_t votemap_min_time = { "mp_votemap_min_time", "180", 0, 180.0f, nullptr };

cvar_t randomspawn = { "mp_randomspawn", "0", FCVAR_SERVER, 0.0f, nullptr };

void GameDLL_Version_f()
{
if (Q_stricmp(CMD_ARGV(1), "version") != 0)
Expand Down Expand Up @@ -459,6 +461,7 @@ void EXT_FUNC GameDLLInit()

CVAR_REGISTER(&vote_flags);
CVAR_REGISTER(&votemap_min_time);
CVAR_REGISTER(&randomspawn);
Vaqtincha marked this conversation as resolved.
Show resolved Hide resolved

// print version
CONSOLE_ECHO("ReGameDLL version: " APP_VERSION "\n");
Expand Down
1 change: 1 addition & 0 deletions regamedll/dlls/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ extern cvar_t weapon_respawn_time;
extern cvar_t ammo_respawn_time;
extern cvar_t vote_flags;
extern cvar_t votemap_min_time;
extern cvar_t randomspawn;

#endif

Expand Down
59 changes: 51 additions & 8 deletions regamedll/dlls/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5335,17 +5335,24 @@ void EXT_FUNC CBasePlayer::__API_HOOK(PostThink)()
}

// checks if the spot is clear of players
BOOL IsSpawnPointValid(CBaseEntity *pPlayer, CBaseEntity *pSpot)
BOOL IsSpawnPointValid(CBaseEntity *pPlayer, CBaseEntity *pSpot, float fRadius)
{
if (!pSpot->IsTriggered(pPlayer))
return FALSE;

CBaseEntity *pEntity = nullptr;
while ((pEntity = UTIL_FindEntityInSphere(pEntity, pSpot->pev->origin, MAX_PLAYER_USE_RADIUS)))

while ((pEntity = UTIL_FindEntityInSphere(pEntity, pSpot->pev->origin, fRadius)))
{
// if ent is a client, don't spawn on 'em
if (pEntity->IsPlayer() && pEntity != pPlayer)
if (pEntity->IsPlayer() && pEntity != pPlayer
#ifdef REGAMEDLL_FIXES
&& pEntity->IsAlive()
#endif
)
{
return FALSE;
}
}

return TRUE;
Expand All @@ -5370,17 +5377,34 @@ bool CBasePlayer::SelectSpawnSpot(const char *pEntClassName, CBaseEntity *&pSpot
{
if (pSpot)
{
// check if pSpot is valid
if (IsSpawnPointValid(this, pSpot))
#ifdef REGAMEDLL_ADD
if (FClassnameIs(pSpot->edict(), "info_spawn_point"))
{
if (pSpot->pev->origin == Vector(0, 0, 0))
if (!IsSpawnPointValid(this, pSpot, 512.0f) || pSpot->pev->origin == Vector(0, 0, 0))
{
pSpot = UTIL_FindEntityByClassname(pSpot, pEntClassName);
continue;
}
else
{
return true;
}
}
else
#endif
{
// check if pSpot is valid
if (IsSpawnPointValid(this, pSpot, MAX_PLAYER_USE_RADIUS))
{
if (pSpot->pev->origin == Vector(0, 0, 0))
{
pSpot = UTIL_FindEntityByClassname(pSpot, pEntClassName);
continue;
}

// if so, go to pSpot
return true;
// if so, go to pSpot
return true;
}
}
}

Expand Down Expand Up @@ -5438,6 +5462,24 @@ edict_t *EXT_FUNC CBasePlayer::__API_HOOK(EntSelectSpawnPoint)()
if (!FNullEnt(pSpot))
goto ReturnSpot;
}
#ifdef REGAMEDLL_ADD
else if (randomspawn.value > 0)
{
pSpot = g_pLastSpawn;

if (SelectSpawnSpot("info_spawn_point", pSpot))
{
g_pLastSpawn = pSpot;

return pSpot->edict();
}

if (m_iTeam == CT)
goto CTSpawn;
else if (m_iTeam == TERRORIST)
goto TSpawn;
}
#endif
// VIP spawn point
else if (g_pGameRules->IsDeathmatch() && m_bIsVIP)
{
Expand Down Expand Up @@ -5465,6 +5507,7 @@ edict_t *EXT_FUNC CBasePlayer::__API_HOOK(EntSelectSpawnPoint)()
// The terrorist spawn points
else if (g_pGameRules->IsDeathmatch() && m_iTeam == TERRORIST)
{
TSpawn:
pSpot = g_pLastTerroristSpawn;

if (SelectSpawnSpot("info_player_deathmatch", pSpot))
Expand Down
2 changes: 1 addition & 1 deletion regamedll/dlls/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ int TrainSpeed(int iSpeed, int iMax);
void LogAttack(CBasePlayer *pAttacker, CBasePlayer *pVictim, int teamAttack, int healthHit, int armorHit, int newHealth, int newArmor, const char *killer_weapon_name);
bool CanSeeUseable(CBasePlayer *me, CBaseEntity *pEntity);
void FixPlayerCrouchStuck(edict_t *pPlayer);
BOOL IsSpawnPointValid(CBaseEntity *pPlayer, CBaseEntity *pSpot);
BOOL IsSpawnPointValid(CBaseEntity *pPlayer, CBaseEntity *pSpot, float fRadius);
CBaseEntity *FindEntityForward(CBaseEntity *pMe);
real_t GetPlayerPitch(const edict_t *pEdict);
real_t GetPlayerYaw(const edict_t *pEdict);
Expand Down
4 changes: 4 additions & 0 deletions regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd
Original file line number Diff line number Diff line change
Expand Up @@ -3098,3 +3098,7 @@
@PointClass base(BaseCommand) size(-8 -8 -8, 8 8 8) = point_clientcommand : "It issues commands to the client console"
[
]

@PointClass iconsprite("sprites/CS/info_player_start.spr") base(PlayerClass) = info_spawn_point : "Random spawn start"
[
]
19 changes: 19 additions & 0 deletions regamedll/game_shared/bot/nav_area.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,25 @@ class CNavArea
void AddLadderUp(CNavLadder *ladder) { m_ladder[LADDER_UP].push_back(ladder); }
void AddLadderDown(CNavLadder *ladder) { m_ladder[LADDER_DOWN].push_back(ladder); }

inline float GetAreaSlope()
{
Vector u, v;

// compute our unit surface normal
u.x = m_extent.hi.x - m_extent.lo.x;
u.y = 0.0f;
u.z = m_neZ - m_extent.lo.z;

v.x = 0.0f;
v.y = m_extent.hi.y - m_extent.lo.y;
v.z = m_swZ - m_extent.lo.z;

Vector normal = CrossProduct(u, v);
normal.NormalizeInPlace();

return normal.z;
}

private:
friend void ConnectGeneratedAreas();
friend void MergeGeneratedAreas();
Expand Down
Loading