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