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