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