From: otta8634 Date: Wed, 12 Feb 2025 09:29:11 +0000 (+0800) Subject: Properly disallow selection of disallowed nades X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=5b6db48b6d58660ef19e24226e50ba41807b3487;p=xonotic%2Fxonotic-data.pk3dir.git Properly disallow selection of disallowed nades Previously with g_nades_client_select, nade types could be selected with cl_nade_type even if the specific type was disabled, for example g_napalm is 0, but napalm nades could be selected regardless with cl_nade_type 2. This fixes that, by instead throwing a normal nade if a disallowed nade type is chosen by the player. This check isn't run on g_nades_nade_type and g_nades_bonus_type, so server admins can have setups such as the following: - Allow all nades except for ice to be chosen by the player as their offhand nade (since ice nade is a bit overpowered), reserve ice nade as the bonus nade. - Etc. Updated the nade type cvar descriptions to reflect that they now need to be set to 1 to allow client selection. This technically does impact balance, but is mostly a minor change, and is also actually a bug fix, so likely doesn't need a vote. It may be worthwhile now reconsidering which nades are allowed, currently everything is disallowed except ice, translocate, spawn, heal, and normal. --- diff --git a/mutators.cfg b/mutators.cfg index 51d8b2046..9b1476868 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -239,7 +239,7 @@ set g_nades_bonus_score_time -1 "bonus nade score given per second (negative t set g_nades_bonus_score_time_flagcarrier 2 "bonus nade score given per second as flag carrier (negative to have the score decay)" // Napalm (2) -set g_nades_napalm 0 "Napalm nade: spreads fire balls around the fountain and burns for a while" // script-ignore +set g_nades_napalm 0 "Napalm nade: spreads fire balls around the fountain and burns for a while; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_napalm_blast 1 "whether the napalm grenades also give damage with the usual grenade explosion" set g_nades_napalm_burntime 0.5 "time that the fire from napalm will stick to the player" set g_nades_napalm_selfdamage 1 "whether the player that tossed the nade can be harmed by its fire" @@ -258,55 +258,55 @@ set g_nades_napalm_fountain_edgedamage 20 "damage caused by the edge of the foun set g_nades_napalm_fountain_radius 130 "distance from the fountain" // Ice (3) -set g_nades_ice 1 "Ice nade: freezes and reduces health" // script-ignore +set g_nades_ice 1 "Ice nade: freezes and reduces health; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_ice_freeze_time 3 "how long the ice field will last" set g_nades_ice_health 0 "how much health the player will have after being unfrozen" set g_nades_ice_explode 0 "whether the ice nade should explode again once the ice field dissipated" set g_nades_ice_teamcheck 2 "\"0\" = freezes everyone including the player who threw the nade, \"1\" = freezes enemies and teammates, \"2\" = freezes only enemies" // Translocate (4) -set g_nades_translocate 1 "Translocate nade: teleports into explosion nade location" // script-ignore +set g_nades_translocate 1 "Translocate nade: teleports into explosion nade location; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_translocate_destroy_damage 25 "damage caused when translocate nade is destroyed by some attacker" // Spawn (5) -set g_nades_spawn 1 "Spawn nade: respawns into nade explosion location after being fragged" // script-ignore +set g_nades_spawn 1 "Spawn nade: respawns into nade explosion location after being fragged; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_spawn_count 3 "number of times player will spawn at their spawn nade explosion location" set g_nades_spawn_health_respawn 0 "how much health the player will have when being respawned; \"0\" = normal health respawn" set g_nades_spawn_destroy_damage 25 "damage caused when spawn nade is destroyed by some attacker" // Heal (6) -set g_nades_heal 1 "Heal nade: spawns a orb to recover health inside, enemies take the reverse effect when being inside orb" // script-ignore +set g_nades_heal 1 "Heal nade: spawns a orb to recover health inside, enemies take the reverse effect when being inside orb; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_heal_time 5 "how long the healing field will last" set g_nades_heal_rate 30 "health given per second" set g_nades_heal_friend 1 "multiplier of health given to team mates" set g_nades_heal_foe -2 "multiplier of health given to enemies" // Pokenade (7) -set g_nades_pokenade 0 "Pokenade: spawns a monster into the explosion nade location" // script-ignore +set g_nades_pokenade 0 "Pokenade: spawns a monster into the explosion nade location; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_pokenade_monster_lifetime 150 "how long pokenade monster will survive" set g_nades_pokenade_monster_type "zombie" "monster to spawn" // Entrap (8) -set g_nades_entrap 0 "Entrap nade: spawns a orb to slow down movements inside" // script-ignore +set g_nades_entrap 0 "Entrap nade: spawns a orb to slow down movements inside; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_entrap_strength 0.01 "strength of the orb's movement slowing powers" set g_nades_entrap_speed 0.5 "running speed while entrapped" set g_nades_entrap_time 10 "life time of the orb" set g_nades_entrap_radius 500 "distance from the entrap orb" // Veil (9) -set g_nades_veil 0 "Veil nade: spawns a orb to turn invisible inside" // script-ignore +set g_nades_veil 0 "Veil nade: spawns a orb to turn invisible inside; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_veil_time 8 "life time of the orb" set g_nades_veil_radius 200 "distance from the veil orb" // Ammo (10) -set g_nades_ammo 0 "Ammo nade: spawns a orb to recover ammo inside, enemies take the reverse effect when being inside orb" // script-ignore +set g_nades_ammo 0 "Ammo nade: spawns a orb to recover ammo inside, enemies take the reverse effect when being inside orb; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_ammo_time 4 "life time of the orb" set g_nades_ammo_rate 30 "ammo given per second" set g_nades_ammo_friend 1 "multiplier of ammo given to team mates" set g_nades_ammo_foe -2 "multiplier of ammo given to enemies" // Darkness (11) -set g_nades_darkness 0 "Darkness nade: blinds enemies" // script-ignore +set g_nades_darkness 0 "Darkness nade: blinds enemies; \"1\" = allow client selection of this nade type" // script-ignore set g_nades_darkness_time 4 "how long the dark field will last" set g_nades_darkness_explode 0 "whether the darkness nade should explode again once the dark field dissipated" set g_nades_darkness_teamcheck 2 "\"0\" = blinds everyone including the player who threw the nade, \"1\" = blinds enemies and teammates, \"2\" = blinds only enemies" diff --git a/qcsrc/common/mutators/mutator/nades/sv_nades.qc b/qcsrc/common/mutators/mutator/nades/sv_nades.qc index a14787861..e513cf585 100644 --- a/qcsrc/common/mutators/mutator/nades/sv_nades.qc +++ b/qcsrc/common/mutators/mutator/nades/sv_nades.qc @@ -400,6 +400,32 @@ void toss_nade(entity e, bool set_owner, vector _velocity, float _time) STAT(NADE_TIMER, e) = 0; } +Nade nades_CheckTypes(Nade ntype) +{ +#define NADE_TYPE_CHECK(nade_ent, nade_cvar) \ + case nade_ent.m_id: \ + if (nade_cvar) \ + return ntype; \ + break + + switch (ntype.m_id) + { + case 0: return NADE_TYPE_Null; // use NADE_TYPE_Null to signify a random nade + NADE_TYPE_CHECK(NADE_TYPE_NAPALM, autocvar_g_nades_napalm); + NADE_TYPE_CHECK(NADE_TYPE_ICE, autocvar_g_nades_ice); + NADE_TYPE_CHECK(NADE_TYPE_TRANSLOCATE, autocvar_g_nades_translocate); + NADE_TYPE_CHECK(NADE_TYPE_SPAWN, autocvar_g_nades_spawn); + NADE_TYPE_CHECK(NADE_TYPE_HEAL, autocvar_g_nades_heal); + NADE_TYPE_CHECK(NADE_TYPE_MONSTER, autocvar_g_nades_pokenade); + NADE_TYPE_CHECK(NADE_TYPE_ENTRAP, autocvar_g_nades_entrap); + NADE_TYPE_CHECK(NADE_TYPE_VEIL, autocvar_g_nades_veil); + NADE_TYPE_CHECK(NADE_TYPE_AMMO, autocvar_g_nades_ammo); + NADE_TYPE_CHECK(NADE_TYPE_DARKNESS, autocvar_g_nades_darkness); + } + return NADE_TYPE_NORMAL; // default to NADE_TYPE_NORMAL for unknown nade types +#undef NADE_TYPE_CHECK +} + void nades_GiveBonus(entity player, float score) { if (autocvar_g_nades) @@ -460,6 +486,29 @@ bool nade_customize(entity this, entity client) return true; } +int nade_choose_random() +{ + // loops: first determine how many allowed options there are (A), then randomly select one of them (B) + int total_allowed = 0; + FOREACH(Nades, it != NADE_TYPE_Null, { // (A) + if (nades_CheckTypes(it).m_id == it.m_id) // this nade type is allowed + ++total_allowed; + }); + int choose_nth = floor(random() * total_allowed); + int n_i = 0; + int ntype = 0; + FOREACH(Nades, true, { // (B) + if (it != NADE_TYPE_Null && nades_CheckTypes(it).m_id == it.m_id) + { + if (n_i == choose_nth) + break; + ++n_i; + } + ++ntype; + }); + return ntype; +} + Nade Nades_FromString(string ntype) { FOREACH(Nades, it != NADE_TYPE_Null && (it.netname == ntype || ftos(it.impulse) == ntype), @@ -473,10 +522,7 @@ Nade Nades_GetType(string ntype) { Nade def = Nades_FromString(ntype); if(ntype == "random" || ntype == "0") - { - int rnade = floor(random() * (REGISTRY_COUNT(Nades) - 1)) + 1; - def = REGISTRY_GET(Nades, rnade); - } + def = REGISTRY_GET(Nades, nade_choose_random()); return (def == NADE_TYPE_Null) ? NADE_TYPE_NORMAL : def; } @@ -554,8 +600,16 @@ void nade_prime(entity this) } else { - ntype = Nades_FromString(((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_nade_type : autocvar_g_nades_nade_type)); - pntype = ((autocvar_g_nades_client_select) ? CS_CVAR(this).cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type); + if (autocvar_g_nades_client_select) + { + ntype = nades_CheckTypes(Nades_FromString(CS_CVAR(this).cvar_cl_nade_type)); + pntype = CS_CVAR(this).cvar_cl_pokenade_type; + } + else + { + ntype = Nades_FromString(autocvar_g_nades_nade_type); + pntype = autocvar_g_nades_pokenade_monster_type; + } } spawn_held_nade(this, this, autocvar_g_nades_nade_lifetime, ntype.netname, pntype); @@ -609,30 +663,6 @@ void nades_Clear(entity player) STAT(NADE_TIMER, player) = 0; } -int nades_CheckTypes(entity player, Nade cl_ntype) -{ - // TODO check what happens without this patch -#define CL_NADE_TYPE_CHECK(nade_ent, nade_cvar) \ - case nade_ent: if (nade_cvar) return cl_ntype.m_id - - switch (cl_ntype) - { - case NADE_TYPE_Null: return 0; // random nade - CL_NADE_TYPE_CHECK(NADE_TYPE_NAPALM, autocvar_g_nades_napalm); - CL_NADE_TYPE_CHECK(NADE_TYPE_ICE, autocvar_g_nades_ice); - CL_NADE_TYPE_CHECK(NADE_TYPE_TRANSLOCATE, autocvar_g_nades_translocate); - CL_NADE_TYPE_CHECK(NADE_TYPE_SPAWN, autocvar_g_nades_spawn); - CL_NADE_TYPE_CHECK(NADE_TYPE_HEAL, autocvar_g_nades_heal); - CL_NADE_TYPE_CHECK(NADE_TYPE_MONSTER, autocvar_g_nades_pokenade); - CL_NADE_TYPE_CHECK(NADE_TYPE_ENTRAP, autocvar_g_nades_entrap); - CL_NADE_TYPE_CHECK(NADE_TYPE_VEIL, autocvar_g_nades_veil); - CL_NADE_TYPE_CHECK(NADE_TYPE_AMMO, autocvar_g_nades_ammo); - CL_NADE_TYPE_CHECK(NADE_TYPE_DARKNESS, autocvar_g_nades_darkness); - } - return NADE_TYPE_NORMAL.m_id; // default to NADE_TYPE_NORMAL for unknown nade types -#undef CL_NADE_TYPE_CHECK -} - MUTATOR_HOOKFUNCTION(nades, VehicleEnter) { entity player = M_ARGV(0, entity); @@ -740,17 +770,15 @@ MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) if(autocvar_g_nades_bonus_client_select) { - STAT(NADE_BONUS_TYPE, player) = nades_CheckTypes(player, Nades_FromString(CS_CVAR(player).cvar_cl_nade_type)); - player.pokenade_type = CS_CVAR(player).cvar_cl_pokenade_type; + STAT(NADE_BONUS_TYPE, player) = nades_CheckTypes(Nades_FromString(CS_CVAR(player).cvar_cl_nade_type)).m_id; + player.pokenade_type = CS_CVAR(player).cvar_cl_pokenade_type; } else { STAT(NADE_BONUS_TYPE, player) = Nades_FromString(autocvar_g_nades_bonus_type).m_id; - player.pokenade_type = autocvar_g_nades_pokenade_monster_type; + player.pokenade_type = autocvar_g_nades_pokenade_monster_type; } - STAT(NADE_BONUS_TYPE, player) = bound(0, STAT(NADE_BONUS_TYPE, player), REGISTRY_COUNT(Nades)); - if(STAT(NADE_BONUS_SCORE, player) >= 0 && autocvar_g_nades_bonus_score_max) nades_GiveBonus(player, time_score / autocvar_g_nades_bonus_score_max); }