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