mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
69bbad2b9a
Added video menu anisotropic mode list generation from cvar. Cleanup of menus and UI utils code from last refactoring. Added player obituary for Makron in Zaaero game DLL.
1914 lines
46 KiB
C
1914 lines
46 KiB
C
#include "g_local.h"
|
|
#include "m_player.h"
|
|
|
|
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
|
|
|
|
void SP_misc_teleporter_dest (edict_t *ent);
|
|
void zCam_SetLocalCopy(struct edict_s *player, char *s);
|
|
void stopCamera(edict_t *ent);
|
|
|
|
|
|
//
|
|
// Gross, ugly, disgustuing hack section
|
|
//
|
|
|
|
// this function is an ugly as hell hack to fix some map flaws
|
|
//
|
|
// the coop spawn spots on some maps are SNAFU. There are coop spots
|
|
// with the wrong targetname as well as spots with no name at all
|
|
//
|
|
// we use carnal knowledge of the maps to fix the coop spot targetnames to match
|
|
// that of the nearest named single player spot
|
|
|
|
/*static*/ void SP_FixCoopSpots (edict_t *self)
|
|
{
|
|
edict_t *spot;
|
|
vec3_t d;
|
|
|
|
spot = NULL;
|
|
|
|
while(1)
|
|
{
|
|
spot = G_Find(spot, FOFS(classname), "info_player_start");
|
|
if (!spot)
|
|
return;
|
|
if (!spot->targetname)
|
|
continue;
|
|
VectorSubtract(self->s.origin, spot->s.origin, d);
|
|
if (VectorLength(d) < 384)
|
|
{
|
|
if ((!self->targetname) || Q_stricmp(self->targetname, spot->targetname) != 0)
|
|
{
|
|
// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname);
|
|
self->targetname = spot->targetname;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now if that one wasn't ugly enough for you then try this one on for size
|
|
// some maps don't have any coop spots at all, so we need to create them
|
|
// where they should have been
|
|
|
|
/*static*/ void SP_CreateCoopSpots (edict_t *self)
|
|
{
|
|
edict_t *spot;
|
|
|
|
if(Q_stricmp(level.mapname, "security") == 0)
|
|
{
|
|
spot = G_Spawn();
|
|
spot->classname = "info_player_coop";
|
|
spot->spawnflags2 = 0;
|
|
spot->s.origin[0] = 188 - 64;
|
|
spot->s.origin[1] = -164;
|
|
spot->s.origin[2] = 80;
|
|
spot->targetname = "jail3";
|
|
spot->s.angles[1] = 90;
|
|
|
|
spot = G_Spawn();
|
|
spot->classname = "info_player_coop";
|
|
spot->spawnflags2 = 0;
|
|
spot->s.origin[0] = 188 + 64;
|
|
spot->s.origin[1] = -164;
|
|
spot->s.origin[2] = 80;
|
|
spot->targetname = "jail3";
|
|
spot->s.angles[1] = 90;
|
|
|
|
spot = G_Spawn();
|
|
spot->classname = "info_player_coop";
|
|
spot->spawnflags2 = 0;
|
|
spot->s.origin[0] = 188 + 128;
|
|
spot->s.origin[1] = -164;
|
|
spot->s.origin[2] = 80;
|
|
spot->targetname = "jail3";
|
|
spot->s.angles[1] = 90;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*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)
|
|
{
|
|
if (!coop->value)
|
|
return;
|
|
if(Q_stricmp(level.mapname, "security") == 0)
|
|
{
|
|
// invoke one of our gross, ugly, disgusting hacks
|
|
self->think = SP_CreateCoopSpots;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
/*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)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
SP_misc_teleporter_dest (self);
|
|
}
|
|
|
|
/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
|
|
potential spawning position for coop games
|
|
*/
|
|
|
|
void SP_info_player_coop(edict_t *self)
|
|
{
|
|
if (!coop->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
if((Q_stricmp(level.mapname, "jail2") == 0) ||
|
|
(Q_stricmp(level.mapname, "jail4") == 0) ||
|
|
(Q_stricmp(level.mapname, "mine1") == 0) ||
|
|
(Q_stricmp(level.mapname, "mine2") == 0) ||
|
|
(Q_stricmp(level.mapname, "mine3") == 0) ||
|
|
(Q_stricmp(level.mapname, "mine4") == 0) ||
|
|
(Q_stricmp(level.mapname, "lab") == 0) ||
|
|
(Q_stricmp(level.mapname, "boss1") == 0) ||
|
|
(Q_stricmp(level.mapname, "fact3") == 0) ||
|
|
(Q_stricmp(level.mapname, "biggun") == 0) ||
|
|
(Q_stricmp(level.mapname, "space") == 0) ||
|
|
(Q_stricmp(level.mapname, "command") == 0) ||
|
|
(Q_stricmp(level.mapname, "power2") == 0) ||
|
|
(Q_stricmp(level.mapname, "strike") == 0))
|
|
{
|
|
// invoke one of our gross, ugly, disgusting hacks
|
|
self->think = SP_FixCoopSpots;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
|
|
/*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(void)
|
|
{
|
|
}
|
|
|
|
|
|
//=======================================================================
|
|
|
|
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
|
|
// if we're in a camera, get out
|
|
if (self->client->zCameraTrack)
|
|
{
|
|
stopCamera(self);
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
struct monsterObit {
|
|
char *classname;
|
|
char *message;
|
|
} obits[] =
|
|
{
|
|
"monster_soldier", "%s was slaughtered by a Shotgun Guard.\n",
|
|
"monster_soldier_light", "%s was exterminated by a Light Guard.\n",
|
|
"monster_soldier_ss", "%s was eradicated by a Machinegun Guard.\n",
|
|
"monster_tank", "%s felt the pain of a Tank.\n",
|
|
"monster_tank_commander", "%s was annihilated by a Tank Commander.\n",
|
|
"monster_hound", "%s was leg humped to death by a Hound.\n",
|
|
"monster_handler", "%s was ravished by an Enforcer.\n",
|
|
"monster_infantry", "%s was obliterated by an Enforcer.\n",
|
|
"monster_sentien", "%s was lobotomized by a badass Sentien.\n",
|
|
"monster_zboss", "%s was killed by a big, bad MOFO.\n",
|
|
"monster_gunner", "A Gunner went medievil on %s's ass.\n",
|
|
"monster_berserk", "%s was shattered by a Berserker. TRESPASSA!\n",
|
|
"monster_chick", "%s was bitch slapped by an Iron Maiden.\n",
|
|
"monster_parasite", "%s was sucked by a Parasite.\n",
|
|
"monster_mutant", "%s was demolished by a Mutant.\n",
|
|
"monster_flyer", "%s was killed by a Flyer.\n",
|
|
"monster_hover", "%s was waxed out by an Icarus.\n",
|
|
"monster_medic", "%s overdosed on Medics\n",
|
|
"monster_floater", "%s was tweaked by a Technician.\n",
|
|
"monster_flipper", "%s was killed by a Barracuda Shark.\n",
|
|
"monster_gladiator", "%s was made into swiss cheese by a Gladiator.\n",
|
|
"monster_brain", "%s was scanned by a Brain.\n",
|
|
"monster_supertank", "%s was stomped by a Super Tank.\n",
|
|
"monster_boss2", "%s was killed by some flying boss thingy.\n",
|
|
"monster_jorg", "%s was assassinated by a Jorg.\n",
|
|
"monster_makron", "%s was laid low by the Makron.\n",
|
|
NULL, NULL
|
|
};
|
|
|
|
void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
|
|
{
|
|
int mod;
|
|
char *message;
|
|
char *message2;
|
|
qboolean ff;
|
|
|
|
if (coop->value && attacker->client)
|
|
meansOfDeath |= MOD_FRIENDLY_FIRE;
|
|
|
|
if (attacker != self && attacker->svflags & SVF_MONSTER)
|
|
{
|
|
struct monsterObit *ptr = obits;
|
|
while (ptr->classname != NULL)
|
|
{
|
|
if (Q_stricmp(attacker->classname, ptr->classname) == 0)
|
|
{
|
|
gi.bprintf (PRINT_MEDIUM, ptr->message, self->client->pers.netname);
|
|
return;
|
|
}
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
|
|
if (deathmatch->value || coop->value)
|
|
{
|
|
ff = meansOfDeath & MOD_FRIENDLY_FIRE;
|
|
mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
|
|
message = NULL;
|
|
message2 = "";
|
|
|
|
switch (mod)
|
|
{
|
|
case MOD_SUICIDE:
|
|
message = "suicides";
|
|
break;
|
|
case MOD_FALLING:
|
|
message = "cratered";
|
|
break;
|
|
case MOD_CRUSH:
|
|
message = "was squished";
|
|
break;
|
|
case MOD_WATER:
|
|
message = "sank like a rock";
|
|
break;
|
|
case MOD_SLIME:
|
|
message = "melted";
|
|
break;
|
|
case MOD_LAVA:
|
|
message = "does a back flip into the lava";
|
|
break;
|
|
case MOD_EXPLOSIVE:
|
|
case MOD_BARREL:
|
|
message = "blew up";
|
|
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:
|
|
case MOD_SPLASH:
|
|
case MOD_TRIGGER_HURT:
|
|
case MOD_AUTOCANNON:
|
|
message = "was in the wrong place";
|
|
break;
|
|
}
|
|
if (attacker == self)
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_HELD_GRENADE:
|
|
message = "tried to put the pin back in";
|
|
break;
|
|
case MOD_HG_SPLASH:
|
|
case MOD_G_SPLASH:
|
|
if (IsFemale(self))
|
|
message = "tripped on her own grenade";
|
|
else
|
|
message = "tripped on his own grenade";
|
|
break;
|
|
case MOD_R_SPLASH:
|
|
if (IsFemale(self))
|
|
message = "blew herself up";
|
|
else
|
|
message = "blew himself up";
|
|
break;
|
|
case MOD_BFG_BLAST:
|
|
message = "should have used a smaller gun";
|
|
break;
|
|
|
|
case MOD_A2K:
|
|
message = "realized he was expendable";
|
|
break;
|
|
|
|
case MOD_SONICCANNON:
|
|
message = "got carried away";
|
|
break;
|
|
|
|
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);
|
|
if (deathmatch->value)
|
|
self->client->resp.score--;
|
|
self->enemy = NULL;
|
|
return;
|
|
}
|
|
|
|
self->enemy = attacker;
|
|
if (attacker && attacker->client)
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_BLASTER:
|
|
message = "was blasted by";
|
|
break;
|
|
case MOD_SHOTGUN:
|
|
message = "was gunned down by";
|
|
break;
|
|
case MOD_SSHOTGUN:
|
|
message = "was blown away by";
|
|
message2 = "'s super shotgun";
|
|
break;
|
|
case MOD_MACHINEGUN:
|
|
message = "was machinegunned by";
|
|
break;
|
|
case MOD_CHAINGUN:
|
|
message = "was cut in half by";
|
|
message2 = "'s chaingun";
|
|
break;
|
|
case MOD_GRENADE:
|
|
message = "was popped by";
|
|
message2 = "'s grenade";
|
|
break;
|
|
case MOD_G_SPLASH:
|
|
message = "was shredded by";
|
|
message2 = "'s shrapnel";
|
|
break;
|
|
case MOD_ROCKET:
|
|
message = "ate";
|
|
message2 = "'s rocket";
|
|
break;
|
|
case MOD_R_SPLASH:
|
|
message = "almost dodged";
|
|
message2 = "'s rocket";
|
|
break;
|
|
case MOD_HYPERBLASTER:
|
|
message = "was melted by";
|
|
message2 = "'s hyperblaster";
|
|
break;
|
|
case MOD_RAILGUN:
|
|
message = "was railed by";
|
|
break;
|
|
case MOD_BFG_LASER:
|
|
message = "saw the pretty lights from";
|
|
message2 = "'s BFG";
|
|
break;
|
|
case MOD_BFG_BLAST:
|
|
message = "was disintegrated by";
|
|
message2 = "'s BFG blast";
|
|
break;
|
|
case MOD_BFG_EFFECT:
|
|
message = "couldn't hide from";
|
|
message2 = "'s BFG";
|
|
break;
|
|
case MOD_HANDGRENADE:
|
|
message = "caught";
|
|
message2 = "'s handgrenade";
|
|
break;
|
|
case MOD_HG_SPLASH:
|
|
message = "didn't see";
|
|
message2 = "'s handgrenade";
|
|
break;
|
|
case MOD_HELD_GRENADE:
|
|
message = "feels";
|
|
message2 = "'s pain";
|
|
break;
|
|
case MOD_TELEFRAG:
|
|
message = "tried to invade";
|
|
message2 = "'s personal space";
|
|
break;
|
|
case MOD_SNIPERRIFLE:
|
|
message = "was ventilated by";
|
|
message2 = "'s bullet";
|
|
break;
|
|
case MOD_TRIPBOMB:
|
|
message = "tripped over";
|
|
message2 = "'s trip bomb";
|
|
break;
|
|
case MOD_FLARE:
|
|
message = "didn't see";
|
|
message2 = "'s flare";
|
|
break;
|
|
case MOD_GL_POLYBLEND:
|
|
message = "turned off gl_polyblend and was damaged by";
|
|
message2 = "'s flare";
|
|
break;
|
|
case MOD_A2K:
|
|
message = "got dissassembled by";
|
|
message2 = "";
|
|
break;
|
|
|
|
case MOD_SONICCANNON:
|
|
message = "got microwaved by";
|
|
message2 = "";
|
|
break;
|
|
|
|
}
|
|
if (message)
|
|
{
|
|
gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
|
|
if (deathmatch->value)
|
|
{
|
|
if (ff)
|
|
attacker->client->resp.score--;
|
|
else
|
|
attacker->client->resp.score++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
|
|
if (deathmatch->value)
|
|
self->client->resp.score--;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
if (!deathmatch->value)
|
|
return;
|
|
|
|
item = self->client->pers.weapon;
|
|
if (! self->client->pers.inventory[self->client->ammo_index] )
|
|
item = NULL;
|
|
if (item && (strcmp (item->pickup_name, "Blaster") == 0))
|
|
item = NULL;
|
|
|
|
#if defined(_DEBUG) && defined(_Z_TESTMODE)
|
|
if (item && (strcmp (item->pickup_name, "Line Draw") == 0))
|
|
item = NULL;
|
|
#endif
|
|
|
|
if (!((int)(dmflags->value) & DF_QUAD_DROP))
|
|
quad = false;
|
|
else
|
|
quad = (self->client->quad_framenum > (level.framenum + 10));
|
|
|
|
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;
|
|
drop->spawnflags = DROPPED_PLAYER_ITEM;
|
|
}
|
|
|
|
if (quad)
|
|
{
|
|
self->client->v_angle[YAW] += spread;
|
|
drop = Drop_Item (self, FindItemByClassname ("item_quad"));
|
|
self->client->v_angle[YAW] -= spread;
|
|
drop->spawnflags |= DROPPED_PLAYER_ITEM;
|
|
|
|
drop->touch = Touch_Item;
|
|
drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
|
|
drop->think = G_FreeEdict;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
LookAtKiller
|
|
==================
|
|
*/
|
|
void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
|
|
{
|
|
vec3_t dir;
|
|
|
|
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;
|
|
}
|
|
|
|
self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
player_die
|
|
==================
|
|
*/
|
|
void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
int n;
|
|
|
|
// if we're in a camera, get out
|
|
if (self->client->zCameraTrack)
|
|
{
|
|
stopCamera(self);
|
|
}
|
|
|
|
VectorClear (self->avelocity);
|
|
|
|
self->takedamage = DAMAGE_YES;
|
|
self->movetype = MOVETYPE_TOSS;
|
|
|
|
self->s.modelindex2 = 0; // remove linked weapon model
|
|
|
|
self->s.angles[0] = 0;
|
|
self->s.angles[2] = 0;
|
|
|
|
self->s.sound = 0;
|
|
self->client->weapon_sound = 0;
|
|
|
|
self->maxs[2] = -8;
|
|
|
|
// self->solid = SOLID_NOT;
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
|
|
if (!self->deadflag)
|
|
{
|
|
self->client->respawn_time = level.time + 1.0;
|
|
LookAtKiller (self, inflictor, attacker);
|
|
self->client->ps.pmove.pm_type = PM_DEAD;
|
|
ClientObituary (self, inflictor, attacker);
|
|
TossClientWeapon (self);
|
|
if (deathmatch->value)
|
|
Cmd_Help_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->client->a2kFramenum = 0;
|
|
|
|
// clear inventory
|
|
memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory));
|
|
|
|
// if (self->health < -40)
|
|
if (self->health < player_gib_health->value)
|
|
{ // gib
|
|
int num_giblets = 4;
|
|
if (deathmatch->value && (self->health < (player_gib_health->value*2)))
|
|
num_giblets = 8;
|
|
|
|
gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
|
for (n= 0; n < num_giblets; n++)
|
|
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
|
|
if (mega_gibs->value)
|
|
{
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone2/tris.md2", damage, GIB_ORGANIC);
|
|
}
|
|
ThrowClientHead (self, damage);
|
|
|
|
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;
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
//=======================================================================
|
|
|
|
void SwitchToBestStartWeapon (gclient_t *client)
|
|
{
|
|
if (!client)
|
|
return;
|
|
|
|
if ( client->pers.inventory[slugs_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("railgun");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[slugs_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("Sniper Rifle"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("Sniper Rifle");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[cells_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("Sonic Cannon"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("Sonic Cannon");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[cells_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("hyperblaster");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[bullets_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("chaingun");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[bullets_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("machinegun");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[shells_index] > 1
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("super shotgun");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[shells_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("shotgun");
|
|
return;
|
|
}
|
|
client->pers.weapon = FindItem ("blaster");
|
|
}
|
|
|
|
void SelectStartWeapon (gclient_t *client)
|
|
{
|
|
gitem_t *item;
|
|
|
|
#if defined(_DEBUG) && defined(_Z_TESTMODE)
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Line Draw"))] = 1;
|
|
#endif
|
|
item = FindItem("Push");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
|
|
item = FindItem("Blaster");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
client->pers.weapon = item;
|
|
|
|
if (!deathmatch->value)
|
|
{
|
|
item = FindItem("Flare Gun");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
item = FindItem("Flares");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 3;
|
|
}
|
|
|
|
// Knightmare- DM start values
|
|
if (deathmatch->value)
|
|
{
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] = sk_dm_start_shells->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] = sk_dm_start_bullets->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Rockets"))] = sk_dm_start_rockets->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] = sk_dm_start_grenades->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] = sk_dm_start_cells->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] = sk_dm_start_slugs->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Flares"))] = sk_dm_start_flares->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("IRED"))] = sk_dm_start_tbombs->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("EMPNuke"))] = sk_dm_start_empnuke->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Plasma Shield"))] = sk_dm_start_plasmashield->value;
|
|
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Shotgun"))] = sk_dm_start_shotgun->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Super Shotgun"))] = sk_dm_start_sshotgun->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Machinegun"))] = sk_dm_start_machinegun->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Chaingun"))] = sk_dm_start_chaingun->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Grenade Launcher"))] = sk_dm_start_grenadelauncher->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = sk_dm_start_rocketlauncher->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("HyperBlaster"))] = sk_dm_start_hyperblaster->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Railgun"))] = sk_dm_start_railgun->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("BFG10K"))] = sk_dm_start_bfg->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Flare Gun"))] = sk_dm_start_flaregun->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Sniper Rifle"))] = sk_dm_start_sniperrifle->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Sonic Cannon"))] = sk_dm_start_soniccannon->value;
|
|
SwitchToBestStartWeapon (client);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
|
|
memset (&client->pers, 0, sizeof(client->pers));
|
|
|
|
SelectStartWeapon (client);
|
|
/*
|
|
#if defined(_DEBUG) && defined(_Z_TESTMODE)
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Line Draw"))] = 1;
|
|
#endif
|
|
item = FindItem("Push");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
|
|
item = FindItem("Blaster");
|
|
client->pers.selected_item = ITEM_INDEX(item);
|
|
client->pers.inventory[client->pers.selected_item] = 1;
|
|
client->pers.weapon = item;
|
|
|
|
if (!deathmatch->value)
|
|
{
|
|
item = FindItem("Flare Gun");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
item = FindItem("Flares");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 3;
|
|
}
|
|
*/
|
|
|
|
client->pers.health = 100;
|
|
if (deathmatch->value)
|
|
client->pers.max_health = sk_max_health_dm->value;
|
|
else
|
|
client->pers.max_health = sk_max_health->value;
|
|
client->pers.max_bullets = sk_max_bullets->value;
|
|
client->pers.max_shells = sk_max_shells->value;
|
|
client->pers.max_rockets = sk_max_rockets->value;
|
|
client->pers.max_grenades = sk_max_grenades->value;
|
|
client->pers.max_cells = sk_max_cells->value;
|
|
client->pers.max_slugs = sk_max_slugs->value;
|
|
client->pers.max_flares = sk_max_flares->value;
|
|
client->pers.max_tbombs = sk_max_tbombs->value;
|
|
client->pers.max_a2k = sk_max_a2k->value;
|
|
client->pers.max_empnuke = sk_max_empnuke->value;
|
|
client->pers.max_plasmashield = sk_max_plasmashield->value;
|
|
|
|
client->pers.connected = true;
|
|
client->pers.gl_polyblend = 1;
|
|
}
|
|
|
|
|
|
void InitClientResp (gclient_t *client)
|
|
{
|
|
memset (&client->resp, 0, sizeof(client->resp));
|
|
client->resp.enterframe = level.framenum;
|
|
client->resp.coop_respawn = client->pers;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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<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.powerArmorActive = (ent->flags & FL_POWER_ARMOR);
|
|
if (coop->value)
|
|
game.clients[i].pers.score = ent->client->resp.score;
|
|
}
|
|
}
|
|
|
|
void FetchClientEntData (edict_t *ent)
|
|
{
|
|
ent->health = ent->client->pers.health;
|
|
ent->max_health = ent->client->pers.max_health;
|
|
if (ent->client->pers.powerArmorActive)
|
|
ent->flags |= FL_POWER_ARMOR;
|
|
if (coop->value)
|
|
ent->client->resp.score = ent->client->pers.score;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
SelectSpawnPoint
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
PlayersRangeFromSpot
|
|
|
|
Returns the distance to the nearest player from the given spot
|
|
================
|
|
*/
|
|
float PlayersRangeFromSpot (edict_t *spot)
|
|
{
|
|
edict_t *player;
|
|
float bestplayerdistance;
|
|
vec3_t v;
|
|
int n;
|
|
float playerdistance;
|
|
|
|
|
|
bestplayerdistance = 9999999;
|
|
|
|
for (n = 1; n <= maxclients->value; n++)
|
|
{
|
|
player = &g_edicts[n];
|
|
|
|
if (!player->inuse)
|
|
continue;
|
|
|
|
if (player->health <= 0)
|
|
continue;
|
|
|
|
VectorSubtract (spot->s.origin, player->s.origin, v);
|
|
playerdistance = VectorLength (v);
|
|
|
|
if (playerdistance < bestplayerdistance)
|
|
bestplayerdistance = playerdistance;
|
|
}
|
|
|
|
return bestplayerdistance;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SelectRandomDeathmatchSpawnPoint
|
|
|
|
go to a random point, but NOT the two points closest
|
|
to other players
|
|
================
|
|
*/
|
|
edict_t *SelectRandomDeathmatchSpawnPoint (void)
|
|
{
|
|
edict_t *spot, *spot1, *spot2;
|
|
int count = 0;
|
|
int selection;
|
|
float range, range1, range2;
|
|
|
|
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;
|
|
float bestdistance, bestplayerdistance;
|
|
edict_t *spot;
|
|
|
|
|
|
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 ();
|
|
}
|
|
|
|
|
|
edict_t *SelectCoopSpawnPoint (edict_t *ent)
|
|
{
|
|
int index;
|
|
edict_t *spot = NULL;
|
|
char *target;
|
|
|
|
index = ent->client - game.clients;
|
|
|
|
// player 0 starts in normal player spawn point
|
|
if (!index)
|
|
return NULL;
|
|
|
|
spot = NULL;
|
|
|
|
// assume there are four coop spots at each spawnpoint
|
|
while (1)
|
|
{
|
|
spot = G_Find (spot, FOFS(classname), "info_player_coop");
|
|
if (!spot)
|
|
return NULL; // we didn't have enough...
|
|
|
|
target = spot->targetname;
|
|
if (!target)
|
|
target = "";
|
|
if ( Q_stricmp(game.spawnpoint, target) == 0 )
|
|
{ // this is a coop spawn point for one of the clients here
|
|
index--;
|
|
if (!index)
|
|
return spot; // this is it
|
|
}
|
|
}
|
|
|
|
|
|
return spot;
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
SelectSpawnPoint
|
|
|
|
Chooses a player start, deathmatch start, coop start, etc
|
|
============
|
|
*/
|
|
void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
|
|
{
|
|
edict_t *spot = NULL;
|
|
|
|
if (deathmatch->value)
|
|
spot = SelectDeathmatchSpawnPoint ();
|
|
else if (coop->value)
|
|
spot = SelectCoopSpawnPoint (ent);
|
|
|
|
// 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;
|
|
VectorCopy (spot->s.angles, angles);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
|
|
void InitBodyQue (void)
|
|
{
|
|
int i;
|
|
edict_t *ent;
|
|
|
|
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)
|
|
if (self->health < player_gib_health->value)
|
|
{
|
|
int num_giblets = 4;
|
|
//if (deathmatch->value && (self->health < (self->gib_health*2)))
|
|
if (deathmatch->value && (self->health < (player_gib_health->value*2)))
|
|
num_giblets = 8;
|
|
|
|
gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
|
for (n=0; n < num_giblets; n++)
|
|
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
|
|
if (mega_gibs->value)
|
|
{
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone2/tris.md2", damage, GIB_ORGANIC);
|
|
}
|
|
self->s.origin[2] -= 48;
|
|
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;
|
|
|
|
// FIXME: send an effect on the removed body
|
|
|
|
gi.unlinkentity (ent);
|
|
|
|
gi.unlinkentity (body);
|
|
body->s = ent->s;
|
|
body->s.number = body - g_edicts;
|
|
|
|
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;
|
|
|
|
gi.linkentity (body);
|
|
}
|
|
|
|
|
|
void respawn (edict_t *self)
|
|
{
|
|
if (deathmatch->value || coop->value)
|
|
{
|
|
CopyToBodyQue (self);
|
|
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;
|
|
|
|
return;
|
|
}
|
|
|
|
// restart the entire server
|
|
gi.AddCommandString ("menu_loadgame\n");
|
|
}
|
|
|
|
//==============================================================
|
|
|
|
|
|
/*
|
|
===========
|
|
PutClientInServer
|
|
|
|
Called when a player connects to a server or respawns in
|
|
a deathmatch.
|
|
============
|
|
*/
|
|
void PutClientInServer (edict_t *ent)
|
|
{
|
|
vec3_t mins = {-16, -16, -24};
|
|
vec3_t maxs = {16, 16, 32};
|
|
int index;
|
|
vec3_t spawn_origin, spawn_angles;
|
|
gclient_t *client;
|
|
int i;
|
|
char userinfo[MAX_INFO_STRING];
|
|
client_persistant_t saved;
|
|
client_respawn_t resp;
|
|
|
|
// find a spawn point
|
|
// do it before setting health back up, so farthest
|
|
// ranging doesn't count this client
|
|
SelectSpawnPoint (ent, spawn_origin, spawn_angles);
|
|
|
|
index = ent-g_edicts-1;
|
|
client = ent->client;
|
|
|
|
// deathmatch wipes most client data every spawn
|
|
if (deathmatch->value)
|
|
{
|
|
// char userinfo[MAX_INFO_STRING];
|
|
|
|
resp = client->resp;
|
|
memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
|
|
InitClientPersistant (client);
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
}
|
|
else if (coop->value)
|
|
{
|
|
int n;
|
|
// char userinfo[MAX_INFO_STRING];
|
|
|
|
resp = client->resp;
|
|
memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
|
|
// this is kind of ugly, but it's how we want to handle keys in coop
|
|
for (n = 0; n < MAX_ITEMS; n++)
|
|
{
|
|
if (itemlist[n].flags & IT_KEY)
|
|
resp.coop_respawn.inventory[n] = client->pers.inventory[n];
|
|
}
|
|
client->pers = resp.coop_respawn;
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
if (resp.score > client->pers.score)
|
|
client->pers.score = resp.score;
|
|
}
|
|
else
|
|
{
|
|
memset (&resp, 0, sizeof(resp));
|
|
}
|
|
|
|
// A bug in Q2 that you couldn't see without thirdpp
|
|
// Knightmare- make sure skin doesn't reset to male/grunt after level change
|
|
memcpy (userinfo, ent->client->pers.userinfo, sizeof(userinfo));
|
|
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);
|
|
else if (Q_stricmp(level.mapname, "zboss") == 0)
|
|
{
|
|
char userinfo[MAX_INFO_STRING];
|
|
|
|
int health = client->pers.health;
|
|
|
|
memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
|
|
InitClientPersistant(client);
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
client->pers.health = health;
|
|
}
|
|
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 = "player";
|
|
ent->mass = 200;
|
|
ent->solid = SOLID_BBOX;
|
|
ent->deadflag = DEAD_NO;
|
|
ent->air_finished = level.time + 12;
|
|
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;
|
|
|
|
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;
|
|
|
|
if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
|
|
{
|
|
client->ps.fov = 90;
|
|
}
|
|
else
|
|
{
|
|
client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
|
|
if (client->ps.fov < 1)
|
|
client->ps.fov = 90;
|
|
else if (client->ps.fov > 160)
|
|
client->ps.fov = 160;
|
|
}
|
|
|
|
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
|
|
ent->s.modelindex2 = MAX_MODELS-1; // custom gun model
|
|
ent->s.frame = 0;
|
|
VectorCopy (spawn_origin, ent->s.origin);
|
|
ent->s.origin[2] += 1; // 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;
|
|
ent->s.angles[YAW] = spawn_angles[YAW];
|
|
ent->s.angles[ROLL] = 0;
|
|
VectorCopy (ent->s.angles, client->ps.viewangles);
|
|
VectorCopy (ent->s.angles, client->v_angle);
|
|
|
|
if (!KillBox (ent))
|
|
{ // could't spawn in?
|
|
}
|
|
|
|
gi.linkentity (ent);
|
|
|
|
// force the current weapon up
|
|
client->newweapon = client->pers.weapon;
|
|
ChangeWeapon (ent);
|
|
|
|
// Paril's fix for this getting reset after map changes
|
|
if (!ent->client->pers.connected)
|
|
ent->client->pers.connected = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
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);
|
|
|
|
// send effect
|
|
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)
|
|
{
|
|
int i;
|
|
|
|
ent->client = game.clients + (ent - g_edicts - 1);
|
|
|
|
if (deathmatch->value)
|
|
{
|
|
ClientBeginDeathmatch (ent);
|
|
return;
|
|
}
|
|
|
|
// if there is already a body waiting for us (a loadgame), just
|
|
// take it, otherwise spawn one from scratch
|
|
if (ent->inuse == true)
|
|
{
|
|
// the client has cleared the client side viewangles upon
|
|
// connecting to the server, which is different than the
|
|
// state when the game is saved, so we need to compensate
|
|
// with deltaangles
|
|
for (i=0 ; i<3 ; i++)
|
|
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
|
|
}
|
|
else
|
|
{
|
|
// a spawn point will completely reinitialize the entity
|
|
// except for the persistant data that was initialized at
|
|
// ClientConnect() time
|
|
G_InitEdict (ent);
|
|
ent->classname = "player";
|
|
InitClientResp (ent->client);
|
|
PutClientInServer (ent);
|
|
}
|
|
|
|
if (level.intermissiontime)
|
|
{
|
|
MoveClientToIntermission (ent);
|
|
}
|
|
else
|
|
{
|
|
// send effect if in a multiplayer game
|
|
if (game.maxclients > 1)
|
|
{
|
|
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);
|
|
}
|
|
|
|
/*
|
|
===========
|
|
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");
|
|
}
|
|
|
|
// set name
|
|
s = Info_ValueForKey (userinfo, "name");
|
|
Com_strcpy (ent->client->pers.netname, sizeof(ent->client->pers.netname), s);
|
|
|
|
// set skin
|
|
s = Info_ValueForKey (userinfo, "skin");
|
|
|
|
zCam_SetLocalCopy(ent, s);
|
|
|
|
playernum = ent-g_edicts-1;
|
|
|
|
// combine name and skin into a configstring
|
|
gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );
|
|
|
|
// fov
|
|
if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
|
|
{
|
|
ent->client->ps.fov = 90;
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
|
|
if (ent->client->ps.fov < 1)
|
|
ent->client->ps.fov = 90;
|
|
else if (ent->client->ps.fov > 160)
|
|
ent->client->ps.fov = 160;
|
|
}
|
|
|
|
// handedness
|
|
s = Info_ValueForKey (userinfo, "hand");
|
|
if (strlen(s))
|
|
{
|
|
ent->client->pers.hand = atoi(s);
|
|
}
|
|
|
|
// is the player cheating?
|
|
s = Info_ValueForKey (userinfo, "gl_polyblend");
|
|
if (strlen(s))
|
|
{
|
|
ent->client->pers.gl_polyblend = atoi(s);
|
|
}
|
|
|
|
// save off the userinfo in case we want to check something later
|
|
Com_strcpy (ent->client->pers.userinfo, sizeof(ent->client->pers.userinfo), userinfo);
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
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;
|
|
|
|
// check to see if they are on the banned IP list
|
|
value = Info_ValueForKey (userinfo, "ip");
|
|
|
|
// check for a password
|
|
value = Info_ValueForKey (userinfo, "password");
|
|
if (strcmp(password->string, value) != 0)
|
|
return false;
|
|
|
|
// 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
|
|
InitClientResp (ent->client);
|
|
if (!game.autosaved || !ent->client->pers.weapon)
|
|
InitClientPersistant (ent->client);
|
|
}
|
|
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
|
|
if (game.maxclients > 1)
|
|
gi.dprintf ("%s connected\n", ent->client->pers.netname);
|
|
|
|
ent->client->pers.connected = true;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientDisconnect
|
|
|
|
Called when a player drops from the server.
|
|
Will not be called between levels.
|
|
============
|
|
*/
|
|
void ClientDisconnect (edict_t *ent)
|
|
{
|
|
int playernum;
|
|
|
|
if (!ent->client)
|
|
return;
|
|
|
|
gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
|
|
|
|
// send effect
|
|
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;
|
|
ent->solid = SOLID_NOT;
|
|
ent->inuse = false;
|
|
ent->classname = "disconnected";
|
|
ent->client->pers.connected = false;
|
|
|
|
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,i;
|
|
v = 0;
|
|
for (i=0 ; i<c ; i++)
|
|
v+= ((byte *)b)[i];
|
|
return v;
|
|
}
|
|
void PrintPmove (pmove_t *pm)
|
|
{
|
|
unsigned c1, 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);
|
|
}
|
|
|
|
|
|
#if defined(_DEBUG) && defined(_Z_TESTMODE)
|
|
|
|
extern edict_t *testItemDroped;
|
|
extern qboolean testitemOriginMove;
|
|
|
|
#endif
|
|
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
int i, j;
|
|
pmove_t pm;
|
|
|
|
#if defined(_DEBUG) && defined(_Z_TESTMODE)
|
|
|
|
if(testitemOriginMove && testItemDroped)
|
|
{
|
|
if(ucmd->forwardmove > 0)
|
|
{
|
|
testItemDroped->s.origin[2]++;
|
|
}
|
|
else if(ucmd->forwardmove < 0)
|
|
{
|
|
testItemDroped->s.origin[2]--;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
level.current_entity = ent;
|
|
client = ent->client;
|
|
|
|
if (level.intermissiontime)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (ent->movetype == MOVETYPE_FREEZE)
|
|
{
|
|
client->ps.pmove.pm_type = PM_FREEZE;
|
|
return;
|
|
}
|
|
|
|
pm_passent = ent;
|
|
|
|
// 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)
|
|
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;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
pm.s.origin[i] = ent->s.origin[i]*8;
|
|
// FIXME: make sure this short doesn't overflow
|
|
pm.s.velocity[i] = ent->velocity[i]*8;
|
|
}
|
|
|
|
if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
|
|
{
|
|
pm.snapinitial = true;
|
|
// gi.dprintf ("pmove changed!\n");
|
|
}
|
|
|
|
pm.cmd = *ucmd;
|
|
|
|
pm.trace = PM_trace; // adds default parms
|
|
pm.pointcontents = gi.pointcontents;
|
|
|
|
// perform a pmove
|
|
gi.Pmove (&pm);
|
|
|
|
// save results of pmove
|
|
client->ps.pmove = pm.s;
|
|
client->old_pmove = pm.s;
|
|
|
|
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))
|
|
{
|
|
// Knightmare- allow disabling of STUPID grunting when jumping
|
|
if (deathmatch->value || player_jump_sounds->value)
|
|
{
|
|
gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
|
|
PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
|
|
}
|
|
}
|
|
|
|
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;
|
|
client->ps.viewangles[PITCH] = -15;
|
|
client->ps.viewangles[YAW] = client->killer_yaw;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy (pm.viewangles, client->v_angle);
|
|
VectorCopy (pm.viewangles, client->ps.viewangles);
|
|
}
|
|
|
|
|
|
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 & BUTTONS_ATTACK)
|
|
{
|
|
if (!client->weapon_thunk)
|
|
{
|
|
client->weapon_thunk = true;
|
|
Think_Weapon (ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
|
|
// run weapon animations if it hasn't been done by a ucmd_t
|
|
if (!client->weapon_thunk)
|
|
Think_Weapon (ent);
|
|
else
|
|
client->weapon_thunk = false;
|
|
|
|
// make sure the fov and weapon models are gone if we're in camera mode
|
|
if (client->zCameraTrack)
|
|
{
|
|
client->ps.fov = 90;
|
|
client->ps.gunindex = 0;
|
|
}
|
|
|
|
if (ent->deadflag)
|
|
{
|
|
// wait for any button just going down
|
|
if ( level.time > client->respawn_time)
|
|
{
|
|
// in deathmatch, only wait for attack button
|
|
if (deathmatch->value)
|
|
buttonMask = BUTTONS_ATTACK;
|
|
else
|
|
buttonMask = -1;
|
|
|
|
if ( ( client->latched_buttons & buttonMask ) ||
|
|
(deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) )
|
|
{
|
|
respawn(ent);
|
|
client->latched_buttons = 0;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// add player trail so monsters can follow
|
|
if (!deathmatch->value)
|
|
if (!visible (ent, PlayerTrail_LastSpot() ) )
|
|
PlayerTrail_Add (ent->s.old_origin);
|
|
|
|
client->latched_buttons = 0;
|
|
}
|