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