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