]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_devastator.qc
Add weaponstartoverride property to weapons
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / w_devastator.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id */ DEVASTATOR,
4 /* function */ W_Devastator,
5 /* ammotype */ IT_ROCKETS,
6 /* impulse  */ 9,
7 /* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
8 /* rating   */ BOT_PICKUP_RATING_HIGH,
9 /* model    */ "rl",
10 /* netname  */ "devastator",
11 /* fullname */ _("Devastator")
12 );
13
14 #define DEVASTATOR_SETTINGS(w_cvar,w_prop) \
15         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, ammo) \
16         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, animtime) \
17         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, damage) \
18         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, damageforcescale) \
19         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, detonatedelay) \
20         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, edgedamage) \
21         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, force) \
22         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, guidedelay) \
23         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, guidegoal) \
24         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, guiderate) \
25         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, guideratedelay) \
26         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, guidestop) \
27         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, health) \
28         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, lifetime) \
29         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, radius) \
30         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, refire) \
31         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, remote_damage) \
32         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, remote_edgedamage) \
33         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, remote_force) \
34         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, remote_radius) \
35         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, speed) \
36         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, speedaccel) \
37         w_cvar(WEP_DEVASTATOR, devastator, MO_NONE, speedstart) \
38         w_prop(WEP_DEVASTATOR, devastator, float,  reloading_ammo, reload_ammo) \
39         w_prop(WEP_DEVASTATOR, devastator, float,  reloading_time, reload_time) \
40         w_prop(WEP_DEVASTATOR, devastator, float,  switchdelay_raise, switchdelay_raise) \
41         w_prop(WEP_DEVASTATOR, devastator, float,  switchdelay_drop, switchdelay_drop) \
42         w_prop(WEP_DEVASTATOR, devastator, string, weaponreplace, weaponreplace) \
43         w_prop(WEP_DEVASTATOR, devastator, float,  weaponstart, weaponstart) \
44         w_prop(WEP_DEVASTATOR, devastator, float,  weaponstartoverride, weaponstartoverride)
45
46 #ifdef SVQC
47 DEVASTATOR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
48 .float rl_release;
49 .float rl_detonate_later;
50 #endif
51 #else
52 #ifdef SVQC
53 void spawnfunc_weapon_devastator() { weapon_defaultspawnfunc(WEP_DEVASTATOR); }
54 void spawnfunc_weapon_rocketlauncher() { spawnfunc_weapon_devastator(); }
55
56 void W_Devastator_Unregister()
57 {
58         if(self.realowner && self.realowner.lastrocket == self)
59         {
60                 self.realowner.lastrocket = world;
61                 // self.realowner.rl_release = 1;
62         }
63 }
64
65 void W_Devastator_Explode()
66 {
67         W_Devastator_Unregister();
68
69         if(other.takedamage == DAMAGE_AIM)
70                 if(IS_PLAYER(other))
71                         if(DIFF_TEAM(self.realowner, other))
72                                 if(other.deadflag == DEAD_NO)
73                                         if(IsFlying(other))
74                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
75
76         self.event_damage = func_null;
77         self.takedamage = DAMAGE_NO;
78
79         RadiusDamage (self, self.realowner, WEP_CVAR(devastator, damage), WEP_CVAR(devastator, edgedamage), WEP_CVAR(devastator, radius), world, world, WEP_CVAR(devastator, force), self.projectiledeathtype, other);
80
81         if (self.realowner.weapon == WEP_DEVASTATOR)
82         {
83                 if(self.realowner.ammo_rockets < WEP_CVAR(devastator, ammo))
84                 {
85                         self.realowner.cnt = WEP_DEVASTATOR;
86                         ATTACK_FINISHED(self.realowner) = time;
87                         self.realowner.switchweapon = w_getbestweapon(self.realowner);
88                 }
89         }
90         remove (self);
91 }
92
93 void W_Devastator_DoRemoteExplode()
94 {
95         W_Devastator_Unregister();
96
97         self.event_damage = func_null;
98         self.takedamage = DAMAGE_NO;
99
100         RadiusDamage (self, self.realowner, WEP_CVAR(devastator, remote_damage), WEP_CVAR(devastator, remote_edgedamage), WEP_CVAR(devastator, remote_radius), world, world, WEP_CVAR(devastator, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world);
101
102         if (self.realowner.weapon == WEP_DEVASTATOR)
103         {
104                 if(self.realowner.ammo_rockets < WEP_CVAR(devastator, ammo))
105                 {
106                         self.realowner.cnt = WEP_DEVASTATOR;
107                         ATTACK_FINISHED(self.realowner) = time;
108                         self.realowner.switchweapon = w_getbestweapon(self.realowner);
109                 }
110         }
111         remove (self);
112 }
113
114 void W_Devastator_RemoteExplode()
115 {
116         if(self.realowner.deadflag == DEAD_NO)
117         if(self.realowner.lastrocket)
118         {
119                 if((self.spawnshieldtime >= 0)
120                         ? (time >= self.spawnshieldtime) // timer
121                         : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(devastator, remote_radius)) // safety device
122                 )
123                 {
124                         W_Devastator_DoRemoteExplode();
125                 }
126         }
127 }
128
129 vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
130 {
131         if(thisdir * goaldir > maxturn_cos)
132                 return goaldir;
133         if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
134                 return thisdir; // refuse to guide (better than letting a numerical error happen)
135         float f, m2;
136         vector v;
137         // solve:
138         //   g = normalize(thisdir + goaldir * X)
139         //   thisdir * g = maxturn
140         //
141         //   gg = thisdir + goaldir * X
142         //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
143         //
144         //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
145         f = thisdir * goaldir;
146         //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
147         //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
148         m2 = maxturn_cos * maxturn_cos;
149         v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
150         return normalize(thisdir + goaldir * v_y); // the larger solution!
151 }
152 // assume thisdir == -goaldir:
153 //   f == -1
154 //   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
155 //   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
156 //   x^2 - 2 * x + 1 = 0
157 //   (x - 1)^2 = 0
158 //   x = 1
159 //   normalize(thisdir + goaldir)
160 //   normalize(0)
161
162 void W_Devastator_Think (void)
163 {
164         vector desireddir, olddir, newdir, desiredorigin, goal;
165 #if 0
166         float cosminang, cosmaxang, cosang;
167 #endif
168         float velspeed, f;
169         self.nextthink = time;
170         if (time > self.cnt)
171         {
172                 other = world;
173                 self.projectiledeathtype |= HITTYPE_BOUNCE;
174                 W_Devastator_Explode ();
175                 return;
176         }
177
178         // accelerate
179         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
180         velspeed = WEP_CVAR(devastator, speed) * g_weaponspeedfactor - (self.velocity * v_forward);
181         if (velspeed > 0)
182                 self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * g_weaponspeedfactor * frametime, velspeed);
183
184         // laser guided, or remote detonation
185         if (self.realowner.weapon == WEP_DEVASTATOR)
186         {
187                 if(self == self.realowner.lastrocket)
188                 if(!self.realowner.rl_release)
189                 if(!self.BUTTON_ATCK2)
190                 if(WEP_CVAR(devastator, guiderate))
191                 if(time > self.pushltime)
192                 if(self.realowner.deadflag == DEAD_NO)
193                 {
194                         f = WEP_CVAR(devastator, guideratedelay);
195                         if(f)
196                                 f = bound(0, (time - self.pushltime) / f, 1);
197                         else
198                                 f = 1;
199
200                         velspeed = vlen(self.velocity);
201
202                         makevectors(self.realowner.v_angle);
203                         desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
204                         desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
205                         olddir = normalize(self.velocity);
206
207                         // now it gets tricky... we want to move like some curve to approximate the target direction
208                         // but we are limiting the rate at which we can turn!
209                         goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + WEP_CVAR(devastator, guidegoal)) * desireddir;
210                         newdir = W_Devastator_SteerTo(olddir, normalize(goal - self.origin), cos(WEP_CVAR(devastator, guiderate) * f * frametime * DEG2RAD));
211
212                         self.velocity = newdir * velspeed;
213                         self.angles = vectoangles(self.velocity);
214
215                         if(!self.count)
216                         {
217                                 pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1);
218                                 // TODO add a better sound here
219                                 sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM);
220                                 self.count = 1;
221                         }
222                 }
223
224                 if(self.rl_detonate_later)
225                         W_Devastator_RemoteExplode();
226         }
227
228         if(self.csqcprojectile_clientanimate == 0)
229                 UpdateCSQCProjectile(self);
230 }
231
232 void W_Devastator_Touch (void)
233 {
234         if(WarpZone_Projectile_Touch())
235         {
236                 if(wasfreed(self))
237                         W_Devastator_Unregister();
238                 return;
239         }
240         W_Devastator_Unregister();
241         W_Devastator_Explode ();
242 }
243
244 void W_Devastator_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
245 {
246         if (self.health <= 0)
247                 return;
248         
249         if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
250                 return; // g_projectiles_damage says to halt
251                 
252         self.health = self.health - damage;
253         self.angles = vectoangles(self.velocity);
254         
255         if (self.health <= 0)
256                 W_PrepareExplosionByDamage(attacker, W_Devastator_Explode);
257 }
258
259 void W_Devastator_Attack (void)
260 {
261         entity missile;
262         entity flash;
263
264         W_DecreaseAmmo(ammo_rockets, WEP_CVAR(devastator, ammo), WEP_CVAR(devastator, reload_ammo));
265
266         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, WEP_CVAR(devastator, damage));
267         pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
268
269         missile = WarpZone_RefSys_SpawnSameRefSys(self);
270         missile.owner = missile.realowner = self;
271         self.lastrocket = missile;
272         if(WEP_CVAR(devastator, detonatedelay) >= 0)
273                 missile.spawnshieldtime = time + WEP_CVAR(devastator, detonatedelay);
274         else
275                 missile.spawnshieldtime = -1;
276         missile.pushltime = time + WEP_CVAR(devastator, guidedelay);
277         missile.classname = "rocket";
278         missile.bot_dodge = TRUE;
279         missile.bot_dodgerating = WEP_CVAR(devastator, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
280
281         missile.takedamage = DAMAGE_YES;
282         missile.damageforcescale = WEP_CVAR(devastator, damageforcescale);
283         missile.health = WEP_CVAR(devastator, health);
284         missile.event_damage = W_Devastator_Damage;
285         missile.damagedbycontents = TRUE;
286
287         missile.movetype = MOVETYPE_FLY;
288         PROJECTILE_MAKETRIGGER(missile);
289         missile.projectiledeathtype = WEP_DEVASTATOR;
290         setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
291
292         setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
293         W_SetupProjectileVelocity(missile, WEP_CVAR(devastator, speedstart), 0);
294         missile.angles = vectoangles (missile.velocity);
295
296         missile.touch = W_Devastator_Touch;
297         missile.think = W_Devastator_Think;
298         missile.nextthink = time;
299         missile.cnt = time + WEP_CVAR(devastator, lifetime);
300         missile.flags = FL_PROJECTILE;
301         missile.missile_flags = MIF_SPLASH; 
302
303         CSQCProjectile(missile, WEP_CVAR(devastator, guiderate) == 0 && WEP_CVAR(devastator, speedaccel) == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound
304
305         // muzzle flash for 1st person view
306         flash = spawn ();
307         setmodel (flash, "models/flash.md3"); // precision set below
308         SUB_SetFade (flash, time, 0.1);
309         flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
310         W_AttachToShotorg(flash, '5 0 0');
311
312         // common properties
313         other = missile; MUTATOR_CALLHOOK(EditProjectile);
314 }
315
316 float W_Devastator(float req)
317 {
318         entity rock;
319         float rockfound;
320         float ammo_amount;
321         switch(req)
322         {
323                 case WR_AIM: // WEAPONTODO: rewrite this, it's WAY too complicated for what it should be
324                 {
325                         // aim and decide to fire if appropriate
326                         self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), FALSE);
327                         if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
328                         {
329                                 // decide whether to detonate rockets
330                                 entity missile, targetlist, targ;
331                                 targetlist = findchainfloat(bot_attack, TRUE);
332                                 for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self)
333                                 {
334                                         targ = targetlist;
335                                         while(targ)
336                                         {
337                                                 if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius))
338                                                 {
339                                                         self.BUTTON_ATCK2 = TRUE;
340                                                         break;
341                                                 }
342                                                 targ = targ.chain;
343                                         }
344                                 }
345                                 
346                                 if(self.BUTTON_ATCK2) self.BUTTON_ATCK = FALSE;
347                         }
348                         
349                         return TRUE;
350                 }
351                 case WR_THINK:
352                 {
353                         if(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload
354                                 WEP_ACTION(self.weapon, WR_RELOAD);
355                         else
356                         {
357                                 if(self.BUTTON_ATCK)
358                                 {
359                                         if(self.rl_release || WEP_CVAR(devastator, guidestop))
360                                         if(weapon_prepareattack(0, WEP_CVAR(devastator, refire)))
361                                         {
362                                                 W_Devastator_Attack();
363                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
364                                                 self.rl_release = 0;
365                                         }
366                                 }
367                                 else
368                                         self.rl_release = 1;
369
370                                 if(self.BUTTON_ATCK2)
371                                 {
372                                         rockfound = 0;
373                                         for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
374                                         {
375                                                 if(!rock.rl_detonate_later)
376                                                 {
377                                                         rock.rl_detonate_later = TRUE;
378                                                         rockfound = 1;
379                                                 }
380                                         }
381                                         if(rockfound)
382                                                 sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
383                                 }
384                         }
385                         
386                         return TRUE;
387                 }
388                 case WR_INIT:
389                 {
390                         if(autocvar_sv_precacheweapons)
391                         {
392                                 precache_model("models/flash.md3");
393                                 precache_model("models/weapons/g_rl.md3");
394                                 precache_model("models/weapons/v_rl.md3");
395                                 precache_model("models/weapons/h_rl.iqm");
396                                 precache_sound("weapons/rocket_det.wav");
397                                 precache_sound("weapons/rocket_fire.wav");
398                                 precache_sound("weapons/rocket_mode.wav");
399                         }
400                         DEVASTATOR_SETTINGS(WEP_SKIPCVAR, WEP_SET_PROP)
401                         return TRUE;
402                 }
403                 case WR_SETUP:
404                 {
405                         self.current_ammo = ammo_rockets;
406                         self.rl_release = 1;
407                         return TRUE;
408                 }
409                 case WR_CHECKAMMO1:
410                 {
411                         // don't switch while guiding a missile
412                         if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR)
413                         {
414                                 ammo_amount = FALSE;
415                                 if(WEP_CVAR(devastator, reload_ammo))
416                                 {
417                                         if(self.ammo_rockets < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR]) < WEP_CVAR(devastator, ammo))
418                                                 ammo_amount = TRUE;
419                                 }
420                                 else if(self.ammo_rockets < WEP_CVAR(devastator, ammo))
421                                         ammo_amount = TRUE;
422                                 return !ammo_amount;
423                         }
424                         
425                         return TRUE;
426                 }
427                 case WR_CHECKAMMO2:
428                 {
429                         return FALSE;
430                 }
431                 case WR_CONFIG:
432                 {
433                         DEVASTATOR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
434                         return TRUE;
435                 }
436                 case WR_RESETPLAYER:
437                 {
438                         self.rl_release = 0;
439                         return TRUE;
440                 }
441                 case WR_RELOAD:
442                 {
443                         W_Reload(WEP_CVAR(devastator, ammo), "weapons/reload.wav");
444                         return TRUE;
445                 }
446                 case WR_SUICIDEMESSAGE:
447                 {
448                         return WEAPON_ROCKETLAUNCHER_SUICIDE;
449                 }
450                 case WR_KILLMESSAGE:
451                 {
452                         if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
453                                 return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH;
454                         else
455                                 return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT;
456                 }
457         }
458         return TRUE;
459 }
460 #endif
461 #ifdef CSQC
462 float W_Devastator(float req)
463 {
464         switch(req)
465         {
466                 case WR_IMPACTEFFECT:
467                 {
468                         vector org2;
469                         org2 = w_org + w_backoff * 12;
470                         pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
471                         if(!w_issilent)
472                                 sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
473                                 
474                         return TRUE;
475                 }
476                 case WR_INIT:
477                 {
478                         precache_sound("weapons/rocket_impact.wav");
479                         return TRUE;
480                 }
481         }
482         return TRUE;
483 }
484 #endif
485 #endif