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