]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/crylink.qc
Merge branch 'master' into martin-t/okc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / crylink.qc
1 #include "crylink.qh"
2
3 #ifdef SVQC
4 spawnfunc(weapon_crylink) { weapon_defaultspawnfunc(this, WEP_CRYLINK); }
5
6 void W_Crylink_CheckLinks(entity e)
7 {
8         float i;
9         entity p;
10
11         if(e == NULL)
12                 error("W_Crylink_CheckLinks: entity is NULL");
13         if(e.classname != "spike" || wasfreed(e))
14                 error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
15
16         p = e;
17         for(i = 0; i < 1000; ++i)
18         {
19                 if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
20                         error("W_Crylink_CheckLinks: queue is inconsistent");
21                 p = p.queuenext;
22                 if(p == e)
23                         break;
24         }
25         if(i >= 1000)
26                 error("W_Crylink_CheckLinks: infinite chain");
27 }
28
29 void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
30 {
31         W_Crylink_CheckLinks(next);
32         .entity weaponentity = me.weaponentity_fld;
33         if(me == own.(weaponentity).crylink_lastgroup)
34                 own.(weaponentity).crylink_lastgroup = ((me == next) ? NULL : next);
35         prev.queuenext = next;
36         next.queueprev = prev;
37         me.classname = "spike_oktoremove";
38         if(me != next)
39                 W_Crylink_CheckLinks(next);
40 }
41
42 void W_Crylink_Dequeue(entity e)
43 {
44         W_Crylink_Dequeue_Raw(e.crylink_owner, e.queueprev, e, e.queuenext);
45 }
46
47 void W_Crylink_Reset(entity this)
48 {
49         W_Crylink_Dequeue(this);
50         delete(this);
51 }
52
53 // force projectile to explode
54 void W_Crylink_LinkExplode(entity e, entity e2, entity directhitentity)
55 {
56         float a;
57
58         if(e == e2)
59                 return;
60
61         a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
62
63         .entity weaponentity = e.weaponentity_fld;
64         if(e == e.crylink_owner.(weaponentity).crylink_lastgroup)
65                 e.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
66
67         float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
68
69         RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius),
70                                 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, directhitentity);
71
72         W_Crylink_LinkExplode(e.queuenext, e2, directhitentity);
73
74         e.classname = "spike_oktoremove";
75         delete(e);
76 }
77
78 // adjust towards center
79 // returns the origin where they will meet... and the time till the meeting is
80 // stored in w_crylink_linkjoin_time.
81 // could possibly network this origin and time, and display a special particle
82 // effect when projectiles meet there :P
83 // jspeed: joining speed (calculate this as join spread * initial speed)
84 float w_crylink_linkjoin_time;
85 vector W_Crylink_LinkJoin(entity e, float jspeed)
86 {
87         vector avg_origin, avg_velocity;
88         vector targ_origin;
89         float avg_dist, n;
90         entity p;
91
92         // FIXME remove this debug code
93         W_Crylink_CheckLinks(e);
94
95         w_crylink_linkjoin_time = 0;
96
97         avg_origin = e.origin;
98         avg_velocity = e.velocity;
99         n = 1;
100         for(p = e; (p = p.queuenext) != e; )
101         {
102                 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
103                 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
104                 ++n;
105         }
106         avg_origin *= (1.0 / n);
107         avg_velocity *= (1.0 / n);
108
109         if(n < 2)
110                 return avg_origin; // nothing to do
111
112         // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
113         avg_dist = (vlen(e.origin - avg_origin) ** 2);
114         for(p = e; (p = p.queuenext) != e; )
115                 avg_dist += (vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin) ** 2);
116         avg_dist *= (1.0 / n);
117         avg_dist = sqrt(avg_dist);
118
119         if(avg_dist == 0)
120                 return avg_origin; // no change needed
121
122         if(jspeed == 0)
123         {
124                 e.velocity = avg_velocity;
125                 UpdateCSQCProjectile(e);
126                 for(p = e; (p = p.queuenext) != e; )
127                 {
128                         p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
129                         UpdateCSQCProjectile(p);
130                 }
131                 targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
132         }
133         else
134         {
135                 w_crylink_linkjoin_time = avg_dist / jspeed;
136                 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
137
138                 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
139                 UpdateCSQCProjectile(e);
140                 for(p = e; (p = p.queuenext) != e; )
141                 {
142                         p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
143                         UpdateCSQCProjectile(p);
144                 }
145
146                 // analysis:
147                 //   jspeed -> +infinity:
148                 //      w_crylink_linkjoin_time -> +0
149                 //      targ_origin -> avg_origin
150                 //      p->velocity -> HUEG towards center
151                 //   jspeed -> 0:
152                 //      w_crylink_linkjoin_time -> +/- infinity
153                 //      targ_origin -> avg_velocity * +/- infinity
154                 //      p->velocity -> avg_velocity
155                 //   jspeed -> -infinity:
156                 //      w_crylink_linkjoin_time -> -0
157                 //      targ_origin -> avg_origin
158                 //      p->velocity -> HUEG away from center
159         }
160
161         W_Crylink_CheckLinks(e);
162
163         return targ_origin;
164 }
165
166 void W_Crylink_LinkJoinEffect_Think(entity this)
167 {
168         // is there at least 2 projectiles very close?
169         entity e, p;
170         float n;
171         .entity weaponentity = this.weaponentity_fld;
172         e = this.owner.(weaponentity).crylink_lastgroup;
173         n = 0;
174         if(e)
175         {
176                 if(vlen2(e.origin - this.origin) < vlen2(e.velocity) * frametime)
177                         ++n;
178                 for(p = e; (p = p.queuenext) != e; )
179                 {
180                         if(vlen2(p.origin - this.origin) < vlen2(p.velocity) * frametime)
181                                 ++n;
182                 }
183                 if(n >= 2)
184                 {
185                         float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
186
187                         if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
188                         {
189                                 n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
190                                 RadiusDamage(
191                                         e,
192                                         e.realowner,
193                                         WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
194                                         WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
195                                         WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
196                                         e.realowner,
197                                         NULL,
198                                         WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
199                                         e.projectiledeathtype,
200                                         NULL
201                                 );
202                                 Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, this.origin, '0 0 0', n);
203                         }
204                 }
205         }
206         delete(this);
207 }
208
209 float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
210 {
211         entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
212         float hit_friendly = 0;
213         float hit_enemy = 0;
214
215         while(head)
216         {
217                 if((head.takedamage != DAMAGE_NO) && (!IS_DEAD(head)))
218                 {
219                         if(SAME_TEAM(head, projectile.realowner))
220                                 ++hit_friendly;
221                         else
222                                 ++hit_enemy;
223                 }
224
225                 head = head.chain;
226         }
227
228         return (hit_enemy ? false : hit_friendly);
229 }
230
231 // NO bounce protection, as bounces are limited!
232 void W_Crylink_Touch(entity this, entity toucher)
233 {
234         float finalhit;
235         float f;
236         float isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
237         PROJECTILE_TOUCH(this, toucher);
238
239         float a;
240         a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
241
242         finalhit = ((this.cnt <= 0) || (toucher.takedamage != DAMAGE_NO));
243         if(finalhit)
244                 f = 1;
245         else
246                 f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
247         if(a)
248                 f *= a;
249
250         float totaldamage = RadiusDamage(this, this.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius), NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * f, this.projectiledeathtype, toucher);
251
252         if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(this, WEP_CVAR_BOTH(crylink, isprimary, radius)))))
253         {
254                 .entity weaponentity = this.weaponentity_fld;
255                 if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
256                         this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
257                 W_Crylink_LinkExplode(this.queuenext, this, toucher);
258                 this.classname = "spike_oktoremove";
259                 delete(this);
260                 return;
261         }
262         else if(finalhit)
263         {
264                 // just unlink
265                 W_Crylink_Dequeue(this);
266                 delete(this);
267                 return;
268         }
269         this.cnt = this.cnt - 1;
270         this.angles = vectoangles(this.velocity);
271         this.owner = NULL;
272         this.projectiledeathtype |= HITTYPE_BOUNCE;
273         // commented out as it causes a little hitch...
274         //if(proj.cnt == 0)
275         //      CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
276 }
277
278 void W_Crylink_Fadethink(entity this)
279 {
280         W_Crylink_Dequeue(this);
281         delete(this);
282 }
283
284 void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
285 {
286         float counter, shots;
287         entity proj, prevproj, firstproj;
288         vector s;
289         vector forward, right, up;
290         float maxdmg;
291
292         W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
293
294         maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
295         maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
296         if(WEP_CVAR_PRI(crylink, joinexplode))
297                 maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
298
299         W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg);
300         forward = v_forward;
301         right = v_right;
302         up = v_up;
303
304         shots = WEP_CVAR_PRI(crylink, shots);
305         Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
306         proj = prevproj = firstproj = NULL;
307         for(counter = 0; counter < shots; ++counter)
308         {
309                 proj = new(spike);
310                 proj.reset = W_Crylink_Reset;
311                 proj.realowner = proj.owner = actor;
312                 proj.crylink_owner = actor;
313                 proj.weaponentity_fld = weaponentity;
314                 proj.bot_dodge = true;
315                 proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
316                 if(shots == 1) {
317                         proj.queuenext = proj;
318                         proj.queueprev = proj;
319                 }
320                 else if(counter == 0) { // first projectile, store in firstproj for now
321                         firstproj = proj;
322                 }
323                 else if(counter == shots - 1) { // last projectile, link up with first projectile
324                         prevproj.queuenext = proj;
325                         firstproj.queueprev = proj;
326                         proj.queuenext = firstproj;
327                         proj.queueprev = prevproj;
328                 }
329                 else { // else link up with previous projectile
330                         prevproj.queuenext = proj;
331                         proj.queueprev = prevproj;
332                 }
333
334                 prevproj = proj;
335
336                 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
337                 PROJECTILE_MAKETRIGGER(proj);
338                 proj.projectiledeathtype = WEP_CRYLINK.m_id;
339                 //proj.gravity = 0.001;
340
341                 setorigin(proj, w_shotorg);
342                 setsize(proj, '0 0 0', '0 0 0');
343
344
345                 s = '0 0 0';
346                 if(counter == 0)
347                         s = '0 0 0';
348                 else
349                 {
350                         makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
351                         s.y = v_forward.x;
352                         s.z = v_forward.y;
353                 }
354                 s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
355                 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
356                 settouch(proj, W_Crylink_Touch);
357
358                 setthink(proj, W_Crylink_Fadethink);
359                 if(counter == 0)
360                 {
361                         proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
362                         proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
363                         proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
364                 }
365                 else
366                 {
367                         proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
368                         proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
369                         proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
370                 }
371                 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
372                 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
373                 //proj.scale = 1 + 1 * proj.cnt;
374
375                 proj.angles = vectoangles(proj.velocity);
376
377                 //proj.glow_size = 20;
378
379                 proj.flags = FL_PROJECTILE;
380                 IL_PUSH(g_projectiles, proj);
381                 IL_PUSH(g_bot_dodge, proj);
382                 proj.missile_flags = MIF_SPLASH;
383
384                 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
385
386                 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
387         }
388         if(WEP_CVAR_PRI(crylink, joinspread) != 0)
389         {
390                 actor.(weaponentity).crylink_lastgroup = proj;
391                 W_Crylink_CheckLinks(proj);
392                 actor.(weaponentity).crylink_waitrelease = 1;
393         }
394 }
395
396 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
397 {
398         float counter, shots;
399         entity proj, prevproj, firstproj;
400         vector s;
401         vector forward, right, up;
402         float maxdmg;
403
404         W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
405
406         maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
407         maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
408         if(WEP_CVAR_SEC(crylink, joinexplode))
409                 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
410
411         W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg);
412         forward = v_forward;
413         right = v_right;
414         up = v_up;
415
416         shots = WEP_CVAR_SEC(crylink, shots);
417         Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
418         proj = prevproj = firstproj = NULL;
419         for(counter = 0; counter < shots; ++counter)
420         {
421                 proj = new(spike);
422                 proj.weaponentity_fld = weaponentity;
423                 proj.reset = W_Crylink_Reset;
424                 proj.realowner = proj.owner = actor;
425                 proj.crylink_owner = actor;
426                 proj.bot_dodge = true;
427                 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
428                 if(shots == 1) {
429                         proj.queuenext = proj;
430                         proj.queueprev = proj;
431                 }
432                 else if(counter == 0) { // first projectile, store in firstproj for now
433                         firstproj = proj;
434                 }
435                 else if(counter == shots - 1) { // last projectile, link up with first projectile
436                         prevproj.queuenext = proj;
437                         firstproj.queueprev = proj;
438                         proj.queuenext = firstproj;
439                         proj.queueprev = prevproj;
440                 }
441                 else { // else link up with previous projectile
442                         prevproj.queuenext = proj;
443                         proj.queueprev = prevproj;
444                 }
445
446                 prevproj = proj;
447
448                 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
449                 PROJECTILE_MAKETRIGGER(proj);
450                 proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
451                 //proj.gravity = 0.001;
452
453                 setorigin(proj, w_shotorg);
454                 setsize(proj, '0 0 0', '0 0 0');
455
456                 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
457                 {
458                         s = '0 0 0';
459                         if(counter == 0)
460                                 s = '0 0 0';
461                         else
462                         {
463                                 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
464                                 s.y = v_forward.x;
465                                 s.z = v_forward.y;
466                         }
467                         s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
468                         s = w_shotdir + right * s.y + up * s.z;
469                 }
470                 else
471                 {
472                         s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
473                 }
474
475                 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
476                 settouch(proj, W_Crylink_Touch);
477                 setthink(proj, W_Crylink_Fadethink);
478                 if(counter == (shots - 1) / 2)
479                 {
480                         proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
481                         proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
482                         proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
483                 }
484                 else
485                 {
486                         proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
487                         proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
488                         proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
489                 }
490                 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
491                 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
492                 //proj.scale = 1 + 1 * proj.cnt;
493
494                 proj.angles = vectoangles(proj.velocity);
495
496                 //proj.glow_size = 20;
497
498                 proj.flags = FL_PROJECTILE;
499                 IL_PUSH(g_projectiles, proj);
500                 IL_PUSH(g_bot_dodge, proj);
501         proj.missile_flags = MIF_SPLASH;
502
503                 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
504
505                 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
506         }
507         if(WEP_CVAR_SEC(crylink, joinspread) != 0)
508         {
509                 actor.(weaponentity).crylink_lastgroup = proj;
510                 W_Crylink_CheckLinks(proj);
511                 actor.(weaponentity).crylink_waitrelease = 2;
512         }
513 }
514
515 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
516 {
517     if(random() < 0.10)
518         PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
519     else
520         PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
521 }
522 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
523 {
524     if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
525         thiswep.wr_reload(thiswep, actor, weaponentity);
526     }
527
528     if(fire & 1)
529     {
530         if(actor.(weaponentity).crylink_waitrelease != 1)
531         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
532         {
533             W_Crylink_Attack(thiswep, actor, weaponentity);
534             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
535         }
536     }
537
538     if((fire & 2) && autocvar_g_balance_crylink_secondary)
539     {
540         if(actor.(weaponentity).crylink_waitrelease != 2)
541         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
542         {
543             W_Crylink_Attack2(thiswep, actor, weaponentity);
544             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
545         }
546     }
547
548     if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
549     {
550         if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
551         {
552             // fired and released now!
553             if(actor.(weaponentity).crylink_lastgroup)
554             {
555                 vector pos;
556                 entity linkjoineffect;
557                 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
558
559                 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
560
561                 linkjoineffect = new(linkjoineffect);
562                 linkjoineffect.weaponentity_fld = weaponentity;
563                 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
564                 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
565                 linkjoineffect.owner = actor;
566                 setorigin(linkjoineffect, pos);
567             }
568             actor.(weaponentity).crylink_waitrelease = 0;
569             if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
570             if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
571             {
572                 // ran out of ammo!
573                 actor.cnt = WEP_CRYLINK.m_id;
574                 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
575             }
576         }
577     }
578 }
579 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
580 {
581     // don't "run out of ammo" and switch weapons while waiting for release
582     if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
583         return true;
584
585     float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(crylink, ammo);
586     ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
587     return ammo_amount;
588 }
589 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
590 {
591     // don't "run out of ammo" and switch weapons while waiting for release
592     if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
593         return true;
594
595     float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(crylink, ammo);
596     ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
597     return ammo_amount;
598 }
599 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
600 {
601     W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
602 }
603 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
604 {
605     return WEAPON_CRYLINK_SUICIDE;
606 }
607 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
608 {
609     return WEAPON_CRYLINK_MURDER;
610 }
611 #endif
612 #ifdef CSQC
613 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
614 {
615     vector org2;
616     org2 = w_org + w_backoff * 2;
617     if(w_deathtype & HITTYPE_SECONDARY)
618     {
619         pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
620         if(!w_issilent)
621             sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
622     }
623     else
624     {
625         pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
626         if(!w_issilent)
627             sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);
628     }
629 }
630 #endif