diff --git a/gamemodes/terrortown/gamemode/server/sv_main.lua b/gamemodes/terrortown/gamemode/server/sv_main.lua index f7ebc86c7..60f31d39d 100644 --- a/gamemodes/terrortown/gamemode/server/sv_main.lua +++ b/gamemodes/terrortown/gamemode/server/sv_main.lua @@ -45,6 +45,7 @@ ttt_include("sh_armor") ttt_include("sh_player_ext") ttt_include("sv_player_ext") +ttt_include("sv_player_custom") ttt_include("sv_player") ttt_include("sv_addonchecker") @@ -66,21 +67,6 @@ local util = util local hook = hook local playerGetAll = player.GetAll ---- --- @realm server -local cvPreferMapModels = - CreateConVar("ttt2_prefer_map_models", "1", { FCVAR_NOTIFY, FCVAR_ARCHIVE }) - ---- --- @realm server -local cvSelectModelPerRound = - CreateConVar("ttt2_select_model_per_round", "1", { FCVAR_NOTIFY, FCVAR_ARCHIVE }) - ---- --- @realm server -local cvSelectUniqueModelPerPlayer = - CreateConVar("ttt2_select_unique_model_per_player", "0", { FCVAR_NOTIFY, FCVAR_ARCHIVE }) - --- -- @realm server CreateConVar("ttt_haste_minutes_per_death", "0.5", { FCVAR_NOTIFY, FCVAR_ARCHIVE }) @@ -130,15 +116,6 @@ local map_switch_delay = CreateConVar( 0 ) ---- --- @realm server -CreateConVar( - "ttt_enforce_playermodel", - "1", - { FCVAR_NOTIFY, FCVAR_ARCHIVE }, - "Whether or not to enforce terrorist playermodels. Set to 0 for compatibility with Enhanced Playermodel Selector" -) - --- -- @realm server CreateConVar("ttt_newroles_enabled", "1", { FCVAR_NOTIFY, FCVAR_ARCHIVE }) @@ -427,9 +404,7 @@ function GM:InitPostEntity() -- initialize playermodel database playermodels.Initialize() - -- set the default random playermodel - self.playermodel = playermodels.GetRandomPlayerModel() - self.playercolor = COLOR_WHITE + self.playermodel, self.playercolor = hook.Run("TTT2GetDefaultPlayerDisplay") timer.Simple(0, function() addonChecker.Check() @@ -700,9 +675,7 @@ function GM:OnReloaded() button.SetUp() - -- set the default random playermodel - self.playermodel = playermodels.GetRandomPlayerModel() - self.playercolor = COLOR_WHITE + self.playermodel, self.playercolor = hook.Run("TTT2GetDefaultPlayerDisplay") -- register synced player variables player.RegisterSettingOnServer("enable_dynamic_fov", "bool") @@ -922,29 +895,8 @@ function GM:TTT2PrePrepareRound(duration) MuteForRestart(false) end) - -- sets the player model - -- supports map models or random player models - if cvPreferMapModels:GetBool() and self.force_plymodel and self.force_plymodel ~= "" then - self.playermodel = self.force_plymodel - elseif cvSelectModelPerRound:GetBool() then - if cvSelectUniqueModelPerPlayer:GetBool() then - local plys = player.GetAll() - for i = 1, #plys do - plys[i].defaultModel = playermodels.GetRandomPlayerModel() - end - else - local plys = player.GetAll() - for i = 1, #plys do - plys[i].defaultModel = nil - end - - self.playermodel = playermodels.GetRandomPlayerModel() - end - end - - --- - -- @realm server - self.playercolor = hook.Run("TTTPlayerColor", self.playermodel) + -- select new default playermodels for the round + self.playermodel, self.playercolor = hook.Run("TTT2GetDefaultPlayerDisplayForRound") end --- diff --git a/gamemodes/terrortown/gamemode/server/sv_player.lua b/gamemodes/terrortown/gamemode/server/sv_player.lua index 36103f119..be8d439d8 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player.lua @@ -176,27 +176,6 @@ function GM:PlayerSpawn(ply) end end ---- --- Called whenever view model hands needs setting a model. --- By default this calls @{Player:GetHandsModel} and if that fails, --- sets the hands model according to his @{Player} model. --- @param Player ply The @{Player} whose hands needs a model set --- @param Entity ent The hands to set model of --- @hook --- @realm server --- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetHandsModel --- @local -function GM:PlayerSetHandsModel(ply, ent) - local simplemodel = player_manager.TranslateToPlayerModelName(ply:GetModel()) - local info = player_manager.TranslatePlayerHands(simplemodel) - - if info then - ent:SetModel(info.model) - ent:SetSkin(info.skin) - ent:SetBodyGroups(info.body) - end -end - --- -- Check if a @{Player} can spawn at a certain spawnpoint. -- @param Player ply The @{Player} who is spawned @@ -249,49 +228,6 @@ function GM:PlayerSelectSpawn(ply, transition) -- "[PlayerSelectSpawn] Error! No spawn points!" end ---- --- Called whenever a @{Player} spawns and must choose a model. --- A good place to assign a model to a @{Player}. --- @note This function may not work in your custom gamemode if you have overridden --- your @{GM:PlayerSpawn} and you do not use self.BaseClass.PlayerSpawn or @{hook.Run}. --- @param Player ply The @{Player} being chosen --- @hook --- @realm server --- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetModel --- @local -function GM:PlayerSetModel(ply) - -- The player modes has to be applied here since some player model selectors overwrite - -- this hook to suppress the TTT2 player models. If the model is assigned elsewhere, it - -- breaks with external model selectors. - if not IsValid(ply) then - return - end - - -- this will call the overwritten internal function to modify the model - ply:SetModel(ply.defaultModel or GAMEMODE.playermodel) - - -- Always clear color state, may later be changed in TTTPlayerSetColor - ply:SetColor(COLOR_WHITE) -end - ---- --- Called when a @{Player} spawns and updates the @{Color} --- @param Player ply --- @hook --- @realm server -function GM:TTTPlayerSetColor(ply) - local c = COLOR_WHITE - - if GAMEMODE.playercolor then - -- If this player has a colorable model, always use the same color as all - -- other colorable players, so color will never be the factor that lets - -- you tell players apart. - c = GAMEMODE.playercolor - end - - ply:SetPlayerColor(Vector(c.r / 255.0, c.g / 255.0, c.b / 255.0)) -end - --- -- Determines if the @{Player} can kill themselves using the concommands "kill" or "explode". -- Only active players can use kill cmd diff --git a/gamemodes/terrortown/gamemode/server/sv_player_custom.lua b/gamemodes/terrortown/gamemode/server/sv_player_custom.lua new file mode 100644 index 000000000..3ea1acc0e --- /dev/null +++ b/gamemodes/terrortown/gamemode/server/sv_player_custom.lua @@ -0,0 +1,290 @@ +--- +-- logic to control player customization on the server side +-- @realm server + +customization = customization or {} + +customization.cv = { + + playermodels = { + --- + -- + -- @realm server + enforce = CreateConVar( + "ttt_enforce_playermodel", + "1", + { FCVAR_NOTIFY, FCVAR_ARCHIVE }, + "Whether or not to enforce terrorist playermodels. Set to 0 for compatibility with Enhanced Playermodel Selector" + ), + + --- + -- + -- @realm server + useMap = CreateConVar( + "ttt2_prefer_map_models", + "1", + { FCVAR_NOTIFY, FCVAR_ARCHIVE }, + "Whether to use a map's preferred model when available" + ), + + --- + -- + -- @realm server + customModels = CreateConVar( + "ttt2_use_custom_models", + "0", + { FCVAR_NOTIFY, FCVAR_ARCHIVE }, + "Whether to use custom playermodels instead of just the default" + ), + + --- + -- + -- @realm server + perRound = CreateConVar( + "ttt2_select_model_per_round", + "1", + { FCVAR_NOTIFY, FCVAR_ARCHIVE }, + "Whether to select a player's model each round or once per map" + ), + + --- + -- + -- @realm server + uniquePerPlayer = CreateConVar( + "ttt2_select_unique_model_per_player", + "1", + { FCVAR_NOTIFY, FCVAR_ARCHIVE }, + "Whether playermodels should be unique among players" + ), + }, + + colors = { + --- + -- + -- @realm server + mode = CreateConVar( + "ttt_playercolor_mode", + "1", + { FCVAR_NOTIFY, FCVAR_ARCHIVE }, + "Controls the player color selection mode." + ), + }, +} + +local ttt_playercolors = { + all = { + COLOR_WHITE, + COLOR_BLACK, + COLOR_GREEN, + COLOR_DGREEN, + COLOR_RED, + COLOR_YELLOW, + COLOR_LGRAY, + COLOR_BLUE, + COLOR_NAVY, + COLOR_PINK, + COLOR_OLIVE, + COLOR_ORANGE, + }, + serious = { + COLOR_WHITE, + COLOR_BLACK, + COLOR_NAVY, + COLOR_LGRAY, + COLOR_DGREEN, + COLOR_OLIVE, + }, +} + +--- +-- @note In TTT (and before the playermodel rework) this is marked "shared" realm, and the +-- associated playercolor_mode cvar is likewise. The rework moved both to be server-only, since all +-- meaningful interaction with it occurs on the server exclusively. +-- @param string model The selected (default) playermodel +-- @hook +-- @realm server +function GM:TTTPlayerColor(model) + local mode = customization.cv.colors.mode:GetInt() + + if mode == 1 then + return ttt_playercolors.serious[math.random(#ttt_playercolors.serious)] + elseif mode == 2 then + return ttt_playercolors.all[math.random(#ttt_playercolors.all)] + elseif mode == 3 then + return Color(math.random(0, 255), math.random(0, 255), math.random(0, 255)) + end + + -- No coloring + return COLOR_WHITE +end + +local plySelectedModels +local plyUsedModels + +local function RandomUniqueModel() + plyUsedModels = plyUsedModels or {} + local pm = nil + + -- select a playermodel which hasn't been used before + local i = #playermodels.GetSelectedModels() -- i means we only attempt at #playermodels times, before figuring there are no remaining options + while i > 0 and (not pm or plyUsedModels[pm]) do + pm = playermodels.GetRandomPlayerModel() + i = i - 1 + end + + if not pm or i == 0 then + -- we reached the iteration limit; likely all models are used, so start reusing + plyUsedModels = {} + return RandomUniqueModel() + end + + return pm +end + +--- +-- Called to get the default player display information (model/color) for this map. +-- The returned settings will be overridden when the round starts by +-- @{TTT2GetDefaultPlayerDisplayForRound}. +-- @return string,Color the model,color pair for the default display +-- @hook +-- @realm server +function GM:TTT2GetDefaultPlayerDisplay() + local pm = playermodels.GetRandomPlayerModel() + return pm, hook.Run("TTTPlayerColor", pm) +end + +function GM:TTT2GetDefaultPlayerDisplayForRound() + local pm = self.playermodel + + -- clear the per-player selected models table + plySelectedModels = nil + + if + customization.cv.playermodels.useMap:GetBool() + and self.force_plymodel + and self.force_plymodel ~= "" + then + -- server wants map-configured playermodel and the map specified a playermodel, + -- respect that by default + pm = self.force_plymodel + elseif customization.cv.playermodels.perRound:GetBool() then + -- server wants new playermodels each round, choose a new default + pm = playermodels.GetRandomPlayerModel() + + if customization.cv.playermodels.uniquePerPlayer:GetBool() then + -- we want to set a unique playermodel for each player as well + plySelectedModels = {} + local plys = player.GetAll() + for i = 1, #plys do + if not plys[i]:IsTerror() then + continue + end + + plySelectedModels[plys[i]] = RandomUniqueModel() + end + end + end + + local color = self.playercolor + + if pm ~= self.playermodel then + -- the playermodel got changed, so we also want to recompute color + color = hook.Run("TTTPlayerColor", pm) + end + + return pm, color +end + +--- +-- Called whenever a @{Player} spawns and must choose a model. +-- A good place to assign a model to a @{Player}. +-- @note This function may not work in your custom gamemode if you have overridden +-- your @{GM:PlayerSpawn} and you do not use self.BaseClass.PlayerSpawn or @{hook.Run}. +-- @param Player ply The @{Player} being chosen +-- @hook +-- @realm server +-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetModel +-- @local +function GM:PlayerSetModel(ply) + -- The player model has to be applied here since some player model selectors overwrite + -- this hook to suppress the TTT2 player models. If the model is assigned elsewhere, it + -- breaks with external model selectors. + if not IsValid(ply) then + return + end + + -- We need to clear subrole models at some point; we'll do it here + ply:SetSubRoleModel(nil) + + local pm = plySelectedModels and plySelectedModels[ply] + + if + not pm + and customization.cv.playermodels.perRound:GetBool() + and customization.cv.playermodels.uniquePerPlayer:GetBool() + then + -- plySelectedModels doesn't have this player's model, but a unique one was requested for + -- each round. Compute it now. + pm = RandomUniqueModel() + plySelectedModels = plySelectedModels or {} + plySelectedModels[ply] = pm + end + + ply:SetModel(pm) + -- Always reset the color, which may (will) be later changed by TTTPlayerSetColor + ply:SetColor(COLOR_WHITE) +end + +--- +-- Called whenever view model hands needs setting a model. +-- By default this calls @{Player:GetHandsModel} and if that fails, +-- sets the hands model according to his @{Player} model. +-- @param Player ply The @{Player} whose hands needs a model set +-- @param Entity ent The hands to set model of +-- @hook +-- @realm server +-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetHandsModel +-- @local +function GM:PlayerSetHandsModel(ply, ent) + local simplemodel = player_manager.TranslateToPlayerModelName(ply:GetModel()) + local info = player_manager.TranslatePlayerHands(simplemodel) + + if info then + ent:SetModel(info.model) + ent:SetSkin(info.skin) + ent:SetBodyGroups(info.body) + end +end + +--- +-- Called when a @{Player} spawns and updates the @{Color} +-- @param Player ply +-- @hook +-- @realm server +function GM:TTTPlayerSetColor(ply) + local c = COLOR_WHITE + + if GAMEMODE.playercolor then + -- If we have a playercolor, by default we should set it for ALL players. + c = GAMEMODE.playercolor + end + + ply:SetPlayerColor(Vector(c.r / 255.0, c.g / 255.0, c.b / 255.0)) +end + +--- +-- @param ply Player the player to update +-- @param doSetModel boolean true if the player's model should be explicitly set +function GM:TTT2UpdateSubrolePlayermodel(ply, doSetModel) + -- doSetModel is false when the 'oldSubrole' local of the caller is unset. The original coments + -- there indicated that this meant that the player isn't already spawned, which causes problems. + -- I am unsure what problems, exactly, though it likely has to do with some part of the work + -- done in plymeta:SetModel. + if doSetModel and customization.cv.playermodels.enforce:GetBool() then + ply:SetModel(ply:GetSubRoleModel()) + end + + -- Always clear color state before invoking TTTPlayerSetColor + ply:SetColor(COLOR_WHITE) + hook.Run("TTTPlayerSetColor", ply) +end diff --git a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua index dfe409f16..70cc976fb 100644 --- a/gamemodes/terrortown/gamemode/server/sv_player_ext.lua +++ b/gamemodes/terrortown/gamemode/server/sv_player_ext.lua @@ -621,14 +621,6 @@ end -- @return boolean Returns true if player is spawned -- @realm server function plymeta:SpawnForRound(deadOnly) - --- - -- @realm server - hook.Run("PlayerSetModel", self) - - --- - -- @realm server - hook.Run("TTTPlayerSetColor", self) - -- wrong alive status and not a willing spec who unforced after prep started -- (and will therefore be "alive") if deadOnly and self:Alive() and not self:IsSpec() then @@ -1703,12 +1695,6 @@ local function SetPlayerReady(_, ply) gameloop.PlayerReady(ply) - -- if random models for all players are enabled, they should be set as soon - -- as the player connects - if GetConVar("ttt2_select_unique_model_per_player"):GetBool() then - ply.defaultModel = playermodels.GetRandomPlayerModel() - end - --- -- @realm server hook.Run("TTT2PlayerReady", ply) diff --git a/gamemodes/terrortown/gamemode/shared/sh_include.lua b/gamemodes/terrortown/gamemode/shared/sh_include.lua index 7725bc8d4..26a7c27f3 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_include.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_include.lua @@ -169,6 +169,7 @@ if SERVER then sv_networking = { file = "sv_networking.lua", on = "server" }, sv_network_sync = { file = "sv_network_sync.lua", on = "server" }, sv_player_ext = { file = "sv_player_ext.lua", on = "server" }, + sv_player_custom = { file = "sv_player_custom.lua", on = "server" }, sv_player = { file = "sv_player.lua", on = "server" }, sv_propspec = { file = "sv_propspec.lua", on = "server" }, sv_roleselection = { file = "sv_roleselection.lua", on = "server" }, diff --git a/gamemodes/terrortown/gamemode/shared/sh_main.lua b/gamemodes/terrortown/gamemode/shared/sh_main.lua index 7d8378ddf..8e9f05a79 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_main.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_main.lua @@ -328,58 +328,6 @@ function GM:ClientSignOnStateChanged(userID, oldState, newState) GAMEMODE.PlayerSignOnStates[userID] = newState end -local ttt_playercolors = { - all = { - COLOR_WHITE, - COLOR_BLACK, - COLOR_GREEN, - COLOR_DGREEN, - COLOR_RED, - COLOR_YELLOW, - COLOR_LGRAY, - COLOR_BLUE, - COLOR_NAVY, - COLOR_PINK, - COLOR_OLIVE, - COLOR_ORANGE, - }, - serious = { - COLOR_WHITE, - COLOR_BLACK, - COLOR_NAVY, - COLOR_LGRAY, - COLOR_DGREEN, - COLOR_OLIVE, - }, -} -local ttt_playercolors_all_count = #ttt_playercolors.all -local ttt_playercolors_serious_count = #ttt_playercolors.serious - ---- --- @realm shared -local colormode = - CreateConVar("ttt_playercolor_mode", "1", { FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED }) - ---- --- @param string model The selected (default) playermodel --- @hook --- @realm shared -function GM:TTTPlayerColor(model) - local mode = colormode:GetInt() - - if mode == 1 then - return ttt_playercolors.serious[math.random(ttt_playercolors_serious_count)] - elseif mode == 2 then - return ttt_playercolors.all[math.random(ttt_playercolors_all_count)] - elseif mode == 3 then - -- Full randomness - return Color(math.random(0, 255), math.random(0, 255), math.random(0, 255)) - end - - -- No coloring - return COLOR_WHITE -end - --- -- Called every frame on client and server. -- This will be the same as @{GM:Tick} on the server when there is no lag, diff --git a/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua b/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua index 18619ab60..214f820d8 100644 --- a/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua +++ b/gamemodes/terrortown/gamemode/shared/sh_player_ext.lua @@ -193,18 +193,9 @@ function plymeta:SetRole(subrole, team, forceHooks, suppressEvent) -- @realm server hook.Run("PlayerLoadout", self, false) - -- Don't update the model if oldSubrole is nil (player isn't already spawned, leading to an initialization error) - if oldSubrole and GetConVar("ttt_enforce_playermodel"):GetBool() then - -- update subroleModel - self:SetModel(self:GetSubRoleModel()) - end - - -- Always clear color state, may later be changed in TTTPlayerSetColor - self:SetColor(COLOR_WHITE) - --- -- @realm server - hook.Run("TTTPlayerSetColor", self) + hook.Run("TTT2UpdateSubrolePlayermodel", self, oldSubrole ~= nil) end end @@ -1085,43 +1076,32 @@ local oldSetModel = plymeta.SetModel or plymeta.MetaBaseClass.SetModel -- @note override to fix PS/ModelSelector/... issues -- @realm shared function plymeta:SetModel(mdlName) - local mdl + --Dev(1, "Player", self, ":SetModel", mdlName or "(nil)") + --ErrorNoHaltWithStack("^^ ply:SetModel") - local curMdl = mdlName or self:GetModel() + local mdl = mdlName or self:GetModel() - if not checkModel(curMdl) then - curMdl = self.defaultModel + if not checkModel(mdl) then + hook.Run("PlayerSetModel", self) + mdl = self:GetModel() - if not checkModel(curMdl) then + if not checkModel(mdl) then + -- TODO: this is in the original code; is it still actually needed? if not checkModel(GAMEMODE.playermodel) then GAMEMODE.playermodel = GAMEMODE.force_plymodel - if not checkModel(GAMEMODE.playermodel) then GAMEMODE.playermodel = "models/player/phoenix.mdl" end end - curMdl = GAMEMODE.playermodel + mdl = GAMEMODE.playermodel end end - local srMdl = self:GetSubRoleModel() - if srMdl then - mdl = srMdl - - if curMdl ~= srMdl then - self.oldModel = curMdl - end - else - if self.oldModel then - mdl = self.oldModel - self.oldModel = nil - else - mdl = curMdl - end - end + -- TODO: original checked and enforced subrole model here. I don't think this is a good idea, so + -- that other addons can override the model selection here, so I'm ommitting it, but there's + -- probably a reason for it to exist. - -- last but not least, we fix this grey model "bug" if not checkModel(mdl) then mdl = "models/player/phoenix.mdl" end diff --git a/lua/ttt2/libraries/playermodels.lua b/lua/ttt2/libraries/playermodels.lua index a29bb97c0..f2b0ed270 100644 --- a/lua/ttt2/libraries/playermodels.lua +++ b/lua/ttt2/libraries/playermodels.lua @@ -18,10 +18,6 @@ local function GetPlayerSize(ply) return top - bottom end ---- --- @realm server -local cvCustomModels = CreateConVar("ttt2_use_custom_models", "0", { FCVAR_NOTIFY, FCVAR_ARCHIVE }) - local initialDefaultStates = { selected = { ["css_phoenix"] = true, @@ -271,7 +267,7 @@ function playermodels.GetRandomPlayerModel() local availableModels = playermodels.GetSelectedModels() local sizeAvailableModels = #availableModels - if cvCustomModels:GetBool() and sizeAvailableModels > 0 then + if customization.cv.playermodels.customModels:GetBool() and sizeAvailableModels > 0 then local modelPaths = playerManagerAllValidModels() local randomModel = availableModels[mathRandom(sizeAvailableModels)]