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