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