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