diff --git a/xonotic special sauce/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc b/xonotic special sauce/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc new file mode 100644 index 0000000..b86e80a --- /dev/null +++ b/xonotic special sauce/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc @@ -0,0 +1,434 @@ +#include "sv_instagib.qh" + +#include +#include +#include +#include +#include "../random_items/sv_random_items.qh" + +bool autocvar_g_instagib_damagedbycontents = true; +bool autocvar_g_instagib_blaster_keepdamage = false; +bool autocvar_g_instagib_blaster_keepforce = false; +bool autocvar_g_instagib_mirrordamage; +bool autocvar_g_instagib_friendlypush = true; +//int autocvar_g_instagib_ammo_drop; +bool autocvar_g_instagib_ammo_convert_cells; +bool autocvar_g_instagib_ammo_convert_rockets; +bool autocvar_g_instagib_ammo_convert_shells; +bool autocvar_g_instagib_ammo_convert_bullets; + +/// \brief Returns a random classname of the instagib item. +/// \param[in] prefix Prefix of the cvars that hold probabilities. +/// \return Random classname of the instagib item. +string RandomItems_GetRandomInstagibItemClassName(string prefix) +{ + RandomSelection_Init(); + IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it), + { + string cvar_name = sprintf("g_%s_%s_probability", prefix, + it.m_canonical_spawnfunc); + if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) + { + LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); + continue; + } + RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1); + }); + return RandomSelection_chosen_string; +} + +.float instagib_nextthink; +.float instagib_needammo; +void instagib_stop_countdown(entity e) +{ + if (!e.instagib_needammo) + return; + Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO); + e.instagib_needammo = false; +} + +void instagib_countdown(entity this) +{ + float hp = GetResource(this, RES_HEALTH); + + float dmg = (hp <= 10) ? 5 : 10; + Damage(this, this, this, dmg, DEATH_NOAMMO.m_id, DMG_NOWEP, this.origin, '0 0 0'); + + entity annce = (hp <= 5) ? ANNCE_INSTAGIB_TERMINATED : Announcer_PickNumber(CNT_NORMAL, ceil(hp / 10)); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, annce); + + if (hp > 80) + { + if (hp <= 90) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO); + else + Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO); + } +} + +void instagib_ammocheck(entity this) +{ + if(time < this.instagib_nextthink) + return; + if(!IS_PLAYER(this)) + return; // not a player + + if(IS_DEAD(this) || game_stopped) + instagib_stop_countdown(this); + else if (GetResource(this, RES_CELLS) > 0 || (this.items & IT_UNLIMITED_AMMO) || (this.flags & FL_GODMODE)) + instagib_stop_countdown(this); + else if(autocvar_g_rm && autocvar_g_rm_laser) + { + if(!this.instagib_needammo) + { + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE); + this.instagib_needammo = true; + } + } + else + { + this.instagib_needammo = true; + instagib_countdown(this); + } + this.instagib_nextthink = time + 1; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd) +{ + FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); }); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName) +{ + M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName( + M_ARGV(0, string)); + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem) +{ + entity item = M_ARGV(1, entity); + + item.itemdef = ITEM_VaporizerCells; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn) +{ + entity mon = M_ARGV(0, entity); + + // always refill ammo + if(mon.monsterdef == MON_MAGE) + mon.skin = 1; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + instagib_stop_countdown(player); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidRandomStartWeapons) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.effects |= EF_FULLBRIGHT; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + instagib_ammocheck(player); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen) +{ + // no regeneration in instagib + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor) +{ + M_ARGV(4, float) = M_ARGV(7, float); // take = damage + M_ARGV(5, float) = 0; // save +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon) +{ + // weapon dropping on death handled by FilterItem + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + float frag_mirrordamage = M_ARGV(5, float); + vector frag_force = M_ARGV(6, vector); + + if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker)) + frag_damage = 0; + + if(IS_PLAYER(frag_target)) + { + if(frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; // never count fall damage + + if(!autocvar_g_instagib_damagedbycontents) + switch(DEATH_ENT(frag_deathtype)) + { + case DEATH_DROWN: + case DEATH_SLIME: + case DEATH_LAVA: + frag_damage = 0; + break; + } + + if(IS_PLAYER(frag_attacker)) + if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) + { + if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker)) + frag_force = '0 0 0'; + + float armor = GetResource(frag_target, RES_ARMOR); + if(armor) + { + armor -= 1; + SetResource(frag_target, RES_ARMOR, armor); + frag_damage = 0; + frag_target.hitsound_damage_dealt += 1; + frag_attacker.hitsound_damage_dealt += 1; + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor); + } + } + + if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) + { + if(frag_deathtype & HITTYPE_SECONDARY) + { + if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target) + { + frag_damage = 0; + if(!autocvar_g_instagib_mirrordamage) + frag_mirrordamage = 0; // never do mirror damage on enemies + } + + if(frag_target != frag_attacker) + { + if(!autocvar_g_instagib_blaster_keepforce) + frag_force = '0 0 0'; + } + } + } + } + + if(!autocvar_g_instagib_mirrordamage) // only apply the taking lives hack if we don't want to support real damage mirroring + if(IS_PLAYER(frag_attacker)) + if(frag_mirrordamage > 0) + { + // just lose extra LIVES, don't kill the player for mirror damage + float armor = GetResource(frag_attacker, RES_ARMOR); + if(armor > 0) + { + armor -= 1; + SetResource(frag_attacker, RES_ARMOR, armor); + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, armor); + frag_attacker.hitsound_damage_dealt += frag_mirrordamage; + } + frag_mirrordamage = 0; + } + + if(frag_target.alpha && frag_target.alpha < 1) + if(IS_PLAYER(frag_target)) + yoda = 1; + + M_ARGV(4, float) = frag_damage; + M_ARGV(5, float) = frag_mirrordamage; + M_ARGV(6, vector) = frag_force; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems, CBC_ORDER_LAST) +{ + start_health = warmup_start_health = 100; + start_armorvalue = warmup_start_armorvalue = 0; + + start_ammo_shells = warmup_start_ammo_shells = 0; + start_ammo_nails = warmup_start_ammo_nails = 0; + start_ammo_cells = warmup_start_ammo_cells = cvar("g_instagib_ammo_start"); + start_ammo_plasma = warmup_start_ammo_plasma = 0; + start_ammo_rockets = warmup_start_ammo_rockets = 0; + //start_ammo_fuel = warmup_start_ammo_fuel = 0; + + start_weapons = warmup_start_weapons = WEPSET(VAPORIZER); + start_items |= IT_UNLIMITED_SUPERWEAPONS; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, SetWeaponArena) +{ + // turn weapon arena off + M_ARGV(0, string) = "off"; +} + +void instagib_replace_item_with(entity this, GameItem def) +{ + entity new_item = spawn(); + switch (def) + { + case ITEM_Invisibility: + new_item.invisibility_finished = autocvar_g_instagib_invisibility_time; + break; + case ITEM_Speed: + new_item.speed_finished = autocvar_g_instagib_speed_time; + break; + } + Item_CopyFields(this, new_item); + StartItem(new_item, def); +} + +const int INSTAGIB_POWERUP_COUNT = 3; +GameItem instagib_remaining_powerups[INSTAGIB_POWERUP_COUNT]; + +// replaces item with a random powerup selected among the less spawned ones +void instagib_replace_item_with_random_powerup(entity item) +{ + static int remaining_powerups_count = INSTAGIB_POWERUP_COUNT; + if (remaining_powerups_count == 0) + remaining_powerups_count = INSTAGIB_POWERUP_COUNT; + if (remaining_powerups_count == INSTAGIB_POWERUP_COUNT) + { + instagib_remaining_powerups[0] = ITEM_Invisibility; + instagib_remaining_powerups[1] = ITEM_ExtraLife; + instagib_remaining_powerups[2] = ITEM_Speed; + } + + float r = floor(random() * remaining_powerups_count); + instagib_replace_item_with(item, instagib_remaining_powerups[r]); + for(int i = r; i < INSTAGIB_POWERUP_COUNT - 1; ++i) + instagib_remaining_powerups[i] = instagib_remaining_powerups[i + 1]; + --remaining_powerups_count; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem) +{ + entity item = M_ARGV(0, entity); + + switch (item.itemdef) + { + case ITEM_Strength: case ITEM_Shield: case ITEM_HealthMega: case ITEM_ArmorMega: + if(autocvar_g_powerups) + instagib_replace_item_with_random_powerup(item); + return true; + case ITEM_Cells: + if(autocvar_g_instagib_ammo_convert_cells) + instagib_replace_item_with(item, ITEM_VaporizerCells); + return true; + case ITEM_Rockets: + if(autocvar_g_instagib_ammo_convert_rockets) + instagib_replace_item_with(item, ITEM_VaporizerCells); + return true; + case ITEM_Shells: + if(autocvar_g_instagib_ammo_convert_shells) + instagib_replace_item_with(item, ITEM_VaporizerCells); + return true; + case ITEM_Bullets: + if(autocvar_g_instagib_ammo_convert_bullets) + instagib_replace_item_with(item, ITEM_VaporizerCells); + return true; + } + + switch (item.weapon) + { + case WEP_VAPORIZER.m_id: + if (ITEM_IS_LOOT(item)) + { + SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop); + return false; + } + break; + case WEP_DEVASTATOR.m_id: case WEP_VORTEX.m_id: + instagib_replace_item_with(item, ITEM_VaporizerCells); + return true; + } + + if(item.itemdef.instanceOfPowerup) + return false; + + float cells = GetResource(item, RES_CELLS); + if(cells > autocvar_g_instagib_ammo_drop && item.classname != "item_vaporizer_cells") + SetResource(item, RES_CELLS, autocvar_g_instagib_ammo_drop); + + if(cells && !item.weapon) + return false; + + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies) +{ + float frag_deathtype = M_ARGV(3, float); + entity attacker = M_ARGV(1, entity); + if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) + M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death + .entity weaponentity = weaponentities[0]; + ATTACK_FINISHED(attacker, weaponentity) = time; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch) +{ + if(MUTATOR_RETURNVALUE) return false; + + entity item = M_ARGV(0, entity); + entity toucher = M_ARGV(1, entity); + + if(GetResource(item, RES_CELLS)) + { + // play some cool sounds ;) + float hp = GetResource(toucher, RES_HEALTH); + if (IS_CLIENT(toucher)) + { + if(hp <= 5) + Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND); + else if(hp < 50) + Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY); + } + + if(hp < 100) + SetResource(toucher, RES_HEALTH, 100); + + return MUT_ITEMTOUCH_CONTINUE; + } + + if(item.itemdef == ITEM_ExtraLife) + { + GiveResource(toucher, RES_ARMOR, autocvar_g_instagib_extralives); + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES, autocvar_g_instagib_extralives); + Inventory_pickupitem(item.itemdef, toucher); + return MUT_ITEMTOUCH_PICKUP; + } + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib"); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", InstaGib"); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname) +{ + M_ARGV(0, string) = "InstaGib"; + return true; +}