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
210{
211 return (e.takedamage != DAMAGE_NO && !IS_DEAD(e));
212}
213bool W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
214{
215 entity head = WarpZone_SearchInRadius(projectile.origin + (projectile.mins + projectile.maxs) * 0.5, rad + MAX_DAMAGEEXTRARADIUS,
217 bool hit_friendly = false;
218 for (; head; head = head.chain)
219 {
220 if (SAME_TEAM(head, projectile.realowner))
221 hit_friendly = true;
222 else
223 return false;
224 }
225
226 return hit_friendly;
227}
228
229// NO bounce protection, as bounces are limited!
231{
232 bool is_primary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
234
235 float a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
236 float finalhit = (this.cnt <= 0 || toucher.takedamage != DAMAGE_NO);
237 float f = (finalhit ? 1 : WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, bouncedamagefactor));
238 if (a)
239 f *= a;
240
241 float totaldamage = RadiusDamage(this, this.realowner,
242 f * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, damage),
243 f * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, edgedamage),
244 WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, radius),
245 NULL,
246 NULL,
247 f * WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, force),
249 this.weaponentity_fld,
250 toucher
251 );
252
253 if (totaldamage
254 && ((WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, linkexplode) == 1 && !W_Crylink_Touch_WouldHitFriendly(this, WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, radius)))
255 || WEP_CVAR_BOTH(WEP_CRYLINK, is_primary, linkexplode) == 2))
256 {
257 .entity weaponentity = this.weaponentity_fld;
258 if (this == this.crylink_owner.(weaponentity).crylink_lastgroup)
259 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
261 this.classname = "spike_oktoremove";
262 delete(this);
263 return;
264 }
265 else if (finalhit)
266 {
267 // just unlink
268 delete(this);
269 return;
270 }
271 --this.cnt;
272 this.angles = vectoangles(this.velocity);
273 this.owner = NULL;
275 // commented out as it causes a little hitch...
276 //if (proj.cnt == 0)
277 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
278}
279
281{
282 delete(this);
283}
284
285void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
286{
287 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(WEP_CRYLINK, ammo), weaponentity);
288
289 float maxdmg = WEP_CVAR_PRI(WEP_CRYLINK, damage) * WEP_CVAR_PRI(WEP_CRYLINK, shots);
290 maxdmg *= 1 + WEP_CVAR_PRI(WEP_CRYLINK, bouncedamagefactor) * WEP_CVAR_PRI(WEP_CRYLINK, bounces);
291 if (WEP_CVAR_PRI(WEP_CRYLINK, joinexplode))
292 maxdmg += WEP_CVAR_PRI(WEP_CRYLINK, joinexplode_damage);
293
294 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
295 vector right = v_right;
296 vector up = v_up;
297
298 int shots = WEP_CVAR_PRI(WEP_CRYLINK, shots);
299 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
300 entity proj = NULL, prevproj = NULL, firstproj = NULL;
301 vector s;
302 for (int counter = 0; counter < shots; ++counter)
303 {
304 proj = new(spike);
305 WarpZone_RefSys_Copy(proj, actor);
306 proj.dtor = W_Crylink_DeleteLink;
307 proj.reset = W_Crylink_Reset;
308 proj.realowner = proj.owner = actor;
309 proj.crylink_owner = actor;
310 proj.weaponentity_fld = weaponentity;
311 proj.bot_dodge = true;
312 proj.bot_dodgerating = WEP_CVAR_PRI(WEP_CRYLINK, damage);
313 if (shots == 1)
314 {
315 proj.queuenext = proj;
316 proj.queueprev = proj;
317 }
318 else if (counter == 0) // first projectile, store in firstproj for now
319 firstproj = proj;
320 else if (counter == shots - 1) // last projectile, link up with first projectile
321 {
322 prevproj.queuenext = proj;
323 firstproj.queueprev = proj;
324 proj.queuenext = firstproj;
325 proj.queueprev = prevproj;
326 }
327 else // else link up with previous projectile
328 {
329 prevproj.queuenext = proj;
330 proj.queueprev = prevproj;
331 }
332
333 prevproj = proj;
334
337 proj.projectiledeathtype = thiswep.m_id;
338 //proj.gravity = 0.001;
339
340 setorigin(proj, w_shotorg);
341 setsize(proj, '0 0 0', '0 0 0');
342
343 s = W_CalculateSpreadPattern(1, 0, counter, shots) * WEP_CVAR_PRI(WEP_CRYLINK, spread) * autocvar_g_weaponspreadfactor;
344 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(WEP_CRYLINK, speed), 0, 0, 0, false);
346
348 if (counter == 0)
349 {
350 proj.fade_time = time + WEP_CVAR_PRI(WEP_CRYLINK, middle_lifetime);
351 proj.fade_rate = 1 / WEP_CVAR_PRI(WEP_CRYLINK, middle_fadetime);
352 proj.nextthink = time + WEP_CVAR_PRI(WEP_CRYLINK, middle_lifetime) + WEP_CVAR_PRI(WEP_CRYLINK, middle_fadetime);
353 }
354 else
355 {
356 proj.fade_time = time + WEP_CVAR_PRI(WEP_CRYLINK, other_lifetime);
357 proj.fade_rate = 1 / WEP_CVAR_PRI(WEP_CRYLINK, other_fadetime);
358 proj.nextthink = time + WEP_CVAR_PRI(WEP_CRYLINK, other_lifetime) + WEP_CVAR_PRI(WEP_CRYLINK, other_fadetime);
359 }
360 proj.teleport_time = time + WEP_CVAR_PRI(WEP_CRYLINK, joindelay);
361 proj.cnt = WEP_CVAR_PRI(WEP_CRYLINK, bounces);
362 //proj.scale = 1 + 1 * proj.cnt;
363
364 proj.angles = vectoangles(proj.velocity);
365
366 //proj.glow_size = 20;
367
368 proj.flags = FL_PROJECTILE;
369 IL_PUSH(g_projectiles, proj);
370 IL_PUSH(g_bot_dodge, proj);
371 proj.missile_flags = MIF_SPLASH;
372
373 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
374
375 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
376 }
377 if (WEP_CVAR_PRI(WEP_CRYLINK, joinspread) != 0 && WEP_CVAR_PRI(WEP_CRYLINK, shots) > 1)
378 {
379 actor.(weaponentity).crylink_lastgroup = proj;
381 actor.(weaponentity).crylink_waitrelease = 1;
382 }
383}
384
385void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
386{
387 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(WEP_CRYLINK, ammo), weaponentity);
388
389 float maxdmg = WEP_CVAR_SEC(WEP_CRYLINK, damage) * WEP_CVAR_SEC(WEP_CRYLINK, shots);
390 maxdmg *= 1 + WEP_CVAR_SEC(WEP_CRYLINK, bouncedamagefactor) * WEP_CVAR_SEC(WEP_CRYLINK, bounces);
391 if (WEP_CVAR_SEC(WEP_CRYLINK, joinexplode))
392 maxdmg += WEP_CVAR_SEC(WEP_CRYLINK, joinexplode_damage);
393
394 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
395 vector right = v_right;
396 vector up = v_up;
397
398 int shots = WEP_CVAR_SEC(WEP_CRYLINK, shots);
399 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
400 entity proj = NULL, prevproj = NULL, firstproj = NULL;
401 vector s;
402 for (int counter = 0; counter < shots; ++counter)
403 {
404 proj = new(spike);
405 WarpZone_RefSys_Copy(proj, actor);
406 proj.dtor = W_Crylink_DeleteLink;
407 proj.weaponentity_fld = weaponentity;
408 proj.reset = W_Crylink_Reset;
409 proj.realowner = proj.owner = actor;
410 proj.crylink_owner = actor;
411 proj.bot_dodge = true;
412 proj.bot_dodgerating = WEP_CVAR_SEC(WEP_CRYLINK, damage);
413 if (shots == 1)
414 {
415 proj.queuenext = proj;
416 proj.queueprev = proj;
417 }
418 else if (counter == 0) // first projectile, store in firstproj for now
419 firstproj = proj;
420 else if (counter == shots - 1) // last projectile, link up with first projectile
421 {
422 prevproj.queuenext = proj;
423 firstproj.queueprev = proj;
424 proj.queuenext = firstproj;
425 proj.queueprev = prevproj;
426 }
427 else // else link up with previous projectile
428 {
429 prevproj.queuenext = proj;
430 proj.queueprev = prevproj;
431 }
432
433 prevproj = proj;
434
437 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
438 //proj.gravity = 0.001;
439
440 setorigin(proj, w_shotorg);
441 setsize(proj, '0 0 0', '0 0 0');
442
443 if (WEP_CVAR_SEC(WEP_CRYLINK, spreadtype) == 1)
444 {
445 s = W_CalculateSpreadPattern(1, 0, counter, shots) * WEP_CVAR_SEC(WEP_CRYLINK, spread) * autocvar_g_weaponspreadfactor;
446 s = w_shotdir + right * s.y + up * s.z;
447 }
448 else
449 s = w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(WEP_CRYLINK, spread) * autocvar_g_weaponspreadfactor;
450
451 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(WEP_CRYLINK, speed), 0, 0, 0, false);
454 if (counter == (shots - 1) * 0.5)
455 {
456 proj.fade_time = time + WEP_CVAR_SEC(WEP_CRYLINK, middle_lifetime);
457 proj.fade_rate = 1 / WEP_CVAR_SEC(WEP_CRYLINK, middle_fadetime);
458 proj.nextthink = time + WEP_CVAR_SEC(WEP_CRYLINK, middle_lifetime) + WEP_CVAR_SEC(WEP_CRYLINK, middle_fadetime);
459 }
460 else
461 {
462 proj.fade_time = time + WEP_CVAR_SEC(WEP_CRYLINK, other_lifetime);
463 proj.fade_rate = 1 / WEP_CVAR_SEC(WEP_CRYLINK, other_fadetime);
464 proj.nextthink = time + WEP_CVAR_SEC(WEP_CRYLINK, other_lifetime) + WEP_CVAR_SEC(WEP_CRYLINK, other_fadetime);
465 }
466 proj.teleport_time = time + WEP_CVAR_SEC(WEP_CRYLINK, joindelay);
467 proj.cnt = WEP_CVAR_SEC(WEP_CRYLINK, bounces);
468 //proj.scale = 1 + 1 * proj.cnt;
469
470 proj.angles = vectoangles(proj.velocity);
471
472 //proj.glow_size = 20;
473
474 proj.flags = FL_PROJECTILE;
475 IL_PUSH(g_projectiles, proj);
476 IL_PUSH(g_bot_dodge, proj);
477 proj.missile_flags = MIF_SPLASH;
478
479 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
480
481 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
482 }
483 if (WEP_CVAR_SEC(WEP_CRYLINK, joinspread) != 0 && WEP_CVAR_SEC(WEP_CRYLINK, shots) > 1)
484 {
485 actor.(weaponentity).crylink_lastgroup = proj;
487 actor.(weaponentity).crylink_waitrelease = 2;
488 }
489}
490
491METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
492{
493 if (random() < 0.10)
494 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);
495 else
496 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);
497}
498
499METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
500{
501 if (autocvar_g_balance_crylink_reload_ammo
502 && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(WEP_CRYLINK, ammo), WEP_CVAR_SEC(WEP_CRYLINK, ammo)))
503 // forced reload
504 thiswep.wr_reload(thiswep, actor, weaponentity);
505 else if (fire & 1)
506 {
507 if (actor.(weaponentity).crylink_waitrelease != 1
508 && weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_CRYLINK, refire)))
509 {
510 W_Crylink_Attack(thiswep, actor, weaponentity);
511 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_CRYLINK, animtime), w_ready);
512 }
513 }
514 else if ((fire & 2) && autocvar_g_balance_crylink_secondary)
515 {
516 if (actor.(weaponentity).crylink_waitrelease != 2
517 && weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(WEP_CRYLINK, refire)))
518 {
519 W_Crylink_Attack2(thiswep, actor, weaponentity);
520 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_CRYLINK, animtime), w_ready);
521 }
522 }
523
524 if ((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1))
525 || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
526 {
527 if (!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
528 {
529 // fired and released now!
530 if (actor.(weaponentity).crylink_lastgroup)
531 {
532 bool is_primary = (actor.(weaponentity).crylink_waitrelease == 1);
533 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));
534
535 entity linkjoineffect = new(linkjoineffect);
536 linkjoineffect.weaponentity_fld = weaponentity;
538 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
539 linkjoineffect.owner = actor;
540 setorigin(linkjoineffect, pos);
541 }
542 actor.(weaponentity).crylink_waitrelease = 0;
543 if (!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity)
544 && !(actor.items & IT_UNLIMITED_AMMO))
545 {
546 // ran out of ammo!
547 actor.cnt = thiswep.m_id;
548 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
549 }
550 }
551 }
552}
553
554METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
555{
556 // don't "run out of ammo" and switch weapons while waiting for release
557 if (actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
558 return true;
559
560 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(WEP_CRYLINK, ammo);
561 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(WEP_CRYLINK, ammo);
562 return ammo_amount;
563}
564
565METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
566{
567 // don't "run out of ammo" and switch weapons while waiting for release
568 if (actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
569 return true;
570
571 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(WEP_CRYLINK, ammo);
572 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(WEP_CRYLINK, ammo);
573 return ammo_amount;
574}
575
576METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
577{
578 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(WEP_CRYLINK, ammo), WEP_CVAR_SEC(WEP_CRYLINK, ammo)), SND_RELOAD);
579}
580
581METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
582{
583 return WEAPON_CRYLINK_SUICIDE;
584}
585
586METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
587{
588 return WEAPON_CRYLINK_MURDER;
589}
590
591#endif // SVQC
592#ifdef CSQC
593
594METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
595{
596 vector org2 = w_org + w_backoff * 2;
598 {
599 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
600 if (!w_issilent)
601 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
602 }
603 else
604 {
605 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
606 if (!w_issilent)
607 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);
608 }
609}
610
611#endif // CSQC
612#ifdef MENUQC
614
615METHOD(Crylink, describe, string(Crylink this))
616{
617 TC(Crylink, this);
619 PAR(_("The %s fires bursts of laser-like projectiles, spreading out as they travel away and deflecting off walls. "
620 "If the primary fire is held, when it's released the projectiles will converge before spreading out again, "
621 "which can be utilized to deal more damage to an enemy by making the projectiles converge on them."), COLORED_NAME(this));
622 PAR(_("Projectiles deal damage on collision but also when they bounce, so with good positioning they can sometimes deal double damage."));
623 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."));
624 PAR(_("It consumes %s ammo for each projectile, which is shared by several weapons."), COLORED_NAME(ITEM_Cells));
625 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));
626 PAR(_("The %s deals knockback in a unique way, pulling the player from their center to the point of impact. "
627 "This makes it one of the best weapons to slow someone down if you are chasing them, particularly with the secondary fire. "
628 "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));
629 PAR(W_Guide_Keybinds(this));
630 PAR(W_Guide_DPS_bothMultishot(this.netname, "primary", "secondary"));
631 return PAGE_TEXT;
632}
633
634#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:189
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:77
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:937
vector w_org
const float MAX_DAMAGEEXTRARADIUS
int w_deathtype
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 Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:155
ent angles
Definition ent_cs.qc:146
prev
Definition all.qh:53
next
Definition all.qh:75
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define TC(T, sym)
Definition _all.inc:82
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
vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
Transform velocity vel in from's reference into to's reference.
Definition common.qc:796
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Transform origin org in from's reference into to's reference.
Definition common.qc:787
void WarpZone_RefSys_Copy(entity me, entity from)
Copies the warpzone reference of from onto me.
Definition common.qc:824
#define MOVE_NOTHING
Definition common.qh:35
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:144
entity Notification
always last
Definition all.qh:85
#define METHOD(cname, name, prototype)
Definition oo.qh:274
#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:96
entity entity toucher
Definition self.qh:76
#define settouch(e, f)
Definition self.qh:77
const int MIF_SPLASH
Definition common.qh:58
int projectiledeathtype
Definition common.qh:33
#define PROJECTILE_TOUCH(e, t)
Definition common.qh:40
float fade_time
Definition common.qh:35
IntrusiveList g_projectiles
Definition common.qh:70
#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
#define sound(e, c, s, v, a)
Definition sound.qh:52
#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_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:188
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