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