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