BlitzMax BNet UDP Multiplayer Tutorial





What will this tutorial teach me?
This tutorial aims to show users that are new to Vertex's BNet networking library how to construct a simple multiplayer game in a small amount of time. You should then be able to take the basics learned here and expand on them to create a multiplayer game of much greater complexity.

Tutorial includes:
Controls:
Important Values




Where can I grab a compiled demo for testing?
If you would rather test the tutorial code right away without compiling it yourself, the compiled version can be downloaded from here:

Download Multi.zip ()

To test the multiplayer aspect, run server.exe, then run client.exe and hit 'C' to connect. Luigi should now be visible and animate the same across both windows.




What do I need to compile the code myself?
This tutorial relies on Vertex's BNet module, version 1.41. You may notice that newer versions are available, but version 1.41 is the version I prefer to use. There is no loss of functionality with this version, and after using it, I think you'll agree that it is superior to newer versions that change the way messages are sent and generally over complicate things. Download a special version of BNet 1.41 that has been modified to work with BlitzMax 1.20 here:

Download BNet141.zip

Place the BNet.mod folder in your BlitzMax/mod/pub.mod/ folder to be able to compile the tutorial code.




I get a "CreateUDPStream Failed!" error when running the server.
What am I doing wrong?

The most likely reason for this error is that the UDP port the server is attempting to open is being blocked by a router or firewall. The default value for the HOSTPORT constant is set to 41219. If you are unsure of how to forward or unblock a port, please refer to www.PortForward.com. If you already have a UDP port open, change the HOSTPORT value to the open port number and the server should run correctly.







+ I.Declares


' Multiplayer Tutorial by Eikon
' Made for BlitzBasic.com
' Uses BNet 1.41, modified to work with
' BlitzMax version 1.20
' Need help? E-mail eikon@eiksoft.com

Framework BRL.Max2D 
Import BRL.D3D7Max2D
Import PUB.BNet
Import BRL.Basic
Import BRL.Retro
Import BRL.PNGLoader

Extern "win32"                      ' win32 API calls
    Function FindWindow(lpClassName$z, lpWindowName$z) = "FindWindowA@8"
    Function GetWindowRect(hWnd, lpRect:Byte Ptr)
End Extern

Type lpRECT                         ' Type for holding Window Rect
    Field l, t, r, b
End Type

AppTitle = "Multiplayer Test"
SetGraphicsDriver D3D7Max2DDriver() ' Use Direct3D7
Graphics 640, 480, 0, 60
WindowMode
In our first section of code, we use the Framework and Import commands to only include the modules that we need when building our EXE. This ensures the EXE will be as small as possible. Next we declare a couple of API functions that we'll be needing in a later function. The lpRect type is for holding the window position that is returned when using the GetWindowRect function. We then give our window a title, set the graphics driver to D3D, and initialize our graphics window. The WindowMode() function is then called to center the window on the user's screen. See the "Junk" section for more info.





' // Network Specific Declares
Global bHost% = False               ' Are you hosting or joining?
Global bConn% = False               ' Are you connected?
Global ServerIP$ = "127.0.0.1"      ' Client should set to server's IP (localhost for testing)
Global intServerIP% = IntIP(ServerIP$) ' Int of server IP
Global Stream:TUDPStream            ' UDP Stream
Global myPing%, PingTime            ' Ping and Ping Timer
Global UpdateTime                   ' Main Update Timer
Global SendID%, RecvID%, lastRecv[32] ' Packet IDs and Duplicate checking Array
Global ClientIP%, ClientPort:Short  ' Client IP and Port
Global DebugMode = False            ' Toggles the display of debug messages
Global bSentFull                    ' Full update boolean

' // Networking Constants
Const HOSTPORT = 41219              ' * IMPORTANT * This UDP port must not be blocked by a router 
                                    ' or firewall, or CreateUDPStream will fail
