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