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