Const NET_ACK    = 1                ' ACKnowledge 
Const NET_JOIN   = 2                ' Join Attempt
Const NET_PING   = 3                ' Ping
Const NET_PONG   = 4                ' Pong
Const NET_UPDATE = 5                ' Update
Const NET_STATE  = 6                ' State update only
Const NET_QUIT   = 7                ' Quit
Here we have some public and constant values to be used by network specific code. The commenting should help you understand what each value will be used for.





Global n:NetObj, netList:TList = New TList ' Packet Object and List

' NetObj Type (Makes UDP Packets reliable)
Type NetObj
    Field ID, SID, T, Retry   ' Packet ID, SendID, Timer, Retry

    Function Create:NetObj(ID)
        If Stream = Null Then Return

	n:NetObj = New NetObj ' Create Packet
	n.ID = ID             ' Packet ID
	n.SID = SendID        ' Send ID

	Select ID
	    Case 1            ' Join Packet
	    WriteInt Stream, n.SID
	    WriteByte Stream, NET_JOIN
			
	    Case 2            ' Quit Packet
	    WriteInt Stream, n.SID
	    WriteByte Stream, NET_QUIT
						
	End Select
		
	SendUDP               ' Send UDP Message
	n.T = MilliSecs()     ' Set Timer
		
	Return n
    End Function
This very important piece of code is the foundation for our reliable UDP packet system. It is important to note that when sending UDP packets under normal circumstances, the packets are not guaranteed to arrive in order, or even to arrive at all. A method must be devised that will make packets reliable, and that can also differentiate between old, new, and duplicate packets. The above Type takes care of the former issue by adding the packets to a type list. We can then resend the packets up to a retry limit, or send them endlessly until a reply is received.

The first line in the code creates a NetObj type handle, then creates a TList object to hold the packets. Each NetObj in the NetList represents one data packet. The Create function sends out the initial packet, assigns the packet its permanent SendID, and then returns the packet to be added to the NetList. The SendID is the number each packet carries to identify itself. This value is used later on to determine if the packet is old or a duplicate, so it can be discarded. Only the NET_JOIN and NET_QUIT packets need to be made reliable, the other packets will use a different send method.





   ' Resends Packets 	
    Method Handle()
        If Stream = Null Then Return
	
	Select ID
	    Case 1 ' Join Attempt
	    If MilliSecs() >= T + 100 Then ' Resend every 100ms
	        WriteInt Stream, SID
		WriteByte Stream, NET_JOIN
				
		SendUDP
				
		T = MilliSecs()
		If Retry < 20 Then    ' Retry (20 times every 100ms = 2 seconds)
		    Retry:+1
		    If DebugMode = True Then 
			Print "*** Resent join packet to host, attempt " + Retry + " ***"
		    Endif
	        Else
		    If DebugMode = True Then Print "*** Dropped join packet ***"
         	    NetList.Remove n  ' Drop Packet
	        EndIf
	    EndIf
			
	    Case 2 ' Quit Attempt (Never Drops)
	    If MilliSecs() >= T + 100 Then ' Resend every 100ms
    	        WriteInt Stream, SID
	        WriteByte Stream, NET_QUIT
				
	        SendUDP

	        T = MilliSecs()
                If DebugMode = True Then Print "*** Resent quit packet ***"
            EndIf

        End Select
    End Method
End Type
The Handle method is responsible for checking each packet timer to see if it needs to be resent. The NET_JOIN packet is dropped after 20 sends over a period of two seconds. The NET_QUIT packet is never dropped until a response is received because removing a player has to be 100% reliable. Also notice the debug messages here, you can use them to diagnose packet loss issues when compiling the code.





' // Load Images
SetMaskColor 255, 0, 255
Global imgMario:TImage  = LoadAnimImage("mario.png", 17, 32, 0, 14, MASKEDIMAGE|FILTEREDIMAGE)
Global imgLuigi:TImage  = LoadAnimImage("luigi.png", 17, 32, 0, 14, MASKEDIMAGE|FILTEREDIMAGE)
Global imgTiles:TImage  = LoadImage("tiles.png", MASKEDIMAGE|FILTEREDIMAGE)
Global imgClouds:TImage = LoadImage("clouds.png", MASKEDIMAGE|FILTEREDIMAGE)

