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