2dcd433c7e27d533de6fe3d299065259fb0107f2
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_crylink.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(CRYLINK, w_crylink, IT_CELLS, 6, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_MID, "crylink", "crylink", "Crylink");
3 #else
4 #ifdef SVQC
5 .float gravity;
6 .float crylink_waitrelease;
7 .entity crylink_lastgroup;
8
9 .entity queuenext;
10 .entity queueprev;
11
12 void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
13 {
14         if(me == own.crylink_lastgroup)
15                 own.crylink_lastgroup = ((me == next) ? world : next);
16         prev.queuenext = next;
17         next.queueprev = prev;
18 }
19
20 void W_Crylink_Dequeue(entity e)
21 {
22         W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
23 }
24
25 // force projectile to explode
26 void W_Crylink_LinkExplode (entity e, entity e2)
27 {
28         float a;
29         a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
30
31         if(e == e.realowner.crylink_lastgroup)
32                 e.realowner.crylink_lastgroup = world;
33
34         RadiusDamage (e, e.realowner, cvar("g_balance_crylink_primary_damage") * a, cvar("g_balance_crylink_primary_edgedamage") * a, cvar("g_balance_crylink_primary_radius"), world, cvar("g_balance_crylink_primary_force") * a, e.projectiledeathtype, other);
35
36         if(e.queuenext != e2)
37                 W_Crylink_LinkExplode(e.queuenext, e2);
38
39         remove (e);
40 }
41
42 // adjust towards center
43 // returns the origin where they will meet... and the time till the meeting is
44 // stored in w_crylink_linkjoin_time.
45 // could possibly network this origin and time, and display a special particle
46 // effect when projectiles meet there :P
47 // jspeed: MINIMUM jing speed
48 // jtime: MAXIMUM jing time (0: none)
49 float w_crylink_linkjoin_time;
50 vector W_Crylink_LinkJoin(entity e, float jspeed, float jtime)
51 {
52         vector avg_origin, avg_velocity;
53         vector targ_origin;
54         float avg_dist, n;
55         entity p;
56
57         w_crylink_linkjoin_time = 0;
58
59         avg_origin = e.origin;
60         avg_velocity = e.velocity;
61         n = 1;
62         for(p = e; (p = p.queuenext) != e; )
63         {
64                 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
65                 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
66                 ++n;
67         }
68         avg_origin *= (1.0 / n);
69         avg_velocity *= (1.0 / n);
70
71         if(n < 2)
72                 return avg_origin; // nothing to do
73
74         // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
75         avg_dist = pow(vlen(e.origin - avg_origin), 2);
76         for(p = e; (p = p.queuenext) != e; )
77                 avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
78         avg_dist *= (1.0 / n);
79         avg_dist = sqrt(avg_dist);
80
81         if(avg_dist == 0)
82                 return avg_origin; // no change needed
83
84         if(jspeed == 0 && jtime == 0)
85         {
86                 e.velocity = avg_velocity;
87                 UpdateCSQCProjectile(e);
88                 for(p = e; (p = p.queuenext) != e; )
89                 {
90                         p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
91                         UpdateCSQCProjectile(p);
92                 }
93         }
94         else
95         {
96                 if(jtime)
97                 {
98                         if(jspeed)
99                                 w_crylink_linkjoin_time = min(jtime, avg_dist / jspeed);
100                         else
101                                 w_crylink_linkjoin_time = jtime;
102                 }
103                 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
104
105                 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
106                 UpdateCSQCProjectile(e);
107                 for(p = e; (p = p.queuenext) != e; )
108                 {
109                         p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
110                         UpdateCSQCProjectile(p);
111                 }
112
113                 // analysis:
114                 //   jspeed -> +infinity:
115                 //      w_crylink_linkjoin_time -> +0
116                 //      targ_origin -> avg_origin
117                 //      p->velocity -> HUEG towards center
118                 //   jspeed -> 0:
119                 //      w_crylink_linkjoin_time -> +/- infinity
120                 //      targ_origin -> avg_velocity * +/- infinity
121                 //      p->velocity -> avg_velocity
122                 //   jspeed -> -infinity:
123                 //      w_crylink_linkjoin_time -> -0
124                 //      targ_origin -> avg_origin
125                 //      p->velocity -> HUEG away from center
126         }
127
128         return targ_origin;
129 }
130
131 void W_Crylink_LinkJoinEffect_Think()
132 {
133         // is there at least 2 projectiles very close?
134         entity e, p;
135         float n;
136         e = self.owner.crylink_lastgroup;
137         n = 0;
138         if(e)
139         {
140                 if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
141                         ++n;
142                 for(p = e; (p = p.queuenext) != e; )
143                 {
144                         if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
145                                 ++n;
146                 }
147                 if(n >= 2)
148                 {
149                         // they seem to touch...
150                         // TODO make a specific particle effect for this
151                         pointparticles(particleeffectnum("crylink_linkjoin"), self.origin, '0 0 0', 1);
152                 }
153         }
154         remove(self);
155 }
156
157 // NO bounce protection, as bounces are limited!
158 void W_Crylink_Touch (void)
159 {
160         float finalhit;
161         float f;
162         //PROJECTILE_TOUCH;
163         local entity savenext, saveprev, saveown;
164         saveown = self.realowner;
165         savenext = self.queuenext;
166         saveprev = self.queueprev;
167         if(WarpZone_Projectile_Touch())
168         {
169                 if(wasfreed(self))
170                         W_Crylink_Dequeue_Raw(saveown, saveprev, self, savenext);
171                 return;
172         }
173
174         float a;
175         a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
176
177         finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
178         if(finalhit)
179                 f = 1;
180         else
181                 f = cvar("g_balance_crylink_primary_bouncedamagefactor");
182         if(a)
183                 f *= a;
184         if (RadiusDamage (self, self.realowner, cvar("g_balance_crylink_primary_damage") * f, cvar("g_balance_crylink_primary_edgedamage") * f, cvar("g_balance_crylink_primary_radius"), world, cvar("g_balance_crylink_primary_force") * f, self.projectiledeathtype, other) && cvar("g_balance_crylink_primary_linkexplode"))
185         {
186                 W_Crylink_LinkExplode(self.queuenext, self);
187                 remove (self);
188                 return;
189         }
190         else if(finalhit)
191         {
192                 // just unlink
193                 W_Crylink_Dequeue(self);
194                 remove(self);
195                 return;
196         }
197         self.cnt = self.cnt - 1;
198         self.angles = vectoangles(self.velocity);
199         self.owner = world;
200         self.projectiledeathtype |= HITTYPE_BOUNCE;
201         // commented out as it causes a little hitch...
202         //if(proj.cnt == 0)
203         //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
204 }
205
206 void W_Crylink_Touch2 (void)
207 {
208         float finalhit;
209         float f;
210         //PROJECTILE_TOUCH;
211         local entity savenext, saveprev, saveown;
212         savenext = self.queuenext;
213         saveprev = self.queueprev;
214         saveown = self.realowner;
215         if(WarpZone_Projectile_Touch())
216         {
217                 if(wasfreed(self))
218                         W_Crylink_Dequeue_Raw(saveown, saveprev, self, savenext);
219                 return;
220         }
221
222         float a;
223         a = 1 - (time - self.fade_time) * self.fade_rate;
224
225         finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
226         if(finalhit)
227                 f = 1;
228         else
229                 f = cvar("g_balance_crylink_secondary_bouncedamagefactor");
230         if(a)
231                 f *= a;
232         if (RadiusDamage (self, self.realowner, cvar("g_balance_crylink_secondary_damage") * f, cvar("g_balance_crylink_secondary_edgedamage") * f, cvar("g_balance_crylink_secondary_radius"), world, cvar("g_balance_crylink_secondary_force") * f, self.projectiledeathtype, other) && cvar("g_balance_crylink_secondary_linkexplode"))
233         {
234                 W_Crylink_LinkExplode(self.queuenext, self);
235                 remove (self);
236                 return;
237         }
238         else if(finalhit)
239         {
240                 // just unlink
241                 W_Crylink_Dequeue(self);
242                 remove(self);
243                 return;
244         }
245         self.cnt = self.cnt - 1;
246         self.angles = vectoangles(self.velocity);
247         self.owner = world;
248         self.projectiledeathtype |= HITTYPE_BOUNCE;
249         // commented out as it causes a little hitch...
250         //if(proj.cnt == 0)
251         //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
252 }
253
254 void W_Crylink_Fadethink (void)
255 {
256         W_Crylink_Dequeue(self);
257         remove(self);
258 }
259
260 void W_Crylink_Attack (void)
261 {
262         local float counter, shots;
263         local entity proj, prevproj, firstproj;
264         local vector s;
265         vector forward, right, up;
266
267         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
268                 self.ammo_cells = self.ammo_cells - cvar("g_balance_crylink_primary_ammo");
269
270         W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", (cvar("g_balance_crylink_primary_damage")*cvar("g_balance_crylink_primary_shots")));
271         forward = v_forward;
272         right = v_right;
273         up = v_up;
274
275         shots = cvar("g_balance_crylink_primary_shots");
276         pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
277         proj = world;
278         while (counter < shots)
279         {
280                 proj = spawn ();
281                 proj.realowner = proj.owner = self;
282                 proj.classname = "spike";
283                 proj.bot_dodge = TRUE;
284                 proj.bot_dodgerating = cvar("g_balance_crylink_primary_damage");
285                 if(counter == 0) { // first projectile, store in firstproj for now
286                         firstproj = proj;
287                 }
288                 else if(counter == shots - 1) { // last projectile, link up with first projectile
289                         prevproj.queuenext = proj;
290                         firstproj.queueprev = proj;
291                         proj.queuenext = firstproj;
292                         proj.queueprev = prevproj;
293                 }
294                 else { // else link up with previous projectile
295                         prevproj.queuenext = proj;
296                         proj.queueprev = prevproj;
297                 }
298
299                 prevproj = proj;
300
301                 proj.movetype = MOVETYPE_BOUNCEMISSILE;
302                 PROJECTILE_MAKETRIGGER(proj);
303                 proj.projectiledeathtype = WEP_CRYLINK;
304                 //proj.gravity = 0.001;
305
306                 setorigin (proj, w_shotorg);
307                 setsize(proj, '0 0 0', '0 0 0');
308
309
310                 s = '0 0 0';
311                 if (counter == 0)
312                         s = '0 0 0';
313                 else
314                 {
315                         makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
316                         s_y = v_forward_x;
317                         s_z = v_forward_y;
318                 }
319                 s = s * cvar("g_balance_crylink_primary_spread") * g_weaponspreadfactor;
320                 W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, cvar("g_balance_crylink_primary_speed"), 0, 0, 0);
321                 proj.touch = W_Crylink_Touch;
322
323                 proj.think = W_Crylink_Fadethink;
324                 if(counter == 0)
325                 {
326                         proj.fade_time = time + cvar("g_balance_crylink_primary_middle_lifetime");
327                         self.fade_rate = 1 / cvar("g_balance_crylink_primary_middle_fadetime");
328                         proj.nextthink = time + cvar("g_balance_crylink_primary_middle_lifetime") + cvar("g_balance_crylink_primary_middle_fadetime");
329                 }
330                 else
331                 {
332                         proj.fade_time = time + cvar("g_balance_crylink_primary_other_lifetime");
333                         self.fade_rate = 1 / cvar("g_balance_crylink_primary_other_fadetime");
334                         proj.nextthink = time + cvar("g_balance_crylink_primary_other_lifetime") + cvar("g_balance_crylink_primary_other_fadetime");
335                 }
336                 proj.cnt = cvar("g_balance_crylink_primary_bounces");
337                 //proj.scale = 1 + 1 * proj.cnt;
338
339                 proj.angles = vectoangles (proj.velocity);
340
341                 //proj.glow_size = 20;
342
343                 proj.flags = FL_PROJECTILE;
344
345                 CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
346
347                 other = proj; MUTATOR_CALLHOOK(EditProjectile);
348
349                 counter = counter + 1;
350         }
351         self.crylink_lastgroup = proj;
352 }
353
354 void W_Crylink_Attack2 (void)
355 {
356         local float counter, shots;
357         local entity proj, prevproj, firstproj;
358
359         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
360                 self.ammo_cells = self.ammo_cells - cvar("g_balance_crylink_secondary_ammo");
361
362         W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", (cvar("g_balance_crylink_secondary_damage")*cvar("g_balance_crylink_secondary_shots")));
363
364         shots = cvar("g_balance_crylink_secondary_shots");
365         pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
366         proj = world;
367         while (counter < shots)
368         {
369                 proj = spawn ();
370                 proj.realowner = proj.owner = self;
371                 proj.classname = "spike";
372                 proj.bot_dodge = TRUE;
373                 proj.bot_dodgerating = cvar("g_balance_crylink_secondary_damage");
374                 if(counter == 0) { // first projectile, store in firstproj for now
375                         firstproj = proj;
376                 }
377                 else if(counter == shots - 1) { // last projectile, link up with first projectile
378                         prevproj.queuenext = proj;
379                         firstproj.queueprev = proj;
380                         proj.queuenext = firstproj;
381                         proj.queueprev = prevproj;
382                 }
383                 else { // else link up with previous projectile
384                         prevproj.queuenext = proj;
385                         proj.queueprev = prevproj;
386                 }
387
388                 prevproj = proj;
389
390                 proj.movetype = MOVETYPE_BOUNCEMISSILE;
391                 PROJECTILE_MAKETRIGGER(proj);
392                 proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY;
393                 //proj.gravity = 0.001;
394
395                 setorigin (proj, w_shotorg);
396                 setsize(proj, '0 0 0', '0 0 0');
397
398                 W_SetupProjectileVelocityEx(proj, (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * cvar("g_balance_crylink_secondary_spread") * g_weaponspreadfactor), v_up, cvar("g_balance_crylink_secondary_speed"), 0, 0, 0);
399                 proj.touch = W_Crylink_Touch2;
400                 proj.think = W_Crylink_Fadethink;
401                 if(counter == (shots - 1) / 2)
402                 {
403                         proj.fade_time = time + cvar("g_balance_crylink_secondary_middle_lifetime");
404                         self.fade_rate = 1 / cvar("g_balance_crylink_secondary_middle_fadetime");
405                         proj.nextthink = time + cvar("g_balance_crylink_secondary_middle_lifetime") + cvar("g_balance_crylink_secondary_middle_fadetime");
406                 }
407                 else
408                 {
409                         proj.fade_time = time + cvar("g_balance_crylink_secondary_line_lifetime");
410                         self.fade_rate = 1 / cvar("g_balance_crylink_secondary_line_fadetime");
411                         proj.nextthink = time + cvar("g_balance_crylink_secondary_line_lifetime") + cvar("g_balance_crylink_secondary_line_fadetime");
412                 }
413                 proj.cnt = cvar("g_balance_crylink_secondary_bounces");
414                 //proj.scale = 1 + 1 * proj.cnt;
415
416                 proj.angles = vectoangles (proj.velocity);
417
418                 //proj.glow_size = 20;
419
420                 proj.flags = FL_PROJECTILE;
421
422                 CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
423
424                 other = proj; MUTATOR_CALLHOOK(EditProjectile);
425
426                 counter = counter + 1;
427         }
428         self.crylink_lastgroup = proj;
429 }
430
431 void spawnfunc_weapon_crylink (void)
432 {
433         weapon_defaultspawnfunc(WEP_CRYLINK);
434 }
435
436 float w_crylink(float req)
437 {
438         if (req == WR_AIM)
439         {
440                 if (random() > 0.15)
441                         self.BUTTON_ATCK = bot_aim(cvar("g_balance_crylink_primary_speed"), 0, cvar("g_balance_crylink_primary_middle_lifetime"), FALSE);
442                 else
443                         self.BUTTON_ATCK2 = bot_aim(cvar("g_balance_crylink_secondary_speed"), 0, cvar("g_balance_crylink_secondary_middle_lifetime"), FALSE);
444         }
445         else if (req == WR_THINK)
446         {
447                 if (self.BUTTON_ATCK)
448                 {
449                         if (!self.crylink_waitrelease)
450                         if (weapon_prepareattack(0, cvar("g_balance_crylink_primary_refire")))
451                         {
452                                 W_Crylink_Attack();
453                                 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_crylink_primary_animtime"), w_ready);
454                                 if(cvar("g_balance_crylink_primary_joinspeed") != 0)
455                                         self.crylink_waitrelease = 1;
456                         }
457                 }
458                 else if(self.BUTTON_ATCK2 && cvar("g_balance_crylink_secondary"))
459                 {
460                         if (!self.crylink_waitrelease)
461                         if (weapon_prepareattack(1, cvar("g_balance_crylink_secondary_refire")))
462                         {
463                                 W_Crylink_Attack2();
464                                 weapon_thinkf(WFRAME_FIRE2, cvar("g_balance_crylink_secondary_animtime"), w_ready);
465                                 if(cvar("g_balance_crylink_secondary_joinspeed") != 0)
466                                         self.crylink_waitrelease = 2;
467                         }
468                 }
469                 else
470                 {
471                         if (self.crylink_waitrelease)
472                         {
473                                 // fired and released now!
474                                 if(self.crylink_lastgroup)
475                                 {
476                                         vector pos;
477                                         if(self.crylink_waitrelease == 1)
478                                         {
479                                                 pos = W_Crylink_LinkJoin(self.crylink_lastgroup, cvar("g_balance_crylink_primary_joinspeed"), cvar("g_balance_crylink_primary_jointime"));
480                                         }
481                                         else
482                                         {
483                                                 pos = W_Crylink_LinkJoin(self.crylink_lastgroup, cvar("g_balance_crylink_secondary_joinspeed"), cvar("g_balance_crylink_secondary_jointime"));
484                                         }
485
486                                         entity linkjoineffect;
487                                         linkjoineffect = spawn();
488                                         linkjoineffect.classname = "linkjoineffect";
489                                         linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
490                                         linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
491                                         linkjoineffect.owner = self;
492                                         setorigin(linkjoineffect, pos);
493                                 }
494                                 self.crylink_waitrelease = 0;
495                                 if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2))
496                                 {
497                                         // ran out of ammo!
498                                         self.cnt = WEP_CRYLINK;
499                                         self.switchweapon = w_getbestweapon(self);
500                                 }
501                         }
502                 }
503         }
504         else if (req == WR_PRECACHE)
505         {
506                 precache_model ("models/weapons/g_crylink.md3");
507                 precache_model ("models/weapons/v_crylink.md3");
508                 precache_model ("models/weapons/h_crylink.iqm");
509                 precache_sound ("weapons/crylink_fire.wav");
510                 precache_sound ("weapons/crylink_fire2.wav");
511                 precache_sound ("weapons/crylink_linkjoin.wav");
512         }
513         else if (req == WR_SETUP)
514                 weapon_setup(WEP_CRYLINK);
515         else if (req == WR_CHECKAMMO1)
516         {
517                 // don't "run out of ammo" and switch weapons while waiting for release
518                 if(self.crylink_lastgroup && self.crylink_waitrelease)
519                         return TRUE;
520                 return self.ammo_cells >= cvar("g_balance_crylink_primary_ammo");
521         }
522         else if (req == WR_CHECKAMMO2)
523         {
524                 // don't "run out of ammo" and switch weapons while waiting for release
525                 if(self.crylink_lastgroup && self.crylink_waitrelease)
526                         return TRUE;
527                 return self.ammo_cells >= cvar("g_balance_crylink_secondary_ammo");
528         }
529         return TRUE;
530 };
531 #endif
532 #ifdef CSQC
533 float w_crylink(float req)
534 {
535         if(req == WR_IMPACTEFFECT)
536         {
537                 vector org2;
538                 org2 = w_org + w_backoff * 2;
539                 if(w_deathtype & HITTYPE_SECONDARY)
540                 {
541                         pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1);
542                         if(!w_issilent)
543                                 sound(self, CHAN_PROJECTILE, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM);
544                 }
545                 else
546                 {
547                         pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1);
548                         if(!w_issilent)
549                                 sound(self, CHAN_PROJECTILE, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM);
550                 }
551         }
552         else if(req == WR_PRECACHE)
553         {
554                 precache_sound("weapons/crylink_impact2.wav");
555                 precache_sound("weapons/crylink_impact.wav");
556         }
557         else if (req == WR_SUICIDEMESSAGE)
558         {
559                 w_deathtypestring = "%s succeeded at self-destructing themself with the Crylink";
560         }
561         else if (req == WR_KILLMESSAGE)
562         {
563                 if(w_deathtype & HITTYPE_BOUNCE)
564                         w_deathtypestring = "%s could not hide from %s's Crylink"; // unchecked: SPLASH (SECONDARY can't be)
565                 else if(w_deathtype & HITTYPE_SPLASH)
566                         w_deathtypestring = "%s was too close to %s's Crylink"; // unchecked: SECONDARY
567                 else
568                         w_deathtypestring = "%s took a close look at %s's Crylink"; // unchecked: SECONDARY
569         }
570         return TRUE;
571 }
572 #endif
573 #endif