' Cloud scrolling
Local cloudX#

' Player 1 (You)
' player X, player Y, player Frame, player Direction, player State, animation Timer
Global pX% = 20, pY# = 352, pF%, pD% = 1, pState%, animTime
Global pJ%, pG# ' player Jumping?, player Gravity

' Player 2 (Client)
Global cX%, cY#, cF%, cD%, cState%, cAnimTime ' Client variables
Global cJ%, cG# ' client Jumping?, client Gravity

' Player State Key
' ==========================
' 0 = Idle
' 1 = Running Left
' 2 = Running Right
' 3 = Jumping Left
' 4 = Jumping Right
' 5 = Jumping Up
First our game images are loaded. These PNG files can be found in the Multi.zip file at the top of the tutorial. We'll need Mario to play the host, and Luigi will be the client, the player who is joining the game. There are tiles for them to stand on, and also a scrolling cloud background to liven things up.

Player and client publics are then declared. This basically creates two sets of positions, one for you and one for the second player. The state key lays out which value correlates to each player state. Instead of constantly sending out our position over the network, we send a single state byte for the receiver to handle. The receiver can then move and animate Mario or Luigi accordingly. This method is what accounts for the smoothness in movement and animation over the network.





If bHost = True Then ' Hosting preperations
    pX = 595; pD = 0 ' Move Host to Right Side
    
    InitNetwork                        ' Initialize Network
    Stream = CreateUDPStream(HOSTPORT) ' Attempt to open port
Else                                   ' Client preperations
    Stream = CreateUDPStream()         ' Attempt to open port
EndIf

If Not Stream Then Notify "CreateUDPStream Failed!"; End ' Failed to open stream
ClearLastRecv ' Clear Duplicate Array
This last piece of code before the Main section finally opens the UDP Stream that we use to send data between users. The host stream requires an open UDP port (HOSTPORT) that we talked about earlier. All in all, a pretty basic section. Now we must tackle...





+ II.Main


' // MAIN
SetClsColor 92, 148, 252 ' Blue Sky
Repeat
    SetScale 2, 2                           ' Render Block
    DrawImage imgClouds, Int(cloudX), 0     ' Clouds
    DrawImage imgTiles, 0, 416              ' Tiles
    If bHost = False Then                   ' Client is Luigi
        DrawImage imgLuigi, pX, Int(pY), pF ' Luigi
	If bConn = True Then DrawImage imgMario, cX, Int(cY), cF ' Draw 2nd player
    Else                                    ' Host is Mario
	DrawImage imgMario, pX, Int(pY), pF ' Mario
	If bConn = True Then DrawImage imgLuigi, cX, Int(cY), cF ' Draw 2nd player
    EndIf
    
    SetScale 1, 1
    RenderHUD                               ' Heads Up Display

    If cloudX > -(ImageWidth(imgClouds) * 1.5) Then cloudX:-.3 Else cloudX = 640 ' Scrolling Clouds

    HandleClient                            ' Handle Client based on states
    HandleJump                              ' Jump
    HandleInput                             ' Keyboard Input
	
    UpdateUDP                               ' Handle UDP Packets
    
    Flip; Cls
Until KeyDown(KEY_ESCAPE) Or AppTerminate() = True
If bConn = True Then NetDisconnect ' Disconnect
End
Main is where all the rendering in our game will occur. You can see, starting from the ' Render Block comment, that we draw the scrolling clouds, the tiles, then Mario and Luigi last. The second player is only drawn once bConn is checked to make sure we are connected. The last part of main is devoted to the functions that handle keyboard input and networking tasks. If the user decides to exit the game, the NetDisconnect function is invoked only if they are connected. Speaking of functions...





+ III.Functions


