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