Pierre-Marie Baty |
05-01-2004 00:27 |
Re: Hearing sounds
I think I can boast to have the most complicated sound code in my bot - ever :D
In fact, sounds are of 2 types:
- "important" sounds are played by the server, which orders then some or all of its client to play it at the same time, through a network message.
- most of the other sounds are emulated by the client DLL, which interprets the game and decides if a sound must be played or not. This is done to save a whole lot of network bandwidth.
Only a few sounds are hookable through server-side functions, actually. All the rest is client-side stuff only.
Since bots have no client DLL, they need to emulate all the other sounds themselves. Movement sounds AND weapon sounds. Movement sounds can be evaluated each frame, while weapon sounds can be hooked by catching "ammo decrease" network messages.
Here's how I do it (here for emulating a Counter-Strike client DLL).
Code:
void PlayClientSoundsForBots (edict_t *pPlayer)
{
// this function determines if the player pPlayer is walking or running, or climbing a ladder,
// or landing on the ground, and so if he's likely to emit some client sound or not. Since
// these types of sounds are predicted on the client side only, and bots have no client DLL,
// we have to simulate their emitting in order for the bots to hear them. So in case a player
// is moving, we bring his footstep sounds to the ears of the bots around. This sound is based
// on the texture the player is walking on. Using TraceTexture(), we ask the engine for that
// texture, then look up in the step sounds database in order to determine which footstep
// sound is related to that texture. The ladder check then assumes that a player moving
// vertically, not on the ground, having a ladder in his immediate surroundings is climbing
// it, and the ladder sound is emitted periodically the same way footstep sounds are emitted.
// Then, the landing check looks for non-null value of the player's punch angles (screen
// tilting) while this player's damage inflictor be either null, or the world. If the test
// success, a landing sound is emitted as well.
// thanks to Tom Simpson from FoxBot for the water sounds handling
edict_t *pGroundEntity = NULL;
const char *texture_name, *player_weapon;
char texture_type;
char sound_path[256];
int player_index;
float player_velocity, volume;
player_index = ENTINDEX (pPlayer) - 1; // get the player index
if (DebugLevel.is_observer && !players[player_index].is_racc_bot)
return; // skip real players if in observer mode
player_velocity = pPlayer->v.velocity.Length (); // get the player velocity
player_weapon = STRING (pPlayer->v.weaponmodel) + 9; // get player's weapon, skip 'models/p_'
// does the server allow footstep sounds AND this player is actually moving
// AND is player on the ground AND is it time for him to make a footstep sound
// OR has that player just landed on the ground after a jump ?
if ((server.does_footsteps && IsOnFloor (pPlayer) && (player_velocity > 0)
&& (players[player_index].step_sound_time < *server.time))
|| ((FNullEnt (pPlayer->v.dmg_inflictor) || (pPlayer->v.dmg_inflictor == pWorldEntity))
&& (pPlayer->v.punchangle != g_vecZero)
&& !(players[player_index].prev_v.flags & (FL_ONGROUND | FL_PARTIALGROUND))))
{
// is this player sloshing in water ?
if (pPlayer->v.waterlevel > 0)
{
sprintf (sound_path, "player/pl_slosh%d.wav", RANDOM_LONG (1, 4)); // build a slosh sound path
// bring slosh sound from this player to the bots' ears
DispatchSound (sound_path, pPlayer->v.origin + Vector (0, 0, -18), 0.9, ATTN_NORM);
players[player_index].step_sound_time = *server.time + 0.300; // next slosh in 300 milliseconds
}
// else this player is definitely not in water, does he move fast enough to make sounds ?
else if (player_velocity > MAX_WALK_SPEED)
{
// get the entity under the player's feet
if (!FNullEnt (pPlayer->v.groundentity))
pGroundEntity = pPlayer->v.groundentity; // this player is standing over something
else
pGroundEntity = pWorldEntity; // this player is standing over the world itself
// ask the engine for the texture name on pGroundEntity under the player's feet
texture_name = TRACE_TEXTURE (pGroundEntity, pPlayer->v.origin, Vector (0, 0, -9999));
// if the engine found the texture, ask the game DLL for the texture type
if (texture_name != NULL)
texture_type = PM_FindTextureType ((char *) texture_name); // ask for texture type
// given the type of texture under player's feet, prepare a sound file for being played
switch (texture_type)
{
default:
case CHAR_TEX_CONCRETE:
sprintf (sound_path, "player/pl_step%d.wav", RANDOM_LONG (1, 4)); // 4 step sounds
volume = 0.9;
break;
case CHAR_TEX_METAL:
sprintf (sound_path, "player/pl_metal%d.wav", RANDOM_LONG (1, 4)); // 4 metal sounds
volume = 0.9;
break;
case CHAR_TEX_DIRT:
sprintf (sound_path, "player/pl_dirt%d.wav", RANDOM_LONG (1, 4)); // 4 dirt sounds
volume = 0.9;
break;
case CHAR_TEX_VENT:
sprintf (sound_path, "player/pl_duct%d.wav", RANDOM_LONG (1, 4)); // 4 duct sounds
volume = 0.5;
break;
case CHAR_TEX_GRATE:
sprintf (sound_path, "player/pl_grate%d.wav", RANDOM_LONG (1, 4)); // 4 grate sounds
volume = 0.9;
break;
case CHAR_TEX_TILE:
sprintf (sound_path, "player/pl_tile%d.wav", RANDOM_LONG (1, 5)); // 5 tile sounds
volume = 0.8;
break;
case CHAR_TEX_SLOSH:
sprintf (sound_path, "player/pl_slosh%d.wav", RANDOM_LONG (1, 4)); // 4 slosh sounds
volume = 0.9;
break;
case CHAR_TEX_WOOD:
sprintf (sound_path, "debris/wood%d.wav", RANDOM_LONG (1, 3)); // 3 wood sounds
volume = 0.9;
break;
case CHAR_TEX_GLASS:
case CHAR_TEX_COMPUTER:
sprintf (sound_path, "debris/glass%d.wav", RANDOM_LONG (1, 4)); // 4 glass sounds
volume = 0.8;
break;
case 'N':
sprintf (sound_path, "player/pl_snow%d.wav", RANDOM_LONG (1, 6)); // 6 snow sounds
volume = 0.8;
break;
}
// did we hit a breakable ?
if (!FNullEnt (pPlayer->v.groundentity)
&& (strcmp ("func_breakable", STRING (pPlayer->v.groundentity->v.classname)) == 0))
volume /= 1.5; // drop volume, the object will already play a damaged sound
// bring footstep sound from this player's feet to the bots' ears
DispatchSound (sound_path, pPlayer->v.origin + Vector (0, 0, -18), volume, ATTN_NORM);
players[player_index].step_sound_time = *server.time + 0.3; // next step in 300 milliseconds
}
}
// is this player completely in water AND it's time to play a wade sound
// AND this player is pressing the jump key for swimming up ?
if ((players[player_index].step_sound_time < *server.time)
&& (pPlayer->v.waterlevel == 2) && (pPlayer->v.button & IN_JUMP))
{
sprintf (sound_path, "player/pl_wade%d.wav", RANDOM_LONG (1, 4)); // build a wade sound path
// bring wade sound from this player to the bots' ears
DispatchSound (sound_path, pPlayer->v.origin + Vector (0, 0, -18), 0.9, ATTN_NORM);
players[player_index].step_sound_time = *server.time + 0.5; // next wade in 500 milliseconds
}
// now let's see if this player is on a ladder, for that we consider that he's not on the
// ground, he's actually got a velocity (especially vertical), and that he's got a
// func_ladder entity right in front of him. Is that player moving anormally NOT on ground ?
if ((pPlayer->v.velocity.z != 0) && IsFlying (pPlayer) && !IsOnFloor (pPlayer))
{
pGroundEntity = NULL; // first ensure the pointer at which to start the search is NULL
// cycle through all ladders...
while ((pGroundEntity = UTIL_FindEntityByString (pGroundEntity, "classname", "func_ladder")) != NULL)
{
// is this ladder at the same height as the player AND the player is next to it (in
// which case, assume he's climbing it), AND it's time for him to emit ladder sound ?
if ((pGroundEntity->v.absmin.z < pPlayer->v.origin.z) && (pGroundEntity->v.absmax.z > pPlayer->v.origin.z)
&& (((pGroundEntity->v.absmin + pGroundEntity->v.absmax) / 2 - pPlayer->v.origin).Length2D () < 40)
&& (players[player_index].step_sound_time < *server.time))
{
volume = 0.8; // default volume for ladder sounds (empirical)
// now build a random sound path amongst the 4 different ladder sounds
sprintf (sound_path, "player/pl_ladder%d.wav", RANDOM_LONG (1, 4));
// is the player ducking ?
if (pPlayer->v.button & IN_DUCK)
volume /= 1.5; // drop volume, the player is trying to climb silently
// bring ladder sound from this player's feet to the bots' ears
DispatchSound (sound_path, pPlayer->v.origin + Vector (0, 0, -18), volume, ATTN_NORM);
players[player_index].step_sound_time = *server.time + 0.500; // next in 500 milliseconds
}
}
}
// and now let's see if this player is pulling the pin of a grenade...
if ((pPlayer->v.button & IN_ATTACK) && !(pPlayer->v.oldbuttons & IN_ATTACK)
&& ((strcmp (player_weapon, "flashbang.mdl") == 0)
|| (strcmp (player_weapon, "hegrenade.mdl") == 0)
|| (strcmp (player_weapon, "smokegrenade.mdl") == 0)))
DispatchSound ("weapons/pinpull.wav", GetGunPosition (pPlayer), 1.0, ATTN_NORM);
return;
}
Code:
void PlayBulletSoundsForBots (edict_t *pPlayer)
{
// this function is in charge of emulating the gunshot sounds for the bots. Since these sounds
// are only predicted by the client, and bots have no client DLL, obviously we have to do the
// work for them. We consider a client is told gunshot sound occurs when he receives the
// msg_CurWeapon network message, which gets sent whenever a player is lowering his ammo.
// That's why we hook those messages in MessageBegin(), and send the entity responsible of
// it have a walk around here. Given the weapon this player is holding in his hand then, the
// appropriate gunshot sound is played, amongst all the sounds that are listed in the
// weaponsounds.cfg file. Then DispatchSound() is called to bring the selected sound to the
// bot's ears.
weapon_t *pPlayerWeapon;
const char *texture_name;
char texture_type;
int player_index, sound_index;
Vector v_gun_position;
player_index = ENTINDEX (pPlayer) - 1; // get the player index
if (!IsValidPlayer (pPlayer) || !players[player_index].is_alive)
return; // skip invalid and dead players
if (DebugLevel.is_observer && !players[player_index].is_racc_bot)
return; // skip real players if in observer mode
if (!(pPlayer->v.button & (IN_ATTACK | IN_ATTACK2)))
return; // cancel if player is not firing
if (STRING (pPlayer->v.weaponmodel)[0] == 0)
return; // cancel if player has no weapon
pPlayerWeapon = FindWeaponByModel (STRING (pPlayer->v.weaponmodel)); // get player's weapon
if (pPlayerWeapon->id == 0)
return; // cancel if player has no weapon
v_gun_position = GetGunPosition (pPlayer); // get this player's gun position
// now select the sound according to rail (primary or secondary) and mode
if (pPlayer->v.button & IN_ATTACK)
{
// primary rail
if (pPlayer->v.weaponanim == 0)
DispatchSound (pPlayerWeapon->primary.sound1, v_gun_position, 1.0, ATTN_NORM);
else
DispatchSound (pPlayerWeapon->primary.sound2, v_gun_position, 1.0, ATTN_NORM);
}
else
{
// secondary rail
if (pPlayer->v.weaponanim == 0)
DispatchSound (pPlayerWeapon->secondary.sound1, v_gun_position, 1.0, ATTN_NORM);
else
DispatchSound (pPlayerWeapon->secondary.sound2, v_gun_position, 1.0, ATTN_NORM);
}
// do we have to worry about ricochet sounds ?
if (ricochetsound_count > 0)
{
// did this player's last traceline hit something AND it is not a player ?
if ((players[player_index].tr.flFraction < 1.0) && !FNullEnt (players[player_index].tr.pHit)
&& !(players[player_index].tr.pHit->v.flags & (FL_MONSTER | FL_CLIENT)))
{
// ask the engine for the texture name at the bullet hit point
texture_name = TRACE_TEXTURE (players[player_index].tr.pHit, v_gun_position, players[player_index].tr.vecEndPos);
// if the engine found the texture, ask the MOD DLL for the texture type
if (texture_name != NULL)
texture_type = PM_FindTextureType ((char *) texture_name); // ask for texture type
// loop through all the ricochet sounds the bot knows until we find the right one
for (sound_index = 0; sound_index < ricochetsound_count; sound_index++)
{
// is it this texture type the bullet just hit OR have we reached the default sound ?
if ((texture_type == ricochetsounds[sound_index].texture_type)
|| (ricochetsounds[sound_index].texture_type == '*'))
break; // then no need to search further
}
// bring this ricochet sound to the bots' ears
DispatchSound (ricochetsounds[sound_index].file_path, players[player_index].tr.vecEndPos, 0.9, ATTN_NORM);
}
}
return;
}
|