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