From 5e549e390563424021006875c67d8931cdcb225d Mon Sep 17 00:00:00 2001 From: EntranceJew Date: Sun, 10 Dec 2023 01:28:13 -0600 Subject: [PATCH 1/3] allow role avoiding, role RNG optimized --- CHANGELOG.md | 4 ++ .../gamemode/server/sv_player_ext.lua | 22 ++++++-- .../gamemode/server/sv_roleselection.lua | 56 ++++++++----------- .../gamemode/shared/sh_rolelayering.lua | 4 ++ .../entities/roles/traitor/shared.lua | 6 +- lua/terrortown/lang/en.lua | 5 ++ .../menus/gamemode/administration/roles.lua | 9 +++ .../menus/gamemode/gameplay/avoidroles.lua | 7 +++ 8 files changed, 74 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33c3d9c5d1..2ee973ac55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,10 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Moved reset buttons onto the left (by @a7f3) - Added ammo icons to the weapon switch HUD and player status HUD elements (by @EntranceJew) - Changed the disguiser icon to be more fitting (by @TimGoll) +- Role selection RNG improvements (by @EntranceJew): + - Uses `Player:CanSelectRole()` now instead of duplicating logic. + - Option for server to disregard player role selection preferences. + - Players can choose to avoid Traitor, in addition to the other base classes. ### Fixed diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index 8fbcd5f21b..a5be1f4976 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -759,13 +759,23 @@ end -- @return boolean -- @realm server function plymeta:CanSelectRole(roleData, choice_count, role_count) - local min_karmas = ConVarExists("ttt_" .. roleData.name .. "_karma_min") and GetConVar("ttt_" .. roleData.name .. "_karma_min"):GetInt() or 0 + -- if there aren't enough players anymore to have a greater role variety + if choice_count <= role_count then return true end - return ( - choice_count <= role_count - or self:GetBaseKarma() > min_karmas and GAMEMODE.LastRole[self:SteamID64()] == ROLE_INNOCENT - or math.random(3) == 2 - ) and (choice_count <= role_count or not self:GetAvoidRole(roleData.index)) + -- and the player doesn't avoid this role + local allowRoleAvoiding = GetConVar("ttt2_roles_allow_avoiding"):GetBool() + if allowRoleAvoiding and self:GetAvoidRole(roleData.index) then return false end + + -- or the player has enough karma + local minKarmaCVar = GetConVar("ttt_" .. roleData.name .. "_karma_min") + local minKarma = minKarmaCVar and minKarmaCVar:GetInt() or 0 + if KARMA.cv.enabled:GetBool() and self:GetBaseKarma() > minKarma then return true end + + -- or if the randomness decides + local trickleRate = roleselection.trickleDownRate + if trickleRate > 0 and math.random(trickleRate) == 1 then return true end + + return false end --- diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 8eceea8c24..23da8979d6 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -16,6 +16,7 @@ roleselection.finalRoles = {} roleselection.selectableRoles = nil roleselection.baseroleLayers = {} roleselection.subroleLayers = {} +roleselection.trickleDownRate = 0 -- Convars roleselection.cv = { @@ -33,7 +34,7 @@ roleselection.cv = { --- -- @realm server - ttt_max_baseroles_pct = CreateConVar("ttt_max_baseroles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles based on player amount. ttt_max_baseroles needs to be 0") + ttt_max_baseroles_pct = CreateConVar("ttt_max_baseroles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles based on player amount. ttt_max_baseroles needs to be 0"), } -- saving and loading @@ -572,10 +573,14 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced local plysAmount = #plys local availableRolesAmount = #availableRoles local tmpSelectableRoles = table.Copy(selectableRoles) + -- plys are already shuffled, continue with existing priority + local newPlys = table.Copy(plys) - while plysAmount > 0 and availableRolesAmount > 0 do - local pick = math.random(plysAmount) - local ply = plys[pick] + for i = #newPlys, 1, -1 do + if not (plysAmount > 0 and availableRolesAmount > 0) then + break + end + local ply = plys[i] local rolePick = math.random(availableRolesAmount) local subrole = availableRoles[rolePick] @@ -586,16 +591,8 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced roleCount = roleCount - selectedForcedRoles[subrole] end - local minKarmaCVar = GetConVar("ttt_" .. roleData.name .. "_karma_min") - local minKarma = minKarmaCVar and minKarmaCVar:GetInt() or 0 - - -- give this player the role if - if plysAmount <= roleCount -- or there aren't enough players anymore to have a greater role variety - or ply:GetBaseKarma() > minKarma -- or the player has enough karma - and not ply:GetAvoidRole(subrole) -- and the player doesn't avoid this role - or math.random(3) == 2 -- or if the randomness decides - then - table.remove(plys, pick) + if ply:CanSelectRole(roleData, plysAmount, availableRolesAmount) then + table.remove(newPlys, i) roleselection.finalRoles[ply] = subrole @@ -782,30 +779,22 @@ end local function SelectBaseRolePlayers(plys, subrole, roleAmount) local curRoles = 0 local plysList = {} + local roleData = roles.GetByIndex(subrole) - local minKarmaCVar = GetConVar("ttt_" .. roles.GetByIndex(subrole).name .. "_karma_min") - local min_karmas = minKarmaCVar and minKarmaCVar:GetInt() or 0 - - while curRoles < roleAmount and #plys > 0 do - -- select random index in plys table - local pick = math.random(#plys) - + for i = #plys, 1, -1 do -- the player we consider - local ply = plys[pick] - - -- give this player the role if - if subrole == ROLE_INNOCENT -- this role is an innocent role - or #plys <= roleAmount -- or there aren't enough players anymore to have a greater role variety - or ply:GetBaseKarma() > min_karmas -- or the player has enough karma - and not ply:GetAvoidRole(subrole) -- and the player doesn't avoid this role - or math.random(3) == 2 -- or if the randomness decides - then - table.remove(plys, pick) + local ply = plys[i] + -- do not unbrace this + if not (curRoles < roleAmount and #plys > 0) then break end + if subrole == ROLE_INNOCENT or ply:CanSelectRole(roleData, #plys, roleAmount) then curRoles = curRoles + 1 plysList[curRoles] = ply - roleselection.finalRoles[ply] = subrole -- give the player the final baserole (maybe he will receive his subrole later) + -- give the player the final baserole (maybe he will receive his subrole later) + roleselection.finalRoles[ply] = subrole + + table.remove(plys, i) end end @@ -826,6 +815,9 @@ function roleselection.SelectRoles(plys, maxPlys) plys = roleselection.GetSelectablePlayers(plys or player.GetAll()) + -- Randomize role assignment by shuffling the list early. + table.Shuffle(plys) + maxPlys = maxPlys or #plys if maxPlys < 2 then return end -- we don't need to select anything if there is just one player diff --git a/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua b/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua index 4268efd37c..eba3b9e782 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua @@ -6,6 +6,10 @@ rolelayering = {} +--- +-- @realm shared +CreateConVar("ttt2_roles_allow_avoiding", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}, "If enabled, players will be allowed to avoid specific roles.") + local function SendLayerData(layerTable) local layerTableSize = #layerTable diff --git a/lua/terrortown/entities/roles/traitor/shared.lua b/lua/terrortown/entities/roles/traitor/shared.lua index 3586afe24d..4204d50034 100644 --- a/lua/terrortown/entities/roles/traitor/shared.lua +++ b/lua/terrortown/entities/roles/traitor/shared.lua @@ -29,9 +29,13 @@ function ROLE:PreInitialize() pct = 0.4, maximum = 32, minPlayers = 1, + traitorButton = 1, + credits = 2, creditsAwardDeadEnable = 1, - creditsAwardKillEnable = 1 + creditsAwardKillEnable = 1, + + togglable = true } end diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index 632afec5b7..de5d0e9607 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -2074,6 +2074,11 @@ L.label_keyhelper_ammo_drop = "drop ammo from selected weapon out of clip" -- 2023-11-18 L.entity_pickup_owner_only = "Only the owner can pick this up" +-- 2023-11-20 +L.help_ttt_role_avoid_disabled = "WARNING! Role avoidance is disabled on this server, but you can still change your settings here." +L.label_roles_allow_avoiding = "Allow Avoiding Roles" +L.help_roles_allow_avoiding = "If enabled, allows players to opt out of specific roles from within the Avoid Role Selection menu." + -- 2023-12-18 L.body_confirm_one = "{finder} confirmed the death of {victim}." L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index e8ebc1fdde..0ab96d4897 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -61,6 +61,15 @@ function CLGAMEMODESUBMENU:Populate(parent) master = masterEnb }) + form:MakeHelp({ + label = "help_roles_allow_avoiding" + }) + + form:MakeCheckBox({ + serverConvar = "ttt2_roles_allow_avoiding", + label = "label_roles_allow_avoiding" + }) + local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") form2:MakeHelp({ diff --git a/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua b/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua index 6ae5bbf7af..e4fbd9276a 100644 --- a/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua +++ b/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua @@ -8,6 +8,13 @@ CLGAMEMODESUBMENU.title = "submenu_gameplay_avoidroles_title" function CLGAMEMODESUBMENU:Populate(parent) local form = vgui.CreateTTT2Form(parent, "header_roleselection") + if GetConVar("ttt2_roles_allow_avoiding"):GetBool() then + form:MakeHelp({ + label = "help_ttt_role_avoid_disabled" + }) + end + + local roles = roles.GetList() for i = 1, #roles do From af3e270221b8985bc4080b9d70c5b4c3edca13ff Mon Sep 17 00:00:00 2001 From: EntranceJew Date: Sun, 10 Dec 2023 01:28:13 -0600 Subject: [PATCH 2/3] allow role avoiding, role RNG optimized --- CHANGELOG.md | 4 ++ .../gamemode/server/sv_player_ext.lua | 22 ++++++-- .../gamemode/server/sv_roleselection.lua | 56 ++++++++----------- .../gamemode/shared/sh_rolelayering.lua | 4 ++ .../entities/roles/traitor/shared.lua | 6 +- lua/terrortown/lang/en.lua | 5 ++ .../menus/gamemode/administration/roles.lua | 9 +++ .../menus/gamemode/gameplay/avoidroles.lua | 7 +++ 8 files changed, 74 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4929acd68e..c3f7a0fb7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,6 +153,10 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Moved reset buttons onto the left (by @a7f3) - Added ammo icons to the weapon switch HUD and player status HUD elements (by @EntranceJew) - Changed the disguiser icon to be more fitting (by @TimGoll) +- Role selection RNG improvements (by @EntranceJew): + - Uses `Player:CanSelectRole()` now instead of duplicating logic. + - Option for server to disregard player role selection preferences. + - Players can choose to avoid Traitor, in addition to the other base classes. ### Fixed diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index 8fbcd5f21b..a5be1f4976 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -759,13 +759,23 @@ end -- @return boolean -- @realm server function plymeta:CanSelectRole(roleData, choice_count, role_count) - local min_karmas = ConVarExists("ttt_" .. roleData.name .. "_karma_min") and GetConVar("ttt_" .. roleData.name .. "_karma_min"):GetInt() or 0 + -- if there aren't enough players anymore to have a greater role variety + if choice_count <= role_count then return true end - return ( - choice_count <= role_count - or self:GetBaseKarma() > min_karmas and GAMEMODE.LastRole[self:SteamID64()] == ROLE_INNOCENT - or math.random(3) == 2 - ) and (choice_count <= role_count or not self:GetAvoidRole(roleData.index)) + -- and the player doesn't avoid this role + local allowRoleAvoiding = GetConVar("ttt2_roles_allow_avoiding"):GetBool() + if allowRoleAvoiding and self:GetAvoidRole(roleData.index) then return false end + + -- or the player has enough karma + local minKarmaCVar = GetConVar("ttt_" .. roleData.name .. "_karma_min") + local minKarma = minKarmaCVar and minKarmaCVar:GetInt() or 0 + if KARMA.cv.enabled:GetBool() and self:GetBaseKarma() > minKarma then return true end + + -- or if the randomness decides + local trickleRate = roleselection.trickleDownRate + if trickleRate > 0 and math.random(trickleRate) == 1 then return true end + + return false end --- diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 8eceea8c24..23da8979d6 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -16,6 +16,7 @@ roleselection.finalRoles = {} roleselection.selectableRoles = nil roleselection.baseroleLayers = {} roleselection.subroleLayers = {} +roleselection.trickleDownRate = 0 -- Convars roleselection.cv = { @@ -33,7 +34,7 @@ roleselection.cv = { --- -- @realm server - ttt_max_baseroles_pct = CreateConVar("ttt_max_baseroles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles based on player amount. ttt_max_baseroles needs to be 0") + ttt_max_baseroles_pct = CreateConVar("ttt_max_baseroles_pct", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "Maximum amount of different baseroles based on player amount. ttt_max_baseroles needs to be 0"), } -- saving and loading @@ -572,10 +573,14 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced local plysAmount = #plys local availableRolesAmount = #availableRoles local tmpSelectableRoles = table.Copy(selectableRoles) + -- plys are already shuffled, continue with existing priority + local newPlys = table.Copy(plys) - while plysAmount > 0 and availableRolesAmount > 0 do - local pick = math.random(plysAmount) - local ply = plys[pick] + for i = #newPlys, 1, -1 do + if not (plysAmount > 0 and availableRolesAmount > 0) then + break + end + local ply = plys[i] local rolePick = math.random(availableRolesAmount) local subrole = availableRoles[rolePick] @@ -586,16 +591,8 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced roleCount = roleCount - selectedForcedRoles[subrole] end - local minKarmaCVar = GetConVar("ttt_" .. roleData.name .. "_karma_min") - local minKarma = minKarmaCVar and minKarmaCVar:GetInt() or 0 - - -- give this player the role if - if plysAmount <= roleCount -- or there aren't enough players anymore to have a greater role variety - or ply:GetBaseKarma() > minKarma -- or the player has enough karma - and not ply:GetAvoidRole(subrole) -- and the player doesn't avoid this role - or math.random(3) == 2 -- or if the randomness decides - then - table.remove(plys, pick) + if ply:CanSelectRole(roleData, plysAmount, availableRolesAmount) then + table.remove(newPlys, i) roleselection.finalRoles[ply] = subrole @@ -782,30 +779,22 @@ end local function SelectBaseRolePlayers(plys, subrole, roleAmount) local curRoles = 0 local plysList = {} + local roleData = roles.GetByIndex(subrole) - local minKarmaCVar = GetConVar("ttt_" .. roles.GetByIndex(subrole).name .. "_karma_min") - local min_karmas = minKarmaCVar and minKarmaCVar:GetInt() or 0 - - while curRoles < roleAmount and #plys > 0 do - -- select random index in plys table - local pick = math.random(#plys) - + for i = #plys, 1, -1 do -- the player we consider - local ply = plys[pick] - - -- give this player the role if - if subrole == ROLE_INNOCENT -- this role is an innocent role - or #plys <= roleAmount -- or there aren't enough players anymore to have a greater role variety - or ply:GetBaseKarma() > min_karmas -- or the player has enough karma - and not ply:GetAvoidRole(subrole) -- and the player doesn't avoid this role - or math.random(3) == 2 -- or if the randomness decides - then - table.remove(plys, pick) + local ply = plys[i] + -- do not unbrace this + if not (curRoles < roleAmount and #plys > 0) then break end + if subrole == ROLE_INNOCENT or ply:CanSelectRole(roleData, #plys, roleAmount) then curRoles = curRoles + 1 plysList[curRoles] = ply - roleselection.finalRoles[ply] = subrole -- give the player the final baserole (maybe he will receive his subrole later) + -- give the player the final baserole (maybe he will receive his subrole later) + roleselection.finalRoles[ply] = subrole + + table.remove(plys, i) end end @@ -826,6 +815,9 @@ function roleselection.SelectRoles(plys, maxPlys) plys = roleselection.GetSelectablePlayers(plys or player.GetAll()) + -- Randomize role assignment by shuffling the list early. + table.Shuffle(plys) + maxPlys = maxPlys or #plys if maxPlys < 2 then return end -- we don't need to select anything if there is just one player diff --git a/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua b/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua index 4268efd37c..eba3b9e782 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_rolelayering.lua @@ -6,6 +6,10 @@ rolelayering = {} +--- +-- @realm shared +CreateConVar("ttt2_roles_allow_avoiding", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED}, "If enabled, players will be allowed to avoid specific roles.") + local function SendLayerData(layerTable) local layerTableSize = #layerTable diff --git a/lua/terrortown/entities/roles/traitor/shared.lua b/lua/terrortown/entities/roles/traitor/shared.lua index 3586afe24d..4204d50034 100644 --- a/lua/terrortown/entities/roles/traitor/shared.lua +++ b/lua/terrortown/entities/roles/traitor/shared.lua @@ -29,9 +29,13 @@ function ROLE:PreInitialize() pct = 0.4, maximum = 32, minPlayers = 1, + traitorButton = 1, + credits = 2, creditsAwardDeadEnable = 1, - creditsAwardKillEnable = 1 + creditsAwardKillEnable = 1, + + togglable = true } end diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index 43b4ed7662..e36711e847 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -2089,6 +2089,11 @@ L.beacon_short_desc = "Beacons are used by policing roles to add local wallhacks -- 2023-11-18 L.entity_pickup_owner_only = "Only the owner can pick this up" +-- 2023-11-20 +L.help_ttt_role_avoid_disabled = "WARNING! Role avoidance is disabled on this server, but you can still change your settings here." +L.label_roles_allow_avoiding = "Allow Avoiding Roles" +L.help_roles_allow_avoiding = "If enabled, allows players to opt out of specific roles from within the Avoid Role Selection menu." + -- 2023-12-18 L.body_confirm_one = "{finder} confirmed the death of {victim}." L.body_confirm_more = "{finder} confirmed the {count} deaths of: {victims}." diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index e8ebc1fdde..0ab96d4897 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -61,6 +61,15 @@ function CLGAMEMODESUBMENU:Populate(parent) master = masterEnb }) + form:MakeHelp({ + label = "help_roles_allow_avoiding" + }) + + form:MakeCheckBox({ + serverConvar = "ttt2_roles_allow_avoiding", + label = "label_roles_allow_avoiding" + }) + local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") form2:MakeHelp({ diff --git a/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua b/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua index 6ae5bbf7af..e4fbd9276a 100644 --- a/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua +++ b/lua/terrortown/menus/gamemode/gameplay/avoidroles.lua @@ -8,6 +8,13 @@ CLGAMEMODESUBMENU.title = "submenu_gameplay_avoidroles_title" function CLGAMEMODESUBMENU:Populate(parent) local form = vgui.CreateTTT2Form(parent, "header_roleselection") + if GetConVar("ttt2_roles_allow_avoiding"):GetBool() then + form:MakeHelp({ + label = "help_ttt_role_avoid_disabled" + }) + end + + local roles = roles.GetList() for i = 1, #roles do From a98d02d7d4268eab2f128639d22c66702009eb83 Mon Sep 17 00:00:00 2001 From: EntranceJew Date: Mon, 8 Jan 2024 11:12:56 -0600 Subject: [PATCH 3/3] changelog migrate --- CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b5c632b40..0d36d0deee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel ### Fixed +- Role selection RNG improvements (by @EntranceJew): + - Uses `Player:CanSelectRole()` now instead of duplicating logic + - Option for server to disregard player role selection preferences + - Players can choose to avoid Traitor, in addition to the other base classes + ## [v0.12.3b](https://github.com/TTT-2/TTT2/tree/v0.12.3b) (2024-01-07) ### Added @@ -175,10 +180,6 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Moved reset buttons onto the left (by @a7f3) - Added ammo icons to the weapon switch HUD and player status HUD elements (by @EntranceJew) - Changed the disguiser icon to be more fitting (by @TimGoll) -- Role selection RNG improvements (by @EntranceJew): - - Uses `Player:CanSelectRole()` now instead of duplicating logic. - - Option for server to disregard player role selection preferences. - - Players can choose to avoid Traitor, in addition to the other base classes. ### Fixed