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