Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
crylink.qc
Go to the documentation of this file.
1#include "crylink.qh"
2
3#ifdef SVQC
4
6{
7 if (e == NULL)
8 error("W_Crylink_CheckLinks: entity is NULL");
9 if (e.classname != "spike" || wasfreed(e))
10 error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
11
12 entity p = e;
13 int i;
14 for (i = 0; i < 1000; ++i)
15 {
16 if (p.queuenext.queueprev != p || p.queueprev.queuenext != p)
17 error("W_Crylink_CheckLinks: queue is inconsistent");
18 p = p.queuenext;
19 if (p == e)
20 break;
21 }
22 if (i >= 1000)
23 error("W_Crylink_CheckLinks: infinite chain");
24}
25
27{
29 .entity weaponentity = me.weaponentity_fld;
30 if (me == own.(weaponentity).crylink_lastgroup)
31 own.(weaponentity).crylink_lastgroup = (me == next) ? NULL : next;
32 prev.queuenext = next;
33 next.queueprev = prev;
34 me.classname = "spike_oktoremove";
35 if (me != next)
37}
38
40{
41 W_Crylink_Dequeue_Raw(e.crylink_owner, e.queueprev, e, e.queuenext);
42}
43
45{
46 if (this.classname != "spike_oktoremove")
48 delete_fn(this);
49}
50
52{
53 delete(this);
54}
55
56// force projectile to explode
57void W_Crylink_LinkExplode(entity e, entity e2, entity directhitentity)
58{
59 if (e == e2)
60 return;
61
62 float a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
63
64 .entity weaponentity = e.weaponentity_fld;
65 if (e == e.crylink_owner.(weaponentity).crylink_lastgroup)
66 e.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
67
68 bool is_primary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
69 RadiusDamage(e, e.realowner,
70 a * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, damage),
71 a * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, edgedamage),
72 WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, radius),
73 NULL,
74 NULL,
75 a * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, force),
76 e.projectiledeathtype,
77 e.weaponentity_fld,
78 directhitentity
79 );
80
81 W_Crylink_LinkExplode(e.queuenext, e2, directhitentity);
82
83 e.classname = "spike_oktoremove";
84 delete(e);
85}
86
87// adjust towards center
88// returns the origin where they will meet... and the time till the meeting is
89// stored in w_crylink_linkjoin_time.
90// could possibly network this origin and time, and display a special particle
91// effect when projectiles meet there :P
92// jspeed: joining speed (calculate this as join spread * initial speed)
95{
96 // FIXME remove this debug code
98
100
101 entity p;
102 vector avg_org = e.origin;
103 vector avg_vel = e.velocity;
104 float n = 1;
105 for (p = e; (p = p.queuenext) != e; )
106 {
107 avg_org += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
108 avg_vel += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
109 ++n;
110 }
111 avg_org *= 1.0 / n;
112 avg_vel *= 1.0 / n;
113
114 if (n < 2)
115 return avg_org; // nothing to do
116
117 // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
118 float avg_dist = vlen2(e.origin - avg_org);
119 for (p = e; (p = p.queuenext) != e; )
120 avg_dist += vlen2(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_org);
121 avg_dist = sqrt(avg_dist * (1.0 / n));
122
123 if (avg_dist == 0)
124 return avg_org; // no change needed
125
126 vector targ_origin;
127 if (jspeed == 0)
128 {
129 e.velocity = avg_vel;
131 for (p = e; (p = p.queuenext) != e; )
132 {
133 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_vel);
135 }
136 targ_origin = avg_org + 1000000000 * normalize(avg_vel); // HUUUUUUGE
137 }
138 else
139 {
140 w_crylink_linkjoin_time = avg_dist / jspeed;
141 targ_origin = avg_org + w_crylink_linkjoin_time * avg_vel;
142
143 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
145 for (p = e; (p = p.queuenext) != e; )
146 {
147 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
149 }
150
151 // analysis:
152 // jspeed -> +infinity:
153 // w_crylink_linkjoin_time -> +0
154 // targ_origin -> avg_org
155 // p->velocity -> HUEG towards center
156 // jspeed -> 0:
157 // w_crylink_linkjoin_time -> +/- infinity
158 // targ_origin -> avg_vel * +/- infinity
159 // p->velocity -> avg_vel
160 // jspeed -> -infinity:
161 // w_crylink_linkjoin_time -> -0
162 // targ_origin -> avg_org
163 // p->velocity -> HUEG away from center
164 }
165
167
168 return targ_origin;
169}
170
172{
173 // is there at least 2 projectiles very close?
174 .entity weaponentity = this.weaponentity_fld;
175 entity e = this.owner.(weaponentity).crylink_lastgroup;
176 if (e)
177 {
178 float n = 0;
179 if (vlen2(e.origin - this.origin) < vlen2(e.velocity) * frametime)
180 ++n;
181 for (entity p = e; (p = p.queuenext) != e; )
182 if (vlen2(p.origin - this.origin) < vlen2(p.velocity) * frametime)
183 ++n;
184 if (n >= 2)
185 {
186 bool is_primary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
187
188 if (WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, joinexplode))
189 {
190 n /= WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, shots);
191 RadiusDamage(e, e.realowner,
192 n * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, joinexplode_damage),
193 n * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, joinexplode_edgedamage),
194 n * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, joinexplode_radius),
195 e.realowner,
196 NULL,
197 n * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, joinexplode_force),
198 e.projectiledeathtype,
199 e.weaponentity_fld,
200 NULL
201 );
202 Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, this.origin, '0 0 0', n);
203 }
204 }
205 }
206 delete(this);
207}
208
209bool W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
210{
211 entity head = WarpZone_FindRadius(projectile.origin + (projectile.mins + projectile.maxs) * 0.5, rad + MAX_DAMAGEEXTRARADIUS, false);
212 bool hit_friendly = false;
213 for (; head; head = head.chain)
214 if (head.takedamage != DAMAGE_NO && !IS_DEAD(head))
215 {
216 if (SAME_TEAM(head, projectile.realowner))
217 hit_friendly = true;
218 else
219 return false;
220 }
221
222 return hit_friendly;
223}
224
225// NO bounce protection, as bounces are limited!
227{
228 bool is_primary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
230
231 float a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
232 float finalhit = (this.cnt <= 0 || toucher.takedamage != DAMAGE_NO);
233 float f = (finalhit ? 1 : WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, bouncedamagefactor));
234 if (a)
235 f *= a;
236
237 float totaldamage = RadiusDamage(this, this.realowner,
238 f * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, damage),
239 f * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, edgedamage),
240 WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, radius),
241 NULL,
242 NULL,
243 f * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, force),
245 this.weaponentity_fld,
246 toucher
247 );
248
249 if (totaldamage
250 && ((WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, linkexplode) == 1 && !W_Crylink_Touch_WouldHitFriendly(this, WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, radius)))
251 || WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, linkexplode) == 2))
252 {
253 .entity weaponentity = this.weaponentity_fld;
254 if (this == this.crylink_owner.(weaponentity).crylink_lastgroup)
255 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
257 this.classname = "spike_oktoremove";
258 delete(this);
259 return;
260 }
261 else if (finalhit)
262 {
263 // just unlink
264 delete(this);
265 return;
266 }
267 --this.cnt;
268 this.angles = vectoangles(this.velocity);
269 this.owner = NULL;
271 // commented out as it causes a little hitch...
272 //if (proj.cnt == 0)
273 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
274}
275
277{
278 delete(this);
279}
280
281void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
282{
283 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(WEP_CRYLINK, ammo), weaponentity);
284
285 float maxdmg = WEP_CVAR_PRI(WEP_CRYLINK, damage) * WEP_CVAR_PRI(WEP_CRYLINK, shots);
286 maxdmg *= 1 + WEP_CVAR_PRI(WEP_CRYLINK, bouncedamagefactor) * WEP_CVAR_PRI(WEP_CRYLINK, bounces);
287 if (WEP_CVAR_PRI(WEP_CRYLINK, joinexplode))
288 maxdmg += WEP_CVAR_PRI(WEP_CRYLINK, joinexplode_damage);
289
290 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
291 vector right = v_right;
292 vector up = v_up;
293
294 int shots = WEP_CVAR_PRI(WEP_CRYLINK, shots);
295 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
296 entity proj = NULL, prevproj = NULL, firstproj = NULL;
297 vector s;
298 for (int counter = 0; counter < shots; ++counter)
299 {
300 proj = new(spike);
301 proj.dtor = W_Crylink_DeleteLink;
302 proj.reset = W_Crylink_Reset;
303 proj.realowner = proj.owner = actor;
304 proj.crylink_owner = actor;
305 proj.weaponentity_fld = weaponentity;
306 proj.bot_dodge = true;
307 proj.bot_dodgerating = WEP_CVAR_PRI(WEP_CRYLINK, damage);
308 if (shots == 1)
309 {
310 proj.queuenext = proj;
311 proj.queueprev = proj;
312 }
313 else if (counter == 0) // first projectile, store in firstproj for now
314 firstproj = proj;
315 else if (counter == shots - 1) // last projectile, link up with first projectile
316 {
317 prevproj.queuenext = proj;
318 firstproj.queueprev = proj;
319 proj.queuenext = firstproj;
320 proj.queueprev = prevproj;
321 }
322 else // else link up with previous projectile
323 {
324 prevproj.queuenext = proj;
325 proj.queueprev = prevproj;
326 }
327
328 prevproj = proj;
329
332 proj.projectiledeathtype = thiswep.m_id;
333 //proj.gravity = 0.001;
334
335 setorigin(proj, w_shotorg);
336 setsize(proj, '0 0 0', '0 0 0');
337
338 s = W_CalculateSpreadPattern(1, 0, counter, shots) * WEP_CVAR_PRI(WEP_CRYLINK, spread) * autocvar_g_weaponspreadfactor;
339 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(WEP_CRYLINK, speed), 0, 0, 0, false);
341
343 if (counter == 0)
344 {
345 proj.fade_time = time + WEP_CVAR_PRI(WEP_CRYLINK, middle_lifetime);
346 proj.fade_rate = 1 / WEP_CVAR_PRI(WEP_CRYLINK, middle_fadetime);
347 proj.nextthink = time + WEP_CVAR_PRI(WEP_CRYLINK, middle_lifetime) + WEP_CVAR_PRI(WEP_CRYLINK, middle_fadetime);
348 }
349 else
350 {
351 proj.fade_time = time + WEP_CVAR_PRI(WEP_CRYLINK, other_lifetime);
352 proj.fade_rate = 1 / WEP_CVAR_PRI(WEP_CRYLINK, other_fadetime);
353 proj.nextthink = time + WEP_CVAR_PRI(WEP_CRYLINK, other_lifetime) + WEP_CVAR_PRI(WEP_CRYLINK, other_fadetime);
354 }
355 proj.teleport_time = time + WEP_CVAR_PRI(WEP_CRYLINK, joindelay);
356 proj.cnt = WEP_CVAR_PRI(WEP_CRYLINK, bounces);
357 //proj.scale = 1 + 1 * proj.cnt;
358
359 proj.angles = vectoangles(proj.velocity);
360
361 //proj.glow_size = 20;
362
363 proj.flags = FL_PROJECTILE;
364 IL_PUSH(g_projectiles, proj);
365 IL_PUSH(g_bot_dodge, proj);
366 proj.missile_flags = MIF_SPLASH;
367
368 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
369
370 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
371 }
372 if (WEP_CVAR_PRI(WEP_CRYLINK, joinspread) != 0 && WEP_CVAR_PRI(WEP_CRYLINK, shots) > 1)
373 {
374 actor.(weaponentity).crylink_lastgroup = proj;
376 actor.(weaponentity).crylink_waitrelease = 1;
377 }
378}
379
380void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
381{
382 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(WEP_CRYLINK, ammo), weaponentity);
383
384 float maxdmg = WEP_CVAR_SEC(WEP_CRYLINK, damage) * WEP_CVAR_SEC(WEP_CRYLINK, shots);
385 maxdmg *= 1 + WEP_CVAR_SEC(WEP_CRYLINK, bouncedamagefactor) * WEP_CVAR_SEC(WEP_CRYLINK, bounces);
386 if (WEP_CVAR_SEC(WEP_CRYLINK, joinexplode))
387 maxdmg += WEP_CVAR_SEC(WEP_CRYLINK, joinexplode_damage);
388
389 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
390 vector right = v_right;
391 vector up = v_up;
392
393 int shots = WEP_CVAR_SEC(WEP_CRYLINK, shots);
394 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
395 entity proj = NULL, prevproj = NULL, firstproj = NULL;
396 vector s;
397 for (int counter = 0; counter < shots; ++counter)
398 {
399 proj = new(spike);
400 proj.dtor = W_Crylink_DeleteLink;
401 proj.weaponentity_fld = weaponentity;
402 proj.reset = W_Crylink_Reset;
403 proj.realowner = proj.owner = actor;
404 proj.crylink_owner = actor;
405 proj.bot_dodge = true;
406 proj.bot_dodgerating = WEP_CVAR_SEC(WEP_CRYLINK, damage);
407 if (shots == 1)
408 {
409 proj.queuenext = proj;
410 proj.queueprev = proj;
411 }
412 else if (counter == 0) // first projectile, store in firstproj for now
413 firstproj = proj;
414 else if (counter == shots - 1) // last projectile, link up with first projectile
415 {
416 prevproj.queuenext = proj;
417 firstproj.queueprev = proj;
418 proj.queuenext = firstproj;
419 proj.queueprev = prevproj;
420 }
421 else // else link up with previous projectile
422 {
423 prevproj.queuenext = proj;
424 proj.queueprev = prevproj;
425 }
426
427 prevproj = proj;
428
431 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
432 //proj.gravity = 0.001;
433
434 setorigin(proj, w_shotorg);
435 setsize(proj, '0 0 0', '0 0 0');
436
437 if (WEP_CVAR_SEC(WEP_CRYLINK, spreadtype) == 1)
438 {
439 s = W_CalculateSpreadPattern(1, 0, counter, shots) * WEP_CVAR_SEC(WEP_CRYLINK, spread) * autocvar_g_weaponspreadfactor;
440 s = w_shotdir + right * s.y + up * s.z;
441 }
442 else
443 s = w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(WEP_CRYLINK, spread) * autocvar_g_weaponspreadfactor;
444
445 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(WEP_CRYLINK, speed), 0, 0, 0, false);
448 if (counter == (shots - 1) * 0.5)
449 {
450 proj.fade_time = time + WEP_CVAR_SEC(WEP_CRYLINK, middle_lifetime);
451 proj.fade_rate = 1 / WEP_CVAR_SEC(WEP_CRYLINK, middle_fadetime);
452 proj.nextthink = time + WEP_CVAR_SEC(WEP_CRYLINK, middle_lifetime) + WEP_CVAR_SEC(WEP_CRYLINK, middle_fadetime);
453 }
454 else
455 {
456 proj.fade_time = time + WEP_CVAR_SEC(WEP_CRYLINK, other_lifetime);
457 proj.fade_rate = 1 / WEP_CVAR_SEC(WEP_CRYLINK, other_fadetime);
458 proj.nextthink = time + WEP_CVAR_SEC(WEP_CRYLINK, other_lifetime) + WEP_CVAR_SEC(WEP_CRYLINK, other_fadetime);
459 }
460 proj.teleport_time = time + WEP_CVAR_SEC(WEP_CRYLINK, joindelay);
461 proj.cnt = WEP_CVAR_SEC(WEP_CRYLINK, bounces);
462 //proj.scale = 1 + 1 * proj.cnt;
463
464 proj.angles = vectoangles(proj.velocity);
465
466 //proj.glow_size = 20;
467
468 proj.flags = FL_PROJECTILE;
469 IL_PUSH(g_projectiles, proj);
470 IL_PUSH(g_bot_dodge, proj);
471 proj.missile_flags = MIF_SPLASH;
472
473 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
474
475 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
476 }
477 if (WEP_CVAR_SEC(WEP_CRYLINK, joinspread) != 0 && WEP_CVAR_SEC(WEP_CRYLINK, shots) > 1)
478 {
479 actor.(weaponentity).crylink_lastgroup = proj;
481 actor.(weaponentity).crylink_waitrelease = 2;
482 }
483}
484
485METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
486{
487 if (random() < 0.10)
488 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(WEP_CRYLINK, speed), 0, WEP_CVAR_PRI(WEP_CRYLINK, middle_lifetime), false, true);
489 else
490 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(WEP_CRYLINK, speed), 0, WEP_CVAR_SEC(WEP_CRYLINK, middle_lifetime), false, true);
491}
492
493METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
494{
495 if (autocvar_g_balance_crylink_reload_ammo
496 && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(WEP_CRYLINK, ammo), WEP_CVAR_SEC(WEP_CRYLINK, ammo)))
497 // forced reload
498 thiswep.wr_reload(thiswep, actor, weaponentity);
499 else if (fire & 1)
500 {
501 if (actor.(weaponentity).crylink_waitrelease != 1
502 && weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_CRYLINK, refire)))
503 {
504 W_Crylink_Attack(thiswep, actor, weaponentity);
505 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_CRYLINK, animtime), w_ready);
506 }
507 }
508 else if ((fire & 2) && autocvar_g_balance_crylink_secondary)
509 {
510 if (actor.(weaponentity).crylink_waitrelease != 2
511 && weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(WEP_CRYLINK, refire)))
512 {
513 W_Crylink_Attack2(thiswep, actor, weaponentity);
514 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_CRYLINK, animtime), w_ready);
515 }
516 }
517
518 if ((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1))
519 || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
520 {
521 if (!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
522 {
523 // fired and released now!
524 if (actor.(weaponentity).crylink_lastgroup)
525 {
526 bool is_primary = (actor.(weaponentity).crylink_waitrelease == 1);
527 vector pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, joinspread) * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, speed));
528
529 entity linkjoineffect = new(linkjoineffect);
530 linkjoineffect.weaponentity_fld = weaponentity;
532 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
533 linkjoineffect.owner = actor;
534 setorigin(linkjoineffect, pos);
535 }
536 actor.(weaponentity).crylink_waitrelease = 0;
537 if (!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity)
538 && !(actor.items & IT_UNLIMITED_AMMO))
539 {
540 // ran out of ammo!
541 actor.cnt = thiswep.m_id;
542 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
543 }
544 }
545 }
546}
547
548METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
549{
550 // don't "run out of ammo" and switch weapons while waiting for release
551 if (actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
552 return true;
553
554 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(WEP_CRYLINK, ammo);
555 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(WEP_CRYLINK, ammo);
556 return ammo_amount;
557}
558
559METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
560{
561 // don't "run out of ammo" and switch weapons while waiting for release
562 if (actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
563 return true;
564
565 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_CRYLINK, ammo);
566 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_CRYLINK, ammo);
567 return ammo_amount;
568}
569
570METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
571{
572 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(WEP_CRYLINK, ammo), WEP_CVAR_SEC(WEP_CRYLINK, ammo)), SND_RELOAD);
573}
574
575METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
576{
577 return WEAPON_CRYLINK_SUICIDE;
578}
579
580METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
581{
582 return WEAPON_CRYLINK_MURDER;
583}
584
585#endif // SVQC
586#ifdef CSQC
587
588METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
589{
590 vector org2 = w_org + w_backoff * 2;
592 {
593 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
594 if (!w_issilent)
595 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
596 }
597 else
598 {
599 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
600 if (!w_issilent)
601 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);
602 }
603}
604
605#endif // CSQC
606#ifdef MENUQC
608
609METHOD(Crylink, describe, string(Crylink this))
610{
611 TC(Crylink, this);
613 PAR(_("The %s fires bursts of laser-like projectiles, spreading out as they travel away and deflecting off walls. "
614 "If the primary fire is held, when it's released the projectiles will converge before spreading out again, "
615 "which can be utilized to deal more damage to an enemy by making the projectiles converge on them."), COLORED_NAME(this));
616 PAR(_("Projectiles deal damage on collision but also when they bounce, so with good positioning they can sometimes deal double damage."));
617 PAR(_("The secondary fire shoots the projectiles together in a tight group, so it is often the better option in situations where the enemy is an easy target to hit."));
618 PAR(_("It consumes %s ammo for each projectile, which is shared by several weapons."), COLORED_NAME(ITEM_Cells));
619 PAR(_("Close to medium range is the ideal time to use the %s, although the secondary fire can be useful for long range combat sometimes."), COLORED_NAME(this));
620 PAR(_("The %s deals knockback in a unique way, pulling the player from their center to the point of impact. "
621 "This makes it one of the best weapons to slow someone down if you are chasing them, particularly with the secondary fire. "
622 "Another common use of the %s is \"crylink running,\" where you partially angle down and use the secondary fire to pull yourself forwards, in order to gain speed."), COLORED_NAME(this), COLORED_NAME(this));
623 PAR(W_Guide_Keybinds(this));
624 PAR(W_Guide_DPS_bothMultishot(this.netname, "primary", "secondary"));
625 return PAGE_TEXT;
626}
627
628#endif // MENUQC
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
vector W_CalculateSpreadPattern(int pattern, float bias, int counter, int total)
float autocvar_g_weaponspreadfactor
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.
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 cnt
Definition powerups.qc:24
entity owner
Definition main.qh:87
#define COLORED_NAME(this)
Definition color.qh:195
const int IT_UNLIMITED_AMMO
Definition item.qh:23
float radius
Definition impulse.qh:11
#define IS_DEAD(s)
Definition player.qh:244
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition player.qh:152
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition player.qh:154
const int FL_PROJECTILE
Definition constants.qh:85
vector v_up
string classname
float frametime
vector velocity
float time
vector v_right
vector origin
const float ATTN_NORM
void UpdateCSQCProjectile(entity e)
void CSQCProjectile(entity e, float clientanimate, int type, float docull)
float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:943
vector w_org
const float MAX_DAMAGEEXTRARADIUS
int w_deathtype
vector w_backoff
float w_issilent
const int HITTYPE_BOUNCE
Definition all.qh:31
const int HITTYPE_SECONDARY
Definition all.qh:29
float speed
Definition dynlight.qc:9
#define pointparticles(effect, org, vel, howmany)
Definition effect.qh:7
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:120
ent angles
Definition ent_cs.qc:121
prev
Definition all.qh:73
next
Definition all.qh:95
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define TC(T, sym)
Definition _all.inc:82
vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
Definition common.qc:771
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition common.qc:684
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:763
float bound(float min, float value, float max)
float random(void)
vector vectoangles(vector v)
float sqrt(float f)
float min(float f,...)
vector normalize(vector v)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_BOUNCEMISSILE
Definition movetypes.qh:140
entity Notification
always last
Definition all.qh:81
#define METHOD(cname, name, prototype)
Definition oo.qh:269
#define NULL
Definition post.qh:14
#define error
Definition pre.qh:6
fade_rate
Definition projectile.qh:14
const int PROJECTILE_CRYLINK_BOUNCING
const int PROJECTILE_CRYLINK
Definition projectiles.qh:6
#define w_getbestweapon(ent, wepent)
Definition selection.qh:23
#define setthink(e, f)
vector
Definition self.qh:92
entity entity toucher
Definition self.qh:72
#define settouch(e, f)
Definition self.qh:73
const int MIF_SPLASH
Definition common.qh:46
int projectiledeathtype
Definition common.qh:21
#define PROJECTILE_TOUCH(e, t)
Definition common.qh:28
float fade_time
Definition common.qh:23
IntrusiveList g_projectiles
Definition common.qh:58
#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
#define sound(e, c, s, v, a)
Definition sound.qh:52
#define PAGE_TEXT
Definition string.qh:642
#define PAR(...)
Adds an individually translatable paragraph to PAGE_TEXT without having to deal with strcat and sprin...
Definition string.qh:648
#define PAGE_TEXT_INIT()
Definition string.qh:641
const int DAMAGE_NO
Definition subs.qh:79
float ammo
Definition sv_turrets.qh:43
var void delete_fn(entity e)
#define SAME_TEAM(a, b)
Definition teams.qh:241
entity realowner
void W_SetupProjVelocity_Explicit(entity proj, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
Definition tracing.qc:185
vector w_shotdir
Definition tracing.qh:20
vector w_shotorg
Definition tracing.qh:19
#define W_SetupShot(ent, wepent, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition tracing.qh:34
#define vlen2(v)
Definition vector.qh:4
string W_Guide_Keybinds(Weapon wep)
Definition all.qc:824
string W_Guide_DPS_bothMultishot(string name, string pri, string sec)
Definition all.qc:932
void W_MuzzleFlash(Weapon thiswep, entity actor,.entity weaponentity, vector shotorg, vector shotdir)
Definition all.qc:715
#define WEP_CVAR_PRI(wep, name)
Definition all.qh:338
#define WEP_CVAR_BOTH(wep, isprimary, name)
Definition all.qh:340
#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)]
Weapon m_switchweapon
Definition wepent.qh:25