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