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