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