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 int myowner = ReadByte();
46 setorigin(this, this.origin);
47 this.colormap = entcs_GetClientColors(myowner - 1);
48 this.ltime = time + ReadByte() / 10.0;
49 // this.ltime = time + this.orb_lifetime;
51 return true;
52}
53#endif
54
55#ifdef SVQC
56bool electro_orb_send(entity this, entity to, int sf)
57{
58 int channel = MSG_ENTITY;
59 WriteHeader(channel, Electro_Orb);
60 WriteVector(channel, this.origin);
61 WriteByte(channel, etof(this.realowner));
62 // round time delta to a 1/10th of a second
63 WriteByte(channel, (this.ltime - time) * 10 + 0.5);
64 return true;
65}
66#endif
67
68#endif
69
70#ifdef SVQC
72
74{
75 entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(WEP_ELECTRO, combo_comboradius_thruwall));
76 for (; e; e = e.chain)
77 if (e.classname == "electro_orb")
78 {
79 // check if the ball we are exploding is not owned by an
80 // independent player which is not the player who shot the ball
81 if (IS_INDEPENDENT_PLAYER(e.realowner) && own != e.realowner)
82 continue;
83
84 // do we allow thruwall triggering?
85 if (WEP_CVAR(WEP_ELECTRO, combo_comboradius_thruwall))
86 {
87 // if distance is greater than thruwall distance, check to make sure it's not through a wall
88 if (vdist(e.WarpZone_findradius_dist, >, WEP_CVAR(WEP_ELECTRO, combo_comboradius_thruwall)))
89 {
91 if (trace_fraction != 1)
92 {
93 // trigger is through a wall and outside of thruwall range, abort
94 continue;
95 }
96 }
97 }
98
99 // change owner to whoever caused the combo explosion
100 e.realowner = own;
101 e.takedamage = DAMAGE_NO;
102 e.classname = "electro_orb_chain";
103
104 // now set the next one to trigger as well
106
107 // delay combo chains, looks cooler
108 float delay = (WEP_CVAR(WEP_ELECTRO, combo_speed))
109 ? vlen(e.WarpZone_findradius_dist) / WEP_CVAR(WEP_ELECTRO, combo_speed)
110 : 0;
111 e.nextthink = time + delay;
112 }
113}
114
116{
117 if (time >= this.ltime)
118 {
119 delete(this);
120 return;
121 }
122
123 this.nextthink = time;
124
125 RadiusDamage(this, this.realowner,
126 PHYS_INPUT_TIMELENGTH * WEP_CVAR(WEP_ELECTRO, combo_damage),
127 PHYS_INPUT_TIMELENGTH * WEP_CVAR(WEP_ELECTRO, combo_edgedamage),
128 WEP_CVAR(WEP_ELECTRO, combo_radius),
129 NULL,
130 NULL,
131 0,
133 this.weaponentity_fld,
134 NULL
135 );
136 this.projectiledeathtype |= HITTYPE_SPAM; // ensure it doesn't spam its effect
137}
138
140{
141 entity newproj = spawn();
142 newproj.classname = this.classname;
143 newproj.solid = this.solid;
144 setorigin(newproj, this.origin);
145 setmodel(newproj, MDL_PROJECTILE_ELECTRO);
146 setsize(newproj, this.mins, this.maxs);
147 newproj.owner = this.owner;
148 newproj.realowner = this.realowner;
149 newproj.weaponentity_fld = this.weaponentity_fld;
150 newproj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_BOUNCE; // use THIS type for a combo because primary can't bounce
151
153 newproj.nextthink = time;
154 newproj.ltime = time + WEP_CVAR(WEP_ELECTRO, combo_duration);
155 set_movetype(newproj, MOVETYPE_NONE);
156
157 Net_LinkEntity(newproj, true, 0, electro_orb_send);
158 newproj.SendFlags = 0xFFFFFF;
159
160 // fire the first damage tick immediately
161 getthink(newproj)(newproj);
162}
163
165{
166 W_Electro_TriggerCombo(this.origin, WEP_CVAR(WEP_ELECTRO, combo_comboradius), this.realowner);
167
168 this.event_damage = func_null;
169 if (!this.velocity)
170 this.velocity = this.movedir; // .velocity must be != '0 0 0' for particle fx and decal to work
171
172 if (WEP_CVAR(WEP_ELECTRO, combo_duration))
173 {
175
176 delete(this);
177 return;
178 }
179
180 RadiusDamage(this, this.realowner,
181 WEP_CVAR(WEP_ELECTRO, combo_damage),
182 WEP_CVAR(WEP_ELECTRO, combo_edgedamage),
183 WEP_CVAR(WEP_ELECTRO, combo_radius),
184 NULL,
185 NULL,
186 WEP_CVAR(WEP_ELECTRO, combo_force),
187 WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
188 this.weaponentity_fld,
189 NULL
190 );
191
192 delete(this);
193}
194
195void W_Electro_Explode(entity this, entity directhitentity)
196{
197 if (directhitentity.takedamage == DAMAGE_AIM
198 && IS_PLAYER(directhitentity) && DIFF_TEAM(this.realowner, directhitentity) && !IS_DEAD(directhitentity)
199 && IsFlying(directhitentity))
200 Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
201
202 this.event_damage = func_null;
203 this.takedamage = DAMAGE_NO;
204 if (!this.velocity)
205 this.velocity = this.movedir; // .velocity must be != '0 0 0' for particle fx and decal to work
206
207 if (this.move_movetype == MOVETYPE_BOUNCE || this.classname == "electro_orb") // TODO: classname is more reliable anyway?
208 RadiusDamage(this, this.realowner,
209 WEP_CVAR_SEC(WEP_ELECTRO, damage),
210 WEP_CVAR_SEC(WEP_ELECTRO, edgedamage),
211 WEP_CVAR_SEC(WEP_ELECTRO, radius),
212 NULL,
213 NULL,
214 WEP_CVAR_SEC(WEP_ELECTRO, force),
216 this.weaponentity_fld,
217 directhitentity
218 );
219 else
220 {
221 W_Electro_TriggerCombo(this.origin, WEP_CVAR_PRI(WEP_ELECTRO, comboradius), this.realowner);
222 RadiusDamage(this, this.realowner,
223 WEP_CVAR_PRI(WEP_ELECTRO, damage),
224 WEP_CVAR_PRI(WEP_ELECTRO, edgedamage),
225 WEP_CVAR_PRI(WEP_ELECTRO, radius),
226 NULL,
227 NULL,
228 WEP_CVAR_PRI(WEP_ELECTRO, force),
230 this.weaponentity_fld,
231 directhitentity
232 );
233 }
234
235 delete(this);
236}
237
238void W_Electro_Explode_use(entity this, entity actor, entity trigger)
239{
240 W_Electro_Explode(this, trigger);
241}
242
248
249
250//void sys_phys_update_single(entity this);
251
253{
254 // sys_phys_update_single(this);
255 if (time >= this.ltime)
256 {
257 this.use(this, NULL, NULL);
258 return;
259 }
260
261 if (WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_radius))
262 {
263 int found = 0;
264 entity e = WarpZone_FindRadius(this.origin, WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_radius), true);
265
266 // loop through nearby orbs and trigger them
267 for (; e; e = e.chain)
268 if (e.classname == "electro_orb")
269 {
270 // check if the ball we are exploding is not owned by an
271 // independent player which is not the player who shot the ball
272 if (IS_INDEPENDENT_PLAYER(e.realowner) && this.realowner != e.realowner)
273 continue;
274
275 bool explode;
276 if (this.owner == e.owner)
277 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_own);
278 else if (SAME_TEAM(this.owner, e.owner))
279 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_teammate);
280 else
281 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_enemy);
282
283 if (explode)
284 {
285 // change owner to whoever caused the combo explosion
286 e.realowner = this.realowner;
287 e.takedamage = DAMAGE_NO;
288 e.classname = "electro_orb_chain";
289
290 // Only first orb explosion uses midaircombo_speed, others use the normal combo_speed.
291 // This allows to avoid the delay on the first explosion which looks better
292 // (the bolt and orb should explode together because they interacted together)
293 // while keeping the chaining delay.
295 float delay = (WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_speed))
296 ? vlen(e.WarpZone_findradius_dist) / WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_speed)
297 : 0;
298 e.nextthink = time + delay;
299
300 ++found;
301 }
302 }
303
304 // if we triggered an orb, should we explode? if not, lets try again next time
305 if (found && WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_explode))
306 this.use(this, NULL, NULL);
307 else
308 this.nextthink = min(time + WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_interval), this.ltime);
309 }
310 else
311 this.nextthink = this.ltime;
312 // this.nextthink = time;
313}
314
315void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
316{
317 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(WEP_ELECTRO, ammo), weaponentity);
318
320 actor,
321 weaponentity,
322 '0 0 -3',
323 '0 0 -3',
324 false,
325 2,
326 SND_ELECTRO_FIRE,
328 WEP_CVAR_PRI(WEP_ELECTRO, damage),
329 thiswep.m_id
330 );
331
332 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
333
334 entity proj = new(electro_bolt);
335 proj.owner = proj.realowner = actor;
336 proj.bot_dodge = true;
337 proj.bot_dodgerating = WEP_CVAR_PRI(WEP_ELECTRO, damage);
338 proj.use = W_Electro_Explode_use;
340 proj.nextthink = time;
341 proj.ltime = time + WEP_CVAR_PRI(WEP_ELECTRO, lifetime);
343 proj.projectiledeathtype = thiswep.m_id;
344 proj.weaponentity_fld = weaponentity;
345 setorigin(proj, w_shotorg);
346
347 // if (IS_CSQC)
349 W_SetupProjVelocity_PRI(proj, WEP_ELECTRO);
350 proj.angles = vectoangles(proj.velocity);
352 setsize(proj, '0 0 -3', '0 0 -3');
353 proj.flags = FL_PROJECTILE;
354 IL_PUSH(g_projectiles, proj);
355 IL_PUSH(g_bot_dodge, proj);
356 proj.missile_flags = MIF_SPLASH;
357
358 CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
359
360 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
361 // proj.com_phys_pos = proj.origin;
362 // proj.com_phys_vel = proj.velocity;
363}
364
366{
367 if (time > this.death_time)
368 {
370 return;
371 }
372 if (this.move_movetype == MOVETYPE_FOLLOW)
373 {
374 int lost = LostMovetypeFollow(this);
375 if (lost == 2)
376 {
377 // FIXME if player disconnected, it isn't possible to drop the orb at player's origin
378 // see comment in LostMovetypeFollow implementation
379 delete(this);
380 return;
381 }
382 if (lost)
383 {
384 // drop the orb at the corpse's location
387
389 this.nextthink = this.death_time;
390 return;
391 }
392 }
393 this.nextthink = time;
394}
395
397{
398 entity newproj = spawn();
399 newproj.classname = this.classname;
400
401 newproj.bot_dodge = this.bot_dodge;
402 newproj.bot_dodgerating = this.bot_dodgerating;
403
404 newproj.owner = this.owner;
405 newproj.realowner = this.realowner;
406 PROJECTILE_MAKETRIGGER(newproj);
407 setorigin(newproj, this.origin);
408 setmodel(newproj, MDL_PROJECTILE_ELECTRO);
409 setsize(newproj, this.mins, this.maxs);
410 newproj.angles = vectoangles(-trace_plane_normal); // face against the surface
411 newproj.traileffectnum = _particleeffectnum(EFFECT_TR_NEXUIZPLASMA.eent_eff_name);
412
413 newproj.movedir = -trace_plane_normal;
414
415 newproj.takedamage = this.takedamage;
416 newproj.damageforcescale = this.damageforcescale;
417 SetResourceExplicit(newproj, RES_HEALTH, GetResource(this, RES_HEALTH));
418 newproj.event_damage = this.event_damage;
419 newproj.spawnshieldtime = this.spawnshieldtime;
420 newproj.damagedbycontents = true;
422
423 set_movetype(newproj, MOVETYPE_NONE); // lock the orb in place
424 newproj.projectiledeathtype = this.projectiledeathtype;
425 newproj.weaponentity_fld = this.weaponentity_fld;
426
427 settouch(newproj, func_null);
428 if (WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime) > 0)
429 newproj.death_time = time + WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime);
430 else
431 newproj.death_time = this.death_time;
432 newproj.use = this.use;
433 newproj.flags = this.flags;
434 IL_PUSH(g_projectiles, newproj);
435 IL_PUSH(g_bot_dodge, newproj);
436
437 // check if limits are enabled (we can tell by checking if the original orb is listed) and push it to the list if so
440
441 delete(this);
442
443 if (to)
444 {
445 SetMovetypeFollow(newproj, to);
446
448 newproj.nextthink = time;
449 }
450 else
451 {
453 newproj.nextthink = newproj.death_time;
454 }
455}
456
458{
460 if (toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(WEP_ELECTRO, touchexplode))
462 else if (toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
463 {
464 //UpdateCSQCProjectile(this);
465 spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
467
468 if (WEP_CVAR_SEC(WEP_ELECTRO, stick))
469 {
470 if (WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime) == 0)
472 else
474 }
475 }
476}
477
478void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
479{
480 if (GetResource(this, RES_HEALTH) <= 0)
481 return;
482
483 // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
484 float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
485
486 if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
487 return; // g_projectiles_damage says to halt
488
489 TakeResource(this, RES_HEALTH, damage);
490 if (GetResource(this, RES_HEALTH) <= 0)
491 {
492 this.takedamage = DAMAGE_NO;
493 this.nextthink = time;
494 if (is_combo)
495 {
496 // change owner to whoever caused the combo explosion
497 this.realowner = inflictor.realowner;
498 this.classname = "electro_orb_chain";
500 // delay combo chains, looks cooler
501 // bound the length, inflictor may be in a galaxy far far away (warpzones)
502 float len = min(WEP_CVAR(WEP_ELECTRO, combo_radius), vlen(this.origin - inflictor.origin));
503 float delay = len / WEP_CVAR(WEP_ELECTRO, combo_speed);
504 this.nextthink = time + delay;
505 }
506 else
507 {
509 setthink(this, adaptor_think2use); // not _hittype_splash, as this runs "immediately"
510 }
511 }
512}
513
514void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
515{
516 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(WEP_ELECTRO, ammo), weaponentity);
517
519 actor,
520 weaponentity,
521 '-4 -4 -4',
522 '4 4 4',
523 false,
524 2,
525 SND_ELECTRO_FIRE2,
527 WEP_CVAR_SEC(WEP_ELECTRO, damage),
528 thiswep.m_id | HITTYPE_SECONDARY
529 );
530
531 w_shotdir = v_forward; // no TrueAim for grenades please
532
533 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
534
535 entity proj = new(electro_orb);
536 proj.owner = proj.realowner = actor;
537 proj.use = W_Electro_Explode_use;
539 proj.bot_dodge = true;
540 proj.bot_dodgerating = WEP_CVAR_SEC(WEP_ELECTRO, damage);
541 proj.nextthink = time + WEP_CVAR_SEC(WEP_ELECTRO, lifetime);
542 proj.death_time = time + WEP_CVAR_SEC(WEP_ELECTRO, lifetime);
544 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
545 proj.weaponentity_fld = weaponentity;
546 setorigin(proj, w_shotorg);
547
548 //proj.glow_size = 50;
549 //proj.glow_color = 45;
551 W_SetupProjVelocity_UP_SEC(proj, WEP_ELECTRO);
553 setsize(proj, '-4 -4 -4', '4 4 4');
554 proj.takedamage = DAMAGE_YES;
555 proj.damageforcescale = WEP_CVAR_SEC(WEP_ELECTRO, damageforcescale);
556 SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_SEC(WEP_ELECTRO, health));
557 proj.event_damage = W_Electro_Orb_Damage;
558 proj.flags = FL_PROJECTILE;
559 IL_PUSH(g_projectiles, proj);
560 IL_PUSH(g_bot_dodge, proj);
561 proj.damagedbycontents = (WEP_CVAR_SEC(WEP_ELECTRO, damagedbycontents));
562 if (proj.damagedbycontents)
564
565 proj.bouncefactor = WEP_CVAR_SEC(WEP_ELECTRO, bouncefactor);
566 proj.bouncestop = WEP_CVAR_SEC(WEP_ELECTRO, bouncestop);
567 proj.missile_flags = MIF_SPLASH | MIF_ARC;
568
569 if (WEP_CVAR_SEC(WEP_ELECTRO, limit) > 0)
570 {
575 }
576
577 CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
578
579 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
580}
581
582void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
583{
584 if (actor.(weaponentity).electro_count > 1
586 && weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
587 {
588 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
589 --actor.(weaponentity).electro_count;
590 actor.(weaponentity).electro_secondarytime = time;
591 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_ELECTRO, animtime), W_Electro_CheckAttack);
592 return;
593 }
594 w_ready(thiswep, actor, weaponentity, fire);
595}
596
597.bool bot_secondary_electromooth; // whatever a mooth is
598
599METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
600{
601 PHYS_INPUT_BUTTON_ATCK(actor) = PHYS_INPUT_BUTTON_ATCK2(actor) = false;
602 if (vdist(actor.origin - actor.enemy.origin, >, 1000))
603 actor.bot_secondary_electromooth = false;
604 if (!actor.bot_secondary_electromooth)
605 {
606 float shoot = (WEP_CVAR_PRI(WEP_ELECTRO, speed))
607 ? bot_aim(actor, weaponentity, WEP_CVAR_PRI(WEP_ELECTRO, speed), 0, WEP_CVAR_PRI(WEP_ELECTRO, lifetime), false, true)
608 : bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
609
610 if (shoot)
611 {
612 PHYS_INPUT_BUTTON_ATCK(actor) = true;
613 if (random() < 0.01)
614 actor.bot_secondary_electromooth = true;
615 }
616 }
617 else
618 {
619 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))
620 {
621 PHYS_INPUT_BUTTON_ATCK2(actor) = true;
622 if (random() < 0.03)
623 actor.bot_secondary_electromooth = false;
624 }
625 }
626}
627
628METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
629{
630 if (autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
631 {
632 float ammo_amount = 0;
633 if (actor.(weaponentity).clip_load >= WEP_CVAR_PRI(WEP_ELECTRO, ammo))
634 ammo_amount = 1;
635 if (actor.(weaponentity).clip_load >= WEP_CVAR_SEC(WEP_ELECTRO, ammo))
636 ++ammo_amount;
637
638 if (!ammo_amount)
639 {
640 thiswep.wr_reload(thiswep, actor, weaponentity);
641 return;
642 }
643 }
644
645 if (fire & 1)
646 {
647 if (time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(WEP_ELECTRO, refire2) * W_WeaponRateFactor(actor)
648 && weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_ELECTRO, refire)))
649 {
650 W_Electro_Attack_Bolt(thiswep, actor, weaponentity);
651 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_ELECTRO, animtime), w_ready);
652 }
653 }
654 else if (fire & 2)
655 {
656 if (time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(WEP_ELECTRO, refire) * W_WeaponRateFactor(actor)
657 && weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
658 {
659 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
660 actor.(weaponentity).electro_count = WEP_CVAR_SEC(WEP_ELECTRO, count);
661 actor.(weaponentity).electro_secondarytime = time;
662 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_ELECTRO, animtime), W_Electro_CheckAttack);
663 }
664 }
665}
666
667METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
668{
669 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(WEP_ELECTRO, ammo);
670 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(WEP_ELECTRO, ammo);
671 return ammo_amount;
672}
673
674METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
675{
676 float ammo_amount;
677 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.
678 {
679 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo) + WEP_CVAR_PRI(WEP_ELECTRO, ammo);
680 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo) + WEP_CVAR_PRI(WEP_ELECTRO, ammo);
681 }
682 else
683 {
684 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo);
685 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo);
686 }
687 return ammo_amount;
688}
689
690METHOD(Electro, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
691{
692 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(WEP_ELECTRO, ammo), WEP_CVAR_SEC(WEP_ELECTRO, ammo)), SND_RELOAD);
693}
694
695METHOD(Electro, wr_suicidemessage, Notification(entity thiswep))
696{
698 return WEAPON_ELECTRO_SUICIDE_ORBS;
699 else
700 return WEAPON_ELECTRO_SUICIDE_BOLT;
701}
702
703METHOD(Electro, wr_killmessage, Notification(entity thiswep))
704{
706 {
707 return WEAPON_ELECTRO_MURDER_ORBS;
708 }
709 else
710 {
712 return WEAPON_ELECTRO_MURDER_COMBO;
713 else
714 return WEAPON_ELECTRO_MURDER_BOLT;
715 }
716}
717
718#endif // GAMEQC
719#ifdef CSQC
720
721METHOD(Electro, wr_impacteffect, void(entity thiswep, entity actor))
722{
723 vector org2 = w_org + w_backoff * 2;
725 {
726 pointparticles(EFFECT_ELECTRO_BALLEXPLODE, org2, '0 0 0', 1);
727 if (!w_issilent)
728 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
729 }
730 else
731 {
733 {
734 // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
735 pointparticles(EFFECT_ELECTRO_COMBO, org2, '0 0 0', 1);
736 if (!w_issilent)
737 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT_COMBO, VOL_BASE, ATTEN_NORM);
738 }
739 else
740 {
741 pointparticles(EFFECT_ELECTRO_IMPACT, org2, '0 0 0', 1);
742 if (!w_issilent)
743 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
744 }
745 }
746}
747
748#endif // CSQC
749#ifdef MENUQC
752
753METHOD(Electro, describe, string(Electro this))
754{
755 TC(Electro, this);
757 PAR(_("The %s shoots electric balls forwards, dealing some splash damage when they burst on impact."), COLORED_NAME(this));
758 PAR(_("The secondary fire launches orbs that are influenced by gravity, "
759 "so they can be laid around the map at high traffic locations (like at %s flag bases) to damage enemies that walk by. "
760 "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));
761 PAR(_("It consumes some %s ammo for each ball / orb."), COLORED_NAME(ITEM_Cells));
762 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. "
763 "Since the primary fire doesn't travel particularly fast, the %s is not useful in many other situations."), COLORED_NAME(this), COLORED_NAME(this));
764 PAR(W_Guide_Keybinds(this));
765 PAR(W_Guide_DPS_secondaryMultishotWithCombo(this.netname, "primary", "secondary", "secondary_count", "secondary_refire2", "combo", true));
766 return PAGE_TEXT;
767}
768
769#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.
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:189
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
float colormap
vector v_forward
vector origin
float trace_fraction
float solid
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:934
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
set manually after projectile has bounced
Definition all.qh:33
const int HITTYPE_SPAM
set manually after first RadiusDamage, stops effect spam
Definition all.qh:36
const int HITTYPE_SECONDARY
Definition all.qh:31
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:243
void electro_orb_draw(entity this)
Definition electro.qc:8
void W_Electro_Explode_use(entity this, entity actor, entity trigger)
Definition electro.qc:238
void W_Electro_Attack_Bolt(Weapon thiswep, entity actor,.entity weaponentity)
Definition electro.qc:315
void W_Electro_Orb_ExplodeOverTime(entity this)
Definition electro.qc:139
void W_Electro_Orb_Touch(entity this, entity toucher)
Definition electro.qc:457
bool electro_orb_send(entity this, entity to, int sf)
Definition electro.qc:56
void W_Electro_Bolt_Think(entity this)
Definition electro.qc:252
void W_Electro_Explode(entity this, entity directhitentity)
Definition electro.qc:195
bool bot_secondary_electromooth
Definition electro.qc:597
void W_Electro_ExplodeComboThink(entity this)
Definition electro.qc:115
void W_Electro_Attack_Orb(Weapon thiswep, entity actor,.entity weaponentity)
Definition electro.qc:514
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:478
void W_Electro_CheckAttack(Weapon thiswep, entity actor,.entity weaponentity, int fire)
Definition electro.qc:582
void W_Electro_Orb_Follow_Think(entity this)
Definition electro.qc:365
void W_Electro_Orb_Stick(entity this, entity to)
Definition electro.qc:396
void W_Electro_TriggerCombo(vector org, float rad, entity own)
Definition electro.qc:73
void W_Electro_ExplodeCombo(entity this)
Definition electro.qc:164
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:146
WriteByte(chan, ent.angles.y/DEC_FACTOR)
SetResourceExplicit(ent, RES_ARMOR, ReadByte() *DEC_FACTOR)) ENTCS_PROP(NAME
int entcs_GetClientColors(int i)
Definition ent_cs.qh:111
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:349
#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:660
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition common.qc:328
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,...)
#define etof(e)
Definition misc.qh:25
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:133
const int MOVETYPE_FOLLOW
Definition movetypes.qh:145
float bouncefactor
Definition movetypes.qh:45
float move_time
Definition movetypes.qh:81
float move_movetype
Definition movetypes.qh:80
const int MOVETYPE_FLY
Definition movetypes.qh:138
const int MOVETYPE_TOSS
Definition movetypes.qh:139
float bouncestop
Definition movetypes.qh:44
const int MOVETYPE_BOUNCE
Definition movetypes.qh:143
var void func_null()
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1500
entity Notification
always last
Definition all.qh:85
#define METHOD(cname, name, prototype)
Definition oo.qh:274
#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:96
vector org
Definition self.qh:96
entity entity toucher
Definition self.qh:76
#define settouch(e, f)
Definition self.qh:77
#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:651
#define PAR(...)
Adds an individually translatable paragraph to PAGE_TEXT without having to deal with strcat and sprin...
Definition string.qh:657
#define PAGE_TEXT_INIT()
Definition string.qh:650
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)]