mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-12-01 16:41:11 +00:00
3940 lines
104 KiB
C
3940 lines
104 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
#include "g_local.h"
|
|
#include "../ghoul2/G2.h"
|
|
#include "bg_saga.h"
|
|
|
|
// g_client.c -- client functions that don't happen every frame
|
|
|
|
static vec3_t playerMins = {-15, -15, DEFAULT_MINS_2};
|
|
static vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2};
|
|
|
|
extern int g_siegeRespawnCheck;
|
|
|
|
void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin );
|
|
void WP_SaberRemoveG2Model( gentity_t *saberent );
|
|
extern qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel );
|
|
extern qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel );
|
|
|
|
forcedata_t Client_Force[MAX_CLIENTS];
|
|
|
|
/*QUAKED info_player_duel (1 0 1) (-16 -16 -24) (16 16 32) initial
|
|
potential spawning position for duelists in duel.
|
|
Targets will be fired when someone spawns in on them.
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_duel( gentity_t *ent )
|
|
{
|
|
int i;
|
|
|
|
G_SpawnInt( "nobots", "0", &i);
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_BOTS;
|
|
}
|
|
G_SpawnInt( "nohumans", "0", &i );
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_HUMANS;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_player_duel1 (1 0 1) (-16 -16 -24) (16 16 32) initial
|
|
potential spawning position for lone duelists in powerduel.
|
|
Targets will be fired when someone spawns in on them.
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_duel1( gentity_t *ent )
|
|
{
|
|
int i;
|
|
|
|
G_SpawnInt( "nobots", "0", &i);
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_BOTS;
|
|
}
|
|
G_SpawnInt( "nohumans", "0", &i );
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_HUMANS;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_player_duel2 (1 0 1) (-16 -16 -24) (16 16 32) initial
|
|
potential spawning position for paired duelists in powerduel.
|
|
Targets will be fired when someone spawns in on them.
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_duel2( gentity_t *ent )
|
|
{
|
|
int i;
|
|
|
|
G_SpawnInt( "nobots", "0", &i);
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_BOTS;
|
|
}
|
|
G_SpawnInt( "nohumans", "0", &i );
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_HUMANS;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
|
|
potential spawning position for deathmatch games.
|
|
The first time a player enters the game, they will be at an 'initial' spot.
|
|
Targets will be fired when someone spawns in on them.
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_deathmatch( gentity_t *ent ) {
|
|
int i;
|
|
|
|
G_SpawnInt( "nobots", "0", &i);
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_BOTS;
|
|
}
|
|
G_SpawnInt( "nohumans", "0", &i );
|
|
if ( i ) {
|
|
ent->flags |= FL_NO_HUMANS;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
|
|
Targets will be fired when someone spawns in on them.
|
|
equivelant to info_player_deathmatch
|
|
*/
|
|
void SP_info_player_start(gentity_t *ent) {
|
|
ent->classname = "info_player_deathmatch";
|
|
SP_info_player_deathmatch( ent );
|
|
}
|
|
|
|
/*QUAKED info_player_start_red (1 0 0) (-16 -16 -24) (16 16 32) INITIAL
|
|
For Red Team DM starts
|
|
|
|
Targets will be fired when someone spawns in on them.
|
|
equivalent to info_player_deathmatch
|
|
|
|
INITIAL - The first time a player enters the game, they will be at an 'initial' spot.
|
|
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_start_red(gentity_t *ent) {
|
|
SP_info_player_deathmatch( ent );
|
|
}
|
|
|
|
/*QUAKED info_player_start_blue (1 0 0) (-16 -16 -24) (16 16 32) INITIAL
|
|
For Blue Team DM starts
|
|
|
|
Targets will be fired when someone spawns in on them.
|
|
equivalent to info_player_deathmatch
|
|
|
|
INITIAL - The first time a player enters the game, they will be at an 'initial' spot.
|
|
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_start_blue(gentity_t *ent) {
|
|
SP_info_player_deathmatch( ent );
|
|
}
|
|
|
|
void SiegePointUse( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
//Toggle the point on/off
|
|
if (self->genericValue1)
|
|
{
|
|
self->genericValue1 = 0;
|
|
}
|
|
else
|
|
{
|
|
self->genericValue1 = 1;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_player_siegeteam1 (1 0 0) (-16 -16 -24) (16 16 32)
|
|
siege start point - team1
|
|
name and behavior of team1 depends on what is defined in the
|
|
.siege file for this level
|
|
|
|
startoff - if non-0 spawn point will be disabled until used
|
|
idealclass - if specified, this spawn point will be considered
|
|
"ideal" for players of this class name. Corresponds to the name
|
|
entry in the .scl (siege class) file.
|
|
Targets will be fired when someone spawns in on them.
|
|
*/
|
|
void SP_info_player_siegeteam1(gentity_t *ent) {
|
|
int soff = 0;
|
|
|
|
if (g_gametype.integer != GT_SIEGE)
|
|
{ //turn into a DM spawn if not in siege game mode
|
|
ent->classname = "info_player_deathmatch";
|
|
SP_info_player_deathmatch( ent );
|
|
|
|
return;
|
|
}
|
|
|
|
G_SpawnInt("startoff", "0", &soff);
|
|
|
|
if (soff)
|
|
{ //start disabled
|
|
ent->genericValue1 = 0;
|
|
}
|
|
else
|
|
{
|
|
ent->genericValue1 = 1;
|
|
}
|
|
|
|
ent->use = SiegePointUse;
|
|
}
|
|
|
|
/*QUAKED info_player_siegeteam2 (0 0 1) (-16 -16 -24) (16 16 32)
|
|
siege start point - team2
|
|
name and behavior of team2 depends on what is defined in the
|
|
.siege file for this level
|
|
|
|
startoff - if non-0 spawn point will be disabled until used
|
|
idealclass - if specified, this spawn point will be considered
|
|
"ideal" for players of this class name. Corresponds to the name
|
|
entry in the .scl (siege class) file.
|
|
Targets will be fired when someone spawns in on them.
|
|
*/
|
|
void SP_info_player_siegeteam2(gentity_t *ent) {
|
|
int soff = 0;
|
|
|
|
if (g_gametype.integer != GT_SIEGE)
|
|
{ //turn into a DM spawn if not in siege game mode
|
|
ent->classname = "info_player_deathmatch";
|
|
SP_info_player_deathmatch( ent );
|
|
|
|
return;
|
|
}
|
|
|
|
G_SpawnInt("startoff", "0", &soff);
|
|
|
|
if (soff)
|
|
{ //start disabled
|
|
ent->genericValue1 = 0;
|
|
}
|
|
else
|
|
{
|
|
ent->genericValue1 = 1;
|
|
}
|
|
|
|
ent->use = SiegePointUse;
|
|
}
|
|
|
|
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) RED BLUE
|
|
The intermission will be viewed from this point. Target an info_notnull for the view direction.
|
|
RED - In a Siege game, the intermission will happen here if the Red (attacking) team wins
|
|
BLUE - In a Siege game, the intermission will happen here if the Blue (defending) team wins
|
|
*/
|
|
void SP_info_player_intermission( gentity_t *ent ) {
|
|
|
|
}
|
|
|
|
/*QUAKED info_player_intermission_red (1 0 1) (-16 -16 -24) (16 16 32)
|
|
The intermission will be viewed from this point. Target an info_notnull for the view direction.
|
|
|
|
In a Siege game, the intermission will happen here if the Red (attacking) team wins
|
|
target - ent to look at
|
|
target2 - ents to use when this intermission point is chosen
|
|
*/
|
|
void SP_info_player_intermission_red( gentity_t *ent ) {
|
|
|
|
}
|
|
|
|
/*QUAKED info_player_intermission_blue (1 0 1) (-16 -16 -24) (16 16 32)
|
|
The intermission will be viewed from this point. Target an info_notnull for the view direction.
|
|
|
|
In a Siege game, the intermission will happen here if the Blue (defending) team wins
|
|
target - ent to look at
|
|
target2 - ents to use when this intermission point is chosen
|
|
*/
|
|
void SP_info_player_intermission_blue( gentity_t *ent ) {
|
|
|
|
}
|
|
|
|
#define JMSABER_RESPAWN_TIME 20000 //in case it gets stuck somewhere no one can reach
|
|
|
|
void ThrowSaberToAttacker(gentity_t *self, gentity_t *attacker)
|
|
{
|
|
gentity_t *ent = &g_entities[self->client->ps.saberIndex];
|
|
vec3_t a;
|
|
int altVelocity = 0;
|
|
|
|
if (!ent || ent->enemy != self)
|
|
{ //something has gone very wrong (this should never happen)
|
|
//but in case it does.. find the saber manually
|
|
#ifdef _DEBUG
|
|
Com_Printf("Lost the saber! Attempting to use global pointer..\n");
|
|
#endif
|
|
ent = gJMSaberEnt;
|
|
|
|
if (!ent)
|
|
{
|
|
#ifdef _DEBUG
|
|
Com_Printf("The global pointer was NULL. This is a bad thing.\n");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
Com_Printf("Got it (%i). Setting enemy to client %i.\n", ent->s.number, self->s.number);
|
|
#endif
|
|
|
|
ent->enemy = self;
|
|
self->client->ps.saberIndex = ent->s.number;
|
|
}
|
|
|
|
trap_SetConfigstring ( CS_CLIENT_JEDIMASTER, "-1" );
|
|
|
|
if (attacker && attacker->client && self->client->ps.saberInFlight)
|
|
{ //someone killed us and we had the saber thrown, so actually move this saber to the saber location
|
|
//if we killed ourselves with saber thrown, however, same suicide rules of respawning at spawn spot still
|
|
//apply.
|
|
gentity_t *flyingsaber = &g_entities[self->client->ps.saberEntityNum];
|
|
|
|
if (flyingsaber && flyingsaber->inuse)
|
|
{
|
|
VectorCopy(flyingsaber->s.pos.trBase, ent->s.pos.trBase);
|
|
VectorCopy(flyingsaber->s.pos.trDelta, ent->s.pos.trDelta);
|
|
VectorCopy(flyingsaber->s.apos.trBase, ent->s.apos.trBase);
|
|
VectorCopy(flyingsaber->s.apos.trDelta, ent->s.apos.trDelta);
|
|
|
|
VectorCopy(flyingsaber->r.currentOrigin, ent->r.currentOrigin);
|
|
VectorCopy(flyingsaber->r.currentAngles, ent->r.currentAngles);
|
|
altVelocity = 1;
|
|
}
|
|
}
|
|
|
|
self->client->ps.saberInFlight = qtrue; //say he threw it anyway in order to properly remove from dead body
|
|
|
|
WP_SaberAddG2Model( ent, self->client->saber[0].model, self->client->saber[0].skin );
|
|
|
|
ent->s.eFlags &= ~(EF_NODRAW);
|
|
ent->s.modelGhoul2 = 1;
|
|
ent->s.eType = ET_MISSILE;
|
|
ent->enemy = NULL;
|
|
|
|
if (!attacker || !attacker->client)
|
|
{
|
|
VectorCopy(ent->s.origin2, ent->s.pos.trBase);
|
|
VectorCopy(ent->s.origin2, ent->s.origin);
|
|
VectorCopy(ent->s.origin2, ent->r.currentOrigin);
|
|
ent->pos2[0] = 0;
|
|
trap_LinkEntity(ent);
|
|
return;
|
|
}
|
|
|
|
if (!altVelocity)
|
|
{
|
|
VectorCopy(self->s.pos.trBase, ent->s.pos.trBase);
|
|
VectorCopy(self->s.pos.trBase, ent->s.origin);
|
|
VectorCopy(self->s.pos.trBase, ent->r.currentOrigin);
|
|
|
|
VectorSubtract(attacker->client->ps.origin, ent->s.pos.trBase, a);
|
|
|
|
VectorNormalize(a);
|
|
|
|
ent->s.pos.trDelta[0] = a[0]*256;
|
|
ent->s.pos.trDelta[1] = a[1]*256;
|
|
ent->s.pos.trDelta[2] = 256;
|
|
}
|
|
|
|
trap_LinkEntity(ent);
|
|
}
|
|
|
|
void JMSaberThink(gentity_t *ent)
|
|
{
|
|
gJMSaberEnt = ent;
|
|
|
|
if (ent->enemy)
|
|
{
|
|
if (!ent->enemy->client || !ent->enemy->inuse)
|
|
{ //disconnected?
|
|
VectorCopy(ent->enemy->s.pos.trBase, ent->s.pos.trBase);
|
|
VectorCopy(ent->enemy->s.pos.trBase, ent->s.origin);
|
|
VectorCopy(ent->enemy->s.pos.trBase, ent->r.currentOrigin);
|
|
ent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm");
|
|
ent->s.eFlags &= ~(EF_NODRAW);
|
|
ent->s.modelGhoul2 = 1;
|
|
ent->s.eType = ET_MISSILE;
|
|
ent->enemy = NULL;
|
|
|
|
ent->pos2[0] = 1;
|
|
ent->pos2[1] = 0; //respawn next think
|
|
trap_LinkEntity(ent);
|
|
}
|
|
else
|
|
{
|
|
ent->pos2[1] = level.time + JMSABER_RESPAWN_TIME;
|
|
}
|
|
}
|
|
else if (ent->pos2[0] && ent->pos2[1] < level.time)
|
|
{
|
|
VectorCopy(ent->s.origin2, ent->s.pos.trBase);
|
|
VectorCopy(ent->s.origin2, ent->s.origin);
|
|
VectorCopy(ent->s.origin2, ent->r.currentOrigin);
|
|
ent->pos2[0] = 0;
|
|
trap_LinkEntity(ent);
|
|
}
|
|
|
|
ent->nextthink = level.time + 50;
|
|
G_RunObject(ent);
|
|
}
|
|
|
|
void JMSaberTouch(gentity_t *self, gentity_t *other, trace_t *trace)
|
|
{
|
|
int i = 0;
|
|
// gentity_t *te;
|
|
|
|
if (!other || !other->client || other->health < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->enemy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!self->s.modelindex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (other->client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (other->client->ps.isJediMaster)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->enemy = other;
|
|
other->client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER);
|
|
other->client->ps.weapon = WP_SABER;
|
|
other->s.weapon = WP_SABER;
|
|
G_AddEvent(other, EV_BECOME_JEDIMASTER, 0);
|
|
|
|
// Track the jedi master
|
|
trap_SetConfigstring ( CS_CLIENT_JEDIMASTER, va("%i", other->s.number ) );
|
|
|
|
if (g_spawnInvulnerability.integer)
|
|
{
|
|
other->client->ps.eFlags |= EF_INVULNERABLE;
|
|
other->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer;
|
|
}
|
|
|
|
trap_SendServerCommand( -1, va("cp \"%s %s\n\"", other->client->pers.netname, G_GetStringEdString("MP_SVGAME", "BECOMEJM")) );
|
|
|
|
other->client->ps.isJediMaster = qtrue;
|
|
other->client->ps.saberIndex = self->s.number;
|
|
|
|
if (other->health < 200 && other->health > 0)
|
|
{ //full health when you become the Jedi Master
|
|
other->client->ps.stats[STAT_HEALTH] = other->health = 200;
|
|
}
|
|
|
|
if (other->client->ps.fd.forcePower < 100)
|
|
{
|
|
other->client->ps.fd.forcePower = 100;
|
|
}
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
other->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
other->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3;
|
|
|
|
i++;
|
|
}
|
|
|
|
self->pos2[0] = 1;
|
|
self->pos2[1] = level.time + JMSABER_RESPAWN_TIME;
|
|
|
|
self->s.modelindex = 0;
|
|
self->s.eFlags |= EF_NODRAW;
|
|
self->s.modelGhoul2 = 0;
|
|
self->s.eType = ET_GENERAL;
|
|
|
|
/*
|
|
te = G_TempEntity( vec3_origin, EV_DESTROY_GHOUL2_INSTANCE );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = self->s.number;
|
|
*/
|
|
G_KillG2Queue(self->s.number);
|
|
|
|
return;
|
|
}
|
|
|
|
gentity_t *gJMSaberEnt = NULL;
|
|
|
|
/*QUAKED info_jedimaster_start (1 0 0) (-16 -16 -24) (16 16 32)
|
|
"jedi master" saber spawn point
|
|
*/
|
|
void SP_info_jedimaster_start(gentity_t *ent)
|
|
{
|
|
if (g_gametype.integer != GT_JEDIMASTER)
|
|
{
|
|
gJMSaberEnt = NULL;
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
ent->enemy = NULL;
|
|
|
|
ent->flags = FL_BOUNCE_HALF;
|
|
|
|
ent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm");
|
|
ent->s.modelGhoul2 = 1;
|
|
ent->s.g2radius = 20;
|
|
//ent->s.eType = ET_GENERAL;
|
|
ent->s.eType = ET_MISSILE;
|
|
ent->s.weapon = WP_SABER;
|
|
ent->s.pos.trType = TR_GRAVITY;
|
|
ent->s.pos.trTime = level.time;
|
|
VectorSet( ent->r.maxs, 3, 3, 3 );
|
|
VectorSet( ent->r.mins, -3, -3, -3 );
|
|
ent->r.contents = CONTENTS_TRIGGER;
|
|
ent->clipmask = MASK_SOLID;
|
|
|
|
ent->isSaberEntity = qtrue;
|
|
|
|
ent->bounceCount = -5;
|
|
|
|
ent->physicsObject = qtrue;
|
|
|
|
VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot
|
|
|
|
ent->touch = JMSaberTouch;
|
|
|
|
trap_LinkEntity(ent);
|
|
|
|
ent->think = JMSaberThink;
|
|
ent->nextthink = level.time + 50;
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
SelectSpawnPoint
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
SpotWouldTelefrag
|
|
|
|
================
|
|
*/
|
|
qboolean SpotWouldTelefrag( gentity_t *spot ) {
|
|
int i, num;
|
|
int touch[MAX_GENTITIES];
|
|
gentity_t *hit;
|
|
vec3_t mins, maxs;
|
|
|
|
VectorAdd( spot->s.origin, playerMins, mins );
|
|
VectorAdd( spot->s.origin, playerMaxs, maxs );
|
|
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
for (i=0 ; i<num ; i++) {
|
|
hit = &g_entities[touch[i]];
|
|
//if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
|
|
if ( hit->client) {
|
|
return qtrue;
|
|
}
|
|
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest )
|
|
{
|
|
int i, num;
|
|
int touch[MAX_GENTITIES];
|
|
gentity_t *hit;
|
|
vec3_t mins, maxs;
|
|
|
|
VectorAdd( dest, mover->r.mins, mins );
|
|
VectorAdd( dest, mover->r.maxs, maxs );
|
|
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
hit = &g_entities[touch[i]];
|
|
if ( hit == mover )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( hit->r.contents & mover->r.contents )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SelectNearestDeathmatchSpawnPoint
|
|
|
|
Find the spot that we DON'T want to use
|
|
================
|
|
*/
|
|
#define MAX_SPAWN_POINTS 128
|
|
gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) {
|
|
gentity_t *spot;
|
|
vec3_t delta;
|
|
float dist, nearestDist;
|
|
gentity_t *nearestSpot;
|
|
|
|
nearestDist = 999999;
|
|
nearestSpot = NULL;
|
|
spot = NULL;
|
|
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
|
|
VectorSubtract( spot->s.origin, from, delta );
|
|
dist = VectorLength( delta );
|
|
if ( dist < nearestDist ) {
|
|
nearestDist = dist;
|
|
nearestSpot = spot;
|
|
}
|
|
}
|
|
|
|
return nearestSpot;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
SelectRandomDeathmatchSpawnPoint
|
|
|
|
go to a random point that doesn't telefrag
|
|
================
|
|
*/
|
|
#define MAX_SPAWN_POINTS 128
|
|
gentity_t *SelectRandomDeathmatchSpawnPoint( void ) {
|
|
gentity_t *spot;
|
|
int count;
|
|
int selection;
|
|
gentity_t *spots[MAX_SPAWN_POINTS];
|
|
|
|
count = 0;
|
|
spot = NULL;
|
|
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
if ( SpotWouldTelefrag( spot ) ) {
|
|
continue;
|
|
}
|
|
spots[ count ] = spot;
|
|
count++;
|
|
}
|
|
|
|
if ( !count ) { // no spots that won't telefrag
|
|
return G_Find( NULL, FOFS(classname), "info_player_deathmatch");
|
|
}
|
|
|
|
selection = rand() % count;
|
|
return spots[ selection ];
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectRandomFurthestSpawnPoint
|
|
|
|
Chooses a player start, deathmatch start, etc
|
|
============
|
|
*/
|
|
gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, team_t team ) {
|
|
gentity_t *spot;
|
|
vec3_t delta;
|
|
float dist;
|
|
float list_dist[64];
|
|
gentity_t *list_spot[64];
|
|
int numSpots, rnd, i, j;
|
|
|
|
numSpots = 0;
|
|
spot = NULL;
|
|
|
|
//in Team DM, look for a team start spot first, if any
|
|
if ( g_gametype.integer == GT_TEAM
|
|
&& team != TEAM_FREE
|
|
&& team != TEAM_SPECTATOR )
|
|
{
|
|
char *classname = NULL;
|
|
if ( team == TEAM_RED )
|
|
{
|
|
classname = "info_player_start_red";
|
|
}
|
|
else
|
|
{
|
|
classname = "info_player_start_blue";
|
|
}
|
|
while ((spot = G_Find (spot, FOFS(classname), classname)) != NULL) {
|
|
if ( SpotWouldTelefrag( spot ) ) {
|
|
continue;
|
|
}
|
|
VectorSubtract( spot->s.origin, avoidPoint, delta );
|
|
dist = VectorLength( delta );
|
|
for (i = 0; i < numSpots; i++) {
|
|
if ( dist > list_dist[i] ) {
|
|
if ( numSpots >= 64 )
|
|
numSpots = 64-1;
|
|
for (j = numSpots; j > i; j--) {
|
|
list_dist[j] = list_dist[j-1];
|
|
list_spot[j] = list_spot[j-1];
|
|
}
|
|
list_dist[i] = dist;
|
|
list_spot[i] = spot;
|
|
numSpots++;
|
|
if (numSpots > 64)
|
|
numSpots = 64;
|
|
break;
|
|
}
|
|
}
|
|
if (i >= numSpots && numSpots < 64) {
|
|
list_dist[numSpots] = dist;
|
|
list_spot[numSpots] = spot;
|
|
numSpots++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !numSpots )
|
|
{//couldn't find any of the above
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
if ( SpotWouldTelefrag( spot ) ) {
|
|
continue;
|
|
}
|
|
VectorSubtract( spot->s.origin, avoidPoint, delta );
|
|
dist = VectorLength( delta );
|
|
for (i = 0; i < numSpots; i++) {
|
|
if ( dist > list_dist[i] ) {
|
|
if ( numSpots >= 64 )
|
|
numSpots = 64-1;
|
|
for (j = numSpots; j > i; j--) {
|
|
list_dist[j] = list_dist[j-1];
|
|
list_spot[j] = list_spot[j-1];
|
|
}
|
|
list_dist[i] = dist;
|
|
list_spot[i] = spot;
|
|
numSpots++;
|
|
if (numSpots > 64)
|
|
numSpots = 64;
|
|
break;
|
|
}
|
|
}
|
|
if (i >= numSpots && numSpots < 64) {
|
|
list_dist[numSpots] = dist;
|
|
list_spot[numSpots] = spot;
|
|
numSpots++;
|
|
}
|
|
}
|
|
if (!numSpots) {
|
|
spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
|
|
if (!spot)
|
|
G_Error( "Couldn't find a spawn point" );
|
|
VectorCopy (spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (spot->s.angles, angles);
|
|
return spot;
|
|
}
|
|
}
|
|
|
|
// select a random spot from the spawn points furthest away
|
|
rnd = random() * (numSpots / 2);
|
|
|
|
VectorCopy (list_spot[rnd]->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (list_spot[rnd]->s.angles, angles);
|
|
|
|
return list_spot[rnd];
|
|
}
|
|
|
|
gentity_t *SelectDuelSpawnPoint( int team, vec3_t avoidPoint, vec3_t origin, vec3_t angles )
|
|
{
|
|
gentity_t *spot;
|
|
vec3_t delta;
|
|
float dist;
|
|
float list_dist[64];
|
|
gentity_t *list_spot[64];
|
|
int numSpots, rnd, i, j;
|
|
char *spotName;
|
|
|
|
if (team == DUELTEAM_LONE)
|
|
{
|
|
spotName = "info_player_duel1";
|
|
}
|
|
else if (team == DUELTEAM_DOUBLE)
|
|
{
|
|
spotName = "info_player_duel2";
|
|
}
|
|
else if (team == DUELTEAM_SINGLE)
|
|
{
|
|
spotName = "info_player_duel";
|
|
}
|
|
else
|
|
{
|
|
spotName = "info_player_deathmatch";
|
|
}
|
|
tryAgain:
|
|
|
|
numSpots = 0;
|
|
spot = NULL;
|
|
|
|
while ((spot = G_Find (spot, FOFS(classname), spotName)) != NULL) {
|
|
if ( SpotWouldTelefrag( spot ) ) {
|
|
continue;
|
|
}
|
|
VectorSubtract( spot->s.origin, avoidPoint, delta );
|
|
dist = VectorLength( delta );
|
|
for (i = 0; i < numSpots; i++) {
|
|
if ( dist > list_dist[i] ) {
|
|
if ( numSpots >= 64 )
|
|
numSpots = 64-1;
|
|
for (j = numSpots; j > i; j--) {
|
|
list_dist[j] = list_dist[j-1];
|
|
list_spot[j] = list_spot[j-1];
|
|
}
|
|
list_dist[i] = dist;
|
|
list_spot[i] = spot;
|
|
numSpots++;
|
|
if (numSpots > 64)
|
|
numSpots = 64;
|
|
break;
|
|
}
|
|
}
|
|
if (i >= numSpots && numSpots < 64) {
|
|
list_dist[numSpots] = dist;
|
|
list_spot[numSpots] = spot;
|
|
numSpots++;
|
|
}
|
|
}
|
|
if (!numSpots)
|
|
{
|
|
if (Q_stricmp(spotName, "info_player_deathmatch"))
|
|
{ //try the loop again with info_player_deathmatch as the target if we couldn't find a duel spot
|
|
spotName = "info_player_deathmatch";
|
|
goto tryAgain;
|
|
}
|
|
|
|
//If we got here we found no free duel or DM spots, just try the first DM spot
|
|
spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
|
|
if (!spot)
|
|
G_Error( "Couldn't find a spawn point" );
|
|
VectorCopy (spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (spot->s.angles, angles);
|
|
return spot;
|
|
}
|
|
|
|
// select a random spot from the spawn points furthest away
|
|
rnd = random() * (numSpots / 2);
|
|
|
|
VectorCopy (list_spot[rnd]->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (list_spot[rnd]->s.angles, angles);
|
|
|
|
return list_spot[rnd];
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectSpawnPoint
|
|
|
|
Chooses a player start, deathmatch start, etc
|
|
============
|
|
*/
|
|
gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, team_t team ) {
|
|
return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles, team );
|
|
|
|
/*
|
|
gentity_t *spot;
|
|
gentity_t *nearestSpot;
|
|
|
|
nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint );
|
|
|
|
spot = SelectRandomDeathmatchSpawnPoint ( );
|
|
if ( spot == nearestSpot ) {
|
|
// roll again if it would be real close to point of death
|
|
spot = SelectRandomDeathmatchSpawnPoint ( );
|
|
if ( spot == nearestSpot ) {
|
|
// last try
|
|
spot = SelectRandomDeathmatchSpawnPoint ( );
|
|
}
|
|
}
|
|
|
|
// find a single player start spot
|
|
if (!spot) {
|
|
G_Error( "Couldn't find a spawn point" );
|
|
}
|
|
|
|
VectorCopy (spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (spot->s.angles, angles);
|
|
|
|
return spot;
|
|
*/
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectInitialSpawnPoint
|
|
|
|
Try to find a spawn point marked 'initial', otherwise
|
|
use normal spawn selection.
|
|
============
|
|
*/
|
|
gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles, team_t team ) {
|
|
gentity_t *spot;
|
|
|
|
spot = NULL;
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
if ( spot->spawnflags & 1 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !spot || SpotWouldTelefrag( spot ) ) {
|
|
return SelectSpawnPoint( vec3_origin, origin, angles, team );
|
|
}
|
|
|
|
VectorCopy (spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (spot->s.angles, angles);
|
|
|
|
return spot;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectSpectatorSpawnPoint
|
|
|
|
============
|
|
*/
|
|
gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) {
|
|
FindIntermissionPoint();
|
|
|
|
VectorCopy( level.intermission_origin, origin );
|
|
VectorCopy( level.intermission_angle, angles );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
BODYQUE
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
BODYQUE
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
#define BODY_SINK_TIME 30000//45000
|
|
|
|
/*
|
|
===============
|
|
InitBodyQue
|
|
===============
|
|
*/
|
|
void InitBodyQue (void) {
|
|
int i;
|
|
gentity_t *ent;
|
|
|
|
level.bodyQueIndex = 0;
|
|
for (i=0; i<BODY_QUEUE_SIZE ; i++) {
|
|
ent = G_Spawn();
|
|
ent->classname = "bodyque";
|
|
ent->neverFree = qtrue;
|
|
level.bodyQue[i] = ent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
BodySink
|
|
|
|
After sitting around for five seconds, fall into the ground and dissapear
|
|
=============
|
|
*/
|
|
void BodySink( gentity_t *ent ) {
|
|
if ( level.time - ent->timestamp > BODY_SINK_TIME + 2500 ) {
|
|
// the body ques are never actually freed, they are just unlinked
|
|
trap_UnlinkEntity( ent );
|
|
ent->physicsObject = qfalse;
|
|
return;
|
|
}
|
|
// ent->nextthink = level.time + 100;
|
|
// ent->s.pos.trBase[2] -= 1;
|
|
|
|
G_AddEvent(ent, EV_BODYFADE, 0);
|
|
ent->nextthink = level.time + 18000;
|
|
ent->takedamage = qfalse;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CopyToBodyQue
|
|
|
|
A player is respawning, so make an entity that looks
|
|
just like the existing corpse to leave behind.
|
|
=============
|
|
*/
|
|
static qboolean CopyToBodyQue( gentity_t *ent ) {
|
|
gentity_t *body;
|
|
int contents;
|
|
int islight = 0;
|
|
|
|
if (level.intermissiontime)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
trap_UnlinkEntity (ent);
|
|
|
|
// if client is in a nodrop area, don't leave the body
|
|
contents = trap_PointContents( ent->s.origin, -1 );
|
|
if ( contents & CONTENTS_NODROP ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION))
|
|
{ //for now, just don't spawn a body if you got disint'd
|
|
return qfalse;
|
|
}
|
|
|
|
// grab a body que and cycle to the next one
|
|
body = level.bodyQue[ level.bodyQueIndex ];
|
|
level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE;
|
|
|
|
trap_UnlinkEntity (body);
|
|
body->s = ent->s;
|
|
|
|
//avoid oddly angled corpses floating around
|
|
body->s.angles[PITCH] = body->s.angles[ROLL] = body->s.apos.trBase[PITCH] = body->s.apos.trBase[ROLL] = 0;
|
|
|
|
body->s.g2radius = 100;
|
|
|
|
body->s.eType = ET_BODY;
|
|
body->s.eFlags = EF_DEAD; // clear EF_TALK, etc
|
|
|
|
if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION))
|
|
{
|
|
body->s.eFlags |= EF_DISINTEGRATION;
|
|
}
|
|
|
|
VectorCopy(ent->client->ps.lastHitLoc, body->s.origin2);
|
|
|
|
body->s.powerups = 0; // clear powerups
|
|
body->s.loopSound = 0; // clear lava burning
|
|
body->s.loopIsSoundset = qfalse;
|
|
body->s.number = body - g_entities;
|
|
body->timestamp = level.time;
|
|
body->physicsObject = qtrue;
|
|
body->physicsBounce = 0; // don't bounce
|
|
if ( body->s.groundEntityNum == ENTITYNUM_NONE ) {
|
|
body->s.pos.trType = TR_GRAVITY;
|
|
body->s.pos.trTime = level.time;
|
|
VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
|
|
} else {
|
|
body->s.pos.trType = TR_STATIONARY;
|
|
}
|
|
body->s.event = 0;
|
|
|
|
body->s.weapon = ent->s.bolt2;
|
|
|
|
if (body->s.weapon == WP_SABER && ent->client->ps.saberInFlight)
|
|
{
|
|
body->s.weapon = WP_BLASTER; //lie to keep from putting a saber on the corpse, because it was thrown at death
|
|
}
|
|
|
|
//G_AddEvent(body, EV_BODY_QUEUE_COPY, ent->s.clientNum);
|
|
//Now doing this through a modified version of the rcg reliable command.
|
|
if (ent->client && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
|
|
{
|
|
islight = 1;
|
|
}
|
|
trap_SendServerCommand(-1, va("ircg %i %i %i %i", ent->s.number, body->s.number, body->s.weapon, islight));
|
|
|
|
body->r.svFlags = ent->r.svFlags | SVF_BROADCAST;
|
|
VectorCopy (ent->r.mins, body->r.mins);
|
|
VectorCopy (ent->r.maxs, body->r.maxs);
|
|
VectorCopy (ent->r.absmin, body->r.absmin);
|
|
VectorCopy (ent->r.absmax, body->r.absmax);
|
|
|
|
body->s.torsoAnim = body->s.legsAnim = ent->client->ps.legsAnim;
|
|
|
|
body->s.customRGBA[0] = ent->client->ps.customRGBA[0];
|
|
body->s.customRGBA[1] = ent->client->ps.customRGBA[1];
|
|
body->s.customRGBA[2] = ent->client->ps.customRGBA[2];
|
|
body->s.customRGBA[3] = ent->client->ps.customRGBA[3];
|
|
|
|
body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
|
|
body->r.contents = CONTENTS_CORPSE;
|
|
body->r.ownerNum = ent->s.number;
|
|
|
|
body->nextthink = level.time + BODY_SINK_TIME;
|
|
body->think = BodySink;
|
|
|
|
body->die = body_die;
|
|
|
|
// don't take more damage if already gibbed
|
|
if ( ent->health <= GIB_HEALTH ) {
|
|
body->takedamage = qfalse;
|
|
} else {
|
|
body->takedamage = qtrue;
|
|
}
|
|
|
|
VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
|
|
trap_LinkEntity (body);
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
|
|
/*
|
|
==================
|
|
SetClientViewAngle
|
|
|
|
==================
|
|
*/
|
|
void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
|
|
int i;
|
|
|
|
// set the delta angle
|
|
for (i=0 ; i<3 ; i++) {
|
|
int cmdAngle;
|
|
|
|
cmdAngle = ANGLE2SHORT(angle[i]);
|
|
ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i];
|
|
}
|
|
VectorCopy( angle, ent->s.angles );
|
|
VectorCopy (ent->s.angles, ent->client->ps.viewangles);
|
|
}
|
|
|
|
void MaintainBodyQueue(gentity_t *ent)
|
|
{ //do whatever should be done taking ragdoll and dismemberment states into account.
|
|
qboolean doRCG = qfalse;
|
|
|
|
assert(ent && ent->client);
|
|
if (ent->client->tempSpectate > level.time ||
|
|
(ent->client->ps.eFlags2 & EF2_SHIP_DEATH))
|
|
{
|
|
ent->client->noCorpse = qtrue;
|
|
}
|
|
|
|
if (!ent->client->noCorpse && !ent->client->ps.fallingToDeath)
|
|
{
|
|
if (!CopyToBodyQue (ent))
|
|
{
|
|
doRCG = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->client->noCorpse = qfalse; //clear it for next time
|
|
ent->client->ps.fallingToDeath = qfalse;
|
|
doRCG = qtrue;
|
|
}
|
|
|
|
if (doRCG)
|
|
{ //bodyque func didn't manage to call ircg so call this to assure our limbs and ragdoll states are proper on the client.
|
|
trap_SendServerCommand(-1, va("rcg %i", ent->s.clientNum));
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
respawn
|
|
================
|
|
*/
|
|
void SiegeRespawn(gentity_t *ent);
|
|
void respawn( gentity_t *ent ) {
|
|
MaintainBodyQueue(ent);
|
|
|
|
if (gEscaping || g_gametype.integer == GT_POWERDUEL)
|
|
{
|
|
ent->client->sess.sessionTeam = TEAM_SPECTATOR;
|
|
ent->client->sess.spectatorState = SPECTATOR_FREE;
|
|
ent->client->sess.spectatorClient = 0;
|
|
|
|
ent->client->pers.teamState.state = TEAM_BEGIN;
|
|
ent->client->sess.spectatorTime = level.time;
|
|
ClientSpawn(ent);
|
|
ent->client->iAmALoser = qtrue;
|
|
return;
|
|
}
|
|
|
|
trap_UnlinkEntity (ent);
|
|
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{
|
|
if (g_siegeRespawn.integer)
|
|
{
|
|
if (ent->client->tempSpectate <= level.time)
|
|
{
|
|
int minDel = g_siegeRespawn.integer* 2000;
|
|
if (minDel < 20000)
|
|
{
|
|
minDel = 20000;
|
|
}
|
|
ent->client->tempSpectate = level.time + minDel;
|
|
ent->health = ent->client->ps.stats[STAT_HEALTH] = 1;
|
|
ent->client->ps.weapon = WP_NONE;
|
|
ent->client->ps.stats[STAT_WEAPONS] = 0;
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
|
|
ent->takedamage = qfalse;
|
|
trap_LinkEntity(ent);
|
|
|
|
// Respawn time.
|
|
if ( ent->s.number < MAX_CLIENTS )
|
|
{
|
|
gentity_t *te = G_TempEntity( ent->client->ps.origin, EV_SIEGESPEC );
|
|
te->s.time = g_siegeRespawnCheck;
|
|
te->s.owner = ent->s.number;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
SiegeRespawn(ent);
|
|
}
|
|
else
|
|
{
|
|
gentity_t *tent;
|
|
|
|
ClientSpawn(ent);
|
|
|
|
// add a teleportation effect
|
|
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
|
|
tent->s.clientNum = ent->s.clientNum;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
TeamCount
|
|
|
|
Returns number of players on a team
|
|
================
|
|
*/
|
|
team_t TeamCount( int ignoreClientNum, int team ) {
|
|
int i;
|
|
int count = 0;
|
|
|
|
for ( i = 0 ; i < level.maxclients ; i++ ) {
|
|
if ( i == ignoreClientNum ) {
|
|
continue;
|
|
}
|
|
if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
|
|
continue;
|
|
}
|
|
if ( level.clients[i].sess.sessionTeam == team ) {
|
|
count++;
|
|
}
|
|
else if (g_gametype.integer == GT_SIEGE &&
|
|
level.clients[i].sess.siegeDesiredTeam == team)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
================
|
|
TeamLeader
|
|
|
|
Returns the client number of the team leader
|
|
================
|
|
*/
|
|
int TeamLeader( int team ) {
|
|
int i;
|
|
|
|
for ( i = 0 ; i < level.maxclients ; i++ ) {
|
|
if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
|
|
continue;
|
|
}
|
|
if ( level.clients[i].sess.sessionTeam == team ) {
|
|
if ( level.clients[i].sess.teamLeader )
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
PickTeam
|
|
|
|
================
|
|
*/
|
|
team_t PickTeam( int ignoreClientNum ) {
|
|
int counts[TEAM_NUM_TEAMS];
|
|
|
|
counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE );
|
|
counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED );
|
|
|
|
if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) {
|
|
return TEAM_RED;
|
|
}
|
|
if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) {
|
|
return TEAM_BLUE;
|
|
}
|
|
// equal team count, so join the team with the lowest score
|
|
if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) {
|
|
return TEAM_RED;
|
|
}
|
|
return TEAM_BLUE;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ForceClientSkin
|
|
|
|
Forces a client's skin (for teamplay)
|
|
===========
|
|
*/
|
|
/*
|
|
static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) {
|
|
char *p;
|
|
|
|
if ((p = Q_strrchr(model, '/')) != 0) {
|
|
*p = 0;
|
|
}
|
|
|
|
Q_strcat(model, MAX_QPATH, "/");
|
|
Q_strcat(model, MAX_QPATH, skin);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
===========
|
|
ClientCheckName
|
|
============
|
|
*/
|
|
static void ClientCleanName( const char *in, char *out, int outSize ) {
|
|
int len, colorlessLen;
|
|
char ch;
|
|
char *p;
|
|
int spaces;
|
|
|
|
//save room for trailing null byte
|
|
outSize--;
|
|
|
|
len = 0;
|
|
colorlessLen = 0;
|
|
p = out;
|
|
*p = 0;
|
|
spaces = 0;
|
|
|
|
while( 1 ) {
|
|
ch = *in++;
|
|
if( !ch ) {
|
|
break;
|
|
}
|
|
|
|
// don't allow leading spaces
|
|
if( !*p && ch == ' ' ) {
|
|
continue;
|
|
}
|
|
|
|
// check colors
|
|
if( ch == Q_COLOR_ESCAPE ) {
|
|
// solo trailing carat is not a color prefix
|
|
if( !*in ) {
|
|
break;
|
|
}
|
|
|
|
// don't allow black in a name, period
|
|
if( ColorIndex(*in) == 0 ) {
|
|
in++;
|
|
continue;
|
|
}
|
|
|
|
// make sure room in dest for both chars
|
|
if( len > outSize - 2 ) {
|
|
break;
|
|
}
|
|
|
|
*out++ = ch;
|
|
*out++ = *in++;
|
|
len += 2;
|
|
continue;
|
|
}
|
|
|
|
// don't allow too many consecutive spaces
|
|
if( ch == ' ' ) {
|
|
spaces++;
|
|
if( spaces > 3 ) {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
spaces = 0;
|
|
}
|
|
|
|
if( len > outSize - 1 ) {
|
|
break;
|
|
}
|
|
|
|
*out++ = ch;
|
|
colorlessLen++;
|
|
len++;
|
|
}
|
|
*out = 0;
|
|
|
|
// don't allow empty names
|
|
if( *p == 0 || colorlessLen == 0 ) {
|
|
Q_strncpyz( p, "Padawan", outSize );
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void G_DebugWrite(const char *path, const char *text)
|
|
{
|
|
fileHandle_t f;
|
|
|
|
trap_FS_FOpenFile( path, &f, FS_APPEND );
|
|
trap_FS_Write(text, strlen(text), f);
|
|
trap_FS_FCloseFile(f);
|
|
}
|
|
#endif
|
|
|
|
qboolean G_SaberModelSetup(gentity_t *ent)
|
|
{
|
|
int i = 0;
|
|
qboolean fallbackForSaber = qtrue;
|
|
|
|
while (i < MAX_SABERS)
|
|
{
|
|
if (ent->client->saber[i].model[0])
|
|
{
|
|
//first kill it off if we've already got it
|
|
if (ent->client->weaponGhoul2[i])
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&(ent->client->weaponGhoul2[i]));
|
|
}
|
|
trap_G2API_InitGhoul2Model(&ent->client->weaponGhoul2[i], ent->client->saber[i].model, 0, 0, -20, 0, 0);
|
|
|
|
if (ent->client->weaponGhoul2[i])
|
|
{
|
|
int j = 0;
|
|
char *tagName;
|
|
int tagBolt;
|
|
|
|
if (ent->client->saber[i].skin)
|
|
{
|
|
trap_G2API_SetSkin(ent->client->weaponGhoul2[i], 0, ent->client->saber[i].skin, ent->client->saber[i].skin);
|
|
}
|
|
|
|
if (ent->client->saber[i].saberFlags & SFL_BOLT_TO_WRIST)
|
|
{
|
|
trap_G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, 3+i);
|
|
}
|
|
else
|
|
{ // bolt to right hand for 0, or left hand for 1
|
|
trap_G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, i);
|
|
}
|
|
|
|
//Add all the bolt points
|
|
while (j < ent->client->saber[i].numBlades)
|
|
{
|
|
tagName = va("*blade%i", j+1);
|
|
tagBolt = trap_G2API_AddBolt(ent->client->weaponGhoul2[i], 0, tagName);
|
|
|
|
if (tagBolt == -1)
|
|
{
|
|
if (j == 0)
|
|
{ //guess this is an 0ldsk3wl saber
|
|
tagBolt = trap_G2API_AddBolt(ent->client->weaponGhoul2[i], 0, "*flash");
|
|
fallbackForSaber = qfalse;
|
|
break;
|
|
}
|
|
|
|
if (tagBolt == -1)
|
|
{
|
|
assert(0);
|
|
break;
|
|
|
|
}
|
|
}
|
|
j++;
|
|
|
|
fallbackForSaber = qfalse; //got at least one custom saber so don't need default
|
|
}
|
|
|
|
//Copy it into the main instance
|
|
trap_G2API_CopySpecificGhoul2Model(ent->client->weaponGhoul2[i], 0, ent->ghoul2, i+1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return fallbackForSaber;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SetupGameGhoul2Model
|
|
|
|
There are two ghoul2 model instances per player (actually three). One is on the clientinfo (the base for the client side
|
|
player, and copied for player spawns and for corpses). One is attached to the centity itself, which is the model acutally
|
|
animated and rendered by the system. The final is the game ghoul2 model. This is animated by pmove on the server, and
|
|
is used for determining where the lightsaber should be, and for per-poly collision tests.
|
|
===========
|
|
*/
|
|
void *g2SaberInstance = NULL;
|
|
|
|
#include "../namespace_begin.h"
|
|
qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName);
|
|
qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors );
|
|
void BG_GetVehicleModelName(char *modelname);
|
|
#include "../namespace_end.h"
|
|
|
|
void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName)
|
|
{
|
|
int handle;
|
|
char afilename[MAX_QPATH];
|
|
#if 0
|
|
char /**GLAName,*/ *slash;
|
|
#endif
|
|
char GLAName[MAX_QPATH];
|
|
vec3_t tempVec = {0,0,0};
|
|
|
|
// First things first. If this is a ghoul2 model, then let's make sure we demolish this first.
|
|
if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2))
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&(ent->ghoul2));
|
|
}
|
|
|
|
//rww - just load the "standard" model for the server"
|
|
if (!precachedKyle)
|
|
{
|
|
int defSkin;
|
|
|
|
Com_sprintf( afilename, sizeof( afilename ), "models/players/kyle/model.glm" );
|
|
handle = trap_G2API_InitGhoul2Model(&precachedKyle, afilename, 0, 0, -20, 0, 0);
|
|
|
|
if (handle<0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
defSkin = trap_R_RegisterSkin("models/players/kyle/model_default.skin");
|
|
trap_G2API_SetSkin(precachedKyle, 0, defSkin, defSkin);
|
|
}
|
|
|
|
if (precachedKyle && trap_G2_HaveWeGhoul2Models(precachedKyle))
|
|
{
|
|
if (d_perPlayerGhoul2.integer || ent->s.number >= MAX_CLIENTS ||
|
|
G_PlayerHasCustomSkeleton(ent))
|
|
{ //rww - allow option for perplayer models on server for collision and bolt stuff.
|
|
char modelFullPath[MAX_QPATH];
|
|
char truncModelName[MAX_QPATH];
|
|
char skin[MAX_QPATH];
|
|
char vehicleName[MAX_QPATH];
|
|
int skinHandle = 0;
|
|
int i = 0;
|
|
char *p;
|
|
|
|
// If this is a vehicle, get it's model name.
|
|
if ( ent->client->NPC_class == CLASS_VEHICLE )
|
|
{
|
|
strcpy(vehicleName, modelname);
|
|
BG_GetVehicleModelName(modelname);
|
|
strcpy(truncModelName, modelname);
|
|
skin[0] = 0;
|
|
if ( ent->m_pVehicle
|
|
&& ent->m_pVehicle->m_pVehicleInfo
|
|
&& ent->m_pVehicle->m_pVehicleInfo->skin
|
|
&& ent->m_pVehicle->m_pVehicleInfo->skin[0] )
|
|
{
|
|
skinHandle = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelname, ent->m_pVehicle->m_pVehicleInfo->skin));
|
|
}
|
|
else
|
|
{
|
|
skinHandle = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelname));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (skinName && skinName[0])
|
|
{
|
|
strcpy(skin, skinName);
|
|
strcpy(truncModelName, modelname);
|
|
}
|
|
else
|
|
{
|
|
strcpy(skin, "default");
|
|
|
|
strcpy(truncModelName, modelname);
|
|
p = Q_strrchr(truncModelName, '/');
|
|
|
|
if (p)
|
|
{
|
|
*p = 0;
|
|
p++;
|
|
|
|
while (p && *p)
|
|
{
|
|
skin[i] = *p;
|
|
i++;
|
|
p++;
|
|
}
|
|
skin[i] = 0;
|
|
i = 0;
|
|
}
|
|
|
|
if (!BG_IsValidCharacterModel(truncModelName, skin))
|
|
{
|
|
strcpy(truncModelName, "kyle");
|
|
strcpy(skin, "default");
|
|
}
|
|
|
|
if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer )
|
|
{
|
|
BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, NULL );
|
|
}
|
|
else if (g_gametype.integer == GT_SIEGE)
|
|
{ //force skin for class if appropriate
|
|
if (ent->client->siegeClass != -1)
|
|
{
|
|
siegeClass_t *scl = &bgSiegeClasses[ent->client->siegeClass];
|
|
if (scl->forcedSkin[0])
|
|
{
|
|
strcpy(skin, scl->forcedSkin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (skin[0])
|
|
{
|
|
char *useSkinName;
|
|
|
|
if (strchr(skin, '|'))
|
|
{//three part skin
|
|
useSkinName = va("models/players/%s/|%s", truncModelName, skin);
|
|
}
|
|
else
|
|
{
|
|
useSkinName = va("models/players/%s/model_%s.skin", truncModelName, skin);
|
|
}
|
|
|
|
skinHandle = trap_R_RegisterSkin(useSkinName);
|
|
}
|
|
|
|
strcpy(modelFullPath, va("models/players/%s/model.glm", truncModelName));
|
|
handle = trap_G2API_InitGhoul2Model(&ent->ghoul2, modelFullPath, 0, skinHandle, -20, 0, 0);
|
|
|
|
if (handle<0)
|
|
{ //Huh. Guess we don't have this model. Use the default.
|
|
|
|
if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2))
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&(ent->ghoul2));
|
|
}
|
|
ent->ghoul2 = NULL;
|
|
trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2);
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_SetSkin(ent->ghoul2, 0, skinHandle, skinHandle);
|
|
|
|
GLAName[0] = 0;
|
|
trap_G2API_GetGLAName( ent->ghoul2, 0, GLAName);
|
|
|
|
if (!GLAName[0] || (!strstr(GLAName, "players/_humanoid/") && ent->s.number < MAX_CLIENTS && !G_PlayerHasCustomSkeleton(ent)))
|
|
{ //a bad model
|
|
trap_G2API_CleanGhoul2Models(&(ent->ghoul2));
|
|
ent->ghoul2 = NULL;
|
|
trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2);
|
|
}
|
|
|
|
if (ent->s.number >= MAX_CLIENTS)
|
|
{
|
|
ent->s.modelGhoul2 = 1; //so we know to free it on the client when we're removed.
|
|
|
|
if (skin[0])
|
|
{ //append it after a *
|
|
strcat( modelFullPath, va("*%s", skin) );
|
|
}
|
|
|
|
if ( ent->client->NPC_class == CLASS_VEHICLE )
|
|
{ //vehicles are tricky and send over their vehicle names as the model (the model is then retrieved based on the vehicle name)
|
|
ent->s.modelindex = G_ModelIndex(vehicleName);
|
|
}
|
|
else
|
|
{
|
|
ent->s.modelindex = G_ModelIndex(modelFullPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Attach the instance to this entity num so we can make use of client-server
|
|
//shared operations if possible.
|
|
trap_G2API_AttachInstanceToEntNum(ent->ghoul2, ent->s.number, qtrue);
|
|
|
|
// The model is now loaded.
|
|
|
|
GLAName[0] = 0;
|
|
|
|
if (!BGPAFtextLoaded)
|
|
{
|
|
if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1)
|
|
{
|
|
Com_Printf( "Failed to load humanoid animation file\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ent->s.number >= MAX_CLIENTS || G_PlayerHasCustomSkeleton(ent))
|
|
{
|
|
ent->localAnimIndex = -1;
|
|
|
|
GLAName[0] = 0;
|
|
trap_G2API_GetGLAName(ent->ghoul2, 0, GLAName);
|
|
|
|
if (GLAName[0] &&
|
|
!strstr(GLAName, "players/_humanoid/") /*&&
|
|
!strstr(GLAName, "players/rockettrooper/")*/)
|
|
{ //it doesn't use humanoid anims.
|
|
char *slash = Q_strrchr( GLAName, '/' );
|
|
if ( slash )
|
|
{
|
|
strcpy(slash, "/animation.cfg");
|
|
|
|
ent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse);
|
|
}
|
|
}
|
|
else
|
|
{ //humanoid index.
|
|
if (strstr(GLAName, "players/rockettrooper/"))
|
|
{
|
|
ent->localAnimIndex = 1;
|
|
}
|
|
else
|
|
{
|
|
ent->localAnimIndex = 0;
|
|
}
|
|
}
|
|
|
|
if (ent->localAnimIndex == -1)
|
|
{
|
|
Com_Error(ERR_DROP, "NPC had an invalid GLA\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GLAName[0] = 0;
|
|
trap_G2API_GetGLAName(ent->ghoul2, 0, GLAName);
|
|
|
|
if (strstr(GLAName, "players/rockettrooper/"))
|
|
{
|
|
//assert(!"Should not have gotten in here with rockettrooper skel");
|
|
ent->localAnimIndex = 1;
|
|
}
|
|
else
|
|
{
|
|
ent->localAnimIndex = 0;
|
|
}
|
|
}
|
|
|
|
if (ent->s.NPC_class == CLASS_VEHICLE &&
|
|
ent->m_pVehicle)
|
|
{ //do special vehicle stuff
|
|
char strTemp[128];
|
|
int i;
|
|
|
|
// Setup the default first bolt
|
|
i = trap_G2API_AddBolt( ent->ghoul2, 0, "model_root" );
|
|
|
|
// Setup the droid unit.
|
|
ent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( ent->ghoul2, 0, "*droidunit" );
|
|
|
|
// Setup the Exhausts.
|
|
for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ )
|
|
{
|
|
Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 );
|
|
ent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp );
|
|
}
|
|
|
|
// Setup the Muzzles.
|
|
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
|
|
{
|
|
Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 );
|
|
ent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp );
|
|
if ( ent->m_pVehicle->m_iMuzzleTag[i] == -1 )
|
|
{//ergh, try *flash?
|
|
Com_sprintf( strTemp, 128, "*flash%i", i + 1 );
|
|
ent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp );
|
|
}
|
|
}
|
|
|
|
// Setup the Turrets.
|
|
for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ )
|
|
{
|
|
if ( ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag )
|
|
{
|
|
ent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag );
|
|
}
|
|
else
|
|
{
|
|
ent->m_pVehicle->m_iGunnerViewTag[i] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ent->client->ps.weapon == WP_SABER || ent->s.number < MAX_CLIENTS)
|
|
{ //a player or NPC saber user
|
|
trap_G2API_AddBolt(ent->ghoul2, 0, "*r_hand");
|
|
trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
|
|
|
|
//rhand must always be first bolt. lhand always second. Whichever you want the
|
|
//jetpack bolted to must always be third.
|
|
trap_G2API_AddBolt(ent->ghoul2, 0, "*chestg");
|
|
|
|
//claw bolts
|
|
trap_G2API_AddBolt(ent->ghoul2, 0, "*r_hand_cap_r_arm");
|
|
trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand_cap_l_arm");
|
|
|
|
trap_G2API_SetBoneAnim(ent->ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1);
|
|
trap_G2API_SetBoneAngles(ent->ghoul2, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time);
|
|
trap_G2API_SetBoneAngles(ent->ghoul2, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, level.time);
|
|
|
|
if (!g2SaberInstance)
|
|
{
|
|
trap_G2API_InitGhoul2Model(&g2SaberInstance, "models/weapons2/saber/saber_w.glm", 0, 0, -20, 0, 0);
|
|
|
|
if (g2SaberInstance)
|
|
{
|
|
// indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied
|
|
trap_G2API_SetBoltInfo(g2SaberInstance, 0, 0);
|
|
// now set up the gun bolt on it
|
|
trap_G2API_AddBolt(g2SaberInstance, 0, "*blade1");
|
|
}
|
|
}
|
|
|
|
if (G_SaberModelSetup(ent))
|
|
{
|
|
if (g2SaberInstance)
|
|
{
|
|
trap_G2API_CopySpecificGhoul2Model(g2SaberInstance, 0, ent->ghoul2, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ent->s.number >= MAX_CLIENTS)
|
|
{ //some extra NPC stuff
|
|
if (trap_G2API_AddBolt(ent->ghoul2, 0, "lower_lumbar") == -1)
|
|
{ //check now to see if we have this bone for setting anims and such
|
|
ent->noLumbar = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
===========
|
|
ClientUserInfoChanged
|
|
|
|
Called from ClientConnect when the player first connects and
|
|
directly by the server system when the player updates a userinfo variable.
|
|
|
|
The game can override any of the settings and call trap_SetUserinfo
|
|
if desired.
|
|
============
|
|
*/
|
|
qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride);
|
|
void G_ValidateSiegeClassForTeam(gentity_t *ent, int team);
|
|
void ClientUserinfoChanged( int clientNum ) {
|
|
gentity_t *ent;
|
|
int teamTask, teamLeader, team, health;
|
|
char *s;
|
|
char model[MAX_QPATH];
|
|
//char headModel[MAX_QPATH];
|
|
char forcePowers[MAX_QPATH];
|
|
char oldname[MAX_STRING_CHARS];
|
|
gclient_t *client;
|
|
char c1[MAX_INFO_STRING];
|
|
char c2[MAX_INFO_STRING];
|
|
// char redTeam[MAX_INFO_STRING];
|
|
// char blueTeam[MAX_INFO_STRING];
|
|
char userinfo[MAX_INFO_STRING];
|
|
char className[MAX_QPATH]; //name of class type to use in siege
|
|
char saberName[MAX_QPATH];
|
|
char saber2Name[MAX_QPATH];
|
|
char *value;
|
|
int maxHealth;
|
|
qboolean modelChanged = qfalse;
|
|
|
|
ent = g_entities + clientNum;
|
|
client = ent->client;
|
|
|
|
trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
|
|
|
|
// check for malformed or illegal info strings
|
|
if ( !Info_Validate(userinfo) ) {
|
|
strcpy (userinfo, "\\name\\badinfo");
|
|
}
|
|
|
|
// check for local client
|
|
s = Info_ValueForKey( userinfo, "ip" );
|
|
if ( !strcmp( s, "localhost" ) ) {
|
|
client->pers.localClient = qtrue;
|
|
}
|
|
|
|
// check the item prediction
|
|
s = Info_ValueForKey( userinfo, "cg_predictItems" );
|
|
if ( !atoi( s ) ) {
|
|
client->pers.predictItemPickup = qfalse;
|
|
} else {
|
|
client->pers.predictItemPickup = qtrue;
|
|
}
|
|
|
|
// set name
|
|
Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
|
|
s = Info_ValueForKey (userinfo, "name");
|
|
ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) );
|
|
|
|
if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
|
|
if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
|
|
Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) );
|
|
}
|
|
}
|
|
|
|
if ( client->pers.connected == CON_CONNECTED ) {
|
|
if ( strcmp( oldname, client->pers.netname ) )
|
|
{
|
|
if ( client->pers.netnameTime > level.time )
|
|
{
|
|
trap_SendServerCommand( clientNum, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NONAMECHANGE")) );
|
|
|
|
Info_SetValueForKey( userinfo, "name", oldname );
|
|
trap_SetUserinfo( clientNum, userinfo );
|
|
strcpy ( client->pers.netname, oldname );
|
|
}
|
|
else
|
|
{
|
|
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s %s\n\"", oldname, G_GetStringEdString("MP_SVGAME", "PLRENAME"), client->pers.netname) );
|
|
client->pers.netnameTime = level.time + 5000;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set model
|
|
Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) );
|
|
|
|
if (d_perPlayerGhoul2.integer)
|
|
{
|
|
if (Q_stricmp(model, client->modelname))
|
|
{
|
|
strcpy(client->modelname, model);
|
|
modelChanged = qtrue;
|
|
}
|
|
}
|
|
|
|
//Get the skin RGB based on his userinfo
|
|
value = Info_ValueForKey (userinfo, "char_color_red");
|
|
if (value)
|
|
{
|
|
client->ps.customRGBA[0] = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
client->ps.customRGBA[0] = 255;
|
|
}
|
|
|
|
value = Info_ValueForKey (userinfo, "char_color_green");
|
|
if (value)
|
|
{
|
|
client->ps.customRGBA[1] = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
client->ps.customRGBA[1] = 255;
|
|
}
|
|
|
|
value = Info_ValueForKey (userinfo, "char_color_blue");
|
|
if (value)
|
|
{
|
|
client->ps.customRGBA[2] = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
client->ps.customRGBA[2] = 255;
|
|
}
|
|
|
|
if ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100)
|
|
{ //hmm, too dark!
|
|
client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255;
|
|
}
|
|
|
|
client->ps.customRGBA[3]=255;
|
|
|
|
Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
|
|
|
|
// bots set their team a few frames later
|
|
if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) {
|
|
s = Info_ValueForKey( userinfo, "team" );
|
|
if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) {
|
|
team = TEAM_RED;
|
|
} else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) {
|
|
team = TEAM_BLUE;
|
|
} else {
|
|
// pick the team with the least number of players
|
|
team = PickTeam( clientNum );
|
|
}
|
|
}
|
|
else {
|
|
team = client->sess.sessionTeam;
|
|
}
|
|
|
|
//Set the siege class
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{
|
|
strcpy(className, client->sess.siegeClass);
|
|
|
|
//This function will see if the given class is legal for the given team.
|
|
//If not className will be filled in with the first legal class for this team.
|
|
/* if (!BG_SiegeCheckClassLegality(team, className) &&
|
|
Q_stricmp(client->sess.siegeClass, "none"))
|
|
{ //if it isn't legal pop up the class menu
|
|
trap_SendServerCommand(ent-g_entities, "scl");
|
|
}
|
|
*/
|
|
//Now that the team is legal for sure, we'll go ahead and get an index for it.
|
|
client->siegeClass = BG_SiegeFindClassIndexByName(className);
|
|
if (client->siegeClass == -1)
|
|
{ //ok, get the first valid class for the team you're on then, I guess.
|
|
BG_SiegeCheckClassLegality(team, className);
|
|
strcpy(client->sess.siegeClass, className);
|
|
client->siegeClass = BG_SiegeFindClassIndexByName(className);
|
|
}
|
|
else
|
|
{ //otherwise, make sure the class we are using is legal.
|
|
G_ValidateSiegeClassForTeam(ent, team);
|
|
strcpy(className, client->sess.siegeClass);
|
|
}
|
|
|
|
//Set the sabers if the class dictates
|
|
if (client->siegeClass != -1)
|
|
{
|
|
siegeClass_t *scl = &bgSiegeClasses[client->siegeClass];
|
|
|
|
if (scl->saber1[0])
|
|
{
|
|
G_SetSaber(ent, 0, scl->saber1, qtrue);
|
|
}
|
|
else
|
|
{ //default I guess
|
|
G_SetSaber(ent, 0, "Kyle", qtrue);
|
|
}
|
|
if (scl->saber2[0])
|
|
{
|
|
G_SetSaber(ent, 1, scl->saber2, qtrue);
|
|
}
|
|
else
|
|
{ //no second saber then
|
|
G_SetSaber(ent, 1, "none", qtrue);
|
|
}
|
|
|
|
//make sure the saber models are updated
|
|
G_SaberModelSetup(ent);
|
|
|
|
if (scl->forcedModel[0])
|
|
{ //be sure to override the model we actually use
|
|
strcpy(model, scl->forcedModel);
|
|
if (d_perPlayerGhoul2.integer)
|
|
{
|
|
if (Q_stricmp(model, client->modelname))
|
|
{
|
|
strcpy(client->modelname, model);
|
|
modelChanged = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//force them to use their class model on the server, if the class dictates
|
|
if (G_PlayerHasCustomSkeleton(ent))
|
|
{
|
|
if (Q_stricmp(model, client->modelname) || ent->localAnimIndex == 0)
|
|
{
|
|
strcpy(client->modelname, model);
|
|
modelChanged = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strcpy(className, "none");
|
|
}
|
|
|
|
//Set the saber name
|
|
strcpy(saberName, client->sess.saberType);
|
|
strcpy(saber2Name, client->sess.saber2Type);
|
|
|
|
// set max health
|
|
if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1)
|
|
{
|
|
siegeClass_t *scl = &bgSiegeClasses[client->siegeClass];
|
|
maxHealth = 100;
|
|
|
|
if (scl->maxhealth)
|
|
{
|
|
maxHealth = scl->maxhealth;
|
|
}
|
|
|
|
health = maxHealth;
|
|
}
|
|
else
|
|
{
|
|
maxHealth = 100;
|
|
health = 100; //atoi( Info_ValueForKey( userinfo, "handicap" ) );
|
|
}
|
|
client->pers.maxHealth = health;
|
|
if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) {
|
|
client->pers.maxHealth = 100;
|
|
}
|
|
client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
|
|
|
|
/* NOTE: all client side now
|
|
|
|
// team
|
|
switch( team ) {
|
|
case TEAM_RED:
|
|
ForceClientSkin(client, model, "red");
|
|
// ForceClientSkin(client, headModel, "red");
|
|
break;
|
|
case TEAM_BLUE:
|
|
ForceClientSkin(client, model, "blue");
|
|
// ForceClientSkin(client, headModel, "blue");
|
|
break;
|
|
}
|
|
// don't ever use a default skin in teamplay, it would just waste memory
|
|
// however bots will always join a team but they spawn in as spectator
|
|
if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) {
|
|
ForceClientSkin(client, model, "red");
|
|
// ForceClientSkin(client, headModel, "red");
|
|
}
|
|
*/
|
|
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
client->pers.teamInfo = qtrue;
|
|
} else {
|
|
s = Info_ValueForKey( userinfo, "teamoverlay" );
|
|
if ( ! *s || atoi( s ) != 0 ) {
|
|
client->pers.teamInfo = qtrue;
|
|
} else {
|
|
client->pers.teamInfo = qfalse;
|
|
}
|
|
}
|
|
/*
|
|
s = Info_ValueForKey( userinfo, "cg_pmove_fixed" );
|
|
if ( !*s || atoi( s ) == 0 ) {
|
|
client->pers.pmoveFixed = qfalse;
|
|
}
|
|
else {
|
|
client->pers.pmoveFixed = qtrue;
|
|
}
|
|
*/
|
|
|
|
// team task (0 = none, 1 = offence, 2 = defence)
|
|
teamTask = atoi(Info_ValueForKey(userinfo, "teamtask"));
|
|
// team Leader (1 = leader, 0 is normal player)
|
|
teamLeader = client->sess.teamLeader;
|
|
|
|
// colors
|
|
strcpy(c1, Info_ValueForKey( userinfo, "color1" ));
|
|
strcpy(c2, Info_ValueForKey( userinfo, "color2" ));
|
|
|
|
// strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" ));
|
|
// strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" ));
|
|
|
|
// send over a subset of the userinfo keys so other clients can
|
|
// print scoreboards, display models, and play custom sounds
|
|
if ( ent->r.svFlags & SVF_BOT ) {
|
|
s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d\\siegeclass\\%s\\st\\%s\\st2\\%s\\dt\\%i\\sdt\\%i",
|
|
client->pers.netname, team, model, c1, c2,
|
|
client->pers.maxHealth, client->sess.wins, client->sess.losses,
|
|
Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader, className, saberName, saber2Name, client->sess.duelTeam, client->sess.siegeDesiredTeam );
|
|
} else {
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{ //more crap to send
|
|
s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\siegeclass\\%s\\st\\%s\\st2\\%s\\dt\\%i\\sdt\\%i",
|
|
client->pers.netname, client->sess.sessionTeam, model, c1, c2,
|
|
client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, className, saberName, saber2Name, client->sess.duelTeam, client->sess.siegeDesiredTeam);
|
|
}
|
|
else
|
|
{
|
|
s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\st\\%s\\st2\\%s\\dt\\%i",
|
|
client->pers.netname, client->sess.sessionTeam, model, c1, c2,
|
|
client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, saberName, saber2Name, client->sess.duelTeam);
|
|
}
|
|
}
|
|
|
|
trap_SetConfigstring( CS_PLAYERS+clientNum, s );
|
|
|
|
if (modelChanged) //only going to be true for allowable server-side custom skeleton cases
|
|
{ //update the server g2 instance if appropriate
|
|
char *modelname = Info_ValueForKey (userinfo, "model");
|
|
SetupGameGhoul2Model(ent, modelname, NULL);
|
|
|
|
if (ent->ghoul2 && ent->client)
|
|
{
|
|
ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update.
|
|
}
|
|
|
|
client->torsoAnimExecute = client->legsAnimExecute = -1;
|
|
client->torsoLastFlip = client->legsLastFlip = qfalse;
|
|
}
|
|
|
|
if (g_logClientInfo.integer)
|
|
{
|
|
G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
ClientConnect
|
|
|
|
Called when a player begins connecting to the server.
|
|
Called again for every map change or tournement restart.
|
|
|
|
The session information will be valid after exit.
|
|
|
|
Return NULL if the client should be allowed, otherwise return
|
|
a string with the reason for denial.
|
|
|
|
Otherwise, the client will be sent the current gamestate
|
|
and will eventually get to ClientBegin.
|
|
|
|
firstTime will be qtrue the very first time a client connects
|
|
to the server machine, but qfalse on map changes and tournement
|
|
restarts.
|
|
============
|
|
*/
|
|
char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) {
|
|
char *value;
|
|
// char *areabits;
|
|
gclient_t *client;
|
|
char userinfo[MAX_INFO_STRING];
|
|
char IPstring[32]={0};
|
|
gentity_t *ent;
|
|
gentity_t *te;
|
|
|
|
ent = &g_entities[ clientNum ];
|
|
|
|
trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
|
|
|
|
// check to see if they are on the banned IP list
|
|
value = Info_ValueForKey (userinfo, "ip");
|
|
Q_strncpyz(IPstring, value, sizeof(IPstring) );
|
|
|
|
|
|
if ( G_FilterPacket( value ) ) {
|
|
return "Banned.";
|
|
}
|
|
|
|
if ( !( ent->r.svFlags & SVF_BOT ) && !isBot && g_needpass.integer ) {
|
|
// check for a password
|
|
value = Info_ValueForKey (userinfo, "password");
|
|
if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) &&
|
|
strcmp( g_password.string, value) != 0) {
|
|
static char sTemp[1024];
|
|
Q_strncpyz(sTemp, G_GetStringEdString("MP_SVGAME","INVALID_ESCAPE_TO_MAIN"), sizeof (sTemp) );
|
|
return sTemp;// return "Invalid password";
|
|
}
|
|
}
|
|
|
|
// they can connect
|
|
ent->client = level.clients + clientNum;
|
|
client = ent->client;
|
|
|
|
//assign the pointer for bg entity access
|
|
ent->playerState = &ent->client->ps;
|
|
|
|
// areabits = client->areabits;
|
|
|
|
memset( client, 0, sizeof(*client) );
|
|
|
|
client->pers.connected = CON_CONNECTING;
|
|
|
|
// read or initialize the session data
|
|
if ( firstTime || level.newSession ) {
|
|
G_InitSessionData( client, userinfo, isBot );
|
|
}
|
|
G_ReadSessionData( client );
|
|
|
|
client->sess.IPstring[0] = 0;
|
|
Q_strncpyz(client->sess.IPstring, IPstring, sizeof(client->sess.IPstring) );
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
(firstTime || level.newSession))
|
|
{ //if this is the first time then auto-assign a desired siege team and show briefing for that team
|
|
client->sess.siegeDesiredTeam = 0;//PickTeam(ent->s.number);
|
|
/*
|
|
trap_SendServerCommand(ent->s.number, va("sb %i", client->sess.siegeDesiredTeam));
|
|
*/
|
|
//don't just show it - they'll see it if they switch to a team on purpose.
|
|
}
|
|
|
|
|
|
if (g_gametype.integer == GT_SIEGE && client->sess.sessionTeam != TEAM_SPECTATOR)
|
|
{
|
|
if (firstTime || level.newSession)
|
|
{ //start as spec
|
|
client->sess.siegeDesiredTeam = client->sess.sessionTeam;
|
|
client->sess.sessionTeam = TEAM_SPECTATOR;
|
|
}
|
|
}
|
|
else if (g_gametype.integer == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR)
|
|
{
|
|
client->sess.sessionTeam = TEAM_SPECTATOR;
|
|
}
|
|
|
|
if( isBot ) {
|
|
ent->r.svFlags |= SVF_BOT;
|
|
ent->inuse = qtrue;
|
|
if( !G_BotConnect( clientNum, !firstTime ) ) {
|
|
return "BotConnectfailed";
|
|
}
|
|
}
|
|
|
|
// get and distribute relevent paramters
|
|
G_LogPrintf( "ClientConnect: %i\n", clientNum );
|
|
ClientUserinfoChanged( clientNum );
|
|
G_LogPrintf( "%s connected with IP: %s\n", client->pers.netname, client->sess.IPstring );
|
|
|
|
// don't do the "xxx connected" messages if they were caried over from previous level
|
|
if ( firstTime ) {
|
|
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLCONNECT")) );
|
|
}
|
|
|
|
if ( g_gametype.integer >= GT_TEAM &&
|
|
client->sess.sessionTeam != TEAM_SPECTATOR ) {
|
|
BroadcastTeamChange( client, -1 );
|
|
}
|
|
|
|
// count current clients and rank for scoreboard
|
|
CalculateRanks();
|
|
|
|
te = G_TempEntity( vec3_origin, EV_CLIENTJOIN );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = clientNum;
|
|
|
|
// for statistics
|
|
// client->areabits = areabits;
|
|
// if ( !client->areabits )
|
|
// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void G_WriteClientSessionData( gclient_t *client );
|
|
|
|
#include "../namespace_begin.h"
|
|
void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName );
|
|
#include "../namespace_end.h"
|
|
|
|
/*
|
|
===========
|
|
ClientBegin
|
|
|
|
called when a client has finished connecting, and is ready
|
|
to be placed into the level. This will happen every level load,
|
|
and on transition between teams, but doesn't happen on respawns
|
|
============
|
|
*/
|
|
extern qboolean gSiegeRoundBegun;
|
|
extern qboolean gSiegeRoundEnded;
|
|
void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin);
|
|
void ClientBegin( int clientNum, qboolean allowTeamReset ) {
|
|
gentity_t *ent;
|
|
gclient_t *client;
|
|
gentity_t *tent;
|
|
int flags, i;
|
|
char userinfo[MAX_INFO_VALUE], *modelname;
|
|
|
|
ent = g_entities + clientNum;
|
|
|
|
if ((ent->r.svFlags & SVF_BOT) && g_gametype.integer >= GT_TEAM)
|
|
{
|
|
if (allowTeamReset)
|
|
{
|
|
const char *team = "Red";
|
|
int preSess;
|
|
|
|
//SetTeam(ent, "");
|
|
ent->client->sess.sessionTeam = PickTeam(-1);
|
|
trap_GetUserinfo(clientNum, userinfo, MAX_INFO_STRING);
|
|
|
|
if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
|
|
{
|
|
ent->client->sess.sessionTeam = TEAM_RED;
|
|
}
|
|
|
|
if (ent->client->sess.sessionTeam == TEAM_RED)
|
|
{
|
|
team = "Red";
|
|
}
|
|
else
|
|
{
|
|
team = "Blue";
|
|
}
|
|
|
|
Info_SetValueForKey( userinfo, "team", team );
|
|
|
|
trap_SetUserinfo( clientNum, userinfo );
|
|
|
|
ent->client->ps.persistant[ PERS_TEAM ] = ent->client->sess.sessionTeam;
|
|
|
|
preSess = ent->client->sess.sessionTeam;
|
|
G_ReadSessionData( ent->client );
|
|
ent->client->sess.sessionTeam = preSess;
|
|
G_WriteClientSessionData(ent->client);
|
|
ClientUserinfoChanged( clientNum );
|
|
ClientBegin(clientNum, qfalse);
|
|
return;
|
|
}
|
|
}
|
|
|
|
client = level.clients + clientNum;
|
|
|
|
if ( ent->r.linked ) {
|
|
trap_UnlinkEntity( ent );
|
|
}
|
|
G_InitGentity( ent );
|
|
ent->touch = 0;
|
|
ent->pain = 0;
|
|
ent->client = client;
|
|
|
|
//assign the pointer for bg entity access
|
|
ent->playerState = &ent->client->ps;
|
|
|
|
client->pers.connected = CON_CONNECTED;
|
|
client->pers.enterTime = level.time;
|
|
client->pers.teamState.state = TEAM_BEGIN;
|
|
|
|
// save eflags around this, because changing teams will
|
|
// cause this to happen with a valid entity, and we
|
|
// want to make sure the teleport bit is set right
|
|
// so the viewpoint doesn't interpolate through the
|
|
// world to the new position
|
|
flags = client->ps.eFlags;
|
|
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if (ent->client->ps.fd.forcePowersActive & (1 << i))
|
|
{
|
|
WP_ForcePowerStop(ent, i);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
i = TRACK_CHANNEL_1;
|
|
|
|
while (i < NUM_TRACK_CHANNELS)
|
|
{
|
|
if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0)
|
|
{
|
|
G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE);
|
|
}
|
|
i++;
|
|
}
|
|
i = 0;
|
|
|
|
memset( &client->ps, 0, sizeof( client->ps ) );
|
|
client->ps.eFlags = flags;
|
|
|
|
client->ps.hasDetPackPlanted = qfalse;
|
|
|
|
//first-time force power initialization
|
|
WP_InitForcePowers( ent );
|
|
|
|
//init saber ent
|
|
WP_SaberInitBladeData( ent );
|
|
|
|
// First time model setup for that player.
|
|
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
|
|
modelname = Info_ValueForKey (userinfo, "model");
|
|
SetupGameGhoul2Model(ent, modelname, NULL);
|
|
|
|
if (ent->ghoul2 && ent->client)
|
|
{
|
|
ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update.
|
|
}
|
|
|
|
if (g_gametype.integer == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR &&
|
|
client->sess.duelTeam == DUELTEAM_FREE)
|
|
{
|
|
SetTeam(ent, "s");
|
|
}
|
|
else
|
|
{
|
|
if (g_gametype.integer == GT_SIEGE && (!gSiegeRoundBegun || gSiegeRoundEnded))
|
|
{
|
|
SetTeamQuick(ent, TEAM_SPECTATOR, qfalse);
|
|
}
|
|
|
|
if ((ent->r.svFlags & SVF_BOT) &&
|
|
g_gametype.integer != GT_SIEGE)
|
|
{
|
|
char *saberVal = Info_ValueForKey(userinfo, "saber1");
|
|
char *saber2Val = Info_ValueForKey(userinfo, "saber2");
|
|
|
|
if (!saberVal || !saberVal[0])
|
|
{ //blah, set em up with a random saber
|
|
int r = rand()%50;
|
|
char sab1[1024];
|
|
char sab2[1024];
|
|
|
|
if (r <= 17)
|
|
{
|
|
strcpy(sab1, "Katarn");
|
|
strcpy(sab2, "none");
|
|
}
|
|
else if (r <= 34)
|
|
{
|
|
strcpy(sab1, "Katarn");
|
|
strcpy(sab2, "Katarn");
|
|
}
|
|
else
|
|
{
|
|
strcpy(sab1, "dual_1");
|
|
strcpy(sab2, "none");
|
|
}
|
|
G_SetSaber(ent, 0, sab1, qfalse);
|
|
G_SetSaber(ent, 0, sab2, qfalse);
|
|
Info_SetValueForKey( userinfo, "saber1", sab1 );
|
|
Info_SetValueForKey( userinfo, "saber2", sab2 );
|
|
trap_SetUserinfo( clientNum, userinfo );
|
|
}
|
|
else
|
|
{
|
|
G_SetSaber(ent, 0, saberVal, qfalse);
|
|
}
|
|
|
|
if (saberVal && saberVal[0] &&
|
|
(!saber2Val || !saber2Val[0]))
|
|
{
|
|
G_SetSaber(ent, 0, "none", qfalse);
|
|
Info_SetValueForKey( userinfo, "saber2", "none" );
|
|
trap_SetUserinfo( clientNum, userinfo );
|
|
}
|
|
else
|
|
{
|
|
G_SetSaber(ent, 0, saber2Val, qfalse);
|
|
}
|
|
}
|
|
|
|
// locate ent at a spawn point
|
|
ClientSpawn( ent );
|
|
}
|
|
|
|
if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
|
|
// send event
|
|
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
|
|
tent->s.clientNum = ent->s.clientNum;
|
|
|
|
if ( g_gametype.integer != GT_DUEL || g_gametype.integer == GT_POWERDUEL ) {
|
|
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLENTER")) );
|
|
}
|
|
}
|
|
G_LogPrintf( "ClientBegin: %i\n", clientNum );
|
|
|
|
// count current clients and rank for scoreboard
|
|
CalculateRanks();
|
|
|
|
G_ClearClientLog(clientNum);
|
|
}
|
|
|
|
static qboolean AllForceDisabled(int force)
|
|
{
|
|
int i;
|
|
|
|
if (force)
|
|
{
|
|
for (i=0;i<NUM_FORCE_POWERS;i++)
|
|
{
|
|
if (!(force & (1<<i)))
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//Convenient interface to set all my limb breakage stuff up -rww
|
|
void G_BreakArm(gentity_t *ent, int arm)
|
|
{
|
|
int anim = -1;
|
|
|
|
assert(ent && ent->client);
|
|
|
|
if (ent->s.NPC_class == CLASS_VEHICLE || ent->localAnimIndex > 1)
|
|
{ //no broken limbs for vehicles and non-humanoids
|
|
return;
|
|
}
|
|
|
|
if (!arm)
|
|
{ //repair him
|
|
ent->client->ps.brokenLimbs = 0;
|
|
return;
|
|
}
|
|
|
|
if (ent->client->ps.fd.saberAnimLevel == SS_STAFF)
|
|
{ //I'm too lazy to deal with this as well for now.
|
|
return;
|
|
}
|
|
|
|
if (arm == BROKENLIMB_LARM)
|
|
{
|
|
if (ent->client->saber[1].model[0] &&
|
|
ent->client->ps.weapon == WP_SABER &&
|
|
!ent->client->ps.saberHolstered &&
|
|
ent->client->saber[1].soundOff)
|
|
{ //the left arm shuts off its saber upon being broken
|
|
G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff);
|
|
}
|
|
}
|
|
|
|
ent->client->ps.brokenLimbs = 0; //make sure it's cleared out
|
|
ent->client->ps.brokenLimbs |= (1 << arm); //this arm is now marked as broken
|
|
|
|
//Do a pain anim based on the side. Since getting your arm broken does tend to hurt.
|
|
if (arm == BROKENLIMB_LARM)
|
|
{
|
|
anim = BOTH_PAIN2;
|
|
}
|
|
else if (arm == BROKENLIMB_RARM)
|
|
{
|
|
anim = BOTH_PAIN3;
|
|
}
|
|
|
|
if (anim == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
|
|
|
|
//This could be combined into a single event. But I guess limbs don't break often enough to
|
|
//worry about it.
|
|
G_EntitySound( ent, CHAN_VOICE, G_SoundIndex("*pain25.wav") );
|
|
//FIXME: A nice bone snapping sound instead if possible
|
|
G_Sound(ent, CHAN_AUTO, G_SoundIndex( va("sound/player/bodyfall_human%i.wav", Q_irand(1, 3)) ));
|
|
}
|
|
|
|
//Update the ghoul2 instance anims based on the playerstate values
|
|
#include "../namespace_begin.h"
|
|
qboolean BG_SaberStanceAnim( int anim );
|
|
qboolean PM_RunningAnim( int anim );
|
|
#include "../namespace_end.h"
|
|
void G_UpdateClientAnims(gentity_t *self, float animSpeedScale)
|
|
{
|
|
static int f;
|
|
static int torsoAnim;
|
|
static int legsAnim;
|
|
static int firstFrame, lastFrame;
|
|
static int aFlags;
|
|
static float animSpeed, lAnimSpeedScale;
|
|
qboolean setTorso = qfalse;
|
|
|
|
torsoAnim = (self->client->ps.torsoAnim);
|
|
legsAnim = (self->client->ps.legsAnim);
|
|
|
|
if (self->client->ps.saberLockFrame)
|
|
{
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "model_root", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "Motion", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
|
|
return;
|
|
}
|
|
|
|
if (self->localAnimIndex > 1 &&
|
|
bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame == 0 &&
|
|
bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames == 0)
|
|
{ //We'll allow this for non-humanoids.
|
|
goto tryTorso;
|
|
}
|
|
|
|
if (self->client->legsAnimExecute != legsAnim || self->client->legsLastFlip != self->client->ps.legsFlip)
|
|
{
|
|
animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[legsAnim].frameLerp;
|
|
lAnimSpeedScale = (animSpeed *= animSpeedScale);
|
|
|
|
if (bgAllAnims[self->localAnimIndex].anims[legsAnim].loopFrames != -1)
|
|
{
|
|
aFlags = BONE_ANIM_OVERRIDE_LOOP;
|
|
}
|
|
else
|
|
{
|
|
aFlags = BONE_ANIM_OVERRIDE_FREEZE;
|
|
}
|
|
|
|
if (animSpeed < 0)
|
|
{
|
|
lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame;
|
|
firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames;
|
|
}
|
|
else
|
|
{
|
|
firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame;
|
|
lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames;
|
|
}
|
|
|
|
aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on server position, but it's here just for the sake of matching them.
|
|
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "model_root", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
|
|
self->client->legsAnimExecute = legsAnim;
|
|
self->client->legsLastFlip = self->client->ps.legsFlip;
|
|
}
|
|
|
|
tryTorso:
|
|
if (self->localAnimIndex > 1 &&
|
|
bgAllAnims[self->localAnimIndex].anims[torsoAnim].firstFrame == 0 &&
|
|
bgAllAnims[self->localAnimIndex].anims[torsoAnim].numFrames == 0)
|
|
|
|
{ //If this fails as well just return.
|
|
return;
|
|
}
|
|
else if (self->s.number >= MAX_CLIENTS &&
|
|
self->s.NPC_class == CLASS_VEHICLE)
|
|
{ //we only want to set the root bone for vehicles
|
|
return;
|
|
}
|
|
|
|
if ((self->client->torsoAnimExecute != torsoAnim || self->client->torsoLastFlip != self->client->ps.torsoFlip) &&
|
|
!self->noLumbar)
|
|
{
|
|
aFlags = 0;
|
|
animSpeed = 0;
|
|
|
|
f = torsoAnim;
|
|
|
|
BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, f, &animSpeedScale, self->client->ps.brokenLimbs);
|
|
|
|
animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[f].frameLerp;
|
|
lAnimSpeedScale = (animSpeed *= animSpeedScale);
|
|
|
|
if (bgAllAnims[self->localAnimIndex].anims[f].loopFrames != -1)
|
|
{
|
|
aFlags = BONE_ANIM_OVERRIDE_LOOP;
|
|
}
|
|
else
|
|
{
|
|
aFlags = BONE_ANIM_OVERRIDE_FREEZE;
|
|
}
|
|
|
|
aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them.
|
|
|
|
if (animSpeed < 0)
|
|
{
|
|
lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame;
|
|
firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames;
|
|
}
|
|
else
|
|
{
|
|
firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame;
|
|
lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames;
|
|
}
|
|
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, /*firstFrame why was it this before?*/-1, 150);
|
|
|
|
self->client->torsoAnimExecute = torsoAnim;
|
|
self->client->torsoLastFlip = self->client->ps.torsoFlip;
|
|
|
|
setTorso = qtrue;
|
|
}
|
|
|
|
if (setTorso &&
|
|
self->localAnimIndex <= 1)
|
|
{ //only set the motion bone for humanoids.
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "Motion", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
|
|
}
|
|
|
|
#if 0 //disabled for now
|
|
if (self->client->ps.brokenLimbs != self->client->brokenLimbs ||
|
|
setTorso)
|
|
{
|
|
if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs &&
|
|
(self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
|
|
{ //broken left arm
|
|
char *brokenBone = "lhumerus";
|
|
animation_t *armAnim;
|
|
int armFirstFrame;
|
|
int armLastFrame;
|
|
int armFlags = 0;
|
|
float armAnimSpeed;
|
|
|
|
armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_DEAD21 ];
|
|
self->client->brokenLimbs = self->client->ps.brokenLimbs;
|
|
|
|
armFirstFrame = armAnim->firstFrame;
|
|
armLastFrame = armAnim->firstFrame+armAnim->numFrames;
|
|
armAnimSpeed = 50.0f / armAnim->frameLerp;
|
|
armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND);
|
|
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150);
|
|
}
|
|
else if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs &&
|
|
(self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)))
|
|
{ //broken right arm
|
|
char *brokenBone = "rhumerus";
|
|
char *supportBone = "lhumerus";
|
|
|
|
self->client->brokenLimbs = self->client->ps.brokenLimbs;
|
|
|
|
//Only put the arm in a broken pose if the anim is such that we
|
|
//want to allow it.
|
|
if ((//self->client->ps.weapon == WP_MELEE ||
|
|
self->client->ps.weapon != WP_SABER ||
|
|
BG_SaberStanceAnim(self->client->ps.torsoAnim) ||
|
|
PM_RunningAnim(self->client->ps.torsoAnim)) &&
|
|
(!self->client->saber[1].model[0] || self->client->ps.weapon != WP_SABER))
|
|
{
|
|
int armFirstFrame;
|
|
int armLastFrame;
|
|
int armFlags = 0;
|
|
float armAnimSpeed;
|
|
animation_t *armAnim;
|
|
|
|
if (self->client->ps.weapon == WP_MELEE ||
|
|
self->client->ps.weapon == WP_SABER ||
|
|
self->client->ps.weapon == WP_BRYAR_PISTOL)
|
|
{ //don't affect this arm if holding a gun, just make the other arm support it
|
|
armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_ATTACK2 ];
|
|
|
|
//armFirstFrame = armAnim->firstFrame;
|
|
armFirstFrame = armAnim->firstFrame+armAnim->numFrames;
|
|
armLastFrame = armAnim->firstFrame+armAnim->numFrames;
|
|
armAnimSpeed = 50.0f / armAnim->frameLerp;
|
|
armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND);
|
|
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150);
|
|
}
|
|
else
|
|
{ //we want to keep the broken bone updated for some cases
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
|
|
}
|
|
|
|
if (self->client->ps.torsoAnim != BOTH_MELEE1 &&
|
|
self->client->ps.torsoAnim != BOTH_MELEE2 &&
|
|
(self->client->ps.torsoAnim == TORSO_WEAPONREADY2 || self->client->ps.torsoAnim == BOTH_ATTACK2 || self->client->ps.weapon < WP_BRYAR_PISTOL))
|
|
{
|
|
//Now set the left arm to "support" the right one
|
|
armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_STAND2 ];
|
|
armFirstFrame = armAnim->firstFrame;
|
|
armLastFrame = armAnim->firstFrame+armAnim->numFrames;
|
|
armAnimSpeed = 50.0f / armAnim->frameLerp;
|
|
armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND);
|
|
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150);
|
|
}
|
|
else
|
|
{ //we want to keep the support bone updated for some cases
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
|
|
}
|
|
}
|
|
else
|
|
{ //otherwise, keep it set to the same as the torso
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
|
|
}
|
|
}
|
|
else if (self->client->brokenLimbs)
|
|
{ //remove the bone now so it can be set again
|
|
char *brokenBone = NULL;
|
|
int broken = 0;
|
|
|
|
//Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index
|
|
//(well, in theory, I haven't actually run into the problem)
|
|
if (self->client->brokenLimbs & (1<<BROKENLIMB_LARM))
|
|
{
|
|
brokenBone = "lhumerus";
|
|
broken |= (1<<BROKENLIMB_LARM);
|
|
}
|
|
else if (self->client->brokenLimbs & (1<<BROKENLIMB_RARM))
|
|
{ //can only have one arm broken at once.
|
|
brokenBone = "rhumerus";
|
|
broken |= (1<<BROKENLIMB_RARM);
|
|
|
|
//want to remove the support bone too then
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, "lhumerus", 0, 1, 0, 0, level.time, -1, 0);
|
|
trap_G2API_RemoveBone(self->ghoul2, "lhumerus", 0);
|
|
}
|
|
|
|
assert(brokenBone);
|
|
|
|
//Set the flags and stuff to 0, so that the remove will succeed
|
|
trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, 0, 1, 0, 0, level.time, -1, 0);
|
|
|
|
//Now remove it
|
|
trap_G2API_RemoveBone(self->ghoul2, brokenBone, 0);
|
|
self->client->brokenLimbs &= ~broken;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientSpawn
|
|
|
|
Called every time a client is placed fresh in the world:
|
|
after the first ClientBegin, and after each respawn
|
|
Initializes all non-persistant parts of playerState
|
|
============
|
|
*/
|
|
extern qboolean WP_HasForcePowers( const playerState_t *ps );
|
|
void ClientSpawn(gentity_t *ent) {
|
|
int index;
|
|
vec3_t spawn_origin, spawn_angles;
|
|
gclient_t *client;
|
|
int i;
|
|
clientPersistant_t saved;
|
|
clientSession_t savedSess;
|
|
int persistant[MAX_PERSISTANT];
|
|
gentity_t *spawnPoint;
|
|
int flags, gameFlags;
|
|
int savedPing;
|
|
int accuracy_hits, accuracy_shots;
|
|
int eventSequence;
|
|
char userinfo[MAX_INFO_STRING];
|
|
forcedata_t savedForce;
|
|
int saveSaberNum = ENTITYNUM_NONE;
|
|
int wDisable = 0;
|
|
int savedSiegeIndex = 0;
|
|
int maxHealth;
|
|
saberInfo_t saberSaved[MAX_SABERS];
|
|
int l = 0;
|
|
void *g2WeaponPtrs[MAX_SABERS];
|
|
char *value;
|
|
char *saber;
|
|
qboolean changedSaber = qfalse;
|
|
qboolean inSiegeWithClass = qfalse;
|
|
|
|
index = ent - g_entities;
|
|
client = ent->client;
|
|
|
|
//first we want the userinfo so we can see if we should update this client's saber -rww
|
|
trap_GetUserinfo( index, userinfo, sizeof(userinfo) );
|
|
while (l < MAX_SABERS)
|
|
{
|
|
switch (l)
|
|
{
|
|
case 0:
|
|
saber = &ent->client->sess.saberType[0];
|
|
break;
|
|
case 1:
|
|
saber = &ent->client->sess.saber2Type[0];
|
|
break;
|
|
default:
|
|
saber = NULL;
|
|
break;
|
|
}
|
|
|
|
value = Info_ValueForKey (userinfo, va("saber%i", l+1));
|
|
if (saber &&
|
|
value &&
|
|
(Q_stricmp(value, saber) || !saber[0] || !ent->client->saber[0].model[0]))
|
|
{ //doesn't match up (or our session saber is BS), we want to try setting it
|
|
if (G_SetSaber(ent, l, value, qfalse))
|
|
{
|
|
changedSaber = qtrue;
|
|
}
|
|
else if (!saber[0] || !ent->client->saber[0].model[0])
|
|
{ //Well, we still want to say they changed then (it means this is siege and we have some overrides)
|
|
changedSaber = qtrue;
|
|
}
|
|
}
|
|
l++;
|
|
}
|
|
|
|
if (changedSaber)
|
|
{ //make sure our new info is sent out to all the other clients, and give us a valid stance
|
|
ClientUserinfoChanged( ent->s.number );
|
|
|
|
//make sure the saber models are updated
|
|
G_SaberModelSetup(ent);
|
|
|
|
l = 0;
|
|
while (l < MAX_SABERS)
|
|
{ //go through and make sure both sabers match the userinfo
|
|
switch (l)
|
|
{
|
|
case 0:
|
|
saber = &ent->client->sess.saberType[0];
|
|
break;
|
|
case 1:
|
|
saber = &ent->client->sess.saber2Type[0];
|
|
break;
|
|
default:
|
|
saber = NULL;
|
|
break;
|
|
}
|
|
|
|
value = Info_ValueForKey (userinfo, va("saber%i", l+1));
|
|
|
|
if (Q_stricmp(value, saber))
|
|
{ //they don't match up, force the user info
|
|
Info_SetValueForKey(userinfo, va("saber%i", l+1), saber);
|
|
trap_SetUserinfo( ent->s.number, userinfo );
|
|
}
|
|
l++;
|
|
}
|
|
|
|
if (ent->client->saber[0].model[0] &&
|
|
ent->client->saber[1].model[0])
|
|
{ //dual
|
|
ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_DUAL;
|
|
}
|
|
else if ((ent->client->saber[0].saberFlags&SFL_TWO_HANDED))
|
|
{ //staff
|
|
ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_STAFF;
|
|
}
|
|
else
|
|
{
|
|
if (ent->client->sess.saberLevel < SS_FAST)
|
|
{
|
|
ent->client->sess.saberLevel = SS_FAST;
|
|
}
|
|
else if (ent->client->sess.saberLevel > SS_STRONG)
|
|
{
|
|
ent->client->sess.saberLevel = SS_STRONG;
|
|
}
|
|
ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel;
|
|
|
|
if (g_gametype.integer != GT_SIEGE &&
|
|
ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
|
|
{
|
|
ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
|
|
}
|
|
}
|
|
if ( g_gametype.integer != GT_SIEGE )
|
|
{
|
|
//let's just make sure the styles we chose are cool
|
|
if ( !WP_SaberStyleValidForSaber( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, ent->client->ps.fd.saberAnimLevel ) )
|
|
{
|
|
WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &ent->client->ps.fd.saberAnimLevel );
|
|
ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = ent->client->ps.fd.saberAnimLevel;
|
|
}
|
|
}
|
|
}
|
|
l = 0;
|
|
|
|
if (client->ps.fd.forceDoInit)
|
|
{ //force a reread of force powers
|
|
WP_InitForcePowers( ent );
|
|
client->ps.fd.forceDoInit = 0;
|
|
}
|
|
|
|
if (ent->client->ps.fd.saberAnimLevel != SS_STAFF &&
|
|
ent->client->ps.fd.saberAnimLevel != SS_DUAL &&
|
|
ent->client->ps.fd.saberAnimLevel == ent->client->ps.fd.saberDrawAnimLevel &&
|
|
ent->client->ps.fd.saberAnimLevel == ent->client->sess.saberLevel)
|
|
{
|
|
if (ent->client->sess.saberLevel < SS_FAST)
|
|
{
|
|
ent->client->sess.saberLevel = SS_FAST;
|
|
}
|
|
else if (ent->client->sess.saberLevel > SS_STRONG)
|
|
{
|
|
ent->client->sess.saberLevel = SS_STRONG;
|
|
}
|
|
ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel;
|
|
|
|
if (g_gametype.integer != GT_SIEGE &&
|
|
ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
|
|
{
|
|
ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
|
|
}
|
|
}
|
|
|
|
// find a spawn point
|
|
// do it before setting health back up, so farthest
|
|
// ranging doesn't count this client
|
|
if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
|
|
spawnPoint = SelectSpectatorSpawnPoint (
|
|
spawn_origin, spawn_angles);
|
|
} else if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) {
|
|
// all base oriented team games use the CTF spawn points
|
|
spawnPoint = SelectCTFSpawnPoint (
|
|
client->sess.sessionTeam,
|
|
client->pers.teamState.state,
|
|
spawn_origin, spawn_angles);
|
|
}
|
|
else if (g_gametype.integer == GT_SIEGE)
|
|
{
|
|
spawnPoint = SelectSiegeSpawnPoint (
|
|
client->siegeClass,
|
|
client->sess.sessionTeam,
|
|
client->pers.teamState.state,
|
|
spawn_origin, spawn_angles);
|
|
}
|
|
else {
|
|
do {
|
|
if (g_gametype.integer == GT_POWERDUEL)
|
|
{
|
|
spawnPoint = SelectDuelSpawnPoint(client->sess.duelTeam, client->ps.origin, spawn_origin, spawn_angles);
|
|
}
|
|
else if (g_gametype.integer == GT_DUEL)
|
|
{ // duel
|
|
spawnPoint = SelectDuelSpawnPoint(DUELTEAM_SINGLE, client->ps.origin, spawn_origin, spawn_angles);
|
|
}
|
|
else
|
|
{
|
|
// the first spawn should be at a good looking spot
|
|
if ( !client->pers.initialSpawn && client->pers.localClient ) {
|
|
client->pers.initialSpawn = qtrue;
|
|
spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles, client->sess.sessionTeam );
|
|
} else {
|
|
// don't spawn near existing origin if possible
|
|
spawnPoint = SelectSpawnPoint (
|
|
client->ps.origin,
|
|
spawn_origin, spawn_angles, client->sess.sessionTeam );
|
|
}
|
|
}
|
|
|
|
// Tim needs to prevent bots from spawning at the initial point
|
|
// on q3dm0...
|
|
if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) {
|
|
continue; // try again
|
|
}
|
|
// just to be symetric, we have a nohumans option...
|
|
if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) {
|
|
continue; // try again
|
|
}
|
|
|
|
break;
|
|
|
|
} while ( 1 );
|
|
}
|
|
client->pers.teamState.state = TEAM_ACTIVE;
|
|
|
|
// toggle the teleport bit so the client knows to not lerp
|
|
// and never clear the voted flag
|
|
flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT );
|
|
flags ^= EF_TELEPORT_BIT;
|
|
gameFlags = ent->client->mGameFlags & ( PSG_VOTED | PSG_TEAMVOTED);
|
|
|
|
// clear everything but the persistant data
|
|
|
|
saved = client->pers;
|
|
savedSess = client->sess;
|
|
savedPing = client->ps.ping;
|
|
// savedAreaBits = client->areabits;
|
|
accuracy_hits = client->accuracy_hits;
|
|
accuracy_shots = client->accuracy_shots;
|
|
for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
|
|
persistant[i] = client->ps.persistant[i];
|
|
}
|
|
eventSequence = client->ps.eventSequence;
|
|
|
|
savedForce = client->ps.fd;
|
|
|
|
saveSaberNum = client->ps.saberEntityNum;
|
|
|
|
savedSiegeIndex = client->siegeClass;
|
|
|
|
l = 0;
|
|
while (l < MAX_SABERS)
|
|
{
|
|
saberSaved[l] = client->saber[l];
|
|
g2WeaponPtrs[l] = client->weaponGhoul2[l];
|
|
l++;
|
|
}
|
|
|
|
i = 0;
|
|
while (i < HL_MAX)
|
|
{
|
|
ent->locationDamage[i] = 0;
|
|
i++;
|
|
}
|
|
|
|
memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset?
|
|
client->bodyGrabIndex = ENTITYNUM_NONE;
|
|
|
|
//Get the skin RGB based on his userinfo
|
|
value = Info_ValueForKey (userinfo, "char_color_red");
|
|
if (value)
|
|
{
|
|
client->ps.customRGBA[0] = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
client->ps.customRGBA[0] = 255;
|
|
}
|
|
|
|
value = Info_ValueForKey (userinfo, "char_color_green");
|
|
if (value)
|
|
{
|
|
client->ps.customRGBA[1] = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
client->ps.customRGBA[1] = 255;
|
|
}
|
|
|
|
value = Info_ValueForKey (userinfo, "char_color_blue");
|
|
if (value)
|
|
{
|
|
client->ps.customRGBA[2] = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
client->ps.customRGBA[2] = 255;
|
|
}
|
|
|
|
if ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100)
|
|
{ //hmm, too dark!
|
|
client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255;
|
|
}
|
|
|
|
client->ps.customRGBA[3]=255;
|
|
|
|
client->siegeClass = savedSiegeIndex;
|
|
|
|
l = 0;
|
|
while (l < MAX_SABERS)
|
|
{
|
|
client->saber[l] = saberSaved[l];
|
|
client->weaponGhoul2[l] = g2WeaponPtrs[l];
|
|
l++;
|
|
}
|
|
|
|
//or the saber ent num
|
|
client->ps.saberEntityNum = saveSaberNum;
|
|
client->saberStoredIndex = saveSaberNum;
|
|
|
|
client->ps.fd = savedForce;
|
|
|
|
client->ps.duelIndex = ENTITYNUM_NONE;
|
|
|
|
//spawn with 100
|
|
client->ps.jetpackFuel = 100;
|
|
client->ps.cloakFuel = 100;
|
|
|
|
client->pers = saved;
|
|
client->sess = savedSess;
|
|
client->ps.ping = savedPing;
|
|
// client->areabits = savedAreaBits;
|
|
client->accuracy_hits = accuracy_hits;
|
|
client->accuracy_shots = accuracy_shots;
|
|
client->lastkilled_client = -1;
|
|
|
|
for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
|
|
client->ps.persistant[i] = persistant[i];
|
|
}
|
|
client->ps.eventSequence = eventSequence;
|
|
// increment the spawncount so the client will detect the respawn
|
|
client->ps.persistant[PERS_SPAWN_COUNT]++;
|
|
client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
|
|
|
|
client->airOutTime = level.time + 12000;
|
|
|
|
// set max health
|
|
if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1)
|
|
{
|
|
siegeClass_t *scl = &bgSiegeClasses[client->siegeClass];
|
|
maxHealth = 100;
|
|
|
|
if (scl->maxhealth)
|
|
{
|
|
maxHealth = scl->maxhealth;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
maxHealth = 100;
|
|
}
|
|
client->pers.maxHealth = maxHealth;//atoi( Info_ValueForKey( userinfo, "handicap" ) );
|
|
if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) {
|
|
client->pers.maxHealth = 100;
|
|
}
|
|
// clear entity values
|
|
client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
|
|
client->ps.eFlags = flags;
|
|
client->mGameFlags = gameFlags;
|
|
|
|
ent->s.groundEntityNum = ENTITYNUM_NONE;
|
|
ent->client = &level.clients[index];
|
|
ent->playerState = &ent->client->ps;
|
|
ent->takedamage = qtrue;
|
|
ent->inuse = qtrue;
|
|
ent->classname = "player";
|
|
ent->r.contents = CONTENTS_BODY;
|
|
ent->clipmask = MASK_PLAYERSOLID;
|
|
ent->die = player_die;
|
|
ent->waterlevel = 0;
|
|
ent->watertype = 0;
|
|
ent->flags = 0;
|
|
|
|
VectorCopy (playerMins, ent->r.mins);
|
|
VectorCopy (playerMaxs, ent->r.maxs);
|
|
client->ps.crouchheight = CROUCH_MAXS_2;
|
|
client->ps.standheight = DEFAULT_MAXS_2;
|
|
|
|
client->ps.clientNum = index;
|
|
//give default weapons
|
|
client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
|
|
|
|
if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
|
|
{
|
|
wDisable = g_duelWeaponDisable.integer;
|
|
}
|
|
else
|
|
{
|
|
wDisable = g_weaponDisable.integer;
|
|
}
|
|
|
|
|
|
|
|
if ( g_gametype.integer != GT_HOLOCRON
|
|
&& g_gametype.integer != GT_JEDIMASTER
|
|
&& !HasSetSaberOnly()
|
|
&& !AllForceDisabled( g_forcePowerDisable.integer )
|
|
&& g_trueJedi.integer )
|
|
{
|
|
if ( g_gametype.integer >= GT_TEAM && (client->sess.sessionTeam == TEAM_BLUE || client->sess.sessionTeam == TEAM_RED) )
|
|
{//In Team games, force one side to be merc and other to be jedi
|
|
if ( level.numPlayingClients > 0 )
|
|
{//already someone in the game
|
|
int i, forceTeam = TEAM_SPECTATOR;
|
|
for ( i = 0 ; i < level.maxclients ; i++ )
|
|
{
|
|
if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
|
|
continue;
|
|
}
|
|
if ( level.clients[i].sess.sessionTeam == TEAM_BLUE || level.clients[i].sess.sessionTeam == TEAM_RED )
|
|
{//in-game
|
|
if ( WP_HasForcePowers( &level.clients[i].ps ) )
|
|
{//this side is using force
|
|
forceTeam = level.clients[i].sess.sessionTeam;
|
|
}
|
|
else
|
|
{//other team is using force
|
|
if ( level.clients[i].sess.sessionTeam == TEAM_BLUE )
|
|
{
|
|
forceTeam = TEAM_RED;
|
|
}
|
|
else
|
|
{
|
|
forceTeam = TEAM_BLUE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if ( WP_HasForcePowers( &client->ps ) && client->sess.sessionTeam != forceTeam )
|
|
{//using force but not on right team, switch him over
|
|
const char *teamName = TeamName( forceTeam );
|
|
//client->sess.sessionTeam = forceTeam;
|
|
SetTeam( ent, (char *)teamName );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( WP_HasForcePowers( &client->ps ) )
|
|
{
|
|
client->ps.trueNonJedi = qfalse;
|
|
client->ps.trueJedi = qtrue;
|
|
//make sure they only use the saber
|
|
client->ps.weapon = WP_SABER;
|
|
client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER);
|
|
}
|
|
else
|
|
{//no force powers set
|
|
client->ps.trueNonJedi = qtrue;
|
|
client->ps.trueJedi = qfalse;
|
|
if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL)))
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );
|
|
}
|
|
if (!wDisable || !(wDisable & (1 << WP_BLASTER)))
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER );
|
|
}
|
|
if (!wDisable || !(wDisable & (1 << WP_BOWCASTER)))
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BOWCASTER );
|
|
}
|
|
client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
|
|
client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE);
|
|
client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
|
|
client->ps.weapon = WP_BRYAR_PISTOL;
|
|
}
|
|
}
|
|
else
|
|
{//jediVmerc is incompatible with this gametype, turn it off!
|
|
trap_Cvar_Set( "g_jediVmerc", "0" );
|
|
if (g_gametype.integer == GT_HOLOCRON)
|
|
{
|
|
//always get free saber level 1 in holocron
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems()
|
|
}
|
|
else
|
|
{
|
|
if (client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems()
|
|
}
|
|
else
|
|
{ //if you don't have saber attack rank then you don't get a saber
|
|
client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE);
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer != GT_SIEGE)
|
|
{
|
|
if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL)))
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );
|
|
}
|
|
else if (g_gametype.integer == GT_JEDIMASTER)
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer == GT_JEDIMASTER)
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
|
|
client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE);
|
|
}
|
|
|
|
if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER))
|
|
{
|
|
client->ps.weapon = WP_SABER;
|
|
}
|
|
else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL))
|
|
{
|
|
client->ps.weapon = WP_BRYAR_PISTOL;
|
|
}
|
|
else
|
|
{
|
|
client->ps.weapon = WP_MELEE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
client->ps.stats[STAT_HOLDABLE_ITEMS] |= ( 1 << HI_BINOCULARS );
|
|
client->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_BINOCULARS, IT_HOLDABLE);
|
|
*/
|
|
|
|
if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1 &&
|
|
client->sess.sessionTeam != TEAM_SPECTATOR)
|
|
{ //well then, we will use a custom weaponset for our class
|
|
int m = 0;
|
|
|
|
client->ps.stats[STAT_WEAPONS] = bgSiegeClasses[client->siegeClass].weapons;
|
|
|
|
if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER))
|
|
{
|
|
client->ps.weapon = WP_SABER;
|
|
}
|
|
else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL))
|
|
{
|
|
client->ps.weapon = WP_BRYAR_PISTOL;
|
|
}
|
|
else
|
|
{
|
|
client->ps.weapon = WP_MELEE;
|
|
}
|
|
inSiegeWithClass = qtrue;
|
|
|
|
while (m < WP_NUM_WEAPONS)
|
|
{
|
|
if (client->ps.stats[STAT_WEAPONS] & (1 << m))
|
|
{
|
|
if (client->ps.weapon != WP_SABER)
|
|
{ //try to find the highest ranking weapon we have
|
|
if (m > client->ps.weapon)
|
|
{
|
|
client->ps.weapon = m;
|
|
}
|
|
}
|
|
|
|
if (m >= WP_BRYAR_PISTOL)
|
|
{ //Max his ammo out for all the weapons he has.
|
|
if ( g_gametype.integer == GT_SIEGE
|
|
&& m == WP_ROCKET_LAUNCHER )
|
|
{//don't give full ammo!
|
|
//FIXME: extern this and check it when getting ammo from supplier, pickups or ammo stations!
|
|
if ( client->siegeClass != -1 &&
|
|
(bgSiegeClasses[client->siegeClass].classflags & (1<<CFL_SINGLE_ROCKET)) )
|
|
{
|
|
client->ps.ammo[weaponData[m].ammoIndex] = 1;
|
|
}
|
|
else
|
|
{
|
|
client->ps.ammo[weaponData[m].ammoIndex] = 10;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( g_gametype.integer == GT_SIEGE
|
|
&& client->siegeClass != -1
|
|
&& (bgSiegeClasses[client->siegeClass].classflags & (1<<CFL_EXTRA_AMMO)) )
|
|
{//double ammo
|
|
client->ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max*2;
|
|
client->ps.eFlags |= EF_DOUBLE_AMMO;
|
|
}
|
|
else
|
|
{
|
|
client->ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m++;
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
client->siegeClass != -1 &&
|
|
client->sess.sessionTeam != TEAM_SPECTATOR)
|
|
{ //use class-specified inventory
|
|
client->ps.stats[STAT_HOLDABLE_ITEMS] = bgSiegeClasses[client->siegeClass].invenItems;
|
|
client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
|
|
}
|
|
else
|
|
{
|
|
client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
|
|
client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
client->siegeClass != -1 &&
|
|
bgSiegeClasses[client->siegeClass].powerups &&
|
|
client->sess.sessionTeam != TEAM_SPECTATOR)
|
|
{ //this class has some start powerups
|
|
i = 0;
|
|
while (i < PW_NUM_POWERUPS)
|
|
{
|
|
if (bgSiegeClasses[client->siegeClass].powerups & (1 << i))
|
|
{
|
|
client->ps.powerups[i] = Q3_INFINITE;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if ( client->sess.sessionTeam == TEAM_SPECTATOR )
|
|
{
|
|
client->ps.stats[STAT_WEAPONS] = 0;
|
|
client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
|
|
client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
|
|
}
|
|
|
|
// nmckenzie: DESERT_SIEGE... or well, siege generally. This was over-writing the max value, which was NOT good for siege.
|
|
if ( inSiegeWithClass == qfalse )
|
|
{
|
|
client->ps.ammo[AMMO_BLASTER] = 100; //ammoData[AMMO_BLASTER].max; //100 seems fair.
|
|
}
|
|
// client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
|
|
// client->ps.ammo[AMMO_FORCE] = ammoData[AMMO_FORCE].max;
|
|
// client->ps.ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max;
|
|
// client->ps.ammo[AMMO_ROCKETS] = ammoData[AMMO_ROCKETS].max;
|
|
/*
|
|
client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BRYAR_PISTOL);
|
|
if ( g_gametype.integer == GT_TEAM ) {
|
|
client->ps.ammo[WP_BRYAR_PISTOL] = 50;
|
|
} else {
|
|
client->ps.ammo[WP_BRYAR_PISTOL] = 100;
|
|
}
|
|
*/
|
|
client->ps.rocketLockIndex = ENTITYNUM_NONE;
|
|
client->ps.rocketLockTime = 0;
|
|
|
|
//rww - Set here to initialize the circling seeker drone to off.
|
|
//A quick note about this so I don't forget how it works again:
|
|
//ps.genericEnemyIndex is kept in sync between the server and client.
|
|
//When it gets set then an entitystate value of the same name gets
|
|
//set along with an entitystate flag in the shared bg code. Which
|
|
//is why a value needs to be both on the player state and entity state.
|
|
//(it doesn't seem to just carry over the entitystate value automatically
|
|
//because entity state value is derived from player state data or some
|
|
//such)
|
|
client->ps.genericEnemyIndex = -1;
|
|
|
|
client->ps.isJediMaster = qfalse;
|
|
|
|
if (client->ps.fallingToDeath)
|
|
{
|
|
client->ps.fallingToDeath = 0;
|
|
client->noCorpse = qtrue;
|
|
}
|
|
|
|
//Do per-spawn force power initialization
|
|
WP_SpawnInitForcePowers( ent );
|
|
|
|
// health will count down towards max_health
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
client->siegeClass != -1 &&
|
|
bgSiegeClasses[client->siegeClass].starthealth)
|
|
{ //class specifies a start health, so use it
|
|
ent->health = client->ps.stats[STAT_HEALTH] = bgSiegeClasses[client->siegeClass].starthealth;
|
|
}
|
|
else if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL )
|
|
{//only start with 100 health in Duel
|
|
if ( g_gametype.integer == GT_POWERDUEL && client->sess.duelTeam == DUELTEAM_LONE )
|
|
{
|
|
if ( g_duel_fraglimit.integer )
|
|
{
|
|
|
|
ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] =
|
|
g_powerDuelStartHealth.integer - ((g_powerDuelStartHealth.integer - g_powerDuelEndHealth.integer) * (float)client->sess.wins / (float)g_duel_fraglimit.integer);
|
|
}
|
|
else
|
|
{
|
|
ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 150;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 100;
|
|
}
|
|
}
|
|
else if (client->ps.stats[STAT_MAX_HEALTH] <= 100)
|
|
{
|
|
ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25;
|
|
}
|
|
else if (client->ps.stats[STAT_MAX_HEALTH] < 125)
|
|
{
|
|
ent->health = client->ps.stats[STAT_HEALTH] = 125;
|
|
}
|
|
else
|
|
{
|
|
ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
|
|
// Start with a small amount of armor as well.
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
client->siegeClass != -1 /*&&
|
|
bgSiegeClasses[client->siegeClass].startarmor*/)
|
|
{ //class specifies a start armor amount, so use it
|
|
client->ps.stats[STAT_ARMOR] = bgSiegeClasses[client->siegeClass].startarmor;
|
|
}
|
|
else if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL )
|
|
{//no armor in duel
|
|
client->ps.stats[STAT_ARMOR] = 0;
|
|
}
|
|
else
|
|
{
|
|
client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 0.25;
|
|
}
|
|
|
|
G_SetOrigin( ent, spawn_origin );
|
|
VectorCopy( spawn_origin, client->ps.origin );
|
|
|
|
// the respawned flag will be cleared after the attack and jump keys come up
|
|
client->ps.pm_flags |= PMF_RESPAWNED;
|
|
|
|
trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
|
|
SetClientViewAngle( ent, spawn_angles );
|
|
|
|
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
|
|
|
|
} else {
|
|
G_KillBox( ent );
|
|
trap_LinkEntity (ent);
|
|
|
|
// force the base weapon up
|
|
//client->ps.weapon = WP_BRYAR_PISTOL;
|
|
//client->ps.weaponstate = FIRST_WEAPON;
|
|
if (client->ps.weapon <= WP_NONE)
|
|
{
|
|
client->ps.weapon = WP_BRYAR_PISTOL;
|
|
}
|
|
|
|
client->ps.torsoTimer = client->ps.legsTimer = 0;
|
|
|
|
if (client->ps.weapon == WP_SABER)
|
|
{
|
|
G_SetAnim(ent, NULL, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0);
|
|
}
|
|
else
|
|
{
|
|
G_SetAnim(ent, NULL, SETANIM_TORSO, TORSO_RAISEWEAP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0);
|
|
client->ps.legsAnim = WeaponReadyAnim[client->ps.weapon];
|
|
}
|
|
client->ps.weaponstate = WEAPON_RAISING;
|
|
client->ps.weaponTime = client->ps.torsoTimer;
|
|
}
|
|
|
|
// don't allow full run speed for a bit
|
|
client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
|
|
client->ps.pm_time = 100;
|
|
|
|
client->respawnTime = level.time;
|
|
client->inactivityTime = level.time + g_inactivity.integer * 1000;
|
|
client->latched_buttons = 0;
|
|
|
|
if ( level.intermissiontime ) {
|
|
MoveClientToIntermission( ent );
|
|
} else {
|
|
// fire the targets of the spawn point
|
|
G_UseTargets( spawnPoint, ent );
|
|
|
|
// select the highest weapon number available, after any
|
|
// spawn given items have fired
|
|
/*
|
|
client->ps.weapon = 1;
|
|
for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
|
|
if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
|
|
client->ps.weapon = i;
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
//set teams for NPCs to recognize
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{ //Imperial (team1) team is allied with "enemy" NPCs in this mode
|
|
if (client->sess.sessionTeam == SIEGETEAM_TEAM1)
|
|
{
|
|
client->playerTeam = ent->s.teamowner = NPCTEAM_ENEMY;
|
|
client->enemyTeam = NPCTEAM_PLAYER;
|
|
}
|
|
else
|
|
{
|
|
client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER;
|
|
client->enemyTeam = NPCTEAM_ENEMY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER;
|
|
client->enemyTeam = NPCTEAM_ENEMY;
|
|
}
|
|
|
|
/*
|
|
//scaling for the power duel opponent
|
|
if (g_gametype.integer == GT_POWERDUEL &&
|
|
client->sess.duelTeam == DUELTEAM_LONE)
|
|
{
|
|
client->ps.iModelScale = 125;
|
|
VectorSet(ent->modelScale, 1.25f, 1.25f, 1.25f);
|
|
}
|
|
*/
|
|
//Disabled. At least for now. Not sure if I'll want to do it or not eventually.
|
|
|
|
// run a client frame to drop exactly to the floor,
|
|
// initialize animations and other things
|
|
client->ps.commandTime = level.time - 100;
|
|
ent->client->pers.cmd.serverTime = level.time;
|
|
ClientThink( ent-g_entities, NULL );
|
|
|
|
// positively link the client, even if the command times are weird
|
|
if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
|
|
BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
|
|
VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
|
|
trap_LinkEntity( ent );
|
|
}
|
|
|
|
if (g_spawnInvulnerability.integer)
|
|
{
|
|
ent->client->ps.eFlags |= EF_INVULNERABLE;
|
|
ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer;
|
|
}
|
|
|
|
// run the presend to set anything else
|
|
ClientEndFrame( ent );
|
|
|
|
// clear entity state values
|
|
BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
|
|
|
|
//rww - make sure client has a valid icarus instance
|
|
trap_ICARUS_FreeEnt( ent );
|
|
trap_ICARUS_InitEnt( ent );
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
ClientDisconnect
|
|
|
|
Called when a player drops from the server.
|
|
Will not be called between levels.
|
|
|
|
This should NOT be called directly by any game logic,
|
|
call trap_DropClient(), which will call this and do
|
|
server system housekeeping.
|
|
============
|
|
*/
|
|
void ClientDisconnect( int clientNum ) {
|
|
gentity_t *ent;
|
|
gentity_t *tent;
|
|
int i;
|
|
|
|
// cleanup if we are kicking a bot that
|
|
// hasn't spawned yet
|
|
G_RemoveQueuedBotBegin( clientNum );
|
|
|
|
ent = g_entities + clientNum;
|
|
if ( !ent->client ) {
|
|
return;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if (ent->client->ps.fd.forcePowersActive & (1 << i))
|
|
{
|
|
WP_ForcePowerStop(ent, i);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
i = TRACK_CHANNEL_1;
|
|
|
|
while (i < NUM_TRACK_CHANNELS)
|
|
{
|
|
if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0)
|
|
{
|
|
G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE);
|
|
}
|
|
i++;
|
|
}
|
|
i = 0;
|
|
|
|
if (ent->client->ps.m_iVehicleNum)
|
|
{ //tell it I'm getting off
|
|
gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum];
|
|
|
|
if (veh->inuse && veh->client && veh->m_pVehicle)
|
|
{
|
|
int pCon = ent->client->pers.connected;
|
|
|
|
ent->client->pers.connected = 0;
|
|
veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue);
|
|
ent->client->pers.connected = pCon;
|
|
}
|
|
}
|
|
|
|
// stop any following clients
|
|
for ( i = 0 ; i < level.maxclients ; i++ ) {
|
|
if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR
|
|
&& level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW
|
|
&& level.clients[i].sess.spectatorClient == clientNum ) {
|
|
StopFollowing( &g_entities[i] );
|
|
}
|
|
}
|
|
|
|
// send effect if they were completely connected
|
|
if ( ent->client->pers.connected == CON_CONNECTED
|
|
&& ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
|
|
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
|
|
tent->s.clientNum = ent->s.clientNum;
|
|
|
|
// They don't get to take powerups with them!
|
|
// Especially important for stuff like CTF flags
|
|
TossClientItems( ent );
|
|
}
|
|
|
|
G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
|
|
G_LogPrintf( "%s disconnected with IP: %s\n", ent->client->pers.netname, ent->client->sess.IPstring );
|
|
|
|
// if we are playing in tourney mode, give a win to the other player and clear his frags for this round
|
|
if ( (g_gametype.integer == GT_DUEL )
|
|
&& !level.intermissiontime
|
|
&& !level.warmupTime ) {
|
|
if ( level.sortedClients[1] == clientNum ) {
|
|
level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] = 0;
|
|
level.clients[ level.sortedClients[0] ].sess.wins++;
|
|
ClientUserinfoChanged( level.sortedClients[0] );
|
|
}
|
|
else if ( level.sortedClients[0] == clientNum ) {
|
|
level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] = 0;
|
|
level.clients[ level.sortedClients[1] ].sess.wins++;
|
|
ClientUserinfoChanged( level.sortedClients[1] );
|
|
}
|
|
}
|
|
|
|
if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2))
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&ent->ghoul2);
|
|
}
|
|
i = 0;
|
|
while (i < MAX_SABERS)
|
|
{
|
|
if (ent->client->weaponGhoul2[i] && trap_G2_HaveWeGhoul2Models(ent->client->weaponGhoul2[i]))
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&ent->client->weaponGhoul2[i]);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
trap_UnlinkEntity (ent);
|
|
ent->s.modelindex = 0;
|
|
ent->inuse = qfalse;
|
|
ent->classname = "disconnected";
|
|
ent->client->pers.connected = CON_DISCONNECTED;
|
|
ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
|
|
ent->client->sess.sessionTeam = TEAM_FREE;
|
|
ent->r.contents = 0;
|
|
|
|
trap_SetConfigstring( CS_PLAYERS + clientNum, "");
|
|
|
|
CalculateRanks();
|
|
|
|
if ( ent->r.svFlags & SVF_BOT ) {
|
|
BotAIShutdownClient( clientNum, qfalse );
|
|
}
|
|
|
|
G_ClearClientLog(clientNum);
|
|
}
|
|
|
|
|