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