mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
3ce18b138c
Simplified loading and saving of cvars in slider menu control. Enlarged text in Game, Multiplayer, and Options menus. Fixed repeat of failed file causing HTTP downloads to restart. Added cl_zoommode cvar to simplify Lazarus zoom command. Changed zoom command to use new cl_zoommode cvar in default Lazarus and missionpack DLLs. Removed unused "crossh" cvar in default Lazarus and missionpack DLLs. Fixed Makron not having a classname when spawned from Jorg in default Lazarus and missionpack DLLs. Made Tactician Gunner ignore small amounts of damage in missionpack DLL.
4369 lines
115 KiB
C
4369 lines
115 KiB
C
#include "g_local.h"
|
|
#include "m_player.h"
|
|
|
|
#define MUD1BASE 0.20
|
|
#define MUD1AMP 0.08
|
|
#define MUD3 0.08
|
|
|
|
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
|
|
|
|
void SP_misc_teleporter_dest (edict_t *ent);
|
|
|
|
//
|
|
// Gross, ugly, disgusting 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; // Zaero added
|
|
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; // Zaero added
|
|
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; // Zaero added
|
|
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)
|
|
{
|
|
self->class_id = ENTITY_INFO_PLAYER_START;
|
|
|
|
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) NO_PAD
|
|
potential spawning position for deathmatch games
|
|
|
|
NO_PAD - don't make a pad
|
|
|
|
count - the number of maxclients must be less than this for it to spawn.
|
|
if you don't have a func_dm_wall in your map, don't enter a value.
|
|
*/
|
|
void SP_info_player_deathmatch (edict_t *self)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
// Mappack - this makes sure players don't get spawned in an area sealed by a func_dm_wall
|
|
if (self->count > 2)
|
|
{
|
|
if (self->count <= game.maxclients)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Mappack - this removes the pad if a flag of 1 is set.
|
|
if (!(self->spawnflags & 1))
|
|
SP_misc_teleporter_dest (self);
|
|
|
|
self->class_id = ENTITY_INFO_PLAYER_DEATHMATCH;
|
|
}
|
|
|
|
/*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;
|
|
}
|
|
|
|
self->class_id = ENTITY_INFO_PLAYER_COOP;
|
|
|
|
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_coop_lava (1 0 1) (-16 -16 -24) (16 16 32)
|
|
potential spawning position for coop games on rmine2 where lava level
|
|
needs to be checked
|
|
*/
|
|
void SP_info_player_coop_lava (edict_t *self)
|
|
{
|
|
if (!coop->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_INFO_PLAYER_COOP_LAVA;
|
|
}
|
|
|
|
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) LETTERBOX
|
|
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) //was void
|
|
{
|
|
self->class_id = ENTITY_INFO_PLAYER_INTERMISSION;
|
|
|
|
// Knightmare- hack for Paradise Lost
|
|
if (Q_stricmp(level.mapname, "coconut1") == 0)
|
|
VectorSet (self->s.angles, 0, 270, 0);
|
|
}
|
|
|
|
|
|
//=======================================================================
|
|
|
|
|
|
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
|
|
|
|
// Zaero
|
|
// if we're in a camera, get out
|
|
if (self->client->zCameraTrack)
|
|
{
|
|
stopCamera(self);
|
|
}
|
|
// end Zaero
|
|
}
|
|
|
|
|
|
qboolean IsFemale (edict_t *ent)
|
|
{
|
|
char *info;
|
|
|
|
if (!ent->client)
|
|
return false;
|
|
|
|
info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
|
|
if (info[0] == 'f' || info[0] == 'F')
|
|
return true;
|
|
if (strstr(info, "crakhor")) // Knightmare added
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
qboolean IsNeutral (edict_t *ent)
|
|
{
|
|
char *info;
|
|
|
|
if (!ent->client)
|
|
return false;
|
|
|
|
info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
|
|
if (strstr(info, "crakhor")) // Knightmare added
|
|
return false;
|
|
if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
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 (deathmatch->value || coop->value || !deathmatch->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:
|
|
if (!attacker->svflags & SVF_MONSTER) // monsters with lasers
|
|
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_VEHICLE:
|
|
message = "was in the wrong place";
|
|
break;
|
|
case MOD_FALLING_ROCKS:
|
|
message = "was killed by falling rocks";
|
|
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 (IsNeutral(self))
|
|
message = "tripped on its own grenade";
|
|
else if (IsFemale(self))
|
|
message = "tripped on her own grenade";
|
|
else
|
|
message = "tripped on his own grenade";
|
|
break;
|
|
case MOD_R_SPLASH:
|
|
if (IsNeutral(self))
|
|
message = "blew itself up";
|
|
else 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;
|
|
// ROGUE
|
|
case MOD_ETF_SPLASH:
|
|
message = "shouldn't have used the ETF Rifle at point blank";
|
|
break;
|
|
case MOD_PROX:
|
|
if (IsNeutral(self))
|
|
message = "got too close to its own proximity mine";
|
|
else if (IsFemale(self))
|
|
message = "got too close to her own proximity mine";
|
|
else
|
|
message = "got too close to his own proximity mine";
|
|
break;
|
|
case MOD_PHALANX_SPLASH:
|
|
if (IsNeutral(self))
|
|
message = "evaporated itself";
|
|
else if (IsFemale(self))
|
|
message = "evaporated herself";
|
|
else
|
|
message = "evaporated himself";
|
|
break;
|
|
case MOD_TRAP:
|
|
if (IsNeutral(self))
|
|
message = "was sucked into its own trap";
|
|
else if (IsFemale(self))
|
|
message = "was sucked into her own trap";
|
|
else
|
|
message = "was sucked into his own trap";
|
|
break;
|
|
|
|
case MOD_DOPPLE_EXPLODE:
|
|
if (IsNeutral(self))
|
|
message = "got caught in its own trap";
|
|
else if (IsFemale(self))
|
|
message = "got caught in her own trap";
|
|
else
|
|
message = "got caught in his own trap";
|
|
break;
|
|
// ROGUE
|
|
|
|
// Knightmare added
|
|
case MOD_SHOCK_SPHERE:
|
|
case MOD_SHOCK_SPLASH:
|
|
if (IsNeutral(self))
|
|
message = "was pulverized by its own shock sphere";
|
|
else if (IsFemale(self))
|
|
message = "was pulverized by her own shock sphere";
|
|
else
|
|
message = "was pulverized by his own shock sphere";
|
|
break;
|
|
|
|
// SKWiD MOD
|
|
case MOD_PLASMA:
|
|
message = "gets shocked";
|
|
break;
|
|
|
|
// Zaero
|
|
case MOD_TRIPBOMB:
|
|
if (IsNeutral(self))
|
|
message = "tripped over its own trip bomb";
|
|
else if (IsFemale(self))
|
|
message = "tripped over her own trip bomb";
|
|
else
|
|
message = "tripped over his own trip bomb";
|
|
break;
|
|
// end Zaero
|
|
|
|
default:
|
|
if (IsNeutral(self))
|
|
message = "killed itself";
|
|
else 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_VEHICLE:
|
|
message = "was splattered by";
|
|
message2 = "'s vehicle";
|
|
break;
|
|
case MOD_KICK:
|
|
message = "was booted by";
|
|
message2 = "'s foot";
|
|
break;
|
|
//===============
|
|
// XATRIX
|
|
case MOD_RIPPER:
|
|
message = "was ripped to shreds by";
|
|
message2 = "'s ION Ripper";
|
|
break;
|
|
case MOD_PHALANX:
|
|
case MOD_PHALANX_SPLASH:
|
|
message = "was evaporated by";
|
|
message2 = "'s particle cannon";
|
|
break;
|
|
case MOD_TRAP:
|
|
message = "was caught in";
|
|
message2 = "'s trap";
|
|
break;
|
|
// XATRIX
|
|
//===============
|
|
|
|
//===============
|
|
// ROGUE
|
|
case MOD_CHAINFIST:
|
|
message = "was shredded by";
|
|
message2 = "'s ripsaw";
|
|
break;
|
|
case MOD_DISINTEGRATOR:
|
|
message = "lost his grip courtesy of";
|
|
message2 = "'s disintegrator";
|
|
break;
|
|
case MOD_ETF_RIFLE:
|
|
message = "was perforated by";
|
|
break;
|
|
case MOD_ETF_SPLASH:
|
|
message = "was torn up by";
|
|
message2 = "'s explosive flechettes";
|
|
break;
|
|
case MOD_HEATBEAM:
|
|
message = "was scorched by";
|
|
message2 = "'s plasma beam";
|
|
break;
|
|
case MOD_TESLA:
|
|
message = "was enlightened by";
|
|
message2 = "'s tesla mine";
|
|
break;
|
|
case MOD_PROX:
|
|
message = "got too close to";
|
|
message2 = "'s proximity mine";
|
|
break;
|
|
case MOD_NUKE:
|
|
message = "was nuked by";
|
|
message2 = "'s antimatter bomb";
|
|
break;
|
|
case MOD_NBOMB:
|
|
message = "was toasted by";
|
|
message2 = "'s fuel-air bomb";
|
|
break;
|
|
case MOD_VENGEANCE_SPHERE:
|
|
message = "was purged by";
|
|
message2 = "'s vengeance sphere";
|
|
break;
|
|
case MOD_DEFENDER_SPHERE:
|
|
message = "had a blast with";
|
|
message2 = "'s defender sphere";
|
|
break;
|
|
case MOD_HUNTER_SPHERE:
|
|
message = "was killed like a dog by";
|
|
message2 = "'s hunter sphere";
|
|
break;
|
|
case MOD_TRACKER:
|
|
message = "was annihilated by";
|
|
message2 = "'s disintegrator";
|
|
break;
|
|
case MOD_DOPPLE_EXPLODE:
|
|
message = "was blown up by";
|
|
message2 = "'s doppleganger";
|
|
break;
|
|
case MOD_DOPPLE_VENGEANCE:
|
|
message = "was purged by";
|
|
message2 = "'s doppleganger";
|
|
break;
|
|
case MOD_DOPPLE_HUNTER:
|
|
message = "was hunted down by";
|
|
message2 = "'s doppleganger";
|
|
break;
|
|
// ROGUE
|
|
//===============
|
|
// Knightmare added
|
|
case MOD_SHOCK_SPHERE:
|
|
case MOD_SHOCK_SPLASH:
|
|
message = "was pulverized by";
|
|
message2 = "'s shock sphere";
|
|
break;
|
|
|
|
// SKWiD MOD
|
|
case MOD_PLASMA:
|
|
message = "got an infusion of plasma from";
|
|
message2 = "'s plasma rifle";
|
|
break;
|
|
|
|
// Zaero
|
|
/* 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;*/
|
|
// end Zaero
|
|
}
|
|
if (message)
|
|
{
|
|
gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
|
|
// ROGUE
|
|
if (gamerules && gamerules->value)
|
|
{
|
|
if (DMGame.Score)
|
|
{
|
|
if (ff)
|
|
DMGame.Score(attacker, self, -1);
|
|
else
|
|
DMGame.Score(attacker, self, 1);
|
|
}
|
|
return;
|
|
}
|
|
// ROGUE
|
|
if (deathmatch->value)
|
|
{
|
|
if (ff)
|
|
attacker->client->resp.score--;
|
|
else
|
|
attacker->client->resp.score++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// Knightmare- Single-player obits
|
|
if (attacker->svflags & (SVF_MONSTER|SVF_AUTOMATON))
|
|
{ // Light Guard
|
|
if (!strcmp(attacker->classname, "monster_soldier_light"))
|
|
message = "was blasted by a";
|
|
// Shotgun Guard
|
|
else if (!strcmp(attacker->classname, "monster_soldier"))
|
|
message = "was gunned down by a";
|
|
// Machinegun Guard
|
|
else if (!strcmp(attacker->classname, "monster_soldier_ss"))
|
|
message = "was machinegunned by a";
|
|
// Ripper Guard
|
|
else if (!strcmp(attacker->classname, "monster_soldier_ripper"))
|
|
message = "was ripped to shreds by a";
|
|
// Hyperblaster Guard
|
|
else if (!strcmp(attacker->classname, "monster_soldier_hypergun"))
|
|
message = "was melted by a";
|
|
// Laser Guard
|
|
else if (!strcmp(attacker->classname, "monster_soldier_lasergun"))
|
|
{
|
|
message = "saw a";
|
|
message2 = "'s light";
|
|
}
|
|
// Plasma Guard
|
|
else if (!strcmp(attacker->classname, "monster_soldier_plasma_re") || !strcmp(attacker->classname, "monster_soldier_plasma_sp"))
|
|
{
|
|
message = "got an infusion of plasma from a";
|
|
}
|
|
// Enforcer
|
|
else if (!strcmp(attacker->classname, "monster_infantry"))
|
|
{
|
|
if (mod == MOD_HIT)
|
|
message = "was bludgened by an";
|
|
else
|
|
message = "was pumped full of lead by an";
|
|
}
|
|
// Gunner
|
|
else if (!strcmp(attacker->classname, "monster_gunner")
|
|
|| !strcmp(attacker->classname, "monster_gunner_tactician") )
|
|
{
|
|
if (mod == MOD_GRENADE) {
|
|
message = "was popped by a";
|
|
message2 = "'s grenade";
|
|
}
|
|
else if (mod == MOD_G_SPLASH) {
|
|
message = "was shredded by a";
|
|
message2 = "'s shrapnel";
|
|
}
|
|
else if (mod == MOD_PROX) {
|
|
message = "got too close to a";
|
|
message2 = "'s proximity mine";
|
|
}
|
|
else if (mod == MOD_ETF_RIFLE) {
|
|
message = "was perforated by a";
|
|
}
|
|
else if (mod == MOD_ETF_SPLASH) {
|
|
message = "was torn up by a";
|
|
message2 = "'s explosive flechettes";
|
|
}
|
|
else
|
|
message = "was machinegunned by a";
|
|
}
|
|
// Berserker
|
|
else if (!strcmp(attacker->classname, "monster_berserk"))
|
|
message = "was smashed by a";
|
|
// Gladiator/Beta Class Gladiator
|
|
else if (!strcmp(attacker->classname, "monster_gladiator")
|
|
|| !strcmp(attacker->classname, "monster_gladb"))
|
|
{
|
|
if (mod == MOD_RAILGUN)
|
|
message = "was railed by a";
|
|
else if ((mod == MOD_PHALANX) || (mod == MOD_PHALANX_SPLASH)) {
|
|
message = "was evaporated by a";
|
|
message2 = "'s particle cannon";
|
|
}
|
|
else if (mod == MOD_HIT) {
|
|
message = "was mangled by a";
|
|
message2 = "'s claw";
|
|
}
|
|
}
|
|
// Medic
|
|
else if (!strcmp(attacker->classname, "monster_medic")
|
|
|| !strcmp(attacker->classname, "monster_medic_commander"))
|
|
message = "was blasted by a";
|
|
// Icarus
|
|
else if (!strcmp(attacker->classname, "monster_hover"))
|
|
message = "was blasted by an";
|
|
// Daedalus
|
|
else if (!strcmp(attacker->classname, "monster_daedalus"))
|
|
message = "was blasted by a";
|
|
// Iron Maiden
|
|
else if (!strcmp(attacker->classname, "monster_chick"))
|
|
{
|
|
if (mod == MOD_ROCKET) {
|
|
message = "ate an";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged an";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_HIT)
|
|
message = "was bitch-slapped by an";
|
|
}
|
|
// Beta Class Iron Maiden
|
|
else if (!strcmp(attacker->classname, "monster_chick_heat"))
|
|
{
|
|
if (mod == MOD_ROCKET) {
|
|
message = "ate a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_HIT)
|
|
message = "was bitch-slapped by a";
|
|
}
|
|
// Parasite
|
|
else if (!strcmp(attacker->classname, "monster_parasite"))
|
|
message = "was exsanguiated by a";
|
|
// Brain
|
|
else if (!strcmp(attacker->classname, "monster_brain")
|
|
|| !strcmp(attacker->classname, "monster_brain_beta"))
|
|
{
|
|
if (mod == MOD_TARGET_LASER) {
|
|
message = "was lobotomized by a";
|
|
message2 = "'s lasers";
|
|
}
|
|
else if (mod == MOD_BRAINTENTACLE) {
|
|
message = "was drilled by a";
|
|
message2 = "'s tongue";
|
|
}
|
|
else {
|
|
message = "was torn up by a";
|
|
message2 = "'s tentacles";
|
|
}
|
|
}
|
|
// Flyer
|
|
else if (!strcmp(attacker->classname, "monster_flyer"))
|
|
{
|
|
if (mod == MOD_BLASTER)
|
|
message = "was blasted by a";
|
|
else if (mod == MOD_HIT) {
|
|
message = "was cut up by a";
|
|
message2 = "'s sharp wings";
|
|
}
|
|
}
|
|
// Kamikaze Flyer
|
|
else if (!strcmp(attacker->classname, "monster_kamikaze"))
|
|
message = "was dive-bombed by a";
|
|
// Technician
|
|
else if (!strcmp(attacker->classname, "monster_floater"))
|
|
{
|
|
if (mod == MOD_BLASTER)
|
|
message = "was blasted by a";
|
|
else if (mod == MOD_HIT) {
|
|
message = "was gouged to death by a";
|
|
message2 = "'s utility claw";
|
|
}
|
|
}
|
|
// Repair Bot
|
|
else if (!strcmp(attacker->classname, "monster_fixbot"))
|
|
message = "was blasted by a";
|
|
// Stalker
|
|
else if (!strcmp(attacker->classname, "monster_stalker"))
|
|
{
|
|
if (mod == MOD_BLASTER)
|
|
message = "was blasted by a";
|
|
else if (mod == MOD_HIT)
|
|
message = "was smacked to death by a";
|
|
}
|
|
// Sentry Turret
|
|
else if (!strcmp(attacker->classname, "monster_turret"))
|
|
{
|
|
if (mod == MOD_BLASTER)
|
|
message = "was blasted by a";
|
|
else if (mod == MOD_RAILGUN)
|
|
message = "was railed by a";
|
|
else if (mod == MOD_ROCKET) {
|
|
message = "never saw a";
|
|
message2 = "'s rocket coming";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a";
|
|
message2 = "'s rocket";
|
|
}
|
|
}
|
|
// Tank/Tank Commander
|
|
else if (!strcmp(attacker->classname, "monster_tank")
|
|
|| !strcmp(attacker->classname, "monster_tank_commander"))
|
|
{
|
|
if (mod == MOD_BLASTER)
|
|
message = "was blasted by a";
|
|
else if (mod == MOD_ROCKET) {
|
|
message = "ate a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else
|
|
message = "was pumped full of lead by a";
|
|
}
|
|
// Supertank/Beta Class Supertank
|
|
else if (!strcmp(attacker->classname, "monster_supertank")
|
|
|| !strcmp(attacker->classname, "monster_boss5"))
|
|
{
|
|
if (mod == MOD_ROCKET) {
|
|
message = "ate a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else
|
|
message = "was chaingunned by a";
|
|
}
|
|
// Hornet
|
|
else if (!strcmp(attacker->classname, "monster_boss2"))
|
|
{
|
|
if (mod == MOD_ROCKET) {
|
|
message = "ate a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a";
|
|
message2 = "'s rockets";
|
|
}
|
|
else
|
|
message = "was chaingunned by a";
|
|
}
|
|
// Carrier
|
|
else if (!strcmp(attacker->classname, "monster_carrier"))
|
|
{
|
|
if (mod == MOD_RAILGUN)
|
|
message = "was railed by a";
|
|
else if (mod == MOD_GRENADE) {
|
|
message = "was popped by a";
|
|
message2 = "'s grenade";
|
|
}
|
|
else if (mod == MOD_G_SPLASH) {
|
|
message = "was shredded by a";
|
|
message2 = "'s shrapnel";
|
|
}
|
|
else if (mod == MOD_ROCKET) {
|
|
message = "ate a";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a";
|
|
message2 = "'s rockets";
|
|
}
|
|
else
|
|
message = "was chaingunned by a";
|
|
}
|
|
// Jorg
|
|
else if (!strcmp(attacker->classname, "monster_jorg"))
|
|
{
|
|
if (mod == MOD_BFG_LASER) {
|
|
message = "saw the pretty lights from the";
|
|
message2 = "'s BFG";
|
|
}
|
|
else if (mod == MOD_BFG_BLAST) {
|
|
message = "was disintegrated by the";
|
|
message2 = "'s BFG blast";
|
|
}
|
|
else if (mod == MOD_BFG_EFFECT) {
|
|
message = "couldn't hide from the";
|
|
message2 = "'s BFG";
|
|
}
|
|
else {
|
|
message = "was shredded by the";
|
|
message2 = "'s chain-cannons";
|
|
}
|
|
}
|
|
// Makron
|
|
else if (!strcmp(attacker->classname, "monster_makron"))
|
|
{
|
|
if (mod == MOD_BLASTER) {
|
|
message = "was melted by the";
|
|
message2 = "'s hyperblaster";
|
|
}
|
|
else if (mod == MOD_RAILGUN)
|
|
message = "was railed by the";
|
|
else if (mod == MOD_BFG_LASER) {
|
|
message = "saw the pretty lights from the";
|
|
message2 = "'s BFG";
|
|
}
|
|
else if (mod == MOD_BFG_BLAST) {
|
|
message = "was disintegrated by the";
|
|
message2 = "'s BFG blast";
|
|
}
|
|
else if (mod == MOD_BFG_EFFECT) {
|
|
message = "couldn't hide from the";
|
|
message2 = "'s BFG";
|
|
}
|
|
}
|
|
// Black Widow
|
|
else if (!strcmp(attacker->classname, "monster_widow")
|
|
|| !strcmp(attacker->classname, "monster_widow2"))
|
|
{
|
|
if (mod == MOD_BLASTER) {
|
|
message = "was melted by the";
|
|
message = "'s hyperblaster";
|
|
}
|
|
else if (mod == MOD_RAILGUN)
|
|
message = "was railed by the";
|
|
else if (mod == MOD_HEATBEAM) {
|
|
message = "was scorched by the";
|
|
message2 = "'s plasma beam";
|
|
}
|
|
else if (mod == MOD_TRACKER) {
|
|
message = "was annihilated by the";
|
|
message2 = "'s disintegrator";
|
|
}
|
|
else if (mod == MOD_HIT)
|
|
message = "was smashed by the";
|
|
}
|
|
// Barracuda Shark
|
|
else if (!strcmp(attacker->classname, "monster_flipper"))
|
|
message = "was chewed up by a";
|
|
// Mutant
|
|
else if (!strcmp(attacker->classname, "monster_mutant"))
|
|
message = "was clawed by a";
|
|
// Gekk
|
|
else if (!strcmp(attacker->classname, "monster_gekk"))
|
|
{
|
|
if (mod == MOD_GEKK) {
|
|
message = "was dissolved by a";
|
|
message2 = "'s acid loogie";
|
|
}
|
|
else
|
|
message = "was slashed to death by a";
|
|
}
|
|
// Autocannon
|
|
else if (!strcmp(attacker->classname, "monster_autocannon") || !strcmp(attacker->classname, "monster_autocannon_floor"))
|
|
{
|
|
if (mod == MOD_HYPERBLASTER || mod == MOD_BLASTER) {
|
|
message = "was melted by an";
|
|
message2 = "'s blaster";
|
|
}
|
|
else if (mod == MOD_ROCKET) {
|
|
message = "ate an";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged an";
|
|
message2 = "'s rocket";
|
|
}
|
|
else
|
|
message = "was pumped full of lead by an";
|
|
}
|
|
// Sentien
|
|
else if (!strcmp(attacker->classname, "monster_sentien"))
|
|
{
|
|
if (mod == MOD_TARGET_LASER)
|
|
message = "was lobotomized by a";
|
|
else
|
|
message = "was pumped full of lead by a";
|
|
}
|
|
// Hound
|
|
else if (!strcmp(attacker->classname, "monster_hound"))
|
|
// message = "was mauled by a";
|
|
message = "was leg humped to death by a";
|
|
// Handler
|
|
else if (!strcmp(attacker->classname, "monster_handler"))
|
|
{
|
|
// message = "was ravished by an";
|
|
if (mod == MOD_HIT)
|
|
message = "was bludgened by an";
|
|
else
|
|
message = "was pumped full of lead by an";
|
|
}
|
|
// ZBoss
|
|
else if (!strcmp(attacker->classname, "monster_zboss"))
|
|
{
|
|
// message = "was killed by a big, bad";
|
|
if (mod == MOD_HIT)
|
|
message = "was pulverized by a big, bad";
|
|
else if (mod == MOD_ROCKET) {
|
|
message = "ate a big, bad";
|
|
message2 = "'s rocket";
|
|
}
|
|
else if (mod == MOD_R_SPLASH) {
|
|
message = "almost dodged a big, bad";
|
|
message2 = "'s rocket";
|
|
}
|
|
else {
|
|
message = "was obliterated by a big, bad";
|
|
message2 = "'s plasma cannon";
|
|
}
|
|
}
|
|
// Rottweiler
|
|
else if (!strcmp(attacker->classname, "monster_dog"))
|
|
message = "was mauled by a";
|
|
// Vulture
|
|
else if (!strcmp(attacker->classname, "monster_vulture"))
|
|
message = "was pecked to death by a";
|
|
// Quake 1 Monsters
|
|
// Fiend
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_fiend") || !Q_stricmp(attacker->classname, "monster_q1_demon") )
|
|
message ="was eviscerated by a";
|
|
// Quake Grunt
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_soldier") || !Q_stricmp(attacker->classname, "monster_q1_army") )
|
|
message = "was shot by a";
|
|
// Quake Enforcer
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_enforcer") || !Q_stricmp(attacker->classname, "monster_q1_enforcer") )
|
|
message = "was blasted by an";
|
|
// Ogre
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_ogre") || !Q_stricmp(attacker->classname, "monster_q1_ogre") )
|
|
message = "was destroyed by an";
|
|
// Knight
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_knight") || !Q_stricmp(attacker->classname, "monster_q1_knight") )
|
|
message = "was slashed by a";
|
|
// Scrag
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_scrag") || !Q_stricmp(attacker->classname, "monster_q1_wizard") )
|
|
message = "was scragged by a";
|
|
// Zombie
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_zombie") || !Q_stricmp(attacker->classname, "monster_q1_zombie") ) {
|
|
message = "joins the";
|
|
message2 = "s";
|
|
}
|
|
// Shambler
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_shambler") || !Q_stricmp(attacker->classname, "monster_q1_shambler") )
|
|
message = "was smashed by a";
|
|
// Rottweiler
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_dog") || !Q_stricmp(attacker->classname, "monster_q1_dog") )
|
|
message = "was mauled by a";
|
|
// Rotfish
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_fish") || !Q_stricmp(attacker->classname, "monster_q1_fish") )
|
|
message = "was fed to the";
|
|
// Hell Knight
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_hknight") || !Q_stricmp(attacker->classname, "monster_q1_hknight") )
|
|
message = "was slain by at";
|
|
// Vore
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_shalrath") || !Q_stricmp(attacker->classname, "monster_q1_shalrath") )
|
|
message = "was exploded by a";
|
|
// Spawn
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_tarbaby") || !Q_stricmp(attacker->classname, "monster_q1_tarbaby") )
|
|
message = "was slimed by a";
|
|
// Freddie
|
|
else if ( !Q_stricmp(attacker->classname, "q1_monster_freddie") || !Q_stricmp(attacker->classname, "monster_q1_freddie") )
|
|
message = "was exterminated by";
|
|
|
|
}
|
|
if (message)
|
|
{
|
|
gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->common_name, message2);
|
|
if (coop->value)
|
|
self->client->resp.score--;
|
|
self->enemy = NULL;
|
|
return;
|
|
}
|
|
// end Knightmare
|
|
}
|
|
// Knightmare- for diagnostics, print name of killing entity
|
|
/*if (strlen(attacker->classname))
|
|
gi.bprintf (PRINT_MEDIUM,"%s was killed by %s.\n", self->client->pers.netname, attacker->classname);
|
|
else*/
|
|
gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
|
|
|
|
//ROGUE
|
|
// if (g_showlogic && g_showlogic->value)
|
|
// {
|
|
// if (mod == MOD_UNKNOWN)
|
|
// gi.dprintf ("Player killed by MOD_UNKNOWN\n");
|
|
// else
|
|
// gi.dprintf ("Player killed by undefined mod %d\n", mod);
|
|
// }
|
|
//ROGUE
|
|
|
|
if (deathmatch->value)
|
|
//ROGUE
|
|
{
|
|
if (gamerules && gamerules->value)
|
|
{
|
|
if (DMGame.Score)
|
|
{
|
|
DMGame.Score(self, self, -1);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
self->client->resp.score--;
|
|
}
|
|
//ROGUE
|
|
|
|
}
|
|
|
|
|
|
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, doubled, quadfire;
|
|
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 (item && (strcmp (item->pickup_name, "No Weapon") == 0))
|
|
item = NULL;
|
|
// Knightmare- don't drop homing rocket launcher (null model error), drop rocket launcher instead
|
|
if (item && (strcmp (item->pickup_name, "Homing Rocket Launcher") == 0))
|
|
item = FindItem("Rocket Launcher");
|
|
|
|
if (!((int)(dmflags->value) & DF_QUAD_DROP)) {
|
|
quad = false;
|
|
doubled = false;
|
|
quadfire = false;
|
|
}
|
|
else {
|
|
quad = (self->client->quad_framenum > (level.framenum + 10));
|
|
doubled = (self->client->double_framenum > (level.framenum + 10));
|
|
quadfire = (self->client->quadfire_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;
|
|
}
|
|
|
|
if (doubled)
|
|
{
|
|
self->client->v_angle[YAW] += spread;
|
|
drop = Drop_Item (self, FindItemByClassname ("item_double"));
|
|
self->client->v_angle[YAW] -= spread;
|
|
drop->spawnflags |= DROPPED_PLAYER_ITEM;
|
|
|
|
drop->touch = Touch_Item;
|
|
drop->nextthink = level.time + (self->client->double_framenum - level.framenum) * FRAMETIME;
|
|
drop->think = G_FreeEdict;
|
|
}
|
|
|
|
if (quadfire)
|
|
{
|
|
self->client->v_angle[YAW] += spread;
|
|
drop = Drop_Item (self, FindItemByClassname ("item_quadfire"));
|
|
self->client->v_angle[YAW] -= spread;
|
|
drop->spawnflags |= DROPPED_PLAYER_ITEM;
|
|
|
|
drop->touch = Touch_Item;
|
|
drop->nextthink = level.time + (self->client->quadfire_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;
|
|
}
|
|
// PMM - fixed to correct for pitch of 0
|
|
if (dir[0])
|
|
self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
|
|
else if (dir[1] > 0)
|
|
self->client->killer_yaw = 90;
|
|
else if (dir[1] < 0)
|
|
self->client->killer_yaw = 270;
|
|
else
|
|
self->client->killer_yaw = 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
player_die
|
|
==================
|
|
*/
|
|
void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
int n;
|
|
|
|
// Zaero
|
|
// if we're in a camera, get out
|
|
if (self->client->zCameraTrack)
|
|
{
|
|
stopCamera(self);
|
|
}
|
|
// end Zaero
|
|
|
|
// Knightmare- Gen cam code
|
|
// Get rid of the chasecam crap shouldnt be needed - Skid
|
|
// if (self->client->chasetoggle && !(self->client->resp.spectator))
|
|
if (self->client->chaseactive && !(self->client->resp.spectator))
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
|
|
self->client->pers.spawn_landmark = false; // paranoia check
|
|
self->client->pers.spawn_levelchange = false;
|
|
SetLazarusCrosshair (self); // backup crosshair
|
|
self->client->zooming = 0;
|
|
self->client->zoomed = false;
|
|
SetSensitivities (self, true);
|
|
|
|
if (self->client->spycam)
|
|
camera_off (self);
|
|
|
|
if (self->turret)
|
|
turret_disengage (self->turret);
|
|
|
|
if (self->client->textdisplay)
|
|
Text_Close (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
|
|
|
|
// clear inventory
|
|
// this is kind of ugly, but it's how we want to handle keys in coop
|
|
for (n = 0; n < game.num_items; n++)
|
|
{
|
|
if (coop->value && itemlist[n].flags & IT_KEY)
|
|
self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n];
|
|
self->client->pers.inventory[n] = 0;
|
|
}
|
|
}
|
|
|
|
if (gamerules && gamerules->value) // if we're in a dm game, alert the game
|
|
{
|
|
if (DMGame.PlayerDeath)
|
|
DMGame.PlayerDeath(self, inflictor, attacker);
|
|
}
|
|
|
|
// 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);
|
|
self->client->flashlight_active = false;
|
|
|
|
// turn off alt-fire mode if on
|
|
self->client->pers.fire_mode = 0;
|
|
self->client->pers.plasma_mode = 0; // SKWiD- plasma rifle fire mode
|
|
//self->client->nNewLatch &= ~BUTTON_ATTACK2;
|
|
|
|
//==============
|
|
// ROGUE stuff
|
|
self->client->double_framenum = 0;
|
|
|
|
// if there's a sphere around, let it know the player died.
|
|
// vengeance and hunter will die if they're not attacking,
|
|
// defender should always die
|
|
if (self->client->owned_sphere)
|
|
{
|
|
edict_t *sphere;
|
|
|
|
sphere = self->client->owned_sphere;
|
|
sphere->die(sphere, self, self, 0, vec3_origin);
|
|
}
|
|
|
|
// if we've been killed by the tracker, GIB!
|
|
if ((meansOfDeath & ~MOD_FRIENDLY_FIRE) == MOD_TRACKER)
|
|
{
|
|
self->health = self->gib_health; //was -100
|
|
damage = 400;
|
|
}
|
|
|
|
// make sure no trackers are still hurting us.
|
|
if (self->client->tracker_pain_framenum)
|
|
{
|
|
RemoveAttackingPainDaemons (self);
|
|
}
|
|
|
|
// if we got obliterated by the nuke, don't gib
|
|
if ((self->health < -80) && (meansOfDeath == MOD_NUKE))
|
|
self->flags |= FL_NUKED;
|
|
|
|
// ROGUE
|
|
//==============
|
|
|
|
if (self->health < self->gib_health)
|
|
{
|
|
// PMM
|
|
// don't toss gibs if we got vaped by the nuke
|
|
if (!(self->flags & FL_NUKED))
|
|
{
|
|
// pmm
|
|
// gib
|
|
gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
|
// more meaty gibs for your dollar!
|
|
if ((deathmatch->value) && (self->health < (self->gib_health * 2))) {
|
|
for (n = 0; n < 4; n++)
|
|
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
}
|
|
for (n = 0; n < 4; n++)
|
|
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
if (mega_gibs->value)
|
|
{
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone2/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
}
|
|
// PMM
|
|
}
|
|
self->flags &= ~FL_NUKED;
|
|
// PMM
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifdef JETPACK_MOD
|
|
if ( self->client->jetpack )
|
|
{
|
|
Jet_BecomeExplosion (self, damage);
|
|
/*stop jetting when dead*/
|
|
self->client->jetpack_framenum = 0;
|
|
self->client->jetpack = false;
|
|
// DWH: force player to gib
|
|
self->health = self->gib_health - 1;
|
|
}
|
|
#endif
|
|
|
|
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[magslug_index]
|
|
&& client->pers.inventory[ITEM_INDEX (FindItem ("phalanx"))])
|
|
{
|
|
client->pers.weapon = FindItem ("phalanx");
|
|
return;
|
|
}
|
|
if ( (client->pers.inventory[cells_index] >= 2)
|
|
&& client->pers.inventory[ITEM_INDEX (FindItem ("ION Ripper"))])
|
|
{
|
|
client->pers.weapon = FindItem ("ION Ripper");
|
|
return;
|
|
}
|
|
if ( (client->pers.inventory[cells_index] >= 2)
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("Plasma Beam"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("Plasma Beam");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[cells_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("hyperblaster");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[flechettes_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("ETF Rifle"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("ETF Rifle");
|
|
return;
|
|
}
|
|
if ( client->pers.inventory[disruptors_index]
|
|
&& client->pers.inventory[ITEM_INDEX(FindItem("Disintegrator"))] )
|
|
{
|
|
client->pers.weapon = FindItem ("Disintegrator");
|
|
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] >= 2
|
|
&& 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;
|
|
}
|
|
// DWH: Dude may not HAVE a blaster
|
|
//ent->client->newweapon = FindItem ("blaster");
|
|
if ( client->pers.inventory[ITEM_INDEX(FindItem("blaster"))] )
|
|
client->pers.weapon = FindItem ("blaster");
|
|
else
|
|
client->pers.weapon = FindItem ("No Weapon");
|
|
}
|
|
|
|
void SelectStartWeapon (gclient_t *client, int style)
|
|
{
|
|
gitem_t *item;
|
|
int n;
|
|
|
|
// Lazarus: We allow choice of weapons (or no weapon) at startup
|
|
// If style is non-zero, first clear player inventory of all
|
|
// weapons and ammo that might have been passed over through
|
|
// target_changelevel or acquired when previously called by
|
|
// InitClientPersistant
|
|
if (style)
|
|
{
|
|
for (n = 0; n < MAX_ITEMS; n++)
|
|
{
|
|
if (itemlist[n].flags & IT_WEAPON)
|
|
client->pers.inventory[n] = 0;
|
|
}
|
|
client->pers.inventory[shells_index] = 0;
|
|
client->pers.inventory[bullets_index] = 0;
|
|
client->pers.inventory[grenades_index] = 0;
|
|
client->pers.inventory[rockets_index] = 0;
|
|
client->pers.inventory[cells_index] = 0;
|
|
client->pers.inventory[slugs_index] = 0;
|
|
client->pers.inventory[homing_index] = 0;
|
|
client->pers.inventory[magslug_index] = 0;
|
|
client->pers.inventory[flechettes_index] = 0;
|
|
client->pers.inventory[prox_index] = 0;
|
|
client->pers.inventory[disruptors_index] = 0;
|
|
client->pers.inventory[tesla_index] = 0;
|
|
client->pers.inventory[trap_index] = 0;
|
|
client->pers.inventory[shocksphere_index] = 0;
|
|
}
|
|
|
|
switch (style)
|
|
{
|
|
case -1:
|
|
item = FindItem("No Weapon");
|
|
break;
|
|
case -2:
|
|
case 2:
|
|
item = FindItem("Shotgun");
|
|
break;
|
|
case -3:
|
|
case 3:
|
|
item = FindItem("Super Shotgun");
|
|
break;
|
|
case -4:
|
|
case 4:
|
|
item = FindItem("Machinegun");
|
|
break;
|
|
case -5:
|
|
case 5:
|
|
item = FindItem("Chaingun");
|
|
break;
|
|
case -6:
|
|
case 6:
|
|
item = FindItem("Grenade Launcher");
|
|
break;
|
|
case -7:
|
|
case 7:
|
|
item = FindItem("Rocket Launcher");
|
|
break;
|
|
case -8:
|
|
case 8:
|
|
item = FindItem("HyperBlaster");
|
|
break;
|
|
case -9:
|
|
case 9:
|
|
item = FindItem("Railgun");
|
|
break;
|
|
case -10:
|
|
case 10:
|
|
item = FindItem("BFG10K");
|
|
break;
|
|
case -11:
|
|
case 11:
|
|
item = FindItem("ION Ripper");
|
|
break;
|
|
case -12:
|
|
case 12:
|
|
item = FindItem("Phalanx");
|
|
break;
|
|
case -13:
|
|
case 13:
|
|
item = FindItem("ETF Rifle");
|
|
break;
|
|
case -14:
|
|
case 14:
|
|
item = FindItem("Prox Launcher");
|
|
break;
|
|
case -15:
|
|
case 15:
|
|
item = FindItem("Plasma Beam");
|
|
break;
|
|
case -16:
|
|
case 16:
|
|
item = FindItem("Disintegrator");
|
|
break;
|
|
case -17:
|
|
case 17:
|
|
item = FindItem("Chainfist");
|
|
break;
|
|
case -18:
|
|
case 18:
|
|
item = FindItem("Shockwave");
|
|
break;
|
|
default:
|
|
item = FindItem("Blaster");
|
|
break;
|
|
}
|
|
client->pers.selected_item = ITEM_INDEX(item);
|
|
client->pers.inventory[client->pers.selected_item] = 1;
|
|
client->pers.weapon = item;
|
|
|
|
// Lazarus: If default weapon is NOT "No Weapon", then give player
|
|
// a blaster
|
|
if (style > 1)
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Blaster"))] = 1;
|
|
|
|
// Knightmare- player always has null weapon in SP to allow holstering
|
|
if (!deathmatch->value)
|
|
client->pers.inventory[ITEM_INDEX(FindItem("No Weapon"))] = 1;
|
|
|
|
// Zaero
|
|
if ( !deathmatch->value && (level.maptype == MAPTYPE_ZAERO) )
|
|
{
|
|
item = FindItem("Flare Gun");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 1;
|
|
item = FindItem("Flares");
|
|
client->pers.inventory[ITEM_INDEX(item)] = 3;
|
|
}
|
|
// end Zaero
|
|
|
|
// and give him standard ammo
|
|
if (item->ammo)
|
|
{
|
|
gitem_t *ammo;
|
|
|
|
ammo = FindItem (item->ammo);
|
|
if ( deathmatch->value && ((int)dmflags->value & DF_INFINITE_AMMO) )
|
|
client->pers.inventory[ITEM_INDEX(ammo)] += 1000;
|
|
else
|
|
client->pers.inventory[ITEM_INDEX(ammo)] += ammo->quantity;
|
|
}
|
|
// 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("Homing Rockets"))] = sk_dm_start_homing->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("Magslug"))] = sk_dm_start_magslugs->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Trap"))] = sk_dm_start_traps->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Flechettes"))] = sk_dm_start_flechettes->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Disruptors"))] = sk_dm_start_rounds->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Prox"))] = sk_dm_start_prox->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Tesla"))] = sk_dm_start_tesla->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Shocksphere"))] = sk_dm_start_shocksphere->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("Homing 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("ION Ripper"))] = sk_dm_start_ionripper->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Phalanx"))] = sk_dm_start_phalanx->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("ETF Rifle"))] = sk_dm_start_etfrifle->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Prox Launcher"))] = sk_dm_start_proxlauncher->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Plasma Beam"))] = sk_dm_start_plasmabeam->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Disintegrator"))] = sk_dm_start_disruptor->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Chainfist"))] = sk_dm_start_chainfist->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem("Shockwave"))] = sk_dm_start_shockwave->value;
|
|
client->pers.inventory[ITEM_INDEX(FindItem(PLASMA_PICKUP_NAME))] = sk_dm_start_plasmarifle->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, int style)
|
|
{
|
|
//gitem_t *item;
|
|
|
|
memset (&client->pers, 0, sizeof(client->pers));
|
|
|
|
client->homing_rocket = NULL;
|
|
SelectStartWeapon (client, style);
|
|
|
|
//item = FindItem("Blaster");
|
|
//client->pers.selected_item = ITEM_INDEX(item);
|
|
//client->pers.inventory[client->pers.selected_item] = 1;
|
|
|
|
//client->pers.weapon = item;
|
|
|
|
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_fc_health = sk_max_foodcube_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_magslug = sk_max_magslugs->value;
|
|
client->pers.max_trap = sk_max_traps->value;
|
|
client->pers.max_shockspheres = sk_max_shocksphere->value;
|
|
client->pers.max_homing_rockets = sk_max_rockets->value;
|
|
client->pers.max_fuel = sk_max_fuel->value;
|
|
|
|
// ROGUE
|
|
client->pers.max_prox = sk_max_prox->value;
|
|
client->pers.max_tesla = sk_max_tesla->value;
|
|
client->pers.max_flechettes = sk_max_flechettes->value;
|
|
client->pers.max_disruptors = sk_max_rounds->value;
|
|
// end ROGUE
|
|
|
|
// Zaero
|
|
// These are currently unused, but init them anyway.
|
|
client->pers.max_flares = 30; // sk_max_flares->value;
|
|
client->pers.max_tbombs = 30; // sk_max_tbombs->value;
|
|
client->pers.max_a2k = 1;
|
|
client->pers.max_empnuke = 50; // sk_max_empnuke->value;
|
|
client->pers.max_plasmashield = 20; // sk_max_plasmashield->value;
|
|
// end Zaero
|
|
|
|
client->pers.fire_mode = 0; // Lazarus alternate fire mode
|
|
client->pers.plasma_mode = 0; // SKWiD- plasma rifle fire mode
|
|
|
|
client->pers.connected = true;
|
|
|
|
client->zooming = 0;
|
|
client->zoomed = false;
|
|
client->spycam = NULL;
|
|
client->pers.spawn_landmark = false;
|
|
client->pers.spawn_levelchange = false;
|
|
}
|
|
|
|
|
|
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.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_SHIELD|FL_POWER_SCREEN));
|
|
if (coop->value)
|
|
game.clients[i].pers.score = ent->client->resp.score;
|
|
}
|
|
}
|
|
|
|
void FetchClientEntData (edict_t *ent)
|
|
{
|
|
ent->health = ent->client->pers.health;
|
|
ent->gib_health = -80;
|
|
ent->max_health = ent->client->pers.max_health;
|
|
ent->flags |= ent->client->pers.savedFlags;
|
|
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 = 999999;
|
|
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 ();
|
|
}
|
|
|
|
//===============
|
|
//ROGUE
|
|
edict_t *SelectLavaCoopSpawnPoint (edict_t *ent)
|
|
{
|
|
int index;
|
|
edict_t *spot = NULL;
|
|
float lavatop;
|
|
edict_t *lava;
|
|
edict_t *pointWithLeastLava;
|
|
float lowest;
|
|
edict_t *spawnPoints[64];
|
|
vec3_t center;
|
|
int numPoints;
|
|
edict_t *highestlava;
|
|
|
|
lavatop = -999999;
|
|
highestlava = NULL;
|
|
|
|
// first, find the highest lava
|
|
// remember that some will stop moving when they've filled their
|
|
// areas...
|
|
lava = NULL;
|
|
while (1)
|
|
{
|
|
lava = G_Find (lava, FOFS(classname), "func_door");
|
|
if (!lava)
|
|
break;
|
|
|
|
VectorAdd (lava->absmax, lava->absmin, center);
|
|
VectorScale (center, 0.5, center);
|
|
|
|
if (lava->spawnflags & 2 && (gi.pointcontents(center) & MASK_WATER))
|
|
{
|
|
if (lava->absmax[2] > lavatop)
|
|
{
|
|
lavatop = lava->absmax[2];
|
|
highestlava = lava;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we didn't find ANY lava, then return NULL
|
|
if (!highestlava)
|
|
return NULL;
|
|
|
|
// find the top of the lava and include a small margin of error (plus bbox size)
|
|
lavatop = highestlava->absmax[2] + 64;
|
|
|
|
// find all the lava spawn points and store them in spawnPoints[]
|
|
spot = NULL;
|
|
numPoints = 0;
|
|
while (spot = G_Find (spot, FOFS(classname), "info_player_coop_lava"))
|
|
{
|
|
if (numPoints == 64)
|
|
break;
|
|
|
|
spawnPoints[numPoints++] = spot;
|
|
}
|
|
|
|
if (numPoints < 1)
|
|
return NULL;
|
|
|
|
// walk up the sorted list and return the lowest, open, non-lava spawn point
|
|
spot = NULL;
|
|
lowest = 999999;
|
|
pointWithLeastLava = NULL;
|
|
for (index = 0; index < numPoints; index++)
|
|
{
|
|
if (spawnPoints[index]->s.origin[2] < lavatop)
|
|
continue;
|
|
|
|
if (PlayersRangeFromSpot(spawnPoints[index]) > 32)
|
|
{
|
|
if (spawnPoints[index]->s.origin[2] < lowest)
|
|
{
|
|
// save the last point
|
|
pointWithLeastLava = spawnPoints[index];
|
|
lowest = spawnPoints[index]->s.origin[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME - better solution????
|
|
// well, we may telefrag someone, but oh well...
|
|
if (pointWithLeastLava)
|
|
return pointWithLeastLava;
|
|
|
|
return NULL;
|
|
}
|
|
//ROGUE
|
|
//===============
|
|
|
|
edict_t *SelectCoopSpawnPoint (edict_t *ent)
|
|
{
|
|
int index;
|
|
edict_t *spot = NULL;
|
|
char *target;
|
|
|
|
//ROGUE
|
|
// rogue hack, but not too gross...
|
|
if (!Q_stricmp(level.mapname, "rmine2p") || !Q_stricmp(level.mapname, "rmine2"))
|
|
return SelectLavaCoopSpawnPoint (ent);
|
|
//ROGUE
|
|
|
|
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, int *style, int *health)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
if (style)
|
|
*style = spot->style;
|
|
if (health)
|
|
*health = spot->health;
|
|
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 < self->gib_health)
|
|
{
|
|
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", 0, 0, damage, GIB_ORGANIC);
|
|
if (mega_gibs->value)
|
|
{
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowGib (self, "models/objects/gibs/bone2/tris.md2", 0, 0, 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);
|
|
}
|
|
|
|
//Skid
|
|
void ChasecamStart (edict_t *ent);
|
|
|
|
void respawn (edict_t *self)
|
|
{
|
|
//CHASECAM Knightmare- Gen cam code
|
|
qboolean chase=false;
|
|
|
|
if (self->client->chaseactive)
|
|
{
|
|
chase = true;
|
|
if (self->client->oldplayer != NULL)
|
|
{
|
|
gi.TagFree(self->client->oldplayer->client);
|
|
G_FreeEdict(self->client->oldplayer);
|
|
}
|
|
if (self->client->chasecam != NULL)
|
|
G_FreeEdict(self->client->chasecam);
|
|
}
|
|
//CHASECAM
|
|
|
|
if (deathmatch->value || coop->value)
|
|
{
|
|
// spectators don't leave bodies
|
|
if (self->movetype != MOVETYPE_NOCLIP)
|
|
CopyToBodyQue (self);
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
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;
|
|
|
|
if (chase == true) //Knightmare- Gen cam code
|
|
ChasecamStart(self);
|
|
|
|
return;
|
|
}
|
|
|
|
// restart the entire server
|
|
gi.AddCommandString ("menu_loadgame\n");
|
|
}
|
|
|
|
/*
|
|
* only called when pers.spectator changes
|
|
* note that resp.spectator should be the opposite of pers.spectator here
|
|
*/
|
|
void spectator_respawn (edict_t *ent)
|
|
{
|
|
int i, numspec;
|
|
|
|
// if the user wants to become a spectator, make sure he doesn't
|
|
// exceed max_spectators
|
|
|
|
if (ent->client->pers.spectator)
|
|
{
|
|
char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator");
|
|
if (*spectator_password->string &&
|
|
strcmp(spectator_password->string, "none") &&
|
|
strcmp(spectator_password->string, value))
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n");
|
|
ent->client->pers.spectator = false;
|
|
gi.WriteByte (svc_stufftext);
|
|
gi.WriteString ("spectator 0\n");
|
|
gi.unicast(ent, true);
|
|
return;
|
|
}
|
|
|
|
// count spectators
|
|
for (i = 1, numspec = 0; i <= maxclients->value; i++)
|
|
{
|
|
if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
|
|
numspec++;
|
|
}
|
|
|
|
if (numspec >= maxspectators->value)
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full.");
|
|
ent->client->pers.spectator = false;
|
|
// reset his spectator var
|
|
gi.WriteByte (svc_stufftext);
|
|
gi.WriteString ("spectator 0\n");
|
|
gi.unicast(ent, true);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// he was a spectator and wants to join the game
|
|
// he must have the right password
|
|
char *value = Info_ValueForKey (ent->client->pers.userinfo, "password");
|
|
if (*password->string && strcmp(password->string, "none") &&
|
|
strcmp(password->string, value))
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n");
|
|
ent->client->pers.spectator = true;
|
|
gi.WriteByte (svc_stufftext);
|
|
gi.WriteString ("spectator 1\n");
|
|
gi.unicast(ent, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// clear score on respawn
|
|
ent->client->pers.score = ent->client->resp.score = 0;
|
|
|
|
ent->svflags &= ~SVF_NOCLIENT;
|
|
PutClientInServer (ent);
|
|
|
|
// add a teleportation effect
|
|
if (!ent->client->pers.spectator)
|
|
{
|
|
// send effect
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (ent-g_edicts);
|
|
gi.WriteByte (MZ_LOGIN);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
// hold in place briefly
|
|
ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
|
|
ent->client->ps.pmove.pm_time = 14;
|
|
}
|
|
|
|
ent->client->respawn_time = level.time;
|
|
|
|
if (ent->client->pers.spectator)
|
|
gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
|
|
else
|
|
gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
|
|
}
|
|
|
|
//==============================================================
|
|
|
|
/*
|
|
===========
|
|
PutClientInServer
|
|
|
|
Called when a player connects to a server or respawns in
|
|
a deathmatch.
|
|
============
|
|
*/
|
|
void PutClientInServer (edict_t *ent)
|
|
{
|
|
extern int nostatus;
|
|
vec3_t mins = {-16, -16, -24};
|
|
vec3_t maxs = {16, 16, 32};
|
|
int index;
|
|
vec3_t spawn_origin, spawn_angles, spawn_viewangles;
|
|
gclient_t *client;
|
|
int i, tempchase;
|
|
gitem_t *newweapon;
|
|
char userinfo[MAX_INFO_STRING];
|
|
qboolean spawn_landmark;
|
|
qboolean spawn_levelchange;
|
|
int spawn_gunframe;
|
|
int spawn_modelframe;
|
|
int spawn_anim_end;
|
|
int spawn_pm_flags;
|
|
int spawn_style;
|
|
int spawn_health;
|
|
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
|
|
if (gamerules && gamerules->value && DMGame.SelectSpawnPoint) // PGM
|
|
DMGame.SelectSpawnPoint (ent, spawn_origin, spawn_angles, &spawn_style, &spawn_health); // PGM
|
|
else // PGM
|
|
// SelectSpawnPoint (ent, spawn_origin, spawn_angles);
|
|
SelectSpawnPoint (ent, spawn_origin, spawn_angles, &spawn_style, &spawn_health);
|
|
|
|
index = ent-g_edicts-1;
|
|
client = ent->client;
|
|
|
|
newweapon = client->pers.newweapon;
|
|
spawn_landmark = client->pers.spawn_landmark;
|
|
spawn_levelchange= client->pers.spawn_levelchange;
|
|
spawn_gunframe = client->pers.spawn_gunframe;
|
|
spawn_modelframe = client->pers.spawn_modelframe;
|
|
spawn_anim_end = client->pers.spawn_anim_end;
|
|
client->pers.spawn_landmark = false;
|
|
client->pers.spawn_levelchange = false;
|
|
|
|
if (spawn_landmark)
|
|
{
|
|
spawn_origin[2] -= 9;
|
|
VectorAdd(spawn_origin,client->pers.spawn_offset,spawn_origin);
|
|
VectorCopy(client->pers.spawn_angles,spawn_angles);
|
|
VectorCopy(client->pers.spawn_viewangles,spawn_viewangles);
|
|
VectorCopy(client->pers.spawn_velocity,ent->velocity);
|
|
spawn_pm_flags = client->pers.spawn_pm_flags;
|
|
}
|
|
|
|
// deathmatch wipes most client data every spawn
|
|
if (deathmatch->value)
|
|
{
|
|
|
|
resp = client->resp;
|
|
memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
|
|
InitClientPersistant (client, spawn_style);
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
}
|
|
else if (coop->value)
|
|
{
|
|
// int n;
|
|
|
|
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 < game.num_items; n++)
|
|
{
|
|
if (itemlist[n].flags & IT_KEY)
|
|
resp.coop_respawn.inventory[n] = client->pers.inventory[n];
|
|
}*/
|
|
resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
|
|
resp.coop_respawn.helpchanged = client->pers.helpchanged;
|
|
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);
|
|
|
|
//Knightmare- backup chasetoggle
|
|
if (client->chasetoggle)
|
|
tempchase = 1;
|
|
else
|
|
tempchase = 0;
|
|
|
|
// clear everything but the persistant data
|
|
saved = client->pers;
|
|
memset (client, 0, sizeof(*client));
|
|
client->pers = saved;
|
|
if (client->pers.health <= 0)
|
|
InitClientPersistant(client, spawn_style);
|
|
else if (spawn_style)
|
|
SelectStartWeapon(client, spawn_style);
|
|
client->resp = resp;
|
|
|
|
// copy some data from the client to the entity
|
|
FetchClientEntData (ent);
|
|
|
|
// Lazarus: Starting health < max. Presumably player was hurt in a crash
|
|
if ( (spawn_health > 0) && !deathmatch->value && !coop->value)
|
|
ent->health = min(ent->health, spawn_health);
|
|
|
|
// 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->in_mud = 0;
|
|
ent->flags &= ~FL_NO_KNOCKBACK;
|
|
ent->svflags &= ~SVF_DEADMONSTER;
|
|
// tpp
|
|
ent->svflags &= ~SVF_NOCLIENT;
|
|
// turn on prediction
|
|
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
// end tpp
|
|
ent->client->spycam = NULL;
|
|
ent->client->camplayer = NULL;
|
|
|
|
ent->flags &= ~FL_SAM_RAIMI; // PGM - turn off sam raimi flag
|
|
|
|
VectorCopy (mins, ent->mins);
|
|
VectorCopy (maxs, ent->maxs);
|
|
if (!spawn_landmark)
|
|
{
|
|
VectorClear (ent->velocity);
|
|
VectorClear (ent->relative_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;
|
|
}
|
|
// DWH
|
|
client->original_fov = client->ps.fov;
|
|
// end DWH
|
|
|
|
//PGM
|
|
if (client->pers.weapon)
|
|
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
|
|
else
|
|
client->ps.gunindex = 0;
|
|
//PGM
|
|
|
|
// Server-side speed control stuff
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
client->ps.maxspeed = player_max_speed->value;
|
|
client->ps.duckspeed = player_crouch_speed->value;
|
|
client->ps.accel = player_accel->value;
|
|
client->ps.stopspeed = player_stopspeed->value;
|
|
#endif
|
|
|
|
// clear entity state values
|
|
ent->s.effects = 0;
|
|
ent->s.modelindex = MAX_MODELS-1; // was 255, will use the skin specified model
|
|
if (ITEM_INDEX(client->pers.weapon) == noweapon_index)
|
|
ent->s.modelindex2 = 0;
|
|
else
|
|
ent->s.modelindex2 = MAX_MODELS-1; // was 255, custom gun model
|
|
// skinnum is player num and weapon number
|
|
// weapon number will be added in changeweapon
|
|
ent->s.skinnum = ent - g_edicts - 1;
|
|
|
|
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;
|
|
if (spawn_landmark)
|
|
{
|
|
VectorCopy(spawn_viewangles, client->ps.viewangles);
|
|
// client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
}
|
|
else
|
|
VectorCopy(ent->s.angles, client->ps.viewangles);
|
|
VectorCopy (ent->s.angles, client->v_angle);
|
|
|
|
// spawn a spectator
|
|
if (client->pers.spectator)
|
|
{
|
|
client->chase_target = NULL;
|
|
|
|
client->resp.spectator = true;
|
|
|
|
ent->movetype = MOVETYPE_NOCLIP;
|
|
ent->solid = SOLID_NOT;
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
ent->client->ps.gunindex = 0;
|
|
gi.linkentity (ent);
|
|
return;
|
|
}
|
|
else
|
|
client->resp.spectator = false;
|
|
|
|
client->flashlight_active = false;
|
|
|
|
if (!KillBox (ent))
|
|
{ // could't spawn in?
|
|
}
|
|
|
|
gi.linkentity (ent);
|
|
|
|
// My tribute to Cash's level-specific hacks. I hope I live
|
|
// up to his trailblazing cheese.
|
|
if (Q_stricmp(level.mapname, "rboss") == 0)
|
|
{
|
|
// if you get on to rboss in single player or coop, ensure
|
|
// the player has the nuke key. (not in DM)
|
|
if (!(deathmatch->value))
|
|
{
|
|
gitem_t *item;
|
|
|
|
item = FindItem("Antimatter Bomb");
|
|
client->pers.selected_item = ITEM_INDEX(item);
|
|
client->pers.inventory[client->pers.selected_item] = 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (spawn_levelchange && !client->chasetoggle && !client->pers.newweapon)
|
|
{
|
|
// we already had a weapon when the level changed... no need to bring it up
|
|
int i;
|
|
|
|
client->pers.lastweapon = client->pers.weapon;
|
|
client->newweapon = NULL;
|
|
client->machinegun_shots = 0;
|
|
i = ((client->pers.weapon->weapmodel & 0xff) << 8);
|
|
ent->s.skinnum = (ent - g_edicts - 1) | i;
|
|
if (client->pers.weapon->ammo)
|
|
client->ammo_index = ITEM_INDEX(FindItem(client->pers.weapon->ammo));
|
|
else
|
|
client->ammo_index = 0;
|
|
client->weaponstate = WEAPON_READY;
|
|
client->ps.gunframe = 0;
|
|
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
|
|
client->ps.gunframe = spawn_gunframe;
|
|
ent->s.frame = spawn_modelframe;
|
|
client->anim_end = spawn_anim_end;
|
|
}
|
|
else
|
|
{
|
|
// 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;
|
|
|
|
// Knightmare- restore chasetoggle
|
|
ent->client->chasetoggle = tempchase;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
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);
|
|
|
|
//PGM
|
|
if (gamerules && gamerules->value && DMGame.ClientBegin)
|
|
{
|
|
DMGame.ClientBegin (ent);
|
|
}
|
|
//PGM
|
|
|
|
// locate ent at a spawn point
|
|
PutClientInServer (ent);
|
|
|
|
if (level.intermissiontime)
|
|
{
|
|
MoveClientToIntermission (ent);
|
|
}
|
|
else
|
|
{
|
|
// 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);
|
|
//Knightmare- restart chasecam
|
|
if (ent->client->chasetoggle)
|
|
ChasecamStart (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;
|
|
}
|
|
|
|
Fog_Off (ent);
|
|
|
|
stuffcmd(ent,"alias +zoomin zoomin;alias -zoomin zoominstop\n");
|
|
stuffcmd(ent,"alias +zoomout zoomout;alias -zoomout zoomoutstop\n");
|
|
stuffcmd(ent,"alias +zoom zoomon;alias -zoom zoomoff\n");
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// DWH
|
|
SetLazarusCrosshair (ent); // backup crosshair
|
|
SetSensitivities (ent, true);
|
|
|
|
if (game.maxclients == 1)
|
|
{
|
|
// For SP games, check for monsters who were mad at player
|
|
// in previous level and have changed levels with the player
|
|
edict_t *monster;
|
|
for (i=2; i<globals.num_edicts; i++)
|
|
{
|
|
monster = &g_edicts[i];
|
|
if (!monster->inuse)
|
|
continue;
|
|
if (!(monster->svflags & SVF_MONSTER))
|
|
continue;
|
|
if (monster->health <= 0)
|
|
continue;
|
|
if (monster->monsterinfo.aiflags & AI_RESPAWN_FINDPLAYER)
|
|
{
|
|
monster->monsterinfo.aiflags &= ~AI_RESPAWN_FINDPLAYER;
|
|
if (!monster->enemy)
|
|
{
|
|
monster->enemy = ent;
|
|
FoundTarget(monster);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 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 ShowGun(edict_t *ent);
|
|
|
|
void ClientUserinfoChanged (edict_t *ent, char *userinfo)
|
|
{
|
|
char *s;
|
|
int playernum;
|
|
|
|
// check for malformed or illegal info strings
|
|
if (!Info_Validate(userinfo))
|
|
{
|
|
// strncpy (userinfo, "\\name\\badinfo\\skin\\male/grunt");
|
|
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 spectator
|
|
s = Info_ValueForKey (userinfo, "spectator");
|
|
// spectators are only supported in deathmatch
|
|
// if (deathmatch->value && strcmp(s, "0"))
|
|
if (deathmatch->value && *s && strcmp(s, "0"))
|
|
ent->client->pers.spectator = true;
|
|
else
|
|
ent->client->pers.spectator = false;
|
|
|
|
// set skin
|
|
s = Info_ValueForKey (userinfo, "skin");
|
|
|
|
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;*/
|
|
float new_fov;
|
|
|
|
new_fov = atoi(Info_ValueForKey(userinfo, "fov"));
|
|
if (new_fov < 1)
|
|
new_fov = 90;
|
|
else if (new_fov > 160)
|
|
new_fov = 160;
|
|
if (new_fov != ent->client->original_fov)
|
|
{
|
|
ent->client->ps.fov = new_fov;
|
|
ent->client->original_fov = new_fov;
|
|
}
|
|
}
|
|
|
|
// handedness
|
|
s = Info_ValueForKey (userinfo, "hand");
|
|
if (strlen(s))
|
|
{
|
|
ent->client->pers.hand = atoi(s);
|
|
}
|
|
|
|
// save off the userinfo in case we want to check something later
|
|
strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
|
|
if (use_vwep->value)
|
|
ShowGun(ent); //Knightmare- added vwep code
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
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");
|
|
if (SV_FilterPacket(value))
|
|
{
|
|
Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
|
|
return false;
|
|
}
|
|
|
|
// check for a spectator
|
|
value = Info_ValueForKey (userinfo, "spectator");
|
|
// if (deathmatch->value && strcmp(value, "0"))
|
|
if (deathmatch->value && *value && strcmp(value, "0"))
|
|
{
|
|
int i, numspec;
|
|
|
|
if (*spectator_password->string &&
|
|
strcmp(spectator_password->string, "none") &&
|
|
strcmp(spectator_password->string, value))
|
|
{
|
|
Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
|
|
return false;
|
|
}
|
|
|
|
// count spectators
|
|
for (i = numspec = 0; i < maxclients->value; i++)
|
|
{
|
|
if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator)
|
|
numspec++;
|
|
}
|
|
|
|
if (numspec >= maxspectators->value)
|
|
{
|
|
Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check for a password
|
|
value = Info_ValueForKey (userinfo, "password");
|
|
if (*password->string && strcmp(password->string, "none") &&
|
|
strcmp(password->string, value))
|
|
{
|
|
Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
|
|
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, world->style);
|
|
}
|
|
|
|
ClientUserinfoChanged (ent, userinfo);
|
|
|
|
if (game.maxclients > 1)
|
|
gi.dprintf ("%s connected\n", ent->client->pers.netname);
|
|
|
|
ent->svflags = 0; // make sure we start with known default
|
|
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;
|
|
|
|
|
|
// DWH
|
|
SetLazarusCrosshair (ent); // backup crosshair
|
|
ent->client->zooming = 0;
|
|
ent->client->zoomed = false;
|
|
SetSensitivities (ent, true);
|
|
// end DWH
|
|
|
|
if (ent->client->textdisplay)
|
|
Text_Close (ent);
|
|
|
|
gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
|
|
|
|
//============
|
|
//ROGUE
|
|
// make sure no trackers are still hurting us.
|
|
if (ent->client->tracker_pain_framenum)
|
|
RemoveAttackingPainDaemons (ent);
|
|
|
|
if (ent->client->owned_sphere)
|
|
{
|
|
if (ent->client->owned_sphere->inuse)
|
|
G_FreeEdict (ent->client->owned_sphere);
|
|
ent->client->owned_sphere = NULL;
|
|
}
|
|
|
|
if (gamerules && gamerules->value)
|
|
{
|
|
if (DMGame.PlayerDisconnect)
|
|
DMGame.PlayerDisconnect(ent);
|
|
}
|
|
//ROGUE
|
|
//============
|
|
|
|
// 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;
|
|
|
|
if (ent->client->spycam)
|
|
camera_off(ent);
|
|
|
|
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);
|
|
}
|
|
|
|
void RemovePush(edict_t *ent)
|
|
{
|
|
ent->client->push->s.sound = 0;
|
|
ent->client->push->activator = NULL;
|
|
ent->client->push = NULL;
|
|
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
// If tpp is NOT always on, and auto-switch for func_pushables IS on,
|
|
// and we're currently in third-person view, switch it off
|
|
// Knightmare- don't autoswitch if client-side chasecam is on
|
|
if (tpp_auto->value && (!cl_thirdperson->value || deathmatch->value || coop->value)
|
|
&& !ent->client->chasetoggle)
|
|
// Cmd_Chasecam_Toggle(ent);
|
|
ChasecamRemove (ent);
|
|
}
|
|
|
|
void ClientPushPushable(edict_t *ent)
|
|
{
|
|
edict_t *box = ent->client->push;
|
|
vec_t dist;
|
|
vec3_t new_origin, v, vbox;
|
|
|
|
VectorAdd (box->absmax,box->absmin,vbox);
|
|
VectorScale (vbox,0.5,vbox);
|
|
if (point_infront(ent,vbox))
|
|
{
|
|
VectorSubtract (ent->s.origin, box->offset, new_origin);
|
|
VectorSubtract (new_origin, box->s.origin, v);
|
|
v[2] = 0;
|
|
dist = VectorLength(v);
|
|
if (dist > 8)
|
|
{
|
|
// func_pushable got hung up somehow. Break off contact
|
|
RemovePush(ent);
|
|
}
|
|
else if (dist > 0)
|
|
{
|
|
if (!box->speaker)
|
|
box->s.sound = box->noise_index;
|
|
box_walkmove ( box, vectoyaw(v), dist );
|
|
}
|
|
else
|
|
box->s.sound = 0;
|
|
}
|
|
else
|
|
RemovePush(ent);
|
|
}
|
|
|
|
void ClientSpycam (edict_t *ent)
|
|
{
|
|
gclient_t *client = ent->client;
|
|
edict_t *camera = ent->client->spycam;
|
|
pmove_t pm;
|
|
qboolean is_actor;
|
|
trace_t tr;
|
|
vec3_t forward, left, up;
|
|
vec3_t dir, start;
|
|
float dist;
|
|
int i;
|
|
|
|
memset (&pm, 0, sizeof(pm));
|
|
if ( client->ucmd.sidemove && (level.time > ent->last_move_time + 1) )
|
|
{
|
|
camera->flags &= ~FL_ROBOT;
|
|
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->monsterinfo.aiflags & AI_ACTOR)
|
|
{
|
|
camera->flags |= FL_ROBOT;
|
|
if (camera->monsterinfo.aiflags & AI_FOLLOW_LEADER)
|
|
{
|
|
camera->monsterinfo.aiflags &= ~AI_FOLLOW_LEADER;
|
|
camera->monsterinfo.old_leader = NULL;
|
|
camera->monsterinfo.leader = NULL;
|
|
camera->movetarget = camera->goalentity = NULL;
|
|
camera->monsterinfo.stand(camera);
|
|
}
|
|
}
|
|
}
|
|
if ((camera->svflags & SVF_MONSTER) && (camera->monsterinfo.aiflags & AI_ACTOR))
|
|
is_actor = true;
|
|
else
|
|
is_actor = false;
|
|
if (camera->enemy && (camera->enemy->deadflag || !camera->enemy->inuse))
|
|
camera->enemy = NULL;
|
|
AngleVectors (camera->s.angles ,forward, left, up);
|
|
|
|
if (is_actor && !camera->enemy)
|
|
{
|
|
if ((abs(client->ucmd.forwardmove) > 199) && (camera->groundentity))
|
|
{ // walk/run
|
|
|
|
edict_t *thing;
|
|
vec3_t end;
|
|
float dist;
|
|
|
|
thing = camera->vehicle;
|
|
|
|
VectorMA(camera->s.origin, WORLD_SIZE, forward, end); // was 8192
|
|
tr = gi.trace(camera->s.origin, camera->mins, camera->maxs, end, camera, MASK_SOLID);
|
|
if (client->ucmd.forwardmove < 0)
|
|
{
|
|
trace_t back;
|
|
VectorMA(camera->s.origin, -WORLD_SIZE, forward, end); // was -8192
|
|
back = gi.trace(camera->s.origin, camera->mins, camera->maxs, end, camera, MASK_SOLID);
|
|
VectorSubtract(back.endpos,camera->s.origin,end);
|
|
dist = VectorLength(end);
|
|
VectorCopy (tr.endpos ,end);
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract (tr.endpos, camera->s.origin, end);
|
|
dist = VectorLength(end) - 8;
|
|
VectorMA (camera->s.origin, dist, forward, end);
|
|
}
|
|
if (dist > 8)
|
|
{
|
|
if (!thing || !thing->inuse || Q_stricmp(thing->classname,"thing"))
|
|
thing = camera->vehicle = SpawnThing();
|
|
thing->touch_debounce_time = level.time + 5.0;
|
|
thing->target_ent = camera;
|
|
VectorCopy(end,thing->s.origin);
|
|
ED_CallSpawn(thing);
|
|
camera->monsterinfo.aiflags |= AI_CHASE_THING;
|
|
camera->monsterinfo.aiflags &= ~(AI_CHICKEN | AI_STAND_GROUND);
|
|
camera->monsterinfo.pausetime = 0;
|
|
camera->movetarget = camera->goalentity = thing;
|
|
camera->monsterinfo.old_leader = NULL;
|
|
camera->monsterinfo.leader = thing;
|
|
VectorSubtract (thing->s.origin, camera->s.origin, dir);
|
|
camera->ideal_yaw = vectoyaw(dir);
|
|
if (client->ucmd.forwardmove > 300)
|
|
actor_run(camera);
|
|
else if (client->ucmd.forwardmove > 199)
|
|
actor_walk(camera);
|
|
else if (client->ucmd.forwardmove < -300)
|
|
actor_run_back(camera);
|
|
else
|
|
actor_walk_back(camera);
|
|
}
|
|
else if (thing)
|
|
{
|
|
camera->monsterinfo.aiflags &= ~AI_CHASE_THING;
|
|
camera->movetarget = camera->goalentity = NULL;
|
|
G_FreeEdict(thing);
|
|
camera->vehicle = NULL;
|
|
actor_stand(camera);
|
|
}
|
|
}
|
|
if ((client->ucmd.forwardmove == 0) && (camera->groundentity))
|
|
{
|
|
// stop
|
|
edict_t *thing = camera->vehicle;
|
|
if (thing)
|
|
{
|
|
camera->monsterinfo.aiflags &= ~AI_CHASE_THING;
|
|
camera->movetarget = camera->goalentity = NULL;
|
|
G_FreeEdict(thing);
|
|
camera->vehicle = NULL;
|
|
actor_stand(camera);
|
|
}
|
|
}
|
|
|
|
if (client->ucmd.upmove)
|
|
{
|
|
if ((client->ucmd.upmove > 0) && camera->groundentity && !camera->waterlevel)
|
|
{
|
|
// jump
|
|
if (client->ucmd.forwardmove > 300)
|
|
VectorScale (forward, 400, camera->velocity);
|
|
else if (client->ucmd.forwardmove > 199)
|
|
VectorScale (forward, 200, camera->velocity);
|
|
else if (client->ucmd.forwardmove < -300)
|
|
VectorScale (forward, -400, camera->velocity);
|
|
else if (client->ucmd.forwardmove < -199)
|
|
VectorScale (forward, -200, camera->velocity);
|
|
camera->velocity[2] = 250;
|
|
camera->monsterinfo.savemove = camera->monsterinfo.currentmove;
|
|
actor_jump(camera);
|
|
camera->groundentity = NULL;
|
|
}
|
|
else if ((client->ucmd.upmove < 0) && (camera->groundentity) && !(camera->monsterinfo.aiflags & AI_CROUCH))
|
|
{
|
|
// crouch
|
|
if ( (camera->monsterinfo.currentmove == &actor_move_walk) ||
|
|
(camera->monsterinfo.currentmove == &actor_move_run) ||
|
|
(camera->monsterinfo.currentmove == &actor_move_run_bad) )
|
|
{
|
|
camera->monsterinfo.currentmove = &actor_move_crouchwalk;
|
|
camera->maxs[2] -= 28;
|
|
camera->viewheight -= 28;
|
|
camera->move_origin[2] -= 28;
|
|
camera->monsterinfo.aiflags |= AI_CROUCH;
|
|
}
|
|
else if ( (camera->monsterinfo.currentmove == &actor_move_walk_back) ||
|
|
(camera->monsterinfo.currentmove == &actor_move_run_back) )
|
|
{
|
|
camera->monsterinfo.currentmove = &actor_move_crouchwalk_back;
|
|
camera->maxs[2] -= 28;
|
|
camera->viewheight -= 28;
|
|
camera->move_origin[2] -= 28;
|
|
camera->monsterinfo.aiflags |= AI_CROUCH;
|
|
}
|
|
else if (camera->monsterinfo.currentmove == &actor_move_stand)
|
|
{
|
|
camera->monsterinfo.currentmove = &actor_move_crouch;
|
|
camera->maxs[2] -= 28;
|
|
camera->viewheight -= 28;
|
|
camera->move_origin[2] -= 28;
|
|
camera->monsterinfo.aiflags |= AI_CROUCH;
|
|
}
|
|
}
|
|
}
|
|
if ( (client->ucmd.upmove >= 0) && (camera->monsterinfo.aiflags & AI_CROUCH))
|
|
{
|
|
// come out of crouch
|
|
camera->maxs[2] += 28;
|
|
camera->viewheight += 28;
|
|
camera->move_origin[2] += 28;
|
|
camera->monsterinfo.aiflags &= ~AI_CROUCH;
|
|
if (camera->monsterinfo.currentmove == &actor_move_crouchwalk)
|
|
actor_walk(camera);
|
|
else if (camera->monsterinfo.currentmove == &actor_move_crouchwalk_back)
|
|
actor_walk_back(camera);
|
|
else if (camera->monsterinfo.currentmove == &actor_move_crouch)
|
|
actor_stand(camera);
|
|
}
|
|
}
|
|
|
|
client->ps.pmove.pm_type = PM_FREEZE;
|
|
if (camera->viewer == ent)
|
|
{
|
|
if ( (client->old_owner_angles[0] != client->ucmd.angles[0]) ||
|
|
(client->old_owner_angles[1] != client->ucmd.angles[1]) )
|
|
{
|
|
// Give game a bit of time to catch up after player
|
|
// causes ucmd pitch angle to roll over... otherwise
|
|
// we'll hit on the above test even though player
|
|
// hasn't hit +lookup/+lookdown
|
|
float delta;
|
|
delta = level.time - camera->touch_debounce_time;
|
|
if ( delta < 0 || delta > 1.0)
|
|
{
|
|
if (is_actor)
|
|
{
|
|
float diff;
|
|
diff = SHORT2ANGLE(client->ucmd.angles[1] - client->old_owner_angles[1]);
|
|
if (diff < -180)
|
|
diff += 360;
|
|
if (diff > 180)
|
|
diff -= 360;
|
|
camera->ideal_yaw += diff;
|
|
if ((abs(diff) > 100) && camera->vehicle)
|
|
{
|
|
vec3_t angles;
|
|
vec3_t end, f;
|
|
VectorSet (angles, 0, camera->ideal_yaw, 0);
|
|
AngleVectors (angles, f, NULL, NULL);
|
|
VectorMA (camera->s.origin, WORLD_SIZE, f, end); // was 8192
|
|
tr = gi.trace(camera->s.origin, camera->mins, camera->maxs, end, camera, MASK_SOLID);
|
|
VectorCopy (tr.endpos, camera->vehicle->s.origin);
|
|
camera->vehicle->touch_debounce_time = level.time + 5.0f;
|
|
gi.linkentity(camera->vehicle);
|
|
}
|
|
ai_turn (camera, 0.0f);
|
|
diff = SHORT2ANGLE(client->ucmd.angles[0]-client->old_owner_angles[0]);
|
|
if (diff < -180)
|
|
diff += 360;
|
|
if (diff > 180)
|
|
diff -= 360;
|
|
camera->move_angles[0] += diff;
|
|
client->old_owner_angles[0] = client->ucmd.angles[0];
|
|
client->old_owner_angles[1] = client->ucmd.angles[1];
|
|
}
|
|
}
|
|
}
|
|
if ( (client->ucmd.buttons & BUTTON_ATTACK) && (camera->sounds >= 0) )
|
|
{
|
|
// Knightmare- stop firing weapon when switching to turret
|
|
client->latched_buttons &= ~BUTTONS_ATTACK;
|
|
client->buttons &= ~BUTTONS_ATTACK;
|
|
if (level.time >= camera->monsterinfo.attack_finished)
|
|
{
|
|
// client->latched_buttons &= ~BUTTONS_ATTACK;
|
|
if ( !Q_stricmp(camera->classname, "turret_breach") || !Q_stricmp(camera->classname, "model_turret") )
|
|
{
|
|
if ( (camera->sounds == 5) || (camera->sounds == 6) )
|
|
camera->monsterinfo.attack_finished = level.time;
|
|
else
|
|
camera->monsterinfo.attack_finished = level.time + 1.0;
|
|
turret_breach_fire (camera);
|
|
}
|
|
else if (is_actor)
|
|
{
|
|
int weapon = camera->actor_weapon[camera->actor_current_weapon];
|
|
if (!camera->enemy)
|
|
{
|
|
edict_t *target;
|
|
target = LookingAt(ent,0,NULL,NULL);
|
|
if (target && target->takedamage && (target != client->camplayer))
|
|
{
|
|
if (camera->vehicle)
|
|
{
|
|
// Currently following "thing" - turn that off
|
|
camera->monsterinfo.aiflags &= ~AI_CHASE_THING;
|
|
camera->movetarget = camera->goalentity = NULL;
|
|
G_FreeEdict (camera->vehicle);
|
|
camera->vehicle = NULL;
|
|
}
|
|
camera->enemy = target;
|
|
actor_fire (camera);
|
|
camera->enemy = NULL;
|
|
if (camera->monsterinfo.aiflags & AI_HOLD_FRAME)
|
|
camera->monsterinfo.attack_finished = level.time + FRAMETIME;
|
|
else
|
|
camera->monsterinfo.attack_finished = level.time + 1.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Knightmare- stop firing weapon when switching to spycam
|
|
else if ( (client->ucmd.buttons & BUTTON_ATTACK) && (camera->sounds < 0) ) {
|
|
client->latched_buttons &= ~BUTTONS_ATTACK;
|
|
client->buttons &= ~BUTTONS_ATTACK;
|
|
}
|
|
|
|
if (client->zoomed) {
|
|
camera->touch_debounce_time = max(camera->touch_debounce_time, level.time + 1.0);
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (dist < 0) dist = 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);
|
|
// client->old_owner_angles[0] = client->ucmd.angles[0];
|
|
// client->old_owner_angles[1] = client->ucmd.angles[1];
|
|
|
|
G_TouchTriggers (ent); // we'll only allow touching trigger_look with "Cam Owner" SF
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
edict_t *ground;
|
|
pmove_t pm;
|
|
vec_t t;
|
|
// vec3_t view;
|
|
vec3_t oldorigin, oldvelocity;
|
|
int i, j;
|
|
float ground_speed;
|
|
|
|
level.current_entity = ent;
|
|
client = ent->client;
|
|
// Lazarus: Copy latest usercmd stuff for use in other routines
|
|
client->ucmd = *ucmd;
|
|
|
|
VectorCopy(ent->s.origin,oldorigin);
|
|
VectorCopy(ent->velocity,oldvelocity);
|
|
ground = ent->groundentity;
|
|
|
|
if (ground && (ground->movetype == MOVETYPE_PUSH) && (ground != world) && ground->turn_rider)
|
|
ground_speed = VectorLength(ground->velocity);
|
|
else
|
|
ground_speed = 0;
|
|
|
|
// Knightmare- determine if client-side prediction should be on
|
|
// Thanks to DH
|
|
if ( (ent->in_mud) ||
|
|
(ent->client->push) ||
|
|
(ent->vehicle) ||
|
|
(ent->client->chasetoggle) ||
|
|
(ent->turret) ||
|
|
(ent->client->spycam) ||
|
|
(ground_speed > 0) )
|
|
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
else
|
|
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
|
|
// Server-side speed control stuff
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
client->ps.maxspeed = player_max_speed->value;
|
|
client->ps.duckspeed = player_crouch_speed->value;
|
|
client->ps.accel = player_accel->value;
|
|
client->ps.stopspeed = player_stopspeed->value;
|
|
#endif
|
|
|
|
// VectorCopy(ent->s.origin,view);
|
|
// view[2] += ent->viewheight;
|
|
Fog (ent); //view);
|
|
|
|
// MUD - get mud level
|
|
if (level.mud_puddles)
|
|
{
|
|
edict_t *mud;
|
|
|
|
ent->in_mud = 0;
|
|
for (i=game.maxclients+1; i<globals.num_edicts && !ent->in_mud; i++)
|
|
{
|
|
mud = &g_edicts[i];
|
|
if (!mud->inuse) continue;
|
|
if (!(mud->svflags & SVF_MUD)) continue;
|
|
if (ent->absmin[0] > mud->absmax[0]) continue;
|
|
if (ent->absmin[1] > mud->absmax[1]) continue;
|
|
if (ent->absmin[2] > mud->absmax[2]) continue;
|
|
if (ent->absmax[0] < mud->absmin[0]) continue;
|
|
if (ent->absmax[1] < mud->absmin[1]) continue;
|
|
if (ent->absmax[2] < mud->absmin[2]) continue;
|
|
ent->in_mud = 1;
|
|
if (ent->s.origin[2] < mud->absmax[2])
|
|
ent->in_mud = 2;
|
|
if (ent->s.origin[2] + ent->viewheight < mud->absmax[2])
|
|
ent->in_mud = 3;
|
|
}
|
|
}
|
|
|
|
// USE - special actions taken when +use is pressed
|
|
if (!client->use && (ucmd->buttons & BUTTON_USE))
|
|
{
|
|
// 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;
|
|
|
|
viewing = LookingAt(ent,0,intersect,&range);
|
|
if (viewing && viewing->classname)
|
|
{
|
|
if (!Q_stricmp(viewing->classname,"crane_control") && range <= 100)
|
|
crane_control_action(viewing,ent,intersect);
|
|
if (!Q_stricmp(viewing->classname,"target_lock_digit") && range <= 100)
|
|
lock_digit_increment(viewing,ent);
|
|
if (!Q_stricmp(viewing->classname,"func_trainbutton") && (viewing->spawnflags & 1) && range <= 64)
|
|
trainbutton_use(viewing,ent,ent);
|
|
|
|
// Knightmare- different range for chasecam
|
|
if (!Q_stricmp(viewing->classname,"func_monitor") && ((range <= 100) || (client->chaseactive && range <= 190)) )
|
|
{
|
|
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];
|
|
}
|
|
}
|
|
if (viewing->monsterinfo.aiflags & AI_ACTOR)
|
|
{
|
|
if (viewing->monsterinfo.aiflags & AI_FOLLOW_LEADER)
|
|
{
|
|
viewing->monsterinfo.aiflags &= ~AI_FOLLOW_LEADER;
|
|
viewing->monsterinfo.old_leader = NULL;
|
|
viewing->monsterinfo.leader = NULL;
|
|
viewing->movetarget = viewing->goalentity = NULL;
|
|
viewing->monsterinfo.stand(viewing);
|
|
}
|
|
else
|
|
{
|
|
vec3_t dir;
|
|
viewing->monsterinfo.aiflags |= AI_FOLLOW_LEADER;
|
|
viewing->monsterinfo.leader = ent;
|
|
VectorSubtract(ent->s.origin,viewing->s.origin,dir);
|
|
viewing->ideal_yaw = vectoyaw(dir);
|
|
if (fabs(viewing->s.angles[YAW] - viewing->ideal_yaw) < 90)
|
|
actor_salute(viewing);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ucmd->buttons & BUTTON_USE)
|
|
client->use = 1;
|
|
else
|
|
client->use = 0;
|
|
|
|
if ( client->push )
|
|
{
|
|
// currently pushing or pulling a func_pushable
|
|
if ( !client->use )
|
|
{
|
|
// whoops - released USE key
|
|
RemovePush(ent);
|
|
}
|
|
else if ( (!ent->groundentity) && (ent->waterlevel==0 || client->push->waterlevel == 0 ) )
|
|
{
|
|
// oops, we fall down
|
|
RemovePush(ent);
|
|
}
|
|
else
|
|
{
|
|
// Scale client velocity by mass of func_pushable
|
|
t = VectorLength(ent->velocity);
|
|
if ( t > client->maxvelocity )
|
|
VectorScale(ent->velocity, client->maxvelocity/t, ent->velocity);
|
|
client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
t = 200./client->push->mass;
|
|
ucmd->forwardmove *= t;
|
|
ucmd->sidemove *= t;
|
|
}
|
|
}
|
|
|
|
if (ent->turret && ucmd->upmove > 20)
|
|
turret_disengage (ent->turret);
|
|
|
|
// INTERMISSION
|
|
if (level.intermissiontime)
|
|
{
|
|
// Lazarus spycam
|
|
if (client->spycam)
|
|
camera_off(ent);
|
|
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;
|
|
}
|
|
|
|
// Zaero
|
|
if (ent->movetype == MOVETYPE_FREEZE)
|
|
{
|
|
client->ps.pmove.pm_type = PM_FREEZE;
|
|
return;
|
|
}
|
|
// Zaero
|
|
|
|
// TARGET_MONITOR angles
|
|
if (ent->target_ent && !Q_stricmp(ent->target_ent->classname,"target_monitor"))
|
|
{
|
|
edict_t *monitor = ent->target_ent;
|
|
if (monitor->target_ent && monitor->target_ent->inuse)
|
|
{
|
|
if (monitor->spawnflags & 2)
|
|
VectorCopy(monitor->target_ent->s.angles,client->ps.viewangles);
|
|
else
|
|
{
|
|
vec3_t dir;
|
|
VectorSubtract(monitor->target_ent->s.origin,monitor->s.origin,dir);
|
|
vectoangles(dir,client->ps.viewangles);
|
|
}
|
|
}
|
|
else
|
|
VectorCopy (monitor->s.angles, client->ps.viewangles);
|
|
VectorCopy(monitor->s.origin,ent->s.origin);
|
|
client->ps.pmove.pm_type = PM_FREEZE;
|
|
return;
|
|
}
|
|
|
|
// THIRDPERSON VIEW in/out
|
|
// if NOT pushing something AND in third person AND use key is pressed,
|
|
// move viewpoint in/out
|
|
/* if (client->chasetoggle && !client->push)
|
|
{
|
|
if ((ucmd->buttons & BUTTON_USE) && (!deathmatch->value))
|
|
{
|
|
client->use = 1;
|
|
if ((ucmd->forwardmove < 0) && (client->zoom < 100))
|
|
client->zoom++;
|
|
else if ((ucmd->forwardmove > 0) && (client->zoom > -40))
|
|
client->zoom--;
|
|
ucmd->forwardmove = 0;
|
|
ucmd->sidemove = 0;
|
|
}
|
|
else if (client->use)
|
|
{
|
|
//client->zoom = 0;
|
|
if (client->oldplayer)
|
|
{
|
|
// set angles
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->oldplayer->s.angles[i] - ent->client->resp.cmd_angles[i]);
|
|
}
|
|
}
|
|
client->use = 0;
|
|
}
|
|
}*/
|
|
|
|
// ZOOM
|
|
if (client->zooming)
|
|
{
|
|
client->pers.hand = 2;
|
|
if (client->zooming > 0)
|
|
{
|
|
if (client->ps.fov > 5)
|
|
{
|
|
client->ps.fov -= client->frame_zoomrate;
|
|
if (client->ps.fov < 5)
|
|
client->ps.fov = 5;
|
|
} else {
|
|
client->ps.fov = 5;
|
|
}
|
|
client->zoomed = true;
|
|
}
|
|
else
|
|
{
|
|
if (client->ps.fov < client->original_fov)
|
|
{
|
|
client->ps.fov += client->frame_zoomrate;
|
|
if (client->ps.fov > client->original_fov)
|
|
{
|
|
client->ps.fov = client->original_fov;
|
|
client->zoomed = false;
|
|
}
|
|
else
|
|
client->zoomed = true;
|
|
}
|
|
else
|
|
{
|
|
client->ps.fov = client->original_fov;
|
|
client->zoomed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Mappack - stop client from turning when this flag is set.
|
|
// if (ent->client->incamera)
|
|
// return;
|
|
|
|
// SPYCAM
|
|
if (client->spycam)
|
|
{
|
|
ClientSpycam(ent);
|
|
return; // no movement while in cam
|
|
}
|
|
|
|
pm_passent = ent;
|
|
|
|
//Knightmare- restart chasecam if toggled on and not active
|
|
if (ent->client->chasetoggle && !ent->client->chaseactive)
|
|
ChasecamStart (ent);
|
|
|
|
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]);
|
|
//Knightmare- Gen cam code
|
|
// ### Hentai ### BEGIN - this is improved chasecam stuff
|
|
if (ucmd->forwardmove < 0)
|
|
{
|
|
ent->speed--;
|
|
if (ent->speed < 0)
|
|
ent->speed = 0;
|
|
}
|
|
else if (ucmd->forwardmove > 0)
|
|
{
|
|
ent->speed++;
|
|
}
|
|
// ### Hentai ### END
|
|
}
|
|
else
|
|
{
|
|
// 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;
|
|
|
|
//PGM trigger_gravity support
|
|
if (level.time > ent->gravity_debounce_time)
|
|
// client->ps.pmove.gravity = sv_gravity->value;
|
|
client->ps.pmove.gravity = sv_gravity->value * ent->gravity;
|
|
//PGM
|
|
else
|
|
client->ps.pmove.gravity = 0;
|
|
|
|
#ifdef JETPACK_MOD
|
|
if ( client->jetpack )
|
|
{
|
|
if ( (ucmd->upmove != 0) || (ucmd->forwardmove != 0) || (ucmd->sidemove != 0) )
|
|
{
|
|
if (ucmd->upmove > 0 || !ent->groundentity)
|
|
{
|
|
if (!client->jetpack_thrusting)
|
|
{
|
|
gi.sound (ent, CHAN_AUTO, gi.soundindex("jetpack/rev.wav"), 1, ATTN_NORM, 0);
|
|
client->jetpack_start_thrust = level.framenum;
|
|
}
|
|
client->jetpack_thrusting = true;
|
|
}
|
|
else
|
|
client->jetpack_thrusting = false;
|
|
}
|
|
else
|
|
client->jetpack_thrusting = false;
|
|
|
|
if (client->jetpack_framenum + client->pers.inventory[fuel_index] > level.framenum)
|
|
{
|
|
if (jetpack_weenie->value)
|
|
{
|
|
Jet_ApplyJet( ent, ucmd );
|
|
if (client->jetpack_framenum < level.framenum)
|
|
{
|
|
if (!client->jetpack_infinite)
|
|
client->pers.inventory[fuel_index] -= 10;
|
|
client->jetpack_framenum = level.framenum + 10;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (client->jetpack_thrusting)
|
|
Jet_ApplyJet( ent, ucmd );
|
|
if (client->jetpack_framenum <= level.framenum)
|
|
{
|
|
if (client->jetpack_thrusting)
|
|
{
|
|
if (!client->jetpack_infinite)
|
|
client->pers.inventory[fuel_index] -= 1;
|
|
client->jetpack_framenum = level.framenum + 10;
|
|
}
|
|
else
|
|
{
|
|
if (!client->jetpack_infinite)
|
|
client->pers.inventory[fuel_index]--;
|
|
client->jetpack_framenum = level.framenum + 10;
|
|
}
|
|
}
|
|
if (ucmd->upmove == 0)
|
|
{
|
|
// accelerate to 75% gravity in 2 seconds
|
|
float gravity;
|
|
float g_max = 0.75 * sv_gravity->value;
|
|
|
|
gravity = g_max * (level.framenum - client->jetpack_last_thrust)/20;
|
|
if (gravity > g_max) gravity = g_max;
|
|
client->ps.pmove.gravity = (short)gravity;
|
|
}
|
|
else
|
|
client->jetpack_last_thrust = level.framenum;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
client->jetpack = false;
|
|
ent->s.frame = FRAME_jump2; // reset from stand to avoid goofiness
|
|
}
|
|
}
|
|
#endif // #ifdef JETPACK_MOD
|
|
|
|
//Knightmare- Gen cam code
|
|
//Smoother Chasecam ?
|
|
// if (client->chasetoggle || client->chase_target)
|
|
if (client->chaseactive || client->chase_target)
|
|
{
|
|
if (ent->groundentity)
|
|
client->ps.pmove.gravity = 0;
|
|
}
|
|
|
|
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;
|
|
|
|
if (ent->vehicle)
|
|
pm.s.pm_flags |= PMF_ON_GROUND;
|
|
|
|
// 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]);
|
|
|
|
#ifdef JETPACK_MOD
|
|
if ( client->jetpack && jetpack_weenie->value )
|
|
{
|
|
if ( pm.groundentity ) // are we on ground
|
|
if ( Jet_AvoidGround(ent) ) // then lift us if possible
|
|
pm.groundentity = NULL; // now we are no longer on ground
|
|
}
|
|
#endif
|
|
|
|
// MUD - "correct" Pmove physics
|
|
if (pm.waterlevel && ent->in_mud)
|
|
{
|
|
vec3_t point;
|
|
vec3_t end;
|
|
|
|
vec3_t deltapos, deltavel;
|
|
float frac;
|
|
|
|
pm.watertype |= CONTENTS_MUD;
|
|
ent->in_mud = pm.waterlevel;
|
|
VectorSubtract(ent->s.origin,oldorigin,deltapos);
|
|
VectorSubtract(ent->velocity,oldvelocity,deltavel);
|
|
if (pm.waterlevel == 1)
|
|
{
|
|
frac = MUD1BASE + MUD1AMP*sin( (float)(level.framenum%10)/10.*2*M_PI);
|
|
ent->s.origin[0] = oldorigin[0] + frac*deltapos[0];
|
|
ent->s.origin[1] = oldorigin[1] + frac*deltapos[1];
|
|
ent->s.origin[2] = oldorigin[2] + 0.75*deltapos[2];
|
|
ent->velocity[0] = oldvelocity[0] + frac*deltavel[0];
|
|
ent->velocity[1] = oldvelocity[1] + frac*deltavel[1];
|
|
ent->velocity[2] = oldvelocity[2] + 0.75*deltavel[2];
|
|
}
|
|
else if (pm.waterlevel == 2)
|
|
{
|
|
trace_t tr;
|
|
float dist;
|
|
|
|
VectorCopy(oldorigin,point);
|
|
point[2] += ent->maxs[2];
|
|
end[0] = point[0]; end[1] = point[1]; end[2] = oldorigin[2] + ent->mins[2];
|
|
tr = gi.trace(point,NULL,NULL,end,ent,CONTENTS_WATER);
|
|
dist = point[2] - tr.endpos[2];
|
|
// frac = waterlevel 1 frac at dist=32 or more,
|
|
// = waterlevel 3 frac at dist=10 or less
|
|
if (dist <= 10)
|
|
frac = MUD3;
|
|
else
|
|
frac = MUD3 + (dist-10)/22.*(MUD1BASE-MUD3);
|
|
ent->s.origin[0] = oldorigin[0] + frac*deltapos[0];
|
|
ent->s.origin[1] = oldorigin[1] + frac*deltapos[1];
|
|
ent->s.origin[2] = oldorigin[2] + frac*deltapos[2];
|
|
ent->velocity[0] = oldvelocity[0] + frac*deltavel[0];
|
|
ent->velocity[1] = oldvelocity[1] + frac*deltavel[1];
|
|
ent->velocity[2] = oldvelocity[2] + frac*deltavel[2];
|
|
if (!ent->groundentity)
|
|
{
|
|
// Player can't possibly move up
|
|
ent->s.origin[2] = min(oldorigin[2], ent->s.origin[2]);
|
|
ent->velocity[2] = min(oldvelocity[2],ent->velocity[2]);
|
|
ent->velocity[2] = min(-10,ent->velocity[2]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->s.origin[0] = oldorigin[0] + MUD3*deltapos[0];
|
|
ent->s.origin[1] = oldorigin[1] + MUD3*deltapos[1];
|
|
ent->velocity[0] = oldvelocity[0] + MUD3*deltavel[0];
|
|
ent->velocity[1] = oldvelocity[1] + MUD3*deltavel[1];
|
|
if (ent->groundentity)
|
|
{
|
|
ent->s.origin[2] = oldorigin[2] + MUD3*deltapos[2];
|
|
ent->velocity[2] = oldvelocity[2] + MUD3*deltavel[2];
|
|
}
|
|
else
|
|
{
|
|
ent->s.origin[2] = min(oldorigin[2],ent->s.origin[2]);
|
|
ent->velocity[2] = min(oldvelocity[2], 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ent->in_mud = 0;
|
|
// end MUD
|
|
|
|
if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0) && !client->jetpack)
|
|
{ // Knightmare- allow disabling of STUPID grunting when jumping
|
|
if ((deathmatch->value || player_jump_sounds->value) && !ent->vehicle)
|
|
{
|
|
gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
|
|
PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
|
|
}
|
|
// Paril's vehicle targeting
|
|
if (ent->vehicle)
|
|
G_UseTargets (ent->vehicle, ent);
|
|
// Lazarus: temporarily match velocities with entity we just
|
|
// jumped from (add platform's velocity to jumping player)
|
|
VectorAdd (ent->groundentity->velocity, ent->velocity, ent->velocity);
|
|
}
|
|
|
|
//ROGUE sam raimi cam support
|
|
/* if (ent->flags & FL_SAM_RAIMI)
|
|
ent->viewheight = 8;
|
|
else
|
|
ent->viewheight = pm.viewheight;*/
|
|
//ROGUE
|
|
|
|
if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
|
|
ent->client->jumping = 1;
|
|
|
|
if (ent->deadflag != DEAD_FROZEN)
|
|
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;
|
|
|
|
// Lazarus - lie about ground when driving a vehicle.
|
|
// Pmove apparently doesn't think the ground
|
|
// can be "owned"
|
|
if (ent->vehicle && !ent->groundentity)
|
|
{
|
|
ent->groundentity = ent->vehicle;
|
|
ent->groundentity_linkcount = ent->vehicle->linkcount;
|
|
}
|
|
|
|
if (ent->deadflag)
|
|
{
|
|
if (ent->deadflag != DEAD_FROZEN)
|
|
{
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef JETPACK_MOD
|
|
if ( client->jetpack && !(ucmd->buttons & BUTTONS_ATTACK))
|
|
ent->s.frame = FRAME_stand20;
|
|
#endif
|
|
|
|
gi.linkentity (ent);
|
|
|
|
//PGM trigger_gravity support
|
|
ent->gravity = 1.0;
|
|
//PGM
|
|
if (ent->movetype != MOVETYPE_NOCLIP)
|
|
G_TouchTriggers (ent);
|
|
|
|
if ( (world->effects & FX_WORLDSPAWN_JUMPKICK) && (ent->client->jumping) && (ent->client->ucmd.upmove >= 20) && (ent->solid != SOLID_NOT))
|
|
kick_attack(ent);
|
|
|
|
// touch other objects
|
|
// Lazarus: but NOT if game is frozen
|
|
if (!level.freeze)
|
|
{
|
|
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)
|
|
{
|
|
if (client->resp.spectator)
|
|
{
|
|
client->latched_buttons = 0;
|
|
|
|
if (client->chase_target)
|
|
{
|
|
client->chase_target = NULL;
|
|
client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
}
|
|
else
|
|
GetChaseTarget(ent);
|
|
}
|
|
else if (!client->weapon_thunk)
|
|
{
|
|
client->weapon_thunk = true;
|
|
Think_Weapon (ent);
|
|
}
|
|
}
|
|
|
|
if (client->resp.spectator)
|
|
{
|
|
if (ucmd->upmove >= 10)
|
|
{
|
|
if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD))
|
|
{
|
|
client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
|
|
if (client->chase_target)
|
|
ChaseNext(ent);
|
|
else
|
|
GetChaseTarget(ent);
|
|
}
|
|
}
|
|
else
|
|
client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
|
|
}
|
|
|
|
// update chase cam if being followed
|
|
for (i = 1; i <= maxclients->value; i++)
|
|
{
|
|
other = g_edicts + i;
|
|
if (other->inuse && other->client->chase_target == ent)
|
|
UpdateChaseCam(other);
|
|
}
|
|
|
|
//Knightmare- Gen cam code
|
|
// update chase cam if being followed
|
|
/* for (i = 1; i <= maxclients->value; i++)
|
|
{
|
|
other = g_edicts + i;
|
|
if (other->inuse && other->client->chase_target == ent)
|
|
UpdateChaseCam(other);
|
|
}*/
|
|
if (client->push != NULL)
|
|
{
|
|
if (client->use &&
|
|
( (ucmd->forwardmove != 0) || (ucmd->sidemove != 0) ) )
|
|
ClientPushPushable(ent);
|
|
else
|
|
client->push->s.sound = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
|
|
// DWH
|
|
if (client->spycam)
|
|
client = client->camplayer->client;
|
|
|
|
if (deathmatch->value &&
|
|
client->pers.spectator != client->resp.spectator &&
|
|
(level.time - client->respawn_time) >= 5)
|
|
{
|
|
spectator_respawn(ent);
|
|
return;
|
|
}
|
|
|
|
// run weapon animations if it hasn't been done by a ucmd_t
|
|
if (!client->weapon_thunk && !client->resp.spectator)
|
|
Think_Weapon (ent);
|
|
else
|
|
client->weapon_thunk = false;
|
|
|
|
// Zaero
|
|
// 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;
|
|
}
|
|
// end Zaero
|
|
|
|
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 = BUTTON_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
|
|
// DWH: Don't add player trail for players in camera
|
|
if (!deathmatch->value && !client->spycam)
|
|
if (!visible (ent, PlayerTrail_LastSpot() ) )
|
|
PlayerTrail_Add (ent->s.old_origin);
|
|
|
|
client->latched_buttons = 0;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
RemoveAttackingPainDaemons
|
|
|
|
This is called to clean up the pain daemons that the disruptor attaches
|
|
to clients to damage them.
|
|
==============
|
|
*/
|
|
void RemoveAttackingPainDaemons (edict_t *self)
|
|
{
|
|
edict_t *tracker;
|
|
|
|
tracker = G_Find (NULL, FOFS(classname), "pain daemon");
|
|
while (tracker)
|
|
{
|
|
if (tracker->enemy == self)
|
|
G_FreeEdict(tracker);
|
|
tracker = G_Find (tracker, FOFS(classname), "pain daemon");
|
|
}
|
|
|
|
if (self->client)
|
|
self->client->tracker_pain_framenum = 0;
|
|
}
|