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