openmohaa/code/fgame/dm_team.cpp
2023-07-05 20:52:55 +02:00

764 lines
16 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// dm_team.cpp: Deathmatch Team Manager.
#include "player.h"
#include "dm_team.h"
#include "dm_manager.h"
#include "playerstart.h"
typedef struct spawnsort_s {
PlayerStart *spawnpoint;
float fMetric;
} spawnsort_t;
static qboolean SpotWouldTelefrag( float *origin )
{
static Vector mins = Vector( -16, -16, 1 );
static Vector maxs = Vector( 16, 16, 97 );
trace_t trace;
trace = G_Trace( Vector(origin), mins, maxs, Vector(origin), NULL, MASK_PLAYERSOLID, qfalse, "SpotWouldTelefrag" );
if( trace.startsolid || trace.allsolid )
return qtrue;
else
return qfalse;
}
static int compare_spawnsort( const void *pe1, const void *pe2 )
{
float fDelta = ( ( spawnsort_t * )pe1 )->fMetric - ( ( spawnsort_t * )pe2 )->fMetric;
if( fDelta >= -0.001f )
{
if( fDelta <= 0.001f )
{
return 0;
}
else
{
return -1;
}
}
else
{
return 1;
}
}
static PlayerStart *GetRandomSpawnpointFromList( spawnsort_t *pSpots, int nSpots )
{
int i = nSpots;
float fTotalMetric;
float fMinPosMetric;
float fChosen;
if( nSpots <= 0 ) {
return NULL;
}
qsort( pSpots, nSpots, sizeof( spawnsort_t ), compare_spawnsort );
if( pSpots[ 0 ].fMetric > 0.0f )
{
if( nSpots > 5 ) {
nSpots = 5;
}
fMinPosMetric = pSpots[ 0 ].fMetric * nSpots;
fTotalMetric = fMinPosMetric;
if( nSpots <= 1 )
{
fChosen = fMinPosMetric;
}
else
{
i = 0;
fTotalMetric = 0.0f;
for( i = 0; i < nSpots; i++ )
{
if( pSpots[ i ].fMetric <= 0.0f ) {
break;
}
fChosen = pSpots[ i ].fMetric * ( nSpots - i );
fTotalMetric += fChosen;
i++;
}
if( i < nSpots )
{
fChosen = fMinPosMetric;
}
fMinPosMetric = fTotalMetric;
}
fTotalMetric = ( fMinPosMetric - i * fChosen * 0.90f ) * G_Random();
for( i = 0; i < nSpots - 1; i++ )
{
fTotalMetric -= ( nSpots - i ) * pSpots[ i ].fMetric - ( fChosen * 0.90f );
if( fTotalMetric <= 0.0f ) {
break;
}
}
return pSpots[ i ].spawnpoint;
}
else
{
// return the spot anyway
return pSpots[ 0 ].spawnpoint;
}
}
void DM_Team::Reset( void )
{
m_spawnpoints.ClearObjectList();
m_players.ClearObjectList();
}
void DM_Team::AddDeaths( Player *player, int numDeaths )
{
if( level.intermissiontime || dmManager.GetTeamWin() )
return;
if( g_gametype->integer == GT_TEAM_ROUNDS || g_gametype->integer == GT_OBJECTIVE )
return;
player->AddDeaths( numDeaths );
if( m_teamnumber > TEAM_FREEFORALL )
m_iDeaths += numDeaths;
}
void DM_Team::AddKills( Player *player, int numKills )
{
if( level.intermissiontime || dmManager.GetTeamWin() )
return;
player->AddKills( numKills );
if( m_teamnumber > TEAM_FREEFORALL )
{
m_iKills += numKills;
if( g_gametype->integer == GT_TEAM_ROUNDS || g_gametype->integer == GT_OBJECTIVE )
player->AddDeaths( numKills );
else
m_teamwins += numKills;
}
}
void DM_Team::AddPlayer( Player *player )
{
m_players.AddObject( player );
}
void DM_Team::RemovePlayer( Player *player )
{
m_players.RemoveObject( player );
}
void DM_Team::TeamWin( void )
{
m_teamwins++;
m_wins_in_a_row++;
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
m_players.ObjectAt( i )->WonMatch();
}
UpdateTeamStatus();
if( m_teamnumber == TEAM_ALLIES )
{
gi.Cvar_Set( "g_scoreboardpicover", "textures/hud/allieswin" );
}
else if( m_teamnumber == TEAM_AXIS )
{
gi.Cvar_Set( "g_scoreboardpicover", "textures/hud/axiswin" );
}
}
void DM_Team::TeamLoss( void )
{
m_wins_in_a_row = 0;
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
m_players.ObjectAt( i )->LostMatch();
}
UpdateTeamStatus();
}
bool DM_Team::IsDead( void ) const
{
Player *player;
if( IsEmpty() )
{
if( dmManager.IsGameActive() ) {
return true;
}
return false;
}
if( !m_bHasSpawnedPlayers ) {
return false;
}
if( dmManager.AllowRespawn() ) {
return false;
}
for( int i = m_players.NumObjects(); i > 0; i-- )
{
player = m_players.ObjectAt( i );
if( player->IsSpectator() ) {
continue;
}
if( !player->IsDead() )
{
return false;
}
}
return true;
}
bool DM_Team::IsEmpty( void ) const
{
return m_players.NumObjects() <= 0;
}
bool DM_Team::IsReady( void ) const
{
return !NumNotReady();
}
int DM_Team::NumNotReady( void ) const
{
Player *player;
int num = 0;
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
player = m_players.ObjectAt( i );
if( player->IsReady() && !player->IsDead() )
i++;
}
return num;
}
int DM_Team::NumLivePlayers( void ) const
{
Player *player;
int num = 0;
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
player = m_players.ObjectAt( i );
if( !player->IsDead() && !player->IsSpectator() )
{
num++;
}
}
return num;
}
int DM_Team::TotalPlayersKills( void ) const
{
Player *player;
int iKills = 0;
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
player = m_players.ObjectAt( i );
if( !player->IsDead() && !player->IsSpectator() )
{
iKills += player->GetNumKills();
}
}
return iKills;
}
void DM_Team::TeamInvulnerable( void )
{
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
m_players.ObjectAt( i )->takedamage = DAMAGE_NO;
}
}
void DM_Team::BeginFight( void )
{
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
m_players.ObjectAt( i )->BeginFight();
}
}
void DM_Team::UpdateTeamStatus( void )
{
for( int i = 1; i <= m_players.NumObjects(); i++ )
{
m_players.ObjectAt( i )->UpdateStatus( va( "%d wins (%d in a row)", m_teamwins, m_wins_in_a_row ) );
}
}
float SpawnpointMetric_Ffa( const float *origin, DM_Team *dmTeam, const Player *player )
{
float fMinEnemyDistSquared = 23170.0f * 23170.0f;
int i;
int nPlayers = dmManager.PlayerCount();
float fDist;
for( i = 1; i < nPlayers; i++ )
{
Player *teammate = dmManager.GetPlayer( i );
if( teammate == player || teammate->IsDead() || teammate->IsSpectator() ) {
continue;
}
fDist = ( teammate->origin - Vector( origin ) ).lengthSquared();
if( fMinEnemyDistSquared > fDist ) {
fMinEnemyDistSquared = fDist;
}
}
return fMinEnemyDistSquared - ( G_Random( 0.25f ) + 1.0f ) * ( 1024.0f * 1024.0f );
}
float SpawnpointMetric_Team( const float *origin, DM_Team *dmTeam, const Player *player )
{
float fMinEnemyDistSquared = 23170.0f * 23170.0f;
float fSumFriendDistSquared = 0.0f;
float fDistSquared;
float fMetric;
int i;
int nPlayers = dmManager.PlayerCount();
int nFriends = 0;
for( i = 1; i < nPlayers; i++ )
{
Player *teammate = dmManager.GetPlayer( i );
if( teammate == player || teammate->IsDead() || teammate->IsSpectator() ) {
continue;
}
fDistSquared = ( teammate->origin - Vector( origin ) ).lengthSquared();
if( teammate->GetDM_Team() == dmTeam )
{
nFriends++;
fSumFriendDistSquared += fDistSquared;
}
else
{
if( fMinEnemyDistSquared > fDistSquared ) {
fMinEnemyDistSquared = fDistSquared;
}
}
}
fMetric = fMinEnemyDistSquared - ( G_Random( 0.25f ) + 1.0f ) * ( 1024.0f * 1024.0f );
if( nFriends )
{
fMetric += 0.25f * ( ( 23170.0f * 23170.0f ) - fSumFriendDistSquared / nFriends );
}
return fMetric;
}
float SpawnpointMetric_Objective( const float *origin, DM_Team *dmTeam, const Player *player )
{
return rand() * 0.0000000005f;
}
PlayerStart *DM_Team::GetRandomFfaSpawnpoint( Player *player )
{
return GetRandomSpawnpointWithMetric( player, SpawnpointMetric_Ffa );
}
PlayerStart *DM_Team::GetRandomTeamSpawnpoint( Player *player )
{
return GetRandomSpawnpointWithMetric( player, SpawnpointMetric_Team );
}
PlayerStart *DM_Team::GetRandomObjectiveSpawnpoint( Player *player )
{
return GetRandomSpawnpointWithMetric( player, SpawnpointMetric_Objective );
}
float DM_Team::PlayersRangeFromSpot( PlayerStart *spot )
{
float bestplayerdistance = 9999999.0f;
Vector vDist;
int i;
gentity_t *ent;
Player *player;
if( g_gametype->integer > GT_FFA ) {
return bestplayerdistance;
}
// find the nearest player from the post
for( i = 0, ent = g_entities; i < game.maxclients; i++, ent++ )
{
if( !ent->client || !ent->entity ) {
continue;
}
player = ( Player * )ent->entity;
if( !player->IsDead() && !player->IsSpectator() && m_teamnumber != player->GetTeam() )
{
vDist = spot->origin - player->origin;
if( bestplayerdistance > vDist.length() )
bestplayerdistance = vDist.length();
}
}
return bestplayerdistance;
}
PlayerStart *DM_Team::FarthestSpawnPoint( void )
{
int i;
int iNumPoints;
float bestdistance;
float bestplayerdistance;
PlayerStart *pSpot;
PlayerStart *pBestSpot;
PlayerStart *pSpot2;
PlayerStart *pSpot3;
bestplayerdistance = 0.0f;
pBestSpot = NULL;
pSpot2 = NULL;
pSpot3 = NULL;
iNumPoints = m_spawnpoints.NumObjects();
for( i = 1; i <= iNumPoints; i++ )
{
pSpot = m_spawnpoints.ObjectAt( i );
bestdistance = PlayersRangeFromSpot( m_spawnpoints.ObjectAt( i ) );
if( bestdistance > bestplayerdistance )
{
bestplayerdistance = bestdistance;
pSpot3 = pSpot2;
pSpot2 = pBestSpot;
pBestSpot = pSpot;
}
}
if( pSpot3 && G_Random() < 0.2f )
{
return pSpot3;
}
else if( pSpot2 && G_Random() < 0.3f )
{
return pSpot2;
}
else
{
return pBestSpot;
}
}
PlayerStart *DM_Team::GetRandomSpawnpoint( void )
{
PlayerStart *spot = NULL;
int numPoints = m_spawnpoints.NumObjects();
if( numPoints )
{
spot = FarthestSpawnPoint();
if( !spot )
{
spot = m_spawnpoints.ObjectAt( ( int )( G_Random( numPoints ) + 1.0f ) );
}
}
else
{
warning( "DM_Team::GetRandomSpawnpoint", "No spawnpoints found\n" );
spot = NULL;
}
return spot;
}
PlayerStart *DM_Team::GetRandomSpawnpointWithMetric( Player *player, float( *MetricFunction ) ( const float *origin, DM_Team *dmTeam, const Player *player ) )
{
static float offset[ 4 ][ 3 ];
spawnsort_t points[ 1024 ];
PlayerStart *spot = NULL;
int numSpots = 0;
int iPoint = 0;
for( int i = 1; i <= m_spawnpoints.NumObjects(); i++ )
{
spot = m_spawnpoints.ObjectAt( i );
if( spot->m_bForbidSpawns || player->GetLastSpawnpoint() == spot ) {
continue;
}
if( !SpotWouldTelefrag( spot->origin ) )
{
points[ numSpots ].spawnpoint = spot;
points[ numSpots ].fMetric = MetricFunction( spot->origin, this, player );
numSpots++;
if( numSpots >= ( sizeof( points ) / sizeof( points[ 0 ] ) ) ) {
break;
}
}
}
spot = GetRandomSpawnpointFromList( points, numSpots );
if( spot ) {
return spot;
}
numSpots = 0;
for( int i = 1; i <= m_spawnpoints.NumObjects(); i++ )
{
spot = m_spawnpoints.ObjectAt( i );
if( spot->m_bForbidSpawns ) {
continue;
}
for( int j = 0; j < sizeof( offset ) / sizeof( offset[ 0 ] ); j++ )
{
Vector vNewSpawn = spot->origin + offset[ j ];
if( G_SightTrace(
spot->origin,
player->mins,
player->maxs,
vNewSpawn,
( Entity * )NULL,
( Entity * )NULL,
MASK_PLAYERSOLID,
qfalse,
"DM_Team::GetRandomSpawnpointWithMetric" ) == 1 )
{
Vector vEnd = vNewSpawn - Vector( 0, 0, 64 );
trace_t trace = G_Trace(
vNewSpawn,
player->mins,
player->maxs,
vEnd,
player,
MASK_PLAYERSOLID,
qfalse,
"TempSpawnPoint" );
if( !trace.allsolid && !trace.startsolid &&
trace.fraction != 1.0f && trace.fraction != 1.0f &&
trace.plane.dist >= 0.8f )
{
points[ numSpots ].spawnpoint = new PlayerStart;
points[ numSpots ].spawnpoint->setOrigin( trace.endpos );
points[ numSpots ].spawnpoint->setAngles( spot->angles );
points[ numSpots ].fMetric = MetricFunction( vNewSpawn, this, player );
if( numSpots >= ( sizeof( points ) / sizeof( points[ 0 ] ) ) ) {
break;
}
}
}
}
}
spot = GetRandomSpawnpointFromList( points, numSpots );
for( int i = 0; i < numSpots; i++ )
{
// delete all created spawnpoint
if( points[ numSpots ].spawnpoint != spot ) {
delete points[ numSpots ].spawnpoint;
}
}
if( spot ) {
return spot;
}
numSpots = 0;
for( int i = 1; i <= m_spawnpoints.NumObjects(); i++ )
{
spot = m_spawnpoints.ObjectAt( i );
if( !spot->m_bForbidSpawns && player->GetLastSpawnpoint() != spot ) {
continue;
}
if( !SpotWouldTelefrag( spot->origin ) )
{
points[ numSpots ].spawnpoint = spot;
points[ numSpots ].fMetric = MetricFunction( spot->origin, this, player );
if( numSpots >= ( sizeof( points ) / sizeof( points[ 0 ] ) ) ) {
break;
}
}
}
return GetRandomSpawnpointFromList( points, numSpots );
}
void DM_Team::InitSpawnPoints
(
void
)
{
int i;
if( teamType == TEAM_SPECTATOR )
{
for( i = level.m_SimpleArchivedEntities.NumObjects(); i > 0; i-- )
{
PlayerStart *spawnpoint = ( PlayerStart * )level.m_SimpleArchivedEntities.ObjectAt( i );
const char *classname = spawnpoint->getClassID();
if( !Q_stricmp( classname, "info_player_deathmatch" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
else if( !Q_stricmp( classname, "info_player_intermission" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
}
return;
}
if( g_gametype->integer <= GT_FFA )
{
for( i = level.m_SimpleArchivedEntities.NumObjects(); i > 0; i-- )
{
PlayerStart *spawnpoint = ( PlayerStart * )level.m_SimpleArchivedEntities.ObjectAt( i );
const char *classname = spawnpoint->getClassID();
if( !Q_stricmp( classname, "info_player_deathmatch" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
}
if( m_spawnpoints.NumObjects() <= 1 )
{
// Try with team spawnpoints
for( i = level.m_SimpleArchivedEntities.NumObjects(); i > 0; i-- )
{
PlayerStart *spawnpoint = ( PlayerStart * )level.m_SimpleArchivedEntities.ObjectAt( i );
const char *classname = spawnpoint->getClassID();
if( !Q_stricmp( classname, "info_player_allied" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
else if( !Q_stricmp( classname, "info_player_axis" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
}
}
}
else if( g_gametype->integer >= GT_TEAM )
{
if( teamType >= TEAM_ALLIES )
{
for( i = level.m_SimpleArchivedEntities.NumObjects(); i > 0; i-- )
{
PlayerStart *spawnpoint = ( PlayerStart * )level.m_SimpleArchivedEntities.ObjectAt( i );
const char *classname = spawnpoint->getClassID();
if( teamType == TEAM_ALLIES && !Q_stricmp( classname, "info_player_allied" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
else if( teamType == TEAM_AXIS && !Q_stricmp( classname, "info_player_axis" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
}
}
if( !m_spawnpoints.NumObjects() )
{
// Try with info_player_deathmatch
for( int i = level.m_SimpleArchivedEntities.NumObjects(); i > 0; i-- )
{
PlayerStart *spawnpoint = ( PlayerStart * )level.m_SimpleArchivedEntities.ObjectAt( i );
const char *classname = spawnpoint->getClassID();
if( !Q_stricmp( classname, "info_player_deathmatch" ) )
{
m_spawnpoints.AddObject( spawnpoint );
}
}
}
}
}
DM_Team::DM_Team()
{
m_countdown = 0;
m_teamwins = 0;
m_wins_in_a_row = 0;
m_teamnumber = -1;
m_iKills = 0;
m_iDeaths = 0;
m_bHasSpawnedPlayers = false;
}
DM_Team::~DM_Team()
{
}
CLASS_DECLARATION( Listener, DM_Team, NULL )
{
{ NULL, NULL }
};