multiplayer -- what?

BlitzMax Forums/BlitzMax Programming/multiplayer -- what?

It took me a while to figure it out too. Once you get your head around it though, it's easy to play with. Try this demo.

note: you'll have to change the image.

Strict


Const NET_TYPE 		= 1
Const NET_TXT		= 2
Const NET_POSITION 	= 3
Const NET_HEALTH	= 4
Const NET_NAME		= 5
Const NET_MODE		= 6
' ETC, ETC


Graphics 640,480,0



Global localPlayer:TPlayer
Global Game:TGame=New TGame
Global net:TNetwork=New TNetwork

Global txtList:TList = CreateList()




localPlayer=TPlayer.createLocalPlayer("local","happy")





While KeyHit(KEY_ESCAPE)=0
	Cls
		WaitTimer(game.timer)
		
		' are we connected at all?
		' no... then ask if you want to host or join
		If net.connectionType=0
			DrawText "1. Host",5,5
			DrawText "2. Join",5,15
			DrawText "3. Quit",5,25			
			If KeyHit(KEY_1) net.StartHost();localPlayer.changeName("local")
			If KeyHit(KEY_2) net.connectToHost("127.0.0.1");localPlayer.changeName("remote")
			If KeyHit(KEY_3) Exit
		EndIf
	
		If KeyHit(KEY_M) net.sendAMessage("chat","hello there")
	
		game.update()
	
		DrawTxt() ' output our messages to see progress
	
	
		DrawText "Players: "+game.objects.count()+" | M = send message, < and > = move, 1,2,3 = change modes",0,GraphicsHeight()-20
	Flip	
	Delay 1
Wend

End














Type TGame
	Field objects:TList=CreateList()	
	Field timer:TTimer=CreateTimer(30)
	
	Method update()
		' update all players, whether local or remote
		For Local i:TPlayer=EachIn objects
			i.update()
		Next		
		net.update()		
	End Method
	
	Method removePlayer(p:Tplayer)
		' to be honest, I'm just guessing about this one.
		' since GnetObjects can only be closed by the creator
		' we have to close it at some point.
		' check to see if it was created locally
		' then destroy it so it can be synced across the network.
		If p.g
			If GNetObjectLocal(p.g) CloseGNetObject(p.g)
		EndIf		
		
		' and remove the player so the garbage collector takes care of it
		ListRemove(objects,p)
	End Method
	
	Method getPlayerFromGNetObj:Tplayer(f:TGNetObject)
		' find a player from its gnetObject, g
		For Local i:TPlayer=EachIn objects
			If i.g=f Return i
		Next		
	End Method
	
	Method parse$(in$,de$,se)
		Local m$=""
		Local i=0
		For Local x=1 To Len(in)
			Local v$=Mid(in,x,1)
			If v=de i=i+1
			If se=i And v<>de m$=m+v
		Next
		Return m
	End Method	
	
End Type

