anachronox-sdk/tools/MapEditing/IONRadiant/entities/p_client.c

1126 lines
24 KiB
C
Raw Normal View History

2002-01-21 00:00:00 +00:00
#include "g_local.h"
#include "m_player.h"
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
void SP_misc_teleporter_dest (edict_t *ent);
/*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(void)
{
}
/*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 ( 1) //!coop->value)
{
G_FreeEdict (self);
return;
}
// SP_misc_teleporter_dest (self);
}
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
The deathmatch intermission point will be at one of these
Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
*/
void SP_info_player_intermission(void)
{
}
int SexedSoundIndex (edict_t *ent, char *base)
{
char buffer[MAX_QPATH];
Com_sprintf (buffer, sizeof(buffer), "%s/%s.wav", ent->client->pers.sounddir, base);
return gi.soundindex(buffer);
}
//=======================================================================
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
}
void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
{
if (attacker == self)
{
gi.bprintf (PRINT_MEDIUM,"%s killed self.\n", self->client->pers.netname);
self->client->resp.score--;
self->enemy = NULL;
return;
}
self->enemy = attacker;
if (attacker && attacker->client)
{
gi.bprintf (PRINT_MEDIUM,"%s was killed by %s\n", self->client->pers.netname, attacker->client->pers.netname);
attacker->client->resp.score++;
return;
}
gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
self->client->resp.score--;
}
void TossClientWeapon (edict_t *self)
{
gitem_t *item;
edict_t *drop;
if (!deathmatch->value)
return;
item = self->client->pers.weapon;
if (!item)
return;
if (! self->client->pers.inventory[self->client->ammo_index] )
return;
if (!strcmp (item->pickup_name, "Blaster"))
return;
drop = Drop_Item (self, item);
// let them have some ammo with it
drop->spawnflags = DROPPED_PLAYER_ITEM;
}
/*
==================
LookAtKiller
==================
*/
void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
{
vec3_t dir;
if (attacker && attacker != world && attacker != self)
{
VectorSubtract (attacker->s.origin, self->s.origin, dir);
}
else if (inflictor && inflictor != world && inflictor != self)
{
VectorSubtract (inflictor->s.origin, self->s.origin, dir);
}
else
{
self->client->killer_yaw = self->s.angles[YAW];
return;
}
self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
}
/*
==================
player_die
==================
*/
void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
char sound[MAX_QPATH];
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;
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
memset (self->client->pers.inventory, 0, sizeof(self->client->pers.inventory));
}
if (self->health < -40)
{ // gib
gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
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;
}
Com_sprintf(sound, sizeof(sound), "death%i", (rand()%4)+1);
gi.sound (self, CHAN_VOICE, SexedSoundIndex(self,sound), 1, ATTN_NORM, 0);
}
}
// FIXME once we have death frames
// self->deadflag = DEAD_DYING;
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;
}
void InitClientResp (gclient_t *client)
{
memset (&client->resp, 0, sizeof(client->resp));
client->resp.enterframe = level.framenum;
}
/*
==================
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;
}
}
void FetchClientEntData (edict_t *ent)
{
ent->health = ent->client->pers.health;
ent->max_health = ent->client->pers.max_health;
}
/*
=======================================================================
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 (void)
{
edict_t *spot = NULL;
while ((spot = G_Find (spot, FOFS(classname), "info_player_coop")) != NULL)
{
if (!game.spawnpoint[0] && !spot->targetname)
break;
if (!game.spawnpoint[0] || !spot->targetname)
continue;
if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
break;
}
return spot;
}
/*
===========
SelectSpawnPoint
Chooses a player start, deathmatch start, coop start, etc
============
*/
void SelectSpawnPoint (vec3_t origin, vec3_t angles)
{
edict_t *spot = NULL;
if (deathmatch->value)
spot = SelectDeathmatchSpawnPoint ();
#if 0
else if (coop->value)
spot = SelectCoopSpawnPoint ();
#endif
// 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, *head;
head = G_Spawn();
head->chain = head;
level.body_que = head;
for (i=0 ; i<7 ; i++)
{
ent = G_Spawn();
ent->chain = head->chain;
head->chain = ent;
}
}
void CopyToBodyQue (edict_t *ent)
{
edict_t *body;
// grab a body que and cycle to the next one
body = level.body_que;
level.body_que = body->chain;
// FIXME: send an effect on the removed body
gi.unlinkentity (ent);
gi.unlinkentity (body);
body->s = ent->s;
body->s.number = body - g_edicts;
// FIXME: make fall to ground?
gi.linkentity (body);
}
void respawn (edict_t *self)
{
if (deathmatch->value /*|| coop->value */)
{
// FIXME: make bodyque objects obey gravity
CopyToBodyQue (self);
PutClientInServer (self);
// add a teleportation effect
self->s.event = EV_PLAYER_TELEPORT;
// hold in place briefly
self->client->ps.pmove.teleport_time = 50;
return;
}
// restart the entire server
gi.AddCommandString ("menu_loadgame\n");
}
//==============================================================
/*
===========
PutClientInServer
Called when a player connects to a server or respawns in
a deathmatch.
============
*/
void PutClientInServer (edict_t *ent)
{
vec3_t mins = {-16, -16, -24};
vec3_t maxs = {16, 16, 32};
int index;
vec3_t spawn_origin, spawn_angles;
gclient_t *client;
int i;
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 (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 = ent->client->resp;
memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
InitClientPersistant (client);
ClientUserinfoChanged (ent, userinfo);
}
else
memset (&resp, 0, sizeof(resp));
// clear everything but the persistant data
saved = client->pers;
memset (client, 0, sizeof(*client));
client->pers = saved;
client->resp = resp;
// copy some data from the client to the entity
FetchClientEntData (ent);
// clear entity values
ent->groundentity = NULL;
ent->client = &game.clients[index];
ent->takedamage = DAMAGE_AIM;
ent->movetype = MOVETYPE_WALK;
ent->viewheight = 22;
ent->inuse = true;
ent->classname = "player";
ent->mass = 200;
ent->solid = SOLID_BBOX;
ent->deadflag = DEAD_NO;
ent->air_finished = level.time + 12;
ent->clipmask = MASK_PLAYERSOLID;
ent->model = "players/male/tris.md2";
ent->pain = player_pain;
ent->die = player_die;
ent->waterlevel = 0;
ent->watertype = 0;
ent->flags &= ~FL_NO_KNOCKBACK;
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;
client->ps.fov = 90;
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
// clear entity state values
ent->s.effects = 0;
ent->s.skinnum = ent - g_edicts - 1;
ent->s.modelindex = 255; // will use the skin specified model
ent->s.modelindex2 = 255; // custom gun model
ent->s.frame = 0;
VectorCopy (spawn_origin, ent->s.origin);
ent->s.origin[2] += 1; // make sure off ground
// set the delta angle
for (i=0 ; i<3 ; i++)
client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
ent->s.angles[PITCH] = 0;
ent->s.angles[YAW] = spawn_angles[YAW];
ent->s.angles[ROLL] = 0;
VectorCopy (ent->s.angles, client->ps.viewangles);
VectorCopy (ent->s.angles, client->v_angle);
if (!KillBox (ent))
{ // could't spawn in?
}
gi.linkentity (ent);
// force the current weapon up
client->newweapon = client->pers.weapon;
ChangeWeapon (ent);
}
/*
=====================
ClientBeginDeathmatch
A client has just connected to the server in
deathmatch mode, so clear everything out before starting them.
=====================
*/
void ClientBeginDeathmatch (edict_t *ent)
{
G_InitEdict (ent);
InitClientResp (ent->client);
// locate ent at a spawn point
PutClientInServer (ent);
// send effect
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
gi.WriteByte (MZ_LOGIN);
gi.multicast (ent->s.origin, MULTICAST_PVS);
gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
// make sure all view stuff is valid
ClientEndServerFrame (ent);
}
/*
===========
ClientBegin
called when a client has finished connecting, and is ready
to be placed into the game. This will happen every level load.
============
*/
void ClientBegin (edict_t *ent, qboolean loadgame)
{
int i;
if (deathmatch->value)
{
ClientBeginDeathmatch (ent);
return;
}
if (loadgame && ent->classname && !strcmp(ent->classname, "player") )
{
// a loadgame will just use the entity exactly as it is
// if more clients connect than were saved, they will be
// spawned as normal
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
G_InitEdict (ent);
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;
// set name
s = Info_ValueForKey (userinfo, "name");
strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1);
// set skin
s = Info_ValueForKey (userinfo, "skin");
// decide sound directory
if (s[0] == 'f' || s[0] == 'F')
strcpy (ent->client->pers.sounddir, "player/female");
else
strcpy (ent->client->pers.sounddir, "player/male");
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) );
// 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 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.
============
*/
qboolean ClientConnect (edict_t *ent, char *userinfo, qboolean loadgame)
{
if (!loadgame)
{
// clear the respawning variables
InitClientResp (ent->client);
InitClientPersistant (ent->client);
}
ClientUserinfoChanged (ent, userinfo);
if (game.maxclients > 1)
gi.dprintf ("%s connected\n", ent->client->pers.netname);
level.players++;
return true;
}
/*
===========
ClientDisconnect
called when a player drops from the server
============
*/
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);
ent->s.modelindex = 0;
ent->solid = SOLID_NOT;
gi.linkentity (ent);
ent->inuse = false;
level.players--;
playernum = ent-g_edicts-1;
gi.configstring (CS_PLAYERSKINS+playernum, "");
}
//==============================================================
void respawn (edict_t *self);
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 = true;
return;
}
pm_passent = ent;
// set up for pmove
memset (&pm, 0, sizeof(pm));
if (ent->movetype == MOVETYPE_NOCLIP)
client->ps.pmove.pm_type = PM_SPECTATOR;
else if (ent->s.modelindex != 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 = true;
// gi.dprintf ("pmove changed!\n");
}
pm.cmd = *ucmd;
pm.trace = PM_trace; // adds default parms
pm.pointcontents = gi.pointcontents;
// perform a pmove
gi.Pmove (&pm);
// save results of pmove
client->ps.pmove = pm.s;
client->old_pmove = pm.s;
for (i=0 ; i<3 ; i++)
{
ent->s.origin[i] = pm.s.origin[i]*0.125;
ent->velocity[i] = pm.s.velocity[i]*0.125;
}
VectorCopy (pm.mins, ent->mins);
VectorCopy (pm.maxs, ent->maxs);
client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
{
gi.sound(ent, CHAN_VOICE, SexedSoundIndex(ent, "jump1"), 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->weapon_thunk)
{
client->weapon_thunk = true;
Think_Weapon (ent);
}
}
}
/*
==============
ClientBeginServerFrame
This will be called once for each server frame, before running
any other entities in the world.
==============
*/
void ClientBeginServerFrame (edict_t *ent)
{
gclient_t *client;
if (level.intermissiontime)
return;
client = ent->client;
// run weapon animations if it hasn't been done by a ucmd_t
if (!client->weapon_thunk)
Think_Weapon (ent);
else
client->weapon_thunk = false;
if (ent->deadflag)
{
// wait for any button just going down
if ( level.time > client->respawn_time)
{
if (client->latched_buttons ||
(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;
}