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

852 lines
21 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);
}
int PM_pointcontents( vec3_t point )
{
return SV_PointContents( point, pm_passent );
}
/*
===========
PutClientInServer
Called when a player connects to a server or respawns in
a deathmatch.
============
*/
void SV_PutClientInServer (edict_t *ent)
{
int index;
gclient_t *client;
int i;
index = PRVM_NUM_FOR_EDICT(ent) - 1;
client = ent->priv.sv->client;
prog->globals.sv->time = sv.time;
prog->globals.sv->pev = PRVM_EDICT_TO_PROG( ent );
if(sv.loadgame)
{
}
else PRVM_ExecuteProgram (prog->globals.sv->PutClientInServer, "QC function PutClientInServer is missing");
ent->priv.sv->client = svs.gclients + index;
ent->priv.sv->free = false;
(int)ent->progs.sv->flags &= ~FL_DEADMONSTER;
// clear playerstate values
memset (&ent->priv.sv->client->ps, 0, sizeof(client->ps));
// info_player_start
VectorCopy( ent->progs.sv->origin, client->ps.origin );
client->ps.fov = 90;
client->ps.fov = bound(1, client->ps.fov, 160);
client->ps.vmodel.index = SV_ModelIndex(PRVM_GetString(ent->progs.sv->weaponmodel));
if(sv.loadgame)
{
}
else
{
ent->progs.sv->frame = 0;
ent->progs.sv->origin[2] += 1; // make sure off ground
}
VectorCopy(ent->progs.sv->origin, ent->progs.sv->old_origin);
// set the delta angle
for (i = 0; i < 3; i++)
{
client->ps.delta_angles[i] = ANGLE2SHORT(ent->progs.sv->angles[i]);
}
if(sv.loadgame)
{
}
else
{
ent->progs.sv->angles[PITCH] = 0;
ent->progs.sv->angles[ROLL] = 0;
}
VectorCopy( ent->progs.sv->angles, client->ps.viewangles );
SV_LinkEdict(ent);
ent->priv.sv->physbody = pe->CreatePlayer( ent->priv.sv, SV_GetModelPtr( ent ), ent->progs.sv->m_pmatrix );
}
/*
============
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 );
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.sv->pev = PRVM_EDICT_TO_PROG(hit);
prog->globals.sv->other = PRVM_EDICT_TO_PROG(ent);
prog->globals.sv->time = sv.time;
if( hit->progs.sv->touch )
{
PRVM_ExecuteProgram (hit->progs.sv->touch, "QC function pev->touch is missing\n");
}
}
// 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.vmodel.angles[ROLL] = xyspeed * bobfracsin * 0.005;
ent->priv.sv->client->ps.vmodel.angles[YAW] = xyspeed * bobfracsin * 0.01;
if (bobcycle & 1)
{
ent->priv.sv->client->ps.vmodel.angles[ROLL] = -ent->priv.sv->client->ps.vmodel.angles[ROLL];
ent->priv.sv->client->ps.vmodel.angles[YAW] = -ent->priv.sv->client->ps.vmodel.angles[YAW];
}
ent->priv.sv->client->ps.vmodel.angles[PITCH] = xyspeed * bobfracsin * 0.005;
ent->priv.sv->client->ps.viewoffset[2] = ent->priv.sv->client->ps.viewheight;
// gun angles from delta movement
for (i = 0; i < 3; i++)
{
delta = ent->priv.sv->client->ps.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.vmodel.angles[ROLL] += 0.1*delta;
ent->priv.sv->client->ps.vmodel.angles[i] += 0.2 * delta;
}
// gun height
VectorClear (ent->priv.sv->client->ps.vmodel.offset);
// gun_x / gun_y / gun_z are development tools
for (i = 0; i < 3; i++)
{
ent->priv.sv->client->ps.vmodel.offset[i] += forward[i];
ent->priv.sv->client->ps.vmodel.offset[i] += right[i];
ent->priv.sv->client->ps.vmodel.offset[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;
VectorCopy(ent->progs.sv->punchangle, 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.pm_flags & PMF_DUCKED)
delta *= 6; // crouching
angles[PITCH] += delta;
delta = bobfracsin * 0.002 * xyspeed;
if (ent->priv.sv->client->ps.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("hud/i_health");
ent->priv.sv->client->ps.stats[STAT_HEALTH] = ent->progs.sv->health;
}
void ClientEndServerFrame (edict_t *ent)
{
float bobtime;
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"
//
VectorCopy(ent->progs.sv->origin, current_client->ps.origin );
VectorCopy(ent->progs.sv->velocity, current_client->ps.velocity );
AngleVectors (ent->priv.sv->client->ps.viewangles, 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->ps.viewangles[PITCH] > 180)
ent->progs.sv->angles[PITCH] = (-360 + ent->priv.sv->client->ps.viewangles[PITCH])/3;
else ent->progs.sv->angles[PITCH] = ent->priv.sv->client->ps.viewangles[PITCH]/3;
ent->progs.sv->angles[YAW] = ent->priv.sv->client->ps.viewangles[YAW];
ent->progs.sv->angles[ROLL] = 0;
ent->progs.sv->angles[ROLL] = SV_CalcRoll (ent->progs.sv->angles, ent->progs.sv->velocity)*4;
ent->priv.sv->client->ps.pm_time = ent->progs.sv->teleport_time * 1000; //in msecs
//
// 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->ps.bobtime = 0; // start at beginning of cycle again
}
else if (ent->progs.sv->groundentity)
{
// 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->ps.bobtime += bobmove);
if (current_client->ps.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 );
// if the scoreboard is up, update it
if (!(sv.framenum & 31))
{
}
}
/*
=================
ClientEndServerFrames
=================
*/
void ClientEndServerFrames (void)
{
int i;
// calc the player views now that all pushing
// and damage has been added
for (i = 0; i < maxclients->value; i++)
{
if(svs.clients[i].state != cs_spawned) continue;
ClientEndServerFrame (svs.clients[i].edict);
}
}
/*
================
SV_RunFrame
Advances the world by 0.1 seconds
================
*/
void SV_RunFrame( void )
{
int i;
edict_t *ent;
// let the progs know that a new frame has started
prog->globals.sv->pev = PRVM_EDICT_TO_PROG(prog->edicts);
prog->globals.sv->other = PRVM_EDICT_TO_PROG(prog->edicts);
prog->globals.sv->time = sv.time;
prog->globals.sv->frametime = sv.frametime;
PRVM_ExecuteProgram (prog->globals.sv->StartFrame, "QC function StartFrame is missing");
for (i = 1; i < prog->num_edicts; i++ )
{
ent = PRVM_EDICT_NUM(i);
if (ent->priv.sv->free) continue;
VectorCopy (ent->progs.sv->origin, ent->progs.sv->old_origin);
// don't apply phys on clients
if (i > 0 && i <= maxclients->value) continue;
SV_Physics( ent );
}
// build the playerstate_t structures for all players
ClientEndServerFrames ();
prog->globals.sv->pev = PRVM_EDICT_TO_PROG(prog->edicts);
prog->globals.sv->other = PRVM_EDICT_TO_PROG(prog->edicts);
prog->globals.sv->time = sv.time;
PRVM_ExecuteProgram (prog->globals.sv->EndFrame, "QC function EndFrame is missing");
// decrement prog->num_edicts if the highest number entities died
for ( ;PRVM_EDICT_NUM(prog->num_edicts - 1)->priv.sv->free; prog->num_edicts-- );
}
bool SV_ClientConnect (edict_t *ent, char *userinfo)
{
// they can connect
ent->priv.sv->client = svs.gclients + PRVM_NUM_FOR_EDICT(ent) - 1;
ent->progs.sv->flags = 0; // make sure we start with known default
ent->progs.sv->health = 100;
MsgDev(D_NOTE, "SV_ClientConnect()\n");
prog->globals.sv->time = sv.time;
prog->globals.sv->pev = PRVM_EDICT_TO_PROG(ent);
PRVM_ExecuteProgram (prog->globals.sv->ClientConnect, "QC function ClientConnect is missing");
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 = svs.gclients + 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.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;
// call standard client pre-think
prog->globals.sv->time = sv.time;
prog->globals.sv->pev = PRVM_EDICT_TO_PROG(ent);
PRVM_ExecuteProgram (prog->globals.sv->PlayerPreThink, "QC function PlayerPreThink is missing");
VectorCopy(ent->progs.sv->origin, oldorigin);
VectorCopy(ent->progs.sv->velocity, oldvelocity);
ent->priv.sv->client->ps.pm_flags &= ~PMF_NO_PREDICTION;
VectorCopy(ent->progs.sv->origin, view);
pm_passent = ent;
// set up for pmove
memset (&pm, 0, sizeof(pm));
if (ent->progs.sv->movetype == MOVETYPE_NOCLIP) client->ps.pm_type = PM_SPECTATOR;
else client->ps.pm_type = PM_NORMAL;
client->ps.gravity = sv_gravity->value;
if(ent->progs.sv->teleport_time) client->ps.pm_flags |= PMF_TIME_TELEPORT;
else client->ps.pm_flags &= ~PMF_TIME_TELEPORT;
pm.ps = client->ps;
memcpy( &client->ucmd, ucmd, sizeof(usercmd_t));//IMPORTANT!!!
VectorCopy(ent->progs.sv->origin, pm.ps.origin );
VectorCopy(ent->progs.sv->velocity, pm.ps.velocity );
pm.cmd = *ucmd;
pm.trace = PM_trace; // adds default parms
pm.pointcontents = PM_pointcontents;
// perform a pmove
pe->PlayerMove( &pm, false );
// save results of pmove
client->ps = pm.ps;
VectorCopy(pm.ps.origin, ent->progs.sv->origin);
VectorCopy(pm.ps.velocity, ent->progs.sv->velocity);
VectorCopy(pm.mins, ent->progs.sv->mins);
VectorCopy(pm.maxs, ent->progs.sv->maxs);
VectorCopy(pm.ps.viewangles, client->ps.viewangles);
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
if (!other->progs.sv->touch) continue;
prog->globals.sv->pev = PRVM_EDICT_TO_PROG(other);
prog->globals.sv->other = PRVM_EDICT_TO_PROG(ent);
prog->globals.sv->time = sv.time;
if( other->progs.sv->touch )
{
PRVM_ExecuteProgram (other->progs.sv->touch, "QC function pev->touch is missing\n");
}
}
PRVM_POP_GLOBALS;
// call standard player post-think
prog->globals.sv->time = sv.time;
prog->globals.sv->pev = PRVM_EDICT_TO_PROG(ent);
PRVM_ExecuteProgram (prog->globals.sv->PlayerPostThink, "QC function PlayerPostThink is missing");
}
/*
===========
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;
SV_UnlinkEdict(ent);
ent->progs.sv->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, "");
}
/*
==================
SV_StartParticle
Make sure the event gets sent to all clients
==================
*/
void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count)
{
MsgWarn("SV_StartParticle: implement me\n");
}
/*
===============
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;
com.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;
}
}
void SV_Transform( sv_edict_t *ed, matrix4x3 transform )
{
edict_t *edict;
vec3_t origin, angles;
matrix4x4 objmatrix;
if(!ed) return;
edict = PRVM_EDICT_NUM( ed->serialnumber );
// save matrix (fourth value will be reset on save\load)
VectorCopy( transform[0], edict->progs.sv->m_pmatrix[0] );
VectorCopy( transform[1], edict->progs.sv->m_pmatrix[1] );
VectorCopy( transform[2], edict->progs.sv->m_pmatrix[2] );
VectorCopy( transform[3], edict->progs.sv->m_pmatrix[3] );
MatrixLoadIdentity( objmatrix );
VectorCopy( transform[0], objmatrix[0] );
VectorCopy( transform[1], objmatrix[1] );
VectorCopy( transform[2], objmatrix[2] );
VectorCopy( transform[3], objmatrix[3] );
MatrixAngles( objmatrix, origin, angles );
VectorCopy( origin, edict->progs.sv->origin );
VectorCopy( angles, edict->progs.sv->angles );
// refresh force and torque
pe->GetForce( ed->physbody, edict->progs.sv->velocity, edict->progs.sv->avelocity, edict->progs.sv->force, edict->progs.sv->torque );
pe->GetMassCentre( ed->physbody, edict->progs.sv->m_pcentre );
}
/*
==============
CV_ClientMove
grab user cmd from player_state_t
send it to transform callback
==============
*/
void SV_PlayerMove( sv_edict_t *ed )
{
pmove_t pm;
gclient_t *client;
edict_t *player;
client = ed->client;
player = PRVM_PROG_TO_EDICT( ed->serialnumber );
memset( &pm, 0, sizeof(pm) );
if( player->progs.sv->movetype == MOVETYPE_NOCLIP )
client->ps.pm_type = PM_SPECTATOR;
else client->ps.pm_type = PM_NORMAL;
client->ps.gravity = sv_gravity->value;
if( player->progs.sv->teleport_time )
client->ps.pm_flags |= PMF_TIME_TELEPORT;
else client->ps.pm_flags &= ~PMF_TIME_TELEPORT;
pm.ps = client->ps;
pm.cmd = client->ucmd;
pm.body = ed->physbody; // member body ptr
VectorCopy( player->progs.sv->origin, pm.ps.origin );
VectorCopy( player->progs.sv->velocity, pm.ps.velocity );
pe->ServerMove( &pm );
// save results of pmove
client->ps = pm.ps;
VectorCopy(pm.ps.origin, player->progs.sv->origin);
VectorCopy(pm.ps.velocity, player->progs.sv->velocity);
VectorCopy(pm.mins, player->progs.sv->mins);
VectorCopy(pm.maxs, player->progs.sv->maxs);
VectorCopy(pm.ps.viewangles, client->ps.viewangles);
}
void SV_PlaySound( sv_edict_t *ed, float volume, const char *sample )
{
float vol = bound( 0.0f, volume/255.0f, 255.0f );
int sound_idx = SV_SoundIndex( sample );
edict_t *ent;
if( !ed ) ed = prog->edicts->priv.sv;
ent = PRVM_PROG_TO_EDICT( ed->serialnumber );
SV_StartSound( ent->progs.sv->origin, ent, CHAN_BODY, sound_idx, vol, 1.0f, 0 );
}