sof2-sdk/code/cgame/cg_event.c
2002-09-24 00:00:00 +00:00

1217 lines
30 KiB
C

// Copyright (C) 2001-2002 Raven Software
//
// cg_event.c -- handle entity events at snapshot or playerstate transitions
#include "cg_local.h"
#include "..\ghoul2\g2.h"
#include "../../ui/menudef.h"
//==========================================================================
/*
===================
CG_PlaceString
Also called by scoreboard drawing
===================
*/
const char *CG_PlaceString( int rank ) {
static char str[64];
char *s, *t;
if ( rank & RANK_TIED_FLAG ) {
rank &= ~RANK_TIED_FLAG;
t = "Tied for ";
} else {
t = "";
}
if ( rank == 1 ) {
s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue
} else if ( rank == 2 ) {
s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red
} else if ( rank == 3 ) {
s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow
} else if ( rank == 11 ) {
s = "11th";
} else if ( rank == 12 ) {
s = "12th";
} else if ( rank == 13 ) {
s = "13th";
} else if ( rank % 10 == 1 ) {
s = va("%ist", rank);
} else if ( rank % 10 == 2 ) {
s = va("%ind", rank);
} else if ( rank % 10 == 3 ) {
s = va("%ird", rank);
} else {
s = va("%ith", rank);
}
Com_sprintf( str, sizeof( str ), "%s%s", t, s );
return str;
}
/*
=============
CG_GameOver
=============
*/
static void CG_GameOver ( entityState_t *ent )
{
switch ( ent->eventParm )
{
case GAME_OVER_TIMELIMIT:
Com_sprintf ( cgs.gameover, MAX_QPATH, "Timelimit Hit" );
break;
case GAME_OVER_SCORELIMIT:
if ( cgs.gametypeData->teams )
{
switch ( ent->otherEntityNum )
{
case TEAM_RED:
Com_sprintf ( cgs.gameover, MAX_QPATH, "Red Team hit the score limit" );
break;
case TEAM_BLUE:
Com_sprintf ( cgs.gameover, MAX_QPATH, "Blue Team hit the score limit" );
break;
}
}
else
{
Com_sprintf ( cgs.gameover, MAX_QPATH, "%s" S_COLOR_WHITE " hit the score limit", cgs.clientinfo[ent->otherEntityNum].name );
}
break;
default:
return;
}
CG_CenterPrint ( cgs.gameover, 0.43f );
Com_Printf ( "@%s\n", cgs.gameover );
}
/*
=============
CG_Obituary
=============
*/
static void CG_Obituary( entityState_t *ent )
{
int mod;
int target, attacker;
char *message;
char *message2;
const char *targetInfo;
const char *attackerInfo;
char targetName[32];
char attackerName[32];
const char *targetColor;
const char *attackerColor;
attackType_t attack;
gender_t gender;
clientInfo_t *ci;
target = ent->otherEntityNum;
attacker = ent->otherEntityNum2;
mod = ent->eventParm & 0xFF;
attack = (ent->eventParm >> 8) & 0xFF;
attackerColor = S_COLOR_WHITE;
targetColor = S_COLOR_WHITE;
if ( target < 0 || target >= MAX_CLIENTS )
{
Com_Error( ERR_FATAL, "CG_Obituary: target out of range" );
}
// Play the death sound, water if they drowned
if ( mod == MOD_WATER )
{
trap_S_StartSound ( NULL, target, CHAN_AUTO, cgs.media.drownDeathSound, -1, -1 );
}
else
{
trap_S_StartSound( NULL, target, CHAN_VOICE, CG_CustomPlayerSound(target, SOUND_DIE_1 + (cg.time%3)), -1, -1);
}
// Play the frag sound, and make sure its not played more than every 250ms
if ( cg.time - cg.lastKillTime > 250 && attacker == cg.snap->ps.clientNum )
{
if ( cg_soundFrag.integer )
{
// If the attacker killed themselves play the selffrag sound
if ( attacker == target )
{
trap_S_StartLocalSound ( cgs.media.fragSelfSound, CHAN_AUTO );
}
else
{
// In a team game a kill of a teammate will play the self frag sound rather
// than the frag sound
if ( cgs.gametypeData->teams )
{
if ( cgs.clientinfo[target].team == cgs.clientinfo[attacker].team )
{
trap_S_StartLocalSound ( cgs.media.fragSelfSound, CHAN_AUTO );
}
else
{
trap_S_StartLocalSound ( cgs.media.fragSound, CHAN_AUTO );
}
}
else
{
trap_S_StartLocalSound ( cgs.media.fragSound, CHAN_AUTO );
}
}
}
cg.lastKillTime = cg.time;
}
ci = &cgs.clientinfo[target];
if ( attacker < 0 || attacker >= MAX_CLIENTS )
{
attacker = ENTITYNUM_WORLD;
attackerInfo = NULL;
}
else
{
attackerInfo = CG_ConfigString( CS_PLAYERS + attacker );
}
targetInfo = CG_ConfigString( CS_PLAYERS + target );
if ( !targetInfo ) {
return;
}
Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2);
strcat( targetName, S_COLOR_WHITE );
switch ( cgs.clientinfo[target].team )
{
case TEAM_RED:
targetColor = S_COLOR_RED;
break;
case TEAM_BLUE:
targetColor = S_COLOR_BLUE;
break;
}
message2 = "";
// check for single client messages
gender = ci->gender;
switch( mod )
{
case MOD_SUICIDE:
message = "suicides";
break;
case MOD_FALLING:
if ( gender == GENDER_FEMALE )
message = "fell to her death";
else
message = "fell to his death";
break;
case MOD_CRUSH:
message = "was squished";
break;
case MOD_WATER:
message = "sank like a rock";
break;
case MOD_TARGET_LASER:
message = "saw the light";
break;
case MOD_TRIGGER_HURT:
case MOD_TRIGGER_HURT_NOSUICIDE:
message = "was in the wrong place";
break;
case MOD_TEAMCHANGE:
return;
default:
message = NULL;
break;
}
// Attacker killed themselves. Ridicule them for it.
if (attacker == target)
{
switch (mod)
{
case MOD_MM1_GRENADE_LAUNCHER:
case MOD_RPG7_LAUNCHER:
case MOD_M84_GRENADE:
case MOD_SMOHG92_GRENADE:
case MOD_ANM14_GRENADE:
case MOD_M15_GRENADE:
if ( gender == GENDER_FEMALE )
message = "blew herself up";
else if ( gender == GENDER_NEUTER )
message = "blew itself up";
else
message = "blew himself up";
break;
default:
if ( gender == GENDER_FEMALE )
message = "killed herself";
else if ( gender == GENDER_NEUTER )
message = "killed itself";
else
message = "killed himself";
break;
}
}
if (message)
{
Com_Printf( "%s%s %s.\n", targetColor, targetName, message);
return;
}
// check for kill messages from the current clientNum when
// not in a team game.
if ( cgs.gametypeData->showKills )
{
if ( attacker == cg.snap->ps.clientNum )
{
char *s;
if ( !cgs.gametypeData->teams )
{
s = va("You killed %s%s\n%s place with %i", targetColor, targetName,
CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),
cg.snap->ps.persistant[PERS_SCORE] );
}
else
{
s = va("You killed %s%s", targetColor, targetName );
}
CG_CenterPrint( s, 0.43f );
}
}
// check for double client messages
if ( !attackerInfo )
{
attacker = ENTITYNUM_WORLD;
strcpy( attackerName, "noname" );
}
else
{
Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2);
strcat( attackerName, S_COLOR_WHITE );
// check for kill messages about the current clientNum
if ( target == cg.snap->ps.clientNum )
{
Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) );
}
switch ( cgs.clientinfo[attacker].team )
{
case TEAM_RED:
attackerColor = S_COLOR_RED;
break;
case TEAM_BLUE:
attackerColor = S_COLOR_BLUE;
break;
}
}
if ( attacker != ENTITYNUM_WORLD )
{
switch (mod)
{
case MOD_KNIFE:
message = "was sliced by";
break;
case MOD_USAS_12_SHOTGUN:
case MOD_M590_SHOTGUN:
if ( attack == ATTACK_ALTERNATE )
{
message = "was bludgeoned by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
}
else
{
message = "was pumped full of lead by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
}
break;
case MOD_M1911A1_PISTOL:
case MOD_USSOCOM_PISTOL:
case MOD_SILVER_TALON:
if ( attack == ATTACK_ALTERNATE )
{
message = "was pistol whipped by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
}
else
{
message = "was shot by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
}
break;
case MOD_AK74_ASSAULT_RIFLE:
if ( attack == ATTACK_ALTERNATE )
{
message = "was stabbed by";
}
else
{
message = "was shot by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
}
break;
case MOD_M4_ASSAULT_RIFLE:
if ( attack == ATTACK_ALTERNATE )
{
message = "was detonated by";
message2 = va("'s %s", "M203" );
}
else
{
message = "was shot by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
}
break;
case MOD_M60_MACHINEGUN:
case MOD_MICRO_UZI_SUBMACHINEGUN:
case MOD_MP5:
case MOD_M3A1_SUBMACHINEGUN:
case MOD_SIG551:
message = "was shot by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
break;
case MOD_MSG90A1_SNIPER_RIFLE:
message = "was sniped by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
break;
case MOD_MM1_GRENADE_LAUNCHER:
case MOD_RPG7_LAUNCHER:
case MOD_M84_GRENADE:
case MOD_SMOHG92_GRENADE:
case MOD_ANM14_GRENADE:
case MOD_M15_GRENADE:
message = "was detonated by";
message2 = va("'s %s", weaponParseInfo[mod].mName );
break;
case MOD_TELEFRAG:
message = "tried to invade";
message2 = "'s personal space";
break;
default:
message = "was killed by";
break;
}
if (message) {
Com_Printf( "%s%s %s %s%s%s\n", targetColor, targetName, message, attackerColor, attackerName, message2);
return;
}
}
// we don't know what it was
Com_Printf( "%s%s died.\n", targetColor, targetName );
}
/*
================
CG_ItemPickup
A new item was picked up this frame
================
*/
static void CG_ItemPickup( int itemNum, qboolean autoswitch )
{
cg.itemPickup = itemNum;
// see if it should be the grabbed weapon
if ( cg_autoswitch.integer && bg_itemlist[itemNum].giType == IT_WEAPON && autoswitch )
{
if ( cg_autoswitch.integer >= 2 )
{
if ( weaponData[bg_itemlist[itemNum].giTag].safe )
{
cg.weaponSelectTime = cg.time;
cg.weaponSelect = bg_itemlist[itemNum].giTag;
}
}
else
{
cg.weaponSelectTime = cg.time;
cg.weaponSelect = bg_itemlist[itemNum].giTag;
}
}
Com_Printf ( "You picked up %s %s!\n", bg_itemlist[itemNum].pickup_prefix, bg_itemlist[itemNum].pickup_name );
}
/*
================
CG_PainEvent
Also called by playerstate transition
================
*/
void CG_PainEvent( centity_t *cent, int health )
{
ECustomSounds sound;
// don't do more than two pain sounds a second
if ( cg.time - cent->pe.painTime < 500 ) {
return;
}
if (health <= 0 )
{
return;
}
else if ( health < 25 )
{
sound = SOUND_PAIN_1;
}
else if ( health < 50 )
{
sound = SOUND_PAIN_2;
}
else if ( health < 75 )
{
sound = SOUND_PAIN_3;
}
else
{
sound = SOUND_PAIN_3;
}
trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE,
CG_CustomPlayerSound( cent->currentState.number, sound ), -1, -1 );
// save pain time for programitic twitch animation
cent->pe.painTime = cg.time;
cent->pe.painDirection ^= 1;
}
static void CG_BodyQueueCopy(centity_t *cent, int clientNum, int hitLocation, vec3_t direction )
{
centity_t *source;
animation_t *anim;
float animSpeed;
int flags=BONE_ANIM_OVERRIDE_FREEZE;
clientInfo_t *ci;
int i;
if (cent->ghoul2)
{
trap_G2API_CleanGhoul2Models(&cent->ghoul2);
cent->ghoul2 = 0;
}
if (clientNum < 0 || clientNum >= MAX_CLIENTS)
{
return;
}
source = CG_GetEntity ( clientNum );
ci = &cgs.clientinfo[ clientNum ];
cent->radius = 100;
if (!source)
{
return;
}
// Make sure the player model is updated before copying it to the body queue
CG_UpdatePlayerModel ( source );
if (!source->ghoul2)
{
// some how we don't have a g2 model, so don't do anything
return;
}
// Remove the weapon bolt for the death
if ( source->pe.weaponModelSpot )
{
trap_G2API_DetachG2Model ( source->ghoul2, source->pe.weaponModelSpot );
trap_G2API_RemoveGhoul2Model ( &source->ghoul2, source->pe.weaponModelSpot );
source->pe.weaponModelSpot = 0;
source->flashBoltInterface.isValid = qfalse;
source->ejectBoltInterface.isValid = qfalse;
}
trap_G2API_DuplicateGhoul2Instance(source->ghoul2, &cent->ghoul2);
if ( !cent->ghoul2 )
{
return;
}
// Reset all mision bolt positions
for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
{
ci->boltGametypeItems[i] = -1;
}
ci->boltNightvision = -1;
// Clear the source's ghoul2 model to force it to be re-duplicatd. This will
// then cause all the gore attached to it to be cleared
trap_G2API_CleanGhoul2Models ( &source->ghoul2 );
source->ghoul2 = NULL;
if (cg_lockDeaths.integer)
{
anim = &ci->animations[ BOTH_DEATH_CHEST_1 ];
}
else
{
anim = &ci->animations[ cent->currentState.torsoAnim & ~(ANIM_TOGGLEBIT) ];
}
animSpeed = 50.0f / anim->frameLerp;
// Clear any bone angles
trap_G2API_SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time );
trap_G2API_SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time );
trap_G2API_SetBoneAngles(cent->ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels,0, cg.time );
// Set the death animation
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 150);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 150);
// hit location is a bit field and we need to iterate through the bits
for ( i = 0; (1<<i) < HL_MAX; i++ )
{
if (hitLocation & (1<<i))
{
CG_ApplyGore(clientNum, cent, i+1, direction);
}
}
}
/*
==============
CG_EntityEvent
An entity has an event value
also called by CG_CheckPlayerstateEvents
==============
*/
#define DEBUGNAME(x) if(cg_debugEvents.integer){Com_Printf(x"\n");}
void CG_EntityEvent( centity_t *cent, vec3_t position )
{
entityState_t *es;
int event;
vec3_t dir;
const char *s;
int clientNum;
clientInfo_t *ci;
es = &cent->currentState;
event = es->event & ~EV_EVENT_BITS;
if ( cg_debugEvents.integer )
{
Com_Printf( "ent:%3i event:%3i ", es->number, event );
}
// Ignore all events until map is done changing
if ( cg.mMapChange )
{
return;
}
if ( !event )
{
DEBUGNAME("ZEROEVENT");
return;
}
clientNum = es->clientNum;
if ( clientNum < 0 || clientNum >= MAX_CLIENTS )
{
clientNum = 0;
}
ci = &cgs.clientinfo[ clientNum ];
switch ( event )
{
//
// movement generated events
//
case EV_FOOTSTEP:
DEBUGNAME("EV_FOOTSTEP");
if (cg_footsteps.integer)
{
trap_S_StartSound (NULL, es->number, CHAN_BODY, trap_MAT_GetSound(MAT_FOOTSTEP_NORMAL, (es->eventParm&MATERIAL_MASK)), 180, 1000 );
}
break;
case EV_FOOTWADE:
DEBUGNAME("EV_FOOTWADE");
break;
case EV_FALL_SHORT:
DEBUGNAME("EV_FALL_SHORT");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_LAND_NORMAL, es->eventParm&MATERIAL_MASK), 150, 900 );
if ( clientNum == cg.predictedPlayerState.clientNum )
{
// smooth landing z changes
cg.landChange = -8;
cg.landTime = cg.time;
}
break;
case EV_FALL_MEDIUM:
DEBUGNAME("EV_FALL_MEDIUM");
// use normal pain sound
trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_LAND_PAIN, (es->eventParm>>8)&MATERIAL_MASK), 150, 900 );
trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomPlayerSound( es->number, SOUND_PAIN_3 ), -1, -1 );
if ( clientNum == cg.predictedPlayerState.clientNum )
{
// smooth landing z changes
cg.landChange = -16;
cg.landTime = cg.time;
}
break;
case EV_FALL_FAR:
DEBUGNAME("EV_FALL_FAR");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_LAND_DEATH, (es->eventParm>>8)&MATERIAL_MASK), -1, -1 );
trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomPlayerSound( cent->currentState.number, SOUND_PAIN_2 ), 150, 900 );
// don't play a pain sound right after this
cent->pe.painTime = cg.time;
if ( clientNum == cg.predictedPlayerState.clientNum )
{
// smooth landing z changes
cg.landChange = -24;
cg.landTime = cg.time;
}
break;
case EV_STEP_4:
case EV_STEP_8:
case EV_STEP_12:
case EV_STEP_16: // smooth out step up transitions
{
float oldStep;
int delta;
int step;
DEBUGNAME("EV_STEP");
if ( clientNum != cg.predictedPlayerState.clientNum ) {
break;
}
// if we are interpolating, we don't need to smooth steps
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ||
cg_nopredict.integer || cg_synchronousClients.integer ) {
break;
}
// check for stepping up before a previous step is completed
delta = cg.time - cg.stepTime;
if (delta < STEP_TIME)
{
oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME;
}
else
{
oldStep = 0;
}
// add this amount
step = 4 * (event - EV_STEP_4 + 1 );
cg.stepChange = oldStep + step;
if ( cg.stepChange > MAX_STEP_CHANGE )
{
cg.stepChange = MAX_STEP_CHANGE;
}
cg.stepTime = cg.time;
break;
}
case EV_JUMP:
DEBUGNAME("EV_JUMP");
trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ), -1, -1 );
break;
case EV_WATER_FOOTSTEP:
DEBUGNAME("EV_WATER_FOOTSTEP");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterFootstep[rand()%2], -1, -1);
break;
case EV_WATER_TOUCH:
DEBUGNAME("EV_WATER_TOUCH");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterFootstep[rand()%2], -1, -1);
break;
case EV_WATER_LAND:
DEBUGNAME("EV_WATER_LAND");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterJumpIn, -1, -1 );
break;
case EV_WATER_CLEAR:
DEBUGNAME("EV_WATER_CLEAR");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterLeave, -1, -1 );
break;
case EV_SWIM:
DEBUGNAME("EV_SWIM");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterWade[rand()%2], -1, -1 );
break;
case EV_ITEM_PICKUP_QUIET:
case EV_ITEM_PICKUP:
{
gitem_t *item;
int index;
qboolean autoswitch = qfalse;
DEBUGNAME("EV_ITEM_PICKUP");
// Dtermine if this item should autoswitch
autoswitch = (es->eventParm & ITEM_AUTOSWITCHBIT)?qtrue:qfalse;
// player predicted index
index = es->eventParm & ~(ITEM_AUTOSWITCHBIT|ITEM_QUIETPICKUP);
if ( index < 1 || index >= bg_numItems )
{
break;
}
item = &bg_itemlist[ index ];
if ( event != EV_ITEM_PICKUP_QUIET && item->pickup_sound )
{
trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ), -1, -1 );
}
// show icon and name on status bar
if ( es->number == cg.snap->ps.clientNum )
{
if ( cg.predictedPlayerState.pm_type == PM_NORMAL )
{
CG_ItemPickup( index, autoswitch );
}
}
break;
}
//=================================================================
//
// weapon events
//
//=================================================================
case EV_CHANGE_WEAPON_CANCELLED:
case EV_CHANGE_WEAPON:
DEBUGNAME("EV_CHANGE_WEAPON");
// Determine whether or not the alt fire popup should show up
if(es->number==cg.snap->ps.clientNum && cg.weaponMenuUp )
{
// done with weapon menu
cg.weaponMenuUp = qfalse;
cg.weaponSelect = cg.weaponMenuSelect;
}
break;
case EV_READY_WEAPON:
DEBUGNAME("EV_READY_WEAPON");
break;
case EV_FIRE_WEAPON:
DEBUGNAME("EV_FIRE_WEAPON");
CG_FireWeapon( cent, ATTACK_NORMAL );
break;
case EV_ALT_FIRE:
DEBUGNAME("EV_ALT_FIRE");
CG_FireWeapon( cent, ATTACK_ALTERNATE );
break;
case EV_NOAMMO:
DEBUGNAME("EV_NOAMMO");
if(es->number==cg.snap->ps.clientNum)
{
CG_OutOfAmmoChange( es->eventParm );
}
break;
case EV_ITEM_POP:
DEBUGNAME("EV_ITEM_POP");
break;
case EV_ITEM_RESPAWN:
DEBUGNAME("EV_ITEM_RESPAWN");
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.itemRespawnSound, -1, -1 );
cent->miscTime = cg.time;
break;
//=================================================================
//
// other events
//
//=================================================================
case EV_PLAYER_TELEPORT_IN:
DEBUGNAME("EV_PLAYER_TELEPORT_IN");
if ( cgs.gametypeData->respawnType != RT_NONE )
{
trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound, -1, -1 );
}
break;
case EV_PLAYER_TELEPORT_OUT:
DEBUGNAME("EV_PLAYER_TELEPORT_OUT");
break;
case EV_GRENADE_BOUNCE:
DEBUGNAME("EV_GRENADE_BOUNCE");
if ( rand() & 1 )
{
trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_BOUNCEMETAL_1, (es->eventParm&MATERIAL_MASK)), -1, -1 );
}
else
{
trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_BOUNCEMETAL_2, (es->eventParm&MATERIAL_MASK)), -1, -1 );
}
break;
case EV_DESTROY_GHOUL2_INSTANCE:
{
centity_t* cent2 = CG_GetEntity (es->eventParm);
DEBUGNAME("EV_DESTROY_GHOUL2_INSTANCE");
if ( cent2->ghoul2 && trap_G2_HaveWeGhoul2Models( cent2->ghoul2))
{
trap_G2API_CleanGhoul2Models(&(cent2->ghoul2));
}
break;
}
//=================================================================
//
// missile impacts
//
case EV_MISSILE_HIT:
DEBUGNAME("EV_MISSILE_HIT");
ByteToDir( (es->eventParm >> MATERIAL_BITS), dir );
CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum,
(cent->currentState.eFlags & EF_ALT_FIRING)?ATTACK_ALTERNATE:ATTACK_NORMAL );
if ( es->otherEntityNum != cg.snap->ps.clientNum )
{
// Some missiles - e.g. thrown knives stick in players (for visual effect only).
CG_HandleStickyMissile(cent,es,dir,es->otherEntityNum);
}
break;
case EV_MISSILE_MISS:
DEBUGNAME("EV_MISSILE_MISS");
ByteToDir( (es->eventParm >> MATERIAL_BITS), dir );
CG_MissileHitWall(es->weapon, position, dir,
(es->eventParm & MATERIAL_MASK), (cent->currentState.eFlags & EF_ALT_FIRING)?ATTACK_ALTERNATE:ATTACK_NORMAL );
break;
case EV_BULLET_HIT_WALL:
DEBUGNAME("EV_BULLET_HIT_WALL");
if ( !(cg_antiLag.integer && cg_impactPrediction.integer && !cg_synchronousClients.integer && es->otherEntityNum == cg.predictedPlayerState.clientNum ) )
{
// eventParm contains the direction byte and the material id
ByteToDir( (es->eventParm >> MATERIAL_BITS), dir );
// time contains the weapon and attack of the shot
CG_Bullet( es->pos.trBase, es->otherEntityNum, (es->time&0xFF),
dir, ENTITYNUM_WORLD, (es->eventParm & MATERIAL_MASK),
((es->time>>8)&0xFF) );
}
break;
case EV_BULLET_HIT_FLESH:
DEBUGNAME("EV_BULLET_HIT_FLESH");
// Play hit sounds for local player
if ( es->otherEntityNum2 == cg.snap->ps.clientNum )
{
if ( cg.snap->ps.stats[STAT_ARMOR] )
{
trap_S_StartLocalSound ( cgs.media.armorHitSound[rand()%2], CHAN_AUTO );
}
else
{
trap_S_StartLocalSound ( cgs.media.fleshHitSound[rand()%2], CHAN_AUTO );
}
}
#ifdef _SOF2_FLESHIMPACTPREDICTION
if ( !(cg_antiLag.integer && cg_impactPrediction.integer >= 2 && !!cg_synchronousClients.integer && es->otherEntityNum == cg.predictedPlayerState.clientNum ) )
#endif
{
int fxtype = MATERIAL_FLESH;
// eventParm contains the direction byte
ByteToDir( es->eventParm, dir );
if (cg_lockBlood.integer)
{
fxtype = MATERIAL_NONE;
}
CG_Bullet( es->pos.trBase, es->otherEntityNum, (es->time&0xFF), dir,
es->otherEntityNum2, fxtype,
((es->time>>8)&0xFF) );
CG_AddProcGore ( cent );
}
break;
case EV_EXPLOSION_HIT_FLESH:
DEBUGNAME("EV_EXPLOSION_HIT_FLESH");
CG_AddProcGore ( cent );
break;
case EV_PLAY_EFFECT:
DEBUGNAME("EV_PLAY_EFFECT");
if (es->eventParm != -1)
{
trap_FX_PlayEffectID(es->eventParm, es->origin, es->angles, -1, -1 );
}
break;
case EV_GENERAL_SOUND:
DEBUGNAME("EV_GENERAL_SOUND");
if ( cgs.gameSounds[ es->eventParm ] ) {
trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ], -1, -1 );
} else {
s = CG_ConfigString( CS_SOUNDS + es->eventParm );
trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ), -1, -1 );
}
break;
case EV_GLOBAL_SOUND:
if ( cg_soundGlobal.integer )
{
DEBUGNAME("EV_GLOBAL_SOUND");
if ( cgs.gameSounds[ es->eventParm ] )
{
trap_S_StartLocalSound ( cgs.gameSounds[ es->eventParm ], CHAN_AUTO );
}
else
{
s = CG_ConfigString( CS_SOUNDS + es->eventParm );
trap_S_StartLocalSound ( CG_CustomSound( es->number, s ), CHAN_AUTO );
}
}
break;
case EV_ENTITY_SOUND:
DEBUGNAME("EV_ENTITY_SOUND");
//somewhat of a hack - weapon is the caller entity's index, trickedentindex is the proper sound channel
if ( cgs.gameSounds[ es->eventParm ] ) {
trap_S_StartSound (NULL, es->weapon, 0, cgs.gameSounds[ es->eventParm ], -1, -1 );
} else {
s = CG_ConfigString( CS_SOUNDS + es->eventParm );
trap_S_StartSound (NULL, es->weapon, 0, CG_CustomSound( es->weapon, s ), -1, -1 );
}
break;
case EV_GLASS_SHATTER:
DEBUGNAME("EV_GLASS_SHATTER");
CG_GlassShatter(es->number, es->pos.trBase, es->angles, es->origin);
break;
case EV_PAIN:
// local player sounds are triggered in CG_CheckLocalSounds,
// so ignore events on the player
DEBUGNAME("EV_PAIN");
if ( cent->currentState.number != cg.snap->ps.clientNum )
{
CG_PainEvent( cent, es->eventParm );
}
break;
case EV_PAIN_WATER:
{
static drownIndex = 0;
DEBUGNAME("EV_PAIN_WATER");
trap_S_StartSound ( NULL, es->number, CHAN_VOICE, cgs.media.drownPainSound[(drownIndex++)%2], -1, -1 );
break;
}
case EV_GAME_OVER:
DEBUGNAME("EV_OBITUARY");
CG_GameOver ( es );
break;
case EV_GOGGLES:
DEBUGNAME("EV_GOGGLES");
// Sound is handled elsewhere for local client
trap_S_StartSound ( NULL, es->number, CHAN_AUTO, es->eventParm?cgs.media.gogglesOnSound:cgs.media.gogglesOffSound, 120, -1 );
break;
case EV_OBITUARY:
DEBUGNAME("EV_OBITUARY");
CG_Obituary( es );
break;
case EV_STOPLOOPINGSOUND:
DEBUGNAME("EV_STOPLOOPINGSOUND");
trap_S_StopLoopingSound( es->number );
es->loopSound = 0;
break;
case EV_DEBUG_LINE:
DEBUGNAME("EV_DEBUG_LINE");
CG_Beam( cent );
break;
case EV_TESTLINE:
DEBUGNAME("EV_TESTLINE");
CG_TestLine(es->origin, es->origin2, 0, es->weapon, 1);
break;
case EV_BODY_QUEUE_COPY:
DEBUGNAME("EV_BODY_QUEUE_COPY");
// First byte of eventParm is the client Number
// Second byte of eventParm is the direction of the incoming shot
// Third and fourth byte of eventParm is the hit location
ByteToDir( (es->eventParm & 0xFF), dir );
CG_BodyQueueCopy(cent, es->otherEntityNum, (es->eventParm>>8), dir);
break;
case EV_PROC_GORE:
DEBUGNAME("EV_PROC_GORE");
CG_AddProcGore(cent);
break;
case EV_BOTWAYPOINT:
DEBUGNAME("EV_BOTWAYPOINT");
CG_TestLine(cent->lerpOrigin, es->angles, 30000, 0x0000ff, 3);
//Just render for 30 seconds because this waypoint might not be rendered again for quite some time.
break;
case EV_GAMETYPE_RESTART:
CG_MapRestart ( qtrue );
break;
case EV_USE:
break;
case EV_WEAPON_CALLBACK:
DEBUGNAME("EV_WEAPON_CALLBACK");
if ( cent->currentState.number == cg.snap->ps.clientNum )
{
CG_WeaponCallback ( &cg.predictedPlayerState,
&cg_entities[cg.predictedPlayerState.clientNum],
(es->eventParm&0xFF), // Weapon id
((es->eventParm>>8)&0xFF), // Anim id
((es->eventParm>>16)&0xFF), // Anim choice
((es->eventParm>>24)&0xFF) ); // Callback step
}
break;
default:
DEBUGNAME("UNKNOWN");
Com_Error( ERR_FATAL, "Unknown event: %i", event );
break;
}
}
/*
==============
CG_CheckEvents
==============
*/
void CG_CheckEvents( centity_t *cent )
{
// check for event-only entities
if ( cent->currentState.eType > ET_EVENTS )
{
// already fired
if ( cent->previousEvent )
{
return;
}
// if this is a player event set the entity number of the client entity number
if ( cent->currentState.eFlags & EF_PLAYER_EVENT )
{
cent->currentState.number = cent->currentState.otherEntityNum;
}
cent->previousEvent = 1;
cent->currentState.event = cent->currentState.eType - ET_EVENTS;
}
else
{
// check for events riding with another entity
if ( cent->currentState.event == cent->previousEvent )
{
return;
}
cent->previousEvent = cent->currentState.event;
if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 )
{
return;
}
}
// calculate the position at exactly the frame time
BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
CG_SetEntitySoundPosition( cent );
CG_EntityEvent( cent, cent->lerpOrigin );
}