' // HandleInput function handles keyboard input and player animation
Function HandleInput()
    If KeyDown(KEY_LEFT) And pX > 0 Then         ' Move Left
        If pD = 1 Then pF = 8                    ' Correct frame
	pD = 0; pX:-2                            ' Move player, facing left
	If pJ = True Then pState = 3 Else pState = 1 ' Set player state to jumping left

	If MilliSecs() >= animTime + 125 Then     ' Animate
	    If pF < 10 Then pF:+1 Else pF = 8
	    animTime = MilliSecs()
	EndIf
		
	If pState = 3 Then pF = 12 ' Jump Frame
	bSentFull = False          ' Full update needed

    ElseIf KeyDown(KEY_RIGHT) And pX < 606 Then
	If pD = 0 Then pF = 1      ' Correct frame
	pD = 1; pX:+2              ' Move player, facing right
	If pJ = True Then pState = 4 Else pState = 2 ' Set player state to jumping right
		
	If MilliSecs() >= animTime + 125 Then     ' Animate
	    If pF < 3 Then pF:+1 Else pF = 1
	    animTime = MilliSecs()
	EndIf
		
	If pState = 4 Then pF = 5 ' Jump Frame
	bSentFull = False         ' Full update needed

    Else
        If pD = 1 Then pF = 0 Else pF = 7         ' Idle
	animTime = MilliSecs() - 125
	If pState = 5 And pJ = True Then 
	    If pD = 0 Then pF = 12 Else pF = 5
	Else
	    pState = 0; pJ = False ' Set player state to idle
		
	    If bSentFull = False And bConn = True And pJ = False Then ' Send Full Update to align player
	        WriteInt Stream, SendID
		WriteByte Stream, NET_UPDATE
		WriteInt Stream, pX
		WriteInt Stream, pY
		WriteByte Stream, pD
				
		SendUDP
   		bSentFull = True
	    EndIf

	EndIf	
    EndIf

    If KeyDown(KEY_SPACE) And pJ = False Then pJ = True; pG = -4.0 ' Jump
    If KeyHit(KEY_C) And bConn = False Then NetConnect   ' Attempt to Connect
	
End Function
The HandleInput() function doesn't have a lot to do with networking, only a small section of code near the bottom is relevant. The remainder is very basic input and animation stuff, things that should be pretty self explanatory. The important thing to note is that when the player is idle, the bSentFull flag is checked. If True, the player has moved, and a full update packet is sent. Unlike the NET_STATE packet that contains only the player's current state, the full update packet consists of the player's full position, direction included. This allows the receiver to align your character on his screen whenever you become idle. Lastly, the 'SPACE' key is checked to initiate a jump, and 'C' is used by the client to call up the connect routine.





Function HandleClient()
    If bConn = False Then Return ' No Connection
	
    If cState = 1 Or cState = 3 Then ' Run / Jump Left
        If cD = 1 Then cF = 8
        cD = 0; cX:-2
		
	If MilliSecs() >= cAnimTime + 125 Then     ' Animate
	    If cF < 10 Then cF:+1 Else cF = 8
	    cAnimTime = MilliSecs()
	EndIf
		
    ElseIf cState = 2 Or cState = 4 ' Run / Jump Right
	If cD = 0 Then cF = 1
	cD = 1; cX:+2
		
	If MilliSecs() >= cAnimTime + 125 Then     ' Animate
	    If cF < 3 Then cF:+1 Else cF = 1
	    cAnimTime = MilliSecs()
	EndIf
	
    ElseIf cState = 0 Then ' Idle
	If cD = 1 Then cF = 0 Else cF = 7          ' Idle
	cAnimTime = MilliSecs() - 125
		
    EndIf
	
    If cState >= 3 And cJ = False Then cJ = True; cG = -4.0 ' Jump
End Function
The HandleClient() function is almost an exact clone of HandleInput(), the difference is that this function is responsible for controlling the net player. The cState variable that we receive from our opponent's NET_STATE packet tells us how to move and animate his character on our screen.