Type TPlayer
	Field name$,mode$,health
	Field g:TGnetObject
	Field x,y
	Field image:TImage

	Function createLocalPlayer:Tplayer(name$,mode$)
		Local i:Tplayer=New TPlayer		
		i.name=name
		i.mode=mode
		i.image=LoadImage("player.png")
		i.health=Rand(1,100)
		i.x=Rand(0,GraphicsWidth())
		i.y=Rand(0,GraphicsHeight())
		i.g=CreateGNetObject(net.host) ' create a local GNetObject
		' now, the magic happens in GNetSync because that creates the object on every computer
		' connected to the host automatically. It's possible to see which objects are
		
		' we have to set them ALL here otherwise you'll receive an assert failure for some reason.	
		SetGNetString i.g,NET_TYPE, "player"				
		SetGNetString i.g,NET_NAME, i.name
		SetGNetString i.g,NET_MODE, i.mode
		SetGNetInt i.g,NET_HEALTH, i.health
		SetGNetString i.g,NET_POSITION, i.x+"|"+i.y ' x|y|angle... we'll pack this later
		
		ListAddLast(game.objects,i)			
		Return i
	End Function

	Method changeName(f$)
		name=f
		SetGNetString g,NET_NAME, name
	End Method

	Method changeMode(f$)
		mode=f
		SetGNetString g,NET_MODE, mode		
	End Method

	Method update()
		' you can tell whether the player is yours (local) or not by 
		' checking agains the localPlayer we created at the beginning
		' although there are probably better ways of doing it
		
		If Self=localPlayer
			DrawText name+" "+mode,x,y-20
			DrawImage image,x,y
			
			' update position here
			' you'll want to limit this, depending on whether 
			' the x and y have changed recently.
						
			SetGNetString(g,NET_POSITION,x+"|"+y)
			SetGNetInt(g,NET_HEALTH,health)
			
			' once you've called these, GnetSync will automagically sync other computers
			' with the variable changes.
			
			If KeyDown(KEY_LEFT) x:-1
			If KeyDown(KEY_RIGHT) x:+1
			
			If KeyHit(KEY_1) changeMode("unhappy")
			If KeyHit(KEY_2) changeMode("happy")
			If KeyHit(KEY_3) changeMode("something else")
					
		Else
			' it's another, remote player
			' we don't  need to decode the x and y, it's done for us
			' in the network.update() bit. see below.
			
			DrawImage image,x,y
			DrawText name+" "+mode,x,y-20
		EndIf
	End Method	
	
	Method toString$()
		Return "player - "+name+" mode: "+mode+" health: "+health
	End Method
End Type



