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
115const float ELECTRO_COMBO_OVERTIME_DELAY = 0.0625; // (1/16) exact multiple of default frametime 0.015625 (1/64)
117{
118 // NOTE: W_Hook_ExplodeThink uses similar code, please keep them in sync
119 float dt = time - this.teleport_time;
120
121 float dmg_remaining_next = bound(0, 1 - dt / WEP_CVAR(WEP_ELECTRO, combo_duration), 1);
122
123 float f = this.dmg_last - dmg_remaining_next;
124 this.dmg_last = dmg_remaining_next;
125
126 RadiusDamage(this, this.realowner,
127 f * WEP_CVAR(WEP_ELECTRO, combo_damage),
128 f * WEP_CVAR(WEP_ELECTRO, combo_edgedamage),
129 WEP_CVAR(WEP_ELECTRO, combo_radius),
130 NULL,
131 NULL,
132 0,
134 this.weaponentity_fld,
135 NULL
136 );
137 this.projectiledeathtype |= HITTYPE_SPAM; // ensure it doesn't spam its effect
138
139 if (dt < WEP_CVAR(WEP_ELECTRO, combo_duration))
141 else
142 delete(this);
143}
144
146{
147 entity newproj = spawn();
148 newproj.classname = this.classname;
149 newproj.solid = this.solid;
150 setorigin(newproj, this.origin);
151 setmodel(newproj, MDL_PROJECTILE_ELECTRO);
152 setsize(newproj, this.mins, this.maxs);
153 newproj.owner = this.owner;
154 newproj.realowner = this.realowner;
155 newproj.weaponentity_fld = this.weaponentity_fld;
156 newproj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_BOUNCE; // use THIS type for a combo because primary can't bounce
157
159 newproj.nextthink = time + ELECTRO_COMBO_OVERTIME_DELAY;
160 newproj.teleport_time = time;
161 newproj.dmg_last = 1;
162 set_movetype(newproj, MOVETYPE_NONE);
163
164 Net_LinkEntity(newproj, true, 0, electro_orb_send);
165 newproj.SendFlags = 0xFFFFFF;
166}
167
169{
170 W_Electro_TriggerCombo(this.origin, WEP_CVAR(WEP_ELECTRO, combo_comboradius), this.realowner);
171
172 this.event_damage = func_null;
173 if (!this.velocity)
174 this.velocity = this.movedir; // .velocity must be != '0 0 0' for particle fx and decal to work
175
176 if (WEP_CVAR(WEP_ELECTRO, combo_duration))
177 {
179
180 delete(this);
181 return;
182 }
183
184 RadiusDamage(this, this.realowner,
185 WEP_CVAR(WEP_ELECTRO, combo_damage),
186 WEP_CVAR(WEP_ELECTRO, combo_edgedamage),
187 WEP_CVAR(WEP_ELECTRO, combo_radius),
188 NULL,
189 NULL,
190 WEP_CVAR(WEP_ELECTRO, combo_force),
191 WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
192 this.weaponentity_fld,
193 NULL
194 );
195
196 delete(this);
197}
198
199void W_Electro_Explode(entity this, entity directhitentity)
200{
201 if (directhitentity.takedamage == DAMAGE_AIM
202 && IS_PLAYER(directhitentity) && DIFF_TEAM(this.realowner, directhitentity) && !IS_DEAD(directhitentity)
203 && IsFlying(directhitentity))
204 Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
205
206 this.event_damage = func_null;
207 this.takedamage = DAMAGE_NO;
208 if (!this.velocity)
209 this.velocity = this.movedir; // .velocity must be != '0 0 0' for particle fx and decal to work
210
211 if (this.move_movetype == MOVETYPE_BOUNCE || this.classname == "electro_orb") // TODO: classname is more reliable anyway?
212 RadiusDamage(this, this.realowner,
213 WEP_CVAR_SEC(WEP_ELECTRO, damage),
214 WEP_CVAR_SEC(WEP_ELECTRO, edgedamage),
215 WEP_CVAR_SEC(WEP_ELECTRO, radius),
216 NULL,
217 NULL,
218 WEP_CVAR_SEC(WEP_ELECTRO, force),
220 this.weaponentity_fld,
221 directhitentity
222 );
223 else
224 {
225 W_Electro_TriggerCombo(this.origin, WEP_CVAR_PRI(WEP_ELECTRO, comboradius), this.realowner);
226 RadiusDamage(this, this.realowner,
227 WEP_CVAR_PRI(WEP_ELECTRO, damage),
228 WEP_CVAR_PRI(WEP_ELECTRO, edgedamage),
229 WEP_CVAR_PRI(WEP_ELECTRO, radius),
230 NULL,
231 NULL,
232 WEP_CVAR_PRI(WEP_ELECTRO, force),
234 this.weaponentity_fld,
235 directhitentity
236 );
237 }
238
239 delete(this);
240}
241
242void W_Electro_Explode_use(entity this, entity actor, entity trigger)
243{
244 W_Electro_Explode(this, trigger);
245}
246
252
253
254//void sys_phys_update_single(entity this);
255
257{
258 // sys_phys_update_single(this);
259 if (time >= this.ltime)
260 {
261 this.use(this, NULL, NULL);
262 return;
263 }
264
265 if (WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_radius))
266 {
267 int found = 0;
268 entity e = WarpZone_FindRadius(this.origin, WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_radius), true);
269
270 // loop through nearby orbs and trigger them
271 for (; e; e = e.chain)
272 if (e.classname == "electro_orb")
273 {
274 // check if the ball we are exploding is not owned by an
275 // independent player which is not the player who shot the ball
276 if (IS_INDEPENDENT_PLAYER(e.realowner) && this.realowner != e.realowner)
277 continue;
278
279 bool explode;
280 if (this.owner == e.owner)
281 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_own);
282 else if (SAME_TEAM(this.owner, e.owner))
283 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_teammate);
284 else
285 explode = WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_enemy);
286
287 if (explode)
288 {
289 // change owner to whoever caused the combo explosion
290 e.realowner = this.realowner;
291 e.takedamage = DAMAGE_NO;
292 e.classname = "electro_orb_chain";
293
294 // Only first orb explosion uses midaircombo_speed, others use the normal combo_speed.
295 // This allows to avoid the delay on the first explosion which looks better
296 // (the bolt and orb should explode together because they interacted together)
297 // while keeping the chaining delay.
299 float delay = (WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_speed))
300 ? vlen(e.WarpZone_findradius_dist) / WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_speed)
301 : 0;
302 e.nextthink = time + delay;
303
304 ++found;
305 }
306 }
307
308 // if we triggered an orb, should we explode? if not, lets try again next time
309 if (found && WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_explode))
310 this.use(this, NULL, NULL);
311 else
312 this.nextthink = min(time + WEP_CVAR_PRI(WEP_ELECTRO, midaircombo_interval), this.ltime);
313 }
314 else
315 this.nextthink = this.ltime;
316 // this.nextthink = time;
317}
318
319void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
320{
321 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(WEP_ELECTRO, ammo), weaponentity);
322
324 actor,
325 weaponentity,
326 '0 0 -3',
327 '0 0 -3',
328 false,
329 2,
330 SND_ELECTRO_FIRE,
332 WEP_CVAR_PRI(WEP_ELECTRO, damage),
333 thiswep.m_id
334 );
335
336 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
337
338 entity proj = new(electro_bolt);
339 proj.owner = proj.realowner = actor;
340 proj.bot_dodge = true;
341 proj.bot_dodgerating = WEP_CVAR_PRI(WEP_ELECTRO, damage);
342 proj.use = W_Electro_Explode_use;
344 proj.nextthink = time;
345 proj.ltime = time + WEP_CVAR_PRI(WEP_ELECTRO, lifetime);
347 proj.projectiledeathtype = thiswep.m_id;
348 proj.weaponentity_fld = weaponentity;
349 setorigin(proj, w_shotorg);
350
351 // if (IS_CSQC)
353 W_SetupProjVelocity_PRI(proj, WEP_ELECTRO);
354 proj.angles = vectoangles(proj.velocity);
356 setsize(proj, '0 0 -3', '0 0 -3');
357 proj.flags = FL_PROJECTILE;
358 IL_PUSH(g_projectiles, proj);
359 IL_PUSH(g_bot_dodge, proj);
360 proj.missile_flags = MIF_SPLASH;
361
362 CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
363
364 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
365 // proj.com_phys_pos = proj.origin;
366 // proj.com_phys_vel = proj.velocity;
367}
368
370{
371 if (time > this.death_time)
372 {
374 return;
375 }
376 if (this.move_movetype == MOVETYPE_FOLLOW)
377 {
378 int lost = LostMovetypeFollow(this);
379 if (lost == 2)
380 {
381 // FIXME if player disconnected, it isn't possible to drop the orb at player's origin
382 // see comment in LostMovetypeFollow implementation
383 delete(this);
384 return;
385 }
386 if (lost)
387 {
388 // drop the orb at the corpse's location
391
393 this.nextthink = this.death_time;
394 return;
395 }
396 }
397 this.nextthink = time;
398}
399
401{
402 entity newproj = spawn();
403 newproj.classname = this.classname;
404
405 newproj.bot_dodge = this.bot_dodge;
406 newproj.bot_dodgerating = this.bot_dodgerating;
407
408 newproj.owner = this.owner;
409 newproj.realowner = this.realowner;
410 PROJECTILE_MAKETRIGGER(newproj);
411 setorigin(newproj, this.origin);
412 setmodel(newproj, MDL_PROJECTILE_ELECTRO);
413 setsize(newproj, this.mins, this.maxs);
414 newproj.angles = vectoangles(-trace_plane_normal); // face against the surface
415 newproj.traileffectnum = _particleeffectnum(EFFECT_TR_NEXUIZPLASMA.eent_eff_name);
416
417 newproj.movedir = -trace_plane_normal;
418
419 newproj.takedamage = this.takedamage;
420 newproj.damageforcescale = this.damageforcescale;
421 SetResourceExplicit(newproj, RES_HEALTH, GetResource(this, RES_HEALTH));
422 newproj.event_damage = this.event_damage;
423 newproj.spawnshieldtime = this.spawnshieldtime;
424 newproj.damagedbycontents = true;
426
427 set_movetype(newproj, MOVETYPE_NONE); // lock the orb in place
428 newproj.projectiledeathtype = this.projectiledeathtype;
429 newproj.weaponentity_fld = this.weaponentity_fld;
430
431 settouch(newproj, func_null);
432 if (WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime) > 0)
433 newproj.death_time = time + WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime);
434 else
435 newproj.death_time = this.death_time;
436 newproj.use = this.use;
437 newproj.flags = this.flags;
438 IL_PUSH(g_projectiles, newproj);
439 IL_PUSH(g_bot_dodge, newproj);
440
441 // check if limits are enabled (we can tell by checking if the original orb is listed) and push it to the list if so
444
445 delete(this);
446
447 if (to)
448 {
449 SetMovetypeFollow(newproj, to);
450
452 newproj.nextthink = time;
453 }
454 else
455 {
457 newproj.nextthink = newproj.death_time;
458 }
459}
460
462{
464 if (toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(WEP_ELECTRO, touchexplode))
466 else if (toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
467 {
468 //UpdateCSQCProjectile(this);
469 spamsound(this, CH_SHOTS, SND_ELECTRO_BOUNCE, VOL_BASE, ATTEN_NORM);
471
472 if (WEP_CVAR_SEC(WEP_ELECTRO, stick))
473 {
474 if (WEP_CVAR_SEC(WEP_ELECTRO, stick_lifetime) == 0)
476 else
478 }
479 }
480}
481
482void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
483{
484 if (GetResource(this, RES_HEALTH) <= 0)
485 return;
486
487 // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
488 float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
489
490 if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
491 return; // g_projectiles_damage says to halt
492
493 TakeResource(this, RES_HEALTH, damage);
494 if (GetResource(this, RES_HEALTH) <= 0)
495 {
496 this.takedamage = DAMAGE_NO;
497 this.nextthink = time;
498 if (is_combo)
499 {
500 // change owner to whoever caused the combo explosion
501 this.realowner = inflictor.realowner;
502 this.classname = "electro_orb_chain";
504 // delay combo chains, looks cooler
505 // bound the length, inflictor may be in a galaxy far far away (warpzones)
506 float len = min(WEP_CVAR(WEP_ELECTRO, combo_radius), vlen(this.origin - inflictor.origin));
507 float delay = len / WEP_CVAR(WEP_ELECTRO, combo_speed);
508 this.nextthink = time + delay;
509 }
510 else
511 {
513 setthink(this, adaptor_think2use); // not _hittype_splash, as this runs "immediately"
514 }
515 }
516}
517
518void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
519{
520 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(WEP_ELECTRO, ammo), weaponentity);
521
523 actor,
524 weaponentity,
525 '-4 -4 -4',
526 '4 4 4',
527 false,
528 2,
529 SND_ELECTRO_FIRE2,
531 WEP_CVAR_SEC(WEP_ELECTRO, damage),
532 thiswep.m_id | HITTYPE_SECONDARY
533 );
534
535 w_shotdir = v_forward; // no TrueAim for grenades please
536
537 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
538
539 entity proj = new(electro_orb);
540 proj.owner = proj.realowner = actor;
541 proj.use = W_Electro_Explode_use;
543 proj.bot_dodge = true;
544 proj.bot_dodgerating = WEP_CVAR_SEC(WEP_ELECTRO, damage);
545 proj.nextthink = time + WEP_CVAR_SEC(WEP_ELECTRO, lifetime);
546 proj.death_time = time + WEP_CVAR_SEC(WEP_ELECTRO, lifetime);
548 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
549 proj.weaponentity_fld = weaponentity;
550 setorigin(proj, w_shotorg);
551
552 //proj.glow_size = 50;
553 //proj.glow_color = 45;
555 W_SetupProjVelocity_UP_SEC(proj, WEP_ELECTRO);
557 setsize(proj, '-4 -4 -4', '4 4 4');
558 proj.takedamage = DAMAGE_YES;
559 proj.damageforcescale = WEP_CVAR_SEC(WEP_ELECTRO, damageforcescale);
560 SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_SEC(WEP_ELECTRO, health));
561 proj.event_damage = W_Electro_Orb_Damage;
562 proj.flags = FL_PROJECTILE;
563 IL_PUSH(g_projectiles, proj);
564 IL_PUSH(g_bot_dodge, proj);
565 proj.damagedbycontents = (WEP_CVAR_SEC(WEP_ELECTRO, damagedbycontents));
566 if (proj.damagedbycontents)
568
569 proj.bouncefactor = WEP_CVAR_SEC(WEP_ELECTRO, bouncefactor);
570 proj.bouncestop = WEP_CVAR_SEC(WEP_ELECTRO, bouncestop);
571 proj.missile_flags = MIF_SPLASH | MIF_ARC;
572
573 if (WEP_CVAR_SEC(WEP_ELECTRO, limit) > 0)
574 {
579 }
580
581 CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
582
583 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
584}
585
586void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
587{
588 if (actor.(weaponentity).electro_count > 1
590 && weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
591 {
592 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
593 --actor.(weaponentity).electro_count;
594 actor.(weaponentity).electro_secondarytime = time;
595 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_ELECTRO, animtime), W_Electro_CheckAttack);
596 return;
597 }
598 w_ready(thiswep, actor, weaponentity, fire);
599}
600
601.bool bot_secondary_electromooth; // whatever a mooth is
602
603METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
604{
605 PHYS_INPUT_BUTTON_ATCK(actor) = PHYS_INPUT_BUTTON_ATCK2(actor) = false;
606 if (vdist(actor.origin - actor.enemy.origin, >, 1000))
607 actor.bot_secondary_electromooth = false;
608 if (!actor.bot_secondary_electromooth)
609 {
610 float shoot = (WEP_CVAR_PRI(WEP_ELECTRO, speed))
611 ? bot_aim(actor, weaponentity, WEP_CVAR_PRI(WEP_ELECTRO, speed), 0, WEP_CVAR_PRI(WEP_ELECTRO, lifetime), false, true)
612 : bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
613
614 if (shoot)
615 {
616 PHYS_INPUT_BUTTON_ATCK(actor) = true;
617 if (random() < 0.01)
618 actor.bot_secondary_electromooth = true;
619 }
620 }
621 else
622 {
623 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))
624 {
625 PHYS_INPUT_BUTTON_ATCK2(actor) = true;
626 if (random() < 0.03)
627 actor.bot_secondary_electromooth = false;
628 }
629 }
630}
631
632METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
633{
634 if (autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
635 {
636 float ammo_amount = 0;
637 if (actor.(weaponentity).clip_load >= WEP_CVAR_PRI(WEP_ELECTRO, ammo))
638 ammo_amount = 1;
639 if (actor.(weaponentity).clip_load >= WEP_CVAR_SEC(WEP_ELECTRO, ammo))
640 ++ammo_amount;
641
642 if (!ammo_amount)
643 {
644 thiswep.wr_reload(thiswep, actor, weaponentity);
645 return;
646 }
647 }
648
649 if (fire & 1)
650 {
651 if (time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(WEP_ELECTRO, refire2) * W_WeaponRateFactor(actor)
652 && weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_ELECTRO, refire)))
653 {
654 W_Electro_Attack_Bolt(thiswep, actor, weaponentity);
655 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_ELECTRO, animtime), w_ready);
656 }
657 }
658 else if (fire & 2)
659 {
660 if (time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(WEP_ELECTRO, refire) * W_WeaponRateFactor(actor)
661 && weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
662 {
663 W_Electro_Attack_Orb(thiswep, actor, weaponentity);
664 actor.(weaponentity).electro_count = WEP_CVAR_SEC(WEP_ELECTRO, count);
665 actor.(weaponentity).electro_secondarytime = time;
666 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_ELECTRO, animtime), W_Electro_CheckAttack);
667 }
668 }
669}
670
671METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
672{
673 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(WEP_ELECTRO, ammo);
674 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(WEP_ELECTRO, ammo);
675 return ammo_amount;
676}
677
678METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
679{
680 float ammo_amount;
681 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.
682 {
683 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo) + WEP_CVAR_PRI(WEP_ELECTRO, ammo);
684 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo) + WEP_CVAR_PRI(WEP_ELECTRO, ammo);
685 }
686 else
687 {
688 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo);
689 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_ELECTRO, ammo);
690 }
691 return ammo_amount;
692}
693
694METHOD(Electro, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
695{
696 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(WEP_ELECTRO, ammo), WEP_CVAR_SEC(WEP_ELECTRO, ammo)), SND_RELOAD);
697}
698
699METHOD(Electro, wr_suicidemessage, Notification(entity thiswep))
700{
702 return WEAPON_ELECTRO_SUICIDE_ORBS;
703 else
704 return WEAPON_ELECTRO_SUICIDE_BOLT;
705}
706
707METHOD(Electro, wr_killmessage, Notification(entity thiswep))
708{
710 {
711 return WEAPON_ELECTRO_MURDER_ORBS;
712 }
713 else
714 {
716 return WEAPON_ELECTRO_MURDER_COMBO;
717 else
718 return WEAPON_ELECTRO_MURDER_BOLT;
719 }
720}
721
722#endif // GAMEQC
723#ifdef CSQC
724
725METHOD(Electro, wr_impacteffect, void(entity thiswep, entity actor))
726{
727 vector org2 = w_org + w_backoff * 2;
729 {
730 pointparticles(EFFECT_ELECTRO_BALLEXPLODE, org2, '0 0 0', 1);
731 if (!w_issilent)
732 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
733 }
734 else
735 {
737 {
738 // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
739 pointparticles(EFFECT_ELECTRO_COMBO, org2, '0 0 0', 1);
740 if (!w_issilent)
741 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT_COMBO, VOL_BASE, ATTEN_NORM);
742 }
743 else
744 {
745 pointparticles(EFFECT_ELECTRO_IMPACT, org2, '0 0 0', 1);
746 if (!w_issilent)
747 sound(actor, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
748 }
749 }
750}
751
752#endif // CSQC
753#ifdef MENUQC
756
757METHOD(Electro, describe, string(Electro this))
758{
759 TC(Electro, this);
761 PAR(_("The %s shoots electric balls forwards, dealing some splash damage when they burst on impact."), COLORED_NAME(this));
762 PAR(_("The secondary fire launches orbs that are influenced by gravity, "
763 "so they can be laid around the map at high traffic locations (like at %s flag bases) to damage enemies that walk by. "
764 "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));
765 PAR(_("It consumes some %s ammo for each ball / orb."), COLORED_NAME(ITEM_Cells));
766 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. "
767 "Since the primary fire doesn't travel particularly fast, the %s is not useful in many other situations."), COLORED_NAME(this), COLORED_NAME(this));
768 PAR(W_Guide_Keybinds(this));
769 PAR(W_Guide_DPS_secondaryMultishotWithCombo(this.netname, "primary", "secondary", "secondary_count", "secondary_refire2", "combo", true));
770 return PAGE_TEXT;
771}
772
773#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 IS_DEAD(s)
Definition player.qh:244
float teleport_time
Definition player.qh:218
#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:2141
int LostMovetypeFollow(entity ent)
Definition util.qc:2171
const int FL_PROJECTILE
Definition constants.qh:76
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:247
void electro_orb_draw(entity this)
Definition electro.qc:8
void W_Electro_Explode_use(entity this, entity actor, entity trigger)
Definition electro.qc:242
void W_Electro_Attack_Bolt(Weapon thiswep, entity actor,.entity weaponentity)
Definition electro.qc:319
void W_Electro_Orb_ExplodeOverTime(entity this)
Definition electro.qc:145
void W_Electro_Orb_Touch(entity this, entity toucher)
Definition electro.qc:461
bool electro_orb_send(entity this, entity to, int sf)
Definition electro.qc:56
void W_Electro_Bolt_Think(entity this)
Definition electro.qc:256
void W_Electro_Explode(entity this, entity directhitentity)
Definition electro.qc:199
bool bot_secondary_electromooth
Definition electro.qc:601
void W_Electro_ExplodeComboThink(entity this)
Definition electro.qc:116
void W_Electro_Attack_Orb(Weapon thiswep, entity actor,.entity weaponentity)
Definition electro.qc:518
void electro_orb_setup(entity e)
Definition electro.qc:20
const float ELECTRO_COMBO_OVERTIME_DELAY
Definition electro.qc:115
void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition electro.qc:482
void W_Electro_CheckAttack(Weapon thiswep, entity actor,.entity weaponentity, int fire)
Definition electro.qc:586
void W_Electro_Orb_Follow_Think(entity this)
Definition electro.qc:369
void W_Electro_Orb_Stick(entity this, entity to)
Definition electro.qc:400
void W_Electro_TriggerCombo(vector org, float rad, entity own)
Definition electro.qc:73
void W_Electro_ExplodeCombo(entity this)
Definition electro.qc:168
float dmg_last
Definition electro.qh:101
IntrusiveList LimitedElectroBallRubbleList
Definition electro.qh:100
float electro_count
Definition electro.qh:102
float electro_secondarytime
Definition electro.qh:103
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:651
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)
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)]