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