Type TNetwork	
	Field host:TGNetHost = CreateGNetHost()	' create our local TGNetHost object
	Field port = 8086, timeout = 5000
	Field LocalIP$
	Field connected, ping, lastPing, currentMs, lastMs, pingTimer, displayPing, restartHost
	
	Const CONNECTION_HOST		= 1
	Const CONNECTION_CLIENT		= -1
	Field connectionType

	' you can use this to determine whether WE are the host or a CLIENT
	Method isHost()
		If connectionType=CONNECTION_HOST Return True
		Return False
	End Method

	Method sendAMessage(t$,txt$)
		' create a GNetMessage
		' I see these as temporary GnetObjects.
		' we can use them to send chat, etc
		'
		Local i:TGNetObject=CreateGNetMessage(host)
		SetGNetString i,NET_TYPE,t
		SetGNetString i,NET_TXT,txt
		
		' send to all players
		For Local j:TPlayer=EachIn game.objects
			SendGNetMessage(i,j.g)
		Next
	End Method



	Method update()
			
			GNetSync host ' send to other instance & get updates
			
			
			' get TNetMessages sent. this can be chat, etc
			Local msgs:TList=GNetMessages(host)
			
			' now cycle throgh all the messages received
			For Local i:TGnetObject = EachIn msgs
				addtxt("new object: "+i.toString(),255,255,0)
				Select GetGNetString(i,NET_TYPE)
					Case "chat" 
						AddTxt("message received",255,0,0)
						AddTxt("message: "+GetGNetString(i,NET_TXT),255,0,0)		
				End Select	
			Next		
			
			' get all the GnetObjects created during our session using the GNET_CREATED flag
			Local joined:TList=GNetObjects(host, GNET_CREATED)
			
			' this represents objects created. so, depending on their type, it might
			' be a player or a torpedo, or anything.
			' in my example, players have a TNetObject called g, player.g
			' so, we check to see whether a player already exists with this gNetObject
			' and if not, create a remotePlayer type and assign its g as the one we received.
			
			For Local i:TGNetObject = EachIn joined
				If GetGNetString(i,NET_TYPE) = "player"				
					
					' this is my way of seeing whether a player with this GNetObject already exists.
					If game.getPlayerFromGNetObj(i)=Null			
					
						' only create a player is it doesn't exist already
						addtxt "new player joining...",0,255,0					
						Local p:TPlayer=New TPlayer
						
						p.g=i	' assign its gnetObject we got from the joined list.
						
						' now get all its variables					
						p.image=LoadImage("player.png")
						
						p.mode=GetGNetString(i,NET_MODE)
						p.name=GetGNetString(i,NET_NAME)
						p.health=GetGNetInt(i,NET_HEALTH)
						Local loc$=GetGNetString(i,NET_POSITION) ' x,y',angle
						p.x=		Int(game.parse(loc,"|",0))
						p.y=		Int(game.parse(loc,"|",1))

						' and add it to the game to get updated
						ListAddLast(game.objects,p)
						
						addtxt p.name+" joined successfully",0,255,0
					EndIf					
				EndIf	
			Next
			
			' as well as GNET_JOINED and GNET_MODIFIED, we have
			' GNET_CLOSED which gives us a list of all the objects that have been
			' destroyed. Let's say for example that we leave and destroy our localPlayer.g
			' object. GnetSync will tell every other computer that that g object
			' has been destroyed. So we should destroy players linked to that TGnetObject
			' too. 
			
			Local leftGame:TList=GNetObjects(host, GNET_CLOSED)
			
			For Local i:TGNetObject = EachIn leftGame
			
				If GetGNetString(i,NET_TYPE) = "player"
					addtxt "player leaving...",255,255,0
					
					' find the player associaed with that g, TGnetObject
					Local p:TPlayer=game.getPlayerFromGNetObj(i)
					
					If p game.removePlayer(p)
				EndIf			
			Next			
			
			' we have GNET_ALL too, but that's not particularly helpful. we make
			' a list of all the modified objects to check whether to set player's
			' variables.
			
			Local objList:TList = GNetObjects(host, GNET_MODIFIED)	
			
			' cycle through the modified Gnetobjects...
			For Local i:TGNetObject  = EachIn objList
			
				' get the player this i, TGnetObject is referring to.
				Local o:TPlayer=game.getPlayerFromGNetObj(i)
				If o
					Local p:Tplayer=Tplayer(o)
					' now unpack their info.
					
					Local loc$=GetGNetString(p.g,NET_POSITION)
					p.x=Int(game.parse(loc,"|",0))
					p.y=Int(game.parse(loc,"|",1))
					p.mode=GetGNetString(p.g,NET_MODE)
					p.health=GetGNetInt(p.g,NET_HEALTH)
					
				EndIf				
			Next		
	End Method
	
		
	Method StartHost()
		addtxt "hosting"
		connectionType = CONNECTION_HOST
		If Not GNetListen(host, port) Then
			connectionType = 0
			Return 0
		Else
			addtxt "success!"
		EndIf	
		Return True	
	End Method
	
	Method connectToHost(ip$)
		addtxt "joining"
		connectionType = CONNECTION_CLIENT
		If GNetConnect(host, ip$, port, timeout)
			Return 1
			addtxt "success!"
		Else	
			addtxt "failed to join"
			connectionType = 0
			Return 0
		EndIf
	End Method		
	
	Method New()
		Local ip_array[] = HostIps("")
		Local ip = HostIp("")		
		localIp$ = DottedIP(ip_array[0])	
	End Method
	
	Method Delete()		
		disconnect()
	End Method
	
	Method disconnect()
		GNetSync host
		clearConnection()		
		restartHost = 5
	End Method	
	
	Method clearConnection()
		connectionType = 0;connected = False;ping = 0;lastPing = 0;displayPing = 0
	End Method	
End Type



Type Txt
	Field content$
	Field red%, green%, blue%
End Type

Function AddTxt(content$, red% = 255, green% = 255, blue% = 255, limit = 10)
	Local t:Txt = New Txt
	t.content = content
	t.red = red
	t.green = green
	t.blue = blue
	txtList.AddLast t
	DebugLog content
	If txtList.Count() > limit Then txtList.RemoveFirst()
End Function

Function DrawTxt(x% = 20, y% = 20, yStep% = 20)
	For Local t:Txt = EachIn txtList
		SetColor t.red, t.green, t.blue
		DrawText t.content,x,y
		y :+ yStep
	Next 
	SetColor 255,255,255
End Function