Jump to content

Recommended Posts

I've spent the last few days creating a simple self contained server browser. It will list all the servers of a specific game, allow the user to select a server and send a callback with the selected info.

kqr55SK.gif

Requirements: 

The script from this topic placed in a file here: Scripts/Functions/eventqueuemanager.lua

The Script:

 

--[[
		  Name:	serverBrowser
		Author:	Einlander
Date Completed: 2017-8-22
   Description:	A Server Browser for Leadwerks
	     Notes:	This is a rather simple Server Browser script that allows for listing of servers and selection.
				-2017-8-24: -Added the ability to directly connect to a ip and port.
							-Moved the code to put the window into a function and check on server browser creation.
				-2017-8-25: -Added the abilitiy to automatically resize to fit inside the bounds of the current context
--]]


--TODO: Add manual server connect

import("Scripts/Functions/eventqueuemanager.lua")
function serverBrowser(gameName,context,x,y,width,height)
	local self = {}	
	self.gameName=""
	local _context
	local serverBrowser = {}
	local ServerBrowserClient = Client:Create()
	local callback = {}
	local movemode = false
	local currentWindow
	
	
	--[[
	Description: Initializes and Draws the GUI
	Parameters: x as integer
				y as integer
				width as integer
				height as integer
	Notes: 	None
	]]
	
	local function createUI(x,y,width,height)		
		x = ((tonumber(x)~= nil) and x or 100)
		y = ((tonumber(y)~= nil) and y or 100)
		width = ((tonumber(width)~= nil) and width or 500)
		height = ((tonumber(height)~= nil) and height or 300)				
		serverBrowser.gui = GUI:Create(_context)
		serverBrowser.gui:Hide()
		serverBrowser.root = serverBrowser.gui:GetBase()				
		serverBrowser.root:SetLayout(x,y,width,height)
		serverBrowser.panel = Widget:Panel(0,0,width,height,serverBrowser.root)
		serverBrowser.closeButton = Widget:Button("X",1,1,19,19,serverBrowser.panel )
		serverBrowser.closeButton:SetLayout(serverBrowser.panel:GetSize().x-serverBrowser.closeButton:GetSize().x - 10,1,19,19)	-- hides browser when clicked, regardless of results	
		serverBrowser.moveButton = Widget:Button("+",10,1,19,19,serverBrowser.panel )
		serverBrowser.titlelabel = Widget:Label("Server Browser",40,5,100,25,serverBrowser.panel)
		
		serverBrowser.serveriplabel = Widget:Label("IP/Address:",15,serverBrowser.panel:GetSize().y - 23,100,25,serverBrowser.panel)
		serverBrowser.serverportlabel = Widget:Label("Port:",290,serverBrowser.panel:GetSize().y - 23,100,25,serverBrowser.panel)
		serverBrowser.serveripfield = Widget:TextField("",85,serverBrowser.panel:GetSize().y - 30, 200, 25, serverBrowser.panel)		
		serverBrowser.serverportfield = Widget:TextField("",320,serverBrowser.panel:GetSize().y - 30,80,25,serverBrowser.panel)		
		serverBrowser.connectButton = Widget:Button("Connect",405,serverBrowser.panel:GetSize().y - 30,55,25,serverBrowser.panel )		
		serverBrowser.serverList = Widget:ListBox(10,20,serverBrowser.panel:GetSize().x - 20,serverBrowser.panel:GetSize().y - 100,serverBrowser.panel)
		serverBrowser.refreshButton = Widget:Button("Refresh",10,serverBrowser.panel:GetSize().y - 70,55,25,serverBrowser.panel )
		serverBrowser.joinButton = Widget:Button("Join",0,0,55,25,serverBrowser.panel )		
		serverBrowser.joinButton:SetLayout(serverBrowser.panel:GetSize().x-serverBrowser.refreshButton:GetSize().x - 10,serverBrowser.panel:GetSize().y - 70,55,25)				
		serverBrowser.gui:Show()
	end
	
	
	--[[
	Description: Resizes the GUI
	Parameters: Width as integer
				Height as integer
	Notes: none
	]]
	
	function self:SetSize(width,height)				
		serverBrowser.root:SetLayout(serverBrowser.root:GetPosition().x,serverBrowser.root:GetPosition().y,width,height)
		serverBrowser.panel:SetLayout(0,0,width,height)				
		serverBrowser.closeButton:SetLayout(serverBrowser.panel:GetSize().x-serverBrowser.closeButton:GetSize().x - 10,1,19,19)	-- hides browser when clicked, regardless of results
		serverBrowser.moveButton:SetLayout(10,1,19,19)
		serverBrowser.titlelabel:SetLayout(40,5,100,25)	
		serverBrowser.serveriplabel:SetLayout(15,serverBrowser.panel:GetSize().y - 23,100,25)
		serverBrowser.serverportlabel:SetLayout(290,serverBrowser.panel:GetSize().y - 23,100,25)
		serverBrowser.serveripfield:SetLayout(85,serverBrowser.panel:GetSize().y - 30, 200, 25)
		serverBrowser.serverportfield:SetLayout(320,serverBrowser.panel:GetSize().y - 30,80,25)
		serverBrowser.connectButton:SetLayout(405,serverBrowser.panel:GetSize().y - 30,55,25)
		serverBrowser.serverList:SetLayout(10,20,serverBrowser.panel:GetSize().x - 20,serverBrowser.panel:GetSize().y - 100)
		serverBrowser.refreshButton:SetLayout(10,serverBrowser.panel:GetSize().y - 70,55,25)		
		serverBrowser.joinButton:SetLayout(serverBrowser.panel:GetSize().x-serverBrowser.refreshButton:GetSize().x - 10,serverBrowser.panel:GetSize().y - 70,55,25)						
	end
	
	
	
	--[[
	Description: Returns the server browsers size
	Parameters: ScaledCoords as boolean
	Notes: scaledCoords: if set to false logical coordinates will be returned, otherwise scaled coordinates will be returned.
	]]
	
	function self:GetSize(ScaledCoords)
		return serverBrowser.root:GetSize(ScaledCoords == true and true or false )
	end
	
	--[[
	Description: Shows UI
	Parameters: none
	Notes: none
	]]
	
	function self:Show()
		serverBrowser.gui:Show()
		serverBrowser.root:Enable()
		self.context:GetWindow():ShowMouse()
	end
	
	
	--[[
	Description: Hides UI
	Parameters: none
	Notes: none
	]]
	function self:Hide()
		serverBrowser.gui:Hide()
		serverBrowser.root:Disable()		
	end
	
	
	--[[
	Description: Returns if UI is hidden or not
	Parameters: none
	Notes: none
	]]
	function self:Hidden()		
		return serverBrowser.gui:Hidden()		
	end
	
	
	--[[
	Description: Sets a external function to call when server is selected or window is closed.
	Parameters: callback as function
	Notes: 
				--self:SetCallback is an overloaded function
				--self:SetCallback(callback) simple callback, used on functions not attached to an entity
				--self:SetCallback(self, callback) Used when adding from a script attached to an entity (calling from and to any function that starts with Script: )
				
				self:SetCallback requires the callback function to have these 3 parameters: (ip,port,close)
				Ip is the server address as a string
				port is unused as of now since the leadwerks master server does not return it and also the remoteserver class has no property for it
				close is a boolean that retuns true if the serverbrowser is closed
				if it is closed it will return (nil,nil,true)
				if a server is chosen and connect is clicked, it will return ("address",nil,true) it will also close the window. Take care of how the callback handles the data
					(recursion is possible if not thought out properly)
				
	]]	
	
	function self:SetCallback(_callback)		
		--if (#args==1) then		
			if type(_callback) ~= "function" then return false end
			callback.functionValue = _callback
		--elseif (#args==2) then
			--if type(args[2]) ~= "function" then return false end
			--callback.selfValue = args[1]
			--callback.functionValue = args[2]			
		--else
			--return false
		--end	
		
	end
	

	--[[
	Description: Removes the callback
	Parameters: none
	Notes: none
	]]
	function self:ClearCallback()
		--callback.selfValue = nil
		callback.functionValue = nil
	end
	
	--[[
	Description: Moves the serverbrowser back into the window
	Parameters: non
	Notes: none
	]]
	local function FitInScreen()
		-- resize to fit inside current context
		if serverBrowser.root:GetSize().x > _context:GetWidth() then
			self:SetSize(_context:GetWidth(), serverBrowser.root:GetSize().y)
		end
		if serverBrowser.root:GetSize().y > _context:GetHeight() then
			self:SetSize(serverBrowser.root:GetSize().x, _context:GetHeight() )
		end
		-- move it into view
		if serverBrowser.root:GetPosition().x + serverBrowser.root:GetSize().x > _context:GetWidth() then
			serverBrowser.root:SetLayout(serverBrowser.root:GetPosition().x - (  (serverBrowser.root:GetPosition().x + serverBrowser.root:GetSize().x)  - _context:GetWidth()) ,serverBrowser.root:GetPosition().y ,serverBrowser.root:GetSize().x,serverBrowser.root:GetSize().y)
		end
		if serverBrowser.root:GetPosition().x < 0 then
			serverBrowser.root:SetLayout(0,serverBrowser.root:GetPosition().y ,serverBrowser.root:GetSize().x,serverBrowser.root:GetSize().y)
		end
		
		if serverBrowser.root:GetPosition().y + serverBrowser.root:GetSize().y > _context:GetHeight() then
			serverBrowser.root:SetLayout(serverBrowser.root:GetPosition().x  ,serverBrowser.root:GetPosition().y - (  (serverBrowser.root:GetPosition().y + serverBrowser.root:GetSize().y)  - _context:GetHeight()),serverBrowser.root:GetSize().x,serverBrowser.root:GetSize().y)
		end
		if serverBrowser.root:GetPosition().y < 0 then
			serverBrowser.root:SetLayout(serverBrowser.root:GetPosition().x, 0 ,serverBrowser.root:GetSize().x,serverBrowser.root:GetSize().y)
		end											
	end
	
	--[[
	Description: Processes all the events recived
	Parameters: event as Event
	Notes: none
	]]
	local function ProcessEvent(event)
		if event.id == Event.WidgetSelect then											
		elseif event.id == Event.WidgetAction then		
			if movemode == true and (event.source == serverBrowser.gui) then 			
				_context:SetColor(0,0,0,0) -- need to clear alpha!!! otherwise you wont see the 3d
				serverBrowser.gui:Clear()
				serverBrowser.root:SetLayout(currentWindow:GetMousePosition().x - serverBrowser.moveButton:GetSize().x , currentWindow:GetMousePosition().y - (serverBrowser.moveButton:GetSize().y/2) ,serverBrowser.root:GetSize().x,serverBrowser.root:GetSize().y)				
				EventQueue:Emit(Event.WidgetAction,serverBrowser.gui)				
				if currentWindow:MouseHit(Key.LButton) then
					-- make sure it doesnt escape the screen					
					movemode = false
					FitInScreen()
					_context:SetColor(0,0,0,0)
					serverBrowser.gui:Clear()					
					serverBrowser.root:Enable()
				end										
				serverBrowser.panel:Redraw()
			return true
			end	
			
			if event.source == serverBrowser.moveButton then				
				movemode = true				
				currentWindow = Window:GetCurrent()				
				EventQueue:Emit(Event.WidgetAction,serverBrowser.gui)
				_context:SetColor(0,0,0,0)
				serverBrowser.gui:Clear()
				serverBrowser.root:Disable()
				currentWindow:FlushMouse()
				return true
			end
			if event.source == serverBrowser.closeButton then				
				serverBrowser.root:Disable()
				serverBrowser.gui:Hide()				
				--System:Print(self.gameName .. ":: Close button")
				if callback.functionValue ~= nil then
					callback.functionValue(nil,nil,true)
				end
				self:Release()
				return true
			end		
			
			if event.source == serverBrowser.connectButton then
				local serverip = ((string.len(serverBrowser.serveripfield:GetText())>0)	and serverBrowser.serveripfield:GetText() or nil)
				local serverport = ((tonumber(serverBrowser.serverportfield:GetText()) ~= nil) and tonumber(serverBrowser.serverportfield:GetText())	or nil)
				if callback.functionValue ~= nil then						
					local cb = callback.functionValue
					-- release everything to minimize mem use in case of consecutive server hopping.abs
					self:Release()
					cb(serverip,serverport,true)-- should it close when they chose a server?						
					return true
				end
				return false
			end
			
			if event.source == serverBrowser.joinButton then	
				--System:Print(self.gameName .. ":: Connect button")
				if serverBrowser.serverList:GetSelectedItem() > -1 then					
					local servercount = ServerBrowserClient:CountServers(self.gameName) -- has a super high chance of screwing up when clicked, i mean REALLY HIGH
					local remoteServerData = ServerBrowserClient:GetServer(serverBrowser.serverList:GetSelectedItem())
					--System:Print("Server Name::" .. remoteServerData.address .."#### Description" .. remoteServerData.description)
					serverBrowser.root:Disable()
					serverBrowser.gui:Hide()
					if callback.functionValue ~= nil then
						local cb = callback.functionValue
						local addy = remoteServerData.address
						-- there is no port :/
						remoteServerData:Release() -- release everything to minimize mem use in case of consecutive server hopping.
						self:Release()
						cb(addy,nil,true)-- should it close when they chose a server?						
						return true
					end					
				end
				return false
			end	
			if event.source == serverBrowser.refreshButton then				
				--System:Print(self.gameName .. ":: Refresh button")								
				self:PopulateServers(self.gameName)
				return true
			end
		end
		return true
	end
	
	
	--[[
	Description: Hides UI
	Parameters: none
	Notes: none
	]]
	function self:Update(event)
		if serverBrowser == nil then return false end
		if serverBrowser.gui:Hidden() then return false end				
		if event:GetClassName() == "Event" then					
			ProcessEvent(event)
		end
		return true		
	end
	
	--[[
	Description: Adds Servers to the server list
	Parameters: gameName as string
	Notes: Gamename is the exact name of the game the server registered
	]]
	
	function self:PopulateServers(gameName)
		if gameName == nil then return end		
		local servercount = ServerBrowserClient:CountServers(gameName)
		--System:Print("Server Count::" .. tostring(servercount))
		serverBrowser.serverList:ClearItems()
		serverBrowser.serverList:SelectItem(-1)
		for i=0, servercount-1 do
			remoteServerData = ServerBrowserClient:GetServer(i)
		--	System:Print("Server Name::" .. remoteServerData.address .."#### Description" .. remoteServerData.description)
			serverBrowser.serverList:AddItem("["..remoteServerData.address.."]  ["..remoteServerData.description.."]")
		end		
	end
	
	
	--[[
	Description: Initializes Class
	Parameters: gameName as string
				context as Context
				x as integer
				y as y as integer
				width as integer
				height as integer
	Notes: Gamename is the exact name of the game the server registered
	]]
	local function init(gameName,context,x,y,width,height)
		-- check for the context
		_context = context		
		createUI(x,y,width,height)
		FitInScreen()
		self.gameName = gameName		
		self:PopulateServers(self.gameName)		
		EventQueueManager:AddListener(self.Update)		
	end	
	
	
	--[[
	Description: Unloads the class and all it's componentes
	Parameters: none
	Notes: none
	]]
	function self:Release()
		for item in pairs(self) do
			if (type(self[item]) == "function") or (type(self[item]) == "userdata") then
				self[item] = nil
			end
		end					
		self.gameName = nil	
		
		-- The order is important!
		serverBrowser.root:Disable()
		serverBrowser.gui:Hide()
		serverBrowser.closeButton:Release()
		serverBrowser.titlelabel:Release()		
		serverBrowser.serverList:Release()
		serverBrowser.joinButton:Release()		
		serverBrowser.refreshButton:Release()
		serverBrowser.connectButton:Release()
		serverBrowser.moveButton:Release()
		serverBrowser.serveriplabel:Release()
		serverBrowser.serverportlabel:Release()
		serverBrowser.serveripfield:Release()
		serverBrowser.serverportfield:Release()		
		serverBrowser.panel:Release()
		serverBrowser.root:Release()
		serverBrowser.gui = nil
		serverBrowser=nil
		ServerBrowserClient:Release()
		_context = nil
		callback = nil		
		movemode = nil
		self.Show = nil
		self = nil		
	end
	
	
	init(gameName,context,x,y,width,height)
	
	return self
end

 

A simple example:

It will create a server and open a server browser window. + Starts the move mode, click to exit it. X closes the window.

import("Scripts/Functions/eventqueuemanager.lua")
import("Scripts/serverbrowser.lua")
local window = Window:Create()
local context = Context:Create(window)
local server = Server:Create()
System:Print("Server1 publish::" .. tostring(server:Publish("Example Temp Test Server", "Testing population of server list from masterserver ")))
server:Release()

function test(ip,port,close) -- callback function
	System:Print("Callback")
	System:Print(ip)	
	System:Print(tostring((close == true) and "close" or ""))
end

local serverbrowser = serverBrowser("Example Temp Test Server",context,0,0) -- create the serverbrowser
serverbrowser:SetCallback(test) -- set callback

-- do stuff
while true do
	if window:Closed() then return end
	if window:KeyHit(Key.Escape) then return end	
	Time:Update()
	context:SetColor(0,0,0,0) -- need to clear alpha!! or the gui will stay on screen!!
	context:Clear()	
	EventQueueManager:Update()					
	context:Sync()
end
serverbrowser:ClearCallback() -- remove callback
serverbrowser:Release() -- release everything

The server browser will obey the limits of your window. If any part of it is not on screen, it will be pushed back onto it.

Edits:

-2017-8-24: -Added the ability to directly connect to a ip and port.
                    -Moved the code to put the window into a function and check on server browser creation.

-2017-8-25: -Added the abilitiy to automatically resize to fit inside the bounds of the current context

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...