Merge branch 'master' into terencehill/music_player
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_hagar.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id  */ HAGAR,
4 /* function  */ w_hagar,
5 /* ammotype  */ IT_ROCKETS,
6 /* impulse   */ 8,
7 /* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
8 /* rating    */ BOT_PICKUP_RATING_MID,
9 /* model     */ "hagar",
10 /* shortname */ "hagar",
11 /* fullname  */ _("Hagar")
12 );
13 #else
14 #ifdef SVQC
15 // NO bounce protection, as bounces are limited!
16
17 void W_Hagar_Explode (void)
18 {
19         self.event_damage = func_null;
20         RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_primary_damage, autocvar_g_balance_hagar_primary_edgedamage, autocvar_g_balance_hagar_primary_radius, world, autocvar_g_balance_hagar_primary_force, self.projectiledeathtype, other);
21
22         remove (self);
23 }
24
25 void W_Hagar_Explode2 (void)
26 {
27         self.event_damage = func_null;
28         RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_secondary_damage, autocvar_g_balance_hagar_secondary_edgedamage, autocvar_g_balance_hagar_secondary_radius, world, autocvar_g_balance_hagar_secondary_force, self.projectiledeathtype, other);
29
30         remove (self);
31 }
32
33 void W_Hagar_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
34 {
35         if (self.health <= 0)
36                 return;
37
38         float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : TRUE)
39                 && (inflictor.projectiledeathtype & HITTYPE_SECONDARY)
40                 && (self.projectiledeathtype & HITTYPE_SECONDARY));
41
42         if(is_linkexplode)
43                 is_linkexplode = (is_linkexplode && autocvar_g_balance_hagar_secondary_load_linkexplode);
44         else
45                 is_linkexplode = -1; // not secondary load, so continue as normal without exception.
46
47         if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode))
48                 return; // g_projectiles_damage says to halt
49
50         self.health = self.health - damage;
51         self.angles = vectoangles(self.velocity);
52
53         if (self.health <= 0)
54                 W_PrepareExplosionByDamage(attacker, self.think);
55 }
56
57 void W_Hagar_Touch (void)
58 {
59         PROJECTILE_TOUCH;
60         self.use ();
61 }
62
63 void W_Hagar_Touch2 (void)
64 {
65         PROJECTILE_TOUCH;
66
67         if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) {
68                 self.use();
69         } else {
70                 self.cnt++;
71                 pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1);
72                 self.angles = vectoangles (self.velocity);
73                 self.owner = world;
74                 self.projectiledeathtype |= HITTYPE_BOUNCE;
75         }
76 }
77
78 void W_Hagar_Attack (void)
79 {
80         entity missile;
81
82         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_reload_ammo);
83
84         W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_primary_damage);
85
86         pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
87
88         missile = spawn ();
89         missile.owner = missile.realowner = self;
90         missile.classname = "missile";
91         missile.bot_dodge = TRUE;
92         missile.bot_dodgerating = autocvar_g_balance_hagar_primary_damage;
93
94         missile.takedamage = DAMAGE_YES;
95         missile.health = autocvar_g_balance_hagar_primary_health;
96         missile.damageforcescale = autocvar_g_balance_hagar_primary_damageforcescale;
97         missile.event_damage = W_Hagar_Damage;
98         missile.damagedbycontents = TRUE;
99
100         missile.touch = W_Hagar_Touch;
101         missile.use = W_Hagar_Explode;
102         missile.think = adaptor_think2use_hittype_splash;
103         missile.nextthink = time + autocvar_g_balance_hagar_primary_lifetime;
104         PROJECTILE_MAKETRIGGER(missile);
105         missile.projectiledeathtype = WEP_HAGAR;
106         setorigin (missile, w_shotorg);
107         setsize(missile, '0 0 0', '0 0 0');
108
109         missile.movetype = MOVETYPE_FLY;
110         W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_primary);
111
112         missile.angles = vectoangles (missile.velocity);
113         missile.flags = FL_PROJECTILE;
114         missile.missile_flags = MIF_SPLASH;
115
116         CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE);
117
118         other = missile; MUTATOR_CALLHOOK(EditProjectile);
119 }
120
121 void W_Hagar_Attack2 (void)
122 {
123         entity missile;
124
125         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo);
126
127         W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage);
128
129         pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
130
131         missile = spawn ();
132         missile.owner = missile.realowner = self;
133         missile.classname = "missile";
134         missile.bot_dodge = TRUE;
135         missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage;
136
137         missile.takedamage = DAMAGE_YES;
138         missile.health = autocvar_g_balance_hagar_secondary_health;
139         missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale;
140         missile.event_damage = W_Hagar_Damage;
141         missile.damagedbycontents = TRUE;
142
143         missile.touch = W_Hagar_Touch2;
144         missile.cnt = 0;
145         missile.use = W_Hagar_Explode2;
146         missile.think = adaptor_think2use_hittype_splash;
147         missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand;
148         PROJECTILE_MAKETRIGGER(missile);
149         missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY;
150         setorigin (missile, w_shotorg);
151         setsize(missile, '0 0 0', '0 0 0');
152
153         missile.movetype = MOVETYPE_BOUNCEMISSILE;
154         W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_secondary);
155
156         missile.angles = vectoangles (missile.velocity);
157         missile.flags = FL_PROJECTILE;
158         missile.missile_flags = MIF_SPLASH;
159
160         CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR_BOUNCING, TRUE);
161
162         other = missile; MUTATOR_CALLHOOK(EditProjectile);
163 }
164
165 .float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
166 void W_Hagar_Attack2_Load_Release (void)
167 {
168         // time to release the rockets we've loaded
169
170         entity missile;
171         float counter, shots, spread_pershot;
172         vector s;
173         vector forward, right, up;
174
175         if(!self.hagar_load)
176                 return;
177
178         weapon_prepareattack_do(1, autocvar_g_balance_hagar_secondary_refire);
179
180         W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage);
181         pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
182
183         forward = v_forward;
184         right = v_right;
185         up = v_up;
186
187         shots = self.hagar_load;
188         missile = world;
189         for(counter = 0; counter < shots; ++counter)
190         {
191                 missile = spawn ();
192                 missile.owner = missile.realowner = self;
193                 missile.classname = "missile";
194                 missile.bot_dodge = TRUE;
195                 missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage;
196
197                 missile.takedamage = DAMAGE_YES;
198                 missile.health = autocvar_g_balance_hagar_secondary_health;
199                 missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale;
200                 missile.event_damage = W_Hagar_Damage;
201                 missile.damagedbycontents = TRUE;
202
203                 missile.touch = W_Hagar_Touch; // not bouncy
204                 missile.use = W_Hagar_Explode2;
205                 missile.think = adaptor_think2use_hittype_splash;
206                 missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand;
207                 PROJECTILE_MAKETRIGGER(missile);
208                 missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY;
209                 setorigin (missile, w_shotorg);
210                 setsize(missile, '0 0 0', '0 0 0');
211                 missile.movetype = MOVETYPE_FLY;
212                 missile.missile_flags = MIF_SPLASH;
213
214                 // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar)
215                 spread_pershot = ((shots - 1) / (autocvar_g_balance_hagar_secondary_load_max - 1));
216                 spread_pershot = (1 - (spread_pershot * autocvar_g_balance_hagar_secondary_load_spread_bias));
217                 spread_pershot = (autocvar_g_balance_hagar_secondary_spread * spread_pershot * g_weaponspreadfactor);
218
219                 // pattern spread calculation
220                 s = '0 0 0';
221                 if (counter == 0)
222                         s = '0 0 0';
223                 else
224                 {
225                         makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
226                         s_y = v_forward_x;
227                         s_z = v_forward_y;
228                 }
229                 s = s * autocvar_g_balance_hagar_secondary_load_spread * g_weaponspreadfactor;
230
231                 W_SetupProjectileVelocityEx(missile, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_hagar_secondary_speed, 0, 0, spread_pershot, FALSE);
232
233                 missile.angles = vectoangles (missile.velocity);
234                 missile.flags = FL_PROJECTILE;
235
236                 CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE);
237
238                 other = missile; MUTATOR_CALLHOOK(EditProjectile);
239         }
240
241         weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_load_animtime, w_ready);
242         self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_refire * W_WeaponRateFactor();
243         self.hagar_load = 0;
244 }
245
246 void W_Hagar_Attack2_Load (void)
247 {
248         // loadable hagar secondary attack, must always run each frame
249
250         if(time < game_starttime)
251                 return;
252
253         float loaded, enough_ammo;
254         loaded = self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max;
255
256         // this is different than WR_CHECKAMMO when it comes to reloading
257         if(autocvar_g_balance_hagar_reload_ammo)
258                 enough_ammo = self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo;
259         else
260                 enough_ammo = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo;
261
262         if(self.BUTTON_ATCK2)
263         {
264                 if(self.BUTTON_ATCK && autocvar_g_balance_hagar_secondary_load_abort)
265                 {
266                         if(self.hagar_load)
267                         {
268                                 // if we pressed primary fire while loading, unload all rockets and abort
269                                 self.weaponentity.state = WS_READY;
270                                 W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo
271                                 self.hagar_load = 0;
272                                 sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTEN_NORM);
273
274                                 // pause until we can load rockets again, once we re-press the alt fire button
275                                 self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor();
276
277                                 // require letting go of the alt fire button before we can load again
278                                 self.hagar_loadblock = TRUE;
279                         }
280                 }
281                 else
282                 {
283                         // check if we can attempt to load another rocket
284                         if(!loaded && enough_ammo)
285                         {
286                                 if(!self.hagar_loadblock && self.hagar_loadstep < time)
287                                 {
288                                         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo);
289                                         self.weaponentity.state = WS_INUSE;
290                                         self.hagar_load += 1;
291                                         sound(self, CH_WEAPON_B, "weapons/hagar_load.wav", VOL_BASE * 0.8, ATTEN_NORM); // sound is too loud according to most
292
293                                         if (self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max)
294                                                 self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_hold * W_WeaponRateFactor();
295                                         else
296                                                 self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor();
297                                 }
298                         }
299                         else if(!self.hagar_loadbeep && self.hagar_load) // prevents the beep from playing each frame
300                         {
301                                 // if this is the last rocket we can load, play a beep sound to notify the player
302                                 sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTEN_NORM);
303                                 self.hagar_loadbeep = TRUE;
304                         }
305                 }
306         }
307         else if(self.hagar_loadblock)
308         {
309                 // the alt fire button has been released, so re-enable loading if blocked
310                 self.hagar_loadblock = FALSE;
311         }
312
313         if(self.hagar_load)
314         {
315                 // play warning sound if we're about to release
316                 if((loaded || !enough_ammo) && self.hagar_loadstep - 0.5 < time && autocvar_g_balance_hagar_secondary_load_hold >= 0)
317                 {
318                         if(!self.hagar_warning && self.hagar_load) // prevents the beep from playing each frame
319                         {
320                                 // we're about to automatically release after holding time, play a beep sound to notify the player
321                                 sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTEN_NORM);
322                                 self.hagar_warning = TRUE;
323                         }
324                 }
325
326                 // release if player let go of button or if they've held it in too long
327                 if(!self.BUTTON_ATCK2 || ((loaded || !enough_ammo) && self.hagar_loadstep < time && autocvar_g_balance_hagar_secondary_load_hold >= 0))
328                 {
329                         self.weaponentity.state = WS_READY;
330                         W_Hagar_Attack2_Load_Release();
331                 }
332         }
333         else
334         {
335                 self.hagar_loadbeep = FALSE;
336                 self.hagar_warning = FALSE;
337         }
338
339         // we aren't checking ammo during an attack, so we must do it here
340         if (!(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2)))
341         {
342                 // note: this doesn't force the switch
343                 W_SwitchToOtherWeapon(self);
344                 return;
345         }
346 }
347
348 void spawnfunc_weapon_hagar (void)
349 {
350         weapon_defaultspawnfunc(WEP_HAGAR);
351 }
352
353 float w_hagar(float req)
354 {
355         float ammo_amount;
356         if (req == WR_AIM)
357                 if (random()>0.15)
358                         self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE);
359                 else
360                 {
361                         // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming
362                         self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE);
363                 }
364         else if (req == WR_THINK)
365         {
366                 float loadable_secondary;
367                 loadable_secondary = (autocvar_g_balance_hagar_secondary_load && autocvar_g_balance_hagar_secondary);
368
369                 if (loadable_secondary)
370                         W_Hagar_Attack2_Load(); // must always run each frame
371                 if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo)) // forced reload
372                         weapon_action(self.weapon, WR_RELOAD);
373                 else if (self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset
374                 {
375                         if (weapon_prepareattack(0, autocvar_g_balance_hagar_primary_refire))
376                         {
377                                 W_Hagar_Attack();
378                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hagar_primary_refire, w_ready);
379                         }
380                 }
381                 else if (self.BUTTON_ATCK2 && !loadable_secondary && autocvar_g_balance_hagar_secondary)
382                 {
383                         if (weapon_prepareattack(1, autocvar_g_balance_hagar_secondary_refire))
384                         {
385                                 W_Hagar_Attack2();
386                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_refire, w_ready);
387                         }
388                 }
389         }
390         else if (req == WR_GONETHINK)
391         {
392                 // we lost the weapon and want to prepare switching away
393                 if(self.hagar_load)
394                 {
395                         self.weaponentity.state = WS_READY;
396                         W_Hagar_Attack2_Load_Release();
397                 }
398         }
399         else if (req == WR_PRECACHE)
400         {
401                 precache_model ("models/weapons/g_hagar.md3");
402                 precache_model ("models/weapons/v_hagar.md3");
403                 precache_model ("models/weapons/h_hagar.iqm");
404                 precache_sound ("weapons/hagar_fire.wav");
405                 precache_sound ("weapons/hagar_load.wav");
406                 precache_sound ("weapons/hagar_beep.wav");
407                 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
408         }
409         else if (req == WR_SETUP)
410         {
411                 weapon_setup(WEP_HAGAR);
412                 self.current_ammo = ammo_rockets;
413                 self.hagar_loadblock = FALSE;
414
415                 if(self.hagar_load)
416                 {
417                         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo if necessary
418                         self.hagar_load = 0;
419                 }
420         }
421         else if (req == WR_CHECKAMMO1)
422         {
423                 ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_primary_ammo;
424                 ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_primary_ammo;
425                 return ammo_amount;
426         }
427         else if (req == WR_CHECKAMMO2)
428         {
429                 ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo;
430                 ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo;
431                 return ammo_amount;
432         }
433         else if (req == WR_RESETPLAYER)
434         {
435                 self.hagar_load = 0;
436         }
437         else if (req == WR_PLAYERDEATH)
438         {
439                 // if we have any rockets loaded when we die, release them
440                 if(self.hagar_load && autocvar_g_balance_hagar_secondary_load_releasedeath)
441                         W_Hagar_Attack2_Load_Release();
442         }
443         else if (req == WR_RELOAD)
444         {
445                 if (!self.hagar_load) // require releasing loaded rockets first
446                         W_Reload(min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo), autocvar_g_balance_hagar_reload_ammo, autocvar_g_balance_hagar_reload_time, "weapons/reload.wav");
447         }
448         else if (req == WR_SUICIDEMESSAGE)
449         {
450                 return WEAPON_HAGAR_SUICIDE;
451         }
452         else if (req == WR_KILLMESSAGE)
453         {
454                 if(w_deathtype & HITTYPE_SECONDARY)
455                         return WEAPON_HAGAR_MURDER_BURST;
456                 else
457                         return WEAPON_HAGAR_MURDER_SPRAY;
458         }
459         return TRUE;
460 }
461 #endif
462 #ifdef CSQC
463 float w_hagar(float req)
464 {
465         if(req == WR_IMPACTEFFECT)
466         {
467                 vector org2;
468                 org2 = w_org + w_backoff * 6;
469                 pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
470                 if(!w_issilent)
471                 {
472                         if (w_random<0.15)
473                                 sound(self, CH_SHOTS, "weapons/hagexp1.wav", VOL_BASE, ATTEN_NORM);
474                         else if (w_random<0.7)
475                                 sound(self, CH_SHOTS, "weapons/hagexp2.wav", VOL_BASE, ATTEN_NORM);
476                         else
477                                 sound(self, CH_SHOTS, "weapons/hagexp3.wav", VOL_BASE, ATTEN_NORM);
478                 }
479         }
480         else if(req == WR_PRECACHE)
481         {
482                 precache_sound("weapons/hagexp1.wav");
483                 precache_sound("weapons/hagexp2.wav");
484                 precache_sound("weapons/hagexp3.wav");
485         }
486         return TRUE;
487 }
488 #endif
489 #endif