Add internal support for damage types. still a few more useful ones to add

on a per-case basis.
This commit is contained in:
Marco Cawthorne 2019-09-29 01:54:29 +02:00
parent 6fea65a586
commit 32b7791b2b
42 changed files with 143 additions and 64 deletions

View File

@ -7,5 +7,6 @@ client/env_sound.cpp
client/sky_camera.cpp
server/func_ladder.cpp
server/trigger_gravity.cpp
client/point_message.cpp
client/worldspawn.cpp
#endlist

View File

@ -0,0 +1,49 @@
/***
*
* Copyright (c) 2016-2019 Marco 'eukara' Hladik. All rights reserved.
*
* See the file LICENSE attached with the sources for usage details.
*
****/
/*QUAKED point_message (1 0 0) (-8 -8 -8) (8 8 8)
"message" The message to display.
"radius" The radius in which it will appear.
Client-side overlay/message that is projected in relation to its position
in 3D space.
Used for zoo and test maps in which less interactive overlays are desired.
*/
class point_message:CBaseEntity
{
float radius;
string message;
void() point_message;
virtual void(string, string) SpawnKey;
};
void point_message::SpawnKey(string strField, string strKey)
{
switch (strField) {
case "radius":
radius = stof(strKey);
break;
case "message":
message = strKey;
break;
case "origin":
origin = stov( strKey );
setorigin( this, origin );
break;
default:
CBaseEntity::SpawnKey(strField, strKey);
}
}
void point_message::point_message(void)
{
radius = 512;
message = "No message";
Init();
}

View File

@ -16,17 +16,6 @@
//#define GS_DEVELOPER
.float gflags;
enumflags
{
GF_CANRESPAWN,
GF_USE_RELEASED,
GF_IN_VEHICLE,
GF_FROZEN,
GF_SEMI_TOGGLED
};
void Effect_CreateSpark(vector, vector);
void Effect_BreakModel(int, vector, vector, vector, float);

View File

@ -60,7 +60,7 @@ void env_explosion::Trigger(void)
Effect_CreateExplosion(origin);
if (!(spawnflags & ENVEXPLO_NODAMAGE)) {
Damage_Radius(origin, this, m_iMagnitude, m_iMagnitude * 2.5f, TRUE);
Damage_Radius(origin, this, m_iMagnitude, m_iMagnitude * 2.5f, TRUE, 0);
}
// TODO: Respawn after round instead?

View File

@ -106,7 +106,7 @@ void func_breakable::Explode(void)
vWorldPos[2] = absmin[2] + ( 0.5 * ( absmax[2] - absmin[2] ) );
Effect_BreakModel(20, absmin, absmax, '0 0 0', m_iMaterial);
Effect_CreateExplosion(vWorldPos);
Damage_Radius(vWorldPos, this, m_flExplodeMag, m_flExplodeMag * 2.5f, TRUE);
Damage_Radius(vWorldPos, this, m_flExplodeMag, m_flExplodeMag * 2.5f, TRUE, 0);
CBaseTrigger::UseTargets();
CBaseEntity::Hide();
}
@ -157,10 +157,10 @@ void func_breakable::PlayerTouch(void)
if (fDamage >= health) {
touch = __NULL__;
Damage_Apply(this, other, fDamage, absmin, FALSE, 0);
Damage_Apply(this, other, fDamage, 0, DMG_CRUSH);
if ((m_iMaterial == MATERIAL_GLASS) || (m_iMaterial == MATERIAL_COMPUTER)) {
Damage_Apply(other, this, fDamage / 4, other.origin, FALSE, 0);
Damage_Apply(other, this, fDamage / 4, 0, DMG_CRUSH);
}
}
}

View File

@ -238,7 +238,7 @@ void func_door_rotating::Touch(void)
void func_door_rotating::Blocked(void)
{
if (m_iDamage) {
Damage_Apply(other, this, m_iDamage, other.origin, FALSE, 0);
Damage_Apply(other, this, m_iDamage, 0, DMG_CRUSH);
}
if (m_flWait >= 0) {

View File

@ -72,7 +72,7 @@ void func_rotating :: Blocked ( void )
}
if (other.takedamage == DAMAGE_YES) {
Damage_Apply(other, this, m_flDamage, other.origin, TRUE, 0);
Damage_Apply(other, this, m_flDamage, 0, DMG_CRUSH);
}
}

