mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
95295401a4
Added weapon balancing and tech control cvars to 3ZB2 DLL. Added Vampire and Ammogen techs to 3ZB2 game DLL. Fixed func_door_secret movement bug (when built with origin brush) in Zaero DLL. Fixed armor stacking bug in default Lazarus, missionpack, and Zaero DLLs. Removed unused tech cvars in default Lazarus DLL. Changed parsing of TE_BLASTER_COLORED tempent.
3817 lines
85 KiB
C
3817 lines
85 KiB
C
// p_client.c
|
|
|
|
#include "g_local.h"
|
|
#include "m_player.h"
|
|
|
|
|
|
/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
|
|
The normal starting point for a level.
|
|
*/
|
|
void SP_info_player_start(edict_t *self)
|
|
{
|
|
//CW++
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->style = ENT_ID_PLAYER_SPAWN;
|
|
//CW--
|
|
}
|
|
|
|
/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
|
|
potential spawning position for deathmatch games
|
|
*/
|
|
void SP_info_player_deathmatch(edict_t *self)
|
|
{
|
|
//CW++
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->style = ENT_ID_PLAYER_SPAWN;
|
|
//CW--
|
|
}
|
|
|
|
/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
|
|
We don't use these for Awakening II.
|
|
*/
|
|
void SP_info_player_coop(edict_t *self)
|
|
{
|
|
G_FreeEdict(self);
|
|
}
|
|
|
|
|
|
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
|
|
The deathmatch intermission point will be at one of these
|
|
Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
|
|
*/
|
|
void SP_info_player_intermission(edict_t *self)
|
|
{
|
|
//CW++
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->style = ENT_ID_INTERMISSION;
|
|
//CW--
|
|
}
|
|
|
|
|
|
//CW++
|
|
/*QUAKED info_player_attack (1 0.3 0.3) (-16 -16 -24) (16 16 32) NoModel
|
|
ASSAULT: Potential spawning position for a player on the attacking team.
|
|
*/
|
|
void SP_info_player_attack(edict_t *self)
|
|
{
|
|
if (sv_gametype->value != G_ASLT)
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if (self->count < 0)
|
|
self->count = 0;
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->style = ENT_ID_PLAYER_SPAWN;
|
|
}
|
|
|
|
/*QUAKED info_player_defend (0.3 1 0.3) (-16 -16 -24) (16 16 32) NoModel
|
|
ASSAULT: Potential spawning position for a player on the defending team.
|
|
*/
|
|
void SP_info_player_defend(edict_t *self)
|
|
{
|
|
if (sv_gametype->value != G_ASLT)
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if (self->count < 0)
|
|
self->count = 0;
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->style = ENT_ID_PLAYER_SPAWN;
|
|
}
|
|
//CW--
|
|
|
|
//=======================================================================
|
|
|
|
void player_pain(edict_t *self, edict_t *other, float kick, int damage)
|
|
{
|
|
// player pain is handled at the end of the frame in P_DamageFeedback
|
|
}
|
|
|
|
qboolean IsFemale(edict_t *ent)
|
|
{
|
|
char *info;
|
|
|
|
if (!ent->client)
|
|
return false;
|
|
|
|
info = Info_ValueForKey(ent->client->pers.userinfo, "skin");
|
|
if ((info[0] == 'f') || (info[0] == 'F'))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker)
|
|
{
|
|
char *message = NULL; //Maj
|
|
char *message2 = ""; //Maj
|
|
int mod;
|
|
qboolean ff;
|
|
|
|
//CW++
|
|
edict_t *player;
|
|
char killer[256];
|
|
int i;
|
|
float r;
|
|
//CW--
|
|
|
|
ff = meansOfDeath & MOD_FRIENDLY_FIRE;
|
|
mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
|
|
message = NULL;
|
|
message2 = "";
|
|
|
|
switch (mod) //CW
|
|
{
|
|
case MOD_SUICIDE:
|
|
message = "suicides";
|
|
break;
|
|
|
|
case MOD_FALLING:
|
|
message = (random()<0.5)?"cratered":"did a terminal face-plant"; //CW
|
|
break;
|
|
|
|
case MOD_CRUSH:
|
|
message = (random()<0.5)?"was squished":"was turned into a pancake"; //CW
|
|
break;
|
|
|
|
case MOD_WATER:
|
|
message = (random()<0.5)?"sank like a rock":"went to swim with the fishes"; //CW
|
|
break;
|
|
|
|
case MOD_SLIME:
|
|
message = (random()<0.5)?"melted":"drank too much green goo"; //CW
|
|
break;
|
|
|
|
case MOD_LAVA:
|
|
message = (random()<0.5)?"does a back flip into the lava":"went to visit the volcano god";//CW
|
|
break;
|
|
|
|
case MOD_EXPLOSIVE:
|
|
case MOD_BARREL:
|
|
message = (random()<0.5)?"blew up":"tried to cuddle a large explosion"; //CW
|
|
break;
|
|
|
|
case MOD_EXIT:
|
|
message = "found a way out";
|
|
break;
|
|
|
|
case MOD_TARGET_LASER:
|
|
message = "saw the light";
|
|
break;
|
|
|
|
case MOD_TARGET_BLASTER:
|
|
message = "got blasted";
|
|
break;
|
|
|
|
case MOD_BOMB:
|
|
//CW++
|
|
message = "caught a bomb";
|
|
break;
|
|
|
|
case MOD_FORBIDDEN:
|
|
message = "tried to enter a forbidden zone";
|
|
break;
|
|
//CW--
|
|
case MOD_SPLASH:
|
|
case MOD_TRIGGER_HURT:
|
|
message = "was in the wrong place";
|
|
break;
|
|
}
|
|
|
|
if (attacker == self)
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_R_SPLASH:
|
|
if (IsFemale(self))
|
|
message = "blew herself up";
|
|
else
|
|
message = "blew himself up";
|
|
break;
|
|
//CW++
|
|
case MOD_SPIKE_SPLASH:
|
|
message = "played with explosive sharp objects";
|
|
break;
|
|
|
|
case MOD_FLAMETHROWER:
|
|
message = (random()<0.5)?"had a meltdown":"really shouldn't play with fire";
|
|
break;
|
|
|
|
case MOD_FLAME:
|
|
message = (random()<0.5)?"became toast":"became a greasy charcoal lump";
|
|
break;
|
|
|
|
case MOD_FIREBOMB:
|
|
message = "really shouldn't play with fire";
|
|
break;
|
|
|
|
case MOD_FIREBOMB_SPLASH:
|
|
if (IsFemale(self))
|
|
message = "burned herself out";
|
|
else
|
|
message = "burned himself out";
|
|
break;
|
|
|
|
case MOD_SR_DISINT_WAVE:
|
|
message = (random()<0.5)?"went surfing":"should have used a smaller gun";
|
|
break;
|
|
|
|
case MOD_SR_HOMING:
|
|
if (IsFemale(self))
|
|
message = "ate her own plasma";
|
|
else
|
|
message = "ate his own plasma";
|
|
break;
|
|
|
|
case MOD_C4:
|
|
if (IsFemale(self))
|
|
message = "demolished herself";
|
|
else
|
|
message = "demolished himself";
|
|
break;
|
|
|
|
case MOD_C4_HELD:
|
|
if (IsFemale(self))
|
|
message = "forgot to throw her C4";
|
|
else
|
|
message = "forgot to throw his C4";
|
|
break;
|
|
|
|
case MOD_TRAP:
|
|
if (IsFemale(self))
|
|
message = "was disembowelled by her own trap";
|
|
else
|
|
message = "was disembowelled by his own trap";
|
|
break;
|
|
|
|
case MOD_TRAP_HELD:
|
|
if (IsFemale(self))
|
|
message = "was too attached to her trap";
|
|
else
|
|
message = "was too attached to his trap";
|
|
break;
|
|
|
|
case MOD_D89:
|
|
message = "overdosed on D89";
|
|
break;
|
|
|
|
case MOD_DISC:
|
|
if (IsFemale(self))
|
|
message = "was sliced open by her own disc";
|
|
else
|
|
message = "was sliced open by his own disc";
|
|
break;
|
|
//CW--
|
|
default:
|
|
if (IsFemale(self))
|
|
message = "killed herself";
|
|
else
|
|
message = "killed himself";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (message)
|
|
{
|
|
gi_bprintf (PRINT_MEDIUM, "%s %s\n", self->client->pers.netname, message);
|
|
self->client->resp.score--;
|
|
//CW++
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags1;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags2;
|
|
//CW--
|
|
self->enemy = NULL;
|
|
return;
|
|
}
|
|
|
|
self->enemy = attacker;
|
|
if (attacker && attacker->client)
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_MACHINEGUN:
|
|
message = "was machinegunned by";
|
|
break;
|
|
|
|
case MOD_ROCKET:
|
|
message = "ate";
|
|
message2 = "'s rocket";
|
|
break;
|
|
|
|
case MOD_R_SPLASH:
|
|
message = "almost dodged";
|
|
message2 = "'s rocket";
|
|
break;
|
|
|
|
case MOD_RAILGUN:
|
|
//CW++
|
|
if (random() < 0.5)
|
|
{
|
|
message = "was railed by";
|
|
message2 = " ";
|
|
}
|
|
else
|
|
{
|
|
message = "played slug-magnet with";
|
|
message2 = "'s railgun";
|
|
}
|
|
//CW--
|
|
break;
|
|
|
|
case MOD_TELEFRAG:
|
|
message = "tried to invade";
|
|
message2 = "'s personal space";
|
|
break;
|
|
//ZOID++
|
|
case MOD_GRAPPLE:
|
|
message = (random()<0.5)?"was caught by":"ate the pointy end of"; //CW
|
|
message2 = "'s grapple";
|
|
break;
|
|
//ZOID--
|
|
|
|
//CW++
|
|
case MOD_CHAINSAW:
|
|
r = random();
|
|
if (r < 0.333)
|
|
message = "was turned into puree by";
|
|
else if (r < 0.667)
|
|
message = "was used as grease to lubricate";
|
|
else
|
|
message = "was beautified by";
|
|
message2 = "'s chainsaw";
|
|
break;
|
|
|
|
case MOD_DESERTEAGLE:
|
|
message = (random()<0.5)?"took a bullet from":"bit the bullet from";
|
|
message2 = "'s Desert Eagle";
|
|
break;
|
|
|
|
case MOD_JACKHAMMER:
|
|
r = random();
|
|
if (r < 0.333)
|
|
message = "was blown away by";
|
|
else if (r < 0.667)
|
|
message = "was shredded like tissue by";
|
|
else
|
|
message = "was torn apart by";
|
|
message2 = "'s jackhammer";
|
|
break;
|
|
|
|
case MOD_MAC10:
|
|
r = random();
|
|
if (r < 0.333)
|
|
message = "was mown down by";
|
|
else if (r < 0.667)
|
|
message = "was cut in half by";
|
|
else
|
|
message = "was gunned down by";
|
|
message2 = (random()<0.95)?"'s Mac-10":"'s Sven-10...dat's a good one dere, ya!";
|
|
break;
|
|
|
|
case MOD_GAUSS_BLASTER:
|
|
message = "was splattered by";
|
|
message2 = "'s gauss pistol";
|
|
break;
|
|
|
|
case MOD_GAUSS_BEAM:
|
|
r = random();
|
|
if (r < 0.333)
|
|
{
|
|
if (IsFemale(self))
|
|
message = "had her tent ruined by";
|
|
else
|
|
message = "had his tent ruined by";
|
|
}
|
|
else if (r < 0.667)
|
|
message = "found you can't hide from";
|
|
else
|
|
message = "cursed the long reach of";
|
|
message2 = "'s particle beam";
|
|
break;
|
|
|
|
case MOD_GAUSS_BEAM_REF:
|
|
message = "'s particle beam was reflected off";
|
|
message2 = (random()<0.8)?" ":" ... ha ha!";
|
|
break;
|
|
|
|
case MOD_TRAP:
|
|
if (random() < 0.5)
|
|
{
|
|
message = "was disembowelled by";
|
|
message2 = "'s trap";
|
|
}
|
|
else
|
|
{
|
|
message = "could not get free from";
|
|
message2 = "'s diabolical trap";
|
|
}
|
|
break;
|
|
|
|
case MOD_C4:
|
|
case MOD_C4_HELD:
|
|
r = random();
|
|
if (r < 0.333)
|
|
message = "was demolished by";
|
|
else if (r < 0.667)
|
|
message = "was blown into tiny pieces by";
|
|
else
|
|
message = "was turned into Chinese fireworks by";
|
|
message2 = "'s C4";
|
|
break;
|
|
|
|
case MOD_C4_PROXIMITY:
|
|
message = (random()<0.5)?"didn't notice the deadly outline of":"came too close to";
|
|
message2 = "'s proximity mine";
|
|
break;
|
|
|
|
case MOD_C4_LIFETIME:
|
|
message = "fell victim to";
|
|
message2 = "'s short fuse";
|
|
break;
|
|
|
|
case MOD_SPIKE:
|
|
r = random();
|
|
if (r < 0.45)
|
|
{
|
|
message = "was spiked by";
|
|
message2 = " ";
|
|
}
|
|
else if (r < 0.9)
|
|
{
|
|
message = "was impaled on";
|
|
message2 = "'s spike";
|
|
}
|
|
//SNX++
|
|
else
|
|
{
|
|
message = "was mounted by";
|
|
message2 = "'s spike";
|
|
}
|
|
//SNX--
|
|
break;
|
|
|
|
case MOD_SPIKE_SPLASH:
|
|
message = (random()<0.5)?"exploded everywhere thanks to":"was exploded by";
|
|
message2 = "'s spike";
|
|
break;
|
|
|
|
case MOD_FLAMETHROWER:
|
|
message = (random()<0.5)?"was melted by":"was terminally scorched by";
|
|
message2 = "'s flamethrower";
|
|
break;
|
|
|
|
case MOD_FLAME:
|
|
message = (random()<0.5)?"was cremated by":"was flame-grilled to perfection by";
|
|
message2 = " ";
|
|
break;
|
|
|
|
case MOD_FIREBOMB:
|
|
message = "was obliterated by";
|
|
message2 = "'s firebomb";
|
|
break;
|
|
|
|
case MOD_FIREBOMB_SPLASH:
|
|
message = "was fatally scorched by";
|
|
message2 = "'s firebomb blast";
|
|
break;
|
|
|
|
case MOD_SR_DISINT:
|
|
message = (random()<0.5)?"was disintegrated by":"was turned into a red mist by";
|
|
message2 = "'s shockbolt";
|
|
break;
|
|
|
|
case MOD_SR_DISINT_WAVE:
|
|
message = (random()<0.5)?"was rocked by":"was spread like paste by";
|
|
message2 = "'s shockwave";
|
|
break;
|
|
|
|
case MOD_SR_HOMING:
|
|
message = (random()<0.5)?"couldn't outrun":"ran but couldn't hide from";
|
|
message2 = "'s homing plasma";
|
|
break;
|
|
|
|
case MOD_AGM_FEEDBACK:
|
|
message = "crossed streams with";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_BEAM_REF:
|
|
message = "'s AG Manipulator beam was reflected off";
|
|
message2 = (random()<0.8)?" ":" ... bwahaha!";
|
|
break;
|
|
|
|
case MOD_AGM_HIT:
|
|
message = "was used for bowling practice by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_FLING:
|
|
message = "was smeared across the map by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_SMASH:
|
|
message = "was smashed into a hard surface by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_THROW:
|
|
message = "was used to paint the walls by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_DROP:
|
|
message = (random()<0.5)?"was used to mop the floor by":"was spread across the floor by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_LAVA_HELD:
|
|
message = (random()<0.5)?"was dunked in lava like a donut by":"was forced to drink lava by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_LAVA_DROP:
|
|
message = "was tossed into the lava by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_SLIME_HELD:
|
|
message = (random()<0.5)?"was dunked in slime like a biscuit by":"was forced to drink slime by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_SLIME_DROP:
|
|
message = "was tossed into the slime by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_WATER_HELD:
|
|
message = "was drowned like a rat by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_TRIG_HURT:
|
|
message = "was sent to the wrong place by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_TARG_LASER:
|
|
message = "was shown a very bright light by";
|
|
message2 = "'s AGM";
|
|
break;
|
|
|
|
case MOD_AGM_DISRUPT:
|
|
if (random() < 0.5)
|
|
{
|
|
if (IsFemale(self))
|
|
message = "had her insides scrambled by";
|
|
else
|
|
message = "had his insides scrambled by";
|
|
}
|
|
else
|
|
message = "was sucked dry by";
|
|
message2 = "'s cellular disruptor";
|
|
break;
|
|
|
|
case MOD_DISC:
|
|
message = (random()<0.5)?"was sliced-n-diced by":"was gutted by";
|
|
message2 = "'s disc";
|
|
break;
|
|
//CW--
|
|
}
|
|
|
|
if (message)
|
|
{
|
|
gi_bprintf(PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
|
|
|
|
//Maj++
|
|
if ((int)sv_bots_taunt->value > 0) //CW++
|
|
TauntVictim(attacker, self);
|
|
|
|
if (((int)sv_bots_insult->value > 0) && !attacker->client->pers.muted) //CW++
|
|
InsultVictim(attacker, self);
|
|
//Maj--
|
|
|
|
if (ff)
|
|
{
|
|
attacker->client->resp.score--;
|
|
//CW++
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags1;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags2;
|
|
}
|
|
else if ((mod == MOD_GAUSS_BEAM_REF) || (mod == MOD_AGM_FEEDBACK))
|
|
{
|
|
self->client->resp.score--;
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags1;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags2;
|
|
}
|
|
else
|
|
{
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
++teamgame.frags2;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
++teamgame.frags1;
|
|
//CW--
|
|
attacker->client->resp.score++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
//CW++
|
|
else if (attacker && (!Q_stricmp(attacker->classname, "turret_breach") || !Q_stricmp(attacker->classname, "model_turret")))
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_ROCKET: // turret type 1
|
|
message = "ate the rocket from";
|
|
message2 = "rocket turret";
|
|
break;
|
|
|
|
case MOD_R_SPLASH: // turret type 1
|
|
message = "almost dodged the rocket from";
|
|
message2 = "rocket turret";
|
|
break;
|
|
|
|
case MOD_RAILGUN: // turret type 2
|
|
message = (random()<0.5)?"was railed by":"played slug-magnet with";
|
|
message2 = "railgun turret";
|
|
break;
|
|
|
|
case MOD_MACHINEGUN: // turret type 3
|
|
message = "was mown down by";
|
|
message2 = "machinegun turret";
|
|
break;
|
|
|
|
case MOD_SPIKE: // turret type 4
|
|
message = "was spiked by";
|
|
message2 = "ESG turret";
|
|
break;
|
|
|
|
case MOD_SPIKE_SPLASH: // turret type 4
|
|
message = "was exploded by a spike from";
|
|
message2 = "ESG turret";
|
|
break;
|
|
|
|
case MOD_SR_DISINT: // turret type 5
|
|
message = (random()<0.5)?"was disintegrated by":"was turned into a red mist by";
|
|
message2 = "shockbolt turret";
|
|
break;
|
|
|
|
case MOD_SR_DISINT_WAVE: // turret type 5
|
|
message = (random()<0.5)?"was rocked by":"was spread like paste by";
|
|
message2 = "shockbolt turret";
|
|
break;
|
|
|
|
case MOD_PLASMA: // turret type 6
|
|
message = (random()<0.5)?"was melted by":"was blasted into oblivion by";
|
|
message2 = "plasma turret";
|
|
break;
|
|
}
|
|
|
|
if (message)
|
|
{
|
|
Com_sprintf(killer, sizeof(killer), "an auto-tracking");
|
|
if (self->noise_index)
|
|
Com_sprintf(killer, sizeof(killer), "their very own");
|
|
else
|
|
{
|
|
for (i = 0, player = g_edicts + 1; i < (int)maxclients->value; ++i, ++player)
|
|
{
|
|
if (player->client && (player->client->spycam == attacker))
|
|
{
|
|
Com_sprintf(killer, sizeof(killer), "%s's", player->client->pers.netname);
|
|
if (ff)
|
|
player->client->resp.score--;
|
|
else if (!self->noise_index)
|
|
player->client->resp.score++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gi_bprintf(PRINT_MEDIUM,"%s %s %s %s\n", self->client->pers.netname, message, killer, message2);
|
|
|
|
if (ff)
|
|
{ // death by friendly fire
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags1;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags2;
|
|
}
|
|
else if (self->noise_index)
|
|
{ // shot own self with our remote-controlled turret
|
|
self->client->resp.score--;
|
|
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags1;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags2;
|
|
}
|
|
else
|
|
{ // death by enemy turret
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
++teamgame.frags2;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
++teamgame.frags1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
//CW--...
|
|
|
|
gi_bprintf(PRINT_MEDIUM,"%s died\n", self->client->pers.netname);
|
|
self->client->resp.score--;
|
|
|
|
//CW++
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags1;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags2;
|
|
//CW--
|
|
}
|
|
|
|
void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
|
|
|
|
void TossClientWeapon(edict_t *self)
|
|
{
|
|
gitem_t *item;
|
|
edict_t *drop;
|
|
qboolean quad;
|
|
float spread;
|
|
|
|
item = self->client->pers.weapon;
|
|
if (!self->client->pers.inventory[self->client->ammo_index])
|
|
item = NULL;
|
|
|
|
//CW++
|
|
if (item && ((item->weapmodel == WEAP_CHAINSAW) || (item->weapmodel == WEAP_DESERTEAGLE)))
|
|
item = NULL;
|
|
//CW--
|
|
|
|
if (!((int)(dmflags->value) & DF_QUAD_DROP))
|
|
quad = false;
|
|
else
|
|
quad = ((self->client->quad_framenum > (level.framenum + 10))) ? true : false; //CW
|
|
|
|
if (item && quad)
|
|
spread = 22.5;
|
|
else
|
|
spread = 0.0;
|
|
|
|
if (item)
|
|
{
|
|
self->client->v_angle[YAW] -= spread;
|
|
drop = Drop_Item(self, item);
|
|
self->client->v_angle[YAW] += spread;
|
|
//CW++
|
|
if (drop != NULL)
|
|
//CW--
|
|
drop->spawnflags = DROPPED_PLAYER_ITEM;
|
|
}
|
|
|
|
if (quad)
|
|
{
|
|
self->client->v_angle[YAW] += spread;
|
|
//CW++
|
|
drop = Drop_Item(self, FindItem("Quad Damage"));
|
|
if (drop != NULL)
|
|
{
|
|
drop->classname = "item_quad";
|
|
//CW--
|
|
drop->spawnflags |= DROPPED_PLAYER_ITEM;
|
|
|
|
drop->touch = Touch_Item;
|
|
drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
|
|
drop->think = G_FreeEdict;
|
|
}
|
|
self->client->v_angle[YAW] -= spread;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
LookAtKiller
|
|
==================
|
|
*/
|
|
void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker)
|
|
{
|
|
vec3_t dir;
|
|
|
|
//Maj++
|
|
if (self->isabot)
|
|
return;
|
|
//Maj--
|
|
|
|
if (attacker && (attacker != world) && (attacker != self))
|
|
VectorSubtract(attacker->s.origin, self->s.origin, dir);
|
|
else if (inflictor && (inflictor != world) && (inflictor != self))
|
|
VectorSubtract(inflictor->s.origin, self->s.origin, dir);
|
|
else
|
|
{
|
|
self->client->killer_yaw = self->s.angles[YAW];
|
|
return;
|
|
}
|
|
|
|
if (dir[0])
|
|
self->client->killer_yaw = RAD2DEG(atan2(dir[1], dir[0]));
|
|
else
|
|
{
|
|
self->client->killer_yaw = 0.0;
|
|
if (dir[1] > 0.0)
|
|
self->client->killer_yaw = 90.0;
|
|
else if (dir[1] < 0.0)
|
|
self->client->killer_yaw = -90.0;
|
|
}
|
|
|
|
if (self->client->killer_yaw < 0.0)
|
|
self->client->killer_yaw += 360.0;
|
|
}
|
|
|
|
//CW++
|
|
/*
|
|
==================
|
|
FindLeader
|
|
==================
|
|
*/
|
|
edict_t *FindLeader(void)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *ent_lead = NULL;
|
|
int max_score = -999999;
|
|
int n_clients = 0;
|
|
int i;
|
|
|
|
// Search through client entities to determine who has the highest score.
|
|
|
|
for (i = 1; i <= game.maxclients; ++i)
|
|
{
|
|
ent = &g_edicts[i];
|
|
if (!ent->client)
|
|
continue;
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (ent->health < 1)
|
|
continue;
|
|
|
|
++n_clients;
|
|
if (n_clients == 1)
|
|
{
|
|
ent_lead = ent;
|
|
max_score = ent->client->resp.score;
|
|
}
|
|
|
|
if (ent->client->resp.score > max_score)
|
|
{
|
|
ent_lead = ent;
|
|
max_score = ent->client->resp.score;
|
|
}
|
|
}
|
|
|
|
// In the case of there being "first equal" players, and one of them is the currently indicated
|
|
// leader, make sure they retain the title (otherwise, the first player in the list is to get it).
|
|
|
|
if ((ent_lead != NULL) && (level.leader != NULL))
|
|
{
|
|
if ((ent_lead->client->resp.score == level.lead_score) && (ent_lead != level.leader))
|
|
ent_lead = level.leader;
|
|
}
|
|
|
|
return ent_lead;
|
|
}
|
|
//CW--
|
|
|
|
/*
|
|
==================
|
|
player_die
|
|
==================
|
|
*/
|
|
void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
//CW++
|
|
edict_t *ent;
|
|
edict_t *ent_lead = NULL;
|
|
//CW--
|
|
|
|
int n;
|
|
|
|
//DH++
|
|
if (self->client->spycam)
|
|
camera_off(self);
|
|
|
|
if (self->turret)
|
|
turret_disengage(self->turret);
|
|
//DH--
|
|
|
|
//CW++
|
|
self->client->agm_charge = 0;
|
|
self->client->agm_showcharge = false;
|
|
self->client->agm_tripped = false;
|
|
self->client->agm_on = false;
|
|
self->client->agm_push = false;
|
|
self->client->agm_pull = false;
|
|
self->client->held_by_agm = false;
|
|
self->client->flung_by_agm = false;
|
|
self->client->thrown_by_agm = false;
|
|
|
|
if (self->client->agm_target != NULL)
|
|
{
|
|
self->client->agm_target->client->held_by_agm = false;
|
|
self->client->agm_target->client->flung_by_agm = false;
|
|
self->client->agm_target->client->thrown_by_agm = true;
|
|
self->client->agm_target = NULL;
|
|
}
|
|
//CW--
|
|
|
|
VectorClear(self->avelocity);
|
|
self->takedamage = DAMAGE_YES;
|
|
self->movetype = MOVETYPE_TOSS;
|
|
|
|
self->s.modelindex2 = 0; // remove linked weapon model
|
|
self->s.modelindex3 = 0; // remove linked ctf flag //ZOID++
|
|
self->s.modelindex4 = 0; // remove linked leader indicator //CW++
|
|
|
|
self->s.angles[0] = 0.0;
|
|
self->s.angles[2] = 0.0;
|
|
|
|
self->s.sound = 0;
|
|
self->client->weapon_sound = 0;
|
|
|
|
self->maxs[2] = -8.0;
|
|
|
|
//CW++
|
|
// If we've been trap-tractored to death, destroy the trap that killed us and
|
|
// any others that are currently trying to.
|
|
|
|
if (self->tractored)
|
|
{
|
|
self->tractored = false;
|
|
for (n = 0; n < globals.num_edicts; ++n)
|
|
{
|
|
ent = &g_edicts[n];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (ent->client)
|
|
continue;
|
|
if (!ent->die)
|
|
continue;
|
|
|
|
if ((ent->die == Trap_DieFromDamage) && (ent->enemy == self))
|
|
{
|
|
ent->think = Trap_Die;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
|
|
if (!self->deadflag)
|
|
{
|
|
self->client->respawn_time = level.time + 1.0;
|
|
|
|
//Maj++
|
|
if (self->isabot)
|
|
self->client->respawn_time += rand() % 5; //CW
|
|
//Maj--
|
|
|
|
LookAtKiller(self, inflictor, attacker);
|
|
self->client->ps.pmove.pm_type = PM_DEAD;
|
|
ClientObituary(self, inflictor, attacker);
|
|
|
|
//ZOID++
|
|
// if at start and same team, clear
|
|
if (((int)sv_gametype->value > G_FFA) && (meansOfDeath == MOD_TELEFRAG) && (self->client->resp.ctf_state < 2) && //CW
|
|
(self->client->resp.ctf_team == attacker->client->resp.ctf_team))
|
|
{
|
|
//CW++
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
--teamgame.frags2;
|
|
if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
--teamgame.frags1;
|
|
//CW--
|
|
attacker->client->resp.score--;
|
|
self->client->resp.ctf_state = 0;
|
|
}
|
|
|
|
//CW++
|
|
// Fix scores for team-change "suicide".
|
|
|
|
if (((sv_gametype->value == G_TDM) || (sv_gametype->value == G_ASLT)) && self->client->mod_changeteam)
|
|
{
|
|
if (self->client->resp.ctf_team == CTF_TEAM1)
|
|
{
|
|
++teamgame.frags1;
|
|
teamgame.frags2 -= (self->client->resp.score + 1);
|
|
}
|
|
else if (self->client->resp.ctf_team == CTF_TEAM2)
|
|
{
|
|
++teamgame.frags2;
|
|
teamgame.frags1 -= (self->client->resp.score + 1);
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
CTFFragBonuses(self, inflictor, attacker);
|
|
//ZOID--
|
|
|
|
//CW++
|
|
// Update the score leader status as necessary.
|
|
|
|
if (sv_gametype->value == G_FFA)
|
|
{
|
|
if (level.leader == self) // fragged by a player
|
|
{
|
|
level.lead_score = self->client->resp.score;
|
|
if (attacker->client && (attacker != self))
|
|
{
|
|
if (attacker->client->resp.score > self->client->resp.score)
|
|
{
|
|
level.leader = attacker;
|
|
level.lead_score = attacker->client->resp.score;
|
|
if ((int)sv_show_leader->value)
|
|
attacker->s.modelindex4 = gi.modelindex("models/halo/tris.md2");
|
|
}
|
|
}
|
|
else // fragged by environment or suicide
|
|
{
|
|
if ((ent_lead = FindLeader()) != NULL)
|
|
{
|
|
level.leader = ent_lead;
|
|
level.lead_score = ent_lead->client->resp.score;
|
|
if (ent_lead != self)
|
|
{
|
|
if ((int)sv_show_leader->value)
|
|
ent_lead->s.modelindex4 = gi.modelindex("models/halo/tris.md2");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (attacker->client && (attacker->client->resp.score > level.lead_score))
|
|
{
|
|
level.leader->s.modelindex4 = 0;
|
|
if ((int)sv_show_leader->value)
|
|
attacker->s.modelindex4 = gi.modelindex("models/halo/tris.md2");
|
|
|
|
level.leader = attacker;
|
|
level.lead_score = attacker->client->resp.score;
|
|
}
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
TossClientWeapon(self);
|
|
|
|
//ZOID++
|
|
CTFPlayerResetGrapple(self);
|
|
CTFDeadDropFlag(self);
|
|
CTFDeadDropTech(self);
|
|
//ZOID--
|
|
|
|
if (!self->client->showscores) //CW
|
|
Cmd_Score_f(self); // show scores //
|
|
}
|
|
|
|
// remove powerups
|
|
self->client->quad_framenum = 0;
|
|
self->client->invincible_framenum = 0;
|
|
self->client->breather_framenum = 0;
|
|
self->client->enviro_framenum = 0;
|
|
self->flags &= ~(FL_POWER_SHIELD|FL_POWER_SCREEN);
|
|
|
|
//CW++
|
|
self->client->show_gausscharge = false;
|
|
self->client->show_gausstarget = 0;
|
|
self->client->gauss_dmg = 0;
|
|
self->client->gauss_framenum = 0;
|
|
|
|
self->client->antibeam_framenum = 0;
|
|
self->client->frozen_framenum = 0;
|
|
self->client->siphon_framenum = 0;
|
|
self->client->needle_framenum = 0;
|
|
self->client->haste_framenum = 0;
|
|
self->client->mod_changeteam = false;
|
|
|
|
// Force gibbing for death by chainsaw and disc-launcher ... MUAHAHAHA!
|
|
|
|
if (attacker->client && (attacker != self) && (attacker->health > 0))
|
|
{
|
|
int attack_weapon = attacker->client->pers.weapon->weapmodel;
|
|
|
|
if (attack_weapon == WEAP_CHAINSAW)
|
|
{
|
|
float r;
|
|
|
|
self->health = -41;
|
|
r = random();
|
|
if (r < 0.8)
|
|
gi.sound(self, CHAN_VOICE, gi.soundindex("voice/s_humil.wav"), 1, ATTN_NORM, 0);
|
|
else if (r < 0.9)
|
|
gi.sound(self, CHAN_VOICE, gi.soundindex("voice/s_bleed.wav"), 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, gi.soundindex("voice/s_wound.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else if (attack_weapon == WEAP_DISCLAUNCHER)
|
|
self->health = -41;
|
|
}
|
|
//CW--
|
|
|
|
// clear inventory
|
|
memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory));
|
|
|
|
if (self->health < -40)
|
|
{ // gib
|
|
gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
|
|
|
//CW++
|
|
// For disintegrations, do a fountain of blood; otherwise, throw gibs.
|
|
|
|
if (self->burning)
|
|
{
|
|
self->burning = false;
|
|
if (self->flame) // sanity check
|
|
{
|
|
self->flame->touch = NULL;
|
|
self->flame->think = Flame_Expire;
|
|
self->flame->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
if (self->disintegrated)
|
|
{
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_MOREBLOOD);
|
|
gi.WritePosition(self->s.origin);
|
|
gi.WriteDir(vec3_up);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
}
|
|
else
|
|
//CW--
|
|
{
|
|
for (n = 0; n < 4; ++n)
|
|
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC, 0.0);
|
|
}
|
|
|
|
ThrowClientHead(self, damage);
|
|
|
|
//ZOID++
|
|
self->client->anim_priority = ANIM_DEATH;
|
|
self->client->anim_end = 0;
|
|
//ZOID--
|
|
|
|
self->takedamage = DAMAGE_NO;
|
|
}
|
|
else
|
|
{ // normal death
|
|
if (!self->deadflag)
|
|
{
|
|
static int i;
|
|
|
|
i = (i + 1) % 3;
|
|
// start a death animation
|
|
self->client->anim_priority = ANIM_DEATH;
|
|
if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
self->s.frame = FRAME_crdeath1-1;
|
|
self->client->anim_end = FRAME_crdeath5;
|
|
}
|
|
else switch (i)
|
|
{
|
|
case 0:
|
|
self->s.frame = FRAME_death101-1;
|
|
self->client->anim_end = FRAME_death106;
|
|
break;
|
|
|
|
case 1:
|
|
self->s.frame = FRAME_death201-1;
|
|
self->client->anim_end = FRAME_death206;
|
|
break;
|
|
|
|
case 2:
|
|
self->s.frame = FRAME_death301-1;
|
|
self->client->anim_end = FRAME_death308;
|
|
break;
|
|
}
|
|
gi.sound(self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
self->deadflag = DEAD_DEAD;
|
|
|
|
//Pon++
|
|
// Routing last index move.
|
|
|
|
if ((int)chedit->value && (self == &g_edicts[1]))
|
|
Move_LastRouteIndex();
|
|
//Pon--
|
|
|
|
//CW++
|
|
// Clear any noise entities we own.
|
|
|
|
if (self->mynoise)
|
|
{
|
|
G_FreeEdict(self->mynoise);
|
|
self->mynoise = NULL;
|
|
}
|
|
|
|
if (self->mynoise2)
|
|
{
|
|
G_FreeEdict(self->mynoise2);
|
|
self->mynoise2 = NULL;
|
|
}
|
|
//CW--
|
|
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
//=======================================================================
|
|
|
|
//CW++
|
|
/*
|
|
==================
|
|
GiveClientItem
|
|
==================
|
|
*/
|
|
gitem_t *GiveClientItem(gclient_t *client, char *pickup_name, int num)
|
|
{
|
|
gitem_t *item;
|
|
|
|
item = FindItem(pickup_name);
|
|
client->pers.selected_item = ITEM_INDEX(item);
|
|
client->pers.inventory[client->pers.selected_item] = num;
|
|
|
|
return item;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SetClientWeapon
|
|
==================
|
|
*/
|
|
gitem_t *SetClientWeapon(gclient_t *client, char *pickup_name)
|
|
{
|
|
gitem_t *weapon;
|
|
|
|
weapon = FindItem(pickup_name);
|
|
client->pers.weapon = weapon;
|
|
client->pers.lastweapon = weapon;
|
|
|
|
return weapon;
|
|
}
|
|
//CW--
|
|
|
|
/*
|
|
==============
|
|
InitClientPersistant
|
|
|
|
This is only called when the game first initializes in single player,
|
|
but is called after each death and level change in deathmatch
|
|
==============
|
|
*/
|
|
void InitClientPersistant(gclient_t *client)
|
|
{
|
|
gitem_t *item;
|
|
//CW++
|
|
char ip[22];
|
|
qboolean op_status;
|
|
qboolean muted;
|
|
qboolean have_weap = false;
|
|
qboolean use_weap = false;
|
|
int get_weap = WINV_DESERTEAGLE;
|
|
int atype;
|
|
int weap_note;
|
|
int botindex;
|
|
|
|
Com_sprintf(ip, sizeof(ip), "%s", client->pers.ip);
|
|
op_status = client->pers.op_status;
|
|
muted = client->pers.muted;
|
|
weap_note = client->pers.weap_note;
|
|
botindex = client->pers.botindex;
|
|
//CW--
|
|
|
|
memset(&client->pers, 0, sizeof(client->pers));
|
|
|
|
//CW++
|
|
GiveClientItem(client, "Chainsaw", 1);
|
|
|
|
if ((int)sv_have_traps->value)
|
|
{
|
|
item = GiveClientItem(client, "Traps", (int)sv_initial_traps->value);
|
|
have_weap = true;
|
|
get_weap = WINV_TRAPS;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_TRAPS)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_deserteagle->value)
|
|
{
|
|
item = GiveClientItem(client, "Desert Eagle", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_DESERTEAGLE;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_DESERTEAGLE)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_agm->value)
|
|
{
|
|
item = GiveClientItem(client, "AG Manipulator", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_AGM;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_AGM)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_c4->value)
|
|
{
|
|
item = GiveClientItem(client, "C4", (int)sv_initial_c4->value);
|
|
have_weap = true;
|
|
get_weap = WINV_C4;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_C4)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_flamethrower->value)
|
|
{
|
|
item = GiveClientItem(client, "Flamethrower", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_FLAMETHROWER;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_FLAMETHROWER)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_gausspistol->value)
|
|
{
|
|
item = GiveClientItem(client, "Gauss Pistol", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_GAUSSPISTOL;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_GAUSSPISTOL)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_mac10->value)
|
|
{
|
|
item = GiveClientItem(client, "Mac-10", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_MAC10;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_MAC10)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_jackhammer->value)
|
|
{
|
|
item = GiveClientItem(client, "Jackhammer", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_JACKHAMMER;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_JACKHAMMER)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_disclauncher->value)
|
|
{
|
|
item = GiveClientItem(client, "Disc Launcher", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_DISCLAUNCHER;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_DISCLAUNCHER)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_spikegun->value)
|
|
{
|
|
item = GiveClientItem(client, "E.S.G.", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_ESG;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_ESG)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_rocketlauncher->value)
|
|
{
|
|
item = GiveClientItem(client, "Rocket Launcher", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_ROCKETLAUNCHER;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_ROCKETLAUNCHER)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_railgun->value)
|
|
{
|
|
item = GiveClientItem(client, "Railgun", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_RAILGUN;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_RAILGUN)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if ((int)sv_have_shockrifle->value)
|
|
{
|
|
item = GiveClientItem(client, "Shock Rifle", 1);
|
|
have_weap = true;
|
|
get_weap = WINV_SHOCKRIFLE;
|
|
|
|
if ((int)sv_initial_weapon->value == WINV_SHOCKRIFLE)
|
|
{
|
|
client->pers.weapon = item;
|
|
client->pers.lastweapon = item;
|
|
use_weap = true;
|
|
}
|
|
}
|
|
|
|
if (!have_weap)
|
|
{
|
|
gi.cvar_forceset("initial_weapon", va("%d", WINV_CHAINSAW));
|
|
SetClientWeapon(client, "Chainsaw");
|
|
get_weap = WINV_CHAINSAW;
|
|
use_weap = true;
|
|
}
|
|
|
|
if (!use_weap)
|
|
{
|
|
SetClientWeapon(client, "Chainsaw");
|
|
|
|
switch (get_weap)
|
|
{
|
|
case WINV_DESERTEAGLE:
|
|
if ((int)sv_initial_bullets->value)
|
|
SetClientWeapon(client, "Desert Eagle");
|
|
|
|
break;
|
|
|
|
case WINV_GAUSSPISTOL:
|
|
if ((int)sv_initial_cells->value)
|
|
SetClientWeapon(client, "Gauss Pistol");
|
|
|
|
break;
|
|
|
|
case WINV_MAC10:
|
|
if ((int)sv_initial_bullets->value)
|
|
SetClientWeapon(client, "Mac-10");
|
|
|
|
break;
|
|
|
|
case WINV_JACKHAMMER:
|
|
if ((int)sv_initial_shells->value)
|
|
SetClientWeapon(client, "Jackhammer");
|
|
|
|
break;
|
|
|
|
case WINV_C4:
|
|
if ((int)sv_initial_c4->value)
|
|
SetClientWeapon(client, "C4");
|
|
|
|
break;
|
|
|
|
case WINV_TRAPS:
|
|
if ((int)sv_initial_traps->value)
|
|
SetClientWeapon(client, "Traps");
|
|
|
|
break;
|
|
|
|
case WINV_ESG:
|
|
if ((int)sv_initial_rockets->value)
|
|
SetClientWeapon(client, "E.S.G.");
|
|
|
|
break;
|
|
|
|
case WINV_ROCKETLAUNCHER:
|
|
if ((int)sv_initial_rockets->value)
|
|
SetClientWeapon(client, "Rocket Launcher");
|
|
|
|
break;
|
|
|
|
case WINV_FLAMETHROWER:
|
|
if ((int)sv_initial_cells->value)
|
|
SetClientWeapon(client, "Flamethrower");
|
|
|
|
break;
|
|
|
|
case WINV_RAILGUN:
|
|
if ((int)sv_initial_slugs->value)
|
|
SetClientWeapon(client, "Railgun");
|
|
|
|
break;
|
|
|
|
case WINV_SHOCKRIFLE:
|
|
if ((int)sv_initial_cells->value)
|
|
SetClientWeapon(client, "Shock Rifle");
|
|
|
|
break;
|
|
|
|
case WINV_AGM:
|
|
if ((int)sv_initial_cells->value)
|
|
SetClientWeapon(client, "AG Manipulator");
|
|
|
|
break;
|
|
|
|
case WINV_DISCLAUNCHER:
|
|
if ((int)sv_initial_rockets->value)
|
|
SetClientWeapon(client, "Disc Launcher");
|
|
|
|
break;
|
|
|
|
case WINV_CHAINSAW:
|
|
default:
|
|
SetClientWeapon(client, "Chainsaw");
|
|
break;
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
//ZOID++
|
|
if ((int)sv_allow_hook->value && !level.nohook) //CW
|
|
{
|
|
item = FindItem("Grapple");
|
|
|
|
//CW++
|
|
// Setup off-hand grapple.
|
|
|
|
if ((int)sv_hook_offhand->value)
|
|
{
|
|
item->use = NULL;
|
|
item->view_model = '\0';
|
|
item->icon = '\0';
|
|
client->hookstate = WEAPON_READY;
|
|
}
|
|
//CW--
|
|
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
}
|
|
//ZOID--
|
|
|
|
//CW++
|
|
// Default starting ammo.
|
|
|
|
if ((int)sv_initial_bullets->value)
|
|
{
|
|
item = FindItem("bullets");
|
|
client->pers.inventory[ITEM_INDEX(item)] = (int)sv_initial_bullets->value;
|
|
}
|
|
|
|
if ((int)sv_initial_shells->value)
|
|
{
|
|
item = FindItem("shells");
|
|
client->pers.inventory[ITEM_INDEX(item)] = (int)sv_initial_shells->value;
|
|
}
|
|
|
|
if ((int)sv_initial_rockets->value)
|
|
{
|
|
item = FindItem("rockets");
|
|
client->pers.inventory[ITEM_INDEX(item)] = (int)sv_initial_rockets->value;
|
|
}
|
|
|
|
if ((int)sv_initial_cells->value)
|
|
{
|
|
item = FindItem("cells");
|
|
client->pers.inventory[ITEM_INDEX(item)] = (int)sv_initial_cells->value;
|
|
}
|
|
|
|
if ((int)sv_initial_slugs->value)
|
|
{
|
|
item = FindItem("slugs");
|
|
client->pers.inventory[ITEM_INDEX(item)] = (int)sv_initial_slugs->value;
|
|
}
|
|
//CW--
|
|
|
|
client->pers.health = (int)sv_health_initial->value; //CW...
|
|
client->pers.max_health = (int)sv_health_max->value;
|
|
|
|
client->pers.max_bullets = (int)sv_max_bullets->value; //CW...
|
|
client->pers.max_shells = (int)sv_max_shells->value;
|
|
client->pers.max_rockets = (int)sv_max_rockets->value;
|
|
client->pers.max_c4 = (int)sv_max_c4->value;
|
|
client->pers.max_cells = (int)sv_max_cells->value;
|
|
client->pers.max_slugs = (int)sv_max_slugs->value;
|
|
|
|
//CW++
|
|
client->pers.max_traps = (int)sv_max_traps->value;
|
|
|
|
client->pers.op_status = op_status;
|
|
client->pers.muted = muted;
|
|
Com_sprintf(client->pers.ip, sizeof(client->pers.ip), "%s", ip);
|
|
client->pers.weap_note = weap_note;
|
|
client->pers.botindex = botindex;
|
|
//CW--
|
|
|
|
//CW++
|
|
// Default starting armour.
|
|
|
|
if ((int)sv_initial_armor->value > 0)
|
|
{
|
|
if ((int)sv_initial_armortype->value == 0)
|
|
atype = jacket_armor_index;
|
|
else if ((int)sv_initial_armortype->value == 1)
|
|
atype = combat_armor_index;
|
|
else
|
|
atype = body_armor_index;
|
|
|
|
client->pers.inventory[atype] = (int)sv_initial_armor->value;
|
|
}
|
|
//CW--
|
|
|
|
client->pers.connected = true;
|
|
|
|
// Knightmare- custom client colors
|
|
Vector4Set (client->pers.color1, 255, 255, 255, 0);
|
|
Vector4Set (client->pers.color2, 255, 255, 255, 0);
|
|
|
|
//DH++
|
|
client->spycam = NULL;
|
|
//DH--
|
|
}
|
|
|
|
|
|
void InitClientResp(gclient_t *client)
|
|
{
|
|
//ZOID++
|
|
int ctf_team = client->resp.ctf_team;
|
|
qboolean id_state = client->resp.id_state;
|
|
//ZOID--
|
|
|
|
qboolean id_trap = client->resp.id_trap; //CW++
|
|
|
|
memset(&client->resp, 0, sizeof(client->resp));
|
|
|
|
//ZOID++
|
|
client->resp.ctf_team = ctf_team;
|
|
client->resp.id_state = id_state;
|
|
//ZOID--
|
|
|
|
client->resp.id_trap = id_trap; //CW++
|
|
client->resp.enterframe = level.framenum; //CW
|
|
|
|
//ZOID++
|
|
if (client->resp.ctf_team < CTF_TEAM1) //CW
|
|
CTFAssignTeam(client);
|
|
//ZOID--
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SaveClientData
|
|
|
|
Some information that should be persistant, like health,
|
|
is still stored in the edict structure, so it needs to
|
|
be mirrored out to the client structure before all the
|
|
edicts are wiped.
|
|
==================
|
|
*/
|
|
void SaveClientData(void)
|
|
{
|
|
int i;
|
|
edict_t *ent;
|
|
|
|
for (i = 0; i < (int)game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (!ent->inuse)
|
|
continue;
|
|
|
|
game.clients[i].pers.health = ent->health;
|
|
game.clients[i].pers.max_health = ent->max_health;
|
|
game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_SHIELD|FL_POWER_SCREEN)); //CW
|
|
}
|
|
}
|
|
|
|
void FetchClientEntData(edict_t *ent)
|
|
{
|
|
ent->health = ent->client->pers.health;
|
|
ent->max_health = ent->client->pers.max_health;
|
|
ent->flags |= ent->client->pers.savedFlags; //CW
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
SelectSpawnPoint
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
PlayersRangeFromSpot
|
|
|
|
Returns the distance to the nearest player from the given spot
|
|
================
|
|
*/
|
|
float PlayersRangeFromSpot(edict_t *spot)
|
|
{
|
|
edict_t *player;
|
|
vec3_t v;
|
|
float bestplayerdistance;
|
|
float playerdistance;
|
|
int n;
|
|
|
|
bestplayerdistance = 9999999;
|
|
|
|
for (n = 1; n <= (int)maxclients->value; n++)
|
|
{
|
|
player = &g_edicts[n];
|
|
if (!player->inuse)
|
|
continue;
|
|
if (player->health < 1)
|
|
continue;
|
|
|
|
VectorSubtract(spot->s.origin, player->s.origin, v);
|
|
playerdistance = VectorLength(v);
|
|
|
|
if (playerdistance < bestplayerdistance)
|
|
bestplayerdistance = playerdistance;
|
|
}
|
|
|
|
return bestplayerdistance;
|
|
}
|
|
|
|
//CW++
|
|
/*
|
|
================
|
|
TeamPlayersRangeFromSpot
|
|
|
|
Returns the distance to the nearest player from the given spot
|
|
who is on the same team as the specified team.
|
|
================
|
|
*/
|
|
float TeamPlayersRangeFromSpot(edict_t *spot, ctfteam_t team)
|
|
{
|
|
edict_t *player;
|
|
vec3_t v;
|
|
float bestplayerdistance;
|
|
float playerdistance;
|
|
int n;
|
|
|
|
bestplayerdistance = 9999999;
|
|
|
|
for (n = 1; n <= (int)maxclients->value; n++)
|
|
{
|
|
player = &g_edicts[n];
|
|
if (!player->inuse)
|
|
continue;
|
|
if (player->health < 1)
|
|
continue;
|
|
if (player->client->resp.ctf_team != team)
|
|
continue;
|
|
|
|
VectorSubtract(spot->s.origin, player->s.origin, v);
|
|
playerdistance = VectorLength(v);
|
|
|
|
if (playerdistance < bestplayerdistance)
|
|
bestplayerdistance = playerdistance;
|
|
}
|
|
|
|
return bestplayerdistance;
|
|
}
|
|
//CW--
|
|
|
|
/*
|
|
================
|
|
SelectRandomDeathmatchSpawnPoint
|
|
|
|
go to a random point, but NOT the two points closest
|
|
to other players
|
|
================
|
|
*/
|
|
edict_t *SelectRandomDeathmatchSpawnPoint (void)
|
|
{
|
|
edict_t *spot;
|
|
edict_t *spot1;
|
|
edict_t *spot2;
|
|
float range;
|
|
float range1;
|
|
float range2;
|
|
int count = 0;
|
|
int selection;
|
|
|
|
spot = NULL;
|
|
range1 = range2 = 99999;
|
|
spot1 = spot2 = NULL;
|
|
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
|
|
{
|
|
count++;
|
|
range = PlayersRangeFromSpot(spot);
|
|
if (range < range1)
|
|
{
|
|
range1 = range;
|
|
spot1 = spot;
|
|
}
|
|
else if (range < range2)
|
|
{
|
|
range2 = range;
|
|
spot2 = spot;
|
|
}
|
|
}
|
|
|
|
if (!count)
|
|
return NULL;
|
|
|
|
if (count <= 2)
|
|
{
|
|
spot1 = spot2 = NULL;
|
|
}
|
|
else
|
|
count -= 2;
|
|
|
|
selection = rand() % count;
|
|
|
|
spot = NULL;
|
|
do
|
|
{
|
|
spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
|
|
if ((spot == spot1) || (spot == spot2))
|
|
selection++;
|
|
} while (selection--);
|
|
|
|
return spot;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SelectFarthestDeathmatchSpawnPoint
|
|
|
|
================
|
|
*/
|
|
edict_t *SelectFarthestDeathmatchSpawnPoint(void)
|
|
{
|
|
edict_t *bestspot;
|
|
edict_t *spot;
|
|
float bestdistance;
|
|
float bestplayerdistance;
|
|
|
|
spot = NULL;
|
|
bestspot = NULL;
|
|
bestdistance = 0;
|
|
while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL)
|
|
{
|
|
bestplayerdistance = PlayersRangeFromSpot(spot);
|
|
if (bestplayerdistance > bestdistance)
|
|
{
|
|
bestspot = spot;
|
|
bestdistance = bestplayerdistance;
|
|
}
|
|
}
|
|
|
|
if (bestspot)
|
|
return bestspot;
|
|
|
|
// if there is a player just spawned on each and every start spot
|
|
// we have no choice to turn one into a telefrag meltdown
|
|
spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
|
|
|
|
return spot;
|
|
}
|
|
|
|
edict_t *SelectDeathmatchSpawnPoint(void)
|
|
{
|
|
if ((int)(dmflags->value) & DF_SPAWN_FARTHEST)
|
|
return SelectFarthestDeathmatchSpawnPoint();
|
|
else
|
|
return SelectRandomDeathmatchSpawnPoint();
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
SelectSpawnPoint
|
|
|
|
Chooses a player start position.
|
|
============
|
|
*/
|
|
void SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles)
|
|
{
|
|
edict_t *spot = NULL;
|
|
|
|
//ZOID++
|
|
if (sv_gametype->value == G_CTF)
|
|
spot = SelectCTFSpawnPoint(ent, false); //CW
|
|
//ZOID--
|
|
|
|
//CW++
|
|
else if (sv_gametype->value == G_ASLT)
|
|
spot = SelectASLTSpawnPoint(ent);
|
|
//CW--
|
|
else
|
|
spot = SelectDeathmatchSpawnPoint();
|
|
|
|
// find a single player start spot
|
|
if (!spot)
|
|
{
|
|
while ((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL)
|
|
{
|
|
if (!game.spawnpoint[0] && !spot->targetname)
|
|
break;
|
|
|
|
if (!game.spawnpoint[0] || !spot->targetname)
|
|
continue;
|
|
|
|
if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
|
|
break;
|
|
}
|
|
|
|
if (!spot)
|
|
{
|
|
if (!game.spawnpoint[0])
|
|
{ // there wasn't a spawnpoint without a target, so use any
|
|
spot = G_Find(spot, FOFS(classname), "info_player_start");
|
|
}
|
|
if (!spot)
|
|
gi.error("Couldn't find spawn point %s\n", game.spawnpoint);
|
|
}
|
|
}
|
|
|
|
VectorCopy(spot->s.origin, origin);
|
|
origin[2] += 9.0;
|
|
|
|
//Maj++
|
|
if (ent->isabot)
|
|
origin[2] += 21.0;
|
|
//Maj--
|
|
|
|
VectorCopy(spot->s.angles, angles);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
void InitBodyQue(void)
|
|
{
|
|
edict_t *ent;
|
|
int i;
|
|
|
|
level.body_que = 0;
|
|
for (i = 0; i < BODY_QUEUE_SIZE; ++i)
|
|
{
|
|
ent = G_Spawn();
|
|
ent->classname = "bodyque";
|
|
}
|
|
}
|
|
|
|
void body_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
int n;
|
|
|
|
if (self->health < -40)
|
|
{
|
|
gi.sound(self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
|
for (n = 0; n < 4; ++n)
|
|
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC, 0.0);
|
|
|
|
self->s.origin[2] -= 48.0;
|
|
ThrowClientHead(self, damage);
|
|
self->takedamage = DAMAGE_NO;
|
|
}
|
|
}
|
|
|
|
void CopyToBodyQue(edict_t *ent)
|
|
{
|
|
edict_t *body;
|
|
|
|
// grab a body que and cycle to the next one
|
|
body = &g_edicts[(int)maxclients->value + level.body_que + 1];
|
|
level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
|
|
|
|
gi.unlinkentity(ent);
|
|
|
|
gi.unlinkentity(body);
|
|
body->s = ent->s;
|
|
body->s.number = body - g_edicts;
|
|
|
|
//CW++
|
|
body->s.event = EV_OTHER_TELEPORT;
|
|
//CW--
|
|
|
|
body->svflags = ent->svflags;
|
|
VectorCopy(ent->mins, body->mins);
|
|
VectorCopy(ent->maxs, body->maxs);
|
|
VectorCopy(ent->absmin, body->absmin);
|
|
VectorCopy(ent->absmax, body->absmax);
|
|
VectorCopy(ent->size, body->size);
|
|
body->solid = ent->solid;
|
|
body->clipmask = ent->clipmask;
|
|
body->owner = ent->owner;
|
|
body->movetype = ent->movetype;
|
|
|
|
body->die = body_die;
|
|
body->takedamage = DAMAGE_YES;
|
|
|
|
//CW++
|
|
body->s.modelindex4 = 0;
|
|
//CW--
|
|
|
|
gi.linkentity(body);
|
|
}
|
|
|
|
|
|
void Respawn(edict_t *self)
|
|
{
|
|
if (self->movetype != MOVETYPE_NOCLIP)
|
|
CopyToBodyQue(self);
|
|
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
|
|
//Maj++
|
|
if (self->isabot)
|
|
return;
|
|
//Maj--
|
|
|
|
PutClientInServer(self);
|
|
|
|
// add a teleportation effect
|
|
self->s.event = EV_PLAYER_TELEPORT;
|
|
|
|
// hold in place briefly
|
|
self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
|
|
self->client->ps.pmove.pm_time = 14;
|
|
|
|
self->client->respawn_time = level.time;
|
|
}
|
|
|
|
//==============================================================
|
|
|
|
//CW++
|
|
void player_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
edict_t *agm_enemy;
|
|
float speed;
|
|
int damage;
|
|
|
|
// Sanity checks.
|
|
|
|
if (!other->client)
|
|
return;
|
|
if (!other->inuse)
|
|
return;
|
|
if (other->client->agm_enemy == NULL)
|
|
return;
|
|
|
|
agm_enemy = other->client->agm_enemy;
|
|
if (agm_enemy == self)
|
|
return;
|
|
if (CheckTeamDamage(self, agm_enemy))
|
|
return;
|
|
|
|
// Check for collision with other players when flung/thrown/smashed by an AGM.
|
|
|
|
speed = VectorLength(other->client->oldvelocity);
|
|
if (speed > 400.0)
|
|
{
|
|
damage = (int)(0.1 * (speed - 400.0));
|
|
if (damage < 1)
|
|
damage = 1;
|
|
if (agm_enemy->client->quad_framenum > level.framenum)
|
|
damage *= (int)sv_quad_factor->value;
|
|
|
|
T_Damage(self, other, agm_enemy, other->client->oldvelocity, self->s.origin, vec3_origin, damage, damage, 0, MOD_AGM_HIT);
|
|
T_Damage(other, self, agm_enemy, vec3_origin, other->s.origin, vec3_origin, damage, 0, 0, MOD_AGM_HIT);
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
/*
|
|
===========
|
|
PutClientInServer
|
|
|
|
Called when a player connects to a server or respawns in
|
|
a deathmatch.
|
|
============
|
|
*/
|
|
void PutClientInServer(edict_t *ent)
|
|
{
|
|
client_persistant_t saved;
|
|
client_respawn_t resp;
|
|
gclient_t *client;
|
|
char userinfo[MAX_INFO_STRING];
|
|
vec3_t mins = {-16.0, -16.0, -24.0};
|
|
vec3_t maxs = {16.0, 16.0, 32.0};
|
|
vec3_t spawn_origin;
|
|
vec3_t spawn_angles;
|
|
int index;
|
|
int i;
|
|
|
|
// Find a spawn point. Do it before setting health back up, so the farthest
|
|
// ranging doesn't count this client.
|
|
|
|
SelectSpawnPoint(ent, spawn_origin, spawn_angles);
|
|
|
|
index = ent - g_edicts - 1;
|
|
client = ent->client;
|
|
|
|
// Most client data is wiped every spawn.
|
|
|
|
resp = client->resp; //CW
|
|
memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
|
|
InitClientPersistant (client);
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
|
|
// Clear everything but the persistant data.
|
|
|
|
saved = client->pers;
|
|
memset(client, 0, sizeof(*client));
|
|
client->pers = saved;
|
|
if (client->pers.health <= 0)
|
|
InitClientPersistant (client);
|
|
|
|
client->resp = resp;
|
|
|
|
// Copy some data from the client to the entity.
|
|
|
|
FetchClientEntData(ent);
|
|
|
|
// Clear entity values.
|
|
|
|
ent->groundentity = NULL;
|
|
ent->client = &game.clients[index];
|
|
ent->takedamage = DAMAGE_AIM;
|
|
ent->movetype = MOVETYPE_WALK;
|
|
ent->viewheight = 22;
|
|
ent->inuse = true;
|
|
ent->classname = (ent->isabot)?"AwakenBot":"player"; //CW
|
|
ent->mass = 200;
|
|
ent->solid = SOLID_BBOX;
|
|
ent->deadflag = DEAD_NO;
|
|
ent->air_finished = level.time + 12.0;
|
|
ent->clipmask = MASK_PLAYERSOLID;
|
|
ent->model = "players/male/tris.md2";
|
|
ent->pain = player_pain;
|
|
ent->die = player_die;
|
|
ent->waterlevel = 0;
|
|
ent->watertype = 0;
|
|
ent->flags &= ~FL_NO_KNOCKBACK;
|
|
ent->svflags &= ~SVF_DEADMONSTER;
|
|
|
|
//CW++
|
|
ent->svflags &= ~SVF_NOCLIENT;
|
|
ent->touch = player_touch;
|
|
ent->client->chase_target = NULL;
|
|
|
|
ent->burning = false;
|
|
ent->disintegrated = false;
|
|
ent->gravity = 1.0;
|
|
//CW--
|
|
|
|
//DH++
|
|
ent->client->spycam = NULL;
|
|
ent->client->camplayer = NULL;
|
|
//DH--
|
|
|
|
VectorCopy(mins, ent->mins);
|
|
VectorCopy(maxs, ent->maxs);
|
|
VectorClear(ent->velocity);
|
|
|
|
// Clear playerstate values.
|
|
|
|
memset(&ent->client->ps, 0, sizeof(client->ps));
|
|
client->ps.pmove.origin[0] = spawn_origin[0] * 8;
|
|
client->ps.pmove.origin[1] = spawn_origin[1] * 8;
|
|
client->ps.pmove.origin[2] = spawn_origin[2] * 8;
|
|
|
|
//ZOID++
|
|
client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
//ZOID--
|
|
|
|
if ((int)dmflags->value & DF_FIXED_FOV) //CW
|
|
client->ps.fov = 90.0;
|
|
else
|
|
{
|
|
client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
|
|
if (client->ps.fov < 1.0)
|
|
client->ps.fov = 90.0;
|
|
else if (client->ps.fov > 160.0)
|
|
client->ps.fov = 160.0;
|
|
}
|
|
|
|
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
|
|
|
|
// Clear entity state values.
|
|
|
|
ent->s.effects = 0;
|
|
ent->s.skinnum = ent - g_edicts - 1;
|
|
ent->s.modelindex = (MAX_MODELS-1); // will use the skin specified model // was 255
|
|
ent->s.modelindex2 = (MAX_MODELS-1); // custom gun model // was 255
|
|
ent->s.skinnum = ent - g_edicts - 1; // sknum is player num and weapon number
|
|
// weapon number will be added in changeweapon
|
|
|
|
ent->s.frame = 0;
|
|
VectorCopy(spawn_origin, ent->s.origin);
|
|
ent->s.origin[2] += 1.0; // make sure off ground
|
|
VectorCopy(ent->s.origin, ent->s.old_origin);
|
|
|
|
// Set the delta angle.
|
|
|
|
for (i = 0; i < 3; ++i)
|
|
client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
|
|
|
|
ent->s.angles[PITCH] = 0.0;
|
|
ent->s.angles[YAW] = spawn_angles[YAW];
|
|
ent->s.angles[ROLL] = 0.0;
|
|
VectorCopy(ent->s.angles, client->ps.viewangles);
|
|
VectorCopy(ent->s.angles, client->v_angle);
|
|
|
|
//Maj++
|
|
SetBotThink(ent);
|
|
//Maj--
|
|
|
|
//ZOID++
|
|
if (CTFStartClient(ent))
|
|
return;
|
|
//ZOID--
|
|
|
|
if (!KillBox(ent))
|
|
{ // could't spawn in?
|
|
}
|
|
|
|
gi.linkentity(ent);
|
|
|
|
//CW++
|
|
// Force-set client aliases for off-hand grapple and AGM commands.
|
|
// (Moved after CTFStartClient() call to prevent "Info string..." errors).
|
|
|
|
if (!ent->isabot && !ent->client->resp.aliases_set)
|
|
{
|
|
StuffCmd(ent, "alias +hook cmd hook\n");
|
|
StuffCmd(ent, "alias -hook cmd unhook\n");
|
|
|
|
StuffCmd(ent, "alias +push cmd push\n");
|
|
StuffCmd(ent, "alias -push cmd unpush\n");
|
|
StuffCmd(ent, "alias +pull cmd pull\n");
|
|
StuffCmd(ent, "alias -pull cmd unpull\n");
|
|
|
|
ent->client->resp.aliases_set = true;
|
|
}
|
|
|
|
// Set respawn invulnerability, if appropriate.
|
|
|
|
client->invincible_framenum = level.framenum + (int)(10.0 * sv_respawn_invuln_time->value);
|
|
|
|
// In the game, so not a spectator.
|
|
|
|
client->spectator = false;
|
|
//CW--
|
|
|
|
// Force the current weapon up.
|
|
|
|
client->newweapon = client->pers.weapon;
|
|
ChangeWeapon(ent);
|
|
|
|
//CW++ Determine if we should have the leader indicator.
|
|
|
|
if (sv_gametype->value == G_FFA)
|
|
{
|
|
if ((level.leader == NULL) || (level.leader == ent))
|
|
{
|
|
level.leader = ent;
|
|
level.lead_score = ent->client->resp.score;
|
|
if ((int)sv_show_leader->value)
|
|
ent->s.modelindex4 = gi.modelindex("models/halo/tris.md2");
|
|
}
|
|
else if (ent->client->resp.score > level.lead_score) // just joined, and everyone else has -ve frags
|
|
{
|
|
level.leader->s.modelindex4 = 0;
|
|
level.leader = ent;
|
|
level.lead_score = ent->client->resp.score;
|
|
if ((int)sv_show_leader->value)
|
|
ent->s.modelindex4 = gi.modelindex("models/halo/tris.md2");
|
|
}
|
|
}
|
|
//CW--
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
ClientBeginDeathmatch
|
|
|
|
A client has just connected to the server in
|
|
deathmatch mode, so clear everything out before starting them.
|
|
=====================
|
|
*/
|
|
void ClientBeginDeathmatch(edict_t *ent)
|
|
{
|
|
G_InitEdict(ent);
|
|
InitClientResp(ent->client);
|
|
|
|
// locate ent at a spawn point
|
|
PutClientInServer(ent);
|
|
|
|
if (level.intermissiontime)
|
|
MoveClientToIntermission(ent);
|
|
else
|
|
{
|
|
// send effect
|
|
//CW++
|
|
if (!ent->client->spectator)
|
|
{
|
|
//CW--
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteShort(ent-g_edicts);
|
|
gi.WriteByte(MZ_LOGIN);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS);
|
|
}
|
|
}
|
|
|
|
gi_bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
|
|
|
|
// make sure all view stuff is valid
|
|
ClientEndServerFrame(ent);
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
ClientBegin
|
|
|
|
called when a client has finished connecting, and is ready
|
|
to be placed into the game. This will happen every level load.
|
|
============
|
|
*/
|
|
void ClientBegin(edict_t *ent)
|
|
{
|
|
//CW++
|
|
edict_t *timer;
|
|
//CW--
|
|
|
|
ent->client = game.clients + (ent - g_edicts - 1);
|
|
ClientBeginDeathmatch(ent); //CW
|
|
|
|
//CW++
|
|
if (ent->isabot)
|
|
return;
|
|
|
|
timer = G_Spawn();
|
|
timer->svflags |= SVF_NOCLIENT;
|
|
timer->target_ent = ent;
|
|
timer->message = "exec autoawaken.cfg\n";
|
|
timer->think = StuffCmd_Ent;
|
|
timer->nextthink = level.time + 1.0;
|
|
//CW--
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientUserInfoChanged
|
|
|
|
called whenever the player updates a userinfo variable.
|
|
|
|
The game can override any of the settings in place
|
|
(forcing skins or names, etc) before copying it off.
|
|
============
|
|
*/
|
|
void ClientUserinfoChanged (edict_t *ent, char *userinfo)
|
|
{
|
|
char *s;
|
|
int playernum;
|
|
|
|
// check for malformed or illegal info strings
|
|
if (!Info_Validate(userinfo)) {
|
|
Com_strcpy(userinfo, MAX_INFO_STRING, "\\name\\badinfo\\skin\\male/grunt"); // userinfo is always length of MAX_INFO_STRING
|
|
}
|
|
|
|
// set name
|
|
s = Info_ValueForKey(userinfo, "name");
|
|
strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1);
|
|
|
|
// set skin
|
|
s = Info_ValueForKey(userinfo, "skin");
|
|
playernum = ent - g_edicts - 1;
|
|
|
|
// combine name and skin into a configstring
|
|
//ZOID++
|
|
if ((int)sv_gametype->value > G_FFA) //CW
|
|
CTFAssignSkin(ent, s);
|
|
else
|
|
//ZOID--
|
|
{
|
|
//CW++
|
|
// prevent potential configstring overflow (netname+s)
|
|
if (strlen(ent->client->pers.netname) + strlen(s) + 4 > MAX_SKINLEN - 1) //r1,CW
|
|
{
|
|
gi_cprintf(ent, PRINT_HIGH, "Skin name is too long.\n");
|
|
s = "male/grunt";
|
|
}
|
|
//CW--
|
|
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s));
|
|
}
|
|
|
|
//ZOID++
|
|
// set player name field (used in id_state view)
|
|
gi.configstring(CS_GENERAL+playernum, ent->client->pers.netname);
|
|
//ZOID--
|
|
|
|
// fov
|
|
if ((int)dmflags->value & DF_FIXED_FOV) //CW
|
|
ent->client->ps.fov = 90.0;
|
|
else
|
|
{
|
|
ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
|
|
if (ent->client->ps.fov < 1.0)
|
|
ent->client->ps.fov = 90.0;
|
|
else if (ent->client->ps.fov > 160.0)
|
|
ent->client->ps.fov = 160.0;
|
|
}
|
|
|
|
// handedness
|
|
s = Info_ValueForKey(userinfo, "hand");
|
|
if (strlen(s))
|
|
ent->client->pers.hand = atoi(s);
|
|
|
|
// Knightmare- custom colors
|
|
s = Info_ValueForKey (userinfo, "color1");
|
|
if (strlen(s) >= 6) {
|
|
if ( Com_ParseColorString (s, ent->client->pers.color1) )
|
|
ent->client->pers.color1[3] = 255; // mark as set
|
|
}
|
|
|
|
s = Info_ValueForKey (userinfo, "color2");
|
|
if (strlen(s) >= 6) {
|
|
if ( Com_ParseColorString (s, ent->client->pers.color2) )
|
|
ent->client->pers.color2[3] = 255; // mark as set
|
|
}
|
|
|
|
//CW++
|
|
if (strlen(ent->client->pers.old_name) == 0)
|
|
strncpy(ent->client->pers.old_name, ent->client->pers.netname, sizeof(ent->client->pers.netname));
|
|
//CW--
|
|
|
|
// save off the userinfo in case we want to check something later
|
|
strncpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
ClientConnect
|
|
|
|
Called when a player begins connecting to the server.
|
|
The game can refuse entrance to a client by returning false.
|
|
If the client is allowed, the connection process will continue
|
|
and eventually get to ClientBegin()
|
|
Changing levels will NOT cause this to be called again, but
|
|
loadgames will.
|
|
============
|
|
*/
|
|
qboolean ClientConnect(edict_t *ent, char *userinfo)
|
|
{
|
|
char *value;
|
|
|
|
//CW++
|
|
char *tname;
|
|
qboolean cl_rsv = false;
|
|
|
|
if (Info_Validate(userinfo))
|
|
tname = Info_ValueForKey(userinfo, "name"); //r1: buffer overflow fix (tname)
|
|
else
|
|
tname = "(Unknown)";
|
|
|
|
// If the 'isabot' flag is set, the client is trying to connect into a slot that's occupied
|
|
// by an AwakenBot, ie. there are no free slots left.
|
|
|
|
if (ent->isabot)
|
|
{
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "All player slots are taken.");
|
|
gi.dprintf("** %s : connection refused (no free slots).\n", tname);
|
|
return false;
|
|
}
|
|
//CW--
|
|
|
|
// Check to see if they are on the banned IP list.
|
|
|
|
//SNX++ better way of getting IP
|
|
value = strstr(userinfo,"\\ip\\");
|
|
|
|
if (value == NULL)
|
|
{
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo,"rejmsg","Your userinfo string is malformed, please restart Quake 2.");
|
|
return false;
|
|
}
|
|
//ip will always be the final thing in the userinfo string
|
|
value += 4;
|
|
//SNX--
|
|
|
|
if (SV_FilterPacket(value))
|
|
{
|
|
//CW++
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "You have been banned.");
|
|
gi.dprintf("** %s [%s]: connection refused (banned).\n", tname, ent->client->pers.ip);
|
|
//CW--
|
|
return false;
|
|
}
|
|
|
|
//CW++
|
|
// Reject clients with blank IP addresses if flagged to do so.
|
|
|
|
if ((int)sv_reject_blank_ip->value && (strlen(value) == 0))
|
|
{
|
|
//CW++
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "You have a blank IP address (forbidden).");
|
|
gi.dprintf("** %s : connection refused (blank IP).\n", tname);
|
|
//CW--
|
|
return false;
|
|
}
|
|
|
|
// Need to store their IP separately, because if the client's userinfo changes later (eg. they
|
|
// change their name), then their IP value is blanked.
|
|
|
|
strncpy(ent->client->pers.ip, value, sizeof(ent->client->pers.ip));
|
|
//CW--
|
|
|
|
// Check for a password, or a slot reserved password.
|
|
|
|
value = Info_ValueForKey(userinfo, "password");
|
|
if (*password->string && strcmp(password->string, "none")) //CW
|
|
{ //
|
|
if (strcmp(password->string, value)) //
|
|
{
|
|
//CW++
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
|
|
gi.dprintf("** %s [%s]: connection refused (wrong password).\n", tname, ent->client->pers.ip);
|
|
|
|
// prevent "ghost client" crashes
|
|
ent->solid = SOLID_NOT;
|
|
ent->inuse = false;
|
|
ent->s.effects = 0;
|
|
ent->s.event = 0;
|
|
ent->s.modelindex = 0;
|
|
ent->s.modelindex2 = 0;
|
|
ent->s.modelindex3 = 0;
|
|
ent->s.modelindex4 = 0;
|
|
ent->s.sound = 0;
|
|
//CW--
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//CW++
|
|
// If a server password isn't set, check to see if the "reserved slots" feature is being flagged
|
|
// for use by the server. If the Operator password is set, check to see if the player's password
|
|
// is set to it. If so, try to assign them to a reserved slot first (to keep the public ones free).
|
|
// If there are no free reserved slots, assign them to a public slot. Note that if all the public
|
|
// slots are full at this point, they won't even get passed into this function - they'll get a
|
|
// "server is full" message automatically (from their Q2 EXE).
|
|
// NOTE: array setup = [<--sv_reserved-->|<--(maxclients - sv_reserved)-->]
|
|
|
|
else if (((int)sv_reserved->value > 0) && *sv_rsv_password->string)
|
|
{
|
|
if (!strcmp(sv_rsv_password->string, value))
|
|
{ // reserve password is correct (so try to get them into a reserved slot)
|
|
if (g_reserve_used < (int)sv_reserved->value)
|
|
{ // reserved slot
|
|
g_slots[g_reserve_used] = ent;
|
|
++g_reserve_used;
|
|
cl_rsv = true;
|
|
}
|
|
else
|
|
{
|
|
if (g_public_used < (int)maxclients->value - (int)sv_reserved->value)
|
|
{ // public slot
|
|
g_slots[(int)sv_reserved->value + g_public_used] = ent;
|
|
++g_public_used;
|
|
}
|
|
else
|
|
{ // no free slots
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "All player slots are taken.");
|
|
gi.dprintf("** %s : connection refused (no free slots).\n", tname);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // can use public slots only
|
|
if (g_public_used < (int)maxclients->value - (int)sv_reserved->value)
|
|
{ // public slot
|
|
g_slots[(int)sv_reserved->value + g_public_used] = ent;
|
|
++g_public_used;
|
|
}
|
|
else
|
|
{ // no free slots
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "All player slots are taken.");
|
|
gi.dprintf("** %s : connection refused (no free slots).\n", tname);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// public slots only
|
|
if (g_public_used < (int)maxclients->value)
|
|
{ // public slot
|
|
g_slots[g_public_used] = ent;
|
|
++g_public_used;
|
|
}
|
|
else
|
|
{ // no free slots
|
|
userinfo[0] = '\0';
|
|
Info_SetValueForKey(userinfo, "rejmsg", "All player slots are taken.");
|
|
gi.dprintf("** %s : connection refused (no free slots).\n", tname);
|
|
return false;
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
// They can connect.
|
|
|
|
ent->client = game.clients + (ent - g_edicts - 1);
|
|
|
|
// If there is already a body waiting for us (a loadgame), just take it,
|
|
// otherwise spawn one from scratch.
|
|
|
|
if (ent->inuse == false)
|
|
{
|
|
|
|
// Clear the respawning variables.
|
|
|
|
//ZOID++
|
|
// Force team join.
|
|
|
|
ent->client->resp.ctf_team = -1;
|
|
ent->client->resp.id_state = false; //CW
|
|
//ZOID--
|
|
InitClientResp(ent->client);
|
|
if (!game.autosaved || !ent->client->pers.weapon)
|
|
InitClientPersistant(ent->client);
|
|
}
|
|
|
|
//CW++
|
|
// We don't need to do the full ClientUserinfoChanged() function here, as this sends
|
|
// config strings unnecessarily.
|
|
|
|
if (!Info_Validate(userinfo)) {
|
|
Com_strcpy(userinfo, MAX_INFO_STRING, "\\name\\badinfo\\skin\\male/grunt"); // userinfo is always length of MAX_INFO_STRING
|
|
}
|
|
|
|
strncpy(ent->client->pers.netname, Info_ValueForKey(userinfo, "name"), sizeof(ent->client->pers.netname)-1);
|
|
strncpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
|
|
if (strlen(ent->client->pers.old_name) == 0)
|
|
strncpy(ent->client->pers.old_name, ent->client->pers.netname, sizeof(ent->client->pers.netname));
|
|
//CW--
|
|
|
|
if (game.maxclients > 1)
|
|
gi.dprintf("%s connected %s\n", ent->client->pers.netname, (cl_rsv)?"--> reserved slot":"");
|
|
|
|
ent->client->pers.connected = true;
|
|
|
|
//CW++
|
|
gi.dprintf(" (IP = %s)\n", ent->client->pers.ip);
|
|
|
|
ent->client->resp.id_trap = true; // default = on
|
|
ent->client->pers.weap_note = 1; // default = text only
|
|
//CW--
|
|
|
|
return true;
|
|
}
|
|
|
|
//CW++
|
|
/*
|
|
===========
|
|
ClearSlot
|
|
|
|
Removes the specified player from the global
|
|
player slots array, and reorders the array.
|
|
|
|
array setup = [<--sv_reserved-->|<--(maxclients - sv_reserved)-->]
|
|
============
|
|
*/
|
|
void ClearSlot(edict_t *ent)
|
|
{
|
|
int i;
|
|
int n;
|
|
|
|
for (i = 0; i < MAX_CLIENTS; ++i)
|
|
{
|
|
if (ent == g_slots[i])
|
|
{
|
|
if (((int)sv_reserved->value > 0) && *sv_rsv_password->string)
|
|
{ // reserved slots feature is in use
|
|
if (i < (int)sv_reserved->value)
|
|
{ // client has a reserved slot
|
|
if (g_reserve_used > 1)
|
|
{
|
|
if (i == (int)sv_reserved->value - 1)
|
|
g_slots[i] = NULL;
|
|
else
|
|
{
|
|
for (n = i; n < g_reserve_used - 1; ++n)
|
|
g_slots[n] = g_slots[n+1];
|
|
|
|
g_slots[n] = NULL;
|
|
}
|
|
}
|
|
else if (g_reserve_used == 1)
|
|
g_slots[0] = NULL;
|
|
else
|
|
{ // should never happen
|
|
gi.dprintf("BUG: g_reserve_used <= 0 in ClearSlot()\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
--g_reserve_used;
|
|
break;
|
|
}
|
|
else
|
|
{ //client has a public slot
|
|
if (g_public_used > 1)
|
|
{
|
|
if (i == (int)maxclients->value - 1)
|
|
g_slots[i] = NULL;
|
|
else
|
|
{
|
|
for (n = i; n < (int)sv_reserved->value + g_public_used - 1; ++n)
|
|
g_slots[n] = g_slots[n+1];
|
|
|
|
g_slots[n] = NULL;
|
|
}
|
|
}
|
|
else if (g_public_used == 1)
|
|
g_slots[(int)sv_reserved->value] = NULL;
|
|
else
|
|
{ // should never happen
|
|
gi.dprintf("BUG: g_public_used <= 0 in ClearSlot()\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
--g_public_used;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{ // public slots only (so client must have a public slot)
|
|
if (g_public_used > 1)
|
|
{
|
|
if (i == (int)maxclients->value - 1)
|
|
g_slots[i] = NULL;
|
|
else
|
|
{
|
|
for (n = i; n < (int)sv_reserved->value + g_public_used - 1; ++n)
|
|
g_slots[n] = g_slots[n+1];
|
|
|
|
g_slots[n] = NULL;
|
|
}
|
|
}
|
|
else if (g_public_used == 1)
|
|
g_slots[(int)sv_reserved->value] = NULL;
|
|
else
|
|
{ // should never happen
|
|
gi.dprintf("BUG: g_public_used <= 0 in ClearSlot()\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
--g_public_used;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
/*
|
|
===========
|
|
ClientDisconnect
|
|
|
|
Called when a player drops from the server.
|
|
Will not be called between levels.
|
|
============
|
|
*/
|
|
void ClientDisconnect(edict_t *ent)
|
|
{
|
|
//CW++
|
|
edict_t *index;
|
|
edict_t *check;
|
|
qboolean finished = false;
|
|
int i;
|
|
//CW--
|
|
int playernum;
|
|
|
|
if (!ent->client)
|
|
return;
|
|
|
|
//CW++
|
|
if (ent->isabot)
|
|
gi_bprintf(PRINT_HIGH, "%s was removed\n", ent->client->pers.netname);
|
|
else
|
|
//CW--
|
|
gi_bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
|
|
|
|
//CW++
|
|
ent->s.sound = 0;
|
|
ent->s.effects = 0;
|
|
ent->s.event = 0;
|
|
|
|
ClearSlot(ent);
|
|
|
|
// Destroy any traps that are locked on to us with their tractor-beam.
|
|
|
|
if (ent->tractored)
|
|
{
|
|
ent->tractored = false;
|
|
for (i = 0; i < globals.num_edicts; ++i)
|
|
{
|
|
index = &g_edicts[i];
|
|
if (!index->inuse)
|
|
continue;
|
|
if (index->client)
|
|
continue;
|
|
if (!index->die)
|
|
continue;
|
|
|
|
if ((index->die == Trap_DieFromDamage) && (index->enemy == ent))
|
|
{
|
|
index->think = Trap_Die;
|
|
index->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
}
|
|
|
|
ent->disintegrated = false;
|
|
ent->show_hostile = false;
|
|
ent->client->resp.score = -999999;
|
|
ent->client->resp.ctf_team = CTF_NOTEAM;
|
|
|
|
// Snuff out flame if player is on fire.
|
|
|
|
if (ent->burning)
|
|
{
|
|
ent->burning = false;
|
|
if (ent->flame) // sanity check
|
|
{
|
|
ent->flame->touch = NULL;
|
|
Flame_Expire(ent->flame);
|
|
}
|
|
}
|
|
|
|
// Search through the player's linked list of Trap and C4 entities (if any), and pop them.
|
|
|
|
if (ent->next_node)
|
|
{
|
|
index = ent->next_node;
|
|
while (index && !finished)
|
|
{
|
|
check = index;
|
|
if (index->next_node)
|
|
index = index->next_node;
|
|
else
|
|
finished = true;
|
|
|
|
if (check->die == C4_DieFromDamage)
|
|
C4_Die(check);
|
|
else if (check->die == Trap_DieFromDamage)
|
|
Trap_Die(check);
|
|
else
|
|
gi.dprintf("BUG: Invalid next_node pointer in ClientDisconnect().\nPlease contact musashi@planetquake.com\n");
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
//CW++
|
|
ent->client->agm_on = false;
|
|
ent->client->agm_pull = false;
|
|
ent->client->agm_push = false;
|
|
|
|
if (ent->client->agm_target != NULL)
|
|
{
|
|
ent->client->agm_target->client->held_by_agm = false;
|
|
ent->client->agm_target->client->flung_by_agm = false;
|
|
ent->client->agm_target = NULL;
|
|
}
|
|
//CW--
|
|
|
|
//ZOID++
|
|
CTFDeadDropFlag(ent);
|
|
CTFDeadDropTech(ent);
|
|
//ZOID--
|
|
|
|
// send effect
|
|
//CW++
|
|
if (!ent->client->spectator)
|
|
{
|
|
//CW--
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteShort(ent-g_edicts);
|
|
gi.WriteByte(MZ_LOGOUT);
|
|
gi.multicast(ent->s.origin, MULTICAST_PVS);
|
|
}
|
|
|
|
gi.unlinkentity(ent);
|
|
ent->s.modelindex = 0;
|
|
|
|
//CW++
|
|
ent->s.modelindex2 = 0;
|
|
ent->s.modelindex3 = 0;
|
|
ent->s.modelindex4 = 0;
|
|
|
|
ent->client->pers.op_status = false;
|
|
ent->client->pers.muted = false;
|
|
|
|
ent->s.solid = 0;
|
|
//CW--
|
|
|
|
ent->solid = SOLID_NOT;
|
|
ent->inuse = false;
|
|
ent->classname = "disconnected";
|
|
ent->client->pers.connected = false;
|
|
|
|
//DH++
|
|
if (ent->client->spycam)
|
|
camera_off(ent);
|
|
//DH--
|
|
|
|
//CW++
|
|
if (sv_gametype->value == G_FFA)
|
|
{
|
|
if (level.leader == ent)
|
|
{
|
|
level.leader = NULL;
|
|
if ((check = FindLeader()) != NULL)
|
|
{
|
|
if ((int)sv_show_leader->value)
|
|
check->s.modelindex4 = gi.modelindex("models/halo/tris.md2");
|
|
|
|
level.leader = check;
|
|
level.lead_score = check->client->resp.score;
|
|
}
|
|
else
|
|
level.lead_score = 0;
|
|
}
|
|
}
|
|
|
|
if (ent->client->chase_target)
|
|
{
|
|
ent->client->chase_target = NULL;
|
|
ent->client->update_chase = false;
|
|
}
|
|
|
|
// If we're being watched by someone, deactivate their chase-cam.
|
|
|
|
for (i = 0; i < game.maxclients; ++i)
|
|
{
|
|
check = g_edicts + 1 + i;
|
|
if (!check->client)
|
|
continue;
|
|
if (!check->inuse)
|
|
continue;
|
|
if (check == ent)
|
|
continue;
|
|
|
|
if (check->client->chase_target && (check->client->chase_target == ent))
|
|
ToggleChaseCam(check, NULL);
|
|
}
|
|
|
|
ent->svflags = 0;
|
|
ent->flags = 0;
|
|
//CW--
|
|
|
|
playernum = ent - g_edicts - 1;
|
|
gi.configstring(CS_PLAYERSKINS+playernum, "");
|
|
}
|
|
|
|
|
|
//==============================================================
|
|
|
|
|
|
edict_t *pm_passent;
|
|
|
|
// pmove doesn't need to know about passent and contentmask
|
|
trace_t PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
|
|
{
|
|
if (pm_passent->health > 0)
|
|
return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
|
|
else
|
|
return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
|
|
}
|
|
|
|
unsigned CheckBlock(void *b, int c)
|
|
{
|
|
int v;
|
|
int i;
|
|
|
|
v = 0;
|
|
for (i = 0; i < c; ++i)
|
|
v += ((byte *)b)[i];
|
|
|
|
return v;
|
|
}
|
|
|
|
void PrintPmove(pmove_t *pm)
|
|
{
|
|
unsigned c1;
|
|
unsigned c2;
|
|
|
|
c1 = CheckBlock(&pm->s, sizeof(pm->s));
|
|
c2 = CheckBlock(&pm->cmd, sizeof(pm->cmd));
|
|
Com_Printf("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
|
|
}
|
|
|
|
//DH++
|
|
void ClientSpycam(edict_t *ent)
|
|
{
|
|
gclient_t *client = ent->client;
|
|
edict_t *camera = ent->client->spycam;
|
|
pmove_t pm;
|
|
trace_t tr;
|
|
vec3_t forward;
|
|
vec3_t left;
|
|
vec3_t up;
|
|
vec3_t dir;
|
|
vec3_t start;
|
|
float dist;
|
|
int i;
|
|
|
|
memset(&pm, 0, sizeof(pm));
|
|
if (client->ucmd.sidemove && (level.time > ent->last_move_time + 1.0))
|
|
{
|
|
if (camera->viewer == ent)
|
|
camera->viewer = NULL;
|
|
|
|
if (client->ucmd.sidemove > 0)
|
|
camera = G_FindNextCamera(camera, client->monitor);
|
|
else
|
|
camera = G_FindPrevCamera(camera, client->monitor);
|
|
|
|
if (camera)
|
|
{
|
|
if (!camera->viewer)
|
|
camera->viewer = ent;
|
|
|
|
client->spycam = camera;
|
|
VectorAdd(camera->s.origin, camera->move_origin, ent->s.origin);
|
|
if (camera->viewmessage)
|
|
gi_centerprintf(ent,camera->viewmessage);
|
|
|
|
ent->last_move_time = level.time;
|
|
}
|
|
else
|
|
camera = client->spycam;
|
|
}
|
|
|
|
if (camera->enemy && (camera->enemy->deadflag || !camera->enemy->inuse))
|
|
camera->enemy = NULL;
|
|
|
|
AngleVectors(camera->s.angles, forward, left, up);
|
|
client->ps.pmove.pm_type = PM_FREEZE;
|
|
if (camera->viewer == ent)
|
|
{
|
|
if ((client->ucmd.buttons & BUTTON_ATTACK) && (camera->sounds > -1)) //CW
|
|
{
|
|
if (level.time + 0.001 >= camera->monsterinfo.attack_finished) //CW: account for fp error
|
|
{
|
|
client->latched_buttons &= ~BUTTON_ATTACK;
|
|
if (!Q_stricmp(camera->classname, "turret_breach")
|
|
|| !Q_stricmp(camera->classname, "model_turret")) //CW
|
|
{
|
|
if (camera->sounds == 3) //CW
|
|
camera->monsterinfo.attack_finished = level.time + FRAMETIME; //CW
|
|
else
|
|
camera->monsterinfo.attack_finished = level.time + camera->wait; //CW
|
|
|
|
turret_breach_fire(camera);
|
|
//CW++
|
|
if (ent->client->camplayer == NULL) // player has shot themselves with the turret (well done, llama!)
|
|
return;
|
|
//CW--
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorMA(camera->s.origin, camera->move_origin[0],forward,start);
|
|
VectorMA(start, -camera->move_origin[1],left, start);
|
|
VectorMA(start, camera->move_origin[2],up, start);
|
|
|
|
tr = gi.trace(camera->s.origin, NULL, NULL, start, camera, MASK_SOLID);
|
|
if (tr.fraction < 1.0)
|
|
{
|
|
VectorSubtract(tr.endpos, camera->s.origin, dir);
|
|
dist = VectorNormalize(dir) - 2.0;
|
|
if (dist < 0.0)
|
|
dist = 0.0;
|
|
VectorMA(camera->s.origin, dist, dir, start);
|
|
}
|
|
VectorCopy(start, ent->s.origin);
|
|
VectorCopy(camera->velocity, ent->velocity);
|
|
|
|
client->resp.cmd_angles[0] = SHORT2ANGLE(client->ucmd.angles[0]);
|
|
client->resp.cmd_angles[1] = SHORT2ANGLE(client->ucmd.angles[1]);
|
|
client->resp.cmd_angles[2] = SHORT2ANGLE(client->ucmd.angles[2]);
|
|
|
|
memset(&pm, 0, sizeof(pm));
|
|
pm.s = client->ps.pmove;
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
pm.s.origin[i] = ent->s.origin[i] * 8;
|
|
client->ps.pmove.delta_angles[i] = ANGLE2SHORT(client->ps.viewangles[i] - client->resp.cmd_angles[i]);
|
|
}
|
|
|
|
if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
|
|
pm.snapinitial = true;
|
|
|
|
pm.cmd = client->ucmd;
|
|
pm.trace = PM_trace; // adds default parms
|
|
pm.pointcontents = gi.pointcontents;
|
|
gi.Pmove(&pm);
|
|
|
|
gi.linkentity(ent);
|
|
G_TouchTriggers(ent); // we'll only allow touching trigger_look with "Cam Owner" SF
|
|
|
|
}
|
|
//DH--
|
|
|
|
/*
|
|
==============
|
|
ClientThink
|
|
|
|
This will be called once for each client frame, which will
|
|
usually be a couple times for each server frame.
|
|
==============
|
|
*/
|
|
void ClientThink(edict_t *ent, usercmd_t *ucmd)
|
|
{
|
|
gclient_t *client;
|
|
edict_t *other;
|
|
pmove_t pm;
|
|
int i;
|
|
int j;
|
|
|
|
//CW++
|
|
qboolean cam_user = false;
|
|
//CW--
|
|
|
|
//Pon++
|
|
static edict_t *old_ground;
|
|
static qboolean wasground;
|
|
|
|
if ((int)chedit->value && (CurrentIndex < MAXNODES) && !ent->deadflag && (ent == &g_edicts[1]))
|
|
{
|
|
trace_t rs_trace;
|
|
vec3_t min;
|
|
vec3_t max;
|
|
vec3_t v;
|
|
vec3_t vv;
|
|
float x;
|
|
int oldwaterstate;
|
|
int k;
|
|
int l;
|
|
|
|
oldwaterstate = ent->client->waterstate;
|
|
Get_WaterState(ent);
|
|
i = false;
|
|
l = GRS_NORMAL;
|
|
|
|
if (CurrentIndex > 0)
|
|
Get_RouteOrigin(CurrentIndex - 1, v);
|
|
|
|
if (!Route[CurrentIndex].index)
|
|
{
|
|
VectorCopy(ent->s.origin, v);
|
|
old_ground = ent->groundentity;
|
|
if (ent->groundentity)
|
|
i = true;
|
|
}
|
|
else if (!TraceX(ent, v))
|
|
{
|
|
VectorCopy(ent->s.old_origin, v);
|
|
i = 3;
|
|
}
|
|
else if (ent->client->waterstate != oldwaterstate)
|
|
{
|
|
i = true;
|
|
if (ent->groundentity)
|
|
{
|
|
if ((ent->groundentity->blocked == train_blocked) || (ent->groundentity->blocked == plat_blocked) || ((ent->groundentity->blocked == door_blocked) && (ent->groundentity->style != ENT_ID_DOOR_ROTATING))) //CW
|
|
i = false;
|
|
}
|
|
|
|
if (ent->client->waterstate > oldwaterstate)
|
|
VectorCopy(ent->s.origin, v);
|
|
else
|
|
VectorCopy(ent->s.old_origin, v);
|
|
}
|
|
else if (fabs(v[2] - ent->s.origin[2]) > 20)
|
|
{
|
|
if (ent->groundentity && (ent->waterlevel < 2))
|
|
{
|
|
i = true;
|
|
k = true;
|
|
VectorCopy(ent->s.origin, v);
|
|
}
|
|
|
|
}
|
|
else if(((!ent->groundentity && (wasground == true)) || (ent->groundentity && (wasground == false))) && (Route[CurrentIndex - 1].state <= GRS_ITEMS))
|
|
{
|
|
j = false;
|
|
k = true;
|
|
VectorCopy(ent->s.old_origin, v);
|
|
v[2] -= 2;
|
|
|
|
rs_trace = gi.trace(ent->s.old_origin, ent->mins, ent->maxs, v, ent, MASK_PLAYERSOLID);
|
|
if (rs_trace.fraction != 1.0)
|
|
j = true;
|
|
|
|
if (old_ground)
|
|
{
|
|
if ((old_ground->blocked == train_blocked) || (old_ground->blocked == plat_blocked) || ((old_ground->blocked == door_blocked) && (old_ground->style != ENT_ID_DOOR_ROTATING))) //CW
|
|
k = false;
|
|
}
|
|
|
|
if (!ent->groundentity && (wasground == true) && k)
|
|
{
|
|
VectorCopy(ent->s.old_origin, v);
|
|
i = true;
|
|
}
|
|
else if (ent->groundentity && (wasground == false) && k)
|
|
{
|
|
VectorCopy(ent->s.origin, v);
|
|
i = true;
|
|
}
|
|
}
|
|
else if (Route[CurrentIndex-1].index > 1)
|
|
{
|
|
k = true;
|
|
Get_RouteOrigin(CurrentIndex - 1, min);
|
|
Get_RouteOrigin(CurrentIndex - 2, max);
|
|
VectorSubtract(min, max, v);
|
|
x = Get_yaw(v);
|
|
VectorSubtract(ent->s.origin, ent->s.old_origin, v);
|
|
if ((VectorLength(v) > 0) && (Get_vec_yaw(v, x) > 45) && k)
|
|
{
|
|
VectorCopy(ent->s.old_origin, v);
|
|
i = true;
|
|
}
|
|
}
|
|
|
|
if (ent->groundentity)
|
|
{
|
|
if (ent->groundentity != old_ground)
|
|
{
|
|
other = old_ground;
|
|
old_ground = ent->groundentity;
|
|
if (old_ground->blocked == plat_blocked) //CW
|
|
{
|
|
if (old_ground->union_ent)
|
|
{
|
|
if (old_ground->union_ent->inuse && (old_ground->union_ent->classname[0] == 'R'))
|
|
{
|
|
VectorCopy(old_ground->monsterinfo.last_sighting, v);
|
|
l = GRS_ONPLAT;
|
|
i = 2;
|
|
}
|
|
}
|
|
}
|
|
else if (old_ground->blocked == train_blocked) //CW
|
|
{
|
|
if (old_ground->union_ent)
|
|
{
|
|
if (old_ground->union_ent->inuse && (old_ground->union_ent->classname[0] == 'R'))
|
|
{
|
|
VectorCopy(old_ground->monsterinfo.last_sighting, v);
|
|
l = GRS_ONTRAIN;
|
|
i = 2;
|
|
}
|
|
}
|
|
}
|
|
//else if (!Q_stricmp(old_ground->classname, "func_door"))
|
|
else if ((old_ground->blocked == door_blocked) && (old_ground->style != ENT_ID_DOOR_ROTATING)) //CW
|
|
{
|
|
k = false;
|
|
if (old_ground->targetname && old_ground->union_ent)
|
|
{
|
|
if (TraceX(ent, old_ground->union_ent->s.origin) && (fabs(ent->s.origin[2] - old_ground->union_ent->s.origin[2]) < JumpMax))
|
|
{
|
|
VectorCopy(old_ground->monsterinfo.last_sighting, v);
|
|
l = GRS_ONDOOR;
|
|
i = 2;
|
|
}
|
|
else
|
|
k = true;
|
|
}
|
|
else
|
|
k = true;
|
|
|
|
if (k && i)
|
|
{
|
|
i = 2;
|
|
old_ground = other;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (old_ground)
|
|
{
|
|
if ((old_ground->classname[0] == 'f') && (i != 2))
|
|
{
|
|
if ((old_ground->blocked == train_blocked) || (old_ground->blocked == plat_blocked) || ((old_ground->blocked == door_blocked) && (old_ground->style != ENT_ID_DOOR_ROTATING))) //CW
|
|
i = false;
|
|
}
|
|
}
|
|
|
|
if ((Route[CurrentIndex-1].index > 0) && (i == true))
|
|
{
|
|
Get_RouteOrigin(CurrentIndex - 1, max);
|
|
VectorSubtract(max, v, vv);
|
|
if (VectorLength(vv) <= 32 )
|
|
i = false;
|
|
}
|
|
|
|
if ((l == GRS_ONTRAIN) || (l == GRS_ONPLAT) || (l == GRS_ONDOOR))
|
|
{
|
|
if (Route[CurrentIndex - 1].ent == old_ground)
|
|
i = false;
|
|
}
|
|
|
|
if (i)
|
|
{
|
|
if ((l == GRS_NORMAL) && ent->groundentity)
|
|
{
|
|
if (!Q_stricmp(old_ground->classname, "func_rotating"))
|
|
l = GRS_ONROTATE;
|
|
}
|
|
|
|
VectorCopy(v, Route[CurrentIndex].Pt);
|
|
Route[CurrentIndex].state = l;
|
|
if ((l > GRS_ITEMS) && (l <= GRS_ONTRAIN))
|
|
Route[CurrentIndex].ent = old_ground;
|
|
else if (l == GRS_ONDOOR)
|
|
Route[CurrentIndex].ent = old_ground;
|
|
|
|
if ((l == GRS_ONTRAIN) && old_ground->trainteam && old_ground->target_ent)
|
|
{
|
|
if (old_ground->target_ent->touch == path_corner_touch) //CW
|
|
VectorCopy(old_ground->target_ent->s.origin, Route[CurrentIndex].Tcorner);
|
|
}
|
|
|
|
//when normal or items
|
|
if (++CurrentIndex < MAXNODES)
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "Last %i pod(s).\n", MAXNODES - CurrentIndex);
|
|
memset(&Route[CurrentIndex], 0, sizeof(route_t)); //initialize
|
|
Route[CurrentIndex].index = Route[CurrentIndex - 1].index + 1;
|
|
}
|
|
}
|
|
|
|
if (ent->groundentity != NULL)
|
|
wasground = true;
|
|
else
|
|
wasground = false;
|
|
}
|
|
//Pon--
|
|
|
|
level.current_entity = ent;
|
|
client = ent->client;
|
|
|
|
//DH++
|
|
client->ucmd = *ucmd; // copy latest usercmd for use in other routines
|
|
|
|
if (ent->turret || client->spectator) //CW
|
|
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
else
|
|
//CW++
|
|
{
|
|
if (!ent->client->ctf_grapple)
|
|
//CW--
|
|
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
}
|
|
|
|
|
|
// Perform special actions taken when +use is pressed.
|
|
|
|
if (!client->use && (ucmd->buttons & BUTTON_USE) && (ent->health > 0)) //CW
|
|
{
|
|
// use key was NOT pressed, but now is
|
|
client->use = 1;
|
|
if (client->spycam)
|
|
camera_off(ent);
|
|
else
|
|
{
|
|
edict_t *viewing;
|
|
vec3_t intersect;
|
|
float range;
|
|
//CW++
|
|
if (!ent->tractored && !client->agm_enemy && (client->frozen_framenum <= level.framenum))
|
|
{
|
|
//CW--
|
|
viewing = LookingAt(ent, 0, intersect, &range);
|
|
if (viewing && viewing->classname)
|
|
{
|
|
if ((viewing->use == use_camera) && (range <= 100.0)) //CW
|
|
{
|
|
use_camera(viewing, ent, ent);
|
|
if (client->spycam && (client->spycam->viewer == ent))
|
|
{
|
|
client->old_owner_angles[0] = ucmd->angles[0];
|
|
client->old_owner_angles[1] = ucmd->angles[1];
|
|
}
|
|
//CW++
|
|
cam_user = true;
|
|
//CW--
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//CW++
|
|
if (!cam_user)
|
|
{
|
|
if (client->pers.inventory[ITEM_INDEX(item_teleporter)])
|
|
Use_Teleporter(ent, item_teleporter);
|
|
}
|
|
//CW--
|
|
}
|
|
|
|
if (ucmd->buttons & BUTTON_USE)
|
|
client->use = 1;
|
|
else
|
|
client->use = 0;
|
|
|
|
if (ent->turret && (ucmd->upmove > 10))
|
|
turret_disengage(ent->turret);
|
|
//DH--
|
|
|
|
if (level.intermissiontime)
|
|
{
|
|
//DH++
|
|
if (client->spycam)
|
|
camera_off(ent);
|
|
//DH--
|
|
client->ps.pmove.pm_type = PM_FREEZE;
|
|
|
|
// can exit intermission after five seconds
|
|
if ((level.time > level.intermissiontime + 5.0) && (ucmd->buttons & BUTTON_ANY))
|
|
level.exitintermission = true;
|
|
|
|
return;
|
|
}
|
|
|
|
//DH++
|
|
if (client->spycam)
|
|
{
|
|
ClientSpycam(ent); // no movement while in cam
|
|
return;
|
|
}
|
|
//DH--
|
|
|
|
pm_passent = ent;
|
|
|
|
//CW++
|
|
if (ent->client->spectator)
|
|
{
|
|
if (!client->buttons && (ucmd->buttons & BUTTON_ATTACK))
|
|
SwitchModeChaseCam(ent);
|
|
}
|
|
//CW--
|
|
|
|
//ZOID++
|
|
if (ent->client->chase_target)
|
|
{
|
|
client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
|
|
client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
|
|
client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
|
|
|
|
//CW++
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = ucmd->buttons;
|
|
client->latched_buttons |= client->buttons & ~client->oldbuttons;
|
|
//CW--
|
|
|
|
return;
|
|
}
|
|
//ZOID--
|
|
|
|
// set up for pmove
|
|
memset(&pm, 0, sizeof(pm));
|
|
|
|
if (ent->movetype == MOVETYPE_NOCLIP)
|
|
client->ps.pmove.pm_type = PM_SPECTATOR;
|
|
else if (ent->s.modelindex != (MAX_MODELS-1)) // was 255
|
|
client->ps.pmove.pm_type = PM_GIB;
|
|
else if (ent->deadflag)
|
|
client->ps.pmove.pm_type = PM_DEAD;
|
|
else
|
|
client->ps.pmove.pm_type = PM_NORMAL;
|
|
|
|
client->ps.pmove.gravity = sv_gravity->value;
|
|
pm.s = client->ps.pmove;
|
|
|
|
//CW++
|
|
if (client->frozen_framenum > level.framenum)
|
|
{
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
ent->s.old_origin[i] = ent->s.origin[i];
|
|
ent->velocity[i] = 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CW--
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
pm.s.origin[i] = ent->s.origin[i] * 8;
|
|
pm.s.velocity[i] = ent->velocity[i] * 8;
|
|
}
|
|
}
|
|
|
|
if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
|
|
pm.snapinitial = true;
|
|
|
|
pm.cmd = *ucmd;
|
|
|
|
// adds default parms
|
|
pm.trace = PM_trace;
|
|
pm.pointcontents = gi.pointcontents;
|
|
|
|
// perform a pmove
|
|
gi.Pmove(&pm);
|
|
|
|
// save results of pmove
|
|
client->ps.pmove = pm.s;
|
|
client->old_pmove = pm.s;
|
|
|
|
//CW++
|
|
if (client->frozen_framenum > level.framenum)
|
|
{
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
ent->s.origin[i] = ent->s.old_origin[i];
|
|
ent->velocity[i] = 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CW--
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
ent->s.origin[i] = pm.s.origin[i] * 0.125;
|
|
ent->velocity[i] = pm.s.velocity[i] * 0.125;
|
|
}
|
|
}
|
|
|
|
VectorCopy(pm.mins, ent->mins);
|
|
VectorCopy(pm.maxs, ent->maxs);
|
|
|
|
client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
|
|
client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
|
|
client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
|
|
|
|
if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
|
|
{
|
|
gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
|
|
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //CW
|
|
|
|
//CW++
|
|
// Jumping has a small chance of extinguishing flames.
|
|
|
|
if (ent->burning)
|
|
{
|
|
if (random() < FLAME_EXPIRE_PROB)
|
|
{
|
|
ent->burning = false;
|
|
if (ent->flame != NULL) // sanity check
|
|
{
|
|
ent->flame->touch = NULL;
|
|
ent->flame->think = Flame_Expire;
|
|
ent->flame->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
gi.dprintf("BUG: ClientThink() => %s is burning with a null flame\n", ent->client->pers.netname);
|
|
}
|
|
}
|
|
//CW--
|
|
}
|
|
|
|
ent->viewheight = pm.viewheight;
|
|
ent->waterlevel = pm.waterlevel;
|
|
ent->watertype = pm.watertype;
|
|
ent->groundentity = pm.groundentity;
|
|
if (pm.groundentity)
|
|
ent->groundentity_linkcount = pm.groundentity->linkcount;
|
|
|
|
if (ent->deadflag)
|
|
{
|
|
client->ps.viewangles[ROLL] = 40.0;
|
|
client->ps.viewangles[PITCH] = -15.0;
|
|
client->ps.viewangles[YAW] = client->killer_yaw;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(pm.viewangles, client->v_angle);
|
|
VectorCopy(pm.viewangles, client->ps.viewangles);
|
|
}
|
|
|
|
//ZOID++
|
|
if (client->ctf_grapple)
|
|
CTFGrapplePull(client->ctf_grapple);
|
|
//ZOID--
|
|
|
|
gi.linkentity(ent);
|
|
if (ent->movetype != MOVETYPE_NOCLIP)
|
|
G_TouchTriggers(ent);
|
|
|
|
// touch other objects
|
|
for (i = 0; i < pm.numtouch; ++i)
|
|
{
|
|
other = pm.touchents[i];
|
|
for (j = 0; j < i; ++j)
|
|
{
|
|
if (pm.touchents[j] == other)
|
|
break;
|
|
}
|
|
|
|
if (j != i)
|
|
continue; // duplicated
|
|
if (!other->touch)
|
|
continue;
|
|
|
|
other->touch(other, ent, NULL, NULL);
|
|
}
|
|
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = ucmd->buttons;
|
|
client->latched_buttons |= client->buttons & ~client->oldbuttons;
|
|
|
|
// save light level the player is standing on for monster sighting AI
|
|
ent->light_level = ucmd->lightlevel;
|
|
|
|
// CDawg - add here!
|
|
if (ucmd->forwardmove < -1)
|
|
ent->client->backpedaling = true;
|
|
else
|
|
ent->client->backpedaling = false;
|
|
// CDawg end here!
|
|
|
|
// fire weapon from final position if needed
|
|
if ((client->latched_buttons & BUTTON_ATTACK) && (ent->movetype != MOVETYPE_NOCLIP)) //ZOID
|
|
{
|
|
if (!client->weapon_thunk)
|
|
{
|
|
client->weapon_thunk = true;
|
|
Think_Weapon(ent);
|
|
}
|
|
}
|
|
|
|
//CW++
|
|
if ((int)sv_hook_offhand->value && !level.nohook)
|
|
CTFWeapon_Grapple_OffHand(ent);
|
|
//CW--
|
|
|
|
//ZOID++
|
|
CTFApplyRegeneration(ent);
|
|
|
|
for (i = 1; i <= (int)maxclients->value; ++i)
|
|
{
|
|
other = g_edicts + i;
|
|
if (other->inuse && (other->client->chase_target == ent))
|
|
UpdateChaseCam(other);
|
|
}
|
|
|
|
if (client->menudirty && (client->menutime <= level.time))
|
|
{
|
|
PMenu_Do_Update(ent);
|
|
gi.unicast(ent, true);
|
|
client->menutime = level.time;
|
|
client->menudirty = false;
|
|
}
|
|
//ZOID--
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ClientBeginServerFrame
|
|
|
|
This will be called once for each server frame, before running
|
|
any other entities in the world.
|
|
==============
|
|
*/
|
|
void ClientBeginServerFrame(edict_t *ent)
|
|
{
|
|
gclient_t *client;
|
|
int buttonMask;
|
|
|
|
if (level.intermissiontime)
|
|
return;
|
|
|
|
client = ent->client;
|
|
|
|
//DH++
|
|
if (client->spycam)
|
|
client = client->camplayer->client;
|
|
//DH--
|
|
|
|
//Maj++
|
|
if (ent->isabot)
|
|
SV_Physics_Step(ent);
|
|
//Maj--
|
|
|
|
// run weapon animations if it hasn't been done by a ucmd_t
|
|
if (!client->weapon_thunk && (ent->movetype != MOVETYPE_NOCLIP)) //ZOID
|
|
Think_Weapon(ent);
|
|
else
|
|
client->weapon_thunk = false;
|
|
|
|
if (ent->deadflag)
|
|
{
|
|
// wait for any button just going down
|
|
if (level.time > client->respawn_time)
|
|
{
|
|
// only wait for attack button
|
|
buttonMask = BUTTON_ATTACK; //CW
|
|
|
|
if ((client->latched_buttons & buttonMask) || ((int)dmflags->value & DF_FORCE_RESPAWN) || CTFMatchOn()) //CW
|
|
{
|
|
Respawn(ent);
|
|
client->latched_buttons = 0;
|
|
}
|
|
}
|
|
//CW++
|
|
if (ent->isabot && ((ent->movetype == MOVETYPE_TOSS) || (ent->movetype == MOVETYPE_BOUNCE)))
|
|
{
|
|
if (gi.pointcontents(ent->s.origin) & MASK_WATER)
|
|
ent->velocity[2] -= 0.1 * (ent->gravity * sv_gravity->value * FRAMETIME);
|
|
else
|
|
ent->velocity[2] -= 0.5 * (ent->gravity * sv_gravity->value * FRAMETIME);
|
|
}
|
|
//CW--
|
|
return;
|
|
}
|
|
|
|
client->latched_buttons = 0;
|
|
}
|