]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
34a16b8a7aefba01b7e994f9b035085015386dee
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / instagib / sv_instagib.qc
1 #include "sv_instagib.qh"
2
3 #include <server/client.qh>
4 #include <common/items/_mod.qh>
5 #include "../random_items/sv_random_items.qh"
6
7 bool autocvar_g_instagib_damagedbycontents = true;
8 bool autocvar_g_instagib_blaster_keepdamage = false;
9 bool autocvar_g_instagib_blaster_keepforce = false;
10 bool autocvar_g_instagib_mirrordamage;
11 bool autocvar_g_instagib_friendlypush = true;
12 //int autocvar_g_instagib_ammo_drop;
13 bool autocvar_g_instagib_ammo_convert_cells;
14 bool autocvar_g_instagib_ammo_convert_rockets;
15 bool autocvar_g_instagib_ammo_convert_shells;
16 bool autocvar_g_instagib_ammo_convert_bullets;
17 float autocvar_g_instagib_speed_highspeed;
18
19 void instagib_invisibility(entity this)
20 {
21         this.strength_finished = autocvar_g_instagib_invisibility_time;
22         StartItem(this, ITEM_Invisibility);
23 }
24
25 void instagib_extralife(entity this)
26 {
27         StartItem(this, ITEM_ExtraLife);
28 }
29
30 void instagib_speed(entity this)
31 {
32         this.invincible_finished = autocvar_g_instagib_speed_time;
33         StartItem(this, ITEM_Speed);
34 }
35
36 /// \brief Returns a random classname of the instagib item.
37 /// \param[in] prefix Prefix of the cvars that hold probabilities.
38 /// \return Random classname of the instagib item.
39 string RandomItems_GetRandomInstagibItemClassName(string prefix)
40 {
41         RandomSelection_Init();
42         IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it),
43         {
44                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
45                         it.m_canonical_spawnfunc);
46                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
47                 {
48                         LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
49                         continue;
50                 }
51                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
52         });
53         return RandomSelection_chosen_string;
54 }
55
56 .float instagib_nextthink;
57 .float instagib_needammo;
58 void instagib_stop_countdown(entity e)
59 {
60         if (!e.instagib_needammo)
61                 return;
62         Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO);
63         e.instagib_needammo = false;
64 }
65 void instagib_ammocheck(entity this)
66 {
67         if(time < this.instagib_nextthink)
68                 return;
69         if(!IS_PLAYER(this))
70                 return; // not a player
71
72         if(IS_DEAD(this) || game_stopped)
73                 instagib_stop_countdown(this);
74         else if (GetResource(this, RES_CELLS) > 0 || (this.items & IT_UNLIMITED_AMMO) || (this.flags & FL_GODMODE))
75                 instagib_stop_countdown(this);
76         else if(autocvar_g_rm && autocvar_g_rm_laser)
77         {
78                 if(!this.instagib_needammo)
79                 {
80                         Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE);
81                         this.instagib_needammo = true;
82                 }
83         }
84         else
85         {
86                 float hp = GetResource(this, RES_HEALTH);
87                 this.instagib_needammo = true;
88                 if (hp <= 5)
89                 {
90                         Damage(this, this, this, 5, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
91                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED);
92                 }
93                 else if (hp <= 10)
94                 {
95                         Damage(this, this, this, 5, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
96                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_1);
97                 }
98                 else if (hp <= 20)
99                 {
100                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
101                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_2);
102                 }
103                 else if (hp <= 30)
104                 {
105                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
106                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_3);
107                 }
108                 else if (hp <= 40)
109                 {
110                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
111                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_4);
112                 }
113                 else if (hp <= 50)
114                 {
115                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
116                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_5);
117                 }
118                 else if (hp <= 60)
119                 {
120                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
121                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_6);
122                 }
123                 else if (hp <= 70)
124                 {
125                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
126                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_7);
127                 }
128                 else if (hp <= 80)
129                 {
130                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
131                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_8);
132                 }
133                 else if (hp <= 90)
134                 {
135                         Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO);
136                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
137                         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_9);
138                 }
139                 else
140                 {
141                         Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO);
142                         Damage(this, this, this, 10, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
143                 }
144         }
145         this.instagib_nextthink = time + 1;
146 }
147
148 MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd)
149 {
150         FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); });
151 }
152
153 MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName)
154 {
155         M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName(
156                 M_ARGV(0, string));
157         return true;
158 }
159
160 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem)
161 {
162         entity item = M_ARGV(1, entity);
163
164         item.monster_loot = ITEM_VaporizerCells;
165 }
166
167 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn)
168 {
169         entity mon = M_ARGV(0, entity);
170
171         // always refill ammo
172         if(mon.monsterdef == MON_MAGE)
173                 mon.skin = 1;
174 }
175
176 MUTATOR_HOOKFUNCTION(mutator_instagib, BotShouldAttack)
177 {
178         entity targ = M_ARGV(1, entity);
179
180         if (targ.items & ITEM_Invisibility.m_itemid)
181                 return true;
182 }
183
184 MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver)
185 {
186         entity player = M_ARGV(0, entity);
187
188         instagib_stop_countdown(player);
189 }
190
191 MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidRandomStartWeapons)
192 {
193         return true;
194 }
195
196 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn)
197 {
198         entity player = M_ARGV(0, entity);
199
200         player.effects |= EF_FULLBRIGHT;
201 }
202
203 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink)
204 {
205         entity player = M_ARGV(0, entity);
206
207         instagib_ammocheck(player);
208 }
209
210 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen)
211 {
212         // no regeneration in instagib
213         return true;
214 }
215
216 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups)
217 {
218         entity player = M_ARGV(0, entity);
219
220         if (!(player.effects & EF_FULLBRIGHT))
221                 player.effects |= EF_FULLBRIGHT;
222
223         if (player.items & ITEM_Invisibility.m_itemid)
224         {
225                 play_countdown(player, STAT(STRENGTH_FINISHED, player), SND_POWEROFF);
226                 if (time > STAT(STRENGTH_FINISHED, player))
227                 {
228                         if(!player.vehicle) // already reset upon exit
229                         {
230                                 player.alpha = default_player_alpha;
231                                 player.exteriorweaponentity.alpha = default_weapon_alpha;
232                         }
233                         player.items &= ~ITEM_Invisibility.m_itemid;
234                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY);
235                 }
236         }
237         else
238         {
239                 if (time < STAT(STRENGTH_FINISHED, player))
240                 {
241                         if(!player.vehicle) // incase the player is given powerups while inside a vehicle
242                         {
243                                 player.alpha = autocvar_g_instagib_invis_alpha;
244                                 player.exteriorweaponentity.alpha = autocvar_g_instagib_invis_alpha;
245                         }
246                         player.items |= ITEM_Invisibility.m_itemid;
247                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_INVISIBILITY, player.netname);
248                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_INVISIBILITY);
249                 }
250         }
251
252         if (player.items & ITEM_Speed.m_itemid)
253         {
254                 play_countdown(player, STAT(INVINCIBLE_FINISHED, player), SND_POWEROFF);
255                 if (time > STAT(INVINCIBLE_FINISHED, player))
256                 {
257                         player.items &= ~ITEM_Speed.m_itemid;
258                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_SPEED);
259                 }
260         }
261         else
262         {
263                 if (time < STAT(INVINCIBLE_FINISHED, player))
264                 {
265                         player.items |= ITEM_Speed.m_itemid;
266                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SPEED, player.netname);
267                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_SPEED);
268                 }
269         }
270 }
271
272 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPhysics_UpdateStats)
273 {
274         entity player = M_ARGV(0, entity);
275         // these automatically reset, no need to worry
276
277         if(player.items & ITEM_Speed.m_itemid)
278                 STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_instagib_speed_highspeed;
279 }
280
281 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor)
282 {
283         M_ARGV(4, float) = M_ARGV(7, float); // take = damage
284         M_ARGV(5, float) = 0; // save
285 }
286
287 MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon)
288 {
289         // weapon dropping on death handled by FilterItem
290         return true;
291 }
292
293 MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
294 {
295         entity frag_attacker = M_ARGV(1, entity);
296         entity frag_target = M_ARGV(2, entity);
297         float frag_deathtype = M_ARGV(3, float);
298         float frag_damage = M_ARGV(4, float);
299         float frag_mirrordamage = M_ARGV(5, float);
300         vector frag_force = M_ARGV(6, vector);
301
302         if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
303                 frag_damage = 0;
304
305         if(IS_PLAYER(frag_target))
306         {
307                 if(frag_deathtype == DEATH_FALL.m_id)
308                         frag_damage = 0; // never count fall damage
309
310                 if(!autocvar_g_instagib_damagedbycontents)
311                 switch(DEATH_ENT(frag_deathtype))
312                 {
313                         case DEATH_DROWN:
314                         case DEATH_SLIME:
315                         case DEATH_LAVA:
316                                 frag_damage = 0;
317                                 break;
318                 }
319
320                 if(IS_PLAYER(frag_attacker))
321                 if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
322                 {
323                         if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker))
324                                 frag_force = '0 0 0';
325
326                         float armor = GetResource(frag_target, RES_ARMOR);
327                         if(armor)
328                         {
329                                 armor -= 1;
330                                 SetResource(frag_target, RES_ARMOR, armor);
331                                 frag_damage = 0;
332                                 frag_target.damage_dealt += 1;
333                                 frag_attacker.damage_dealt += 1;
334                                 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor);
335                         }
336                 }
337
338                 if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
339                 {
340                         if(frag_deathtype & HITTYPE_SECONDARY)
341                         {
342                                 if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target)
343                                 {
344                                         frag_damage = 0;
345                                         if(!autocvar_g_instagib_mirrordamage)
346                                                 frag_mirrordamage = 0; // never do mirror damage on enemies
347                                 }
348
349                                 if(frag_target != frag_attacker)
350                                 {
351                                         if(!autocvar_g_instagib_blaster_keepforce)
352                                                 frag_force = '0 0 0';
353                                 }
354                         }
355                 }
356         }
357
358         if(!autocvar_g_instagib_mirrordamage) // only apply the taking lives hack if we don't want to support real damage mirroring
359         if(IS_PLAYER(frag_attacker))
360         if(frag_mirrordamage > 0)
361         {
362                 // just lose extra LIVES, don't kill the player for mirror damage
363                 float armor = GetResource(frag_attacker, RES_ARMOR);
364                 if(armor > 0)
365                 {
366                         armor -= 1;
367                         SetResource(frag_attacker, RES_ARMOR, armor);
368                         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor);
369                         frag_attacker.damage_dealt += frag_mirrordamage;
370                 }
371                 frag_mirrordamage = 0;
372         }
373
374         if(frag_target.alpha && frag_target.alpha < 1)
375         if(IS_PLAYER(frag_target))
376                 yoda = 1;
377
378         M_ARGV(4, float) = frag_damage;
379         M_ARGV(5, float) = frag_mirrordamage;
380         M_ARGV(6, vector) = frag_force;
381 }
382
383 MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems, CBC_ORDER_LAST)
384 {
385         start_health       = warmup_start_health       = 100;
386         start_armorvalue   = warmup_start_armorvalue   = 0;
387
388         start_ammo_shells  = warmup_start_ammo_shells  = 0;
389         start_ammo_nails   = warmup_start_ammo_nails   = 0;
390         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_instagib_ammo_start");
391         start_ammo_plasma  = warmup_start_ammo_plasma  = 0;
392         start_ammo_rockets = warmup_start_ammo_rockets = 0;
393         //start_ammo_fuel    = warmup_start_ammo_fuel    = 0;
394
395         start_weapons = warmup_start_weapons = WEPSET(VAPORIZER);
396         start_items |= IT_UNLIMITED_SUPERWEAPONS;
397 }
398
399 MUTATOR_HOOKFUNCTION(mutator_instagib, SetWeaponArena)
400 {
401         // turn weapon arena off
402         M_ARGV(0, string) = "off";
403 }
404
405 void replace_with_insta_cells(entity item)
406 {
407         entity e = new(item_vaporizer_cells);
408         setorigin(e, item.origin);
409         e.noalign = Item_ShouldKeepPosition(item);
410         e.cnt = item.cnt;
411         e.team = item.team;
412         e.spawnfunc_checked = true;
413         spawnfunc_item_vaporizer_cells(e);
414 }
415
416 MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
417 {
418         entity item = M_ARGV(0, entity);
419
420         if(item.classname == "item_cells")
421         {
422                 if(autocvar_g_instagib_ammo_convert_cells)
423                 {
424                         replace_with_insta_cells(item);
425                 }
426                 return true;
427         }
428         else if(item.classname == "item_rockets")
429         {
430                 if(autocvar_g_instagib_ammo_convert_rockets)
431                 {
432                         replace_with_insta_cells(item);
433                 }
434                 return true;
435         }
436         else if(item.classname == "item_shells")
437         {
438                 if(autocvar_g_instagib_ammo_convert_shells)
439                 {
440                         replace_with_insta_cells(item);
441                 }
442                 return true;
443         }
444         else if(item.classname == "item_bullets")
445         {
446                 if(autocvar_g_instagib_ammo_convert_bullets)
447                 {
448                         replace_with_insta_cells(item);
449                 }
450                 return true;
451         }
452
453         if(item.weapon == WEP_VAPORIZER.m_id && Item_IsLoot(item))
454         {
455                 SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop);
456                 return false;
457         }
458
459         if(item.weapon == WEP_DEVASTATOR.m_id || item.weapon == WEP_VORTEX.m_id)
460         {
461                 replace_with_insta_cells(item);
462                 return true;
463         }
464
465         if(item.flags & FL_POWERUP)
466                 return false;
467
468         float cells = GetResource(item, RES_CELLS);
469         if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_vaporizer_cells")
470                 SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop);
471
472         if(cells && !item.weapon)
473                 return false;
474
475         return true;
476 }
477
478 MUTATOR_HOOKFUNCTION(mutator_instagib, CustomizeWaypoint)
479 {
480         entity wp = M_ARGV(0, entity);
481         entity player = M_ARGV(1, entity);
482
483         entity e = WaypointSprite_getviewentity(player);
484
485         // if you have the invisibility powerup, sprites ALWAYS are restricted to your team
486         // but only apply this to real players, not to spectators
487         if((wp.owner.flags & FL_CLIENT) && (wp.owner.items & ITEM_Invisibility.m_itemid) && (e == player))
488         if(DIFF_TEAM(wp.owner, e))
489                 return true;
490 }
491
492 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies)
493 {
494         float frag_deathtype = M_ARGV(3, float);
495
496         if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
497                 M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death
498 }
499
500 MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch)
501 {
502         if(MUTATOR_RETURNVALUE) return false;
503
504         entity item = M_ARGV(0, entity);
505         entity toucher = M_ARGV(1, entity);
506
507         if(GetResource(item, RES_CELLS))
508         {
509                 // play some cool sounds ;)
510                 float hp = GetResource(toucher, RES_HEALTH);
511                 if (IS_CLIENT(toucher))
512                 {
513                         if(hp <= 5)
514                                 Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND);
515                         else if(hp < 50)
516                                 Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY);
517                 }
518
519                 if(hp < 100)
520                         SetResource(toucher, RES_HEALTH, 100);
521
522                 return MUT_ITEMTOUCH_CONTINUE;
523         }
524
525         if(item.itemdef == ITEM_ExtraLife)
526         {
527                 GiveResource(toucher, RES_ARMOR, autocvar_g_instagib_extralives);
528                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES, autocvar_g_instagib_extralives);
529                 return MUT_ITEMTOUCH_PICKUP;
530         }
531
532         return MUT_ITEMTOUCH_CONTINUE;
533 }
534
535 MUTATOR_HOOKFUNCTION(mutator_instagib, OnEntityPreSpawn)
536 {
537         if (MUTATOR_RETURNVALUE) return false;
538         if (!autocvar_g_powerups) { return; }
539         entity ent = M_ARGV(0, entity);
540         // Can't use .itemdef here
541         if (!(ent.classname == "item_strength" || ent.classname == "item_shield" || ent.classname == "item_health_mega"))
542                 return;
543
544         entity e = spawn();
545
546         float r = random();
547         if (r < 0.3)
548         {
549                 e.classname = "item_invisibility";
550                 setthink(e, instagib_invisibility);
551         }
552         else if (r < 0.6)
553         {
554                 e.classname = "item_extralife";
555                 setthink(e, instagib_extralife);
556         }
557         else
558         {
559                 e.classname = "item_speed";
560                 setthink(e, instagib_speed);
561         }
562
563         e.nextthink = time + 0.1;
564         e.spawnflags = ent.spawnflags;
565         e.noalign = ent.noalign;
566         setorigin(e, ent.origin);
567
568         return true;
569 }
570
571 MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString)
572 {
573         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib");
574 }
575
576 MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString)
577 {
578         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", instagib");
579 }
580
581 MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname)
582 {
583         M_ARGV(0, string) = "InstaGib";
584         return true;
585 }