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