' // Controls Player Jump for both you and client
Function HandleJump()
    If pJ = False And cJ = False Then Return ' Not Jumping
    pState = 5
    pY:+pG
	
    If pG < 3 Then pG:+.08 Else pG = 3 ' Limit gravity
	
    If Int(pY) >= 352 Then 
        pY = 352; pJ = False ' Turn off jumping
	animTime = MilliSecs() - 125

    Else                     ' Handle animation frames
      	If pD = 0 Then pF = 12 Else pF = 5
		
    EndIf
	
    If bConn = True Then ' Client Jump
        cY:+cG
	    
        If cG < 3 Then cG:+.08 Else cG = 3 ' Limit gravity

	If Int(cY) >= 352 Then 
	    cY = 352; cJ = False ' Turn off jumping
	    cAnimTime = MilliSecs() - 125
	    cState = 0
	
        Else                     ' Handle animation frames
	    If cD = 0 Then cF = 12 Else cF = 5
			
	EndIf
    EndIf		
End Function
Another mundane function, HandleJump() controls the jumping and gravity action of both the player and client.





Function UpdateUDP()

    If bConn = True Then ' Updates
        If MilliSecs() >= UpdateTime + 100 ' Send Main Update every 100ms (unreliable)
	    WriteInt Stream, SendID
	    WriteByte Stream, NET_STATE
	    WriteByte Stream, pState

	    SendUDP
            UpdateTime = MilliSecs()
	EndIf
	
	If MilliSecs() >= PingTime + 1000  ' Ping once every second (unreliable)
	    WriteInt Stream, SendID
	    WriteByte Stream, NET_PING
		
	    SendUDP						
	    PingTime = MilliSecs()
	EndIf
    EndIf
	
    Local bMsgIsNew
	
    If RecvUDPMsg(Stream) Then ' A UDP packet has been received
	RecvID = ReadInt(Stream)
	Data   = ReadByte(Stream)
	IP     = UDPMsgIP(Stream)
	Port   = UDPMsgPort(Stream)	
				
	bMsgIsNew = IsMsgNew(RecvID, Data) ' Make sure packet is not a duplicate
			
	If bMsgIsNew = True Then
	    Select Data
	        Case NET_JOIN                 ' JOIN PACKET
		If bConn = False And bHost = True Then
		    ClientIP = IP
		    ClientPort = Port
		    NetAck RecvID             ' Send ACK
		    bConn = True              ' Connected
	        EndIf
	
         	Case NET_PING                 ' Received Ping, Send Pong
		WriteInt Stream, SendID
		WriteByte Stream, NET_PONG
	
		SendUDP
				
		Case NET_PONG                 ' Ping / Pong - Calculate Ping
		myPing = (MilliSecs() - pingTime) / 2
				
		Case NET_ACK                  ' ACK PACKET
		RemoveNetObj ReadInt(Stream)  ' Remove Reliable Packet (ACK has been received)
	
		Case NET_STATE                ' State Update
		cState = ReadByte(Stream)     ' Set client state
				
		Case NET_UPDATE               ' Full update, align
		cX = ReadInt(Stream)          ' X
		cY = ReadInt(Stream)          ' Y
		cD = ReadByte(Stream)         ' Direction
				            
		Case NET_QUIT                 ' Player Quits
		bConn = 2                     ' Quit
		NetAck RecvID                 ' Send ACK           
			
	    End Select	
	EndIf
    EndIf

End Function
UpdateUDP() is where the bulk of our networking code lies. The function is divided into two sections, the top two timer checks are for sending, and the bottom section is for receiving. Let's start with the sending section first.

Packets Sent
  • NET_STATE - This packet is sent every 100 milliseconds, and is considered a partial update, one that only contains player state rather than full position
  • NET_PING - A ping request is sent out once every second

The receive section begins with, If RecvUDPMsg(Stream):

