]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator_nades.qc
Fix spectators throwing nades
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator_nades.qc
1 .entity nade;
2 .float nade_refire;
3
4 void nade_timer_think()
5 {
6         self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10);
7         self.nextthink = time;
8         if(!self.owner || wasfreed(self.owner))
9                 remove(self);
10         
11 }
12
13 void nade_burn_spawn(entity _nade)
14 {
15         float p;
16         
17         switch(_nade.realowner.team)
18         {
19                 case NUM_TEAM_1: p = PROJECTILE_NADE_RED_BURN; break;
20                 case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE_BURN; break;
21                 case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW_BURN; break;
22                 case NUM_TEAM_4: p = PROJECTILE_NADE_PINK_BURN; break;
23                 default:                 p = PROJECTILE_NADE_BURN; break;
24         }
25         
26         CSQCProjectile(_nade, TRUE, p, TRUE);
27 }
28
29 void nade_spawn(entity _nade)
30 {
31         float p;
32         entity timer = spawn();
33         setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3");
34         setattachment(timer, _nade, "");
35         timer.classname = "nade_timer";
36         timer.colormap = _nade.colormap;
37         timer.glowmod = _nade.glowmod;
38         timer.think = nade_timer_think;
39         timer.nextthink = time;
40         timer.wait = _nade.wait;
41         timer.owner = _nade;    
42         timer.skin = 10;
43         
44         switch(_nade.realowner.team)
45         {
46                 case NUM_TEAM_1: p = PROJECTILE_NADE_RED; break;
47                 case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE; break;
48                 case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW; break;
49                 case NUM_TEAM_4: p = PROJECTILE_NADE_PINK; break;
50                 default:                 p = PROJECTILE_NADE; break;
51         }
52         
53         CSQCProjectile(_nade, TRUE, p, TRUE);
54         
55 }
56
57 void nade_boom() // TODO: DamageInfo
58 {
59         string expef;
60         
61         switch(self.realowner.team)
62         {
63                 case NUM_TEAM_1: expef = "nade_red_explode"; break;
64                 case NUM_TEAM_2: expef = "nade_blue_explode"; break;
65                 case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
66                 case NUM_TEAM_4: expef = "nade_pink_explode"; break;
67                 default:                 expef = "nade_explode"; break;
68         }
69         
70         sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTN_NORM);
71         sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
72         pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1);
73
74         self.takedamage = DAMAGE_NO;
75         RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
76                                  autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
77
78         remove(self);
79 }
80
81 void nade_touch()
82 {
83         PROJECTILE_TOUCH;
84         setsize(self, '-2 -2 -2', '2 2 2');
85         UpdateCSQCProjectile(self);
86         if(self.health == autocvar_g_nades_nade_health)
87         {
88                 spamsound(self, CH_SHOTS, strcat("weapons/grenade_bounce", ftos(1 + rint(random() * 5)), ".wav"), VOL_BASE, ATTN_NORM);
89                 return;
90         }
91
92         self.enemy = other;
93         nade_boom();
94 }
95
96 void nade_beep()
97 {
98         sound(self, CH_SHOTS_SINGLE, "overkill/grenadebip.ogg", VOL_BASE, 0.5 *(ATTN_LARGE + ATTN_MAX));
99         self.think = nade_boom;
100         self.nextthink = max(self.wait, time);
101 }
102
103 void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
104 {
105         if(DEATH_ISWEAPON(deathtype, WEP_LASER))
106                 return;
107
108         if(DEATH_ISWEAPON(deathtype, WEP_NEX) || DEATH_ISWEAPON(deathtype, WEP_MINSTANEX))
109         {
110                 force *= 6;
111                 damage = autocvar_g_nades_nade_health * 0.55;
112         }
113
114         if(DEATH_ISWEAPON(deathtype, WEP_UZI))
115                 damage = autocvar_g_nades_nade_health * 0.1;
116
117         if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN))
118                 damage = autocvar_g_nades_nade_health * 1.1;
119         
120         self.velocity += force;
121
122         if(!damage)
123                 return;
124
125         if(self.health == autocvar_g_nades_nade_health)
126         {
127                 sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, 0.5 *(ATTN_LARGE + ATTN_MAX));
128                 self.nextthink = max(time + autocvar_g_nades_nade_lifetime, time);
129                 self.think = nade_beep;
130         }
131
132         self.health   -= damage;
133         self.realowner = attacker;
134
135         if(self.health <= 0)
136                 W_PrepareExplosionByDamage(attacker, nade_boom);
137         else
138                 nade_burn_spawn(self);
139 }
140
141 void toss_nade(entity e, vector _velocity, float _time)
142 {
143         entity _nade = e.nade;
144         e.nade = world;
145         
146         Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES);
147         
148         setorigin(_nade, gettaginfo(_nade, gettagindex(_nade, "Object001")));
149         setattachment(_nade, world, "");
150         PROJECTILE_MAKETRIGGER(_nade);
151         setsize(_nade, '-16 -16 -16', '16 16 16');
152         _nade.movetype = MOVETYPE_BOUNCE;
153         
154         if(e.crouch)
155                 _nade.velocity = '0 0 -10';
156         else if(autocvar_g_nades_nade_newton_style == 1)
157                 _nade.velocity = e.velocity + _velocity;
158         else if(autocvar_g_nades_nade_newton_style == 2)
159                 _nade.velocity = _velocity;
160         else
161                 _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, FALSE);
162
163         _nade.solid = SOLID_BBOX;
164         _nade.touch = nade_touch;
165         _nade.health = autocvar_g_nades_nade_health;
166         _nade.takedamage = DAMAGE_YES;
167         _nade.event_damage = nade_damage;
168         _nade.teleportable = TRUE;
169
170         nade_spawn(_nade);
171
172         if(_time)
173         {
174                 _nade.think = nade_boom;
175                 _nade.nextthink = _time;
176         }
177
178         e.nade_refire = time + autocvar_g_nades_nade_refire;
179 }
180
181 void nade_prime()
182 {
183         if(self.nade)
184                 remove(self.nade);
185         
186         self.nade = spawn();
187         setmodel(self.nade, "models/weapons/h_ok_grenade.iqm");
188         setattachment(self.nade, self.weaponentity, "");
189         self.nade.classname = "nade";
190         self.nade.realowner = self;
191         self.nade.colormap = self.colormap;
192         self.nade.glowmod = self.glowmod;
193         self.nade.wait = time + autocvar_g_nades_nade_lifetime;
194         self.nade.cnt = time;
195         self.nade.think = nade_beep;
196         self.nade.nextthink = max(self.nade.wait - 3, time);
197         self.nade.projectiledeathtype = DEATH_NADE;
198 }
199
200 float CanThrowNade()
201 {
202         if(self.vehicle)
203                 return FALSE;
204                 
205         if(gameover)
206                 return FALSE;
207         
208         if not(autocvar_g_nades)
209                 return FALSE; // allow turning them off mid match
210                 
211         if(forbidWeaponUse())
212                 return FALSE;
213                 
214         if not(IS_PLAYER(self))
215                 return FALSE;
216                 
217         return TRUE;
218 }
219
220 MUTATOR_HOOKFUNCTION(nades_ForbidThrowing)
221 {
222         if(!g_weaponarena || !g_minstagib)
223                 return FALSE; // TODO: fix this to support all modes that don't disable weapon dropping
224                 
225         if(!CanThrowNade())
226                 return FALSE;
227                 
228         if(!self.nade)
229         {
230                 if(self.nade_refire < time)
231                 {
232                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW);
233                         nade_prime();
234                         self.nade_refire = time + autocvar_g_nades_nade_refire;
235                 }
236         }
237         else
238         {
239                 if(time - self.nade.cnt >= 1)
240                 {
241                         makevectors(self.v_angle);
242                         float _force = time - self.nade.cnt;
243                         _force /= autocvar_g_nades_nade_lifetime;
244                         _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
245                         toss_nade(self, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0);
246                 }
247         }
248         
249         return TRUE;
250 }
251
252 MUTATOR_HOOKFUNCTION(nades_VehicleEnter)
253 {
254         if(other.nade)
255                 toss_nade(other, '0 0 100', max(other.nade.wait, time + 0.05));
256         
257         return FALSE;
258 }
259
260 MUTATOR_HOOKFUNCTION(nades_PlayerPreThink)
261 {
262         float key_pressed = ((g_grappling_hook) ? self.button16 : self.BUTTON_HOOK);
263         
264         if(self.nade)
265                 if(self.nade.wait - 0.1 <= time)
266                         toss_nade(self, '0 0 0', time + 0.05);
267                         
268         if(CanThrowNade())
269         if(self.nade_refire < time)
270         {
271                 if(key_pressed)
272                 {
273                         if(!self.nade)
274                                 nade_prime();
275                 }
276                 else if(time - self.nade.cnt >= 1)
277                 {
278                         if(self.nade)
279                         {
280                                 makevectors(self.v_angle);
281                                 float _force = time - self.nade.cnt;
282                                 _force /= autocvar_g_nades_nade_lifetime;
283                                 _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));                         
284                                 toss_nade(self, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0);
285                         }
286                 }
287         }
288
289         return FALSE;
290 }
291
292 MUTATOR_HOOKFUNCTION(nades_PlayerSpawn)
293 {
294         if(autocvar_g_nades_spawn)
295                 self.nade_refire = time + autocvar_g_spawnshieldtime;
296         else
297                 self.nade_refire  = time + autocvar_g_nades_nade_refire;
298
299         return FALSE;
300 }
301
302 MUTATOR_HOOKFUNCTION(nades_PlayerDies)
303 {
304         if(self.nade)
305                 toss_nade(self, '0 0 100', max(self.nade.wait, time + 0.05));
306                 
307         return FALSE;
308 }
309
310 MUTATOR_HOOKFUNCTION(nades_BuildMutatorsString)
311 {
312         ret_string = strcat(ret_string, ":Nades");
313         return FALSE;
314 }
315
316 MUTATOR_HOOKFUNCTION(nades_BuildMutatorsPrettyString)
317 {
318         ret_string = strcat(ret_string, ", Nades");
319         return FALSE;
320 }
321
322 MUTATOR_DEFINITION(mutator_nades)
323 {
324         MUTATOR_HOOK(ForbidThrowCurrentWeapon, nades_ForbidThrowing, CBC_ORDER_ANY);
325         MUTATOR_HOOK(VehicleEnter, nades_VehicleEnter, CBC_ORDER_ANY);
326         MUTATOR_HOOK(PlayerPreThink, nades_PlayerPreThink, CBC_ORDER_ANY);
327         MUTATOR_HOOK(PlayerSpawn, nades_PlayerSpawn, CBC_ORDER_ANY);
328         MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_ANY);
329         MUTATOR_HOOK(BuildMutatorsString, nades_BuildMutatorsString, CBC_ORDER_ANY);
330         MUTATOR_HOOK(BuildMutatorsPrettyString, nades_BuildMutatorsPrettyString, CBC_ORDER_ANY);
331         
332         MUTATOR_ONADD
333         {
334                 precache_model("models/ok_nade_counter/ok_nade_counter.md3");
335                 
336                 precache_model("models/weapons/h_ok_grenade.iqm");
337                 precache_model("models/weapons/v_ok_grenade.md3");
338                 precache_sound("weapons/rocket_impact.wav");
339                 precache_sound("weapons/grenade_bounce1.wav");
340                 precache_sound("weapons/grenade_bounce2.wav");
341                 precache_sound("weapons/grenade_bounce3.wav");
342                 precache_sound("weapons/grenade_bounce4.wav");
343                 precache_sound("weapons/grenade_bounce5.wav");
344                 precache_sound("weapons/grenade_bounce6.wav");
345                 precache_sound("overkill/grenadebip.ogg");
346         }
347
348         return FALSE;
349 }