View File

@ -89,7 +89,7 @@ class func_train:CBaseTrigger
void
func_train::Blocked(void)
{
Damage_Apply(other, this, m_flDamage, other.origin, TRUE, 0);
Damage_Apply(other, this, m_flDamage, 0, DMG_CRUSH);
}
void

View File

@ -41,7 +41,7 @@ void item_food :: Touch ( void )
bevOwner.m_iReady = TRUE;
}
Damage_Apply(other, this, -1, other.origin, FALSE, 0);
Damage_Apply(other, this, -1, 0, DMG_GENERIC);
solid = SOLID_NOT;
remove( this );
}

View File

@ -94,7 +94,7 @@ void trigger_hurt::Touch(void)
}
}
Damage_Apply(other, this, m_iDamage, other.origin, FALSE, 0);
Damage_Apply(other, this, m_iDamage, 0, DMG_GENERIC);
// Shut it down if used once
if (spawnflags & SF_HURT_ONCE) {

View File

@ -25,8 +25,8 @@ void Effect_GibHuman( vector vPos);
void Footsteps_Update( void );
void Vox_Broadcast(string sMessage);
void TraceAttack_FireBullets(int , vector, int, vector, int);
void Damage_Radius( vector vOrigin, entity eAttacker, float fDamage, float fRadius, int iCheckClip );
void Damage_Apply( entity, entity, float, vector, int, int);
void Damage_Radius( vector, entity, float, float, int, int);
void Damage_Apply( entity, entity, float, int, int);
void Client_TriggerCamera( entity eTarget, vector vPos, vector vEndPos, float fResetTime );
void Game_Input(void);
@ -46,3 +46,14 @@ entity eActivator;
string startspot;
string __fullspawndata;
hashtable hashMaterials;
.float gflags;
enumflags
{
GF_CANRESPAWN,
GF_USE_RELEASED,
GF_IN_VEHICLE,
GF_FROZEN,
GF_SEMI_TOGGLED
};

View File

@ -58,7 +58,7 @@ Game_ClientDisconnect(void)
void
Game_ClientKill(void)
{
Damage_Apply(self, self, self.health, self.origin, TRUE, 0);
Damage_Apply(self, self, self.health, 0, DMG_SKIP_ARMOR);
}
void

View File

@ -58,7 +58,7 @@ Game_ClientDisconnect(void)
void
Game_ClientKill(void)
{
Damage_Apply(self, self, self.health, self.origin, TRUE, 0);
Damage_Apply(self, self, self.health, 0, DMG_SKIP_ARMOR);
}
void

View File

@ -70,7 +70,7 @@ TraceAttack_FireSingle(vector vPos, vector vAngle, int iDamage, int iWeapon)
iDamage *= 3;
}
#endif
Damage_Apply(trace_ent, self, iDamage, trace_endpos, FALSE, iWeapon);
Damage_Apply(trace_ent, self, iDamage, iWeapon, DMG_BULLET);
}
if (trace_ent.iBleeds == TRUE) {

View File

@ -54,7 +54,7 @@ Game_ClientDisconnect(void)
void
Game_ClientKill(void)
{
Damage_Apply(self, self, self.health, self.origin, TRUE, WEAPON_NONE);
Damage_Apply(self, self, self.health, WEAPON_NONE, DMG_SKIP_ARMOR);
}
void

View File

@ -30,14 +30,14 @@ Damage_Obituary(entity c, entity t, float weapon, float flags)
/* generic function that applies damage, pain and suffering */
void
Damage_Apply(entity t, entity c, float dmg, vector pos, int a, int w)
Damage_Apply(entity t, entity c, float dmg, int w, int type)
{
if (t.flags & FL_GODMODE) {
return;
}
/* skip armor */
if (!a)
if not (type & DMG_SKIP_ARMOR)
if (t.armor && dmg > 0) {
float flArmor;
float flNewDamage;
@ -131,7 +131,7 @@ Damage_CheckTrace(entity t, vector vecHitPos)
/* even more pain and suffering, mostly used for explosives */
void
Damage_Radius(vector org, entity attacker, float dmg, float radius, int check)
Damage_Radius(vector org, entity attacker, float dmg, float r, int check, int w)
{
float new_dmg;
float dist;
@ -145,7 +145,7 @@ Damage_Radius(vector org, entity attacker, float dmg, float radius, int check)
/* don't bother if it's not anywhere near us */
dist = vlen(org - pos);
if (dist > radius) {
if (dist > r) {
continue;
}
@ -158,11 +158,11 @@ Damage_Radius(vector org, entity attacker, float dmg, float radius, int check)
/* calculate new damage values */
diff = vlen(org - pos);
diff = (radius - diff) / radius;
diff = (r - diff) / r;
new_dmg = rint(dmg * diff);
if (diff > 0) {
Damage_Apply(e, attacker, new_dmg, pos, FALSE, 0);
Damage_Apply(e, attacker, new_dmg, w, DMG_EXPLODE);
/* approximate, feel free to tweak */
if (e.movetype == MOVETYPE_WALK) {

View File

@ -14,4 +14,4 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void Damage_Apply(entity, entity, float, vector, int, int);
void Damage_Apply(entity, entity, float, int, int);

View File

@ -30,7 +30,7 @@ void item_healthkit::touch(void)
if (other.health >= other.max_health) {
return;
}
Damage_Apply(other, this, -20, this.origin, TRUE, 0);
Damage_Apply(other, this, -20, 0, DMG_GENERIC);
sound(this, CHAN_ITEM, "items/smallmedkit1.wav", 1, ATTN_NORM);
Logging_Pickup(other, this, __NULL__);

View File

@ -80,6 +80,35 @@ enum {
BODY_LEGRIGHT
};
enumflags
{
DMG_GENERIC,
DMG_CRUSH,
DMG_BULLET,
DMG_SLASH,
DMG_BURN,
DMG_VEHICLE,
DMG_FALL,
DMG_EXPLODE,
DMG_BLUNT,
DMG_ELECTRO,
DMG_SOUND,
DMG_ENERGYBEAM,
DMG_GIB_NEVER,
DMG_GIB_ALWAYS,
DMG_DROWN,
DMG_PARALYZE,
DMG_NERVEGAS,
DMG_POISON,
DMG_RADIATION,
DMG_DROWNRECOVER,
DMG_ACID,
DMG_SLOWBURN,
DMG_SLOWFREEZE,
DMG_SKIP_ARMOR,
DMG_SKIP_RAGDOLL
};
#define clamp(d,min,max) bound(min,d,max)
.float jumptime;

View File

@ -131,7 +131,7 @@ w_displacer_fireball(void)
if (other.flags & FL_CLIENT) {
w_displacer_teleport(other);
}
Damage_Radius(self.origin, self.owner, 250, 250 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 250, 250 * 2.5f, TRUE, WEAPON_DISPLACER);
sound(self, 1, "weapons/displacer_impact.wav", 1, ATTN_NORM);
#endif
remove(self);

View File

@ -147,7 +147,7 @@ w_grapple_primary(void)
vector src = Weapons_GetCameraPos();
traceline(src, src + (v_forward * 32), FALSE, pl);
if (trace_ent.takedamage == DAMAGE_YES && trace_ent.iBleeds) {
Damage_Apply(trace_ent, pl, 25, trace_endpos, FALSE, WEAPON_GRAPPLE);
Damage_Apply(trace_ent, pl, 25, WEAPON_GRAPPLE, DMG_GENERIC);
}
#endif
pl.w_attack_next = 0.5f;

View File

@ -150,7 +150,7 @@ w_knife_primary(void)
}
if (trace_ent.takedamage) {
Damage_Apply(trace_ent, self, 10, trace_endpos, FALSE, WEAPON_KNIFE);
Damage_Apply(trace_ent, self, 10, WEAPON_KNIFE, DMG_SLASH);
if (!trace_ent.iBleeds) {
return;

View File

@ -107,7 +107,7 @@ penguin_ai(void)
self.weapon = 0.5f + random();
penguin_squeak(self);
input_buttons = 2;
Damage_Apply(self, world, 1, self.origin, TRUE, 0);
Damage_Apply(self, world, 1, 0, DMG_GENERIC);
makevectors(self.angles);
traceline(self.origin, self.origin + (v_forward * 128), 0, self);
@ -115,7 +115,7 @@ penguin_ai(void)
if (trace_ent.takedamage == DAMAGE_YES) {
float pit = 100 + random(0,10);
sound(self, CHAN_BODY, "squeek/sqk_deploy1.wav", 1.0, ATTN_NORM, pit);
Damage_Apply(trace_ent, self.goalentity, 10, trace_endpos, FALSE, WEAPON_PENGUIN);
Damage_Apply(trace_ent, self.goalentity, 10, WEAPON_PENGUIN, DMG_GENERIC);
}
if (self.aiment.health <= 0) {
@ -135,7 +135,7 @@ penguin_die(int i)
/* now we can explodededededed */
Effect_CreateExplosion(self.origin);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE, WEAPON_PENGUIN);
if (random() < 0.5) {
sound(self, 1, "weapons/explode3.wav", 1.0f, ATTN_NORM);

View File

@ -148,7 +148,7 @@ w_pipewrench_primary(void)
}
if (trace_ent.takedamage) {
Damage_Apply(trace_ent, self, 10, trace_endpos, FALSE, WEAPON_PIPEWRENCH);
Damage_Apply(trace_ent, self, 10, WEAPON_PIPEWRENCH, DMG_BLUNT);
if (!trace_ent.iBleeds) {
return;
@ -212,7 +212,7 @@ w_pipewrench_release(void)
if (trace_ent.takedamage == DAMAGE_YES) {
hitsound = floor(random(1, 2));
/* TODO Damage is 45 - 200+ (?) */
Damage_Apply(trace_ent, pl, 200, trace_endpos, FALSE, WEAPON_PIPEWRENCH);
Damage_Apply(trace_ent, pl, 200, WEAPON_PIPEWRENCH, DMG_BLUNT);
} else {
hitsound = 3;
}

View File

@ -109,7 +109,7 @@ w_shockrifle_shoothornet(void)
{
static void Hornet_Touch(void) {
if (other.takedamage == DAMAGE_YES) {
Damage_Apply(other, self.owner, 10, trace_endpos, FALSE, WEAPON_SHOCKRIFLE);
Damage_Apply(other, self.owner, 10, WEAPON_SHOCKRIFLE, DMG_ELECTRO);
}
if (other.iBleeds) {

View File

@ -131,7 +131,7 @@ w_sporelauncher_primary(void)
string hitsnd;
if (other.takedamage == DAMAGE_YES) {
Damage_Apply(other, self.owner, 50, trace_endpos, FALSE, WEAPON_SPORELAUNCHER);
Damage_Apply(other, self.owner, 50, WEAPON_SPORELAUNCHER, DMG_GENERIC);
}
r = floor(random(0,3));

View File

@ -106,7 +106,7 @@ void QPhysics_Run ( entity eTarget )
if ( ( self.flags & FL_ONGROUND ) && self.movetype == MOVETYPE_WALK && ( flFallVel > 580 )) {
float fFallDamage = ( flFallVel - 580 ) * ( 100 / ( 1024 - 580 ) );
Damage_Apply( self, world, fFallDamage, self.origin, FALSE, 0);
Damage_Apply( self, world, fFallDamage, 0, DMG_FALL);
sound(self, CHAN_AUTO, "player/pl_fallpain3.wav", 1.0, ATTN_NORM);
}
#endif

View File

@ -383,7 +383,7 @@ weapon_t w_chemicalgun =
/* entity definitions for pickups */
#ifdef SSQC
void
weapon_chemgun(void)
weapon_SPchemicalgun(void)
{
Weapons_InitItem(WEAPON_CHEMICALGUN);
}

View File

@ -136,7 +136,7 @@ w_crowbar_primary(void)
}
if (trace_ent.takedamage) {
Damage_Apply(trace_ent, self, 10, trace_endpos, FALSE, WEAPON_CROWBAR);
Damage_Apply(trace_ent, self, 10, WEAPON_CROWBAR, DMG_BLUNT);
if (!trace_ent.iBleeds) {
return;

View File

@ -139,7 +139,7 @@ w_umbrella_primary(void)
}
if (trace_ent.takedamage) {
Damage_Apply(trace_ent, self, 10, trace_endpos, FALSE, WEAPON_UMBRELLA);
Damage_Apply(trace_ent, self, 10, WEAPON_UMBRELLA, DMG_BLUNT);
if (!trace_ent.iBleeds) {
return;

View File

@ -129,7 +129,7 @@ w_wrench_primary(void)
}
if (trace_ent.takedamage) {
Damage_Apply(trace_ent, self, 10, trace_endpos, FALSE, WEAPON_WRENCH);
Damage_Apply(trace_ent, self, 10, WEAPON_WRENCH, DMG_BLUNT);
if (!trace_ent.iBleeds) {
return;

View File

@ -111,7 +111,7 @@ void Crossbolt_Touch(void) {
/* explode mode, multiplayer */
if (self.weapon) {
Effect_CreateExplosion(self.origin);
Damage_Radius(self.origin, self.owner, 50, 50 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 50, 50 * 2.5f, TRUE, WEAPON_CROSSBOW);
if (random() < 0.5) {
sound(self, 1, "weapons/explode3.wav", 1.0f, ATTN_NORM);
} else {
@ -130,7 +130,7 @@ void Crossbolt_Touch(void) {
}
/* anything else that can take damage */
Damage_Apply(other, self.owner, 50, trace_endpos, FALSE, WEAPON_CROSSBOW);
Damage_Apply(other, self.owner, 50, WEAPON_CROSSBOW, DMG_BLUNT);
if (random() < 0.5) {
sound(self, 1, "weapons/xbow_hitbod1.wav", 1.0f, ATTN_NORM);
} else {

View File

@ -136,7 +136,7 @@ w_crowbar_primary(void)
}
if (trace_ent.takedamage) {
Damage_Apply(trace_ent, pl, 10, trace_endpos, FALSE, WEAPON_CROWBAR);
Damage_Apply(trace_ent, pl, 10, WEAPON_CROWBAR, DMG_BLUNT);
if (!trace_ent.iBleeds) {
return;

View File

@ -110,7 +110,7 @@ void w_egon_primary(void)
vector src = Weapons_GetCameraPos();
vector endpos = src + v_forward * 1024;
traceline(src, endpos, FALSE, pl);
Damage_Radius(trace_endpos, pl, 14, 64, TRUE);
Damage_Radius(trace_endpos, pl, 14, 64, TRUE, DMG_ELECTRO);
pl.ammo_uranium--;
Weapons_UpdateAmmo(pl, __NULL__, pl.ammo_uranium, __NULL__);
#endif

View File

@ -158,7 +158,7 @@ void w_gauss_fire(int one)
}
if (trace_ent.takedamage == DAMAGE_YES) {
Damage_Apply(trace_ent, self, iDamage, trace_endpos, FALSE, WEAPON_GAUSS);
Damage_Apply(trace_ent, self, iDamage, WEAPON_GAUSS, DMG_ELECTRO);
sound(trace_ent, CHAN_ITEM, sprintf("weapons/electro%d.wav", random(0,3)+4), 1, ATTN_NORM);
}
#else
@ -196,7 +196,7 @@ void w_gauss_fire(int one)
iLoop--;
#ifdef SSQC
if (trace_ent.takedamage == DAMAGE_YES) {
Damage_Apply(trace_ent, self, iDamage, trace_endpos, FALSE, WEAPON_GAUSS);
Damage_Apply(trace_ent, self, iDamage, WEAPON_GAUSS, DMG_ELECTRO);
sound(trace_ent, CHAN_ITEM, sprintf("weapons/electro%d.wav", random(0,3)+4), 1, ATTN_NORM);
}

View File

@ -76,7 +76,7 @@ void w_handgrenade_throw(void)
static void WeaponFrag_Throw_Explode( void )
{
Effect_CreateExplosion(self.origin);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE, WEAPON_HANDGRENADE);
sound(self, CHAN_WEAPON, sprintf( "weapons/explode%d.wav", floor( random() * 2 ) + 3 ), 1, ATTN_NORM);
remove(self);
}
@ -84,7 +84,7 @@ void w_handgrenade_throw(void)
static void WeaponFrag_Throw_Touch( void )
{
if (other.takedamage == DAMAGE_YES) {
Damage_Apply(other, self.owner, 15, self.origin, FALSE, WEAPON_HANDGRENADE);
Damage_Apply(other, self.owner, 15, WEAPON_HANDGRENADE, DMG_BLUNT);
}
int r = floor(random(0,6));
string sample = sprintf("weapons/g_bounce%i.wav", r);

View File

@ -95,7 +95,7 @@ w_hornetgun_shoothornet(void)
{
static void Hornet_Touch(void) {
if (other.takedamage == DAMAGE_YES) {
Damage_Apply(other, self.owner, 10, trace_endpos, FALSE, WEAPON_HORNETGUN);
Damage_Apply(other, self.owner, 10, WEAPON_HORNETGUN, DMG_GENERIC);
}
remove(self);
}

View File

@ -174,7 +174,7 @@ w_mp5_secondary(void)
#else
static void Grenade_ExplodeTouch(void) {
Effect_CreateExplosion(self.origin);
Damage_Radius(self.origin, self.owner, 100, 100 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 100, 100 * 2.5f, TRUE, WEAPON_MP5);
if (random() < 0.5) {
sound(self, 1, "weapons/explode3.wav", 1, ATTN_NORM);

View File

@ -117,7 +117,7 @@ void w_rpg_primary(void)
#else
static void Rocket_Touch(void) {
Effect_CreateExplosion(self.origin);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE, WEAPON_RPG);
sound(self, CHAN_WEAPON, sprintf( "weapons/explode%d.wav", floor( random() * 2 ) + 3 ), 1, ATTN_NORM);
remove(self);
}

View File

@ -109,7 +109,7 @@ void s_satchel_detonate(entity master)
for (entity b = world; (b = find(b, ::classname, "satchel"));) {
if (b.owner == master) {
Effect_CreateExplosion(b.origin);
Damage_Radius(b.origin, master, 150, 150 * 2.5f, TRUE);
Damage_Radius(b.origin, master, 150, 150 * 2.5f, TRUE, WEAPON_SATCHEL);
sound(b, CHAN_WEAPON, sprintf( "weapons/explode%d.wav", floor( random() * 2 ) + 3 ), 1, ATTN_NORM);
remove(b);
}

View File

@ -88,7 +88,7 @@ void w_snark_deploy(void)
self.weapon = 0.5f + random();
sound(self, CHAN_VOICE, sprintf("squeek/sqk_hunt%d.wav",floor(random(1,4))), 1.0, ATTN_NORM);
input_buttons = 2;
Damage_Apply(self, world, 1, self.origin, TRUE, 0);
Damage_Apply(self, world, 1, 0, DMG_GENERIC);
makevectors(self.angles);
traceline(self.origin, self.origin + (v_forward * 128), 0, self);
@ -96,7 +96,7 @@ void w_snark_deploy(void)
if (trace_ent.takedamage == DAMAGE_YES) {
float pit = 100 + random(0,10);
sound(self, CHAN_BODY, "squeek/sqk_deploy1.wav", 1.0, ATTN_NORM, pit);
Damage_Apply(trace_ent, self.goalentity, 10, trace_endpos, FALSE, WEAPON_SNARK);
Damage_Apply(trace_ent, self.goalentity, 10, WEAPON_SNARK, DMG_GENERIC);
}
if (self.aiment.health <= 0) {

View File

@ -88,7 +88,7 @@ void w_tripmine_trip(int unused)
self.takedamage = DAMAGE_NO;
Effect_CreateExplosion(self.origin);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE);
Damage_Radius(self.origin, self.owner, 150, 150 * 2.5f, TRUE, WEAPON_TRIPMINE);
sound(self, CHAN_WEAPON, sprintf( "weapons/explode%d.wav", floor( random() * 2 ) + 3 ), 1, ATTN_NORM);
remove(self);
}