1 #include "sv_instagib.qh"
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"
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;
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)
25 RandomSelection_Init();
26 IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it),
28 string cvar_name = sprintf("g_%s_%s_probability", prefix,
29 it.m_canonical_spawnfunc);
30 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
32 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
35 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
37 return RandomSelection_chosen_string;
40 .float instagib_nextthink;
41 .float instagib_needammo;
42 void instagib_stop_countdown(entity e)
44 if (!e.instagib_needammo)
46 Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO);
47 e.instagib_needammo = false;
50 void instagib_countdown(entity this)
52 float hp = GetResource(this, RES_HEALTH);
54 float dmg = (hp <= 10) ? 5 : 10;
55 Damage(this, this, this, dmg, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0');
57 entity annce = (hp <= 5) ? ANNCE_INSTAGIB_TERMINATED : Announcer_PickNumber(CNT_NORMAL, ceil(hp / 10));
58 Send_Notification(NOTIF_ONE, this, MSG_ANNCE, annce);
63 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO);
65 Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO);
69 void instagib_ammocheck(entity this)
71 if(time < this.instagib_nextthink)
74 return; // not a player
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)
82 if(!this.instagib_needammo)
84 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE);
85 this.instagib_needammo = true;
90 this.instagib_needammo = true;
91 instagib_countdown(this);
93 this.instagib_nextthink = time + 1;
96 MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd)
98 FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); });
101 MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName)
103 M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName(
108 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem)
110 entity item = M_ARGV(1, entity);
112 item.itemdef = ITEM_VaporizerCells;
115 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn)
117 entity mon = M_ARGV(0, entity);
119 // always refill ammo
120 if(mon.monsterdef == MON_MAGE)
124 MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver)
126 entity player = M_ARGV(0, entity);
128 instagib_stop_countdown(player);
131 MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidRandomStartWeapons)
136 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn)
138 entity player = M_ARGV(0, entity);
140 player.effects |= EF_FULLBRIGHT;
143 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink)
145 entity player = M_ARGV(0, entity);
147 instagib_ammocheck(player);
150 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen)
152 // no regeneration in instagib
156 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor)
158 M_ARGV(4, float) = M_ARGV(7, float); // take = damage
159 M_ARGV(5, float) = 0; // save
162 MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon)
164 // weapon dropping on death handled by FilterItem
168 MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
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);
177 if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
180 if(IS_PLAYER(frag_target))
182 if(frag_deathtype == DEATH_FALL.m_id)
183 frag_damage = 0; // never count fall damage
185 if(!autocvar_g_instagib_damagedbycontents)
186 switch(DEATH_ENT(frag_deathtype))
195 if(IS_PLAYER(frag_attacker))
196 if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
198 if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker))
199 frag_force = '0 0 0';
201 float armor = GetResource(frag_target, RES_ARMOR);
205 SetResource(frag_target, RES_ARMOR, armor);
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);
213 if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
215 if(frag_deathtype & HITTYPE_SECONDARY)
217 if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target)
220 if(!autocvar_g_instagib_mirrordamage)
221 frag_mirrordamage = 0; // never do mirror damage on enemies
224 if(frag_target != frag_attacker)
226 if(!autocvar_g_instagib_blaster_keepforce)
227 frag_force = '0 0 0';
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)
237 // just lose extra LIVES, don't kill the player for mirror damage
238 float armor = GetResource(frag_attacker, RES_ARMOR);
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;
246 frag_mirrordamage = 0;
249 if(frag_target.alpha && frag_target.alpha < 1)
250 if(IS_PLAYER(frag_target))
253 M_ARGV(4, float) = frag_damage;
254 M_ARGV(5, float) = frag_mirrordamage;
255 M_ARGV(6, vector) = frag_force;
258 MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems, CBC_ORDER_LAST)
260 start_health = warmup_start_health = 100;
261 start_armorvalue = warmup_start_armorvalue = 0;
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;
270 start_weapons = warmup_start_weapons = WEPSET(VAPORIZER);
271 start_items |= IT_UNLIMITED_SUPERWEAPONS;
274 MUTATOR_HOOKFUNCTION(mutator_instagib, SetWeaponArena)
276 // turn weapon arena off
277 M_ARGV(0, string) = "off";
280 void instagib_replace_item_with(entity this, GameItem def)
282 entity new_item = NULL;
285 case ITEM_Invisibility:
286 new_item = new(item_invisibility);
287 new_item.invisibility_finished = autocvar_g_instagib_invisibility_time;
290 new_item = new(item_speed);
291 new_item.speed_finished = autocvar_g_instagib_speed_time;
294 new_item = new(item_extralife);
296 case ITEM_VaporizerCells:
297 new_item = new(item_vaporizer_cells);
300 error("Unhandled replacement item.");
302 Item_CopyFields(this, new_item);
303 StartItem(new_item, def);
306 const int INSTAGIB_POWERUP_COUNT = 3;
307 GameItem instagib_remaining_powerups[INSTAGIB_POWERUP_COUNT];
309 // replaces item with a random powerup selected among the less spawned ones
310 void instagib_replace_item_with_random_powerup(entity item)
312 static int remaining_powerups_count = INSTAGIB_POWERUP_COUNT;
313 if (remaining_powerups_count == 0)
314 remaining_powerups_count = INSTAGIB_POWERUP_COUNT;
315 if (remaining_powerups_count == INSTAGIB_POWERUP_COUNT)
317 instagib_remaining_powerups[0] = ITEM_Invisibility;
318 instagib_remaining_powerups[1] = ITEM_ExtraLife;
319 instagib_remaining_powerups[2] = ITEM_Speed;
322 float r = floor(random() * remaining_powerups_count);
323 instagib_replace_item_with(item, instagib_remaining_powerups[r]);
324 for(int i = r; i < INSTAGIB_POWERUP_COUNT - 1; ++i)
325 instagib_remaining_powerups[i] = instagib_remaining_powerups[i + 1];
326 --remaining_powerups_count;
329 MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
331 entity item = M_ARGV(0, entity);
332 entity def = item.itemdef;
333 if(def == ITEM_Strength || def == ITEM_Shield || def == ITEM_HealthMega || def == ITEM_ArmorMega)
335 if(autocvar_g_powerups)
336 instagib_replace_item_with_random_powerup(item);
340 if(def == ITEM_Cells)
342 if(autocvar_g_instagib_ammo_convert_cells)
343 instagib_replace_item_with(item, ITEM_VaporizerCells);
346 else if(def == ITEM_Rockets)
348 if(autocvar_g_instagib_ammo_convert_rockets)
349 instagib_replace_item_with(item, ITEM_VaporizerCells);
352 else if(def == ITEM_Shells)
354 if(autocvar_g_instagib_ammo_convert_shells)
355 instagib_replace_item_with(item, ITEM_VaporizerCells);
358 else if(def == ITEM_Bullets)
360 if(autocvar_g_instagib_ammo_convert_bullets)
361 instagib_replace_item_with(item, ITEM_VaporizerCells);
365 if(item.weapon == WEP_VAPORIZER.m_id && ITEM_IS_LOOT(item))
367 SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop);
371 if(item.weapon == WEP_DEVASTATOR.m_id || item.weapon == WEP_VORTEX.m_id)
373 instagib_replace_item_with(item, ITEM_VaporizerCells);
377 if(item.itemdef.instanceOfPowerup)
380 float cells = GetResource(item, RES_CELLS);
381 if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_vaporizer_cells")
382 SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop);
384 if(cells && !item.weapon)
390 MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies)
392 float frag_deathtype = M_ARGV(3, float);
394 if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
395 M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death
398 MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch)
400 if(MUTATOR_RETURNVALUE) return false;
402 entity item = M_ARGV(0, entity);
403 entity toucher = M_ARGV(1, entity);
405 if(GetResource(item, RES_CELLS))
407 // play some cool sounds ;)
408 float hp = GetResource(toucher, RES_HEALTH);
409 if (IS_CLIENT(toucher))
412 Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND);
414 Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY);
418 SetResource(toucher, RES_HEALTH, 100);
420 return MUT_ITEMTOUCH_CONTINUE;
423 if(item.itemdef == ITEM_ExtraLife)
425 GiveResource(toucher, RES_ARMOR, autocvar_g_instagib_extralives);
426 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES, autocvar_g_instagib_extralives);
427 Inventory_pickupitem(item.itemdef, toucher);
428 return MUT_ITEMTOUCH_PICKUP;
431 return MUT_ITEMTOUCH_CONTINUE;
434 MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString)
436 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib");
439 MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString)
441 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", InstaGib");
444 MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname)
446 M_ARGV(0, string) = "InstaGib";