Packets Received
  • NET_JOIN - Join packet from client, send back a NET_ACK
  • NET_PING - Ping packet, respond with a NET_PONG
  • NET_PONG - This is when the ping is calculated, based on travel time / 2
  • NET_ACK - Acknowledge packets. These are sent to verify that a reliable packet has been received
  • NET_STATE - Client has sent a state update
  • NET_UPDATE - Client sends update (when idle), contains full position
  • NET_QUIT - Client sends reliable QUIT message, answer it with a NET_ACK

Important Values
RecvID - Received packet's SendID
Data - Packet ID
IP - Client IP
Port - Client Port

Functions of Interest: IsMsgNew(RecvID, Data), RemoveNetObj(AckID) (See their descriptions below)





' // IsMsgNew determines whether the current UDP packet is old or a duplicate
Function IsMsgNew(ID, Data = 0)

    For i = 30 To 0 Step -1
        lastRecv[i + 1] = lastRecv[ i ]
    Next

    lastRecv[0] = ID

    For i = 1 To 31
        If lastRecv[ i ] = lastRecv[0] Then ' Duplicate
		
	'Print "ID: " + lastRecv[0]
		
	Select Data ' ACK Anyway (Reliable packets only)
	    Case NET_JOIN, NET_QUIT
	    NetAck ID
			
	    If DebugMode = True Then Print "*** Received duplicate packet, resending ACK ***"

	End Select

        Return False
    EndIf
Next

Return True
End Function
Here we have the answer to our duplicate packet problems, the IsMsgNew function. Every time a packet is received, this function places its SendID into an array. The LastRecv[] array is later checked for an occurance of the current packet's SendID. If the same ID is found, the packet can be discarded. If the duplicate is a reliable packet, an ACK still needs to be sent in response.





' // RemoveNetObj removes reliable UDP packets from the queue once an ACK has been received
Function RemoveNetObj(AckID)

For n:NetObj = EachIn NetList
    If n.SID = AckID Then
	tmpID = n.ID
	NetList.Remove n
	For n:NetObj = EachIn NetList                                ' Remove old entries
	If n.ID = tmpID And n.SID <= AckID Then
            NetList.Remove n 
	Next
        Exit
    EndIf
Next

End Function
RemoveNetObj() is called when an ACK packet has been received. The AckID contains the SendID of the reliable packet that can be removed.





' // NetAck sends ACKnowledge packet to client
Function NetAck(RecvID)
    WriteInt Stream, SendID 
    WriteByte Stream, NET_ACK
    WriteInt Stream, RecvID
	
    SendUDP

End Function
NetAck() sends an acknowledge packet that contains the SendID of the reliable packet that was received.





' // SendUDP sends the current packet to either host or client
Function SendUDP()

	If bHost = True Then ' Send to Client
		SendUDPMsg Stream, ClientIP, ClientPort
	Else                 ' Send to Host
		SendUDPMsg Stream, IntServerIP, HOSTPORT
	EndIf
	
	SendID:+1	         ' Increment send counter

End Function
The SendUDP function calls the command that actually sends the data packets over the network. bHost is checked to determine where to send the packet, then the SendID public is incremented.





' // NetConnect attempts to connect client to host
Function NetConnect()

timeOutTime = MilliSecs()
NetList.AddLast netObj.Create(1) ' Send Reliable Join Attempt

Local tmpConn = 0

ClearLastRecv ' Clear Duplicate Array

SetClsColor 0, 0, 0
Repeat
	DrawText "Connecting...", 5, 5
	
	If RecvUDPMsg(Stream) Then ' A UDP packet has been received
		RecvID = ReadInt(Stream)
		Data   = ReadByte(Stream)
		IP     = UDPMsgIP(Stream)
		Port   = UDPMsgPort(Stream)	
				
		bMsgIsNew = True ' ACK is always new
							
		If bMsgIsNew = True Then
			Select Data
				
				Case NET_ACK                  ' ACK PACKET
				RemoveNetObj ReadInt(Stream)  ' Remove Reliable Packet (ACK has been received)
				tmpConn = 1                   ' We have connected!
				
			End Select	
		EndIf
	EndIf
	
	For n:NetObj = EachIn netList; n.Handle; Next ' Handle reliable packets
	
	If MilliSecs() >= timeOutTime + 2500 Then tmpConn = 2 ' No response received in 2.5 seconds, bail
	Flip; Cls
