]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/electro.qc
21a2f9b0128723b4d719acc46d3d20f852e2a569
[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(g_rubble, this))
301         {
302                 newproj.creationtime = this.creationtime;
303                 IL_PUSH(g_rubble, newproj);
304         }
305
306         delete(this);
307
308         if(to)
309                 SetMovetypeFollow(newproj, to);
310 }
311
312 void W_Electro_Orb_Touch(entity this, entity toucher)
313 {
314         PROJECTILE_TOUCH(this, toucher);
315         if(toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(electro, touchexplode))
316                 { W_Electro_Explode(this, toucher); }
317         else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
318         {
319                 //UpdateCSQCProjectile(this);
320                 spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
321                 this.projectiledeathtype |= HITTYPE_BOUNCE;
322
323                 if(WEP_CVAR_SEC(electro, stick))
324                         W_Electro_Orb_Stick(this, toucher);
325         }
326 }
327
328 void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
329 {
330         if(GetResource(this, RES_HEALTH) <= 0)
331                 return;
332
333         // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
334         float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
335
336         if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
337                 return; // g_projectiles_damage says to halt
338
339         TakeResource(this, RES_HEALTH, damage);
340         if(GetResource(this, RES_HEALTH) <= 0)
341         {
342                 this.takedamage = DAMAGE_NO;
343                 this.nextthink = time;
344                 if(is_combo)
345                 {
346                         // change owner to whoever caused the combo explosion
347                         this.realowner = inflictor.realowner;
348                         this.classname = "electro_orb_chain";
349                         setthink(this, W_Electro_ExplodeCombo);
350                         this.nextthink = time +
351                                 (
352                                         // bound the length, inflictor may be in a galaxy far far away (warpzones)
353                                         min(
354                                                 WEP_CVAR(electro, combo_radius),
355                                                 vlen(this.origin - inflictor.origin)
356                                         )
357                                         /
358                                         // delay combo chains, looks cooler
359                                         WEP_CVAR(electro, combo_speed)
360                                 );
361                 }
362                 else
363                 {
364                         this.use = W_Electro_Explode_use;
365                         setthink(this, adaptor_think2use); // not _hittype_splash, as this runs "immediately"
366                 }
367         }
368 }
369
370 void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
371 {
372         W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(electro, ammo), weaponentity);
373
374         W_SetupShot_ProjectileSize(
375                 actor,
376                 weaponentity,
377                 '-4 -4 -4',
378                 '4 4 4',
379                 false,
380                 2,
381                 SND_ELECTRO_FIRE2,
382                 CH_WEAPON_A,
383                 WEP_CVAR_SEC(electro, damage),
384                 thiswep.m_id | HITTYPE_SECONDARY
385         );
386
387         w_shotdir = v_forward; // no TrueAim for grenades please
388
389         W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
390
391         entity proj = new(electro_orb);
392         proj.owner = proj.realowner = actor;
393         proj.use = W_Electro_Explode_use;
394         setthink(proj, adaptor_think2use_hittype_splash);
395         proj.bot_dodge = true;
396         proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage);
397         proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime);
398         PROJECTILE_MAKETRIGGER(proj);
399         proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
400         proj.weaponentity_fld = weaponentity;
401         setorigin(proj, w_shotorg);
402
403         //proj.glow_size = 50;
404         //proj.glow_color = 45;
405         set_movetype(proj, MOVETYPE_BOUNCE);
406         W_SetupProjVelocity_UP_SEC(proj, electro);
407         settouch(proj, W_Electro_Orb_Touch);
408         setsize(proj, '-4 -4 -4', '4 4 4');
409         proj.takedamage = DAMAGE_YES;
410         proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
411         SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_SEC(electro, health));
412         proj.event_damage = W_Electro_Orb_Damage;
413         proj.flags = FL_PROJECTILE;
414         IL_PUSH(g_projectiles, proj);
415         IL_PUSH(g_bot_dodge, proj);
416         proj.damagedbycontents = (WEP_CVAR_SEC(electro, damagedbycontents));
417         if(proj.damagedbycontents)
418                 IL_PUSH(g_damagedbycontents, proj);
419
420         proj.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
421         proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
422         proj.missile_flags = MIF_SPLASH | MIF_ARC;
423
424         if(WEP_CVAR_SEC(electro, limit) > 0)
425         {
426                 RubbleNew(proj);
427                 RubbleLimit("electro_orb", WEP_CVAR_SEC(electro, limit), adaptor_think2use_hittype_splash);
428         }
429
430         CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
431
432         MUTATOR_CALLHOOK(EditProjectile, actor, proj);
433 }
434
435 void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
436 {
437         if(actor.(weaponentity).electro_count > 1)
438         if(PHYS_INPUT_BUTTON_ATCK2(actor))
439         if(weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
440         {
441                 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
442                 actor.(weaponentity).electro_count -= 1;
443                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
444                 return;
445         }
446         // WEAPONTODO: when the player releases the button, cut down the length of refire2?
447         w_ready(thiswep, actor, weaponentity, fire);
448 }
449
450 .float bot_secondary_electromooth;
451
452 METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
453 {
454     PHYS_INPUT_BUTTON_ATCK(actor) = PHYS_INPUT_BUTTON_ATCK2(actor) = false;
455     if(vdist(actor.origin - actor.enemy.origin, >, 1000)) { actor.bot_secondary_electromooth = 0; }
456     if(actor.bot_secondary_electromooth == 0)
457     {
458         float shoot;
459
460         if(WEP_CVAR_PRI(electro, speed))
461             shoot = bot_aim(actor, weaponentity, WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, lifetime), false);
462         else
463             shoot = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
464
465         if(shoot)
466         {
467             PHYS_INPUT_BUTTON_ATCK(actor) = true;
468             if(random() < 0.01) actor.bot_secondary_electromooth = 1;
469         }
470     }
471     else
472     {
473         if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true))
474         {
475             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
476             if(random() < 0.03) actor.bot_secondary_electromooth = 0;
477         }
478     }
479 }
480 METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
481 {
482     if(autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
483     {
484         float ammo_amount = 0;
485         if(actor.(weaponentity).clip_load >= WEP_CVAR_PRI(electro, ammo))
486             ammo_amount = 1;
487         if(actor.(weaponentity).clip_load >= WEP_CVAR_SEC(electro, ammo))
488             ammo_amount += 1;
489
490         if(!ammo_amount)
491         {
492             thiswep.wr_reload(thiswep, actor, weaponentity);
493             return;
494         }
495     }
496
497     if(fire & 1)
498     {
499         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(electro, refire)))
500         {
501                 W_Electro_Attack_Bolt(thiswep, actor, weaponentity);
502                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready);
503         }
504     }
505     else if(fire & 2)
506     {
507         if(time >= actor.(weaponentity).electro_secondarytime)
508         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(electro, refire)))
509         {
510             W_Electro_Attack_Orb(thiswep, actor, weaponentity);
511             actor.(weaponentity).electro_count = WEP_CVAR_SEC(electro, count);
512             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
513             actor.(weaponentity).electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor(actor);
514         }
515     }
516 }
517 METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
518 {
519     float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo);
520     ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(electro, ammo);
521     return ammo_amount;
522 }
523 METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
524 {
525     float ammo_amount;
526     if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
527     {
528         ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
529         ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
530     }
531     else
532     {
533         ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo);
534         ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo);
535     }
536     return ammo_amount;
537 }
538 METHOD(Electro, wr_resetplayer, void(entity thiswep, entity actor))
539 {
540     for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
541     {
542         .entity weaponentity = weaponentities[slot];
543         actor.(weaponentity).electro_secondarytime = time;
544     }
545 }
546 METHOD(Electro, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
547 {
548     W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), SND_RELOAD);
549 }
550 METHOD(Electro, wr_suicidemessage, Notification(entity thiswep))
551 {
552     if(w_deathtype & HITTYPE_SECONDARY)
553         return WEAPON_ELECTRO_SUICIDE_ORBS;
554     else
555         return WEAPON_ELECTRO_SUICIDE_BOLT;
556 }
557 METHOD(Electro, wr_killmessage, Notification(entity thiswep))
558 {
559     if(w_deathtype & HITTYPE_SECONDARY)
560     {
561         return WEAPON_ELECTRO_MURDER_ORBS;
562     }
563     else
564     {
565         if(w_deathtype & HITTYPE_BOUNCE)
566             return WEAPON_ELECTRO_MURDER_COMBO;
567         else
568             return WEAPON_ELECTRO_MURDER_BOLT;
569     }
570 }
571
572 #endif
573 #ifdef CSQC
574
575 METHOD(Electro, wr_impacteffect, void(entity thiswep, entity actor))
576 {
577     vector org2;
578     org2 = w_org + w_backoff * 6;
579     if(w_deathtype & HITTYPE_SECONDARY)
580     {
581         pointparticles(EFFECT_ELECTRO_BALLEXPLODE, org2, '0 0 0', 1);
582         if(!w_issilent)
583             sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
584     }
585     else
586     {
587         if(w_deathtype & HITTYPE_BOUNCE)
588         {
589             // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
590             pointparticles(EFFECT_ELECTRO_COMBO, org2, '0 0 0', 1);
591             if(!w_issilent)
592                 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT_COMBO, VOL_BASE, ATTEN_NORM);
593         }
594         else
595         {
596             pointparticles(EFFECT_ELECTRO_IMPACT, org2, '0 0 0', 1);
597             if(!w_issilent)
598                 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
599         }
600     }
601 }
602
603 #endif