Here's a more advanced version. This is actually pretty fun once you get into it, because you can design your own protocols as you see fit. This handles chat, whisper, kicking, banning, recurring names, and name changes:
SuperStrict
Private
Const ENET_PACKET_FLAG_UNSEQUENCED:Int=2
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_DISCONNECT
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_JOINRESPONSE:Int=6
Const NETWORK_CHAT:Int=7
Const NETWORK_LEAVEGAME:Int=8
Const NETWORK_CHANGENAMEREQUEST:Int=9
Const NETWORK_CHANGENAMERESPONSE:Int=10
Const NETWORK_PLAYERJOINED:Int=11
Const SEND_RELIABLE:Int=ENET_PACKET_FLAG_RELIABLE
Const SEND_UNSEQUENCED:Int=ENET_PACKET_FLAG_UNSEQUENCED
Type TServer Extends TNetworkNode
Const maxplayers:Int=64
Field clients:TList=New TList
Field callback(server:TServer,client:TClient,id:Int,packet:TPacket)
Field bannedips:Int[]
Field clientmap:TMap=New TMap
Method Delete()
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 FindClientByName:TClient(name:String)
Return TClient(clientmap.valueforkey(name))
EndMethod
Method FindClientByPeer:TClient(peer:Byte Ptr)
Local client:TClient
For client=EachIn clients
If client.enetpeer=peer Return client
Next
EndMethod
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_JOINREQUEST
If client Disconnect(client,1)
client=TClient.Find(enet_peer_ip(enetpeer),enet_peer_port(enetpeer))
client.enetpeer=enetpeer
If IPBanned(client.ip)
Disconnect(client,0)
issilent=1
Else
client.name=packet.ReadLine()
If Not FindClientByName(client.name)
clientmap.insert(client.name,client)
clients.AddLast(client)
Local responsepacket:TPacket=New TPacket
responsepacket.WriteByte(1)
responsepacket.WriteByte(clients.count())
Local peer:TClient
For peer=EachIn clients
responsepacket.WriteLine(peer.name)
Next
Send(client,NETWORK_JOINRESPONSE,responsepacket,SEND_RELIABLE)
id=NETWORK_PLAYERJOINED
Else
packet=New TPacket
packet.WriteByte(0)'no, you can't joint
packet.WriteByte(1)'reason: name already taken
Send(client,NETWORK_JOINRESPONSE,packet,SEND_RELIABLE)
'Disconnect(client,0)
issilent=1
EndIf
EndIf
Case NETWORK_CHANGENAMEREQUEST
Local name:String=packet.ReadLine()
packet=New TPacket
If client
If FindClientByName(name)<>Null And client.name<>name
packet.WriteByte(0)
send(client,NETWORK_CHANGENAMERESPONSE,packet,SEND_RELIABLE)
issilent=True
Else
packet.WriteByte(1)
packet.WriteLine(name)
send(client,NETWORK_CHANGENAMERESPONSE,packet,SEND_RELIABLE)
clientmap.remove(client.name)
client.name=name
clientmap.insert(name,client)
EndIf
Else
If FindClientByName(name)=Null
packet.WriteByte(1)
packet.WriteLine(name)
send(client,NETWORK_CHANGENAMERESPONSE,packet,SEND_RELIABLE)
issilent=True
EndIf
EndIf
Case NETWORK_CONNECT
issilent=True
Case NETWORK_DISCONNECT
client=FindClientByPeer(enetpeer)
If client
Disconnect(client,0)
Else
issilent=True
EndIf
Case NETWORK_PINGREQUEST
Send(client,NETWORK_PINGRESPONSE,packet)
issilent=True
Case NETWORK_CHAT
Local relay:TPacket=New TPacket
Local count:Int=packet.ReadByte()
relay.WriteLine(packet.ReadLine())
If count=0
Broadcast(NETWORK_CHAT,relay)
Else
For Local n:Int=1 To count
client=FindClientByName(packet.ReadLine())
If client
Send(client,NETWORK_CHAT,relay)
EndIf
Next
EndIf
issilent=True
EndSelect
If Not issilent
If callback
If packet packet.seek(0)
callback(Self,client,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:Int(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
Local result:Int=1
For Local client:TClient=EachIn clients
If Not Send(client,id,packet,flags,channel) result=0
Next
Return result
Rem
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)
EndRem
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
clients.remove(client)
If Not client.enethost
client.link.remove()
EndIf
If clientmap.valueforkey(client.name)=client
clientmap.remove(client.name)
EndIf
EndIf
EndMethod
Method BanIP(ip:Int)
bannedips=bannedips[..bannedips.length+1]
bannedips[bannedips.length-1]=ip
EndMethod
Method IPBanned:Int(ip:Int)
For Local n:Int=0 To bannedips.length-1
If ip=bannedips[n] Return True
Next
Return False
EndMethod
Method Kick(client:TClient)
BanIP(client.ip)
Disconnect(client)
EndMethod
EndType
Type TClient Extends TNetworkNode
Const maxplayers:Int=64
Global list:TList=New TList
Field name:String
Field link:TLink
Field server:TServer
Field connected:Int
Field joined:Int=0
Field callback(client:TClient,id:Int,packet:TPacket)
Function Find:TClient(ip:Int,port:Int)
Local client:TClient
For client=EachIn list
If client.ip=ip And client.port=port Return client
Next
client=New TClient
client.ip=ip
client.port=port
client.link=list.addlast(client)
Return client
EndFunction
Function Create:TClient(ip:Int=0,port:Int=7776)
Local client:TClient=Find(ip,port)
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 SetName(name:String)
If name.length>16 name=name[..16]
If server
Local packet:TPacket=New TPacket
packet.WriteLine(name)
Send(NETWORK_CHANGENAMEREQUEST,packet,SEND_RELIABLE)
Else
Self.name=name
EndIf
EndMethod
Method Connect:Int(ip:Int,port:Int)
Local addr:Byte Ptr
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
server=Null
Return 0
EndIf
Return 1
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
packet=New TPacket
packet.WriteLine(name)
Send(NETWORK_JOINREQUEST,packet)
Case NETWORK_JOINRESPONSE
joined=packet.ReadByte()
Case NETWORK_CHANGENAMERESPONSE
If packet.ReadByte()=1
name=packet.ReadLine()
EndIf
Case NETWORK_PINGRESPONSE
Case NETWORK_CHAT
EndSelect
If Not issilent
If callback
If packet packet.seek(0)
callback(Self,id,packet)
EndIf
EndIf
EndMethod
Method Join()
If Not joined
Local packet:TPacket=New TPacket
packet.WriteLine(name)
Send(NETWORK_JOINREQUEST,packet)
EndIf
EndMethod
Method Ping:Int()
Local packet:TPacket=New TPacket
packet.WriteInt(MilliSecs())
Return Send(NETWORK_PINGREQUEST,packet)
EndMethod
Method Say:Int(text:String,recipients:String[]=Null)
Local packet:TPacket=New TPacket
If recipients
packet.WriteByte(recipients.length)
packet.WriteLine(text)
For Local n:Int=0 To recipients.length-1
packet.WriteLine(recipients[n])
Next
Else
packet.WriteByte(0)
packet.WriteLine(text)
EndIf
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.name="Steve"
client2.name="Bob"
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.SetName("Barney")
client1.Disconnect()
'client2.Ping()
client2.Say("Hello!")
EndIf
Forever
Function ServerCallBack(server:TServer,client:TClient,id:Int,packet:TPacket)
Select id
Case NETWORK_PLAYERJOINED
Print "New player "+packet.ReadLine()+" joined."
Case NETWORK_DISCONNECT
If client
Print client.name+" disconnected."
Else
Print "Unknown disconnection"
Print server.clients.count()+" players"
EndIf
Case NETWORK_CONNECT
Print "New client connected."
EndSelect
EndFunction
Function ClientCallBack(client:TClient,id:Int,packet:TPacket)
Select id
Case NETWORK_CHANGENAMERESPONSE
If packet.ReadByte()=1
Print "Changed name to "+packet.ReadLine()
Else
Print "Name change rejected."
EndIf
Case NETWORK_JOINRESPONSE
If packet.ReadByte()=1
Print "Joined game"
Local count:Int=packet.ReadByte()
Print count+" players"
For Local n:Int=1 To count
Print n+". "+packet.ReadLine()
Next
Else
Print "Rejected from game"
Select packet.ReadByte()
Case 1
Print "Name already in use"
Default
Print "Unknown reason"
EndSelect
EndIf
Case NETWORK_DISCONNECT
Print "Disconnected from server."
Case NETWORK_CONNECT
Print "Connected to server."
Case NETWORK_CHAT
Print "Says: "+packet.ReadLine()
Case NETWORK_PINGRESPONSE
Print "Ping = "+(MilliSecs()-packet.ReadInt())
EndSelect
EndFunction