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