Until tmpConn <> 0
SetClsColor 92, 148, 252 ' Restore Blue Sky

For n:NetObj = EachIn netList; netList.Remove n ; Next ' Clear packets

If tmpConn = 1 Then bConn = True Else bConn = False

End Function

' // NetDisconnect closes net connections
Function NetDisconnect()

NetList.AddLast netObj.Create(2) ' Send Reliable Quit Attempt

Local tmpConn = 0

SetClsColor 0, 0, 0
Repeat
	DrawText "Disconnecting...", 5, 5
	
	If RecvUDPMsg(Stream) Then ' A UDP packet has been received
		RecvID = ReadInt(Stream)
		Data   = ReadByte(Stream)
		IP     = UDPMsgIP(Stream)
		Port   = UDPMsgPort(Stream)	
				
		bMsgIsNew = True ' ACK is always new
							
		If bMsgIsNew = True Then
			Select Data
				
				Case NET_ACK                  ' ACK PACKET
				RemoveNetObj ReadInt(Stream)  ' Remove Reliable Packet (ACK has been received)
				tmpConn = 1                   ' We have disconnected
				
			End Select	
		EndIf
	EndIf
	
	For n:NetObj = EachIn netList; n.Handle; Next ' Handle reliable packets
	
	Flip; Cls
Until tmpConn <> 0

For n:NetObj = EachIn netList; netList.Remove n ; Next ' Clear packets
bConn = False
CloseStream Stream
Stream = Null

End Function
These last couple of pertinent functions are what begins and closes the network connection. NetConnect adds a single reliable join packet to our NetList, then loops until a response is received, or the TimeOutTime timer is tripped. This means we will resend the join packet for roughly two seconds before giving up. NetDisconnect does basically the same thing, except that it sends a NET_QUIT packet and waits for a response. As aforementioned, the quit packet is never dropped, so we loop continually until a reply is received.

All of the important functions have been covered! Now, go out and make that MMORPG you always wanted! ;)





+ IV.Junk

What remains are basic functions that handle aspects of the program not directly related to networking. The commenting in each function should explain its purpose.


Function ClearLastRecv()

	For i = 0 To 31 
		lastRecv[i] = -1  ' Clear duplicate array
	Next

End Function

' // Draws text at the top of the screen
Function RenderHUD()
	If bHost = False Then ' Client
		If bConn = False ' Not connected
			DrawText "Press 'C' to connect to " + ServerIP$, 5, 5
		Else
			If bConn = True Then
				DrawText "Ping: " + myPing + "ms", 5, 5
			Else
				DrawText "Player disconnected", 5, 5
			EndIf
		EndIf
	Else
		If bConn = False ' Not connected
			DrawText "Waiting for Luigi to join...", 5, 5
		Else
			If bConn = True Then
				DrawText "Ping: " + myPing + "ms", 5, 5
			Else
				DrawText "Player disconnected", 5, 5
			EndIf
		EndIf			
	EndIf

End Function

' // WindowMode function centers our window
Function WindowMode()

	tmpClass$ = "BBDX7Device Window Class"
	Local hWnd% = FindWindow(tmpClass$, "Multiplayer Test")

	Local desk_hWnd% = GetDesktopWindow(), l:lpRect = New lpRECT
	GetWindowRect desk_hWnd, l:lpRECT 

	SetWindowPos hWnd, -2, (l.r / 2) - (640 / 2), (l.b / 2) - (480 / 2), 0, 0, 1
	l:lpRECT = Null

End Function





Enjoyed the tutorial? Comments or questions?