Merge branch 'terencehill/menu_remove_tab_title' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator_instagib.qc
1 #include "../_all.qh"
2
3 #include "mutator.qh"
4
5 #include "../cl_client.qh"
6 #include "../../common/buffs.qh"
7
8 void spawnfunc_item_minst_cells (void)
9 {
10         if (!g_instagib) { remove(self); return; }
11         if (!self.ammo_cells)
12                 self.ammo_cells = autocvar_g_instagib_ammo_drop;
13
14         StartItem ("models/items/a_cells.md3",
15                            "misc/itempickup.wav", 45, 0,
16                            "Vaporizer Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100);
17 }
18
19 void instagib_health_mega()
20 {
21         self.max_health = 1;
22         StartItem ("models/items/g_h100.md3",
23                            "misc/megahealth.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
24                            "Extralife", IT_NAILS, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
25 }
26
27 .float instagib_nextthink;
28 .float instagib_needammo;
29 void instagib_stop_countdown(entity e)
30 {
31         if (!e.instagib_needammo)
32                 return;
33         Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_INSTAGIB_FINDAMMO);
34         e.instagib_needammo = false;
35 }
36 void instagib_ammocheck()
37 {
38         if(time < self.instagib_nextthink)
39                 return;
40         if(!IS_PLAYER(self))
41                 return; // not a player
42
43         if(self.deadflag || gameover)
44                 instagib_stop_countdown(self);
45         else if (self.ammo_cells > 0 || (self.items & IT_UNLIMITED_WEAPON_AMMO) || (self.flags & FL_GODMODE))
46                 instagib_stop_countdown(self);
47         else
48         {
49                 self.instagib_needammo = true;
50                 if (self.health <= 5)
51                 {
52                         Damage(self, self, self, 5, DEATH_NOAMMO, self.origin, '0 0 0');
53                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED);
54                 }
55                 else if (self.health <= 10)
56                 {
57                         Damage(self, self, self, 5, DEATH_NOAMMO, self.origin, '0 0 0');
58                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_1);
59                 }
60                 else if (self.health <= 20)
61                 {
62                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
63                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_2);
64                 }
65                 else if (self.health <= 30)
66                 {
67                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
68                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_3);
69                 }
70                 else if (self.health <= 40)
71                 {
72                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
73                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_4);
74                 }
75                 else if (self.health <= 50)
76                 {
77                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
78                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_5);
79                 }
80                 else if (self.health <= 60)
81                 {
82                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
83                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_6);
84                 }
85                 else if (self.health <= 70)
86                 {
87                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
88                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_7);
89                 }
90                 else if (self.health <= 80)
91                 {
92                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
93                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_8);
94                 }
95                 else if (self.health <= 90)
96                 {
97                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO);
98                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
99                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_NUM_9);
100                 }
101                 else
102                 {
103                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO);
104                         Damage(self, self, self, 10, DEATH_NOAMMO, self.origin, '0 0 0');
105                 }
106         }
107         self.instagib_nextthink = time + 1;
108 }
109
110 MUTATOR_HOOKFUNCTION(instagib_MatchEnd)
111 {
112         entity head;
113         FOR_EACH_PLAYER(head)
114                 instagib_stop_countdown(head);
115
116         return false;
117 }
118
119 MUTATOR_HOOKFUNCTION(instagib_MonsterLoot)
120 {
121         other.monster_loot = spawnfunc_item_minst_cells;
122
123         return false;
124 }
125
126 MUTATOR_HOOKFUNCTION(instagib_MonsterSpawn)
127 {
128         // always refill ammo
129         if(self.monsterid == MON_MAGE)
130                 self.skin = 1;
131
132         return false;
133 }
134
135 MUTATOR_HOOKFUNCTION(instagib_BotShouldAttack)
136 {
137         if(checkentity.items & IT_STRENGTH)
138                 return true;
139
140         return false;
141 }
142
143 MUTATOR_HOOKFUNCTION(instagib_MakePlayerObserver)
144 {
145         instagib_stop_countdown(self);
146         return false;
147 }
148
149 MUTATOR_HOOKFUNCTION(instagib_PlayerSpawn)
150 {
151         self.effects |= EF_FULLBRIGHT;
152         return false;
153 }
154
155 MUTATOR_HOOKFUNCTION(instagib_PlayerPreThink)
156 {
157         instagib_ammocheck();
158         return false;
159 }
160
161 MUTATOR_HOOKFUNCTION(instagib_PlayerRegen)
162 {
163         // no regeneration in instagib
164         return true;
165 }
166
167 MUTATOR_HOOKFUNCTION(instagib_PlayerPowerups)
168 {
169         if (!(self.effects & EF_FULLBRIGHT))
170                 self.effects |= EF_FULLBRIGHT;
171
172         if (self.items & IT_STRENGTH)
173         {
174                 play_countdown(self.strength_finished, "misc/poweroff.wav");
175                 if (time > self.strength_finished)
176                 {
177                         self.alpha = default_player_alpha;
178                         self.exteriorweaponentity.alpha = default_weapon_alpha;
179                         self.items &= ~IT_STRENGTH;
180                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY);
181                 }
182         }
183         else
184         {
185                 if (time < self.strength_finished)
186                 {
187                         self.alpha = autocvar_g_instagib_invis_alpha;
188                         self.exteriorweaponentity.alpha = autocvar_g_instagib_invis_alpha;
189                         self.items |= IT_STRENGTH;
190                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_INVISIBILITY, self.netname);
191                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_INVISIBILITY);
192                 }
193         }
194
195         if (self.items & IT_INVINCIBLE)
196         {
197                 play_countdown(self.invincible_finished, "misc/poweroff.wav");
198                 if (time > self.invincible_finished)
199                 {
200                         self.items &= ~IT_INVINCIBLE;
201                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_SPEED);
202                 }
203         }
204         else
205         {
206                 if (time < self.invincible_finished)
207                 {
208                         self.items |= IT_INVINCIBLE;
209                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_SPEED, self.netname);
210                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_SPEED);
211                 }
212         }
213         return false;
214 }
215
216 MUTATOR_HOOKFUNCTION(instagib_PlayerPhysics)
217 {
218         if(self.items & IT_INVINCIBLE)
219                 self.stat_sv_maxspeed = self.stat_sv_maxspeed * autocvar_g_instagib_speed_highspeed;
220
221         return false;
222 }
223
224 MUTATOR_HOOKFUNCTION(instagib_SplitHealthArmor)
225 {
226         damage_save = 0;
227         damage_take = frag_damage;
228
229         return false;
230 }
231
232 MUTATOR_HOOKFUNCTION(instagib_ForbidThrowing)
233 {
234         // weapon dropping on death handled by FilterItem
235
236         return true;
237 }
238
239 MUTATOR_HOOKFUNCTION(instagib_PlayerDamage)
240 {
241         if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
242                 frag_damage = 0;
243
244         if(IS_PLAYER(frag_target))
245         {
246                 if ((frag_deathtype == DEATH_FALL)  ||
247                         (frag_deathtype == DEATH_DROWN) ||
248                         (frag_deathtype == DEATH_SLIME) ||
249                         (frag_deathtype == DEATH_LAVA))
250                 {
251                         frag_damage = 0;
252                 }
253
254                 if(IS_PLAYER(frag_attacker))
255                 if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
256                 {
257                         if(frag_target.armorvalue)
258                         {
259                                 frag_target.armorvalue -= 1;
260                                 frag_damage = 0;
261                                 frag_target.damage_dealt += 1;
262                                 frag_attacker.damage_dealt += 1; // TODO: change this to a specific hitsound for armor hit
263                                 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_target.armorvalue);
264                         }
265                 }
266
267                 if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
268                 {
269                         if(frag_deathtype & HITTYPE_SECONDARY)
270                         {
271                                 frag_damage = frag_mirrordamage = 0;
272
273                                 if(frag_target != frag_attacker)
274                                 {
275                                         if(frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); }
276                                         frag_force = '0 0 0';
277                                 }
278                         }
279                 }
280         }
281
282         if(IS_PLAYER(frag_attacker))
283         if(frag_mirrordamage > 0)
284         {
285                 // just lose extra LIVES, don't kill the player for mirror damage
286                 if(frag_attacker.armorvalue > 0)
287                 {
288                         frag_attacker.armorvalue -= 1;
289                         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue);
290                         frag_attacker.damage_dealt += 1;
291                 }
292                 frag_mirrordamage = 0;
293         }
294
295         if((frag_target.buffs & BUFF_INVISIBLE) || (frag_target.items & IT_STRENGTH))
296                 yoda = 1;
297
298         return false;
299 }
300
301 MUTATOR_HOOKFUNCTION(instagib_SetStartItems)
302 {
303         start_health       = warmup_start_health       = 100;
304         start_armorvalue   = warmup_start_armorvalue   = 0;
305
306         start_ammo_shells  = warmup_start_ammo_shells  = 0;
307         start_ammo_nails   = warmup_start_ammo_nails   = 0;
308         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_instagib_ammo_start");
309         start_ammo_plasma  = warmup_start_ammo_plasma  = 0;
310         start_ammo_rockets = warmup_start_ammo_rockets = 0;
311         start_ammo_fuel    = warmup_start_ammo_fuel    = 0;
312
313         start_weapons = warmup_start_weapons = WEPSET_VAPORIZER;
314         start_items |= IT_UNLIMITED_SUPERWEAPONS;
315
316         return false;
317 }
318
319 MUTATOR_HOOKFUNCTION(instagib_FilterItem)
320 {
321         if(self.classname == "item_cells")
322                 return true; // no normal cells?
323
324         if(self.weapon == WEP_VAPORIZER && self.classname == "droppedweapon")
325         {
326                 self.ammo_cells = autocvar_g_instagib_ammo_drop;
327                 return false;
328         }
329
330         if(self.weapon == WEP_DEVASTATOR || self.weapon == WEP_VORTEX)
331         {
332                 entity e = spawn();
333                 setorigin(e, self.origin);
334                 entity oldself;
335                 oldself = self;
336                 self = e;
337                 spawnfunc_item_minst_cells();
338                 self = oldself;
339                 return true;
340         }
341
342         if(self.flags & FL_POWERUP)
343                 return false;
344
345         if(self.ammo_cells > autocvar_g_instagib_ammo_drop && self.classname != "item_minst_cells")
346                 self.ammo_cells = autocvar_g_instagib_ammo_drop;
347
348         if(self.ammo_cells && !self.weapon)
349                 return false;
350
351         return true;
352 }
353
354 MUTATOR_HOOKFUNCTION(instagib_CustomizeWaypoint)
355 {
356         entity e = WaypointSprite_getviewentity(other);
357
358         // if you have the invisibility powerup, sprites ALWAYS are restricted to your team
359         // but only apply this to real players, not to spectators
360         if((self.owner.flags & FL_CLIENT) && (self.owner.items & IT_STRENGTH) && (e == other))
361         if(DIFF_TEAM(self.owner, e))
362                 return true;
363
364         return false;
365 }
366
367 MUTATOR_HOOKFUNCTION(instagib_ItemCountdown)
368 {
369         switch(self.items)
370         {
371                 case IT_STRENGTH:   item_name = "item-invis"; item_color = '0 0 1'; break;
372                 case IT_NAILS:      item_name = "item-extralife"; item_color = '1 0 0'; break;
373                 case IT_INVINCIBLE: item_name = "item-speed"; item_color = '1 0 1'; break;
374         }
375         return false;
376 }
377
378 MUTATOR_HOOKFUNCTION(instagib_ItemTouch)
379 {
380         if(self.ammo_cells)
381         {
382                 // play some cool sounds ;)
383                 if (IS_CLIENT(other))
384                 {
385                         if(other.health <= 5)
386                                 Send_Notification(NOTIF_ONE, other, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND);
387                         else if(other.health < 50)
388                                 Send_Notification(NOTIF_ONE, other, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY);
389                 }
390
391                 if(other.health < 100)
392                         other.health = 100;
393
394                 return MUT_ITEMTOUCH_CONTINUE;
395         }
396
397         if(self.max_health)
398         {
399                 other.armorvalue = bound(other.armorvalue, 999, other.armorvalue + autocvar_g_instagib_extralives);
400                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES);
401                 return MUT_ITEMTOUCH_PICKUP;
402         }
403
404         return MUT_ITEMTOUCH_CONTINUE;
405 }
406
407 MUTATOR_HOOKFUNCTION(instagib_OnEntityPreSpawn)
408 {
409         if (!autocvar_g_powerups) { return false; }
410         if (!(self.classname == "item_strength" || self.classname == "item_invincible" || self.classname == "item_health_mega"))
411                 return false;
412
413         entity e = spawn();
414
415         if(random() < 0.3)
416                 e.think = spawnfunc_item_strength;
417         else if(random() < 0.6)
418                 e.think = instagib_health_mega;
419         else
420                 e.think = spawnfunc_item_invincible;
421
422         e.nextthink = time + 0.1;
423         e.spawnflags = self.spawnflags;
424         e.noalign = self.noalign;
425         setorigin(e, self.origin);
426
427         return true;
428 }
429
430 MUTATOR_HOOKFUNCTION(instagib_BuildMutatorsString)
431 {
432         ret_string = strcat(ret_string, ":instagib");
433         return false;
434 }
435
436 MUTATOR_HOOKFUNCTION(instagib_BuildMutatorsPrettyString)
437 {
438         ret_string = strcat(ret_string, ", instagib");
439         return false;
440 }
441
442 MUTATOR_HOOKFUNCTION(instagib_SetModname)
443 {
444         modname = "instagib";
445         return true;
446 }
447
448 MUTATOR_DEFINITION(mutator_instagib)
449 {
450         MUTATOR_HOOK(MatchEnd, instagib_MatchEnd, CBC_ORDER_ANY);
451         MUTATOR_HOOK(MonsterDropItem, instagib_MonsterLoot, CBC_ORDER_ANY);
452         MUTATOR_HOOK(MonsterSpawn, instagib_MonsterSpawn, CBC_ORDER_ANY);
453         MUTATOR_HOOK(BotShouldAttack, instagib_BotShouldAttack, CBC_ORDER_ANY);
454         MUTATOR_HOOK(PlayerPhysics, instagib_PlayerPhysics, CBC_ORDER_ANY);
455         MUTATOR_HOOK(PlayerSpawn, instagib_PlayerSpawn, CBC_ORDER_ANY);
456         MUTATOR_HOOK(PlayerDamage_Calculate, instagib_PlayerDamage, CBC_ORDER_ANY);
457         MUTATOR_HOOK(MakePlayerObserver, instagib_MakePlayerObserver, CBC_ORDER_ANY);
458         MUTATOR_HOOK(SetStartItems, instagib_SetStartItems, CBC_ORDER_ANY);
459         MUTATOR_HOOK(ItemTouch, instagib_ItemTouch, CBC_ORDER_ANY);
460         MUTATOR_HOOK(FilterItem, instagib_FilterItem, CBC_ORDER_ANY);
461         MUTATOR_HOOK(CustomizeWaypoint, instagib_CustomizeWaypoint, CBC_ORDER_ANY);
462         MUTATOR_HOOK(Item_RespawnCountdown, instagib_ItemCountdown, CBC_ORDER_ANY);
463         MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, instagib_SplitHealthArmor, CBC_ORDER_ANY);
464         MUTATOR_HOOK(PlayerPowerups, instagib_PlayerPowerups, CBC_ORDER_ANY);
465         MUTATOR_HOOK(ForbidThrowCurrentWeapon, instagib_ForbidThrowing, CBC_ORDER_ANY);
466         MUTATOR_HOOK(PlayerPreThink, instagib_PlayerPreThink, CBC_ORDER_ANY);
467         MUTATOR_HOOK(PlayerRegen, instagib_PlayerRegen, CBC_ORDER_ANY);
468         MUTATOR_HOOK(OnEntityPreSpawn, instagib_OnEntityPreSpawn, CBC_ORDER_ANY);
469         MUTATOR_HOOK(BuildMutatorsString, instagib_BuildMutatorsString, CBC_ORDER_ANY);
470         MUTATOR_HOOK(BuildMutatorsPrettyString, instagib_BuildMutatorsPrettyString, CBC_ORDER_ANY);
471         MUTATOR_HOOK(SetModname, instagib_SetModname, CBC_ORDER_ANY);
472
473         return false;
474 }