summaryrefslogtreecommitdiff
path: root/src/baseq2/p_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/baseq2/p_client.c')
-rw-r--r--src/baseq2/p_client.c1830
1 files changed, 1830 insertions, 0 deletions
diff --git a/src/baseq2/p_client.c b/src/baseq2/p_client.c
new file mode 100644
index 0000000..6b1033f
--- /dev/null
+++ b/src/baseq2/p_client.c
@@ -0,0 +1,1830 @@
+/*
+Copyright (C) 1997-2001 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+#include "g_local.h"
+#include "m_player.h"
+
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+
+void SP_misc_teleporter_dest (edict_t *ent);
+
+//
+// Gross, ugly, disgustuing hack section
+//
+
+// this function is an ugly as hell hack to fix some map flaws
+//
+// the coop spawn spots on some maps are SNAFU. There are coop spots
+// with the wrong targetname as well as spots with no name at all
+//
+// we use carnal knowledge of the maps to fix the coop spot targetnames to match
+// that of the nearest named single player spot
+
+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
+
+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->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->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->s.origin[0] = 188 + 128;
+ spot->s.origin[1] = -164;
+ spot->s.origin[2] = 80;
+ spot->targetname = "jail3";
+ spot->s.angles[1] = 90;
+
+ return;
+ }
+}
+
+
+/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+The normal starting point for a level.
+*/
+void SP_info_player_start(edict_t *self)
+{
+ if (!coop->value)
+ return;
+ if(Q_stricmp(level.mapname, "security") == 0)
+ {
+ // invoke one of our gross, ugly, disgusting hacks
+ self->think = SP_CreateCoopSpots;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for deathmatch games
+*/
+void SP_info_player_deathmatch(edict_t *self)
+{
+ if (!deathmatch->value)
+ {
+ G_FreeEdict (self);
+ return;
+ }
+ SP_misc_teleporter_dest (self);
+}
+
+/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for coop games
+*/
+
+void SP_info_player_coop(edict_t *self)
+{
+ if (!coop->value)
+ {
+ G_FreeEdict (self);
+ return;
+ }
+
+ if((Q_stricmp(level.mapname, "jail2") == 0) ||
+ (Q_stricmp(level.mapname, "jail4") == 0) ||
+ (Q_stricmp(level.mapname, "mine1") == 0) ||
+ (Q_stricmp(level.mapname, "mine2") == 0) ||
+ (Q_stricmp(level.mapname, "mine3") == 0) ||
+ (Q_stricmp(level.mapname, "mine4") == 0) ||
+ (Q_stricmp(level.mapname, "lab") == 0) ||
+ (Q_stricmp(level.mapname, "boss1") == 0) ||
+ (Q_stricmp(level.mapname, "fact3") == 0) ||
+ (Q_stricmp(level.mapname, "biggun") == 0) ||
+ (Q_stricmp(level.mapname, "space") == 0) ||
+ (Q_stricmp(level.mapname, "command") == 0) ||
+ (Q_stricmp(level.mapname, "power2") == 0) ||
+ (Q_stricmp(level.mapname, "strike") == 0))
+ {
+ // invoke one of our gross, ugly, disgusting hacks
+ self->think = SP_FixCoopSpots;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+
+/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The deathmatch intermission point will be at one of these
+Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
+*/
+void SP_info_player_intermission(void)
+{
+}
+
+
+//=======================================================================
+
+
+void player_pain (edict_t *self, edict_t *other, float kick, int damage)
+{
+ // player pain is handled at the end of the frame in P_DamageFeedback
+}
+
+
+qboolean IsFemale (edict_t *ent)
+{
+ char *info;
+
+ if (!ent->client)
+ return qfalse;
+
+ info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
+ if (info[0] == 'f' || info[0] == 'F')
+ return qtrue;
+ return qfalse;
+}
+
+qboolean IsNeutral (edict_t *ent)
+{
+ char *info;
+
+ if (!ent->client)
+ return qfalse;
+
+ info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
+ if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
+ return qtrue;
+ return qfalse;
+}
+
+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)
+ {
+ ff = meansOfDeath & MOD_FRIENDLY_FIRE;
+ mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+ message = NULL;
+ message2 = "";
+
+ switch (mod)
+ {
+ case MOD_SUICIDE:
+ message = "suicides";
+ break;
+ case MOD_FALLING:
+ message = "cratered";
+ break;
+ case MOD_CRUSH:
+ message = "was squished";
+ break;
+ case MOD_WATER:
+ message = "sank like a rock";
+ break;
+ case MOD_SLIME:
+ message = "melted";
+ break;
+ case MOD_LAVA:
+ message = "does a back flip into the lava";
+ break;
+ case MOD_EXPLOSIVE:
+ case MOD_BARREL:
+ message = "blew up";
+ break;
+ case MOD_EXIT:
+ message = "found a way out";
+ break;
+ case MOD_TARGET_LASER:
+ message = "saw the light";
+ break;
+ case MOD_TARGET_BLASTER:
+ message = "got blasted";
+ break;
+ case MOD_BOMB:
+ case MOD_SPLASH:
+ case MOD_TRIGGER_HURT:
+ message = "was in the wrong place";
+ break;
+ }
+ if (attacker == self)
+ {
+ switch (mod)
+ {
+ case MOD_HELD_GRENADE:
+ message = "tried to put the pin back in";
+ break;
+ case MOD_HG_SPLASH:
+ case MOD_G_SPLASH:
+ if (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;
+ 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;
+ }
+ if (message)
+ {
+ gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
+ if (deathmatch->value)
+ {
+ if (ff)
+ attacker->client->resp.score--;
+ else
+ attacker->client->resp.score++;
+ }
+ return;
+ }
+ }
+ }
+
+ gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
+ if (deathmatch->value)
+ self->client->resp.score--;
+}
+
+
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+void TossClientWeapon (edict_t *self)
+{
+ gitem_t *item;
+ edict_t *drop;
+ qboolean quad;
+ float spread;
+
+ if (!deathmatch->value)
+ return;
+
+ item = self->client->pers.weapon;
+ if (! self->client->pers.inventory[self->client->ammo_index] )
+ item = NULL;
+ if (item && (strcmp (item->pickup_name, "Blaster") == 0))
+ item = NULL;
+
+ if (!((int)(dmflags->value) & DF_QUAD_DROP))
+ quad = qfalse;
+ else
+ quad = (self->client->quad_framenum > (level.framenum + 10));
+
+ if (item && quad)
+ spread = 22.5;
+ else
+ spread = 0.0;
+
+ if (item)
+ {
+ self->client->v_angle[YAW] -= spread;
+ drop = Drop_Item (self, item);
+ self->client->v_angle[YAW] += spread;
+ drop->spawnflags = DROPPED_PLAYER_ITEM;
+ }
+
+ if (quad)
+ {
+ self->client->v_angle[YAW] += spread;
+ drop = Drop_Item (self, FindItemByClassname ("item_quad"));
+ self->client->v_angle[YAW] -= spread;
+ drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+ drop->touch = Touch_Item;
+ drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
+ drop->think = G_FreeEdict;
+ }
+}
+
+
+/*
+==================
+LookAtKiller
+==================
+*/
+void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
+{
+ vec3_t dir;
+
+ if (attacker && attacker != world && attacker != self)
+ {
+ VectorSubtract (attacker->s.origin, self->s.origin, dir);
+ }
+ else if (inflictor && inflictor != world && inflictor != self)
+ {
+ VectorSubtract (inflictor->s.origin, self->s.origin, dir);
+ }
+ else
+ {
+ self->client->killer_yaw = self->s.angles[YAW];
+ return;
+ }
+
+ if (dir[0])
+ self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
+ else {
+ self->client->killer_yaw = 0;
+ if (dir[1] > 0)
+ self->client->killer_yaw = 90;
+ else if (dir[1] < 0)
+ self->client->killer_yaw = -90;
+ }
+ if (self->client->killer_yaw < 0)
+ self->client->killer_yaw += 360;
+
+
+}
+
+/*
+==================
+player_die
+==================
+*/
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+ int n;
+
+ 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;
+ }
+ }
+
+ // 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_ARMOR;
+
+ if (self->health < -40)
+ { // gib
+ gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+ for (n= 0; n < 4; n++)
+ ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ ThrowClientHead (self, damage);
+
+ self->takedamage = DAMAGE_NO;
+ }
+ else
+ { // normal death
+ if (!self->deadflag)
+ {
+ static int i;
+
+ i = (i+1)%3;
+ // start a death animation
+ self->client->anim_priority = ANIM_DEATH;
+ if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ self->s.frame = FRAME_crdeath1-1;
+ self->client->anim_end = FRAME_crdeath5;
+ }
+ else switch (i)
+ {
+ case 0:
+ self->s.frame = FRAME_death101-1;
+ self->client->anim_end = FRAME_death106;
+ break;
+ case 1:
+ self->s.frame = FRAME_death201-1;
+ self->client->anim_end = FRAME_death206;
+ break;
+ case 2:
+ self->s.frame = FRAME_death301-1;
+ self->client->anim_end = FRAME_death308;
+ break;
+ }
+ gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
+ }
+ }
+
+ self->deadflag = DEAD_DEAD;
+
+ gi.linkentity (self);
+}
+
+//=======================================================================
+
+/*
+==============
+InitClientPersistant
+
+This is only called when the game first initializes in single player,
+but is called after each death and level change in deathmatch
+==============
+*/
+void InitClientPersistant (gclient_t *client)
+{
+ gitem_t *item;
+
+ memset (&client->pers, 0, sizeof(client->pers));
+
+ 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;
+ client->pers.max_health = 100;
+
+ client->pers.max_bullets = 200;
+ client->pers.max_shells = 100;
+ client->pers.max_rockets = 50;
+ client->pers.max_grenades = 50;
+ client->pers.max_cells = 200;
+ client->pers.max_slugs = 50;
+
+ client->pers.connected = qtrue;
+}
+
+
+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_ARMOR));
+ if (coop->value)
+ game.clients[i].pers.score = ent->client->resp.score;
+ }
+}
+
+void FetchClientEntData (edict_t *ent)
+{
+ ent->health = ent->client->pers.health;
+ ent->max_health = ent->client->pers.max_health;
+ 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 = 99999;
+ spot1 = spot2 = NULL;
+
+ while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+ {
+ count++;
+ range = PlayersRangeFromSpot(spot);
+ if (range < range1)
+ {
+ range1 = range;
+ spot1 = spot;
+ }
+ else if (range < range2)
+ {
+ range2 = range;
+ spot2 = spot;
+ }
+ }
+
+ if (!count)
+ return NULL;
+
+ if (count <= 2)
+ {
+ spot1 = spot2 = NULL;
+ }
+ else
+ count -= 2;
+
+ selection = rand() % count;
+
+ spot = NULL;
+ do
+ {
+ spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
+ if (spot == spot1 || spot == spot2)
+ selection++;
+ } while(selection--);
+
+ return spot;
+}
+
+/*
+================
+SelectFarthestDeathmatchSpawnPoint
+
+================
+*/
+edict_t *SelectFarthestDeathmatchSpawnPoint (void)
+{
+ edict_t *bestspot;
+ float bestdistance, bestplayerdistance;
+ edict_t *spot;
+
+
+ spot = NULL;
+ bestspot = NULL;
+ bestdistance = 0;
+ while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+ {
+ bestplayerdistance = PlayersRangeFromSpot (spot);
+
+ if (bestplayerdistance > bestdistance)
+ {
+ bestspot = spot;
+ bestdistance = bestplayerdistance;
+ }
+ }
+
+ if (bestspot)
+ {
+ return bestspot;
+ }
+
+ // if there is a player just spawned on each and every start spot
+ // we have no choice to turn one into a telefrag meltdown
+ spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+
+ return spot;
+}
+
+edict_t *SelectDeathmatchSpawnPoint (void)
+{
+ if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
+ return SelectFarthestDeathmatchSpawnPoint ();
+ else
+ return SelectRandomDeathmatchSpawnPoint ();
+}
+
+
+edict_t *SelectCoopSpawnPoint (edict_t *ent)
+{
+ int index;
+ edict_t *spot = NULL;
+ char *target;
+
+ index = ent->client - game.clients;
+
+ // player 0 starts in normal player spawn point
+ if (!index)
+ return NULL;
+
+ spot = NULL;
+
+ // assume there are four coop spots at each spawnpoint
+ while (1)
+ {
+ spot = G_Find (spot, FOFS(classname), "info_player_coop");
+ if (!spot)
+ return NULL; // we didn't have enough...
+
+ target = spot->targetname;
+ if (!target)
+ target = "";
+ if ( Q_stricmp(game.spawnpoint, target) == 0 )
+ { // this is a coop spawn point for one of the clients here
+ index--;
+ if (!index)
+ return spot; // this is it
+ }
+ }
+
+
+ return spot;
+}
+
+
+/*
+===========
+SelectSpawnPoint
+
+Chooses a player start, deathmatch start, coop start, etc
+============
+*/
+void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
+{
+ edict_t *spot = NULL;
+
+ if (deathmatch->value)
+ spot = SelectDeathmatchSpawnPoint ();
+ else if (coop->value)
+ spot = SelectCoopSpawnPoint (ent);
+
+ // find a single player start spot
+ if (!spot)
+ {
+ while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL)
+ {
+ if (!game.spawnpoint[0] && !spot->targetname)
+ break;
+
+ if (!game.spawnpoint[0] || !spot->targetname)
+ continue;
+
+ if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
+ break;
+ }
+
+ if (!spot)
+ {
+ if (!game.spawnpoint[0])
+ { // there wasn't a spawnpoint without a target, so use any
+ spot = G_Find (spot, FOFS(classname), "info_player_start");
+ }
+ if (!spot)
+ gi.error ("Couldn't find spawn point %s\n", game.spawnpoint);
+ }
+ }
+
+ VectorCopy (spot->s.origin, origin);
+ origin[2] += 9;
+ VectorCopy (spot->s.angles, angles);
+}
+
+//======================================================================
+
+
+void InitBodyQue (void)
+{
+ int i;
+ edict_t *ent;
+
+ level.body_que = 0;
+ for (i=0; i<BODY_QUEUE_SIZE ; i++)
+ {
+ ent = G_Spawn();
+ ent->classname = "bodyque";
+ }
+}
+
+void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+ int n;
+
+ if (self->health < -40)
+ {
+ gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+ for (n= 0; n < 4; n++)
+ ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ self->s.origin[2] -= 48;
+ ThrowClientHead (self, damage);
+ self->takedamage = DAMAGE_NO;
+ }
+}
+
+void CopyToBodyQue (edict_t *ent)
+{
+ edict_t *body;
+
+ gi.unlinkentity (ent);
+
+ // grab a body que and cycle to the next one
+ body = &g_edicts[game.maxclients + level.body_que + 1];
+ level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
+
+ // send an effect on the removed body
+ if (body->s.modelindex)
+ {
+ gi.WriteByte (svc_temp_entity);
+ gi.WriteByte (TE_BLOOD);
+ gi.WritePosition (body->s.origin);
+ gi.WriteDir (vec3_origin);
+ gi.multicast (body->s.origin, MULTICAST_PVS);
+ }
+
+ gi.unlinkentity (body);
+ body->s = ent->s;
+ body->s.number = body - g_edicts;
+ body->s.event = EV_OTHER_TELEPORT;
+
+ 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);
+ VectorCopy (ent->velocity, body->velocity);
+ VectorCopy (ent->avelocity, body->avelocity);
+ body->solid = ent->solid;
+ body->clipmask = ent->clipmask;
+ body->owner = ent->owner;
+ body->movetype = ent->movetype;
+ body->groundentity = ent->groundentity;
+
+ body->die = body_die;
+ body->takedamage = DAMAGE_YES;
+
+ gi.linkentity (body);
+}
+
+void respawn (edict_t *self)
+{
+ if (deathmatch->value || coop->value)
+ {
+ // spectator's 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;
+
+ 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 = qfalse;
+ gi.WriteByte (svc_stufftext);
+ gi.WriteString ("spectator 0\n");
+ gi.unicast(ent, qtrue);
+ 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 = qfalse;
+ // reset his spectator var
+ gi.WriteByte (svc_stufftext);
+ gi.WriteString ("spectator 0\n");
+ gi.unicast(ent, qtrue);
+ 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 = qtrue;
+ gi.WriteByte (svc_stufftext);
+ gi.WriteString ("spectator 1\n");
+ gi.unicast(ent, qtrue);
+ return;
+ }
+ }
+
+ // clear client on respawn
+ ent->client->resp.score = ent->client->pers.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)
+{
+ vec3_t mins = {-16, -16, -24};
+ vec3_t maxs = {16, 16, 32};
+ int index;
+ vec3_t spawn_origin, spawn_angles;
+ gclient_t *client;
+ int i;
+ client_persistant_t saved;
+ client_respawn_t resp;
+
+ // find a spawn point
+ // do it before setting health back up, so farthest
+ // ranging doesn't count this client
+ SelectSpawnPoint (ent, spawn_origin, spawn_angles);
+
+ index = ent-g_edicts-1;
+ client = ent->client;
+
+ // deathmatch wipes most client data every spawn
+ if (deathmatch->value)
+ {
+ char userinfo[MAX_INFO_STRING];
+
+ resp = client->resp;
+ memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+ InitClientPersistant (client);
+ ClientUserinfoChanged (ent, userinfo);
+ }
+ else if (coop->value)
+ {
+// int n;
+ char userinfo[MAX_INFO_STRING];
+
+ resp = client->resp;
+ memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+ // this is kind of ugly, but it's how we want to handle keys in coop
+// for (n = 0; n < 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));
+ }
+
+ // clear everything but the persistant data
+ saved = client->pers;
+ memset (client, 0, sizeof(*client));
+ client->pers = saved;
+ if (client->pers.health <= 0)
+ InitClientPersistant(client);
+ client->resp = resp;
+
+ // copy some data from the client to the entity
+ FetchClientEntData (ent);
+
+ // clear entity values
+ ent->groundentity = NULL;
+ ent->client = &game.clients[index];
+ ent->takedamage = DAMAGE_AIM;
+ ent->movetype = MOVETYPE_WALK;
+ ent->viewheight = 22;
+ ent->inuse = qtrue;
+ ent->classname = "player";
+ ent->mass = 200;
+ ent->solid = SOLID_BBOX;
+ ent->deadflag = DEAD_NO;
+ ent->air_finished = level.time + 12;
+ ent->clipmask = MASK_PLAYERSOLID;
+ ent->model = "players/male/tris.md2";
+ ent->pain = player_pain;
+ ent->die = player_die;
+ ent->waterlevel = 0;
+ ent->watertype = 0;
+ ent->flags &= ~FL_NO_KNOCKBACK;
+ ent->svflags &= ~SVF_DEADMONSTER;
+
+ VectorCopy (mins, ent->mins);
+ VectorCopy (maxs, ent->maxs);
+ VectorClear (ent->velocity);
+
+ // clear playerstate values
+ memset (&ent->client->ps, 0, sizeof(client->ps));
+
+ client->ps.pmove.origin[0] = spawn_origin[0]*8;
+ client->ps.pmove.origin[1] = spawn_origin[1]*8;
+ client->ps.pmove.origin[2] = spawn_origin[2]*8;
+
+ if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+ {
+ client->ps.fov = 90;
+ }
+ else
+ {
+ client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
+ if (client->ps.fov < 1)
+ client->ps.fov = 90;
+ else if (client->ps.fov > 160)
+ client->ps.fov = 160;
+ }
+
+ client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
+
+ // clear entity state values
+ ent->s.effects = 0;
+ ent->s.modelindex = 255; // will use the skin specified model
+ ent->s.modelindex2 = 255; // custom gun model
+ // sknum 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;
+ 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 = qtrue;
+
+ 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 = qfalse;
+
+ if (!KillBox (ent))
+ { // could't spawn in?
+ }
+
+ gi.linkentity (ent);
+
+ // force the current weapon up
+ client->newweapon = client->pers.weapon;
+ ChangeWeapon (ent);
+}
+
+/*
+=====================
+ClientBeginDeathmatch
+
+A client has just connected to the server in
+deathmatch mode, so clear everything out before starting them.
+=====================
+*/
+void ClientBeginDeathmatch (edict_t *ent)
+{
+ G_InitEdict (ent);
+
+ InitClientResp (ent->client);
+
+ // locate ent at a spawn point
+ PutClientInServer (ent);
+
+ if (level.intermissiontime)
+ {
+ MoveClientToIntermission (ent);
+ }
+ else
+ {
+ // send effect
+ gi.WriteByte (svc_muzzleflash);
+ gi.WriteShort (ent-g_edicts);
+ gi.WriteByte (MZ_LOGIN);
+ gi.multicast (ent->s.origin, MULTICAST_PVS);
+ }
+
+ gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+
+ // make sure all view stuff is valid
+ ClientEndServerFrame (ent);
+}
+
+
+/*
+===========
+ClientBegin
+
+called when a client has finished connecting, and is ready
+to be placed into the game. This will happen every level load.
+============
+*/
+void ClientBegin (edict_t *ent)
+{
+ int i;
+
+ ent->client = game.clients + (ent - g_edicts - 1);
+
+ if (deathmatch->value)
+ {
+ ClientBeginDeathmatch (ent);
+ return;
+ }
+
+ // if there is already a body waiting for us (a loadgame), just
+ // take it, otherwise spawn one from scratch
+ if (ent->inuse == qtrue)
+ {
+ // the client has cleared the client side viewangles upon
+ // connecting to the server, which is different than the
+ // state when the game is saved, so we need to compensate
+ // with deltaangles
+ for (i=0 ; i<3 ; i++)
+ ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
+ }
+ else
+ {
+ // a spawn point will completely reinitialize the entity
+ // except for the persistant data that was initialized at
+ // ClientConnect() time
+ G_InitEdict (ent);
+ ent->classname = "player";
+ InitClientResp (ent->client);
+ PutClientInServer (ent);
+ }
+
+ if (level.intermissiontime)
+ {
+ MoveClientToIntermission (ent);
+ }
+ else
+ {
+ // send effect if in a multiplayer game
+ if (game.maxclients > 1)
+ {
+ gi.WriteByte (svc_muzzleflash);
+ gi.WriteShort (ent-g_edicts);
+ gi.WriteByte (MZ_LOGIN);
+ gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+ gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+ }
+ }
+
+ // make sure all view stuff is valid
+ ClientEndServerFrame (ent);
+}
+
+/*
+===========
+ClientUserInfoChanged
+
+called whenever the player updates a userinfo variable.
+
+The game can override any of the settings in place
+(forcing skins or names, etc) before copying it off.
+============
+*/
+void ClientUserinfoChanged (edict_t *ent, char *userinfo)
+{
+ char *s;
+ int playernum;
+
+ // check for malformed or illegal info strings
+ if (!Info_Validate(userinfo))
+ {
+ strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt");
+ }
+
+ // 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 && *s && strcmp(s, "0"))
+ ent->client->pers.spectator = qtrue;
+ else
+ ent->client->pers.spectator = qfalse;
+
+ // 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;
+ }
+
+ // 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);
+}
+
+
+/*
+===========
+ClientConnect
+
+Called when a player begins connecting to the server.
+The game can refuse entrance to a client by returning qfalse.
+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 qfalse;
+ }
+
+ // check for a spectator
+ value = Info_ValueForKey (userinfo, "spectator");
+ 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 qfalse;
+ }
+
+ // 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 qfalse;
+ }
+ } 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 qfalse;
+ }
+ }
+
+
+ // 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 == qfalse)
+ {
+ // clear the respawning variables
+ InitClientResp (ent->client);
+ if (!game.autosaved || !ent->client->pers.weapon)
+ InitClientPersistant (ent->client);
+ }
+
+ ClientUserinfoChanged (ent, userinfo);
+
+ if (game.maxclients > 1)
+ gi.dprintf ("%s connected\n", ent->client->pers.netname);
+
+ ent->svflags = 0; // make sure we start with known default
+ ent->client->pers.connected = qtrue;
+ return qtrue;
+}
+
+/*
+===========
+ClientDisconnect
+
+Called when a player drops from the server.
+Will not be called between levels.
+============
+*/
+void ClientDisconnect (edict_t *ent)
+{
+ //int playernum;
+
+ if (!ent->client)
+ return;
+
+ gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
+
+ // send effect
+ gi.WriteByte (svc_muzzleflash);
+ gi.WriteShort (ent-g_edicts);
+ gi.WriteByte (MZ_LOGOUT);
+ gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+ gi.unlinkentity (ent);
+ ent->s.modelindex = 0;
+ ent->s.sound = 0;
+ ent->s.event = 0;
+ ent->s.effects = 0;
+ ent->solid = SOLID_NOT;
+ ent->inuse = qfalse;
+ ent->classname = "disconnected";
+ ent->client->pers.connected = qfalse;
+
+ // FIXME: don't break skins on corpses, etc
+ //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);
+}
+
+/*
+==============
+ClientThink
+
+This will be called once for each client frame, which will
+usually be a couple times for each server frame.
+==============
+*/
+void ClientThink (edict_t *ent, usercmd_t *ucmd)
+{
+ gclient_t *client;
+ edict_t *other;
+ int i, j;
+ pmove_t pm;
+
+ level.current_entity = ent;
+ client = ent->client;
+
+ if (level.intermissiontime)
+ {
+ client->ps.pmove.pm_type = PM_FREEZE;
+ // can exit intermission after five seconds
+ if (level.time > level.intermissiontime + 5.0
+ && (ucmd->buttons & BUTTON_ANY) )
+ level.exitintermission = qtrue;
+ return;
+ }
+
+ pm_passent = 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]);
+
+ } 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 != 255)
+ client->ps.pmove.pm_type = PM_GIB;
+ else if (ent->deadflag)
+ client->ps.pmove.pm_type = PM_DEAD;
+ else
+ client->ps.pmove.pm_type = PM_NORMAL;
+
+ client->ps.pmove.gravity = sv_gravity->value;
+ pm.s = client->ps.pmove;
+
+ for (i=0 ; i<3 ; i++)
+ {
+ pm.s.origin[i] = ent->s.origin[i]*8;
+ pm.s.velocity[i] = ent->velocity[i]*8;
+ }
+
+ if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
+ {
+ pm.snapinitial = qtrue;
+ // gi.dprintf ("pmove changed!\n");
+ }
+
+ pm.cmd = *ucmd;
+
+ pm.trace = PM_trace; // adds default parms
+ pm.pointcontents = gi.pointcontents;
+
+ // perform a pmove
+ gi.Pmove (&pm);
+
+ // save results of pmove
+ client->ps.pmove = pm.s;
+ client->old_pmove = pm.s;
+
+ for (i=0 ; i<3 ; i++)
+ {
+ ent->s.origin[i] = pm.s.origin[i]*0.125;
+ ent->velocity[i] = pm.s.velocity[i]*0.125;
+ }
+
+ VectorCopy (pm.mins, ent->mins);
+ VectorCopy (pm.maxs, ent->maxs);
+
+ client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+ client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+ client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+ if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
+ PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
+ }
+
+ ent->viewheight = pm.viewheight;
+ ent->waterlevel = pm.waterlevel;
+ ent->watertype = pm.watertype;
+ ent->groundentity = pm.groundentity;
+ if (pm.groundentity)
+ ent->groundentity_linkcount = pm.groundentity->linkcount;
+
+ if (ent->deadflag)
+ {
+ client->ps.viewangles[ROLL] = 40;
+ client->ps.viewangles[PITCH] = -15;
+ client->ps.viewangles[YAW] = client->killer_yaw;
+ }
+ else
+ {
+ VectorCopy (pm.viewangles, client->v_angle);
+ VectorCopy (pm.viewangles, client->ps.viewangles);
+ }
+
+ gi.linkentity (ent);
+
+ if (ent->movetype != MOVETYPE_NOCLIP)
+ G_TouchTriggers (ent);
+
+ // touch other objects
+ for (i=0 ; i<pm.numtouch ; i++)
+ {
+ other = pm.touchents[i];
+ for (j=0 ; j<i ; j++)
+ if (pm.touchents[j] == other)
+ break;
+ if (j != i)
+ continue; // duplicated
+ if (!other->touch)
+ continue;
+ other->touch (other, ent, NULL, NULL);
+ }
+
+ }
+
+ client->oldbuttons = client->buttons;
+ client->buttons = ucmd->buttons;
+ client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+ // save light level the player is standing on for
+ // monster sighting AI
+ ent->light_level = ucmd->lightlevel;
+
+ // 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 = qtrue;
+ 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);
+ }
+}
+
+
+/*
+==============
+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;
+
+ 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 = qfalse;
+
+ 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
+ if (!deathmatch->value)
+ if (!visible (ent, PlayerTrail_LastSpot() ) )
+ PlayerTrail_Add (ent->s.old_origin);
+
+ client->latched_buttons = 0;
+}