Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
electro.qc
Go to the documentation of this file.
1#include "electro.qh"
2
3#ifdef GAMEQC
4
5#ifdef CSQC
6
7.float ltime;
9{
10 float dt = time - this.move_time;
11 this.move_time = time;
12 if (dt <= 0)
13 return;
14
15 float myscale = bound(0, (this.ltime - time) * 4, 1);
16 this.scale = (WEP_CVAR(WEP_ELECTRO, combo_radius) * 0.05) * myscale;
17 this.angles += dt * this.avelocity;
18}
19
21{
22 setmodel(e, MDL_PROJECTILE_ELECTRO);
23 setsize(e, '-4 -4 -4', '4 4 4');
24
25 setorigin(e, e.origin);
26
27 e.draw = electro_orb_draw;
29 SetResourceExplicit(e, RES_HEALTH, 255);
31 e.solid = SOLID_NOT;
32 e.avelocity = '7 0 11';
33 e.drawmask = MASK_NORMAL;
34 e.alpha = 0.7;
35}
36#endif
37
38REGISTER_NET_LINKED(Electro_Orb)
39
40#ifdef CSQC
41NET_HANDLE(Electro_Orb, bool isNew)
42{
43 Net_Accept(Electro_Orb);
44 this.origin = ReadVector();
45 setorigin(this, this.origin);
46 this.ltime = time + ReadByte() / 10.0;
47 // this.ltime = time + this.orb_lifetime;
49 return true;
50}
51#endif
52
53#ifdef SVQC
54bool electro_orb_send(entity this, entity to, int sf)
55{
56 int channel = MSG_ENTITY;
57 WriteHeader(channel, Electro_Orb);
58 WriteVector(channel, this.origin);
59 // round time delta to a 1/10th of a second
60 WriteByte(channel, (this.ltime - time) * 10 + 0.5);
61 return true;
62}
63#endif
64
65#endif
66
67#ifdef SVQC
69
71{
72 entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(WEP_ELECTRO, combo_comboradius_thruwall));
73 for (; e; e = e.chain)
74 if (e.classname == "electro_orb")
75 {
76 // check if the ball we are exploding is not owned by an
77 // independent player which is not the player who shot the ball
78 if (IS_INDEPENDENT_PLAYER(e.realowner) && own != e.realowner)
79 continue;
80
81 // do we allow thruwall triggering?
82 if (WEP_CVAR(WEP_ELECTRO, combo_comboradius_thruwall))
83 {
84 // if distance is greater than thruwall distance, check to make sure it's not through a wall
85 if (vdist(e.WarpZone_findradius_dist, >, WEP_CVAR(WEP_ELECTRO, combo_comboradius_thruwall)))
86 {
88 if (trace_fraction != 1)
89 {
90 // trigger is through a wall and outside of thruwall range, abort
91 continue;
92 }
93 }
94 }
95
96 // change owner to whoever caused the combo explosion
97 e.realowner = own;
98 e.takedamage = DAMAGE_NO;
99 e.classname = "electro_orb_chain";
100
101 // now set the next one to trigger as well
103
104 // delay combo chains, looks cooler
105 float delay = (WEP_CVAR(WEP_ELECTRO, combo_speed))
106 ? vlen(e.WarpZone_findradius_dist) / WEP_CVAR(WEP_ELECTRO, combo_speed)
107 : 0;
108 e.nextthink = time + delay;
109 }
110}
111
113{
114 if (time >= this.ltime)
115 {
116 delete(this);
117 return;
118 }
119
120 this.nextthink = time;
121
122 RadiusDamage(this, this.realowner,
123 PHYS_INPUT_TIMELENGTH * WEP_CVAR(WEP_ELECTRO, combo_damage),
124 PHYS_INPUT_TIMELENGTH * WEP_CVAR(WEP_ELECTRO, combo_edgedamage),
125 WEP_CVAR(WEP_ELECTRO, combo_radius),
126 NULL,
127 NULL,
128 0,
130 this.weaponentity_fld,
131 NULL
132 );
133 this.projectiledeathtype |= HITTYPE_SPAM; // ensure it doesn't spam its effect
134}
135
137{
138 entity newproj = spawn();
139 newproj.classname = this.classname;
140 newproj.solid = this.solid;
141 setorigin(newproj, this.origin);
142 setmodel(newproj, MDL_PROJECTILE_ELECTRO);
143 setsize(newproj, this.mins, this.maxs);
144 newproj.owner = this.owner;
145 newproj.realowner = this.realowner;
146 newproj.weaponentity_fld = this.weaponentity_fld;
147 newproj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_BOUNCE; // use THIS type for a combo because primary can't bounce
148
150 newproj.nextthink = time;
151 newproj.ltime = time + WEP_CVAR(WEP_ELECTRO, combo_duration);
152 set_movetype(newproj, MOVETYPE_NONE);
153
154 Net_LinkEntity(newproj, true, 0, electro_orb_send);
155 newproj.SendFlags = 0xFFFFFF;
156
157 // fire the first damage tick immediately
158 getthink(newproj)(newproj);
159}
160
162{
163 W_Electro_TriggerCombo(this.origin, WEP_CVAR(WEP_ELECTRO, combo_comboradius), this.realowner);
164
165 this.event_damage = func_null;
166 if (!this.velocity)
167 this.velocity = this.movedir; // .velocity must be != '0 0 0' for particle fx and decal to work
168
169 if (WEP_CVAR(WEP_ELECTRO, combo_duration))
170 {
172
173 delete(this);
174 return;
175 }
176
177 RadiusDamage(this, this.realowner,
178 WEP_CVAR(WEP_ELECTRO, combo_damage),
179 WEP_CVAR(WEP_ELECTRO, combo_edgedamage),
180 WEP_CVAR(WEP_ELECTRO, combo_radius),
181 NULL,
182 NULL,
183 WEP_CVAR(WEP_ELECTRO, combo_force),
184 WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
185 this.weaponentity_fld,
186 NULL
187 );
188
189 delete(this);
190}
191
192void W_Electro_Explode(entity this, entity directhitentity)
193{
194 if (directhitentity.takedamage == DAMAGE_AIM
195 && IS_PLAYER(directhitentity) && DIFF_TEAM(this.realowner, directhitentity) && !IS_DEAD(directhitentity)
196 && IsFlying(directhitentity))
197 Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
198
199 this.event_damage = func_null;
200 this.takedamage = DAMAGE_NO;
201 if (!this.velocity)
202 this.velocity = this.movedir; // .velocity must be != '0 0 0' for particle fx and decal to work
203
204 if (this.move_movetype == MOVETYPE_BOUNCE || this.classname == "electro_orb") // TODO: classname is more reliable anyway?
205 RadiusDamage(this, this.realowner,
206 WEP_CVAR_SEC(WEP_ELECTRO, damage),
207 WEP_CVAR_SEC(WEP_ELECTRO, edgedamage),
208 WEP_CVAR_SEC(WEP_ELECTRO, radius),
209 NULL,
210 NULL,
211 WEP_CVAR_SEC(WEP_ELECTRO, force),
213 this.weaponentity_fld,
214 directhitentity
215 );
216 else
217 {
218 W_Electro_TriggerCombo(this.origin, WEP_CVAR_PRI(WEP_ELECTRO, comboradius), this.realowner);
219 RadiusDamage(this, this.realowner,
220 WEP_CVAR_PRI(WEP_ELECTRO, damage),
221 WEP_CVAR_PRI(WEP_ELECTRO, edgedamage),
222 WEP_CVAR_PRI(WEP_ELECTRO, radius),
223 NULL,
224 NULL,
225 WEP_CVAR_PRI(WEP_ELECTRO, force),
227 this.weaponentity_fld,
228 directhitentity
229 );
230 }
231
232 delete(this);
233}
234
235void W_Electro_Explode_use(entity this, entity actor, entity trigger)
236{
237 W_Electro_Explode(this, trigger);
238}
239
245
246
247//void sys_phys_update_single(entity this);
248
250{
251 // sys_phys_update_single(this);
252 if (time >= this.ltime)
253 {
254 this.use(this, NULL, NULL);
255 return;
256 }
257
258 if (WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_radius))
259 {
260 int found = 0;
261 entity e = WarpZone_FindRadius(this.origin, WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_radius), true);
262
263 // loop through nearby orbs and trigger them
264 for (; e; e = e.chain)
265 if (e.classname == "electro_orb")
266 {
267 // check if the ball we are exploding is not owned by an
268 // independent player which is not the player who shot the ball
269 if (IS_INDEPENDENT_PLAYER(e.realowner) && this.realowner != e.realowner)
270 continue;
271
272 bool explode;
273 if (this.owner == e.owner)
274 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_own);
275 else if (SAME_TEAM(this.owner, e.owner))
276 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_teammate);
277 else
278 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_enemy);
279
280 if (explode)
281 {
282 // change owner to whoever caused the combo explosion
283 e.realowner = this.realowner;
284 e.takedamage = DAMAGE_NO;
285 e.classname = "electro_orb_chain";
286
287 // Only first orb explosion uses midaircombo_speed, others use the normal combo_speed.
288 // This allows to avoid the delay on the first explosion which looks better
289 // (the bolt and orb should explode together because they interacted together)
290 // while keeping the chaining delay.
292 float delay = (WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_speed))
293 ? vlen(e.WarpZone_findradius_dist) / WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_speed)
294 : 0;
295 e.nextthink = time + delay;
296
297 ++found;
298 }
299 }
300
301 // if we triggered an orb, should we explode? if not, lets try again next time
302 if (found && WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_explode))
303 this.use(this, NULL, NULL);
304 else
305 this.nextthink = min(time + WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_interval), this.ltime);
306 }
307 else
308 this.nextthink = this.ltime;
309 // this.nextthink = time;
310}
311
312void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
313{
314 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(WEP_ELECTRO, ammo), weaponentity);
315
317 actor,
318 weaponentity,
319 '0 0 -3',
320 '0 0 -3',
321 false,
322 2,
323 SND_ELECTRO_FIRE,
325 WEP_CVAR_PRI(WEP_ELECTRO, damage),
326 thiswep.m_id
327 );
328
329 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
330
331 entity proj = new(electro_bolt);
332 proj.owner = proj.realowner = actor;
333 proj.bot_dodge = true;
334 proj.bot_dodgerating = WEP_CVAR_PRI(WEP_ELECTRO, damage);
335 proj.use = W_Electro_Explode_use;
337 proj.nextthink = time;
338 proj.ltime = time + WEP_CVAR_PRI(WEP_ELECTRO, lifetime);
340 proj.projectiledeathtype = thiswep.m_id;
341 proj.weaponentity_fld = weaponentity;
342 setorigin(proj, w_shotorg);
343
344 // if (IS_CSQC)
346 W_SetupProjVelocity_PRI(proj, WEP_ELECTRO);
347 proj.angles = vectoangles(proj.velocity);
349 setsize(proj, '0 0 -3', '0 0 -3');
350 proj.flags = FL_PROJECTILE;
351 IL_PUSH(g_projectiles, proj);
352 IL_PUSH(g_bot_dodge, proj);
353 proj.missile_flags = MIF_SPLASH;
354
355 CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
356
357 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
358 // proj.com_phys_pos = proj.origin;
359 // proj.com_phys_vel = proj.velocity;
360}
361
363{
364 if (time > this.death_time)
365 {
367 return;
368 }
369 if (this.move_movetype == MOVETYPE_FOLLOW)
370 {
371 int lost = LostMovetypeFollow(this);
372 if (lost == 2)
373 {
374 // FIXME if player disconnected, it isn't possible to drop the orb at player's origin
375 // see comment in LostMovetypeFollow implementation
376 delete(this);
377 return;
378 }
379 if (lost)
380 {
381 // drop the orb at the corpse's location
384
386 this.nextthink = this.death_time;
387 return;
388 }
389 }
390 this.nextthink = time;
391}
392
394{
395 entity newproj = spawn();
396 newproj.classname = this.classname;
397
398 newproj.bot_dodge = this.bot_dodge;
399 newproj.bot_dodgerating = this.bot_dodgerating;
400
401 newproj.owner = this.owner;
402 newproj.realowner = this.realowner;
403 PROJECTILE_MAKETRIGGER(newproj);
404 setorigin(newproj, this.origin);
405 setmodel(newproj, MDL_PROJECTILE_ELECTRO);
406 setsize(newproj, this.mins, this.maxs);
407 newproj.angles = vectoangles(-trace_plane_normal); // face against the surface
408 newproj.traileffectnum = _particleeffectnum(EFFECT_TR_NEXUIZPLASMA.eent_eff_name);
409
410 newproj.movedir = -trace_plane_normal;
411
412 newproj.takedamage = this.takedamage;
413 newproj.damageforcescale = this.damageforcescale;
414 SetResourceExplicit(newproj, RES_HEALTH, GetResource(this, RES_HEALTH));
415 newproj.event_damage = this.event_damage;
416 newproj.spawnshieldtime = this.spawnshieldtime;
417 newproj.damagedbycontents = true;
419
420 set_movetype(newproj, MOVETYPE_NONE); // lock the orb in place
421 newproj.projectiledeathtype = this.projectiledeathtype;
422 newproj.weaponentity_fld = this.weaponentity_fld;
423
424 settouch(newproj, func_null);
425 if (WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime) > 0)
426 newproj.death_time = time + WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime);
427 else
428 newproj.death_time = this.death_time;
429 newproj.use = this.use;
430 newproj.flags = this.flags;
431 IL_PUSH(g_projectiles, newproj);
432 IL_PUSH(g_bot_dodge, newproj);
433
434 // check if limits are enabled (we can tell by checking if the original orb is listed) and push it to the list if so
437
438 delete(this);
439
440 if (to)
441 {
442 SetMovetypeFollow(newproj, to);
443
445 newproj.nextthink = time;
446 }
447 else
448 {
450 newproj.nextthink = newproj.death_time;
451 }
452}
453
455{
457 if (toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(WEP_ELECTRO, touchexplode))
459 else if (toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
460 {
461 //UpdateCSQCProjectile(this);
462 spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
464
465 if (WEP_CVAR_SEC(WEP_ELECTRO, stick))
466 {
467 if (WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime) == 0)
469 else
471 }
472 }
473}
474
475void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
476{
477 if (GetResource(this, RES_HEALTH) <= 0)
478 return;
479
480 // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
481 float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
482
483 if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
484 return; // g_projectiles_damage says to halt
485
486 TakeResource(this, RES_HEALTH, damage);
487 if (GetResource(this, RES_HEALTH) <= 0)
488 {
489 this.takedamage = DAMAGE_NO;
490 this.nextthink = time;
491 if (is_combo)
492 {
493 // change owner to whoever caused the combo explosion
494 this.realowner = inflictor.realowner;
495 this.classname = "electro_orb_chain";
497 // delay combo chains, looks cooler
498 // bound the length, inflictor may be in a galaxy far far away (warpzones)
499 float len = min(WEP_CVAR(WEP_ELECTRO, combo_radius), vlen(this.origin - inflictor.origin));
500 float delay = len / WEP_CVAR(WEP_ELECTRO, combo_speed);
501 this.nextthink = time + delay;
502 }
503 else
504 {
506 setthink(this, adaptor_think2use); // not _hittype_splash, as this runs "immediately"
507 }
508 }
509}
510
511void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
512{
513 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(WEP_ELECTRO, ammo), weaponentity);
514
516 actor,
517 weaponentity,
518 '-4 -4 -4',
519 '4 4 4',
520 false,
521 2,
522 SND_ELECTRO_FIRE2,
524 WEP_CVAR_SEC(WEP_ELECTRO, damage),
525 thiswep.m_id | HITTYPE_SECONDARY
526 );
527
528 w_shotdir = v_forward; // no TrueAim for grenades please
529
530 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
531
532 entity proj = new(electro_orb);
533 proj.owner = proj.realowner = actor;
534 proj.use = W_Electro_Explode_use;
536 proj.bot_dodge = true;
537 proj.bot_dodgerating = WEP_CVAR_SEC(WEP_ELECTRO, damage);
538 proj.nextthink = time + WEP_CVAR_SEC(WEP_ELECTRO, lifetime);
539 proj.death_time = time + WEP_CVAR_SEC(WEP_ELECTRO, lifetime);
541 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
542 proj.weaponentity_fld = weaponentity;
543 setorigin(proj, w_shotorg);
544
545 //proj.glow_size = 50;
546 //proj.glow_color = 45;
548 W_SetupProjVelocity_UP_SEC(proj, WEP_ELECTRO);
550 setsize(proj, '-4 -4 -4', '4 4 4');
551 proj.takedamage = DAMAGE_YES;
552 proj.damageforcescale = WEP_CVAR_SEC(WEP_ELECTRO, damageforcescale);
553 SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_SEC(WEP_ELECTRO, health));
554 proj.event_damage = W_Electro_Orb_Damage;
555 proj.flags = FL_PROJECTILE;
556 IL_PUSH(g_projectiles, proj);
557 IL_PUSH(g_bot_dodge, proj);
558 proj.damagedbycontents = (WEP_CVAR_SEC(WEP_ELECTRO, damagedbycontents));
559 if (proj.damagedbycontents)
561
562 proj.bouncefactor = WEP_CVAR_SEC(WEP_ELECTRO, bouncefactor);
563 proj.bouncestop = WEP_CVAR_SEC(WEP_ELECTRO, bouncestop);
564 proj.missile_flags = MIF_SPLASH | MIF_ARC;
565
566 if (WEP_CVAR_SEC(WEP_ELECTRO, limit) > 0)
567 {
572 }
573
574 CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
575
576 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
577}
578
579void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
580{
581 if (actor.(weaponentity).electro_count > 1
583 && weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
584 {
585 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
586 --actor.(weaponentity).electro_count;
587 actor.(weaponentity).electro_secondarytime = time;
588 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_ELECTRO, animtime), W_Electro_CheckAttack);
589 return;
590 }
591 w_ready(thiswep, actor, weaponentity, fire);
592}
593
594.bool bot_secondary_electromooth; // whatever a mooth is
595
596METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
597{
598 PHYS_INPUT_BUTTON_ATCK(actor) = PHYS_INPUT_BUTTON_ATCK2(actor) = false;
599 if (vdist(actor.origin - actor.enemy.origin, >, 1000))
600 actor.bot_secondary_electromooth = false;
601 if (!actor.bot_secondary_electromooth)
602 {
603 float shoot = (WEP_CVAR_PRI(WEP_ELECTRO, speed))
604 ? bot_aim(actor, weaponentity, WEP_CVAR_PRI(WEP_ELECTRO, speed), 0, WEP_CVAR_PRI(WEP_ELECTRO, lifetime), false, true)
605 : bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
606
607 if (shoot)
608 {
609 PHYS_INPUT_BUTTON_ATCK(actor) = true;
610 if (random() < 0.01)
611 actor.bot_secondary_electromooth = true;
612 }
613 }
614 else
615 {
616 if (bot_aim(actor, weaponentity, WEP_CVAR_SEC(WEP_ELECTRO, speed), WEP_CVAR_SEC(WEP_ELECTRO, speed_up), WEP_CVAR_SEC(WEP_ELECTRO, lifetime), true, true))
617 {
618 PHYS_INPUT_BUTTON_ATCK2(actor) = true;
619 if (random() < 0.03)
620 actor.bot_secondary_electromooth = false;
621 }
622 }
623}
624
625METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
626{
627 if (autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
628 {
629 float ammo_amount = 0;
630 if (actor.(weaponentity).clip_load >= WEP_CVAR_PRI(WEP_ELECTRO, ammo))
631 ammo_amount = 1;
632 if (actor.(weaponentity).clip_load >= WEP_CVAR_SEC(WEP_ELECTRO, ammo))
633 ++ammo_amount;
634
635 if (!ammo_amount)
636 {
637 thiswep.wr_reload(thiswep, actor, weaponentity);
638 return;
639 }
640 }
641
642 if (fire & 1)
643 {
644 if (time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(WEP_ELECTRO, refire2) * W_WeaponRateFactor(actor)
645 && weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_ELECTRO, refire)))
646 {
647 W_Electro_Attack_Bolt(thiswep, actor, weaponentity);
648 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_ELECTRO, animtime), w_ready);
649 }
650 }
651 else if (fire & 2)
652 {
653 if (time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(WEP_ELECTRO, refire) * W_WeaponRateFactor(actor)
654 && weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
655 {
656 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
657 actor.(weaponentity).electro_count = WEP_CVAR_SEC(WEP_ELECTRO, count);
658 actor.(weaponentity).electro_secondarytime = time;
659 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_ELECTRO, animtime), W_Electro_CheckAttack);
660 }
661 }
662}
663
664METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
665{
666 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(WEP_ELECTRO, ammo);
667 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(WEP_ELECTRO, ammo);
668 return ammo_amount;
669}
670
671METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
672{
673 float ammo_amount;
674 if (WEP_CVAR(WEP_ELECTRO, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
675 {
676 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo) + WEP_CVAR_PRI(WEP_ELECTRO, ammo);
677 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo) + WEP_CVAR_PRI(WEP_ELECTRO, ammo);
678 }
679 else
680 {
681 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo);
682 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo);
683 }
684 return ammo_amount;
685}
686
687METHOD(Electro, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
688{
689 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(WEP_ELECTRO, ammo), WEP_CVAR_SEC(WEP_ELECTRO, ammo)), SND_RELOAD);
690}
691
692METHOD(Electro, wr_suicidemessage, Notification(entity thiswep))
693{
695 return WEAPON_ELECTRO_SUICIDE_ORBS;
696 else
697 return WEAPON_ELECTRO_SUICIDE_BOLT;
698}
699
700METHOD(Electro, wr_killmessage, Notification(entity thiswep))
701{
703 {
704 return WEAPON_ELECTRO_MURDER_ORBS;
705 }
706 else
707 {
709 return WEAPON_ELECTRO_MURDER_COMBO;
710 else
711 return WEAPON_ELECTRO_MURDER_BOLT;
712 }
713}
714
715#endif // GAMEQC
716#ifdef CSQC
717
718METHOD(Electro, wr_impacteffect, void(entity thiswep, entity actor))
719{
720 vector org2 = w_org + w_backoff * 2;
722 {
723 pointparticles(EFFECT_ELECTRO_BALLEXPLODE, org2, '0 0 0', 1);
724 if (!w_issilent)
725 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
726 }
727 else
728 {
730 {
731 // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
732 pointparticles(EFFECT_ELECTRO_COMBO, org2, '0 0 0', 1);
733 if (!w_issilent)
734 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT_COMBO, VOL_BASE, ATTEN_NORM);
735 }
736 else
737 {
738 pointparticles(EFFECT_ELECTRO_IMPACT, org2, '0 0 0', 1);
739 if (!w_issilent)
740 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
741 }
742 }
743}
744
745#endif // CSQC
746#ifdef MENUQC
749
750METHOD(Electro, describe, string(Electro this))
751{
752 TC(Electro, this);
754 PAR(_("The %s shoots electric balls forwards, dealing some splash damage when they burst on impact."), COLORED_NAME(this));
755 PAR(_("The secondary fire launches orbs that are influenced by gravity, "
756 "so they can be laid around the map at high traffic locations (like at %s flag bases) to damage enemies that walk by. "
757 "The orbs burst after some time, and can be forced to burst in a \"combo\" if a primary fire ball bursts near them."), COLORED_NAME(MAPINFO_TYPE_CTF));
758 PAR(_("It consumes some %s ammo for each ball / orb."), COLORED_NAME(ITEM_Cells));
759 PAR(_("The %s is one of the best spam weapons to use in crowded areas, since combos can deal tons of damage, if the enemy is close enough. "
760 "Since the primary fire doesn't travel particularly fast, the %s is not useful in many other situations."), COLORED_NAME(this), COLORED_NAME(this));
761 PAR(W_Guide_Keybinds(this));
762 PAR(W_Guide_DPS_secondaryMultishotWithCombo(this.netname, "primary", "secondary", "secondary_count", "secondary_refire2", "combo", true));
763 return PAGE_TEXT;
764}
765
766#endif // MENUQC
float bot_dodge
Definition api.qh:40
float bot_dodgerating
Definition api.qh:39
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity, bool shot_accurate)
IntrusiveList g_bot_dodge
Definition api.qh:150
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition weapon.qh:42
int m_id
Definition weapon.qh:43
string netname
Definition powerups.qc:20
float lifetime
Definition powerups.qc:23
float count
Definition powerups.qc:22
float delay
Definition items.qc:17
IntrusiveList g_drawables
Definition main.qh:91
entity owner
Definition main.qh:87
#define COLORED_NAME(this)
Definition color.qh:195
float radius
Definition impulse.qh:11
#define setmodel(this, m)
Definition model.qh:26
float ltime
Definition net.qh:10
bool IsFlying(entity this)
Definition player.qc:843
#define PHYS_INPUT_TIMELENGTH
Definition player.qh:253
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition player.qh:152
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition player.qh:154
float W_WeaponRateFactor(entity this)
void SetMovetypeFollow(entity ent, entity e)
Definition util.qc:2129
int LostMovetypeFollow(entity ent)
Definition util.qc:2159
const int FL_PROJECTILE
Definition constants.qh:85
string classname
float flags
const float MOVE_NOMONSTERS
vector avelocity
const float MASK_NORMAL
vector mins
vector velocity
const float SOLID_NOT
float time
vector maxs
float nextthink
vector v_forward
vector origin
float trace_fraction
vector trace_plane_normal
#define spawn
#define use
float death_time
void CSQCProjectile(entity e, float clientanimate, int type, float docull)
float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:943
float damagedbycontents
Definition damage.qh:45
IntrusiveList g_damagedbycontents
Definition damage.qh:143
float spawnshieldtime
Definition damage.qh:61
vector w_org
int w_deathtype
float damageforcescale
vector w_backoff
float w_issilent
const int HITTYPE_BOUNCE
Definition all.qh:31
const int HITTYPE_SPAM
Definition all.qh:34
const int HITTYPE_SECONDARY
Definition all.qh:29
float speed
Definition dynlight.qc:9
#define pointparticles(effect, org, vel, howmany)
Definition effect.qh:7
void W_Electro_TouchExplode(entity this, entity toucher)
Definition electro.qc:240
void electro_orb_draw(entity this)
Definition electro.qc:8
void W_Electro_Explode_use(entity this, entity actor, entity trigger)
Definition electro.qc:235
void W_Electro_Attack_Bolt(Weapon thiswep, entity actor,.entity weaponentity)
Definition electro.qc:312
void W_Electro_Orb_ExplodeOverTime(entity this)
Definition electro.qc:136
void W_Electro_Orb_Touch(entity this, entity toucher)
Definition electro.qc:454
bool electro_orb_send(entity this, entity to, int sf)
Definition electro.qc:54
void W_Electro_Bolt_Think(entity this)
Definition electro.qc:249
void W_Electro_Explode(entity this, entity directhitentity)
Definition electro.qc:192
bool bot_secondary_electromooth
Definition electro.qc:594
void W_Electro_ExplodeComboThink(entity this)
Definition electro.qc:112
void W_Electro_Attack_Orb(Weapon thiswep, entity actor,.entity weaponentity)
Definition electro.qc:511
void electro_orb_setup(entity e)
Definition electro.qc:20
void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition electro.qc:475
void W_Electro_CheckAttack(Weapon thiswep, entity actor,.entity weaponentity, int fire)
Definition electro.qc:579
void W_Electro_Orb_Follow_Think(entity this)
Definition electro.qc:362
void W_Electro_Orb_Stick(entity this, entity to)
Definition electro.qc:393
void W_Electro_TriggerCombo(vector org, float rad, entity own)
Definition electro.qc:70
void W_Electro_ExplodeCombo(entity this)
Definition electro.qc:161
IntrusiveList LimitedElectroBallRubbleList
Definition electro.qh:100
float electro_count
Definition electro.qh:101
float electro_secondarytime
Definition electro.qh:102
ent angles
Definition ent_cs.qc:121
solid
Definition ent_cs.qc:165
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
ERASEABLE bool IL_CONTAINS(IntrusiveList this, entity it)
#define IL_NEW()
#define TC(T, sym)
Definition _all.inc:82
#define NET_HANDLE(id, param)
Definition net.qh:15
const int MSG_ENTITY
Definition net.qh:156
#define ReadVector()
Definition net.qh:350
#define WriteHeader(to, id)
Definition net.qh:265
#define REGISTER_NET_LINKED(id)
Definition net.qh:91
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:167
int ReadByte()
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition common.qc:684
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition common.qc:348
vector movedir
Definition viewloc.qh:18
float bound(float min, float value, float max)
float random(void)
float vlen(vector v)
vector vectoangles(vector v)
float min(float f,...)
void WriteByte(float data, float dest, float desto)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:129
const int MOVETYPE_FOLLOW
Definition movetypes.qh:141
float bouncefactor
Definition movetypes.qh:47
float move_time
Definition movetypes.qh:77
float move_movetype
Definition movetypes.qh:76
const int MOVETYPE_FLY
Definition movetypes.qh:134
const int MOVETYPE_TOSS
Definition movetypes.qh:135
float bouncestop
Definition movetypes.qh:46
const int MOVETYPE_BOUNCE
Definition movetypes.qh:139
var void func_null()
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1573
entity Notification
always last
Definition all.qh:81
#define METHOD(cname, name, prototype)
Definition oo.qh:269
#define NULL
Definition post.qh:14
float scale
Definition projectile.qc:14
const int PROJECTILE_ELECTRO
Definition projectiles.qh:3
const int PROJECTILE_ELECTRO_BEAM
Definition projectiles.qh:7
float health
Legacy fields for the resources. To be removed.
Definition resources.qh:9
void LimitedChildrenRubble(IntrusiveList list, string cname, int limit, void(entity) deleteproc, entity parent)
Definition rubble.qc:5
entity ListNewChildRubble(IntrusiveList list, entity child)
Definition rubble.qc:46
entity ReplaceOldListedChildRubble(IntrusiveList list, entity child, entity oldChild)
Definition rubble.qc:39
#define setthink(e, f)
#define getthink(e)
vector
Definition self.qh:92
vector org
Definition self.qh:92
entity entity toucher
Definition self.qh:72
#define settouch(e, f)
Definition self.qh:73
#define IS_INDEPENDENT_PLAYER(e)
Definition client.qh:312
float W_CheckProjectileDamage(entity inflictor, entity projowner, int deathtype, float exception)
Definition common.qc:45
void adaptor_think2use_hittype_splash(entity this)
Definition common.qc:106
const int MIF_SPLASH
Definition common.qh:46
int projectiledeathtype
Definition common.qh:21
#define PROJECTILE_TOUCH(e, t)
Definition common.qh:28
IntrusiveList g_projectiles
Definition common.qh:58
const int MIF_ARC
Definition common.qh:47
#define PROJECTILE_MAKETRIGGER(e)
Definition common.qh:34
const float VOL_BASE
Definition sound.qh:36
const int CH_SHOTS
Definition sound.qh:14
const int CH_WEAPON_A
Definition sound.qh:7
const float ATTEN_NORM
Definition sound.qh:30
#define sound(e, c, s, v, a)
Definition sound.qh:52
float spamsound(entity e, int chan, Sound samp, float vol, float _atten)
use this one if you might be causing spam (e.g.
Definition all.qc:124
#define PAGE_TEXT
Definition string.qh:642
#define PAR(...)
Adds an individually translatable paragraph to PAGE_TEXT without having to deal with strcat and sprin...
Definition string.qh:648
#define PAGE_TEXT_INIT()
Definition string.qh:641
const int DAMAGE_YES
Definition subs.qh:80
const int DAMAGE_NO
Definition subs.qh:79
const int DAMAGE_AIM
Definition subs.qh:81
float takedamage
Definition subs.qh:78
float ammo
Definition sv_turrets.qh:43
#define SAME_TEAM(a, b)
Definition teams.qh:241
#define DIFF_TEAM(a, b)
Definition teams.qh:242
entity realowner
vector w_shotdir
Definition tracing.qh:20
#define W_SetupShot_ProjectileSize(ent, wepent, mi, ma, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition tracing.qh:30
#define W_SetupProjVelocity_PRI(ent, wep)
Definition tracing.qh:67
vector w_shotorg
Definition tracing.qh:19
#define W_SetupProjVelocity_UP_SEC(ent, wep)
Definition tracing.qh:56
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
string W_Guide_Keybinds(Weapon wep)
Definition all.qc:824
void W_MuzzleFlash(Weapon thiswep, entity actor,.entity weaponentity, vector shotorg, vector shotdir)
Definition all.qc:715
string W_Guide_DPS_secondaryMultishotWithCombo(string name, string pri, string sec, string shots, string refire2, string combo, bool sec_variable)
Definition all.qc:1002
#define WEP_CVAR_PRI(wep, name)
Definition all.qh:338
#define WEP_CVAR(wep, name)
Definition all.qh:337
#define WEP_CVAR_SEC(wep, name)
Definition all.qh:339
void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use,.entity weaponentity)
void W_Reload(entity actor,.entity weaponentity, float sent_ammo_min, Sound sent_sound)
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
entity weaponentity_fld
float weapon_load[REGISTRY_MAX(Weapons)]