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