]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/electro.qc
79cfa5ebf9fe124988afddc909cb0f4ba5a7a1aa
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / electro.qc
1 #include "electro.qh"
2
3 #ifdef SVQC
4 #include <common/effects/qc/_mod.qh>
5
6 void W_Electro_TriggerCombo(vector org, float rad, entity own)
7 {
8         entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(electro, combo_comboradius_thruwall));
9         while(e)
10         {
11                 if(e.classname == "electro_orb")
12                 {
13                         // do we allow thruwall triggering?
14                         if(WEP_CVAR(electro, combo_comboradius_thruwall))
15                         {
16                                 // if distance is greater than thruwall distance, check to make sure it's not through a wall
17                                 if(vdist(e.WarpZone_findradius_dist, >, WEP_CVAR(electro, combo_comboradius_thruwall)))
18                                 {
19                                         WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e);
20                                         if(trace_fraction != 1)
21                                         {
22                                                 // trigger is through a wall and outside of thruwall range, abort
23                                                 e = e.chain;
24                                                 continue;
25                                         }
26                                 }
27                         }
28
29                         // change owner to whoever caused the combo explosion
30                         e.realowner = own;
31                         e.takedamage = DAMAGE_NO;
32                         e.classname = "electro_orb_chain";
33
34                         // now set the next one to trigger as well
35                         setthink(e, W_Electro_ExplodeCombo);
36
37                         // delay combo chains, looks cooler
38                         e.nextthink =
39                                 (
40                                         time
41                                         +
42                                         (WEP_CVAR(electro, combo_speed) ?
43                                                 (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed))
44                                                 :
45                                                 0
46                                         )
47                                 );
48                 }
49                 e = e.chain;
50         }
51 }
52
53 void W_Electro_ExplodeCombo(entity this)
54 {
55         W_Electro_TriggerCombo(this.origin, WEP_CVAR(electro, combo_comboradius), this.realowner);
56
57         this.event_damage = func_null;
58
59         RadiusDamage(
60                 this,
61                 this.realowner,
62                 WEP_CVAR(electro, combo_damage),
63                 WEP_CVAR(electro, combo_edgedamage),
64                 WEP_CVAR(electro, combo_radius),
65                 NULL,
66                 NULL,
67                 WEP_CVAR(electro, combo_force),
68                 WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
69                 this.weaponentity_fld,
70                 NULL
71         );
72
73         delete(this);
74 }
75
76 void W_Electro_Explode(entity this, entity directhitentity)
77 {
78         if(directhitentity.takedamage == DAMAGE_AIM)
79                 if(IS_PLAYER(directhitentity))
80                         if(DIFF_TEAM(this.realowner, directhitentity))
81                                 if(!IS_DEAD(directhitentity))
82                                         if(IsFlying(directhitentity))
83                                                 Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
84
85         this.event_damage = func_null;
86         this.takedamage = DAMAGE_NO;
87
88         if(this.move_movetype == MOVETYPE_BOUNCE || this.classname == "electro_orb") // TODO: classname is more reliable anyway?
89         {
90                 RadiusDamage(
91                         this,
92                         this.realowner,
93                         WEP_CVAR_SEC(electro, damage),
94                         WEP_CVAR_SEC(electro, edgedamage),
95                         WEP_CVAR_SEC(electro, radius),
96                         NULL,
97                         NULL,
98                         WEP_CVAR_SEC(electro, force),
99                         this.projectiledeathtype,
100                         this.weaponentity_fld,
101                         directhitentity
102                 );
103         }
104         else
105         {
106                 W_Electro_TriggerCombo(this.origin, WEP_CVAR_PRI(electro, comboradius), this.realowner);
107                 RadiusDamage(
108                         this,
109                         this.realowner,
110                         WEP_CVAR_PRI(electro, damage),
111                         WEP_CVAR_PRI(electro, edgedamage),
112                         WEP_CVAR_PRI(electro, radius),
113                         NULL,
114                         NULL,
115                         WEP_CVAR_PRI(electro, force),
116                         this.projectiledeathtype,
117                         this.weaponentity_fld,
118                         directhitentity
119                 );
120         }
121
122         delete(this);
123 }
124
125 void W_Electro_Explode_use(entity this, entity actor, entity trigger)
126 {
127         W_Electro_Explode(this, trigger);
128 }
129
130 void W_Electro_TouchExplode(entity this, entity toucher)
131 {
132         PROJECTILE_TOUCH(this, toucher);
133         W_Electro_Explode(this, toucher);
134 }
135
136
137 void sys_phys_update_single(entity this);
138
139 void W_Electro_Bolt_Think(entity this)
140 {
141         // sys_phys_update_single(this);
142         if(time >= this.ltime)
143         {
144                 this.use(this, NULL, NULL);
145                 return;
146         }
147
148         if(WEP_CVAR_PRI(electro, midaircombo_radius))
149         {
150                 float found = 0;
151                 entity e = WarpZone_FindRadius(this.origin, WEP_CVAR_PRI(electro, midaircombo_radius), true);
152
153                 // loop through nearby orbs and trigger them
154                 while(e)
155                 {
156                         if(e.classname == "electro_orb")
157                         {
158                                 bool explode;
159                                 if (this.owner == e.owner)
160                                 {
161                                         explode = WEP_CVAR_PRI(electro, midaircombo_own);
162                                 }
163                                 else if (SAME_TEAM(this.owner, e.owner))
164                                 {
165                                         explode = WEP_CVAR_PRI(electro, midaircombo_teammate);
166                                 }
167                                 else
168                                 {
169                                         explode = WEP_CVAR_PRI(electro, midaircombo_enemy);
170                                 }
171
172                                 if (explode)
173                                 {
174                                         // change owner to whoever caused the combo explosion
175                                         e.realowner = this.realowner;
176                                         e.takedamage = DAMAGE_NO;
177                                         e.classname = "electro_orb_chain";
178
179                                         // Only first orb explosion uses midaircombo_speed, others use the normal combo_speed.
180                                         // This allows to avoid the delay on the first explosion which looks better
181                                         // (the bolt and orb should explode together because they interacted together)
182                                         // while keeping the chaining delay.
183                                         setthink(e, W_Electro_ExplodeCombo);
184                                         e.nextthink =
185                                         (
186                                                 time
187                                                 +
188                                                 (WEP_CVAR_PRI(electro, midaircombo_speed) ?
189                                                         (vlen(e.WarpZone_findradius_dist) / WEP_CVAR_PRI(electro, midaircombo_speed))
190                                                         :
191                                                         0
192                                                 )
193                                         );
194
195
196                                         ++found;
197                                 }
198                         }
199                         e = e.chain;
200                 }
201
202                 // if we triggered an orb, should we explode? if not, lets try again next time
203                 if(found && WEP_CVAR_PRI(electro, midaircombo_explode))
204                         { this.use(this, NULL, NULL); }
205                 else
206                         { this.nextthink = min(time + WEP_CVAR_PRI(electro, midaircombo_interval), this.ltime); }
207         }
208         else { this.nextthink = this.ltime; }
209         // this.nextthink = time;
210 }
211
212 void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
213 {
214         entity proj;
215
216         W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(electro, ammo), weaponentity);
217
218         W_SetupShot_ProjectileSize(
219                 actor,
220                 weaponentity,
221                 '0 0 -3',
222                 '0 0 -3',
223                 false,
224                 2,
225                 SND_ELECTRO_FIRE,
226                 CH_WEAPON_A,
227                 WEP_CVAR_PRI(electro, damage),
228                 thiswep.m_id
229         );
230
231         W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
232
233         proj = new(electro_bolt);
234         proj.owner = proj.realowner = actor;
235         proj.bot_dodge = true;
236         proj.bot_dodgerating = WEP_CVAR_PRI(electro, damage);
237         proj.use = W_Electro_Explode_use;
238         setthink(proj, W_Electro_Bolt_Think);
239         proj.nextthink = time;
240         proj.ltime = time + WEP_CVAR_PRI(electro, lifetime);
241         PROJECTILE_MAKETRIGGER(proj);
242         proj.projectiledeathtype = thiswep.m_id;
243         proj.weaponentity_fld = weaponentity;
244         setorigin(proj, w_shotorg);
245
246         // if (IS_CSQC)
247         set_movetype(proj, MOVETYPE_FLY);
248         W_SetupProjVelocity_PRI(proj, electro);
249         proj.angles = vectoangles(proj.velocity);
250         settouch(proj, W_Electro_TouchExplode);
251         setsize(proj, '0 0 -3', '0 0 -3');
252         proj.flags = FL_PROJECTILE;
253         IL_PUSH(g_projectiles, proj);
254         IL_PUSH(g_bot_dodge, proj);
255         proj.missile_flags = MIF_SPLASH;
256
257         CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
258
259         MUTATOR_CALLHOOK(EditProjectile, actor, proj);
260         // proj.com_phys_pos = proj.origin;
261         // proj.com_phys_vel = proj.velocity;
262 }
263
264 void W_Electro_Orb_Stick(entity this, entity to)
265 {
266         entity newproj = spawn();
267         newproj.classname = this.classname;
268
269         newproj.bot_dodge = this.bot_dodge;
270         newproj.bot_dodgerating = this.bot_dodgerating;
271
272         newproj.owner = this.owner;
273         newproj.realowner = this.realowner;
274         setsize(newproj, this.mins, this.maxs);
275         setorigin(newproj, this.origin);
276         setmodel(newproj, MDL_PROJECTILE_ELECTRO);
277         newproj.angles = vectoangles(-trace_plane_normal); // face against the surface
278
279         newproj.takedamage = this.takedamage;
280         newproj.damageforcescale = this.damageforcescale;
281         SetResourceExplicit(newproj, RES_HEALTH, GetResource(this, RES_HEALTH));
282         newproj.event_damage = this.event_damage;
283         newproj.spawnshieldtime = this.spawnshieldtime;
284         newproj.damagedbycontents = true;
285         IL_PUSH(g_damagedbycontents, newproj);
286
287         set_movetype(newproj, MOVETYPE_NONE); // lock the orb in place
288         newproj.projectiledeathtype = this.projectiledeathtype;
289         newproj.weaponentity_fld = this.weaponentity_fld;
290
291         settouch(newproj, func_null);
292         setthink(newproj, getthink(this));
293         newproj.nextthink = this.nextthink;
294         newproj.use = this.use;
295         newproj.flags = this.flags;
296         IL_PUSH(g_projectiles, newproj);
297         IL_PUSH(g_bot_dodge, newproj);
298
299         // check if limits are enabled (we can tell by checking if the original orb is listed) and push it to the list if so
300         if(IL_CONTAINS(LimitedElectroBallRubbleList, this))
301         {
302                 ReplaceOldListedChildRubble(LimitedElectroBallRubbleList, newproj, this);
303         }
304
305         delete(this);
306
307         if(to)
308                 SetMovetypeFollow(newproj, to);
309 }
310
311 void W_Electro_Orb_Touch(entity this, entity toucher)
312 {
313         PROJECTILE_TOUCH(this, toucher);
314         if(toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(electro, touchexplode))
315                 { W_Electro_Explode(this, toucher); }
316         else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
317         {
318                 //UpdateCSQCProjectile(this);
319                 spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
320                 this.projectiledeathtype |= HITTYPE_BOUNCE;
321
322                 if(WEP_CVAR_SEC(electro, stick))
323                         W_Electro_Orb_Stick(this, toucher);
324         }
325 }
326
327 void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
328 {
329         if(GetResource(this, RES_HEALTH) <= 0)
330                 return;
331
332         // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
333         float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
334
335         if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
336                 return; // g_projectiles_damage says to halt
337
338         TakeResource(this, RES_HEALTH, damage);
339         if(GetResource(this, RES_HEALTH) <= 0)
340         {
341                 this.takedamage = DAMAGE_NO;
342                 this.nextthink = time;
343                 if(is_combo)
344                 {
345                         // change owner to whoever caused the combo explosion
346                         this.realowner = inflictor.realowner;
347                         this.classname = "electro_orb_chain";
348                         setthink(this, W_Electro_ExplodeCombo);
349                         this.nextthink = time +
350                                 (
351                                         // bound the length, inflictor may be in a galaxy far far away (warpzones)
352                                         min(
353                                                 WEP_CVAR(electro, combo_radius),
354                                                 vlen(this.origin - inflictor.origin)
355                                         )
356                                         /
357                                         // delay combo chains, looks cooler
358                                         WEP_CVAR(electro, combo_speed)
359                                 );
360                 }
361                 else
362                 {
363                         this.use = W_Electro_Explode_use;
364                         setthink(this, adaptor_think2use); // not _hittype_splash, as this runs "immediately"
365                 }
366         }
367 }
368
369 void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
370 {
371         W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(electro, ammo), weaponentity);
372
373         W_SetupShot_ProjectileSize(
374                 actor,
375                 weaponentity,
376                 '-4 -4 -4',
377                 '4 4 4',
378                 false,
379                 2,
380                 SND_ELECTRO_FIRE2,
381                 CH_WEAPON_A,
382                 WEP_CVAR_SEC(electro, damage),
383                 thiswep.m_id | HITTYPE_SECONDARY
384         );
385
386         w_shotdir = v_forward; // no TrueAim for grenades please
387
388         W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
389
390         entity proj = new(electro_orb);
391         proj.owner = proj.realowner = actor;
392         proj.use = W_Electro_Explode_use;
393         setthink(proj, adaptor_think2use_hittype_splash);
394         proj.bot_dodge = true;
395         proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage);
396         proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime);
397         PROJECTILE_MAKETRIGGER(proj);
398         proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
399         proj.weaponentity_fld = weaponentity;
400         setorigin(proj, w_shotorg);
401
402         //proj.glow_size = 50;
403         //proj.glow_color = 45;
404         set_movetype(proj, MOVETYPE_BOUNCE);
405         W_SetupProjVelocity_UP_SEC(proj, electro);
406         settouch(proj, W_Electro_Orb_Touch);
407         setsize(proj, '-4 -4 -4', '4 4 4');
408         proj.takedamage = DAMAGE_YES;
409         proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
410         SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_SEC(electro, health));
411         proj.event_damage = W_Electro_Orb_Damage;
412         proj.flags = FL_PROJECTILE;
413         IL_PUSH(g_projectiles, proj);
414         IL_PUSH(g_bot_dodge, proj);
415         proj.damagedbycontents = (WEP_CVAR_SEC(electro, damagedbycontents));
416         if(proj.damagedbycontents)
417                 IL_PUSH(g_damagedbycontents, proj);
418
419         proj.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
420         proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
421         proj.missile_flags = MIF_SPLASH | MIF_ARC;
422
423         if(WEP_CVAR_SEC(electro, limit) > 0)
424         {
425                 ListNewChildRubble(LimitedElectroBallRubbleList, proj);
426                 LimitedChildrenRubble(LimitedElectroBallRubbleList, "electro_orb", WEP_CVAR_SEC(electro, limit), adaptor_think2use_hittype_splash, actor);
427         }
428
429         CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
430
431         MUTATOR_CALLHOOK(EditProjectile, actor, proj);
432 }
433
434 void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
435 {
436         if(actor.(weaponentity).electro_count > 1)
437         if(PHYS_INPUT_BUTTON_ATCK2(actor))
438         if(weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
439         {
440                 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
441                 actor.(weaponentity).electro_count -= 1;
442                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
443                 return;
444         }
445         // WEAPONTODO: when the player releases the button, cut down the length of refire2?
446         w_ready(thiswep, actor, weaponentity, fire);
447 }
448
449 .float bot_secondary_electromooth;
450
451 METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
452 {
453     PHYS_INPUT_BUTTON_ATCK(actor) = PHYS_INPUT_BUTTON_ATCK2(actor) = false;
454     if(vdist(actor.origin - actor.enemy.origin, >, 1000)) { actor.bot_secondary_electromooth = 0; }
455     if(actor.bot_secondary_electromooth == 0)
456     {
457         float shoot;
458
459         if(WEP_CVAR_PRI(electro, speed))
460             shoot = bot_aim(actor, weaponentity, WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, lifetime), false);
461         else
462             shoot = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
463
464         if(shoot)
465         {
466             PHYS_INPUT_BUTTON_ATCK(actor) = true;
467             if(random() < 0.01) actor.bot_secondary_electromooth = 1;
468         }
469     }
470     else
471     {
472         if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true))
473         {
474             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
475             if(random() < 0.03) actor.bot_secondary_electromooth = 0;
476         }
477     }
478 }
479 METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
480 {
481     if(autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
482     {
483         float ammo_amount = 0;
484         if(actor.(weaponentity).clip_load >= WEP_CVAR_PRI(electro, ammo))
485             ammo_amount = 1;
486         if(actor.(weaponentity).clip_load >= WEP_CVAR_SEC(electro, ammo))
487             ammo_amount += 1;
488
489         if(!ammo_amount)
490         {
491             thiswep.wr_reload(thiswep, actor, weaponentity);
492             return;
493         }
494     }
495
496     if(fire & 1)
497     {
498         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(electro, refire)))
499         {
500                 W_Electro_Attack_Bolt(thiswep, actor, weaponentity);
501                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready);
502         }
503     }
504     else if(fire & 2)
505     {
506         if(time >= actor.(weaponentity).electro_secondarytime)
507         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(electro, refire)))
508         {
509             W_Electro_Attack_Orb(thiswep, actor, weaponentity);
510             actor.(weaponentity).electro_count = WEP_CVAR_SEC(electro, count);
511             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
512             actor.(weaponentity).electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor(actor);
513         }
514     }
515 }
516 METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
517 {
518     float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo);
519     ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(electro, ammo);
520     return ammo_amount;
521 }
522 METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
523 {
524     float ammo_amount;
525     if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
526     {
527         ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
528         ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
529     }
530     else
531     {
532         ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo);
533         ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo);
534     }
535     return ammo_amount;
536 }
537 METHOD(Electro, wr_resetplayer, void(entity thiswep, entity actor))
538 {
539     for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
540     {
541         .entity weaponentity = weaponentities[slot];
542         actor.(weaponentity).electro_secondarytime = time;
543     }
544 }
545 METHOD(Electro, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
546 {
547     W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), SND_RELOAD);
548 }
549 METHOD(Electro, wr_suicidemessage, Notification(entity thiswep))
550 {
551     if(w_deathtype & HITTYPE_SECONDARY)
552         return WEAPON_ELECTRO_SUICIDE_ORBS;
553     else
554         return WEAPON_ELECTRO_SUICIDE_BOLT;
555 }
556 METHOD(Electro, wr_killmessage, Notification(entity thiswep))
557 {
558     if(w_deathtype & HITTYPE_SECONDARY)
559     {
560         return WEAPON_ELECTRO_MURDER_ORBS;
561     }
562     else
563     {
564         if(w_deathtype & HITTYPE_BOUNCE)
565             return WEAPON_ELECTRO_MURDER_COMBO;
566         else
567             return WEAPON_ELECTRO_MURDER_BOLT;
568     }
569 }
570
571 #endif
572 #ifdef CSQC
573
574 METHOD(Electro, wr_impacteffect, void(entity thiswep, entity actor))
575 {
576     vector org2;
577     org2 = w_org + w_backoff * 6;
578     if(w_deathtype & HITTYPE_SECONDARY)
579     {
580         pointparticles(EFFECT_ELECTRO_BALLEXPLODE, org2, '0 0 0', 1);
581         if(!w_issilent)
582             sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
583     }
584     else
585     {
586         if(w_deathtype & HITTYPE_BOUNCE)
587         {
588             // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
589             pointparticles(EFFECT_ELECTRO_COMBO, org2, '0 0 0', 1);
590             if(!w_issilent)
591                 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT_COMBO, VOL_BASE, ATTEN_NORM);
592         }
593         else
594         {
595             pointparticles(EFFECT_ELECTRO_IMPACT, org2, '0 0 0', 1);
596             if(!w_issilent)
597                 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
598         }
599     }
600 }
601
602 #endif