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