This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/server/sv_spawn.c

820 lines
19 KiB
C

#include "engine.h"
#include "server.h"
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->progs.sv->health > 0)
return SV_Trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
return SV_Trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
}
/*
=============
ED_NewString
=============
*/
char *ED_NewString (const char *string)
{
char *newb, *new_p;
int i, l;
l = strlen(string) + 1;
newb = (char *)Z_Malloc(l);
new_p = newb;
for (i = 0; i < l; i++)
{
if (string[i] == '\\' && i < l-1)
{
i++;
if (string[i] == 'n')
*new_p++ = '\n';
else *new_p++ = '\\';
}
else *new_p++ = string[i];
}
return newb;
}
/*
===========
PutClientInServer
Called when a player connects to a server or respawns in
a deathmatch.
============
*/
void SV_PutClientInServer (edict_t *ent)
{
vec3_t mins = {-16, -16, -24};
vec3_t maxs = {16, 16, 32};
int index;
vec3_t spawn_origin = {-128, -32, -72 }, spawn_angles;
gclient_t *client;
int i;
index = PRVM_NUM_FOR_EDICT(ent) - 1;
client = ent->priv.sv->client;
ent->priv.sv->client = &sv.clients[index];
ent->progs.sv->movetype = MOVETYPE_WALK;
ent->priv.sv->free = false;
ent->progs.sv->classname = PRVM_SetEngineString("player");
ent->progs.sv->solid = SOLID_BBOX;
ent->progs.sv->model = PRVM_SetEngineString("models/player.mdl");
(int)ent->progs.sv->flags &= ~FL_DEADMONSTER;
VectorCopy (mins, ent->progs.sv->mins);
VectorCopy (maxs, ent->progs.sv->maxs);
VectorClear (ent->progs.sv->velocity);
// clear playerstate values
memset (&ent->priv.sv->client->ps, 0, sizeof(client->ps));
// info_player_start
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.fov = bound(1, client->ps.fov, 160);
client->ps.gunindex = SV_ModelIndex("models/weapons/v_eagle.mdl");
// clear entity state values
ent->priv.sv->s.effects = 0;
ent->priv.sv->s.modelindex = MAX_MODELS - 1; // will use the skin specified model
ent->priv.sv->s.weaponmodel = MAX_MODELS - 1; // custom gun model
// sknum is player num and weapon number
// weapon number will be added in changeweapon
ent->priv.sv->s.skin = PRVM_NUM_FOR_EDICT(ent) - 1;
ent->priv.sv->s.frame = 0;
VectorCopy (spawn_origin, ent->priv.sv->s.origin);
ent->priv.sv->s.origin[2] += 1; // make sure off ground
VectorCopy (ent->priv.sv->s.origin, ent->priv.sv->s.old_origin);
// set the delta angle
for (i = 0; i < 3; i++)
{
client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i]);
}
ent->priv.sv->s.angles[PITCH] = ent->priv.sv->s.angles[ROLL] = 0;
ent->priv.sv->s.angles[YAW] = spawn_angles[YAW];
VectorCopy(ent->priv.sv->s.angles, client->ps.viewangles);
VectorCopy (client->ps.viewangles, client->v_angle);
SV_LinkEdict(ent);
}
/*
==============
SpawnEntities
Creates a server's entity / program execution context by
parsing textual entity definitions out of an ent file.
==============
*/
void SV_SpawnEntities (char *mapname, char *entities, char *spawnpoint)
{
Msg("====== SpawnEntities ========\n");
PRVM_ED_LoadFromFile ( entities );
}
void SV_InitEdict (edict_t *e)
{
e->priv.sv->free = false;
e->progs.sv->classname = PRVM_SetEngineString("noclass");
e->priv.sv->s.number = PRVM_NUM_FOR_EDICT(e);
e->priv.sv->s.renderfx |= RF_IR_VISIBLE; //evil stuff...
}
/*
=================
SV_FreeEdict
Marks the edict as free
=================
*/
void SV_FreeEdict (edict_t *ed)
{
SV_UnlinkEdict(ed); // unlink from world
// don't free players!
if (PRVM_NUM_FOR_EDICT(ed) <= maxclients->value)
return;
memset (ed, 0, sizeof(*ed));
ed->progs.sv->classname = PRVM_SetEngineString("freed");
ed->priv.sv->freetime = sv.time;
ed->priv.sv->free = true;
ed->progs.sv->model = 0;
ed->progs.sv->takedamage = 0;
ed->progs.sv->modelindex = 0;
ed->progs.sv->skin = 0;
ed->progs.sv->frame = 0;
ed->progs.sv->solid = 0;
VectorClear(ed->progs.sv->origin);
VectorClear(ed->progs.sv->angles);
ed->progs.sv->nextthink = -1;
}
/*
============
SV_TouchTriggers
============
*/
void SV_TouchTriggers (edict_t *ent)
{
int i, num;
edict_t *touch[MAX_EDICTS], *hit;
// dead things don't activate triggers!
if ((ent->priv.sv->client || ((int)ent->progs.sv->flags & FL_MONSTER)) && (ent->progs.sv->health <= 0))
return;
num = SV_AreaEdicts(ent->progs.sv->absmin, ent->progs.sv->absmax, touch, MAX_EDICTS, AREA_TRIGGERS);
PRVM_PUSH_GLOBALS;
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i = 0; i < num; i++)
{
hit = touch[i];
if (hit->priv.sv->free) continue;
prog->globals.server->pev = PRVM_EDICT_TO_PROG(ent);
prog->globals.server->other = PRVM_EDICT_TO_PROG(hit);
prog->globals.server->time = sv.time;
PRVM_ExecuteProgram (ent->progs.sv->touch, "QC function pev->touch is missing");
}
// restore state
PRVM_POP_GLOBALS;
}
static edict_t *current_player;
static gclient_t *current_client;
static vec3_t forward, right, up;
float xyspeed, bobmove, bobfracsin; // sin(bobfrac*M_PI)
int bobcycle; // odd cycles are right foot going forward
/*
===============
SV_CalcRoll
===============
*/
float SV_CalcRoll (vec3_t angles, vec3_t velocity)
{
float sign;
float side;
float value;
side = DotProduct (velocity, right);
sign = side < 0 ? -1 : 1;
side = fabs(side);
value = 2;
if (side < 200) side = side * value / 200;
else side = value;
return side*sign;
}
/*
==============
SV_CalcGunOffset
==============
*/
void SV_CalcGunOffset (edict_t *ent)
{
int i;
float delta;
// gun angles from bobbing
ent->priv.sv->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
ent->priv.sv->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
if (bobcycle & 1)
{
ent->priv.sv->client->ps.gunangles[ROLL] = -ent->priv.sv->client->ps.gunangles[ROLL];
ent->priv.sv->client->ps.gunangles[YAW] = -ent->priv.sv->client->ps.gunangles[YAW];
}
ent->priv.sv->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
ent->priv.sv->client->ps.viewoffset[2] = 22;
// gun angles from delta movement
for (i = 0; i < 3; i++)
{
delta = ent->priv.sv->client->oldviewangles[i] - ent->priv.sv->client->ps.viewangles[i];
if (delta > 180) delta -= 360;
if (delta < -180) delta += 360;
if (delta > 45) delta = 45;
if (delta < -45) delta = -45;
if (i == YAW) ent->priv.sv->client->ps.gunangles[ROLL] += 0.1*delta;
ent->priv.sv->client->ps.gunangles[i] += 0.2 * delta;
}
// gun height
VectorClear (ent->priv.sv->client->ps.gunoffset);
// gun_x / gun_y / gun_z are development tools
for (i = 0; i < 3; i++)
{
ent->priv.sv->client->ps.gunoffset[i] += forward[i];
ent->priv.sv->client->ps.gunoffset[i] += right[i];
ent->priv.sv->client->ps.gunoffset[i] += up[i];
}
}
void SV_CalcViewOffset (edict_t *ent)
{
float *angles;
float bob;
float delta;
vec3_t v;
// base angles
angles = ent->priv.sv->client->ps.kick_angles;
// add angles based on velocity
delta = DotProduct (ent->progs.sv->velocity, forward);
angles[PITCH] += delta * 0.002;
delta = DotProduct (ent->progs.sv->velocity, right);
angles[ROLL] += delta * 0.005;
// add angles based on bob
delta = bobfracsin * 0.002 * xyspeed;
if (ent->priv.sv->client->ps.pmove.pm_flags & PMF_DUCKED)
delta *= 6; // crouching
angles[PITCH] += delta;
delta = bobfracsin * 0.002 * xyspeed;
if (ent->priv.sv->client->ps.pmove.pm_flags & PMF_DUCKED)
delta *= 6; // crouching
if (bobcycle & 1) delta = -delta;
angles[ROLL] += delta;
// base origin
VectorClear (v);
// add view height
v[2] += 22;
// add bob height
bob = bobfracsin * xyspeed * 0.005;
if (bob > 6) bob = 6;
v[2] += bob;
v[0] = bound(-14, v[0], 14);
v[1] = bound(-14, v[1], 14);
v[2] = bound(-22, v[0], 30);
VectorCopy (v, ent->priv.sv->client->ps.viewoffset);
}
void SV_SetStats (edict_t *ent)
{
ent->priv.sv->client->ps.stats[STAT_HEALTH_ICON] = SV_ImageIndex("i_health");
ent->priv.sv->client->ps.stats[STAT_HEALTH] = ent->progs.sv->health;
}
void ClientEndServerFrame (edict_t *ent)
{
float bobtime;
int i;
current_player = ent;
current_client = ent->priv.sv->client;
//
// If the origin or velocity have changed since ClientThink(),
// update the pmove values. This will happen when the client
// is pushed by a bmodel or kicked by an explosion.
//
// If it wasn't updated here, the view position would lag a frame
// behind the body position when pushed -- "sinking into plats"
//
for (i = 0; i < 3; i++)
{
current_client->ps.pmove.origin[i] = ent->priv.sv->s.origin[i]*8.0;
current_client->ps.pmove.velocity[i] = ent->progs.sv->velocity[i]*8.0;
}
AngleVectors (ent->priv.sv->client->v_angle, forward, right, up);
//
// set model angles from view angles so other things in
// the world can tell which direction you are looking
//
if (ent->priv.sv->client->v_angle[PITCH] > 180) ent->priv.sv->s.angles[PITCH] = (-360 + ent->priv.sv->client->v_angle[PITCH])/3;
else ent->priv.sv->s.angles[PITCH] = ent->priv.sv->client->v_angle[PITCH]/3;
ent->priv.sv->s.angles[YAW] = ent->priv.sv->client->v_angle[YAW];
ent->priv.sv->s.angles[ROLL] = 0;
ent->priv.sv->s.angles[ROLL] = SV_CalcRoll (ent->priv.sv->s.angles, ent->progs.sv->velocity)*4;
//
// calculate speed and cycle to be used for
// all cyclic walking effects
//
xyspeed = sqrt(ent->progs.sv->velocity[0] * ent->progs.sv->velocity[0] + ent->progs.sv->velocity[1] * ent->progs.sv->velocity[1]);
if (xyspeed < 5)
{
bobmove = 0;
current_client->bobtime = 0; // start at beginning of cycle again
}
else
{
// so bobbing only cycles when on ground
if (xyspeed > 210) bobmove = 0.25;
else if (xyspeed > 100) bobmove = 0.125;
else bobmove = 0.0625;
}
bobtime = (current_client->bobtime += bobmove);
if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
bobtime *= 4;
bobcycle = (int)bobtime;
bobfracsin = fabs(sin(bobtime*M_PI));
// determine the view offsets
SV_CalcViewOffset (ent);
// determine the gun offsets
SV_CalcGunOffset (ent);
SV_SetStats( ent );
}
/*
=================
ClientEndServerFrames
=================
*/
void ClientEndServerFrames (void)
{
int i;
edict_t *ent;
// calc the player views now that all pushing
// and damage has been added
for (i = 0; i < maxclients->value; i++)
{
ent = PRVM_EDICT_NUM(i);
if (ent->priv.sv->free || !ent->priv.sv->client)
continue;
ClientEndServerFrame (ent);
}
}
/*
================
SV_RunFrame
Advances the world by 0.1 seconds
================
*/
void SV_RunFrame (void)
{
int i;
edict_t *ent;
//
// treat each object in turn
// even the world gets a chance to think
//
ent = PRVM_EDICT_NUM(0);
for (i = 0; i < prog->num_edicts; i++, ent++)
{
if (ent->priv.sv->free) continue;
VectorCopy (ent->priv.sv->s.origin, ent->priv.sv->s.old_origin);
if (i > 0 && i <= maxclients->value)
continue; //don't apply phys on clients
SV_Physics(ent);
}
// build the playerstate_t structures for all players
ClientEndServerFrames ();
}
bool SV_ClientConnect (edict_t *ent, char *userinfo)
{
// they can connect
ent->priv.sv->client = sv.clients + PRVM_NUM_FOR_EDICT(ent) - 1;
ent->progs.sv->flags = 0; // make sure we start with known default
ent->progs.sv->health = 100;
return true;
}
void SV_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 skin
s = Info_ValueForKey (userinfo, "skin");
playernum = PRVM_NUM_FOR_EDICT(ent);
// combine name and skin into a configstring
SV_ConfigString (CS_PLAYERSKINS + playernum, va("%s\\%s", Info_ValueForKey (userinfo, "name"), Info_ValueForKey (userinfo, "skin")));
ent->priv.sv->client->ps.fov = bound(1, atoi(Info_ValueForKey(userinfo, "fov")), 160);
}
/*
===========
SV_ClientBegin
called when a client has finished connecting, and is ready
to be placed into the game. This will happen every level load.
============
*/
void SV_ClientBegin (edict_t *ent)
{
int i;
ent->priv.sv->client = sv.clients + PRVM_NUM_FOR_EDICT(ent) - 1;
// if there is already a body waiting for us (a loadgame), just
// take it, otherwise spawn one from scratch
if (ent->priv.sv->free)
{
// 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->priv.sv->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->priv.sv->client->ps.viewangles[i]);
}
else
{
// a spawn point will completely reinitialize the entity
// except for the persistant data that was initialized at
// ClientConnect() time
SV_InitEdict (ent);
SV_PutClientInServer (ent);
}
// make sure all view stuff is valid
ClientEndServerFrame (ent);
}
/*
==============
ClientThink
This will be called once for each client frame, which will
usually be a couple times for each server frame.
==============
*/
void ClientThink (edict_t *ent, usercmd_t *ucmd)
{
gclient_t *client;
edict_t *other;
pmove_t pm;
vec3_t view;
vec3_t oldorigin, oldvelocity;
int i, j;
client = ent->priv.sv->client;
VectorCopy(ent->priv.sv->s.origin, oldorigin);
VectorCopy(ent->progs.sv->velocity, oldvelocity);
ent->priv.sv->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
VectorCopy(ent->priv.sv->s.origin, view);
pm_passent = ent;
// set up for pmove
memset (&pm, 0, sizeof(pm));
if (ent->progs.sv->movetype == MOVETYPE_NOCLIP) client->ps.pmove.pm_type = PM_SPECTATOR;
else if (ent->priv.sv->s.modelindex != MAX_MODELS - 1) client->ps.pmove.pm_type = PM_GIB;
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->priv.sv->s.origin[i]*8;
pm.s.velocity[i] = ent->progs.sv->velocity[i]*8;
}
if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
pm.snapinitial = true;
pm.cmd = *ucmd;
pm.trace = PM_trace; // adds default parms
pm.pointcontents = SV_PointContents;
// perform a pmove
Pmove (&pm);
// save results of pmove
client->ps.pmove = pm.s;
client->old_pmove = pm.s;
for (i = 0; i < 3; i++)
{
ent->priv.sv->s.origin[i] = pm.s.origin[i]*0.125;
ent->progs.sv->velocity[i] = pm.s.velocity[i]*0.125;
}
VectorCopy (pm.mins, ent->progs.sv->mins);
VectorCopy (pm.maxs, ent->progs.sv->maxs);
SV_LinkEdict(ent);
if (ent->progs.sv->movetype != MOVETYPE_NOCLIP)
SV_TouchTriggers (ent);
PRVM_PUSH_GLOBALS;
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
prog->globals.server->pev = PRVM_EDICT_TO_PROG(other);
prog->globals.server->other = PRVM_EDICT_TO_PROG(ent);
prog->globals.server->time = sv.time;
//PRVM_ExecuteProgram (ent->progs.sv->touch, "QC function pev->touch is missing");
}
PRVM_POP_GLOBALS;
}
/*
===========
SV_ClientDisconnect
Called when a player drops from the server.
Will not be called between levels.
============
*/
void SV_ClientDisconnect (edict_t *ent)
{
int playernum;
if (!ent->priv.sv->client) return;
Msg("player disconnected\n");
// send effect
MSG_Begin( svc_muzzleflash );
MSG_WriteShort( &sv.multicast, PRVM_NUM_FOR_EDICT(ent));
MSG_WriteByte( &sv.multicast, MZ_LOGOUT );
MSG_Send(MSG_PVS, ent->priv.sv->s.origin, NULL);
SV_UnlinkEdict(ent);
ent->priv.sv->s.modelindex = 0;
ent->progs.sv->solid = SOLID_NOT;
ent->priv.sv->free = true;
ent->progs.sv->classname = PRVM_SetEngineString("disconnected");
playernum = PRVM_NUM_FOR_EDICT(ent) - 1;
SV_ConfigString (CS_PLAYERSKINS + playernum, "");
}
/*
===============
PF_cprintf
Print to a single client
===============
*/
void PF_cprintf (edict_t *ent, int level, char *fmt, ...)
{
char msg[1024];
va_list argptr;
int n;
if (ent)
{
n = PRVM_NUM_FOR_EDICT(ent);
if (n < 1 || n > maxclients->value)
Host_Error("cprintf to a non-client\n");
}
va_start (argptr,fmt);
vsprintf (msg, fmt, argptr);
va_end (argptr);
if (ent) SV_ClientPrintf (svs.clients+(n-1), level, "%s", msg);
else Msg ("%s", msg);
}
/*
==================
Cmd_Say_f
==================
*/
void Cmd_Say_f (edict_t *ent, bool team, bool arg0)
{
int j;
edict_t *other;
char *p;
char text[2048];
if (Cmd_Argc () < 2 && !arg0) return;
sprintf (text, "%s: ", "all");
if (arg0)
{
strcat (text, Cmd_Argv(0));
strcat (text, " ");
strcat (text, Cmd_Args());
}
else
{
p = Cmd_Args();
if (*p == '"')
{
p++;
p[strlen(p)-1] = 0;
}
strcat(text, p);
}
// don't let text be too long for malicious reasons
if (strlen(text) > 150) text[150] = 0;
strcat(text, "\n");
if (dedicated->value)
PF_cprintf(NULL, PRINT_CHAT, "%s", text);
for (j = 1; j <= maxclients->value; j++)
{
other = PRVM_EDICT_NUM(j);
if (other->priv.sv->free) continue;
if (!other->priv.sv->client) continue;
PF_cprintf(other, PRINT_CHAT, "%s", text);
}
}
/*
==================
HelpComputer
Draw help computer.
==================
*/
void SV_HelpComputer (edict_t *ent)
{
char string[1024];
char *sk = "medium";
sprintf (string, "xv 32 yv 8 picn help " // background
"xv 202 yv 12 string2 \"%s\" " // skill
"xv 0 yv 24 cstring2 \"%s\" " // level name
"xv 0 yv 54 cstring2 \"%s\" " // help 1
"xv 0 yv 110 cstring2 \"%s\" " // help 2
"xv 50 yv 164 string2 \" kills goals secrets\" "
"xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
sk,
sv.name,
"",
"",
0, 0,
0, 0,
0, 0);
MSG_Begin (svc_layout);
MSG_WriteString (&sv.multicast, string);
MSG_Send (MSG_ONE_R, NULL, ent );
}
/*
==================
Cmd_Help_f
Display the current help message
==================
*/
void Cmd_Help_f (edict_t *ent)
{
SV_HelpComputer (ent);
}
/*
=================
SV_ClientCommand
=================
*/
void SV_ClientCommand (edict_t *ent)
{
char *cmd;
char *parm;
if (!ent->priv.sv->client) return; // not fully in game yet
cmd = Cmd_Argv(0);
if(Cmd_Argc() < 2) parm = NULL;
else parm = Cmd_Argv(1);
if (strcasecmp (cmd, "say") == 0)
{
Cmd_Say_f (ent, false, false);
return;
}
if (strcasecmp (cmd, "say_team") == 0)
{
Cmd_Say_f (ent, true, false);
return;
}
if (strcasecmp (cmd, "help") == 0)
{
Cmd_Help_f (ent);
return;
}
}