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