Take a look at this. Instead of a host/peer design, this is a more intuitive server/client design. If you are hosting a game, you create a server. If you want to play in the game yourself, create a client. Here, I created two clients. You could create a client for each bot and run them through the network, or just one client for yourself. You'd probably want to always run the game through the network, even if it is single-player, so that you have a uniform design for single and multi player modes.
The messages are turned into a form with an ID and a packet, which is a stream of any length. There are built-in message IDs. Some are handled entirely silently. For example, when the server receives a chat message, it just broadcasts the data to all clients, without the user having to do anything. Synced entities would behave this way as well.
The user's interaction with the system comes in the form of a callback. The callback can be used to do stuff when messages are received. In the example below, the client callback is used to print the ping and to print any chat messages that are sent.
SuperStrict
Private
Function enet_host_port:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[1]
Local port:Int=(Short Ptr peer)[4]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return port
EndFunction
Function enet_host_ip:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[1]
Local port:Int=(Short Ptr peer)[4]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return ip
EndFunction
Function enet_peer_port:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[3]
Local port:Int=(Short Ptr peer)[8]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return port
EndFunction
Function enet_peer_ip:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[3]
Local port:Int=(Short Ptr peer)[8]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return ip
EndFunction
Type TNetworkNode
Field port:Int
Field ip:Int
Field enethost:Byte Ptr
Field enetpeer:Byte Ptr
Method Delete()
If enethost
enet_host_destroy(enethost)
enethost=Null
EndIf
EndMethod
Method Update(callback(client:TClient,id:Int,packet:TPacket)=Null)
Local ip:Int,port:Int,client:TClient,ev:ENetEvent=New ENetEvent,id:Byte,packet:TPacket
If Not Self.enethost RuntimeError "Can't update a remote server."
Repeat
If enet_host_service(Self.enethost,ev,0)
Select ev.event
Case ENET_EVENT_TYPE_CONNECT
id=NETWORK_CONNECT
Case ENET_EVENT_TYPE_CONNECT
id=NETWORK_DISCONNECT
Case ENET_EVENT_TYPE_RECEIVE
Local size:Int=enet_packet_size(ev.packet)
Local data:Byte[size]
MemCopy(Varptr id,enet_packet_data(ev.packet),1)
If size>1
packet=New TPacket
packet._bank.resize(size-1)
MemCopy(packet._bank.buf(),enet_packet_data(ev.packet)+1,size-1)
EndIf
Default
Continue
EndSelect
EvaluateEvent(id,packet,ev.peer)
Else
Exit
EndIf
Forever
EndMethod
Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr) Abstract
EndType
Public
Const NETWORK_CONNECT:Int=1
Const NETWORK_DISCONNECT:Int=2
Const NETWORK_PINGREQUEST:Int=3
Const NETWORK_PINGRESPONSE:Int=4
Const NETWORK_JOINREQUEST:Int=5
Const NETWORK_JOINREPLY:Int=6
Const NETWORK_CHAT:Int=7
Type TServer Extends TNetworkNode
Const maxplayers:Int=64
Field clients:TList=New TList
Field callback(server:TServer,id:Int,packet:TPacket)
Method Delete()
If enetpeer
enetpeer=Null
EndIf
If enethost
enet_host_destroy(enethost)
enethost=Null
EndIf
EndMethod
Function Create:TServer(ip:Int=0,port:Int=7777)
Local server:TServer=New TServer,addr:Byte Ptr
If ip
server.ip=ip
Else
server.ip=ENET_HOST_ANY
EndIf
server.port=port
If server.port<>0 Or server.ip<>ENET_HOST_ANY
addr=enet_address_create(server.ip,server.port)
EndIf
server.enethost=enet_host_create(addr,maxplayers,0,0)
If addr
enet_address_destroy(addr)
EndIf
If Not server.enethost
Return Null
EndIf
Return server
EndFunction
Method FindClient:TClient(ip:Int,port:Int)
Local client:TClient
For client=EachIn clients
If client.ip=ip And client.port=port Return client
Next
EndMethod
Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr)
Local client:TClient
Local issilent:Int=False
client=FindClient(enet_peer_ip(enetpeer),enet_peer_port(enetpeer))
Select id
Case NETWORK_CONNECT
If client Disconnect(client,True)
client=New TClient
client.enetpeer=enetpeer
client.ip=enet_peer_ip(enetpeer)
client.port=enet_peer_port(enetpeer)
client.link=clients.AddLast(client)
Case NETWORK_PINGREQUEST
Send(client,NETWORK_PINGRESPONSE,packet)
issilent=True
Case NETWORK_CHAT
Broadcast(id,packet)
issilent=True
EndSelect
If Not issilent
If callback
callback(Self,id,packet)
EndIf
EndIf
EndMethod
Method Send:Int(client:TClient,id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
Local enetpacket:Byte Ptr
Local result:Int
If Not client.enetpeer RuntimeError "Can't send to local client."
Local data:Byte[]
If packet
If packet._bank.size()=0 packet=Null
EndIf
If packet
data=New Byte[packet._bank.size()+1]
MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
Else
data=New Byte[1]
EndIf
data[0]=id
enetpacket=enet_packet_create(data,data.length,flags)
result=(enet_peer_send(client.enetpeer,channel,enetpacket)=0)
Return result
EndMethod
Method Broadcast(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
Local enetpacket:Byte Ptr
Local result:Int
Local data:Byte[]
If packet
If packet._bank.size()=0 packet=Null
EndIf
If packet
data=New Byte[packet._bank.size()+1]
MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
Else
data=New Byte[1]
EndIf
data[0]=id
enetpacket=enet_packet_create(data,data.length,flags)
enet_host_broadcast(Self.enethost,channel,enetpacket)
EndMethod
Method Disconnect(client:TClient,force:Int=False)
If client.enetpeer
If force
enet_peer_reset(client.enetpeer)
Else
enet_peer_disconnect(client.enetpeer)
EndIf
client.link.remove()
EndIf
EndMethod
EndType
Type TClient Extends TNetworkNode
Const maxplayers:Int=64
Field link:TLink
Field server:TServer
Field connected:Int
Field callback(client:TClient,id:Int,packet:TPacket)
Function Create:TClient(ip:Int=0,port:Int=7776)
Local client:TClient=New TClient
Local addr:Byte Ptr
If ip
client.ip=ip
Else
client.ip=ENET_HOST_ANY
EndIf
client.port=port
If client.port<>0 Or client.ip<>ENET_HOST_ANY addr=enet_address_create(client.ip,client.port)
client.enethost=enet_host_create(addr,maxplayers,0,0)
If addr enet_address_destroy(addr)
If Not client.enethost
Return Null
EndIf
Return client
EndFunction
Method Disconnect(force:Int=False)
If server
If force
enet_peer_reset(server.enetpeer)
Else
enet_peer_disconnect(server.enetpeer)
EndIf
server=Null
EndIf
EndMethod
Method Connect:TServer(ip:Int,port:Int)
Local addr:Byte Ptr,server:TServer
If Not Self.enethost RuntimeError "Remote client cannot connect to server."
If server Disconnect()
server=New TServer
server.ip=ip
server.port=port
addr=enet_address_create(server.ip,server.port)
server.enetpeer=enet_host_connect(Self.enethost,addr,1)
enet_address_destroy(addr)
If server.enetpeer=Null Return Null
Self.server=server
Return server
EndMethod
Method Send:Int(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
Local enetpacket:Byte Ptr
Local result:Int
If Not connected Return 0
If Not server RuntimeError "Client is not connected."
Local data:Byte[]
If packet
If packet._bank.size()=0 packet=Null
EndIf
If packet
data=New Byte[packet._bank.size()+1]
MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
Else
data=New Byte[1]
EndIf
data[0]=id
enetpacket=enet_packet_create(data,data.length,flags)
result=(enet_peer_send(server.enetpeer,channel,enetpacket)=0)
Return result
EndMethod
Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr)
Local issilent:Int=False
Select id
Case NETWORK_CONNECT
Self.connected=True
Case NETWORK_PINGRESPONSE
Case NETWORK_CHAT
EndSelect
If Not issilent
If callback
callback(Self,id,packet)
EndIf
EndIf
EndMethod
Method Ping:Int()
Local packet:TPacket=New TPacket
packet.WriteInt(MilliSecs())
Return Send(NETWORK_PINGREQUEST,packet)
EndMethod
Method Say:Int(text:String)
Local packet:TPacket=New TPacket
packet.WriteLine(text)
Return Send(NETWORK_CHAT,packet)
EndMethod
EndType
Type TPacket Extends TBankStream
Method New()
_bank=New TBank
EndMethod
EndType
Function CreatePacket:TPacket()
Local packet:TPacket=New TPacket
Return packet
EndFunction
'----------------------------------------------------------------------------------------------------
Local server:TServer
Local client1:TClient
Local client2:TClient
Local ip:Int=HostIp("127.0.0.1")
server=TServer.Create(ip,7777)
client1=TClient.Create(ip,7776)
client2=TClient.Create(ip,7779)
client1.connect(server.ip,server.port)
client2.connect(server.ip,server.port)
client1.callback=ClientCallBack
client2.callback=ClientCallBack
server.callback=ServerCallBack
Repeat
server.Update()
client1.Update()
client2.Update()
Delay 1
If Rand(100)=1
client1.Ping()
client2.Say("Hello!")
EndIf
Forever
Function ServerCallBack(server:TServer,id:Int,packet:TPacket)
Select id
Case NETWORK_CONNECT
Print "New client connected."
EndSelect
EndFunction
Function ClientCallBack(client:TClient,id:Int,packet:TPacket)
Select id
Case NETWORK_CONNECT
Print "Connected to server."
Case NETWORK_CHAT
Print "Says: "+packet.ReadLine()
Case NETWORK_PINGRESPONSE
Print "Ping = "+(MilliSecs()-packet.ReadInt())
EndSelect
EndFunction