local changeDelay = CreateConVar( "gc_mapchangedelay", "8", FCVAR_ARCHIVE, "Seconds between map completion and loading the next coop map" ) local maxMapTime = CreateConVar( "gc_mapmaxtime", "3600", FCVAR_ARCHIVE, "Maximum map time in seconds before rotating; 0 disables the time limit" ) local changeQueued = false local function MapExists(mapName) if not mapName or mapName == "" then return false end local wanted = string.lower(string.StripExtension(mapName)) for _, fileName in ipairs(file.Find("maps/*.bsp", "GAME")) do if string.lower(string.StripExtension(fileName)) == wanted then return true end end return false end local function SetFreshNextLevel() local nextLevel = GetConVar("nextlevel"):GetString() if nextLevel == "" or not MapExists(nextLevel) or string.lower(nextLevel) == string.lower(game.GetMap()) then nextLevel = NextRandomLevel() end return nextLevel end function QueueCoopMapChange(reason, requestedMap) if changeQueued then return end local destination = requestedMap and string.lower(requestedMap) or nil if not MapExists(destination) or destination == string.lower(game.GetMap()) then destination = SetFreshNextLevel() end if not destination then ErrorNoHalt("[custom_coop] Map completed, but no installed destination map is available\n") return end changeQueued = true local delay = math.max(changeDelay:GetFloat(), 0) PrintMessage(HUD_PRINTTALK, "Map complete! Loading " .. destination .. " in " .. math.ceil(delay) .. " seconds.") print("[custom_coop] Map change queued by " .. (reason or "unknown") .. ": " .. destination) timer.Create("CustomCoopChangeCompletedMap", delay, 1, function() RunConsoleCommand("changelevel", destination) end) end hook.Add("AcceptInput", "CustomCoopDetectMapCompletion", function(ent, inputName, _, _, value) if not IsValid(ent) then return end local className = string.lower(ent:GetClass()) local input = string.lower(inputName or "") if className == "game_end" and input == "endgame" then QueueCoopMapChange("game_end") return true end if className ~= "point_servercommand" or input ~= "command" then return end local command = string.lower(string.Trim(tostring(value or ""))) local requestedMap = string.match(command, "^changelevel%s+[%\"]?([%w_%-%[%]!]+)") or string.match(command, "^map%s+[%\"]?([%w_%-%[%]!]+)") if requestedMap and (not MapExists(requestedMap) or string.lower(requestedMap) == string.lower(game.GetMap())) then QueueCoopMapChange("point_servercommand", requestedMap) return true end end) -- Repair old trigger_changelevel/point_changelevel entities that refer to a map -- which is not mounted. Valid campaign destinations remain untouched. hook.Add("EntityKeyValue", "CustomCoopRepairChangelevelTargets", function(ent, key, value) local className = string.lower(ent:GetClass()) if className ~= "trigger_changelevel" and className ~= "point_changelevel" then return end if string.lower(key or "") ~= "map" then return end if MapExists(value) and string.lower(value) ~= string.lower(game.GetMap()) then return end local replacement = GetRandomLevel() if replacement then print("[custom_coop] Replacing unavailable changelevel target '" .. tostring(value) .. "' with " .. replacement) return replacement end end) hook.Add("InitPostEntity", "CustomCoopStartMapTimeLimit", function() SetFreshNextLevel() local limit = maxMapTime:GetFloat() if limit <= 0 then return end timer.Create("CustomCoopMapTimeLimit", limit, 1, function() QueueCoopMapChange("time limit") end) end)