Merge branch 'master' into terencehill/music_player
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_rocketlauncher.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id  */ ROCKET_LAUNCHER,
4 /* function  */ w_rlauncher,
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 /* shortname */ "rocketlauncher",
11 /* fullname  */ _("Rocket Launcher")
12 );
13 #else
14 #ifdef SVQC
15 .float rl_release;
16 .float rl_detonate_later;
17
18 void W_Rocket_Unregister()
19 {
20         if(self.realowner && self.realowner.lastrocket == self)
21         {
22                 self.realowner.lastrocket = world;
23                 // self.realowner.rl_release = 1;
24         }
25 }
26
27 void W_Rocket_Explode ()
28 {
29         W_Rocket_Unregister();
30
31         if(other.takedamage == DAMAGE_AIM)
32                 if(IS_PLAYER(other))
33                         if(DIFF_TEAM(self.realowner, other))
34                                 if(other.deadflag == DEAD_NO)
35                                         if(IsFlying(other))
36                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
37
38         self.event_damage = func_null;
39         self.takedamage = DAMAGE_NO;
40
41         RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other);
42
43         if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
44         {
45                 if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
46                 {
47                         self.realowner.cnt = WEP_ROCKET_LAUNCHER;
48                         ATTACK_FINISHED(self.realowner) = time;
49                         self.realowner.switchweapon = w_getbestweapon(self.realowner);
50                 }
51         }
52         remove (self);
53 }
54
55 void W_Rocket_DoRemoteExplode ()
56 {
57         W_Rocket_Unregister();
58
59         self.event_damage = func_null;
60         self.takedamage = DAMAGE_NO;
61
62         RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world);
63
64         if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
65         {
66                 if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
67                 {
68                         self.realowner.cnt = WEP_ROCKET_LAUNCHER;
69                         ATTACK_FINISHED(self.realowner) = time;
70                         self.realowner.switchweapon = w_getbestweapon(self.realowner);
71                 }
72         }
73         remove (self);
74 }
75
76 void W_Rocket_RemoteExplode()
77 {
78         if(self.realowner.deadflag == DEAD_NO)
79         if(self.realowner.lastrocket)
80         {
81                 if((self.spawnshieldtime >= 0)
82                         ? (time >= self.spawnshieldtime) // timer
83                         : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device
84                 )
85                 {
86                         W_Rocket_DoRemoteExplode();
87                 }
88         }
89 }
90
91 vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos)
92 {
93         if(thisdir * goaldir > maxturn_cos)
94                 return goaldir;
95         if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
96                 return thisdir; // refuse to guide (better than letting a numerical error happen)
97         float f, m2;
98         vector v;
99         // solve:
100         //   g = normalize(thisdir + goaldir * X)
101         //   thisdir * g = maxturn
102         //
103         //   gg = thisdir + goaldir * X
104         //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
105         //
106         //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
107         f = thisdir * goaldir;
108         //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
109         //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
110         m2 = maxturn_cos * maxturn_cos;
111         v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
112         return normalize(thisdir + goaldir * v_y); // the larger solution!
113 }
114 // assume thisdir == -goaldir:
115 //   f == -1
116 //   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
117 //   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
118 //   x^2 - 2 * x + 1 = 0
119 //   (x - 1)^2 = 0
120 //   x = 1
121 //   normalize(thisdir + goaldir)
122 //   normalize(0)
123
124 void W_Rocket_Think (void)
125 {
126         vector desireddir, olddir, newdir, desiredorigin, goal;
127 #if 0
128         float cosminang, cosmaxang, cosang;
129 #endif
130         float velspeed, f;
131         self.nextthink = time;
132         if (time > self.cnt)
133         {
134                 other = world;
135                 self.projectiledeathtype |= HITTYPE_BOUNCE;
136                 W_Rocket_Explode ();
137                 return;
138         }
139
140         // accelerate
141         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
142         velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward);
143         if (velspeed > 0)
144                 self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed);
145
146         // laser guided, or remote detonation
147         if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
148         {
149                 if(self == self.realowner.lastrocket)
150                 if (!self.realowner.rl_release)
151                 if (!self.BUTTON_ATCK2)
152                 if(autocvar_g_balance_rocketlauncher_guiderate)
153                 if(time > self.pushltime)
154                 if(self.realowner.deadflag == DEAD_NO)
155                 {
156                         f = autocvar_g_balance_rocketlauncher_guideratedelay;
157                         if(f)
158                                 f = bound(0, (time - self.pushltime) / f, 1);
159                         else
160                                 f = 1;
161
162                         velspeed = vlen(self.velocity);
163
164                         makevectors(self.realowner.v_angle);
165                         desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
166                         desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
167                         olddir = normalize(self.velocity);
168
169                         // now it gets tricky... we want to move like some curve to approximate the target direction
170                         // but we are limiting the rate at which we can turn!
171                         goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir;
172                         newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD));
173
174                         self.velocity = newdir * velspeed;
175                         self.angles = vectoangles(self.velocity);
176
177                         if(!self.count)
178                         {
179                                 pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1);
180                                 // TODO add a better sound here
181                                 sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTEN_NORM);
182                                 self.count = 1;
183                         }
184                 }
185
186                 if(self.rl_detonate_later)
187                         W_Rocket_RemoteExplode();
188         }
189
190         if(self.csqcprojectile_clientanimate == 0)
191                 UpdateCSQCProjectile(self);
192 }
193
194 void W_Rocket_Touch (void)
195 {
196         if(WarpZone_Projectile_Touch())
197         {
198                 if(wasfreed(self))
199                         W_Rocket_Unregister();
200                 return;
201         }
202         W_Rocket_Unregister();
203         W_Rocket_Explode ();
204 }
205
206 void W_Rocket_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
207 {
208         if (self.health <= 0)
209                 return;
210
211         if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
212                 return; // g_projectiles_damage says to halt
213
214         self.health = self.health - damage;
215         self.angles = vectoangles(self.velocity);
216
217         if (self.health <= 0)
218                 W_PrepareExplosionByDamage(attacker, W_Rocket_Explode);
219 }
220
221 void W_Rocket_Attack (void)
222 {
223         entity missile;
224         entity flash;
225
226         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo);
227
228         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage);
229         pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
230
231         missile = WarpZone_RefSys_SpawnSameRefSys(self);
232         missile.owner = missile.realowner = self;
233         self.lastrocket = missile;
234         if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0)
235                 missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay;
236         else
237                 missile.spawnshieldtime = -1;
238         missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay;
239         missile.classname = "rocket";
240         missile.bot_dodge = TRUE;
241         missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
242
243         missile.takedamage = DAMAGE_YES;
244         missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale;
245         missile.health = autocvar_g_balance_rocketlauncher_health;
246         missile.event_damage = W_Rocket_Damage;
247         missile.damagedbycontents = TRUE;
248
249         missile.movetype = MOVETYPE_FLY;
250         PROJECTILE_MAKETRIGGER(missile);
251         missile.projectiledeathtype = WEP_ROCKET_LAUNCHER;
252         setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
253
254         setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
255         W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0);
256         missile.angles = vectoangles (missile.velocity);
257
258         missile.touch = W_Rocket_Touch;
259         missile.think = W_Rocket_Think;
260         missile.nextthink = time;
261         missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime;
262         missile.flags = FL_PROJECTILE;
263         missile.missile_flags = MIF_SPLASH;
264
265         CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound
266
267         // muzzle flash for 1st person view
268         flash = spawn ();
269         setmodel (flash, "models/flash.md3"); // precision set below
270         SUB_SetFade (flash, time, 0.1);
271         flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
272         W_AttachToShotorg(flash, '5 0 0');
273
274         // common properties
275         other = missile; MUTATOR_CALLHOOK(EditProjectile);
276 }
277
278 void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc
279
280 float w_rlauncher(float req)
281 {
282         entity rock;
283         float rockfound;
284         float ammo_amount;
285
286         if (req == WR_AIM)
287         {
288                 // aim and decide to fire if appropriate
289                 self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE);
290                 if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
291                 {
292                         // decide whether to detonate rockets
293                         entity missile, targetlist, targ;
294                         float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
295                         float selfdamage, teamdamage, enemydamage;
296                         edgedamage = autocvar_g_balance_rocketlauncher_edgedamage;
297                         coredamage = autocvar_g_balance_rocketlauncher_damage;
298                         edgeradius = autocvar_g_balance_rocketlauncher_radius;
299                         recipricoledgeradius = 1 / edgeradius;
300                         selfdamage = 0;
301                         teamdamage = 0;
302                         enemydamage = 0;
303                         targetlist = findchainfloat(bot_attack, TRUE);
304                         missile = find(world, classname, "rocket");
305                         while (missile)
306                         {
307                                 if (missile.realowner != self)
308                                 {
309                                         missile = find(missile, classname, "rocket");
310                                         continue;
311                                 }
312                                 targ = targetlist;
313                                 while (targ)
314                                 {
315                                         d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin);
316                                         d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
317                                         // count potential damage according to type of target
318                                         if (targ == self)
319                                                 selfdamage = selfdamage + d;
320                                         else if (targ.team == self.team && teamplay)
321                                                 teamdamage = teamdamage + d;
322                                         else if (bot_shouldattack(targ))
323                                                 enemydamage = enemydamage + d;
324                                         targ = targ.chain;
325                                 }
326                                 missile = find(missile, classname, "rocket");
327                         }
328                         float desirabledamage;
329                         desirabledamage = enemydamage;
330                         if (time > self.invincible_finished && time > self.spawnshieldtime)
331                                 desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
332                         if (teamplay && self.team)
333                                 desirabledamage = desirabledamage - teamdamage;
334
335                         missile = find(world, classname, "rocket");
336                         while (missile)
337                         {
338                                 if (missile.realowner != self)
339                                 {
340                                         missile = find(missile, classname, "rocket");
341                                         continue;
342                                 }
343                                 makevectors(missile.v_angle);
344                                 targ = targetlist;
345                                 if (skill > 9) // normal players only do this for the target they are tracking
346                                 {
347                                         targ = targetlist;
348                                         while (targ)
349                                         {
350                                                 if (
351                                                         (v_forward * normalize(missile.origin - targ.origin)< 0.1)
352                                                         && desirabledamage > 0.1*coredamage
353                                                 )self.BUTTON_ATCK2 = TRUE;
354                                                 targ = targ.chain;
355                                         }
356                                 }else{
357                                         float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
358                                         //As the distance gets larger, a correct detonation gets near imposible
359                                         //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
360                                         if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1)
361                                                 if(IS_PLAYER(self.enemy))
362                                                         if(desirabledamage >= 0.1*coredamage)
363                                                                 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
364                                                                         self.BUTTON_ATCK2 = TRUE;
365                                 //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
366                                 }
367
368                                 missile = find(missile, classname, "rocket");
369                         }
370                         // if we would be doing at X percent of the core damage, detonate it
371                         // but don't fire a new shot at the same time!
372                         if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
373                                 self.BUTTON_ATCK2 = TRUE;
374                         if ((skill > 6.5) && (selfdamage > self.health))
375                                 self.BUTTON_ATCK2 = FALSE;
376                         //if(self.BUTTON_ATCK2 == TRUE)
377                         //      dprint(ftos(desirabledamage),"\n");
378                         if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
379                 }
380         }
381         else if (req == WR_THINK)
382         {
383                 if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload
384                         weapon_action(self.weapon, WR_RELOAD);
385                 else
386                 {
387                         if (self.BUTTON_ATCK)
388                         {
389                                 if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop)
390                                 if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire))
391                                 {
392                                         W_Rocket_Attack();
393                                         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready);
394                                         self.rl_release = 0;
395                                 }
396                         }
397                         else
398                                 self.rl_release = 1;
399
400                         if (self.BUTTON_ATCK2)
401                         {
402                                 rockfound = 0;
403                                 for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
404                                 {
405                                         if(!rock.rl_detonate_later)
406                                         {
407                                                 rock.rl_detonate_later = TRUE;
408                                                 rockfound = 1;
409                                         }
410                                 }
411                                 if(rockfound)
412                                         sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTEN_NORM);
413                         }
414                 }
415         }
416         else if (req == WR_PRECACHE)
417         {
418                 precache_model ("models/flash.md3");
419                 precache_model ("models/weapons/g_rl.md3");
420                 precache_model ("models/weapons/v_rl.md3");
421                 precache_model ("models/weapons/h_rl.iqm");
422                 precache_sound ("weapons/rocket_det.wav");
423                 precache_sound ("weapons/rocket_fire.wav");
424                 precache_sound ("weapons/rocket_mode.wav");
425                 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
426         }
427         else if (req == WR_SETUP)
428         {
429                 weapon_setup(WEP_ROCKET_LAUNCHER);
430                 self.current_ammo = ammo_rockets;
431                 self.rl_release = 1;
432         }
433         else if (req == WR_CHECKAMMO1)
434         {
435                 // don't switch while guiding a missile
436                 if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER)
437                 {
438                         ammo_amount = FALSE;
439                         if(autocvar_g_balance_rocketlauncher_reload_ammo)
440                         {
441                                 if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo)
442                                         ammo_amount = TRUE;
443                         }
444                         else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
445                                 ammo_amount = TRUE;
446                         return !ammo_amount;
447                 }
448         }
449         else if (req == WR_CHECKAMMO2)
450                 return FALSE;
451         else if (req == WR_RESETPLAYER)
452         {
453                 self.rl_release = 0;
454         }
455         else if (req == WR_RELOAD)
456         {
457                 W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav");
458         }
459         else if (req == WR_SUICIDEMESSAGE)
460         {
461                 return WEAPON_ROCKETLAUNCHER_SUICIDE;
462         }
463         else if (req == WR_KILLMESSAGE)
464         {
465                 if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
466                         return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH;
467                 else
468                         return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT;
469         }
470         return TRUE;
471 }
472 #endif
473 #ifdef CSQC
474 float w_rlauncher(float req)
475 {
476         if(req == WR_IMPACTEFFECT)
477         {
478                 vector org2;
479                 org2 = w_org + w_backoff * 12;
480                 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
481                 if(!w_issilent)
482                         sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
483         }
484         else if(req == WR_PRECACHE)
485         {
486                 precache_sound("weapons/rocket_impact.wav");
487         }
488         return TRUE;
489 }
490 #endif
491 #endif