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