]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
3a7d563d8b77ef0964c75b7de770b37a85f2159e
[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 <common/mutators/mutator/powerups/_mod.qh>
6 #include <common/mutators/mutator/status_effects/_mod.qh>
7 #include "../random_items/sv_random_items.qh"
8
9 bool autocvar_g_instagib_damagedbycontents = true;
10 bool autocvar_g_instagib_blaster_keepdamage = false;
11 bool autocvar_g_instagib_blaster_keepforce = false;
12 bool autocvar_g_instagib_mirrordamage;
13 bool autocvar_g_instagib_friendlypush = true;
14 //int autocvar_g_instagib_ammo_drop;
15 bool autocvar_g_instagib_ammo_convert_cells;
16 bool autocvar_g_instagib_ammo_convert_rockets;
17 bool autocvar_g_instagib_ammo_convert_shells;
18 bool autocvar_g_instagib_ammo_convert_bullets;
19
20 void instagib_replace_with_invisibility(entity this)
21 {
22         entity e = new(item_invisibility);
23         Item_CopyFields(this, e);
24
25         e.invisibility_finished = autocvar_g_instagib_invisibility_time;
26         StartItem(e, ITEM_Invisibility);
27 }
28
29 void instagib_replace_with_extralife(entity this)
30 {
31         entity e = new(item_extralife);
32         Item_CopyFields(this, e);
33
34         StartItem(e, ITEM_ExtraLife);
35 }
36
37 void instagib_replace_with_speed(entity this)
38 {
39         entity e = new(item_speed);
40         Item_CopyFields(this, e);
41
42         e.speed_finished = autocvar_g_instagib_speed_time;
43         StartItem(e, ITEM_Speed);
44 }
45
46 /// \brief Returns a random classname of the instagib item.
47 /// \param[in] prefix Prefix of the cvars that hold probabilities.
48 /// \return Random classname of the instagib item.
49 string RandomItems_GetRandomInstagibItemClassName(string prefix)
50 {
51         RandomSelection_Init();
52         IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it),
53         {
54                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
55                         it.m_canonical_spawnfunc);
56                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
57                 {
58                         LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
59                         continue;
60                 }
61                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
62         });
63         return RandomSelection_chosen_string;
64 }
65
66 .float instagib_nextthink;
67 .float instagib_needammo;
68 void instagib_stop_countdown(entity e)
69 {
70         if (!e.instagib_needammo)
71                 return;
72         Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO);
73         e.instagib_needammo = false;
74 }
75
76 void instagib_countdown(entity this)
77 {
78         float hp = GetResource(this, RES_HEALTH);
79
80         float dmg = (hp <= 10) ? 5 : 10;
81         Damage(this, this, this, dmg, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
82
83         entity annce = (hp <= 5) ? ANNCE_INSTAGIB_TERMINATED : Announcer_PickNumber(CNT_NORMAL, ceil(hp / 10));
84         Send_Notification(NOTIF_ONE, this, MSG_ANNCE, annce);
85
86         if (hp > 80)
87         {
88                 if (hp <= 90)
89                         Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO);
90                 else
91                         Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO);
92         }
93 }
94
95 void instagib_ammocheck(entity this)
96 {
97         if(time < this.instagib_nextthink)
98                 return;
99         if(!IS_PLAYER(this))
100                 return; // not a player
101
102         if(IS_DEAD(this) || game_stopped)
103                 instagib_stop_countdown(this);
104         else if (GetResource(this, RES_CELLS) > 0 || (this.items & IT_UNLIMITED_AMMO) || (this.flags & FL_GODMODE))
105                 instagib_stop_countdown(this);
106         else if(autocvar_g_rm && autocvar_g_rm_laser)
107         {
108                 if(!this.instagib_needammo)
109                 {
110                         Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE);
111                         this.instagib_needammo = true;
112                 }
113         }
114         else
115         {
116                 this.instagib_needammo = true;
117                 instagib_countdown(this);
118         }
119         this.instagib_nextthink = time + 1;
120 }
121
122 MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd)
123 {
124         FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); });
125 }
126
127 MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName)
128 {
129         M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName(
130                 M_ARGV(0, string));
131         return true;
132 }
133
134 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem)
135 {
136         entity item = M_ARGV(1, entity);
137
138         item.monster_loot = ITEM_VaporizerCells;
139 }
140
141 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn)
142 {
143         entity mon = M_ARGV(0, entity);
144
145         // always refill ammo
146         if(mon.monsterdef == MON_MAGE)
147                 mon.skin = 1;
148 }
149
150 MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver)
151 {
152         entity player = M_ARGV(0, entity);
153
154         instagib_stop_countdown(player);
155 }
156
157 MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidRandomStartWeapons)
158 {
159         return true;
160 }
161
162 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn)
163 {
164         entity player = M_ARGV(0, entity);
165
166         player.effects |= EF_FULLBRIGHT;
167 }
168
169 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink)
170 {
171         entity player = M_ARGV(0, entity);
172
173         instagib_ammocheck(player);
174 }
175
176 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen)
177 {
178         // no regeneration in instagib
179         return true;
180 }
181
182 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor)
183 {
184         M_ARGV(4, float) = M_ARGV(7, float); // take = damage
185         M_ARGV(5, float) = 0; // save
186 }
187
188 MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon)
189 {
190         // weapon dropping on death handled by FilterItem
191         return true;
192 }
193
194 MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
195 {
196         entity frag_attacker = M_ARGV(1, entity);
197         entity frag_target = M_ARGV(2, entity);
198         float frag_deathtype = M_ARGV(3, float);
199         float frag_damage = M_ARGV(4, float);
200         float frag_mirrordamage = M_ARGV(5, float);
201         vector frag_force = M_ARGV(6, vector);
202
203         if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
204                 frag_damage = 0;
205
206         if(IS_PLAYER(frag_target))
207         {
208                 if(frag_deathtype == DEATH_FALL.m_id)
209                         frag_damage = 0; // never count fall damage
210
211                 if(!autocvar_g_instagib_damagedbycontents)
212                 switch(DEATH_ENT(frag_deathtype))
213                 {
214                         case DEATH_DROWN:
215                         case DEATH_SLIME:
216                         case DEATH_LAVA:
217                                 frag_damage = 0;
218                                 break;
219                 }
220
221                 if(IS_PLAYER(frag_attacker))
222                 if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
223                 {
224                         if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker))
225                                 frag_force = '0 0 0';
226
227                         float armor = GetResource(frag_target, RES_ARMOR);
228                         if(armor)
229                         {
230                                 armor -= 1;
231                                 SetResource(frag_target, RES_ARMOR, armor);
232                                 frag_damage = 0;
233                                 frag_target.hitsound_damage_dealt += 1;
234                                 frag_attacker.hitsound_damage_dealt += 1;
235                                 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor);
236                         }
237                 }
238
239                 if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
240                 {
241                         if(frag_deathtype & HITTYPE_SECONDARY)
242                         {
243                                 if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target)
244                                 {
245                                         frag_damage = 0;
246                                         if(!autocvar_g_instagib_mirrordamage)
247                                                 frag_mirrordamage = 0; // never do mirror damage on enemies
248                                 }
249
250                                 if(frag_target != frag_attacker)
251                                 {
252                                         if(!autocvar_g_instagib_blaster_keepforce)
253                                                 frag_force = '0 0 0';
254                                 }
255                         }
256                 }
257         }
258
259         if(!autocvar_g_instagib_mirrordamage) // only apply the taking lives hack if we don't want to support real damage mirroring
260         if(IS_PLAYER(frag_attacker))
261         if(frag_mirrordamage > 0)
262         {
263                 // just lose extra LIVES, don't kill the player for mirror damage
264                 float armor = GetResource(frag_attacker, RES_ARMOR);
265                 if(armor > 0)
266                 {
267                         armor -= 1;
268                         SetResource(frag_attacker, RES_ARMOR, armor);
269                         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor);
270                         frag_attacker.hitsound_damage_dealt += frag_mirrordamage;
271                 }
272                 frag_mirrordamage = 0;
273         }
274
275         if(frag_target.alpha && frag_target.alpha < 1)
276         if(IS_PLAYER(frag_target))
277                 yoda = 1;
278
279         M_ARGV(4, float) = frag_damage;
280         M_ARGV(5, float) = frag_mirrordamage;
281         M_ARGV(6, vector) = frag_force;
282 }
283
284 MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems, CBC_ORDER_LAST)
285 {
286         start_health       = warmup_start_health       = 100;
287         start_armorvalue   = warmup_start_armorvalue   = 0;
288
289         start_ammo_shells  = warmup_start_ammo_shells  = 0;
290         start_ammo_nails   = warmup_start_ammo_nails   = 0;
291         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_instagib_ammo_start");
292         start_ammo_plasma  = warmup_start_ammo_plasma  = 0;
293         start_ammo_rockets = warmup_start_ammo_rockets = 0;
294         //start_ammo_fuel    = warmup_start_ammo_fuel    = 0;
295
296         start_weapons = warmup_start_weapons = WEPSET(VAPORIZER);
297         start_items |= IT_UNLIMITED_SUPERWEAPONS;
298 }
299
300 MUTATOR_HOOKFUNCTION(mutator_instagib, SetWeaponArena)
301 {
302         // turn weapon arena off
303         M_ARGV(0, string) = "off";
304 }
305
306 void instagib_replace_with_vaporizer_cells(entity item)
307 {
308         entity e = new(item_vaporizer_cells);
309         Item_CopyFields(item, e);
310         StartItem(e, ITEM_VaporizerCells);
311 }
312
313 MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
314 {
315         entity item = M_ARGV(0, entity);
316         entity def = item.itemdef;
317         if(def == ITEM_Strength || def == ITEM_Shield || def == ITEM_HealthMega || def == ITEM_ArmorMega)
318         {
319                 if(autocvar_g_powerups)
320                 {
321                         float r = random();
322                         if (r < 0.3)
323                                 instagib_replace_with_invisibility(item);
324                         else if (r < 0.6)
325                                 instagib_replace_with_extralife(item);
326                         else
327                                 instagib_replace_with_speed(item);
328                 }
329                 return true;
330         }
331
332         if(def == ITEM_Cells)
333         {
334                 if(autocvar_g_instagib_ammo_convert_cells)
335                         instagib_replace_with_vaporizer_cells(item);
336                 return true;
337         }
338         else if(def == ITEM_Rockets)
339         {
340                 if(autocvar_g_instagib_ammo_convert_rockets)
341                         instagib_replace_with_vaporizer_cells(item);
342                 return true;
343         }
344         else if(def == ITEM_Shells)
345         {
346                 if(autocvar_g_instagib_ammo_convert_shells)
347                         instagib_replace_with_vaporizer_cells(item);
348                 return true;
349         }
350         else if(def == ITEM_Bullets)
351         {
352                 if(autocvar_g_instagib_ammo_convert_bullets)
353                         instagib_replace_with_vaporizer_cells(item);
354                 return true;
355         }
356
357         if(item.weapon == WEP_VAPORIZER.m_id && Item_IsLoot(item))
358         {
359                 SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop);
360                 return false;
361         }
362
363         if(item.weapon == WEP_DEVASTATOR.m_id || item.weapon == WEP_VORTEX.m_id)
364         {
365                 instagib_replace_with_vaporizer_cells(item);
366                 return true;
367         }
368
369         if(item.itemdef.instanceOfPowerup)
370                 return false;
371
372         float cells = GetResource(item, RES_CELLS);
373         if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_vaporizer_cells")
374                 SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop);
375
376         if(cells && !item.weapon)
377                 return false;
378
379         return true;
380 }
381
382 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies)
383 {
384         float frag_deathtype = M_ARGV(3, float);
385
386         if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
387                 M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death
388 }
389
390 MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch)
391 {
392         if(MUTATOR_RETURNVALUE) return false;
393
394         entity item = M_ARGV(0, entity);
395         entity toucher = M_ARGV(1, entity);
396
397         if(GetResource(item, RES_CELLS))
398         {
399                 // play some cool sounds ;)
400                 float hp = GetResource(toucher, RES_HEALTH);
401                 if (IS_CLIENT(toucher))
402                 {
403                         if(hp <= 5)
404                                 Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND);
405                         else if(hp < 50)
406                                 Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY);
407                 }
408
409                 if(hp < 100)
410                         SetResource(toucher, RES_HEALTH, 100);
411
412                 return MUT_ITEMTOUCH_CONTINUE;
413         }
414
415         if(item.itemdef == ITEM_ExtraLife)
416         {
417                 GiveResource(toucher, RES_ARMOR, autocvar_g_instagib_extralives);
418                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES, autocvar_g_instagib_extralives);
419                 return MUT_ITEMTOUCH_PICKUP;
420         }
421
422         return MUT_ITEMTOUCH_CONTINUE;
423 }
424
425 MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString)
426 {
427         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib");
428 }
429
430 MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString)
431 {
432         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", InstaGib");
433 }
434
435 MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname)
436 {
437         M_ARGV(0, string) = "InstaGib";
438         return true;
439 }