]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_crylink.qc
crylink: also remember the last group of projectiles that was fired
[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_release;
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         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);
32
33         if(e.queuenext != e2)
34                 W_Crylink_LinkExplode(e.queuenext, e2);
35         remove (e);
36 }
37
38 // adjust towards center
39 // returns the origin where they will meet... and the time till the meeting is
40 // stored in w_crylink_linkjoin_time.
41 // could possibly network this origin and time, and display a special particle
42 // effect when projectiles meet there :P
43 float w_crylink_linkjoin_time;
44 vector W_Crylink_LinkJoin(entity e, float joinspeed)
45 {
46         vector avg_origin, avg_velocity;
47         vector targ_origin;
48         float avg_dist, n;
49         entity p;
50
51         avg_origin = e.origin;
52         avg_velocity = e.velocity;
53         n = 1;
54         for(p = e; (p = p.queuenext) != e; )
55         {
56                 avg_origin += p.origin;
57                 avg_velocity += p.velocity;
58                 ++n;
59         }
60         avg_origin *= (1.0 / n);
61         avg_velocity *= (1.0 / n);
62
63         // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
64         avg_dist = pow(vlen(e.origin - avg_origin), 2);
65         for(p = e; (p = p.queuenext) != e; )
66                 avg_dist += pow(vlen(e.origin - avg_origin), 2);
67         avg_dist *= (1.0 / n);
68
69         w_crylink_linkjoin_time = 0;
70         if(avg_dist == 0)
71                 return avg_origin; // no change needed
72
73         if(joinspeed == 0)
74         {
75                 e.velocity = avg_velocity;
76                 UpdateCSQCProjectile(e);
77                 for(p = e; (p = p.queuenext) != e; )
78                 {
79                         p.velocity = avg_velocity;
80                         UpdateCSQCProjectile(p);
81                 }
82         }
83         else
84         {
85                 w_crylink_linkjoin_time = avg_dist / joinspeed;
86                 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
87
88                 e.velocity = (targ_origin - e.origin) * (joinspeed / avg_dist);
89                 UpdateCSQCProjectile(e);
90                 for(p = e; (p = p.queuenext) != e; )
91                 {
92                         p.velocity = (targ_origin - p.origin) * (joinspeed / avg_dist);
93                         UpdateCSQCProjectile(p);
94                 }
95
96                 // analysis:
97                 //   joinspeed -> +infinity:
98                 //      w_crylink_linkjoin_time -> +0
99                 //      targ_origin -> avg_origin
100                 //      p->velocity -> HUEG towards center
101                 //   joinspeed -> 0:
102                 //      w_crylink_linkjoin_time -> +/- infinity
103                 //      targ_origin -> avg_velocity * +/- infinity
104                 //      p->velocity -> avg_velocity
105                 //   joinspeed -> -infinity:
106                 //      w_crylink_linkjoin_time -> -0
107                 //      targ_origin -> avg_origin
108                 //      p->velocity -> HUEG away from center
109         }
110
111         return targ_origin;
112 }
113
114 // NO bounce protection, as bounces are limited!
115 void W_Crylink_Touch (void)
116 {
117         float finalhit;
118         float f;
119         //PROJECTILE_TOUCH;
120         local entity savenext, saveprev, saveown;
121         saveown = self.realowner;
122         savenext = self.queuenext;
123         saveprev = self.queueprev;
124         if(WarpZone_Projectile_Touch())
125         {
126                 if(wasfreed(self))
127                         W_Crylink_Dequeue_Raw(saveown, saveprev, self, savenext);
128                 return;
129         }
130
131         float a;
132         a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
133
134         finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
135         if(finalhit)
136                 f = 1;
137         else
138                 f = cvar("g_balance_crylink_primary_bouncedamagefactor");
139         if(a)
140                 f *= a;
141         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) || finalhit)
142         {
143                 W_Crylink_LinkExplode(self.queuenext, self);
144                 remove (self);
145                 return;
146         }
147         self.cnt = self.cnt - 1;
148         self.angles = vectoangles(self.velocity);
149         self.owner = world;
150         self.projectiledeathtype |= HITTYPE_BOUNCE;
151         // commented out as it causes a little hitch...
152         //if(proj.cnt == 0)
153         //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
154 }
155
156 void W_Crylink_Touch2 (void)
157 {
158         float finalhit;
159         float f;
160         //PROJECTILE_TOUCH;
161         local entity savenext, saveprev, saveown;
162         savenext = self.queuenext;
163         saveprev = self.queueprev;
164         saveown = self.realowner;
165         if(WarpZone_Projectile_Touch())
166         {
167                 if(wasfreed(self))
168                         W_Crylink_Dequeue_Raw(saveown, saveprev, self, savenext);
169                 return;
170         }
171
172         float a;
173         a = 1 - (time - self.fade_time) * self.fade_rate;
174
175         finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
176         if(finalhit)
177                 f = 1;
178         else
179                 f = cvar("g_balance_crylink_secondary_bouncedamagefactor");
180         if(a)
181                 f *= a;
182         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))
183         {
184                 W_Crylink_LinkExplode(self.queuenext, self);
185                 remove (self);
186                 return;
187         }
188         else if(finalhit)
189         {
190                 // just unlink
191                 W_Crylink_Dequeue(self);
192                 remove(self);
193                 return;
194         }
195         self.cnt = self.cnt - 1;
196         self.angles = vectoangles(self.velocity);
197         self.owner = world;
198         self.projectiledeathtype |= HITTYPE_BOUNCE;
199         // commented out as it causes a little hitch...
200         //if(proj.cnt == 0)
201         //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
202 }
203
204 void W_Crylink_Fadethink (void)
205 {
206         W_Crylink_Dequeue(self);
207         remove(self);
208 }
209
210 void W_Crylink_Attack (void)
211 {
212         local float counter, shots;
213         local entity proj, prevproj, firstproj;
214         local vector s;
215         vector forward, right, up;
216
217         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
218                 self.ammo_cells = self.ammo_cells - cvar("g_balance_crylink_primary_ammo");
219
220         W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", (cvar("g_balance_crylink_primary_damage")*cvar("g_balance_crylink_primary_shots")));
221         forward = v_forward;
222         right = v_right;
223         up = v_up;
224
225         shots = cvar("g_balance_crylink_primary_shots");
226         pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
227         proj = world;
228         while (counter < shots)
229         {
230                 proj = spawn ();
231                 proj.realowner = proj.owner = self;
232                 proj.classname = "spike";
233                 proj.bot_dodge = TRUE;
234                 proj.bot_dodgerating = cvar("g_balance_crylink_primary_damage");
235                 if(counter == 0) { // first projectile, store in firstproj for now
236                         firstproj = proj;
237                 }
238                 else if(counter == shots - 1) { // last projectile, link up with first projectile
239                         prevproj.queuenext = proj;
240                         firstproj.queueprev = proj;
241                         proj.queuenext = firstproj;
242                         proj.queueprev = prevproj;
243                 }
244                 else { // else link up with previous projectile
245                         prevproj.queuenext = proj;
246                         proj.queueprev = prevproj;
247                 }
248
249                 prevproj = proj;
250
251                 proj.movetype = MOVETYPE_BOUNCEMISSILE;
252                 PROJECTILE_MAKETRIGGER(proj);
253                 proj.projectiledeathtype = WEP_CRYLINK;
254                 //proj.gravity = 0.001;
255
256                 setorigin (proj, w_shotorg);
257                 setsize(proj, '0 0 0', '0 0 0');
258
259
260                 s = '0 0 0';
261                 if (counter == 0)
262                         s = '0 0 0';
263                 else
264                 {
265                         makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
266                         s_y = v_forward_x;
267                         s_z = v_forward_y;
268                 }
269                 s = s * cvar("g_balance_crylink_primary_spread") * g_weaponspreadfactor;
270                 W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, cvar("g_balance_crylink_primary_speed"), 0, 0, 0);
271                 proj.touch = W_Crylink_Touch;
272
273                 proj.think = W_Crylink_Fadethink;
274                 if(counter == 0)
275                 {
276                         proj.fade_time = time + cvar("g_balance_crylink_primary_middle_lifetime");
277                         self.fade_rate = 1 / cvar("g_balance_crylink_primary_middle_fadetime");
278                         proj.nextthink = time + cvar("g_balance_crylink_primary_middle_lifetime") + cvar("g_balance_crylink_primary_middle_fadetime");
279                 }
280                 else if(counter <= 3)
281                 {
282                         proj.fade_time = time + cvar("g_balance_crylink_primary_star_lifetime");
283                         self.fade_rate = 1 / cvar("g_balance_crylink_primary_star_fadetime");
284                         proj.nextthink = time + cvar("g_balance_crylink_primary_star_lifetime") + cvar("g_balance_crylink_primary_star_fadetime");
285                 }
286                 else
287                 {
288                         proj.fade_time = time + cvar("g_balance_crylink_primary_other_lifetime");
289                         self.fade_rate = 1 / cvar("g_balance_crylink_primary_other_fadetime");
290                         proj.nextthink = time + cvar("g_balance_crylink_primary_other_lifetime") + cvar("g_balance_crylink_primary_other_fadetime");
291                 }
292                 proj.cnt = cvar("g_balance_crylink_primary_bounces");
293                 //proj.scale = 1 + 1 * proj.cnt;
294
295                 proj.angles = vectoangles (proj.velocity);
296
297                 //proj.glow_size = 20;
298
299                 proj.flags = FL_PROJECTILE;
300
301                 CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
302
303                 other = proj; MUTATOR_CALLHOOK(EditProjectile);
304
305                 counter = counter + 1;
306         }
307         self.crylink_lastgroup = proj;
308 }
309
310 void W_Crylink_Attack2 (void)
311 {
312         local float counter, shots;
313         local entity proj, prevproj, firstproj;
314
315         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
316                 self.ammo_cells = self.ammo_cells - cvar("g_balance_crylink_secondary_ammo");
317
318         W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", (cvar("g_balance_crylink_secondary_damage")*cvar("g_balance_crylink_secondary_shots")));
319
320         shots = cvar("g_balance_crylink_secondary_shots");
321         pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
322         proj = world;
323         while (counter < shots)
324         {
325                 proj = spawn ();
326                 proj.realowner = proj.owner = self;
327                 proj.classname = "spike";
328                 proj.bot_dodge = TRUE;
329                 proj.bot_dodgerating = cvar("g_balance_crylink_secondary_damage");
330                 if(counter == 0) { // first projectile, store in firstproj for now
331                         firstproj = proj;
332                 }
333                 else if(counter == shots - 1) { // last projectile, link up with first projectile
334                         prevproj.queuenext = proj;
335                         firstproj.queueprev = proj;
336                         proj.queuenext = firstproj;
337                         proj.queueprev = prevproj;
338                 }
339                 else { // else link up with previous projectile
340                         prevproj.queuenext = proj;
341                         proj.queueprev = prevproj;
342                 }
343
344                 prevproj = proj;
345
346                 proj.movetype = MOVETYPE_BOUNCEMISSILE;
347                 PROJECTILE_MAKETRIGGER(proj);
348                 proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY;
349                 //proj.gravity = 0.001;
350
351                 setorigin (proj, w_shotorg);
352                 setsize(proj, '0 0 0', '0 0 0');
353
354                 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);
355                 proj.touch = W_Crylink_Touch2;
356                 proj.think = W_Crylink_Fadethink;
357                 if(counter == (shots - 1) / 2)
358                 {
359                         proj.fade_time = time + cvar("g_balance_crylink_secondary_middle_lifetime");
360                         self.fade_rate = 1 / cvar("g_balance_crylink_secondary_middle_fadetime");
361                         proj.nextthink = time + cvar("g_balance_crylink_secondary_middle_lifetime") + cvar("g_balance_crylink_secondary_middle_fadetime");
362                 }
363                 else
364                 {
365                         proj.fade_time = time + cvar("g_balance_crylink_secondary_line_lifetime");
366                         self.fade_rate = 1 / cvar("g_balance_crylink_secondary_line_fadetime");
367                         proj.nextthink = time + cvar("g_balance_crylink_secondary_line_lifetime") + cvar("g_balance_crylink_secondary_line_fadetime");
368                 }
369                 proj.cnt = cvar("g_balance_crylink_secondary_bounces");
370                 //proj.scale = 1 + 1 * proj.cnt;
371
372                 proj.angles = vectoangles (proj.velocity);
373
374                 //proj.glow_size = 20;
375
376                 proj.flags = FL_PROJECTILE;
377
378                 CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
379
380                 other = proj; MUTATOR_CALLHOOK(EditProjectile);
381
382                 counter = counter + 1;
383         }
384         self.crylink_lastgroup = proj;
385 }
386
387 void spawnfunc_weapon_crylink (void)
388 {
389         weapon_defaultspawnfunc(WEP_CRYLINK);
390 }
391
392 float w_crylink(float req)
393 {
394         if (req == WR_AIM)
395         {
396                 if (random() > 0.15)
397                         self.BUTTON_ATCK = bot_aim(cvar("g_balance_crylink_primary_speed"), 0, cvar("g_balance_crylink_primary_middle_lifetime"), FALSE);
398                 else
399                         self.BUTTON_ATCK2 = bot_aim(cvar("g_balance_crylink_secondary_speed"), 0, cvar("g_balance_crylink_secondary_middle_lifetime"), FALSE);
400         }
401         else if (req == WR_THINK)
402         {
403                 if (self.BUTTON_ATCK)
404                 if (weapon_prepareattack(0, cvar("g_balance_crylink_primary_refire")))
405                 {
406                         W_Crylink_Attack();
407                         weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_crylink_primary_animtime"), w_ready);
408                 }
409                 if (self.BUTTON_ATCK2 && cvar("g_balance_crylink_secondary"))
410                 if (weapon_prepareattack(1, cvar("g_balance_crylink_secondary_refire")))
411                 {
412                         W_Crylink_Attack2();
413                         weapon_thinkf(WFRAME_FIRE2, cvar("g_balance_crylink_secondary_animtime"), w_ready);
414                 }
415         }
416         else if (req == WR_PRECACHE)
417         {
418                 precache_model ("models/weapons/g_crylink.md3");
419                 precache_model ("models/weapons/v_crylink.md3");
420                 precache_model ("models/weapons/h_crylink.iqm");
421                 precache_sound ("weapons/crylink_fire.wav");
422                 precache_sound ("weapons/crylink_fire2.wav");
423         }
424         else if (req == WR_SETUP)
425                 weapon_setup(WEP_CRYLINK);
426         else if (req == WR_CHECKAMMO1)
427                 return self.ammo_cells >= cvar("g_balance_crylink_primary_ammo");
428         else if (req == WR_CHECKAMMO2)
429                 return self.ammo_cells >= cvar("g_balance_crylink_secondary_ammo");
430         return TRUE;
431 };
432 #endif
433 #ifdef CSQC
434 float w_crylink(float req)
435 {
436         if(req == WR_IMPACTEFFECT)
437         {
438                 vector org2;
439                 org2 = w_org + w_backoff * 2;
440                 if(w_deathtype & HITTYPE_SECONDARY)
441                 {
442                         pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1);
443                         if(!w_issilent)
444                                 sound(self, CHAN_PROJECTILE, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM);
445                 }
446                 else
447                 {
448                         pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1);
449                         if(!w_issilent)
450                                 sound(self, CHAN_PROJECTILE, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM);
451                 }
452         }
453         else if(req == WR_PRECACHE)
454         {
455                 precache_sound("weapons/crylink_impact2.wav");
456                 precache_sound("weapons/crylink_impact.wav");
457         }
458         else if (req == WR_SUICIDEMESSAGE)
459         {
460                 w_deathtypestring = "%s succeeded at self-destructing themself with the Crylink";
461         }
462         else if (req == WR_KILLMESSAGE)
463         {
464                 if(w_deathtype & HITTYPE_BOUNCE)
465                         w_deathtypestring = "%s could not hide from %s's Crylink"; // unchecked: SPLASH (SECONDARY can't be)
466                 else if(w_deathtype & HITTYPE_SPLASH)
467                         w_deathtypestring = "%s was too close to %s's Crylink"; // unchecked: SECONDARY
468                 else
469                         w_deathtypestring = "%s took a close look at %s's Crylink"; // unchecked: SECONDARY
470         }
471         return TRUE;
472 }
473 #endif
474 #endif