mirror of
https://github.com/UberGames/RPG-X2.git
synced 2024-11-28 15:13:11 +00:00
471 lines
13 KiB
C
471 lines
13 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
//
|
|
// g_arenas.c
|
|
//
|
|
|
|
#include "g_local.h"
|
|
|
|
#ifndef min
|
|
#define min(a, b) (a) < (b) ? a : b
|
|
#endif
|
|
|
|
|
|
gentity_t *podium1;
|
|
gentity_t *podium2;
|
|
gentity_t *podium3;
|
|
|
|
|
|
/*
|
|
==================
|
|
UpdateTournamentInfo
|
|
==================
|
|
*/
|
|
|
|
void UpdateTournamentInfo( void ) {
|
|
int i = 0, j = 0, k = 0;
|
|
gentity_t *player = NULL, *ent = NULL;
|
|
int playerClientNum;
|
|
int n;
|
|
char msg[AWARDS_MSG_LENGTH], msg2[AWARDS_MSG_LENGTH];
|
|
int playerRank=level.numNonSpectatorClients-1, highestTiedRank = 0;
|
|
gentity_t *MVP = NULL;
|
|
int mvpNum = -1, mvpPoints = 0, winningCaptures = 0, winningPoints = 0;
|
|
int winningTeam=0;
|
|
int loseCaptures = 0, losePoints = 0;
|
|
char *mvpName = "";
|
|
gclient_t *cl = NULL;
|
|
gclient_t *cl2= NULL;
|
|
int secondPlaceTied=0;
|
|
|
|
memset(msg, 0, AWARDS_MSG_LENGTH);
|
|
memset(msg2, 0, AWARDS_MSG_LENGTH);
|
|
|
|
player = NULL;
|
|
|
|
// Was there a tie for second place on the podium?
|
|
cl = &level.clients[level.sortedClients[1]];
|
|
cl2= &level.clients[level.sortedClients[2]];
|
|
if (cl->ps.persistant[PERS_SCORE] == cl2->ps.persistant[PERS_SCORE])
|
|
secondPlaceTied=1;
|
|
|
|
winningTeam = level.clients[0].ps.persistant[PERS_RANK]+1;
|
|
if ( winningTeam != TEAM_BLUE && winningTeam != TEAM_RED )
|
|
{//tie or not a team game
|
|
winningTeam = 0;
|
|
}
|
|
|
|
// In team game, we want to represent the highest scored client from the WINNING team.
|
|
for (i = 0; i < level.maxclients; i++)
|
|
{
|
|
ent = &g_entities[i];
|
|
if (ent && ent->client &&
|
|
(ent->client->sess.sessionTeam != TEAM_SPECTATOR) &&
|
|
CalculateTeamMVPByRank(ent))
|
|
{
|
|
// found the winning team's MVP
|
|
mvpNum = i;
|
|
break;
|
|
}
|
|
}
|
|
if (mvpNum < 0)
|
|
{//ah, crap no MVP, pick the first player on the winning team
|
|
for (i = 0; i < level.maxclients; i++ )
|
|
{
|
|
if ( !&g_entities[i] ) continue;
|
|
if ( !(&g_entities[i])->client) continue;
|
|
if ( g_entities[i].client->ps.persistant[PERS_TEAM] == winningTeam )
|
|
{
|
|
mvpNum = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mvpNum >= 0)
|
|
{//still no MVP, so skip it
|
|
MVP = &g_entities[mvpNum];
|
|
mvpName = MVP->client->pers.netname;
|
|
mvpPoints = MVP->client->ps.persistant[PERS_SCORE];
|
|
winningTeam = MVP->client->ps.persistant[PERS_TEAM];
|
|
}
|
|
|
|
if ( winningTeam )
|
|
{//one of the teams won
|
|
winningCaptures = level.teamScores[winningTeam];
|
|
if (winningTeam == TEAM_RED)
|
|
loseCaptures = level.teamScores[TEAM_BLUE];
|
|
else
|
|
loseCaptures = level.teamScores[TEAM_RED];
|
|
|
|
if ( g_pModElimination.integer != 0 )
|
|
{//in elimination, don't add each member's points to the total
|
|
winningPoints = winningCaptures;
|
|
losePoints = loseCaptures;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < level.maxclients; i++ )
|
|
{
|
|
if ( !&g_entities[i] ) continue;
|
|
if ( !(&g_entities[i])->client ) continue;
|
|
if ( g_entities[i].client->ps.persistant[PERS_TEAM] == winningTeam )
|
|
winningPoints += g_entities[i].client->ps.persistant[PERS_SCORE];
|
|
else
|
|
losePoints += g_entities[i].client->ps.persistant[PERS_SCORE];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < level.maxclients; i++ )
|
|
{
|
|
player = &g_entities[i];
|
|
if ( !player->inuse || (player->r.svFlags & SVF_BOT))
|
|
{
|
|
continue;
|
|
}
|
|
playerClientNum = i;
|
|
|
|
CalculateRanks( qfalse );
|
|
// put info for the top three players into the msg
|
|
Com_sprintf(msg, AWARDS_MSG_LENGTH, "awards %d", level.numNonSpectatorClients);
|
|
for( j = 0; j < level.numNonSpectatorClients; j++ )
|
|
{
|
|
if (j > 2)
|
|
{
|
|
break;
|
|
}
|
|
n = level.sortedClients[j];
|
|
strcpy(msg2, msg);
|
|
Com_sprintf(msg, AWARDS_MSG_LENGTH, "%s %d", msg2, n);
|
|
}
|
|
|
|
// put this guy's awards into the msg
|
|
if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR )
|
|
{
|
|
strcpy(msg2, msg);
|
|
Com_sprintf( msg, sizeof(msg), "%s 0", msg2);
|
|
}
|
|
else
|
|
{
|
|
CalculateAwards(player, msg);
|
|
}
|
|
|
|
// now supply...
|
|
//
|
|
// 1) winning team's MVP's name
|
|
// 2) winning team's MVP's score
|
|
// 3) winning team's total captures
|
|
// 4) winning team's total points
|
|
// 5) this player's rank
|
|
// 6) the highest rank for which this player tied
|
|
// 7) losing team's total captures
|
|
// 8) losing team's total points
|
|
// 9) if second place was tied
|
|
// 10) intermission point
|
|
// 11) intermission angles
|
|
//
|
|
for (k = 0; k < level.numNonSpectatorClients; k++)
|
|
{
|
|
if (level.sortedClients[k] == playerClientNum)
|
|
{
|
|
playerRank = k;
|
|
break;
|
|
}
|
|
}
|
|
highestTiedRank = 0;
|
|
for (k = playerRank-1; k >= 0; k--)
|
|
{
|
|
cl = &level.clients[level.sortedClients[k]];
|
|
if (cl->ps.persistant[PERS_SCORE] > level.clients[level.sortedClients[playerRank]].ps.persistant[PERS_SCORE])
|
|
{
|
|
break;
|
|
}
|
|
highestTiedRank = k+1;
|
|
}
|
|
strcpy(msg2, msg);
|
|
Com_sprintf(msg, AWARDS_MSG_LENGTH, "%s \"%s\" %d %d %d %d %d %d %d %d %f %f %f %f %f %f",
|
|
msg2, mvpName, mvpPoints, winningCaptures, winningPoints, playerRank, highestTiedRank,
|
|
loseCaptures, losePoints, secondPlaceTied, level.intermission_origin[0], level.intermission_origin[1],
|
|
level.intermission_origin[2], level.intermission_angle[0], level.intermission_angle[1],
|
|
level.intermission_angle[2]);
|
|
|
|
trap_SendServerCommand(player-g_entities, msg);
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SINGLE_PLAYER)
|
|
{
|
|
Com_sprintf( msg, sizeof(msg), "postgame %i", playerRank);
|
|
trap_SendConsoleCommand( EXEC_APPEND, msg);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) {
|
|
gentity_t *body;
|
|
vec3_t vec;
|
|
vec3_t f, r, u;
|
|
entityState_t *eState;
|
|
entityShared_t *eShared;
|
|
|
|
body = G_Spawn();
|
|
if ( !body ) {
|
|
G_Printf( S_COLOR_RED "ERROR: out of gentities\n" );
|
|
return NULL;
|
|
}
|
|
|
|
eState = &body->s;
|
|
eShared = &body->r;
|
|
|
|
body->classname = ent->client->pers.netname;
|
|
body->client = ent->client;
|
|
eState = &ent->s;
|
|
eState->eType = ET_PLAYER; // could be ET_INVISIBLE
|
|
eState->eFlags = 0; // clear EF_TALK, etc
|
|
eState->powerups = 0; // clear powerups
|
|
eState->loopSound = 0; // clear lava burning
|
|
eState->number = body - g_entities;
|
|
body->timestamp = level.time;
|
|
body->physicsObject = qtrue;
|
|
body->physicsBounce = 0; // don't bounce
|
|
eState->event = 0;
|
|
eState->pos.trType = TR_STATIONARY;
|
|
eState->groundEntityNum = ENTITYNUM_WORLD;
|
|
eState->legsAnim = BOTH_STAND1; //TORSO_STAND
|
|
eState->torsoAnim = BOTH_STAND1;
|
|
|
|
// fix up some weapon holding / shooting issues
|
|
if (eState->weapon==WP_PHASER || eState->weapon==WP_DERMAL_REGEN || eState->weapon == WP_NONE )
|
|
eState->weapon = WP_COMPRESSION_RIFLE;
|
|
|
|
eState->event = 0;
|
|
eShared->svFlags = ent->r.svFlags;
|
|
VectorCopy (ent->r.mins, eShared->mins);
|
|
VectorCopy (ent->r.maxs, eShared->maxs);
|
|
VectorCopy (ent->r.absmin, eShared->absmin);
|
|
VectorCopy (ent->r.absmax, eShared->absmax);
|
|
body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
|
|
eShared->contents = CONTENTS_BODY;
|
|
eShared->ownerNum = ent->r.ownerNum;
|
|
body->takedamage = qfalse;
|
|
VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec );
|
|
vectoangles( vec, eState->apos.trBase );
|
|
eState->apos.trBase[PITCH] = 0;
|
|
eState->apos.trBase[ROLL] = 0;
|
|
|
|
AngleVectors( eState->apos.trBase, f, r, u );
|
|
VectorMA( pad->r.currentOrigin, offset[0], f, vec );
|
|
VectorMA( vec, offset[1], r, vec );
|
|
VectorMA( vec, offset[2], u, vec );
|
|
|
|
G_SetOrigin( body, vec );
|
|
|
|
trap_LinkEntity (body);
|
|
|
|
body->count = place;
|
|
|
|
return body;
|
|
}
|
|
|
|
|
|
static void CelebrateStop( gentity_t *player ) {
|
|
int anim;
|
|
|
|
anim = BOTH_STAND1; //TORSO_STAND
|
|
player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
|
|
}
|
|
|
|
|
|
#define TIMER_GESTURE (34*66+50)
|
|
extern void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps );
|
|
static void CelebrateStart( gentity_t *player )
|
|
{
|
|
/*player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE;
|
|
player->nextthink = level.time + TIMER_GESTURE;
|
|
player->think = CelebrateStop;*/
|
|
|
|
// We don't want the taunt sound effect because it interfears with the computer voice giving awards
|
|
// FIXME: just get timing right?
|
|
//FIXME: why does this get lost now?
|
|
BG_AddPredictableEventToPlayerstate( EV_TAUNT, 0, &player->client->ps );
|
|
}
|
|
|
|
|
|
static vec3_t offsetFirst = {0, 0, 64};
|
|
static vec3_t offsetSecond = {-10, 60, 44};
|
|
static vec3_t offsetThird = {-19, -60, 35};
|
|
|
|
static void PodiumPlacementThink( gentity_t *podium ) {
|
|
vec3_t vec;
|
|
vec3_t origin;
|
|
vec3_t f, r, u;
|
|
|
|
podium->nextthink = level.time + 100;
|
|
|
|
AngleVectors( level.intermission_angle, vec, NULL, NULL );
|
|
VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin );
|
|
origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" );
|
|
G_SetOrigin( podium, origin );
|
|
|
|
if( podium1 ) {
|
|
VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec );
|
|
vectoangles( vec, podium1->s.apos.trBase );
|
|
podium1->s.apos.trBase[PITCH] = 0;
|
|
podium1->s.apos.trBase[ROLL] = 0;
|
|
|
|
AngleVectors( podium1->s.apos.trBase, f, r, u );
|
|
VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec );
|
|
VectorMA( vec, offsetFirst[1], r, vec );
|
|
VectorMA( vec, offsetFirst[2], u, vec );
|
|
|
|
G_SetOrigin( podium1, vec );
|
|
}
|
|
|
|
if( podium2 ) {
|
|
VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec );
|
|
vectoangles( vec, podium2->s.apos.trBase );
|
|
podium2->s.apos.trBase[PITCH] = 0;
|
|
podium2->s.apos.trBase[ROLL] = 0;
|
|
|
|
AngleVectors( podium2->s.apos.trBase, f, r, u );
|
|
VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec );
|
|
VectorMA( vec, offsetSecond[1], r, vec );
|
|
VectorMA( vec, offsetSecond[2], u, vec );
|
|
|
|
G_SetOrigin( podium2, vec );
|
|
}
|
|
|
|
if( podium3 ) {
|
|
VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec );
|
|
vectoangles( vec, podium3->s.apos.trBase );
|
|
podium3->s.apos.trBase[PITCH] = 0;
|
|
podium3->s.apos.trBase[ROLL] = 0;
|
|
|
|
AngleVectors( podium3->s.apos.trBase, f, r, u );
|
|
VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec );
|
|
VectorMA( vec, offsetThird[1], r, vec );
|
|
VectorMA( vec, offsetThird[2], u, vec );
|
|
|
|
G_SetOrigin( podium3, vec );
|
|
}
|
|
}
|
|
|
|
|
|
static gentity_t *SpawnPodium( void ) {
|
|
gentity_t *podium;
|
|
vec3_t vec;
|
|
vec3_t origin;
|
|
|
|
podium = G_Spawn();
|
|
if ( !podium ) {
|
|
return NULL;
|
|
}
|
|
|
|
podium->classname = "podium";
|
|
podium->s.eType = ET_GENERAL;
|
|
podium->s.number = podium - g_entities;
|
|
podium->clipmask = CONTENTS_SOLID;
|
|
podium->r.contents = CONTENTS_SOLID;
|
|
if (g_gametype.integer > GT_SINGLE_PLAYER)
|
|
podium->s.modelindex = G_ModelIndex( TEAM_PODIUM_MODEL );
|
|
else
|
|
podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL );
|
|
|
|
AngleVectors( level.intermission_angle, vec, NULL, NULL );
|
|
VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin );
|
|
origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" );
|
|
G_SetOrigin( podium, origin );
|
|
|
|
VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec );
|
|
podium->s.apos.trBase[YAW] = vectoyaw( vec );
|
|
trap_LinkEntity (podium);
|
|
|
|
podium->think = PodiumPlacementThink;
|
|
podium->nextthink = level.time + 100;
|
|
return podium;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SpawnModelsOnVictoryPads
|
|
==================
|
|
*/
|
|
void SpawnModelsOnVictoryPads( void ) {
|
|
gentity_t *player;
|
|
gentity_t *podium;
|
|
int i = 0;
|
|
gentity_t *ent = &g_entities[0];
|
|
|
|
podium1 = NULL;
|
|
podium2 = NULL;
|
|
podium3 = NULL;
|
|
|
|
podium = SpawnPodium();
|
|
|
|
|
|
// SPAWN PLAYER ON TOP MOST PODIUM
|
|
if (g_gametype.integer > GT_SINGLE_PLAYER)
|
|
{
|
|
// In team game, we want to represent the highest scored client from the WINNING team.
|
|
for (i = 0; i < level.maxclients; i++)
|
|
{
|
|
ent = &g_entities[i];
|
|
if (ent->client && CalculateTeamMVPByRank(ent))
|
|
{
|
|
// found the winning team's MVP
|
|
break;
|
|
}
|
|
}
|
|
player = SpawnModelOnVictoryPad( podium, offsetFirst, ent,
|
|
level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG );
|
|
}
|
|
else
|
|
{
|
|
player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]],
|
|
level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG );
|
|
}
|
|
if ( player ) {
|
|
player->nextthink = level.time + 2000;
|
|
player->think = CelebrateStart;
|
|
podium1 = player;
|
|
}
|
|
|
|
// For non team game types, we want to spawn 3 characters on the victory pad
|
|
// For team games (GT_TEAM, GT_CTF) we want to have only a single player on the pad
|
|
if (( g_gametype.integer == GT_FFA ) || (g_gametype.integer == GT_TOURNAMENT) || (g_gametype.integer == GT_SINGLE_PLAYER))
|
|
{
|
|
if ( level.numNonSpectatorClients > 1 ) {
|
|
player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]],
|
|
level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG );
|
|
if ( player ) {
|
|
podium2 = player;
|
|
}
|
|
}
|
|
|
|
if ( level.numNonSpectatorClients > 2 ) {
|
|
player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]],
|
|
level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG );
|
|
if ( player ) {
|
|
podium3 = player;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
Svcmd_AbortPodium_f
|
|
===============
|
|
*/
|
|
void Svcmd_AbortPodium_f( void ) {
|
|
if( g_gametype.integer != GT_SINGLE_PLAYER ) {
|
|
return;
|
|
}
|
|
|
|
if( podium1 ) {
|
|
podium1->nextthink = level.time;
|
|
podium1->think = CelebrateStop;
|
|
}
|
|
}
|