From e327365506557aa196458a44cd9fc44b8f3b88b2 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 01:38:05 -0500 Subject: [PATCH 01/20] Implement core role derandomization logic --- .../gamemode/server/sv_player_ext.lua | 8 +++ .../gamemode/server/sv_roleselection.lua | 70 ++++++++++++++++--- lua/ttt2/extensions/math.lua | 45 ++++++++++++ 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index d2f6b37a20..b2478199ec 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -694,6 +694,9 @@ function plymeta:InitialSpawn() -- We never have weapons here, but this inits our equipment state self:StripAll() + -- Start with an empty role weight table. The table will be updated as needed during role selection. + self:SetRoleWeightTable({}) + -- set spawn position local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) @@ -758,6 +761,11 @@ function plymeta:UnSpectate() self:SetNoTarget(false) end +--- +-- @accessor table A table containing the weights to use when selecting roles, if enabled. +-- @realm server +AccessorFunc(plymeta, "role_weights", "RoleWeightTable"); + --- -- Returns whether a @{Player} is able to select a specific @{ROLE} -- @param ROLE roleData diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 58602fcc63..6ae0b58e40 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -7,6 +7,7 @@ roleselection = {} local math = math local table = table local player = player +local bit = bit local pairs = pairs local IsValid = IsValid local hook = hook @@ -18,6 +19,10 @@ roleselection.selectableRoles = nil roleselection.baseroleLayers = {} roleselection.subroleLayers = {} +local DERAND_NONE = 0 +local DERAND_BASE_FLAG = 1 +local DERAND_SUB_FLAG = 2 + -- Convars roleselection.cv = { --- @@ -38,8 +43,21 @@ roleselection.cv = { --- -- @realm server -- stylua: ignore - 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"), + + --- + -- @realm server + -- stylua: ignore + ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", + DERAND_NONE, DERAND_BASE_FLAG | DERAND_SUB_FLAG), + + --- + -- NOTE: Currently the minimum is 1. In theory, it could be set to 0, which would mean that players cannot get the same role (or subrole, according to the mode) + -- twice in a row. I suspect we'd need some special handling in role distribution to make that not get stuck in an infinite loop or have some other undesirable + -- behavior in certain cases. + -- @realm server + -- stylua: ignore + ttt_role_derandomize_min_weight = CreateConVar("ttt_role_derandomize_min_weight", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The minimum weight a player can have with derandomize on", 1), } -- saving and loading @@ -633,16 +651,29 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced local plysAmount = #plys local availableRolesAmount = #availableRoles local tmpSelectableRoles = table.Copy(selectableRoles) + local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), DERAND_SUB_FLAG) ~= 0; + local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while plysAmount > 0 and availableRolesAmount > 0 do - local pick = math.random(plysAmount) - local ply = plys[pick] - local rolePick = math.random(availableRolesAmount) local subrole = availableRoles[rolePick] local roleData = roles.GetByIndex(subrole) local roleCount = tmpSelectableRoles[subrole] + local pick + if not derand then + -- select random index in plys table + pick = math.random(plysAmount) + else + -- use a weighted sum to select the player + pick = math.random_weighted(plys, function(ply) + local weightTbl = ply:GetRoleWeightTable() + return weightTbl[subrole] or minWeight + end) + end + + local ply = plys[pick] + if selectedForcedRoles[subrole] then roleCount = roleCount - selectedForcedRoles[subrole] end @@ -847,13 +878,24 @@ local function SelectBaseRolePlayers(plys, subrole, roleAmount) local curRoles = 0 local plysList = {} local roleData = roles.GetByIndex(subrole) + local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), DERAND_BASE_FLAG) ~= 0; + local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while curRoles < roleAmount and #plys > 0 do - -- select random index in plys table - local pick = math.random(#plys) + local pick + if not derand then + -- select random index in plys table + pick = math.random(#plys) + else + -- use a weighted sum to select the player + pick = math.random_weighted(plys, function(ply) + local weightTbl = ply:GetRoleWeightTable() + return weightTbl[subrole] or minWeight + end) + end -- the player we consider - local ply = plys[pick] + ply = plys[pick] if subrole == ROLE_INNOCENT or ply:CanSelectRole(roleData, #plys, roleAmount) then table.remove(plys, pick) @@ -981,12 +1023,24 @@ function roleselection.SelectRoles(plys, maxPlys) -- stylua: ignore hook.Run("TTT2ModifyFinalRoles", roleselection.finalRoles) + local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() + for i = 1, #plys do local ply = plys[i] local subrole = roleselection.finalRoles[ply] or ROLE_INNOCENT ply:SetRole(subrole, nil, true) + local baserole = roles.GetByIndex(subrole):GetBaseRole() + local roleWeightTable = ply:GetRoleWeightTable() + -- increment all role weights for the player + for r,w in roleWeightTable do + roleWeightTable[r] = w + 1 + end + -- reset the weights for the final role and its baserole + roleWeightTable[subrole] = minWeight + roleWeightTable[baserole] = minWeight + -- store a steamid -> role map GAMEMODE.LastRole[ply:SteamID64()] = subrole end diff --git a/lua/ttt2/extensions/math.lua b/lua/ttt2/extensions/math.lua index 7abefa16fc..b6033ccdd4 100644 --- a/lua/ttt2/extensions/math.lua +++ b/lua/ttt2/extensions/math.lua @@ -19,3 +19,48 @@ function math.ExponentialDecay(halflife, dt) -- ln(0.5) = -0.69.. return exp((-0.69314718 / halflife) * dt) end + +--- +-- Gets the index of an item in the provided table, weighted according to the weights (derived from get_weight). +-- @param table tbl The array of items to find a weighted item in. +-- @param function get_weight Called as get_weight(item, index). Must return number. +-- @return number +-- @realm shared +function math.random_weighted(tbl, get_weight) + -- There are several possible ways to get a weighted item. The most obvious is to simply include an item in + -- the table N times, where N is an integer proportional to the weight. This, however, requires maintaining + -- that table, which may be undesirable. + -- + -- For a more friendly API, we instead compute the sum, generate a random number from 0 to that sum, then + -- take the first item whose weight prefix-sum is greater than that random number. This requires 2 passes + -- over the table, but enables processing on a normal table, with arbitrary weight storage. + + -- Special case short arrays, because they may otherwise cause problems + if #tbl == 0 then + return nil + end + if #tbl == 1 then + return 1 + end + + -- first, compute the sum weight + local sum = 0 + for k,v in pairs(tbl) do + sum = sum + get_weight(v, k) + end + + -- get the random number + local rand = math.random(0, sum - 1) + + -- now do the prefix-sum for the final value + sum = 0 + for k,v in pairs(tbl) do + sum = sum + get_weight(v, k) + if sum > rand then + return k + end + end + + -- it SHOULD be impossible to reach here, but just in case, we'll do a simple random selection + return math.random(#tbl) +end From 0e3a79da96b0a312ea0d11c198691f43bcb2df29 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 01:47:24 -0500 Subject: [PATCH 02/20] Initialize the role weights to the minimum weight on player initial spawn --- .../gamemode/server/sv_player_ext.lua | 4 ++-- .../gamemode/server/sv_roleselection.lua | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index b2478199ec..975c024559 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -694,8 +694,8 @@ function plymeta:InitialSpawn() -- We never have weapons here, but this inits our equipment state self:StripAll() - -- Start with an empty role weight table. The table will be updated as needed during role selection. - self:SetRoleWeightTable({}) + -- Initialize role weights + roleselection.InitializeRoleWeights(self) -- set spawn position local spawnPoint = plyspawn.GetRandomSafePlayerSpawnPoint(self) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 6ae0b58e40..1a4bf3f597 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -207,6 +207,23 @@ function roleselection.SaveLayers() end end +--- +-- Initializes the player's role weights to be their minimum value. +-- +-- @param Player ply +-- @realm server +function roleselection.InitializeRoleWeights(ply) + -- Initialize the weight table + local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() + local roleWeightTable = {} + for _, v in roles.GetList() do + if not v.isAbstract then + roleWeightTable[v.index] = minWeight + end + end + ply:SetRoleWeightTable(roleWeightTable) +end + --- -- Returns the current amount of selected/already selected @{ROLE}s. -- From d652f0938e79bebf900781618490300389954585 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 02:16:26 -0500 Subject: [PATCH 03/20] Add UI for new convars --- .../gamemode/server/sv_roleselection.lua | 12 +++--- .../menus/gamemode/administration/roles.lua | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 1a4bf3f597..bd93db03ef 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -19,9 +19,9 @@ roleselection.selectableRoles = nil roleselection.baseroleLayers = {} roleselection.subroleLayers = {} -local DERAND_NONE = 0 -local DERAND_BASE_FLAG = 1 -local DERAND_SUB_FLAG = 2 +ROLE_DERAND_NONE = 0 +ROLE_DERAND_BASE_FLAG = 1 +ROLE_DERAND_SUB_FLAG = 2 -- Convars roleselection.cv = { @@ -49,7 +49,7 @@ roleselection.cv = { -- @realm server -- stylua: ignore ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", - DERAND_NONE, DERAND_BASE_FLAG | DERAND_SUB_FLAG), + ROLE_DERAND_NONE, bit.bor(ROLE_DERAND_BASE_FLAG | ROLE_DERAND_SUB_FLAG)), --- -- NOTE: Currently the minimum is 1. In theory, it could be set to 0, which would mean that players cannot get the same role (or subrole, according to the mode) @@ -668,7 +668,7 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced local plysAmount = #plys local availableRolesAmount = #availableRoles local tmpSelectableRoles = table.Copy(selectableRoles) - local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), DERAND_SUB_FLAG) ~= 0; + local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), ROLE_DERAND_SUB_FLAG) ~= 0; local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while plysAmount > 0 and availableRolesAmount > 0 do @@ -895,7 +895,7 @@ local function SelectBaseRolePlayers(plys, subrole, roleAmount) local curRoles = 0 local plysList = {} local roleData = roles.GetByIndex(subrole) - local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), DERAND_BASE_FLAG) ~= 0; + local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), ROLE_DERAND_BASE_FLAG) ~= 0; local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while curRoles < roleAmount and #plys > 0 do diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index 66c48cee3e..fc210a42e6 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -64,6 +64,44 @@ function CLGAMEMODESUBMENU:Populate(parent) master = masterEnb, }) + form:MakeHelp({ + label = "help_roles_derandomize", + }) + + local masterDerand = form:MakeComboBox({ + serverConvar = "ttt_role_derandomize_mode", + label = "label_roles_derandomize_mode", + choices = { + { + title = "label_roles_derandomize_mode_none", + value = ROLE_DERAND_NONE, + }, + { + title = "label_roles_derandomize_mode_base_only", + value = ROLE_DERAND_BASE_FLAG, + }, + { + title = "label_roles_derandomize_mode_sub_only", + value = ROLE_DERAND_SUB_FLAG, + }, + { + title = "label_roles_derandomize_mode_base_and_sub", + value = bit.bor(ROLE_RERAND_BASE_FLAG, ROLE_DERAND_SUB_FLAG), + }, + }, + }) + + form:MakeHelp({ + label = "help_roles_derandmonize_min_weeight", + master = masterDerand, + }) + + form:MakeSlider({ + serverConvar = "ttt_role_derandomize_min_weight", + label = "label_roles_derandomize_min_weight", + master = masterDerand, + }) + local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") form2:MakeHelp({ From 7c44f6b807d8caa0495b01e99da8f84f227a4207 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 02:19:21 -0500 Subject: [PATCH 04/20] Fix syntax --- gamemodes/terrortown/gamemode/server/sv_roleselection.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index bd93db03ef..3b72a7441e 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -48,8 +48,7 @@ roleselection.cv = { --- -- @realm server -- stylua: ignore - ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", - ROLE_DERAND_NONE, bit.bor(ROLE_DERAND_BASE_FLAG | ROLE_DERAND_SUB_FLAG)), + ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", ROLE_DERAND_NONE, bit.bor(ROLE_DERAND_BASE_FLAG, ROLE_DERAND_SUB_FLAG)), --- -- NOTE: Currently the minimum is 1. In theory, it could be set to 0, which would mean that players cannot get the same role (or subrole, according to the mode) From b8fbcb00bc7ffc3202ec4e237fd3038caa55977c Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 02:33:07 -0500 Subject: [PATCH 05/20] Fix references to server-only flags in GUI --- lua/terrortown/menus/gamemode/administration/roles.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index fc210a42e6..430fd7f369 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -74,19 +74,19 @@ function CLGAMEMODESUBMENU:Populate(parent) choices = { { title = "label_roles_derandomize_mode_none", - value = ROLE_DERAND_NONE, + value = 0, -- ROLE_DEMAND_NONE }, { title = "label_roles_derandomize_mode_base_only", - value = ROLE_DERAND_BASE_FLAG, + value = 1, -- ROLE_DEMAND_BASE_FLAG }, { title = "label_roles_derandomize_mode_sub_only", - value = ROLE_DERAND_SUB_FLAG, + value = 2, -- ROLE_DEMAND_SUB_FLAG }, { title = "label_roles_derandomize_mode_base_and_sub", - value = bit.bor(ROLE_RERAND_BASE_FLAG, ROLE_DERAND_SUB_FLAG), + value = bit.bor(1, 2), -- ROLE_DEMAND_BASE_FLAG | ROLE_DEMAND_SUB_FLAG }, }, }) From 0b4b3944273a1ab56fb68b70b946cc2f8f1cb795 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 02:41:00 -0500 Subject: [PATCH 06/20] Change loop in InitializeRoleWeights --- .../terrortown/gamemode/server/sv_roleselection.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 3b72a7441e..d3490c7edc 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -215,11 +215,16 @@ function roleselection.InitializeRoleWeights(ply) -- Initialize the weight table local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() local roleWeightTable = {} - for _, v in roles.GetList() do - if not v.isAbstract then - roleWeightTable[v.index] = minWeight + local roleList = roles.GetList() + + for i = 1, #roleList do + local roleData = roleList[i] + + if not roleData.isAbstract then + roleWeightTable[roleData.index] = minWeight end end + ply:SetRoleWeightTable(roleWeightTable) end From 259eb45aaeb10e583f11d797f92de61882b85bc8 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 02:46:09 -0500 Subject: [PATCH 07/20] pairs() --- gamemodes/terrortown/gamemode/server/sv_roleselection.lua | 2 +- lua/terrortown/menus/gamemode/administration/roles.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index d3490c7edc..9152fe252a 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -1055,7 +1055,7 @@ function roleselection.SelectRoles(plys, maxPlys) local baserole = roles.GetByIndex(subrole):GetBaseRole() local roleWeightTable = ply:GetRoleWeightTable() -- increment all role weights for the player - for r,w in roleWeightTable do + for r,w in pairs(roleWeightTable) do roleWeightTable[r] = w + 1 end -- reset the weights for the final role and its baserole diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index 430fd7f369..230fe80cb2 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -92,7 +92,7 @@ function CLGAMEMODESUBMENU:Populate(parent) }) form:MakeHelp({ - label = "help_roles_derandmonize_min_weeight", + label = "help_roles_derandmonize_min_weight", master = masterDerand, }) From a22a0c617e6304d0f8c104fe24f54acdbd666dfe Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 03:32:05 -0500 Subject: [PATCH 08/20] Add language strings for roles UI --- lua/terrortown/lang/en.lua | 32 +++++++++++++++++++ .../menus/gamemode/administration/roles.lua | 3 ++ 2 files changed, 35 insertions(+) diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index b9c8ff478a..1a9f04d9fa 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -2304,3 +2304,35 @@ L.label_button_level_reset = "reset level" L.loadingscreen_round_restart_title = "Starting new round" L.loadingscreen_round_restart_subtitle = "you're playing on {map}" L.loadingscreen_round_restart_subtitle_limits = "you're playing on {map} for another {rounds} round(s) or {time}" + +-- 2024-06-23 +L.help_roles_derandomize = [[ +Role derandomization can be used to make role distribution feel more fair over the course of a session. + +In essence, when it is enabled, a player's chance of recieving a role increases while they have not been assigned that role. While this can feel more fair, this also enables metagaming, where a player can guess that another will be traitor-aligned based on the fact that they have not been traitor aligned in several rounds. Do not enable this option if this is undesirable. + +There are 4 modes: + +mode 0: Disabled - No derandomization is done. This is the default. + +mode 1: Base roles only - Derandomization is performed for base roles only. Sub-roles will be selected randomly. These are roles like Innocent and Traitor. + +mode 2: Sub-roles only - Derandomization is performed for sub-roles only. Base roles will be selected randomly. Note that sub-roles are only assigned to players which have already been selected for their base role. + +mode 3: Base roles AND sub-roles - Derandomization is performed for both base roles and sub-roles.]] +L.label_roles_derandomize_mode = "Derandomization mode" +L.label_roles_derandomize_mode_none = "mode 0: Disabled" +L.label_roles_derandomize_mode_base_only = "mode 1: Base roles only" +L.label_roles_derandomize_mode_sub_only = "mode 2: Sub-roles only" +L.label_roles_derandomize_mode_base_and_sub = "mode 3: Base roles AND sub-roles" + +L.help_roles_derandomize_min_weight = [[ +Derandomization is performed by making the random player selections during role distribution use a weight associated with each role for each player, and that weight increases by 1 each time the player does not get assigned that role. + +Each time a player is assigned a role, the corresponding weight is reset to this minimum weight. This weight does not have any absolute meaning; it can only be interpreted with respect to other weights. + +For example, given player A with a weight of 1, and player B with a weight of 5, player B is 5 times more likely than player A to be selected. However, if player A had a weight of 4, player B is only 5/4 times more likely to be selected. + +The minimum weight, therefore, effectively controls how much each round affects a player's chance at being selected, with higher values causing it to be affected less. The default value of 1 means that each round causes a fairly significant increase in chance, and conversely, that it is extremely unlikely that a player will get the same role twice in a row. +]] +L.label_roles_derandomize_min_weight = "Derandomization minimum weight" diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index 230fe80cb2..64522f149a 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -100,6 +100,9 @@ function CLGAMEMODESUBMENU:Populate(parent) serverConvar = "ttt_role_derandomize_min_weight", label = "label_roles_derandomize_min_weight", master = masterDerand, + min = 1, + max = 50, + decimal = 0, }) local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") From ea531c78a5f6e4ac39299175673b392fccfbe275 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 03:36:18 -0500 Subject: [PATCH 09/20] Fix localization label name --- lua/terrortown/lang/en.lua | 5 +++-- lua/terrortown/menus/gamemode/administration/roles.lua | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index 1a9f04d9fa..d9b88485ec 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -2327,12 +2327,13 @@ L.label_roles_derandomize_mode_sub_only = "mode 2: Sub-roles only" L.label_roles_derandomize_mode_base_and_sub = "mode 3: Base roles AND sub-roles" L.help_roles_derandomize_min_weight = [[ -Derandomization is performed by making the random player selections during role distribution use a weight associated with each role for each player, and that weight increases by 1 each time the player does not get assigned that role. +Derandomization is performed by making the random player selections during role distribution use a weight associated with each role for each player, and that weight increases by 1 each time the player does not get assigned that role. These weights are not persisted between connections, or across maps. Each time a player is assigned a role, the corresponding weight is reset to this minimum weight. This weight does not have any absolute meaning; it can only be interpreted with respect to other weights. For example, given player A with a weight of 1, and player B with a weight of 5, player B is 5 times more likely than player A to be selected. However, if player A had a weight of 4, player B is only 5/4 times more likely to be selected. The minimum weight, therefore, effectively controls how much each round affects a player's chance at being selected, with higher values causing it to be affected less. The default value of 1 means that each round causes a fairly significant increase in chance, and conversely, that it is extremely unlikely that a player will get the same role twice in a row. -]] + +Changes to this value will not take effect until players reconnect or the map changes.]] L.label_roles_derandomize_min_weight = "Derandomization minimum weight" diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index 64522f149a..164d48a7d8 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -92,7 +92,7 @@ function CLGAMEMODESUBMENU:Populate(parent) }) form:MakeHelp({ - label = "help_roles_derandmonize_min_weight", + label = "help_roles_derandomize_min_weight", master = masterDerand, }) From d580ecd56cbc6b51bf6c9608e55fba88928783f3 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 03:51:26 -0500 Subject: [PATCH 10/20] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff14160164..e68e76e3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel - Made sure this new function is used in our whole codebase for all admin checks - Added `ENTITY:IsPlayerRagdoll` to check if a corpse is a real player ragdoll (by @TimGoll) - Added the `SWEP.DryFireSound` field to the weapon base to allow the dryfire sound to be easily changed (by @TW1STaL1CKY) +- Added role derandomization options for perceptually fairer role distribution ### Changed From 16dad079131d4a5ff034b1819c94d7a892609b83 Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 04:16:24 -0500 Subject: [PATCH 11/20] Run stylua --- .../terrortown/gamemode/server/sv_player_ext.lua | 2 +- .../terrortown/gamemode/server/sv_roleselection.lua | 12 +++++++++--- lua/ttt2/extensions/math.lua | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index 975c024559..443ace6076 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -764,7 +764,7 @@ end --- -- @accessor table A table containing the weights to use when selecting roles, if enabled. -- @realm server -AccessorFunc(plymeta, "role_weights", "RoleWeightTable"); +AccessorFunc(plymeta, "role_weights", "RoleWeightTable") --- -- Returns whether a @{Player} is able to select a specific @{ROLE} diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 9152fe252a..36e703a9ba 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -672,7 +672,10 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced local plysAmount = #plys local availableRolesAmount = #availableRoles local tmpSelectableRoles = table.Copy(selectableRoles) - local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), ROLE_DERAND_SUB_FLAG) ~= 0; + local derand = bit.band( + roleselection.cv.ttt_role_derandomize_mode:GetInt(), + ROLE_DERAND_SUB_FLAG + ) ~= 0 local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while plysAmount > 0 and availableRolesAmount > 0 do @@ -899,7 +902,10 @@ local function SelectBaseRolePlayers(plys, subrole, roleAmount) local curRoles = 0 local plysList = {} local roleData = roles.GetByIndex(subrole) - local derand = bit.band(roleselection.cv.ttt_role_derandomize_mode:GetInt(), ROLE_DERAND_BASE_FLAG) ~= 0; + local derand = bit.band( + roleselection.cv.ttt_role_derandomize_mode:GetInt(), + ROLE_DERAND_BASE_FLAG + ) ~= 0 local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while curRoles < roleAmount and #plys > 0 do @@ -1055,7 +1061,7 @@ function roleselection.SelectRoles(plys, maxPlys) local baserole = roles.GetByIndex(subrole):GetBaseRole() local roleWeightTable = ply:GetRoleWeightTable() -- increment all role weights for the player - for r,w in pairs(roleWeightTable) do + for r, w in pairs(roleWeightTable) do roleWeightTable[r] = w + 1 end -- reset the weights for the final role and its baserole diff --git a/lua/ttt2/extensions/math.lua b/lua/ttt2/extensions/math.lua index b6033ccdd4..ba03e9676c 100644 --- a/lua/ttt2/extensions/math.lua +++ b/lua/ttt2/extensions/math.lua @@ -45,7 +45,7 @@ function math.random_weighted(tbl, get_weight) -- first, compute the sum weight local sum = 0 - for k,v in pairs(tbl) do + for k, v in pairs(tbl) do sum = sum + get_weight(v, k) end @@ -54,7 +54,7 @@ function math.random_weighted(tbl, get_weight) -- now do the prefix-sum for the final value sum = 0 - for k,v in pairs(tbl) do + for k, v in pairs(tbl) do sum = sum + get_weight(v, k) if sum > rand then return k From e2cbb068910be9016455ed740ff3760f35867f1a Mon Sep 17 00:00:00 2001 From: DaNike Date: Sun, 23 Jun 2024 04:18:22 -0500 Subject: [PATCH 12/20] Fix missing local --- gamemodes/terrortown/gamemode/server/sv_roleselection.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 36e703a9ba..a1ded4f8e7 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -922,7 +922,7 @@ local function SelectBaseRolePlayers(plys, subrole, roleAmount) end -- the player we consider - ply = plys[pick] + local ply = plys[pick] if subrole == ROLE_INNOCENT or ply:CanSelectRole(roleData, #plys, roleAmount) then table.remove(plys, pick) From 19cb36859482cc6e040a63fa23b8b20836cfe2e2 Mon Sep 17 00:00:00 2001 From: DaNike Date: Tue, 25 Jun 2024 17:39:17 -0500 Subject: [PATCH 13/20] Spelling --- lua/terrortown/menus/gamemode/administration/roles.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index 164d48a7d8..b0301b6c6a 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -74,19 +74,19 @@ function CLGAMEMODESUBMENU:Populate(parent) choices = { { title = "label_roles_derandomize_mode_none", - value = 0, -- ROLE_DEMAND_NONE + value = 0, -- ROLE_DERAND_NONE }, { title = "label_roles_derandomize_mode_base_only", - value = 1, -- ROLE_DEMAND_BASE_FLAG + value = 1, -- ROLE_DERAND_BASE_FLAG }, { title = "label_roles_derandomize_mode_sub_only", - value = 2, -- ROLE_DEMAND_SUB_FLAG + value = 2, -- ROLE_DERAND_SUB_FLAG }, { title = "label_roles_derandomize_mode_base_and_sub", - value = bit.bor(1, 2), -- ROLE_DEMAND_BASE_FLAG | ROLE_DEMAND_SUB_FLAG + value = bit.bor(1, 2), -- ROLE_DERAND_BASE_FLAG | ROLE_DERAND_SUB_FLAG }, }, }) From 995a3a41517dfc177ff047928cbcf5f8265b1966 Mon Sep 17 00:00:00 2001 From: DaNike Date: Tue, 25 Jun 2024 18:48:59 -0500 Subject: [PATCH 14/20] Make `random_weighted` properly handle floating point weights --- lua/ttt2/extensions/math.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/ttt2/extensions/math.lua b/lua/ttt2/extensions/math.lua index ba03e9676c..61dc0e16af 100644 --- a/lua/ttt2/extensions/math.lua +++ b/lua/ttt2/extensions/math.lua @@ -50,13 +50,13 @@ function math.random_weighted(tbl, get_weight) end -- get the random number - local rand = math.random(0, sum - 1) + local rand = math.Rand(0, sum) -- now do the prefix-sum for the final value sum = 0 for k, v in pairs(tbl) do sum = sum + get_weight(v, k) - if sum > rand then + if sum >= rand then return k end end From 463380324066482000e81c1893ed368ab6119b43 Mon Sep 17 00:00:00 2001 From: DaNike Date: Thu, 11 Jul 2024 22:52:43 -0500 Subject: [PATCH 15/20] Spelling --- lua/terrortown/lang/en.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index d9b88485ec..6a36939003 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -2309,7 +2309,7 @@ L.loadingscreen_round_restart_subtitle_limits = "you're playing on {map} for ano L.help_roles_derandomize = [[ Role derandomization can be used to make role distribution feel more fair over the course of a session. -In essence, when it is enabled, a player's chance of recieving a role increases while they have not been assigned that role. While this can feel more fair, this also enables metagaming, where a player can guess that another will be traitor-aligned based on the fact that they have not been traitor aligned in several rounds. Do not enable this option if this is undesirable. +In essence, when it is enabled, a player's chance of receiving a role increases while they have not been assigned that role. While this can feel more fair, this also enables metagaming, where a player can guess that another will be traitor-aligned based on the fact that they have not been traitor aligned in several rounds. Do not enable this option if this is undesirable. There are 4 modes: From 8328f68399cbdd7123160e872f5cb55e23e364b9 Mon Sep 17 00:00:00 2001 From: DaNike Date: Thu, 15 Aug 2024 04:28:05 -0500 Subject: [PATCH 16/20] Use PascalCase instead of snake_case for math.WeightedRandom --- .../terrortown/gamemode/server/sv_roleselection.lua | 4 ++-- lua/ttt2/extensions/math.lua | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index a1ded4f8e7..1e663e1a01 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -690,7 +690,7 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced pick = math.random(plysAmount) else -- use a weighted sum to select the player - pick = math.random_weighted(plys, function(ply) + pick = math.WeightedRandom(plys, function(ply) local weightTbl = ply:GetRoleWeightTable() return weightTbl[subrole] or minWeight end) @@ -915,7 +915,7 @@ local function SelectBaseRolePlayers(plys, subrole, roleAmount) pick = math.random(#plys) else -- use a weighted sum to select the player - pick = math.random_weighted(plys, function(ply) + pick = math.WeightedRandom(plys, function(ply) local weightTbl = ply:GetRoleWeightTable() return weightTbl[subrole] or minWeight end) diff --git a/lua/ttt2/extensions/math.lua b/lua/ttt2/extensions/math.lua index 61dc0e16af..d7052065d7 100644 --- a/lua/ttt2/extensions/math.lua +++ b/lua/ttt2/extensions/math.lua @@ -21,12 +21,12 @@ function math.ExponentialDecay(halflife, dt) end --- --- Gets the index of an item in the provided table, weighted according to the weights (derived from get_weight). +-- Gets the index of an item in the provided table, weighted according to the weights (derived from getWeight). -- @param table tbl The array of items to find a weighted item in. --- @param function get_weight Called as get_weight(item, index). Must return number. +-- @param function getWeight Called as getWeight(item, index). Must return number. -- @return number -- @realm shared -function math.random_weighted(tbl, get_weight) +function math.WeightedRandom(tbl, getWeight) -- There are several possible ways to get a weighted item. The most obvious is to simply include an item in -- the table N times, where N is an integer proportional to the weight. This, however, requires maintaining -- that table, which may be undesirable. @@ -46,7 +46,7 @@ function math.random_weighted(tbl, get_weight) -- first, compute the sum weight local sum = 0 for k, v in pairs(tbl) do - sum = sum + get_weight(v, k) + sum = sum + getWeight(v, k) end -- get the random number @@ -55,7 +55,7 @@ function math.random_weighted(tbl, get_weight) -- now do the prefix-sum for the final value sum = 0 for k, v in pairs(tbl) do - sum = sum + get_weight(v, k) + sum = sum + getWeight(v, k) if sum >= rand then return k end From 37e6e23c91a851657c77b84b0bc6cde0f718a603 Mon Sep 17 00:00:00 2001 From: Tim Goll Date: Thu, 15 Aug 2024 12:59:57 +0200 Subject: [PATCH 17/20] added new form for derandomization --- lua/terrortown/lang/en.lua | 2 + .../menus/gamemode/administration/roles.lua | 38 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lua/terrortown/lang/en.lua b/lua/terrortown/lang/en.lua index 187a73aa35..ab2f944435 100644 --- a/lua/terrortown/lang/en.lua +++ b/lua/terrortown/lang/en.lua @@ -2307,6 +2307,8 @@ L.loadingscreen_round_restart_subtitle = "you're playing on {map}" L.loadingscreen_round_restart_subtitle_limits = "you're playing on {map} for another {rounds} round(s) or {time}" -- 2024-06-23 +L.header_roles_derandomize = "Role Derandomization" + L.help_roles_derandomize = [[ Role derandomization can be used to make role distribution feel more fair over the course of a session. diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index b0301b6c6a..a779a0db7c 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -64,11 +64,13 @@ function CLGAMEMODESUBMENU:Populate(parent) master = masterEnb, }) - form:MakeHelp({ + local form2 = vgui.CreateTTT2Form(parent, "header_roles_derandomize") + + form2:MakeHelp({ label = "help_roles_derandomize", }) - local masterDerand = form:MakeComboBox({ + local masterDerand = form2:MakeComboBox({ serverConvar = "ttt_role_derandomize_mode", label = "label_roles_derandomize_mode", choices = { @@ -91,12 +93,12 @@ function CLGAMEMODESUBMENU:Populate(parent) }, }) - form:MakeHelp({ + form2:MakeHelp({ label = "help_roles_derandomize_min_weight", master = masterDerand, }) - form:MakeSlider({ + form2:MakeSlider({ serverConvar = "ttt_role_derandomize_min_weight", label = "label_roles_derandomize_min_weight", master = masterDerand, @@ -105,13 +107,13 @@ function CLGAMEMODESUBMENU:Populate(parent) decimal = 0, }) - local form2 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") + local form3 = vgui.CreateTTT2Form(parent, "header_roles_reward_credits") - form2:MakeHelp({ + form3:MakeHelp({ label = "help_roles_award_info", }) - form2:MakeSlider({ + form3:MakeSlider({ serverConvar = "ttt_credits_award_size", label = "label_roles_credits_award_size", min = 0, @@ -119,11 +121,11 @@ function CLGAMEMODESUBMENU:Populate(parent) decimal = 0, }) - form2:MakeHelp({ + form3:MakeHelp({ label = "help_roles_award_pct", }) - form2:MakeSlider({ + form3:MakeSlider({ serverConvar = "ttt_credits_award_pct", label = "label_roles_credits_award_pct", min = 0, @@ -131,20 +133,20 @@ function CLGAMEMODESUBMENU:Populate(parent) decimal = 2, }) - form2:MakeHelp({ + form3:MakeHelp({ label = "help_roles_award_repeat", }) - form2:MakeCheckBox({ + form3:MakeCheckBox({ serverConvar = "ttt_credits_award_repeat", label = "label_roles_credits_award_repeat", }) - form2:MakeHelp({ + form3:MakeHelp({ label = "help_roles_credits_award_kill", }) - form2:MakeSlider({ + form3:MakeSlider({ serverConvar = "ttt_credits_award_kill", label = "label_roles_credits_award_kill", min = 0, @@ -152,22 +154,22 @@ function CLGAMEMODESUBMENU:Populate(parent) decimal = 0, }) - local form3 = vgui.CreateTTT2Form(parent, "header_roles_special_settings") + local form4 = vgui.CreateTTT2Form(parent, "header_roles_special_settings") - form3:MakeHelp({ + form4:MakeHelp({ label = "help_detective_hats", }) - form3:MakeCheckBox({ + form4:MakeCheckBox({ serverConvar = "ttt_detective_hats", label = "label_detective_hats", }) - form3:MakeHelp({ + form4:MakeHelp({ label = "help_inspect_credits_always", }) - form3:MakeCheckBox({ + form4:MakeCheckBox({ serverConvar = "ttt2_inspect_credits_always_visible", label = "label_inspect_credits_always", }) From a4788f10101342f1ed24cf2ea24ed3efd9be2868 Mon Sep 17 00:00:00 2001 From: Tim Goll Date: Thu, 15 Aug 2024 20:53:10 +0200 Subject: [PATCH 18/20] removed bitflags --- .../gamemode/server/sv_roleselection.lua | 21 +++++++++---------- .../menus/gamemode/administration/roles.lua | 8 +++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 1e663e1a01..44be733fca 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -20,8 +20,9 @@ roleselection.baseroleLayers = {} roleselection.subroleLayers = {} ROLE_DERAND_NONE = 0 -ROLE_DERAND_BASE_FLAG = 1 -ROLE_DERAND_SUB_FLAG = 2 +ROLE_DERAND_BASEROLE = 1 +ROLE_DERAND_SUBROLE = 2 +ROLE_DERAND_BOTH = 3 -- Convars roleselection.cv = { @@ -48,7 +49,7 @@ roleselection.cv = { --- -- @realm server -- stylua: ignore - ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", ROLE_DERAND_NONE, bit.bor(ROLE_DERAND_BASE_FLAG, ROLE_DERAND_SUB_FLAG)), + ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", ROLE_DERAND_NONE, 0), --- -- NOTE: Currently the minimum is 1. In theory, it could be set to 0, which would mean that players cannot get the same role (or subrole, according to the mode) @@ -672,10 +673,9 @@ local function SetSubRoles(plys, availableRoles, selectableRoles, selectedForced local plysAmount = #plys local availableRolesAmount = #availableRoles local tmpSelectableRoles = table.Copy(selectableRoles) - local derand = bit.band( - roleselection.cv.ttt_role_derandomize_mode:GetInt(), - ROLE_DERAND_SUB_FLAG - ) ~= 0 + local modeDerandomize = roleselection.cv.ttt_role_derandomize_mode:GetInt() + local derand = modeDerandomize == ROLE_DERAND_SUBROLE or modeDerandomize == ROLE_DERAND_BOTH + local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while plysAmount > 0 and availableRolesAmount > 0 do @@ -902,10 +902,9 @@ local function SelectBaseRolePlayers(plys, subrole, roleAmount) local curRoles = 0 local plysList = {} local roleData = roles.GetByIndex(subrole) - local derand = bit.band( - roleselection.cv.ttt_role_derandomize_mode:GetInt(), - ROLE_DERAND_BASE_FLAG - ) ~= 0 + local modeDerandomize = roleselection.cv.ttt_role_derandomize_mode:GetInt() + local derand = modeDerandomize == ROLE_DERAND_BASEROLE or modeDerandomize == ROLE_DERAND_BOTH + local minWeight = roleselection.cv.ttt_role_derandomize_min_weight:GetInt() while curRoles < roleAmount and #plys > 0 do diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index a779a0db7c..65b07e24e5 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -76,19 +76,19 @@ function CLGAMEMODESUBMENU:Populate(parent) choices = { { title = "label_roles_derandomize_mode_none", - value = 0, -- ROLE_DERAND_NONE + value = ROLE_DERAND_NONE, }, { title = "label_roles_derandomize_mode_base_only", - value = 1, -- ROLE_DERAND_BASE_FLAG + value = ROLE_DERAND_BASEROLE, }, { title = "label_roles_derandomize_mode_sub_only", - value = 2, -- ROLE_DERAND_SUB_FLAG + value = ROLE_DERAND_SUBROLE, }, { title = "label_roles_derandomize_mode_base_and_sub", - value = bit.bor(1, 2), -- ROLE_DERAND_BASE_FLAG | ROLE_DERAND_SUB_FLAG + value = ROLE_DERAND_BOTH, }, }, }) From 59a36fbe2da968807356766511f9fa241ff35ea1 Mon Sep 17 00:00:00 2001 From: Tim Goll Date: Thu, 15 Aug 2024 21:21:10 +0200 Subject: [PATCH 19/20] removed unused variable --- gamemodes/terrortown/gamemode/server/sv_roleselection.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index 44be733fca..be6b1a63f6 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -7,7 +7,6 @@ roleselection = {} local math = math local table = table local player = player -local bit = bit local pairs = pairs local IsValid = IsValid local hook = hook From af141c76ae85981e5dc4c27128475c424dffd36c Mon Sep 17 00:00:00 2001 From: Tim Goll Date: Thu, 15 Aug 2024 21:48:47 +0200 Subject: [PATCH 20/20] fixed/improved UI integration --- .../terrortown/gamemode/server/sv_roleselection.lua | 7 +------ .../terrortown/gamemode/shared/sh_role_module.lua | 5 +++++ lua/terrortown/menus/gamemode/administration/roles.lua | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua index be6b1a63f6..351c1b1053 100644 --- a/gamemodes/terrortown/gamemode/server/sv_roleselection.lua +++ b/gamemodes/terrortown/gamemode/server/sv_roleselection.lua @@ -18,11 +18,6 @@ roleselection.selectableRoles = nil roleselection.baseroleLayers = {} roleselection.subroleLayers = {} -ROLE_DERAND_NONE = 0 -ROLE_DERAND_BASEROLE = 1 -ROLE_DERAND_SUBROLE = 2 -ROLE_DERAND_BOTH = 3 - -- Convars roleselection.cv = { --- @@ -48,7 +43,7 @@ roleselection.cv = { --- -- @realm server -- stylua: ignore - ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", ROLE_DERAND_NONE, 0), + ttt_role_derandomize_mode = CreateConVar("ttt_role_derandomize_mode", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE}, "The mode to use for role selection derandomization", ROLE_DERAND_NONE, ROLE_DERAND_BOTH), --- -- NOTE: Currently the minimum is 1. In theory, it could be set to 0, which would mean that players cannot get the same role (or subrole, according to the mode) diff --git a/gamemodes/terrortown/gamemode/shared/sh_role_module.lua b/gamemodes/terrortown/gamemode/shared/sh_role_module.lua index 3399041d45..8d1f6fb97c 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_role_module.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_role_module.lua @@ -1,3 +1,8 @@ +ROLE_DERAND_NONE = 0 +ROLE_DERAND_BASEROLE = 1 +ROLE_DERAND_SUBROLE = 2 +ROLE_DERAND_BOTH = 3 + -- load roles local rolesPre = "terrortown/entities/roles/" local rolesFiles = file.Find(rolesPre .. "*.lua", "LUA") diff --git a/lua/terrortown/menus/gamemode/administration/roles.lua b/lua/terrortown/menus/gamemode/administration/roles.lua index 65b07e24e5..11a10b6a02 100644 --- a/lua/terrortown/menus/gamemode/administration/roles.lua +++ b/lua/terrortown/menus/gamemode/administration/roles.lua @@ -5,6 +5,8 @@ CLGAMEMODESUBMENU.base = "base_gamemodesubmenu" CLGAMEMODESUBMENU.priority = 97 CLGAMEMODESUBMENU.title = "submenu_administration_roles_general_title" +local TryT = LANG.TryTranslation + function CLGAMEMODESUBMENU:Populate(parent) local form = vgui.CreateTTT2Form(parent, "header_roles_additional") @@ -75,19 +77,19 @@ function CLGAMEMODESUBMENU:Populate(parent) label = "label_roles_derandomize_mode", choices = { { - title = "label_roles_derandomize_mode_none", + title = TryT("label_roles_derandomize_mode_none"), value = ROLE_DERAND_NONE, }, { - title = "label_roles_derandomize_mode_base_only", + title = TryT("label_roles_derandomize_mode_base_only"), value = ROLE_DERAND_BASEROLE, }, { - title = "label_roles_derandomize_mode_sub_only", + title = TryT("label_roles_derandomize_mode_sub_only"), value = ROLE_DERAND_SUBROLE, }, { - title = "label_roles_derandomize_mode_base_and_sub", + title = TryT("label_roles_derandomize_mode_base_and_sub"), value = ROLE_DERAND_BOTH, }, },