]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_crylink.qc
crylink: add some more CheckLinks calls... maybe this will figure out where the probl...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_crylink.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(CRYLINK, w_crylink, IT_CELLS, 6, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_MID, "crylink", "crylink", _("Crylink"));
3 #else
4 #ifdef SVQC
5 .float gravity;
6 .float crylink_waitrelease;
7 .entity crylink_lastgroup;
8
9 .entity queuenext;
10 .entity queueprev;
11
12 void W_Crylink_CheckLinks(entity e)
13 {
14         float i;
15         entity p;
16
17         if(e == world)
18                 error("W_Crylink_CheckLinks: entity is world");
19
20         p = e;
21         for(i = 0; i < 1000; ++i)
22         {
23                 if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
24                         error("W_Crylink_CheckLinks: queue is inconsistent");
25                 p = p.queuenext;
26                 if(p == e)
27                         break;
28         }
29         if(i >= 1000)
30                 error("W_Crylink_CheckLinks: infinite chain");
31 }
32
33 void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
34 {
35         W_Crylink_CheckLinks(next);
36         if(me == own.crylink_lastgroup)
37                 own.crylink_lastgroup = ((me == next) ? world : next);
38         prev.queuenext = next;
39         next.queueprev = prev;
40         if(me != next)
41                 W_Crylink_CheckLinks(next);
42 }
43
44 void W_Crylink_Dequeue(entity e)
45 {
46         W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
47 }
48
49 // force projectile to explode
50 void W_Crylink_LinkExplode (entity e, entity e2)
51 {
52         float a;
53         a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
54
55         if(e == e.realowner.crylink_lastgroup)
56                 e.realowner.crylink_lastgroup = world;
57
58         RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_damage * a, autocvar_g_balance_crylink_primary_edgedamage * a, autocvar_g_balance_crylink_primary_radius, world, autocvar_g_balance_crylink_primary_force * a, e.projectiledeathtype, other);
59
60         if(e.queuenext != e2)
61                 W_Crylink_LinkExplode(e.queuenext, e2);
62
63         remove (e);
64 }
65
66 // adjust towards center
67 // returns the origin where they will meet... and the time till the meeting is
68 // stored in w_crylink_linkjoin_time.
69 // could possibly network this origin and time, and display a special particle
70 // effect when projectiles meet there :P
71 // jspeed: MINIMUM jing speed
72 // jtime: MAXIMUM jing time (0: none)
73 float w_crylink_linkjoin_time;
74 vector W_Crylink_LinkJoin(entity e, float jspeed, float jtime)
75 {
76         vector avg_origin, avg_velocity;
77         vector targ_origin;
78         float avg_dist, n;
79         entity p;
80
81         // FIXME remove this debug code
82         W_Crylink_CheckLinks(e);
83
84         w_crylink_linkjoin_time = 0;
85
86         avg_origin = e.origin;
87         avg_velocity = e.velocity;
88         n = 1;
89         for(p = e; (p = p.queuenext) != e; )
90         {
91                 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
92                 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
93                 ++n;
94         }
95         avg_origin *= (1.0 / n);
96         avg_velocity *= (1.0 / n);
97
98         if(n < 2)
99                 return avg_origin; // nothing to do
100
101         // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
102         avg_dist = pow(vlen(e.origin - avg_origin), 2);
103         for(p = e; (p = p.queuenext) != e; )
104                 avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
105         avg_dist *= (1.0 / n);
106         avg_dist = sqrt(avg_dist);
107
108         if(avg_dist == 0)
109                 return avg_origin; // no change needed
110
111         if(jspeed == 0 && jtime == 0)
112         {
113                 e.velocity = avg_velocity;
114                 UpdateCSQCProjectile(e);
115                 for(p = e; (p = p.queuenext) != e; )
116                 {
117                         p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
118                         UpdateCSQCProjectile(p);
119                 }
120         }
121         else
122         {
123                 if(jtime)
124                 {
125                         if(jspeed)
126                                 w_crylink_linkjoin_time = min(jtime, avg_dist / jspeed);
127                         else
128                                 w_crylink_linkjoin_time = jtime;
129                 }
130                 else
131                         w_crylink_linkjoin_time = avg_dist / jspeed;
132                 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
133
134                 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
135                 UpdateCSQCProjectile(e);
136                 for(p = e; (p = p.queuenext) != e; )
137                 {
138                         p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
139                         UpdateCSQCProjectile(p);
140                 }
141
142                 // analysis:
143                 //   jspeed -> +infinity:
144                 //      w_crylink_linkjoin_time -> +0
145                 //      targ_origin -> avg_origin
146                 //      p->velocity -> HUEG towards center
147                 //   jspeed -> 0:
148                 //      w_crylink_linkjoin_time -> +/- infinity
149                 //      targ_origin -> avg_velocity * +/- infinity
150                 //      p->velocity -> avg_velocity
151                 //   jspeed -> -infinity:
152                 //      w_crylink_linkjoin_time -> -0
153                 //      targ_origin -> avg_origin
154                 //      p->velocity -> HUEG away from center
155         }
156
157         W_Crylink_CheckLinks(e);
158
159         return targ_origin;
160 }
161
162 void W_Crylink_LinkJoinEffect_Think()
163 {
164         // is there at least 2 projectiles very close?
165         entity e, p;
166         float n;
167         e = self.owner.crylink_lastgroup;
168         n = 0;
169         if(e)
170         {
171                 if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
172                         ++n;
173                 for(p = e; (p = p.queuenext) != e; )
174                 {
175                         if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
176                                 ++n;
177                 }
178                 if(n >= 2)
179                 {
180                         if(e.projectiledeathtype & HITTYPE_SECONDARY)
181                         {
182                                 if(autocvar_g_balance_crylink_secondary_joinexplode)
183                                 {
184                                         n = n / autocvar_g_balance_crylink_secondary_shots;
185                                         RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_joinexplode_damage * n,
186                                                                         autocvar_g_balance_crylink_secondary_joinexplode_edgedamage * n,
187                                                                         autocvar_g_balance_crylink_secondary_joinexplode_radius * n, e.realowner,
188                                                                         autocvar_g_balance_crylink_secondary_joinexplode_force * n, e.projectiledeathtype, other);
189
190                                         pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
191                                 }
192                         }
193                         else
194                         {
195                                 if(autocvar_g_balance_crylink_primary_joinexplode)
196                                 {
197                                         n = n / autocvar_g_balance_crylink_primary_shots;
198                                         RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_joinexplode_damage * n,
199                                                                         autocvar_g_balance_crylink_primary_joinexplode_edgedamage * n,
200                                                                         autocvar_g_balance_crylink_primary_joinexplode_radius * n, e.realowner,
201                                                                         autocvar_g_balance_crylink_primary_joinexplode_force * n, e.projectiledeathtype, other);
202
203                                         pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
204                                 }
205                         }
206                 }
207         }
208         remove(self);
209 }
210
211
212 // NO bounce protection, as bounces are limited!
213 void W_Crylink_Touch (void)
214 {
215         float finalhit;
216         float f;
217         //PROJECTILE_TOUCH;
218         local entity savenext, saveprev, saveown;
219         saveown = self.realowner;
220         savenext = self.queuenext;
221         saveprev = self.queueprev;
222         if(WarpZone_Projectile_Touch())
223         {
224                 if(wasfreed(self))
225                         W_Crylink_Dequeue_Raw(saveown, saveprev, self, savenext);
226                 return;
227         }
228
229         float a;
230         a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
231
232         finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
233         if(finalhit)
234                 f = 1;
235         else
236                 f = autocvar_g_balance_crylink_primary_bouncedamagefactor;
237         if(a)
238                 f *= a;
239         if (RadiusDamage (self, self.realowner, autocvar_g_balance_crylink_primary_damage * f, autocvar_g_balance_crylink_primary_edgedamage * f, autocvar_g_balance_crylink_primary_radius, world, autocvar_g_balance_crylink_primary_force * f, self.projectiledeathtype, other) && autocvar_g_balance_crylink_primary_linkexplode)
240         {
241                 if(self == self.realowner.crylink_lastgroup)
242                         self.realowner.crylink_lastgroup = world;
243                 W_Crylink_LinkExplode(self.queuenext, self);
244                 remove (self);
245                 return;
246         }
247         else if(finalhit)
248         {
249                 // just unlink
250                 W_Crylink_Dequeue(self);
251                 remove(self);
252                 return;
253         }
254         self.cnt = self.cnt - 1;
255         self.angles = vectoangles(self.velocity);
256         self.owner = world;
257         self.projectiledeathtype |= HITTYPE_BOUNCE;
258         // commented out as it causes a little hitch...
259         //if(proj.cnt == 0)
260         //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
261 }
262
263 void W_Crylink_Touch2 (void)
264 {
265         float finalhit;
266         float f;
267         //PROJECTILE_TOUCH;
268         local entity savenext, saveprev, saveown;
269         savenext = self.queuenext;
270         saveprev = self.queueprev;
271         saveown = self.realowner;
272         if(WarpZone_Projectile_Touch())
273         {
274                 if(wasfreed(self))
275                         W_Crylink_Dequeue_Raw(saveown, saveprev, self, savenext);
276                 return;
277         }
278
279         float a;
280         a = 1 - (time - self.fade_time) * self.fade_rate;
281
282         finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
283         if(finalhit)
284                 f = 1;
285         else
286                 f = autocvar_g_balance_crylink_secondary_bouncedamagefactor;
287         if(a)
288                 f *= a;
289         if (RadiusDamage (self, self.realowner, autocvar_g_balance_crylink_secondary_damage * f, autocvar_g_balance_crylink_secondary_edgedamage * f, autocvar_g_balance_crylink_secondary_radius, world, autocvar_g_balance_crylink_secondary_force * f, self.projectiledeathtype, other) && autocvar_g_balance_crylink_secondary_linkexplode)
290         {
291                 if(self == self.realowner.crylink_lastgroup)
292                         self.realowner.crylink_lastgroup = world;
293                 W_Crylink_LinkExplode(self.queuenext, self);
294                 remove (self);
295                 return;
296         }
297         else if(finalhit)
298         {
299                 // just unlink
300                 W_Crylink_Dequeue(self);
301                 remove(self);
302                 return;
303         }
304         self.cnt = self.cnt - 1;
305         self.angles = vectoangles(self.velocity);
306         self.owner = world;
307         self.projectiledeathtype |= HITTYPE_BOUNCE;
308         // commented out as it causes a little hitch...
309         //if(proj.cnt == 0)
310         //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
311 }
312
313 void W_Crylink_Fadethink (void)
314 {
315         W_Crylink_Dequeue(self);
316         remove(self);
317 }
318
319 void W_Crylink_Attack (void)
320 {
321         local float counter, shots;
322         local entity proj, prevproj, firstproj;
323         local vector s;
324         vector forward, right, up;
325         float maxdmg;
326
327         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
328                 self.ammo_cells = self.ammo_cells - autocvar_g_balance_crylink_primary_ammo;
329
330         maxdmg = autocvar_g_balance_crylink_primary_damage*autocvar_g_balance_crylink_primary_shots;
331         maxdmg *= 1 + autocvar_g_balance_crylink_primary_bouncedamagefactor * autocvar_g_balance_crylink_primary_bounces;
332         if(autocvar_g_balance_crylink_primary_joinexplode)
333                 maxdmg += autocvar_g_balance_crylink_primary_joinexplode_damage;
334
335         W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", CHAN_WEAPON, maxdmg);
336         forward = v_forward;
337         right = v_right;
338         up = v_up;
339
340         shots = autocvar_g_balance_crylink_primary_shots;
341         pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
342         proj = world;
343         while (counter < shots)
344         {
345                 proj = spawn ();
346                 proj.realowner = proj.owner = self;
347                 proj.classname = "spike";
348                 proj.bot_dodge = TRUE;
349                 proj.bot_dodgerating = autocvar_g_balance_crylink_primary_damage;
350                 if(shots == 1) {
351                         proj.queuenext = proj;
352                         proj.queueprev = proj;
353                 }
354                 else if(counter == 0) { // first projectile, store in firstproj for now
355                         firstproj = proj;
356                 }
357                 else if(counter == shots - 1) { // last projectile, link up with first projectile
358                         prevproj.queuenext = proj;
359                         firstproj.queueprev = proj;
360                         proj.queuenext = firstproj;
361                         proj.queueprev = prevproj;
362                 }
363                 else { // else link up with previous projectile
364                         prevproj.queuenext = proj;
365                         proj.queueprev = prevproj;
366                 }
367
368                 prevproj = proj;
369
370                 proj.movetype = MOVETYPE_BOUNCEMISSILE;
371                 PROJECTILE_MAKETRIGGER(proj);
372                 proj.projectiledeathtype = WEP_CRYLINK;
373                 //proj.gravity = 0.001;
374
375                 setorigin (proj, w_shotorg);
376                 setsize(proj, '0 0 0', '0 0 0');
377
378
379                 s = '0 0 0';
380                 if (counter == 0)
381                         s = '0 0 0';
382                 else
383                 {
384                         makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
385                         s_y = v_forward_x;
386                         s_z = v_forward_y;
387                 }
388                 s = s * autocvar_g_balance_crylink_primary_spread * g_weaponspreadfactor;
389                 W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_crylink_primary_speed, 0, 0, 0, FALSE);
390                 proj.touch = W_Crylink_Touch;
391
392                 proj.think = W_Crylink_Fadethink;
393                 if(counter == 0)
394                 {
395                         proj.fade_time = time + autocvar_g_balance_crylink_primary_middle_lifetime;
396                         proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_middle_fadetime;
397                         proj.nextthink = time + autocvar_g_balance_crylink_primary_middle_lifetime + autocvar_g_balance_crylink_primary_middle_fadetime;
398                 }
399                 else
400                 {
401                         proj.fade_time = time + autocvar_g_balance_crylink_primary_other_lifetime;
402                         proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_other_fadetime;
403                         proj.nextthink = time + autocvar_g_balance_crylink_primary_other_lifetime + autocvar_g_balance_crylink_primary_other_fadetime;
404                 }
405                 proj.teleport_time = time + autocvar_g_balance_crylink_primary_joindelay;
406                 proj.cnt = autocvar_g_balance_crylink_primary_bounces;
407                 //proj.scale = 1 + 1 * proj.cnt;
408
409                 proj.angles = vectoangles (proj.velocity);
410
411                 //proj.glow_size = 20;
412
413                 proj.flags = FL_PROJECTILE;
414
415                 CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
416
417                 other = proj; MUTATOR_CALLHOOK(EditProjectile);
418
419                 counter = counter + 1;
420         }
421         self.crylink_lastgroup = proj;
422         W_Crylink_CheckLinks(proj);
423 }
424
425 void W_Crylink_Attack2 (void)
426 {
427         local float counter, shots;
428         local entity proj, prevproj, firstproj;
429         float maxdmg;
430
431         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
432                 self.ammo_cells = self.ammo_cells - autocvar_g_balance_crylink_secondary_ammo;
433
434         maxdmg = autocvar_g_balance_crylink_secondary_damage*autocvar_g_balance_crylink_secondary_shots;
435         maxdmg *= 1 + autocvar_g_balance_crylink_secondary_bouncedamagefactor * autocvar_g_balance_crylink_secondary_bounces;
436         if(autocvar_g_balance_crylink_secondary_joinexplode)
437                 maxdmg += autocvar_g_balance_crylink_secondary_joinexplode_damage;
438
439         W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", CHAN_WEAPON, maxdmg);
440
441         shots = autocvar_g_balance_crylink_secondary_shots;
442         pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
443         proj = world;
444         while (counter < shots)
445         {
446                 proj = spawn ();
447                 proj.realowner = proj.owner = self;
448                 proj.classname = "spike";
449                 proj.bot_dodge = TRUE;
450                 proj.bot_dodgerating = autocvar_g_balance_crylink_secondary_damage;
451                 if(shots == 1) {
452                         proj.queuenext = proj;
453                         proj.queueprev = proj;
454                 }
455                 else if(counter == 0) { // first projectile, store in firstproj for now
456                         firstproj = proj;
457                 }
458                 else if(counter == shots - 1) { // last projectile, link up with first projectile
459                         prevproj.queuenext = proj;
460                         firstproj.queueprev = proj;
461                         proj.queuenext = firstproj;
462                         proj.queueprev = prevproj;
463                 }
464                 else { // else link up with previous projectile
465                         prevproj.queuenext = proj;
466                         proj.queueprev = prevproj;
467                 }
468
469                 prevproj = proj;
470
471                 proj.movetype = MOVETYPE_BOUNCEMISSILE;
472                 PROJECTILE_MAKETRIGGER(proj);
473                 proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY;
474                 //proj.gravity = 0.001;
475
476                 setorigin (proj, w_shotorg);
477                 setsize(proj, '0 0 0', '0 0 0');
478
479                 W_SetupProjectileVelocityEx(proj, (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor), v_up, autocvar_g_balance_crylink_secondary_speed, 0, 0, 0, FALSE);
480                 proj.touch = W_Crylink_Touch2;
481                 proj.think = W_Crylink_Fadethink;
482                 if(counter == (shots - 1) / 2)
483                 {
484                         proj.fade_time = time + autocvar_g_balance_crylink_secondary_middle_lifetime;
485                         proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_middle_fadetime;
486                         proj.nextthink = time + autocvar_g_balance_crylink_secondary_middle_lifetime + autocvar_g_balance_crylink_secondary_middle_fadetime;
487                 }
488                 else
489                 {
490                         proj.fade_time = time + autocvar_g_balance_crylink_secondary_line_lifetime;
491                         proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_line_fadetime;
492                         proj.nextthink = time + autocvar_g_balance_crylink_secondary_line_lifetime + autocvar_g_balance_crylink_secondary_line_fadetime;
493                 }
494                 proj.teleport_time = time + autocvar_g_balance_crylink_secondary_joindelay;
495                 proj.cnt = autocvar_g_balance_crylink_secondary_bounces;
496                 //proj.scale = 1 + 1 * proj.cnt;
497
498                 proj.angles = vectoangles (proj.velocity);
499
500                 //proj.glow_size = 20;
501
502                 proj.flags = FL_PROJECTILE;
503
504                 CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
505
506                 other = proj; MUTATOR_CALLHOOK(EditProjectile);
507
508                 counter = counter + 1;
509         }
510         self.crylink_lastgroup = proj;
511 }
512
513 void spawnfunc_weapon_crylink (void)
514 {
515         weapon_defaultspawnfunc(WEP_CRYLINK);
516 }
517
518 float w_crylink(float req)
519 {
520         if (req == WR_AIM)
521         {
522                 if (random() < 0.10)
523                         self.BUTTON_ATCK = bot_aim(autocvar_g_balance_crylink_primary_speed, 0, autocvar_g_balance_crylink_primary_middle_lifetime, FALSE);
524                 else
525                         self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_crylink_secondary_speed, 0, autocvar_g_balance_crylink_secondary_middle_lifetime, FALSE);
526         }
527         else if (req == WR_THINK)
528         {
529                 if (self.BUTTON_ATCK)
530                 {
531                         if (!self.crylink_waitrelease)
532                         if (weapon_prepareattack(0, autocvar_g_balance_crylink_primary_refire))
533                         {
534                                 W_Crylink_Attack();
535                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_crylink_primary_animtime, w_ready);
536                                 if(autocvar_g_balance_crylink_primary_joinspread != 0 || autocvar_g_balance_crylink_primary_jointime != 0)
537                                         self.crylink_waitrelease = 1;
538                         }
539                 }
540                 else if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary)
541                 {
542                         if (!self.crylink_waitrelease)
543                         if (weapon_prepareattack(1, autocvar_g_balance_crylink_secondary_refire))
544                         {
545                                 W_Crylink_Attack2();
546                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_crylink_secondary_animtime, w_ready);
547                                 if(autocvar_g_balance_crylink_secondary_joinspread != 0 || autocvar_g_balance_crylink_secondary_jointime != 0)
548                                         self.crylink_waitrelease = 2;
549                         }
550                 }
551                 else
552                 {
553                         if (self.crylink_waitrelease && (!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time))
554                         {
555                                 // fired and released now!
556                                 if(self.crylink_lastgroup)
557                                 {
558                                         vector pos;
559                                         entity linkjoineffect;
560
561                                         if(self.crylink_waitrelease == 1)
562                                         {
563                                                 pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_primary_joinspread * autocvar_g_balance_crylink_primary_speed, autocvar_g_balance_crylink_primary_jointime);
564
565                                         }
566                                         else
567                                         {
568                                                 pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_secondary_joinspread * autocvar_g_balance_crylink_secondary_speed, autocvar_g_balance_crylink_secondary_jointime);
569                                         }
570
571                                         linkjoineffect = spawn();
572                                         linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
573                                         linkjoineffect.classname = "linkjoineffect";
574                                         linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
575                                         linkjoineffect.owner = self;
576                                         setorigin(linkjoineffect, pos);
577                                 }
578                                 self.crylink_waitrelease = 0;
579                                 if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2))
580                                 {
581                                         // ran out of ammo!
582                                         self.cnt = WEP_CRYLINK;
583                                         self.switchweapon = w_getbestweapon(self);
584                                 }
585                         }
586                 }
587         }
588         else if (req == WR_PRECACHE)
589         {
590                 precache_model ("models/weapons/g_crylink.md3");
591                 precache_model ("models/weapons/v_crylink.md3");
592                 precache_model ("models/weapons/h_crylink.iqm");
593                 precache_sound ("weapons/crylink_fire.wav");
594                 precache_sound ("weapons/crylink_fire2.wav");
595                 precache_sound ("weapons/crylink_linkjoin.wav");
596         }
597         else if (req == WR_SETUP)
598                 weapon_setup(WEP_CRYLINK);
599         else if (req == WR_CHECKAMMO1)
600         {
601                 // don't "run out of ammo" and switch weapons while waiting for release
602                 if(self.crylink_lastgroup && self.crylink_waitrelease)
603                         return TRUE;
604                 return self.ammo_cells >= autocvar_g_balance_crylink_primary_ammo;
605         }
606         else if (req == WR_CHECKAMMO2)
607         {
608                 // don't "run out of ammo" and switch weapons while waiting for release
609                 if(self.crylink_lastgroup && self.crylink_waitrelease)
610                         return TRUE;
611                 return self.ammo_cells >= autocvar_g_balance_crylink_secondary_ammo;
612         }
613         return TRUE;
614 };
615 #endif
616 #ifdef CSQC
617 float w_crylink(float req)
618 {
619         if(req == WR_IMPACTEFFECT)
620         {
621                 vector org2;
622                 org2 = w_org + w_backoff * 2;
623                 if(w_deathtype & HITTYPE_SECONDARY)
624                 {
625                         pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1);
626                         if(!w_issilent)
627                                 sound(self, CHAN_PROJECTILE, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM);
628                 }
629                 else
630                 {
631                         pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1);
632                         if(!w_issilent)
633                                 sound(self, CHAN_PROJECTILE, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM);
634                 }
635         }
636         else if(req == WR_PRECACHE)
637         {
638                 precache_sound("weapons/crylink_impact2.wav");
639                 precache_sound("weapons/crylink_impact.wav");
640         }
641         else if (req == WR_SUICIDEMESSAGE)
642         {
643                 w_deathtypestring = _("%s succeeded at self-destructing themself with the Crylink");
644         }
645         else if (req == WR_KILLMESSAGE)
646         {
647                 if(w_deathtype & HITTYPE_BOUNCE)
648                         w_deathtypestring = _("%s could not hide from %s's Crylink"); // unchecked: SPLASH (SECONDARY can't be)
649                 else if(w_deathtype & HITTYPE_SPLASH)
650                         w_deathtypestring = _("%s was too close to %s's Crylink"); // unchecked: SECONDARY
651                 else
652                         w_deathtypestring = _("%s took a close look at %s's Crylink"); // unchecked: SECONDARY
653         }
654         return TRUE;
655 }
656 #endif
657 #endif