; ; UDP Chat code V2.5 14/06/99 ; ; This code sends and receives UDP data packets like a ; a simple IRC client, and checks wether they have arrived ; at their destination, and allows other copies of this ; program to log in to this one (can act as a server or client). ; ; Written by Anton Reinauer . ; GUI: Alvaro Thompson - And awful hacks ; by me to his nice font sensitive code :-) ; - typical bloody games programmer ;-) ; ; Thanks to Paul Burkey for TCP_Funcs and to Dr. Ercole Spiteri ; for TCP-to-Blitz. ; ; ARexx portname: "UDP_Chat" ; ; Turn overflow errors off in Debugger options, ; and have amigalibs.res resident in compiler options. WBStartup NoCli DEFTYPE .w ResetTimer ;**************************************************************** ;**************** UDP Stuff ************** ;*********** Cut out all this and put it in your game ********** ;**************************************************************** ;**************** UDP Variables and Constants ************** hostname.s="localhost" ; "localhost" default destination host address #NO_CONNECTION=1 ; 0 - are we connected, or have we been connected to the Internet since bootup? 1- No #COMMS_DEBUG=1 ; 1 - 1 for debug info and multiple local copies #MESSAGE_ARRAY_SIZE=256 ; 256 #INTERNAL_MESSAGE_SIZE=30 ;30 GAME_NAME.s="UDP_Chat" ; "UDP_Chat" Game name (for login) max_wait=250 ; 250 Time to wait for an acknowledgement (frames) number_resends=5 ; 5 Number of resends to wait, before disconnecting player lag_allowance=5 ; 5 Number of frames to add to the current lag, when waiting for an out-of-order packet. max_number_players=8 ; 8 #ACKNOWLEDGE_PACKET=3 #OFFLINE=0 #ONLINE=1 #ATTEMPTING_TO_CONNECT=1 #SERVER=2 #CLIENT=3 #DISCONNECTED=1 #CONNECTED=2 #CONNECTION_REJECTED=3 #SYSTEM_MESSAGE=4 ; System message #HIGH=3 ; High priority Security Message or Comms Debug info #MEDIUM=2 ; Medium " " #LOW=1 ; Low " " #SERVER_NUMBER=1 #GAME_CLOSING_TIMEOUT=1500 #DISCONNECT_TIMEOUT=1500 #CLIENT_SERVER=0 #PEER_TO_PEER=1 packet_number.l=0 online=#OFFLINE my_number=0 last_message_number.l=0 free_message.l=0 sock.l=-1 security_level=#LOW ; level of security warnings (normally #LOW- show #LOW and higher ie: all) free_security_message=0 next_security_message=0 CNIF #COMMS_DEBUG=1 comms_debug_level=#LOW ; level of debug info (normally #LOW- show #LOW and higher ie: all) free_debug_message=0 next_debug_message=0 CEND tcp_stack=False disconnect_timeout=0 ;*************************** UDP Dims ************************* .Dims INCLUDE "Net_Protocol_Header.bb2" ; Net protocol constants INCLUDE "UDPHeader.bb2" ;Standard TCP/UDP library structures and UDP Funcs Newtypes Dim messages.message_status(#MESSAGE_ARRAY_SIZE) Dim host.sockaddrin(max_number_players), hostlen.l(max_number_players) Dim players.player_info(max_number_players) Dim player_honesty(max_number_players,max_number_players) Dim unreliable_packet_out.l(max_number_players) Dim unreliable_packet_in.l(max_number_players) Dim comms_debug_message.system_message(#INTERNAL_MESSAGE_SIZE) Dim security_message.system_message(#INTERNAL_MESSAGE_SIZE) DEFTYPE .sockaddrin temphost For a=1 To max_number_players ; this array dimension is for the player number who is claiming another is offline For b=1 To max_number_players ; this dimension is for the player number who is claimed to be offline player_honesty(a,b)=False ; initialise array Next Next messages(free_message)\ack=True ;************************ UDP Funcs *********************** ; These are all specific functions for your game to communicate ; over the internet or by modem/null-modem. .Security_Warning Statement Security_Warning{text$,level} SHARED security_level ; security_level= 0 none, 1 all warnings, 2 medium and severe warnings and 3 only severe warnings SHARED security_message(),free_security_message If level>=security_level ; only print out security_level warnings if at set security_level level or higher If level<#SYSTEM_MESSAGE text$="Warning: " + text$ EndIf If free_security_message <= #INTERNAL_MESSAGE_SIZE ; if array not full security_message(free_security_message)\message=text$,level ; add debug message to array free_security_message+1 ; up index pointer Else free_security_message+1 EndIf EndIf End Statement .Read_Security_Warning Function.s Read_Security_Warning{} SHARED security_message(),free_security_message SHARED next_security_message If next_security_message=free_security_message OR next_security_message>#INTERNAL_MESSAGE_SIZE If free_security_message>#INTERNAL_MESSAGE_SIZE+1 next_security_message=0 free_security_message=0 lost_messages=free_security_message-#INTERNAL_MESSAGE_SIZE-1 Function Return "Warning: System message array full- have lost " + Str$(lost_messages) + " message(s)!" Else next_security_message=0 free_security_message=0 Function Return "" EndIf Else next_security_message+1 Function Return security_message(next_security_message-1)\message EndIf End Function CNIF #COMMS_DEBUG=1 .Comms_Debug Statement Comms_Debug{text$,level} SHARED comms_debug_level ; comms_debug= 0 none, 1 all info, 2 medium and high priority info and 3 only high priority info SHARED comms_debug_message(),free_debug_message If level>=comms_debug_level ; only print out debug info if at set debug level or higher If free_debug_message < #INTERNAL_MESSAGE_SIZE ; if array not full comms_debug_message(free_debug_message)\message=text$,level ; add debug message to array free_debug_message+1 ; up index pointer Else If free_debug_message=#INTERNAL_MESSAGE_SIZE ; if array full- send error message comms_debug_message(free_debug_message)\message="Comms debug message array full- may lose next message!",#HIGH free_debug_message+1 EndIf EndIf EndIf End Statement .Read_Comms_Debug_Messages Function.s Read_Comms_Debug_Messages{} SHARED comms_debug_message(),free_debug_message SHARED next_debug_message If next_debug_message=free_debug_message OR next_debug_message>#INTERNAL_MESSAGE_SIZE If free_debug_message>#INTERNAL_MESSAGE_SIZE+1 next_debug_message=0 free_debug_message=0 lost_messages=free_debug_message-#INTERNAL_MESSAGE_SIZE-1 Function Return "Warning: Comms debug message array full- have lost " + Str$(lost_messages) + " message(s)!" Else next_debug_message=0 free_debug_message=0 Function Return "" EndIf Else next_debug_message+1 Function Return comms_debug_message(next_debug_message-1)\message EndIf End Function CEND INCLUDE "UDPFuncs.bb2" ; basic UDP socket functions .Dim_Message_Arrays Statement Dim_Message_Arrays{new_max_number_players} SHARED online,max_number_players SHARED host(),hostlen(),players(),player_honesty() SHARED unreliable_packet_out.l(),unreliable_packet_in.l() max_number_players=new_max_number_players If online>#OFFLINE Dim host.sockaddrin(max_number_players), hostlen.l(max_number_players) Dim players.player_info(max_number_players) Dim player_honesty(max_number_players,max_number_players) Dim unreliable_packet_out.l(max_number_players) Dim unreliable_packet_in.l(max_number_players) For a=1 To max_number_players ; this array dimension is for the player number who is claiming another is offline For b=1 To max_number_players ; this dimension is for the player number who is claimed to be offline player_honesty(a,b)=False ; initialise array Next Next EndIf End Statement .Get_Packet_Source ; Check if packet has come from player already logged on. Function.w Get_Packet_Source{} SHARED host(),temphost,max_number_players i=0 exit=0 Repeat ; check for each player (hostname and socket) i+1 If host(i)\sin_addr\s_addr=temphost\sin_addr\s_addr AND host(i)\sin_port=temphost\sin_port exit=1 EndIf If i=max_number_players AND exit=0; if at end of players and no match is found i=-1 exit=1 EndIf Until exit=1 Function Return i End Function .Get_Ascii_Address ; build a.b.c.d numerical address from long number Function.s Get_Ascii_Address{address.l} string_address.l=Inet_NtoA_(address) ; get memory address of ASCII string version of host address (a.b.c.d) If string_address=0 Function Return "" EndIf Repeat ; build ASCII host string letter.b=(Peek.b(string_address)) If letter<>0 temp$=temp$+Chr$(letter) EndIf string_address+1 Until letter=0 Function Return temp$ End Function .Send_Reliable_Message ; this message is checked to see if it has arrived, and resent if not Statement Send_Reliable_Message{send_string.s,player,protocol} ; if no confirmation can be made, link is considered lost SHARED messages(),free_message,packet_number.l If protocol=#PEER_TO_PEER ; Send message Peer-To-Peer send_string.s=Mkl$(packet_number) + send_string.s messages(free_message)\number=packet_number,False,Ticks,send_string.s,0,player ; log message in message array WriteUDP{&send_string.s,Len(send_string.s),player} ; send message packet_number+1 ; set packet number to next free packet number (+1) free_message+1 ; set next free message to next one messages(free_message)\ack=True ; stop resends if all messages have been acknowledged If free_message=#MESSAGE_ARRAY_SIZE+1 Then free_message=0 ; if packet number >#MESSAGE_ARRAY_SIZE then wrap back to 0 Else ; Send message Client-Server EndIf End Statement .Send_Unreliable_Message Statement Send_Unreliable_Message{send_string.s,player} SHARED UNRELIABLE_PACKET.s,unreliable_packet_out() unreliable_packet_out(player)+1 send.s=Mkl$(unreliable_packet_out(player)) + UNRELIABLE_PACKET.s + send_string.s + Chr$(0) WriteUDP{&send.s,Len(send.s),player} End Statement .Initialise_Server Function Initialise_Server{localport} ; Set up the game so we can act as Server SHARED online,port_used If online=#OFFLINE If Initialise_UDP{localport} ; bind socket to local port online=#SERVER Function Return True Else Security_Warning{"Error in setting up UDP at Port "+ Str$(port_used),#SYSTEM_MESSAGE} EndIf Else Select online Case #CLIENT Security_Warning{"Already logged on to a Server!",#SYSTEM_MESSAGE} Case #SERVER Security_Warning{"Already acting as a Server!",#SYSTEM_MESSAGE} Case #ATTEMPTING_TO_CONNECT Security_Warning{"Already attempting to log on to a Server!",#SYSTEM_MESSAGE} End Select EndIf Function Return False End Function .Disconnect_From_Game Function Disconnect_From_Game{} ; Remove us from the game, and if we're the Server- close down ; the game and take away our ability to act as Server. SHARED online,players(),max_number_players,my_number SHARED disconnect_timeout SHARED CP_END_GAME.s,CP_REQ_PLAYER_DISCONNECT.s Select online Case #OFFLINE Function Return True Case #SERVER ; if we are Server dummy=0 For i=2 To max_number_players If players(i)\status=#ONLINE ; send to player if they're online send$=CP_END_GAME.s dummy=1 Send_Reliable_Message{send$,i,#PEER_TO_PEER} ; Send string to connected players EndIf Next If dummy=1 Security_Warning{"Telling players Game has ended.",#SYSTEM_MESSAGE} disconnect_timeout=#DISCONNECT_TIMEOUT Function Return False Else online=#OFFLINE Function Return True EndIf Case #CLIENT ; we are client send$=CP_REQ_PLAYER_DISCONNECT.s + Mki$(my_number) Send_Reliable_Message{send$,#SERVER_NUMBER,#PEER_TO_PEER} Security_Warning{"Telling Server we are disconnecting from Game.",#SYSTEM_MESSAGE} disconnect_timeout=#DISCONNECT_TIMEOUT Function Return False Case #ATTEMPTING_TO_CONNECT ; we are client Security_Warning{"Game Connection attempt aborted.",#SYSTEM_MESSAGE} online=#OFFLINE Function Return True End Select End Function .Connect_To_Server Function Connect_to_Server{host.s,port.w} SHARED online, CP_REQ_CONNECT.s, GAME_NAME.s, NET_PROTOCOL_VERSION.s If online=#OFFLINE If Initialise_UDP{port} ; bind socket to local port If Get_Host_By_Name{GTGetString(0,51), GTGetInteger(0,52),1}=True Security_Warning{"Attempting to connect to Server",#SYSTEM_MESSAGE} send$=Mkl$(0) + CP_REQ_CONNECT.s + GAME_NAME.s + Chr$(0) + NET_PROTOCOL_VERSION.s ; "0000" is a Pad for packet number WriteUDP{&send$,Len(send$),1} ; send connection request online=#ATTEMPTING_TO_CONNECT Function Return True Else Function Return False EndIf Else Function Return False EndIf Else Select online Case #CLIENT Security_Warning{"Already logged on to a Server!",#SYSTEM_MESSAGE} Case #SERVER Security_Warning{"Already acting as a Server!",#SYSTEM_MESSAGE} Case #ATTEMPTING_TO_CONNECT Security_Warning{"Already attempting to log on to a Server!",#SYSTEM_MESSAGE} End Select Function Return False EndIf End Function .Find_Next_Message Statement Find_Next_Message{} SHARED last_message_number,messages(),free_message exit=0 Repeat ; find next unacknowledged message in array last_message_number+1 If last_message_number=#MESSAGE_ARRAY_SIZE+1 Then last_message_number=0 If last_message_number=free_message ; if have got to free_message then there are no messages waiting to be acknowledged exit=1 Else If messages(last_message_number)\ack=False ; we've found the next unacknowledged message in the array exit=1 EndIf EndIf Until exit=1 End Statement .Clear_Player_Arrays Statement Clear_Player_Arrays{player} ; clear player's arrays SHARED last_message_number,messages(),free_message,host() SHARED player_honesty(),players(),max_number_players SHARED unreliable_packet_out(),unreliable_packet_in() If messages(last_message_number)\player=player find_next_message=True Else find_next_message=False EndIf a=last_message_number exit=False Repeat ; clear message array If messages(a)\player=player messages(a)\ack=True ; clear unacknowledged messages off message array EndIf If a=free_message Then exit=True a+1 If a=#MESSAGE_ARRAY_SIZE+1 Then a=0 ; if at end of array, jump to beginning Until exit=True If find_next_message=True Find_Next_Message{} EndIf host(player)\sin_addr\s_addr=0 ; clear host address array host(player)\sin_port=0 For a= 1 To max_number_players ; clear player_honesty array player_honesty(player,a)=False ; clear disconnected player's array player_honesty(a,player)=False ; clear other player's arrays who have claimed the disconnected player was offline Next players(player)\disconnect_requested=False ; reset disconnect request item players(player)\status=#OFFLINE ; player's status is offline unreliable_packet_out(player)=0 ; set player's unreliable packet number back to zero unreliable_packet_in(player)=0 End Statement .Disconnect_Player Statement Disconnect_Player{player} ; Disconnect Player from game SHARED players(),online,max_number_players SHARED CP_REP_PLAYER_DISCONNECTED.s,CP_REQ_PLAYER_DISCONNECT.s If online=#SERVER ; If we are the Server dummy=0 Security_Warning{"Player " + Str$(player) + " has disconnected",#SYSTEM_MESSAGE} For i=2 To max_number_players If players(i)\status=#ONLINE ; send to all players if they're online If i <> player ; check to see if any players are left online dummy=1 EndIf send$=CP_REP_PLAYER_DISCONNECTED.s + Mki$(player) ; tell other players Send_Reliable_Message{send$,i,#PEER_TO_PEER} ; Send string to connected players that player is offline EndIf Next If dummy=0 ; if no players are left online CNIF #COMMS_DEBUG=1 Comms_Debug{"All players have disconnected",#MEDIUM} CEND EndIf Clear_Player_Arrays{player} ; clear message array of player's messages Else ; if we're a client, inform Server that a player can't be contacted CNIF #COMMS_DEBUG=1 Comms_Debug{"Telling Server we can't contact player!",#MEDIUM} CEND send$=CP_REQ_PLAYER_DISCONNECT.s + Mki$(player) Send_Reliable_Message{send$,#SERVER_NUMBER,#PEER_TO_PEER} ; Send string to Server EndIf End Statement .Resend_Message ; resend a message Statement Resend_Message{message_number} SHARED messages(),number_resends,online,players() SHARED max_number_players messages(message_number)\timestamp=Ticks messages(message_number)\resends+1 If messages(message_number)\resends>number_resends ; is host not responding? After 5 retries consider link broken. CNIF #COMMS_DEBUG=1 Comms_Debug{"Host not responding to Packet no: " + Str$(message_number),#HIGH} CEND If messages(message_number)\player<>#SERVER_NUMBER ; We've lost contact with a Client If players(messages(message_number)\player)\disconnect_requested=False ; if haven't already sent a Disconnect req Disconnect_Player{messages(message_number)\player} ; send disconnect request to Server players(messages(message_number)\player)\disconnect_requested=True ; don't send any more disconnect requests EndIf ; for that player Else ; we've lost contact with Server- game over! online=#OFFLINE For a= 1 To max_number_players Clear_Player_Arrays{a} Next EndIf Else WriteUDP{&messages(message_number)\message,Len(messages(message_number)\message),messages(message_number)\player} CNIF #COMMS_DEBUG=1 Comms_Debug{"Resent Packet no:" + Str$(message_number),#HIGH} CEND EndIf End Statement .Acknowledge_Packet ; mark message as been sucessfully sent Function Acknowledge_Packet{ack_packet_number,player} SHARED last_message_number,free_message,messages() SHARED packet_number.l,lag_allowance,players() a=last_message_number exit=-2 Repeat If messages(a)\number=ack_packet_number ; find message in sent messages array messages(a)\ack=True ; note it as being received current_time.l=Ticks players(player)\lag=current_time-messages(a)\timestamp ; calculate lag exit=True Else If a=free_message ; in case bad packet number sends it into a loop! It will only check around buffer once. exit=False a$="Warning- Invalid Packet number " + Str$(incoming_packet_number) Security_Warning{ a$ + " from Player " + Str$(player),#MEDIUM} EndIf a+1 EndIf If a=#MESSAGE_ARRAY_SIZE+1 Then a=0 ; if at end of array, jump to beginning Until exit>-2 If exit=True ; only do if packet has valid packet_number If a=last_message_number OR (a=0 AND last_message_number=#MESSAGE_ARRAY_SIZE) ; if last message unacknowledged is Find_Next_Message{} ; acknowledged then find next unacknowledged message Else ; we have lost a packet- resend last packet number! allowable_lag=players(messages(last_message_number)\player)\lag + lag_allowance ; current lag of player If messages(last_message_number)\timestamp-current_time > allowable_lag text$="Received packet " + Str$(ack_packet_number) + " ACK out of order,from P: " + Str$(player) Security_Warning{text$ + " resending last packet",#MEDIUM} ; then resend last message Resend_Message{last_message_number} ; if run out of resends- disconnect player Else text$="Received packet " + Str$(ack_packet_number) + " ACK out of order,from P: " + Str$(player) Security_Warning{text$ + "- no resend yet.",#MEDIUM} EndIf EndIf EndIf Function Return exit End Function .Requested_Connection ; a player is trying to log in to us Statement Requested_Connection{incoming_string.s,player} SHARED sock,players(),host(),hostlen(),GAME_NAME.s SHARED temphost,temphostlen,online,my_number,max_number_players SHARED CP_REP_ACCEPT.s,CP_REP_REJECT.s,CP_REP_PLAYER_INFO.s If player=-1 ; make sure player hasn't already logged in If online=#OFFLINE OR online=#SERVER ; If offline or Server dummy=7 Repeat ; build game name string from incoming request b$=Mid$(incoming_string,dummy,1) If b$<>Chr$(0) c$=c$+b$ EndIf dummy+1 Until b$=Chr$(0) OR dummy=(Len(incoming_string)) If b$=Chr$(0) If c$=GAME_NAME.s ; if request is for correct game protocol=Asc(Right$(incoming_string,1)) If protocol=#NET_PROTOCOL_VERSION ; check wether using the same protocol a$="Connection request from " player=1 exit=0 Repeat ;check if a spare player slot is availiable player+1 If players(player)\status=#OFFLINE exit=1 Else If player=max_number_players exit=1 player=-1 EndIf EndIf Until exit=1 If player>0 ; connection accepted If Get_Host_By_Address{&temphost\sin_addr\s_addr,temphost\sin_port,player}=False Security_Warning{Str$(temphost\sin_addr\s_addr) + "- Host not found!",#SYSTEM_MESSAGE} Statement Return EndIf players(player)\status=#ONLINE my_number=#SERVER_NUMBER players(#SERVER_NUMBER)\status=#ONLINE ; We are now online as Server If Get_Host_By_Address{&host(0)\sin_addr\s_addr,host(0)\sin_port,1}= False ; put our details in Server Slot Security_Warning{Str$(host(0)\sin_addr\s_addr) + "- Host not found!",#SYSTEM_MESSAGE} Statement Return EndIf temp$=Get_Ascii_Address{host(player)\sin_addr\s_addr} a$=a$+temp$ + " Port: " + Str$(host(player)\sin_port) players(player)\ascii_host_string=temp$ ; store it ; build player info string to tell other players of new player send_others$=CP_REP_PLAYER_INFO.s + Mki$(player) + Mkl$(host(player)\sin_addr\s_addr) send_others$=send_others$ + Mki$(host(player)\sin_port) + Chr$(players(player)\player_colour) send_others$=send_others$ + Mkl$(players(player)\score) send_others$=send_others$ + players(player)\name + Chr$(0) + players(player)\nick For i=1 To max_number_players If i<> player AND players(i)\status=#ONLINE If i>1 Send_Reliable_Message{send_others$,i,#PEER_TO_PEER} ; tell others players that new player is online ; and give them new players info. EndIf send$=CP_REP_PLAYER_INFO.s + Mki$(i) + Mkl$(host(i)\sin_addr\s_addr) send$=send$ + Mki$(host(i)\sin_port) + Chr$(players(i)\player_colour) send$=send$ + Mkl$(players(i)\score) send$=send$ + players(i)\name + Chr$(0) + players(i)\nick Send_Reliable_Message{send$,player,#PEER_TO_PEER} ; tell new player of others online ; and give them their info. EndIf Next send$=CP_REP_ACCEPT.s send$=send$ + Mki$(host(0)\sin_port)+ Mki$(player) + Mki$(map) ; send connection accepted back to client online=#SERVER players(player)\disconnect_requested=False Send_Reliable_Message{send$,player,#PEER_TO_PEER} Security_Warning{a$,#SYSTEM_MESSAGE} Statement Return Else a$="Connection Request rejected- no spare player slot!" EndIf Else a$="Connection Request rejected- Incorrect Protocol: "+Str$(protocol) EndIf Else a$="Connection Request rejected- wrong game name: "+b$ EndIf Else a$="Connection Request rejected- Unknown error!" EndIf Else a$="Connection Request rejected- host already logged on as Client" EndIf Else a$="Connection Request rejected- host already logged on to Server." EndIf send$=Mkl$(0) + CP_REP_REJECT.s + a$ ; send connection rejected back with reason WriteUDP{&send$,Len(send$),0} ; reply to sender Security_Warning{a$,#MEDIUM} End Statement .Decode_Packet ; Decode incoming packet and act accordingly Function Decode_Packet{} SHARED players(),host(),temphost,online,unreliable_packet_in() SHARED my_number,player_honesty(),max_number_players SHARED CP_REQ_PLAYER_DISCONNECT.s,CP_END_GAME_REC.s,REL_PACKET_ACK.s,CP_REP_ACCEPT.s SHARED CP_GAME_END.s,CP_REP_PLAYER_DISCONNECTED.s,PING_REQUEST.s SHARED PING_RESPONSE.s return_code=False incoming_string.s=ReadUDP{} ; get data from socket If incoming_string<>"" ; if there was some data waiting packet_type=Asc(Mid$(incoming_string,5)) unknown_packet=0 ; check to see if valid packet is received player=Get_Packet_Source{} ; check to see if packet is from reliable online source incoming_packet_number.l=Cvl(Left$(incoming_string,4)) return_message=0 player_known=True Select packet_type ; as set in `Net_Protocol_Header.bb2' Case #CONTROL_PACKET ; connection or control information packet_type=Asc(Mid$(incoming_string,6)) Select packet_type Case #CP_REQ_CONNECT ; request to login to Server Requested_Connection{incoming_string,player} player_known=False Case #CP_REP_ACCEPT ; We Received confirmation of logon from Server If online=#ATTEMPTING_TO_CONNECT online=#CLIENT my_number=Cvi(Mid$(incoming_string,9,2)) ; my player number map.w=Cvi(Mid$(incoming_string,10,2)) players(#SERVER_NUMBER)\status=#ONLINE ; Server is now online Security_Warning{"Connection Accepted",#SYSTEM_MESSAGE} return_message=#ACKNOWLEDGE_PACKET return_code=#CONNECTED Else Security_Warning{"Illegal Reply Accept attempt by player: " + Str$(player),#MEDIUM} EndIf player_known=False Case #CP_REP_REJECT ; Our logon request was rejected by host If online=#ATTEMPTING_TO_CONNECT Security_Warning{Mid$(incoming_string,7),#SYSTEM_MESSAGE} online=#OFFLINE return_code=#CONNECTION_REJECTED Else Security_Warning{"Illegal Reply Reject attempt by player: " + Str$(player),#MEDIUM} EndIf player_known=False Case #CP_REP_PLAYER_INFO ; Information about new player that has just logged on ; If online=#CLIENT ; if we are a client newplayer=Cvi(Mid$(incoming_string,7,2)) address.l=Cvl(Mid$(incoming_string,9,4)) If Get_Host_By_Address{&address,Cvi(Mid$(incoming_string,13,2)),newplayer}=True ascii_address.s=Get_Ascii_Address{address} players(newplayer)\status=#ONLINE,ascii_address.s players(newplayer)\player_colour=Val(Mid$(incoming_string,15,1)) players(newplayer)\score=Cvi(Mid$(incoming_string,16,4)) players(newplayer)\disconnect_requested=False text$="New player at- " + ascii_address.s + " Port: " + Str$(host(newplayer)\sin_port) Security_Warning{text$,#SYSTEM_MESSAGE} Else Security_Warning{"New player at unknown host!",#MEDIUM} EndIf return_message=#ACKNOWLEDGE_PACKET player_known=False ; Else ; Security_Warning{"Illegal Player Info Reply from player: " + Str$(player),#MEDIUM} ; EndIf Case #CP_REQ_PLAYER_DISCONNECT ; Request from client to disconnect ; or inform Server of disconnected player If online=#SERVER AND player>0 ; only if Server, and from legal player disconnecting_player=Cvi(Mid$(incoming_string,7,2)) If player=disconnecting_player ; if player is leaving game Disconnect_Player{player} Else ; Ping suspect player to see if they're still online If players(disconnecting_player)\status=#ONLINE player_honesty(player,disconnecting_player)=True ; record which player claims another is offline. Send_Reliable_Message{PING_REQUEST.s,disconnecting_player,#PEER_TO_PEER} ; so we can disconnect them if the cl CNIF #COMMS_DEBUG=1 text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " text$=text$ + "Player " + Str$(player) + " claims player " + Str$(disconnecting_player) Comms_Debug{text$ + " has disconnected",#HIGH} CEND Else CNIF #COMMS_DEBUG=1 text$="Warning: Player " + Str$(player) + " says UNCONNECTED PLAYER " + Str$(disconnecting_player) text$=text$ + " is unreachable." Comms_Debug{text$,#HIGH} CEND EndIf return_message=#ACKNOWLEDGE_PACKET EndIf Else Security_Warning{"Illegal Player Disconnect Request Packet from player: " + Str$(player), #MEDIUM} EndIf Case #CP_REP_PLAYER_DISCONNECTED ; Server has told us a player has disconnected from the game ; - this can be us as well If online=#CLIENT AND player=#SERVER_NUMBER ; only if we're a client and message is from server disconnected_player=Cvi(Mid$(incoming_string,7,2)) Clear_Player_Arrays{disconnected_player} ; player is now offline If disconnected_player=my_number ; my disconnection request has been confirmed by Server online=#OFFLINE ; go offline For a= 1 To max_number_players If players(a)\status=#ONLINE Clear_Player_Arrays{a} EndIf Next Security_Warning{"We are disconnected from the game.",#SYSTEM_MESSAGE} return_code=#DISCONNECTED Else Security_Warning{"Player " + Str$(disconnected_player) + " is disconnected from the game.",#SYSTEM_MESSAGE} EndIf return_message=#ACKNOWLEDGE_PACKET Else Security_Warning{"Illegal Player Disconnected Reply Packet from player: " + Str$(player),#MEDIUM} EndIf Case #CP_END_GAME ; Server has ended Game If player=#SERVER_NUMBER AND online=#CLIENT; if message is from Server send$=CP_END_GAME_REC.s Send_Reliable_Message{send$,#SERVER_NUMBER,#PEER_TO_PEER} online=#OFFLINE return_message=#ACKNOWLEDGE_PACKET Security_Warning{"Server has shut down game.",#SYSTEM_MESSAGE} For a= 1 To max_number_players If players(a)\status=#ONLINE Clear_Player_Arrays{a} EndIf Next return_code=#DISCONNECTED Else Security_Warning{"Illegal End Game attempt by player: " + Str$(player),#MEDIUM} EndIf Case #CP_END_GAME_REC ; player has confirmed they received the Game End packet If online=#SERVER AND player>0 Clear_Player_Arrays{player} ; player is now offline dummy=0 For i=2 To max_number_players If players(i)\status=#ONLINE ; check to see if all players are offline dummy=1 EndIf Next If dummy=0 ; if all players are offline online=#OFFLINE CNIF #COMMS_DEBUG=1 text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Comms_Debug{text$ + "Player " + Str$(player) + " has confirmed game closure.",#HIGH} CEND Security_Warning{"All players have confirmed game has shut down.",#SYSTEM_MESSAGE} return_code=#DISCONNECTED CNIF #COMMS_DEBUG=1 Else text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Comms_Debug{text$ + "Player " + Str$(player) + " has confirmed game closure.",#HIGH} CEND EndIf return_message=#ACKNOWLEDGE_PACKET Else Security_Warning{"Illegal END_GAME_REC packet received from: " + Str$(player),#MEDIUM} EndIf Default unknown_packet=1 End Select Case #REL_STRING_END ; basic string message received from a player If player>0 AND online>#ATTEMPTING_TO_CONNECT text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Security_Warning{text$ + Mid$(incoming_string,6,Len(incoming_string)-6),#SYSTEM_MESSAGE} return_message=#ACKNOWLEDGE_PACKET EndIf Case #UNRELIABLE_PACKET ; an unreliable packet has been received- no acknowledgement needed If player>0 AND online>#ATTEMPTING_TO_CONNECT If unreliable_packet_in(player) > incoming_packet_number ; if this packet is slow and has got out of order CNIF #COMMS_DEBUG=1 ; ignore it text$="RU " + Str$(incoming_packet_number) + "," + Str$(player) + " Out-of-Order: " Comms_Debug{text$ + Mid$(incoming_string,6,Len(incoming_string)-6),#MEDIUM} CEND Else ; lastest unreliable message text$="RU " + Str$(incoming_packet_number) + "," + Str$(player) + ": " unreliable_packet_in(player)=incoming_packet_number Security_Warning{text$ + Mid$(incoming_string,6,Len(incoming_string)-6),#SYSTEM_MESSAGE} EndIf EndIf Case #REL_PACKET_ACK ; a message has been received by a player If player>0 AND online>#ATTEMPTING_TO_CONNECT If Acknowledge_Packet{incoming_packet_number,player}=True CNIF #COMMS_DEBUG=1 text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Comms_Debug{text$ + " Packet no. " + Str$(incoming_packet_number) + " arrived at destination.",#HIGH} CEND EndIf EndIf Case #PING_REQUEST ; a Ping request has been received CNIF #COMMS_DEBUG=1 text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Comms_Debug{text$ + "Player " + Str$(player) + " has sent a Ping request.",#HIGH} CEND If player=#SERVER_NUMBER AND online=#CLIENT return_message=#ACKNOWLEDGE_PACKET Send_Reliable_Message{PING_RESPONSE.s,player,#PEER_TO_PEER} ; send Ping Response back Else Security_Warning{"Illegal PING_REQUEST received from player: " + Str$(player), #MEDIUM} EndIf Case #PING_RESPONSE ; Ping response received If online=#SERVER CNIF #COMMS_DEBUG=1 text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Comms_Debug{text$ + "Player " + Str$(player) + " has sent a Ping response.",#HIGH} CEND If player>0 ; if from online player For a= 1 To max_number_players ; this is to stop players from disconnecting other players illegally If player_honesty(a,player)=True ; if player loses connection to other player Disconnect_Player{a} ; Disconnect player who has lost connection to another player CNIF #COMMS_DEBUG=1 text$="R " + Str$(incoming_packet_number) + "," + Str$(player) + ": " Comms_Debug{text$ + "Player " + Str$(a) + " has lost connection to player " + Str$(player),#HIGH} CEND EndIf Next EndIf Else Security_Warning{"Illegal PING_RESPONSE received from player: " + Str$(player), #MEDIUM} EndIf Default ; unknown packet received unknown_packet=1 End Select If unknown_packet=1 If player=-1 ; if from unknown source temp$=": " + Get_Ascii_Address{temphost\sin_addr\s_addr} + " Port: " + Str$(temphost\sin_port) Else ; from known source temp$=" Player: " + Str$(player) EndIf Security_Warning{"Warning- Unknown Packet from" + temp$,#MEDIUM} Function Return False EndIf If player_known=True AND player=-1 ; if we get sent a legal packet from unknown player temp$=": " + Get_Ascii_Address{temphost\sin_addr\s_addr} + " Port: " + Str$(temphost\sin_port) Security_Warning{"Warning- Packet from unknown Player at" + temp$,#MEDIUM} EndIf If return_message>0 If return_message=#ACKNOWLEDGE_PACKET ; send packet acknowledgment back send$=Mkl$(incoming_packet_number) + REL_PACKET_ACK.s ; EndIf WriteUDP{&send$,Len(send$),player} ; return a message back if return_message = 1 or 3 EndIf EndIf Function Return return_code End Function .Comms_Housekeeping Function Comms_Housekeeping{} SHARED messages(),last_message_number,players(),online SHARED max_wait,max_number_players,disconnect_timeout return_code=Decode_Packet{} ; find out what was in the packet If messages(last_message_number)\ack=False ; only check if there's messages that haven't been acknowledged yet. If Ticks>messages(last_message_number)\timestamp+max_wait ; has it been 5 secs since the packet was sent? Resend_Message{last_message_number} ; if run out of resends- disconnect player EndIf EndIf If disconnect_timeout>0 ; If disconnection timeout reaches disconnect_timeout-1 ; zero then disconnect the program If disconnect_timeout=0 online=#OFFLINE ; go offline For a= 1 To max_number_players If players(a)\status=#ONLINE Clear_Player_Arrays{a} EndIf Next Security_Warning{"We are disconnected from the game.",#SYSTEM_MESSAGE} return_code=#DISCONNECTED EndIf EndIf Function Return return_code End Function ;*************************************************************** ;*********************** End of UDP Stuff ******************** ;*************************************************************** ;*************************************************************** ;************************** Game stuff *********************** ;*************************************************************** ; Everything below is for UDP_Chat, and is an example of ; putting the UDP_Funcs into your game. ;******************* Game Variables and Constants ************** exit=False game_closing=0 PORT_NAME.s="UDP_Chat" ; "UDP_Chat" Rexx portname #LOCALPORT=27272 ; 27272 default local port to send an receive from #PORT=27272 ; 27272 default destination port to send data to #RELIABLE=0 #UNRELIABLE=1 print_player.w=1 message_type=#RELIABLE ;************************ Game Dims ************************** Dim pingprintx(max_number_players) ;******************* Game Functions ******************** ; ; These Functions are specific to your program, the ones below ; can be used as examples for your game. .Print_String ; Print a string in window and scroll if necessary Statement Print_String{text$} SHARED ypos If ypos<200 ypos+10 Else WScroll 10,60,590,220,0,10 EndIf WLocate 10,ypos Print text$ ; print string received End Statement .Window_Events Function Window_Events{} SHARED pingwidth,hostnamey,online,players(),my_number,packet_number SHARED game_closing.w,max_number_players,pingprintx(),hostname SHARED port_used,message_type,unreliable_packet_out() SHARED REL_STRING_END.s exit=False ev.l=Event ; get window events Select ev Case $40 ; if a gadget has been hit Select GadgetHit Case51 ; Try to connect to a Server If Connect_to_Server{GTGetString(0,51), GTGetInteger(0,52)}=True WLocate 502,4 Print "Bound to: ",port_used Else Print_String{"Unable to Initialise UDP"} EndIf Case52 ; Port to connect at above Server If Get_Host_By_Name{GTGetString(0,51), GTGetInteger(0,52),1} : EndIf Case53 ; Server button- lets our program become the Server If Initialise_Server{#LOCALPORT} Print_String {"Bound to " + hostname + " "+ Str$(port_used)} WLocate 502,4 Print "Bound to: ",port_used WLocate pingprintx(#SERVER_NUMBER),32 Print "Serv" ; show that we're Server Print_String{"We are set up as Server."} Else Print_String{"Couldn't Initialise Server!"} EndIf Case54 ; get name of localhost a$=Localhost_Name {} ; WLocate 5+pingwidth+15,hostnamey+3 Print a$ Case 55 ; button 1 Print_String{"Delay program for 1 second"} VWait 50 ; put in artificial delay of 1 second For testing purposes Print_String{"Delay Ended"} Case 56 ; button 2 Print_String{"Delay program for 80 seconds"} VWait 4000 ; put in artificial delay of 80 seconds for testing purposes- causes you to lose connection Print_String{"Delay Ended"} Case 61 ; button 7- toggle between sending Reliable and unreliable messages If message_type=#RELIABLE message_type=#UNRELIABLE Print_String{"Send Unreliable messages."} Else message_type=#RELIABLE Print_String{"Send Reliable messages."} EndIf Case62 ; button 8- Pause program Print_String{"PAUSED"} Repeat ev.l=WaitEvent If ev=$40 Select GadgetHit Case 62 unpause= 1 Print_String{"UNPAUSED"} End Select EndIf Until unpause=1 Case63 ; Disconnect button- disconnects us from the game If online=#OFFLINE Print_String{"We're already offline."} Else If Disconnect_From_Game{} Print_String{"Game has ended."} For i=1 To 8 WLocate pingprintx(i),32 Print " " ; Clear the lag times Next EndIf EndIf Case64 ; Send string message to connected players If online>#ATTEMPTING_TO_CONNECT temp$=GTGetString(0,64) For i=1 To 8 If players(i)\status=#ONLINE AND i<>my_number ; send to player if they're online and it's not my number :) If message_type=#RELIABLE Print_String{"S " + Str$(packet_number) + "," + Str$(i) + ": "+ temp$} send$=REL_STRING_END.s + temp$ + Chr$(0) Send_Reliable_Message{send$,i,#PEER_TO_PEER} ; Send string to connected players reliably Else Print_String{"SU " + Str$(unreliable_packet_out(i)+1) + "," + Str$(i) + ": "+ temp$} Send_Unreliable_Message{temp$,i} ; Send string to connected players unreliably EndIf EndIf Next EndIf End Select Case $200 ; if close button pressed If Disconnect_From_Game{} exit=True Print_String{"Program Closing."} Else game_closing=#GAME_CLOSING_TIMEOUT EndIf End Select Function Return exit End Function ; ARexx code ; It seems replyrexxmsg doesn't actually work for sending result strings ; back, use WRITERESULT, then replymsg for when you want to send ; something back to rexx with result Statement Result_Reply{*msg.RexxMsg,resulterror.l,resultstring$} *msg\rm_Result1=result1 If ((*msg\rm_Action & #RXFF_RESULT)<>0)&(result1=0) *msg\rm_Result2=CreateArgstring_(&resultstring$,Len(resultstring$)) Else *msg\rm_Result2=0 EndIf ReplyMsg_ *msg End Statement .Get_Rexx_Message Function Get_Rexx_Message{} SHARED rexxport.l,online rmsg.l=RexxEvent(rexxport) If IsRexxMsg(rmsg)=1 rexxline$=GetRexxCommand(rmsg,1) Print_String {rexxline$} linepos=Instr(rexxline$," ") If linepos=0 command$=rexxline$ Else command$=Left$(rexxline$,linepos-1) EndIf Select command$ Case "QUIT" ReplyRexxMsg rmsg,0,0,"" Function Return True Case "ISONLINE" Select online Case #OFFLINE Result_Reply{rmsg,0,"Offline"} Case #ATTEMPTING_TO_CONNECT Result_Reply{rmsg,0,"Attempting to Connect"} Case #SERVER Result_Reply{rmsg,0,"Server"} Case #CLIENT Result_Reply{rmsg,0,"Client"} End Select Case "CONNECTTOSERVER" hostname$=Right$(rexxline$,Len(rexxline$)-linepos) Print_String {hostname$} If Connect_to_Server{hostname$,#PORT} : EndIf ScreenToFront_ Peek.l(Addr Screen(0)) WindowToFront_ Peek.l(Addr Window(0)) ReplyRexxMsg rmsg,0,0,"" Default ReplyRexxMsg rmsg,0,0,"" End Select EndIf Function Return False End Function .General_Housekeeping Function General_Housekeeping{} SHARED game_closing,online,print_player,pingprintx() SHARED players(),my_number,print_count,max_number_players exit=False If Get_Rexx_Message{}=True ; check for any arexx messages exit=True ; exit if `Quit' received EndIf print_count+1 If print_count=50 print_count=1 ; do once per second print_player+1 If print_player>max_number_players Then print_player=1 If online>#ATTEMPTING_TO_CONNECT AND print_player<>my_number WLocate pingprintx(print_player),32 If players(print_player)\status=#ONLINE Format "0000" Print Str$(players(print_player)\lag) ; print lag onscreen Format"" Else Print" " EndIf EndIf EndIf If game_closing>0 ; if game is shutting down If online=#OFFLINE exit=True ; if all players are offline then shutdown Print_String{"We are now Offline- Closing program."} Else If game_closing>=2 ; carry on countdown game_closing-1 Else ; if countdown reaches 10 seconds then disconnect regardless exit=True Print_String{"Emergency Shutdown!- unable to disconnect from Server."} EndIf EndIf EndIf Repeat a$=Read_Security_Warning{} If a$<>"" Print_String{a$} EndIf Until a$="" CNIF #COMMS_DEBUG=1 Repeat a$=Read_Comms_Debug_Messages{} If a$<>"" Print_String{a$} EndIf Until a$="" CEND Function Return exit End Function .Exit Statement Exit{text$} SHARED rexxport.l,rexxmsg.l Print_String{text$} ; print error (if any) If rexxport>0 ; if we've successfully opened an Arexx port then close it DeleteRexxMsg rexxmsg.l DeleteMsgPort rexxport.l Print_String{"Rexxport closed"} EndIf Close_UDP{} ; close all UDP stuff down Print_String{"Program exiting"} Delay_(50) End ; close program End Statement ;********************* End of Functions *********************** Gosub Init_Gui ;************************ Main Loop ************************** .Main Repeat WaitTOF_ ; pause a bit to allow prog to multitask nicely, and sync up display exit=Window_Events{} ; check for input from user If online>#OFFLINE a=Comms_Housekeeping{} ; Check for incoming data and do comms housekeeping work. Select a Case #CONNECTED ; Server has told us we're online WLocate pingprintx(my_number)+5,32 Print "Me" ; print our player number - as a Client. Case #DISCONNECTED ; We're now offline For i=1 To 8 WLocate pingprintx(i),32 Print " " ; Clear the lag times Next If game_closing>0 Then exit=1 Case #CONNECTION_REJECTED ; Our connection attempt was rejected End Select EndIf If General_Housekeeping{}=True ; Check for rexx messages exit=True ; and do general housekeeping work. EndIf Until exit=True ; shut down if close gadget hit Exit{""} ; close UDP socket and Rexxport, and free read memory buffer ; END! ;**************************************************************************** .Init_Gui ; setup window and gadgets- note it's a bit hacked from Alvaro Thompson's original code ypos=40 WBenchToFront_ ; and isn't proportional and completely font sensitive any more :-/ WbToScreen0 DefaultIDCMP $20|$40|$200 *scr.Screen=Peek.l(Addr Screen(0)) *myfont.TextAttr=*scr\Font fontheight=*myfont\ta_YSize ad1.l=*myfont\ta_Name fontname$=Peek$(ad1) LoadFont 1,fontname$,fontheight,0 portwidth=TextLength_(*scr\RastPort,"Port:",5)+10 numberswidth=TextLength_(*scr\RastPort,"99999",5)+14 serverwidth=TextLength_(*scr\RastPort,"Send To:",8)+10 connectwidth=TextLength_(*scr\RastPort,"Connect",7)+14 disconnectwidth=TextLength_(*scr\RastPort,"Disconnect",10)+14 pingwidth=TextLength_(*scr\RastPort,"Localhost:",11) delaywidth=TextLength_(*scr\RastPort,"SERVER",8)+10 inputwidth=TextLength_(*scr\RastPort,"Input:",6)+10 firstperson=TextLength_(*scr\RastPort,"1:",2)+10 #SERVERBTN =51 #PORTBTN =52 #BUTTON =53 x=1 y=1 x1=x+(serverwidth*3)+portwidth+numberswidth+9+3+connectwidth+5 If #NO_CONNECTION=0 GTString 0,#SERVERBTN,x+serverwidth,y,serverwidth*2,fontheight+4,"Send To:",1,256,Localhost_Name {} Else GTString 0,#SERVERBTN,x+serverwidth,y,serverwidth*2,fontheight+4,"Send To:",1,256,"localhost" EndIf GTInteger 0,#PORTBTN,x+(serverwidth*3)+portwidth+3,y,numberswidth,fontheight+4,"Port:",1,#PORT GTButton 0,#BUTTON,331,y,delaywidth,fontheight+4,"Server",$10 GTButton 0,63,333+delaywidth,y,disconnectwidth,fontheight+4,"Disconnect",$10 y+fontheight+5 hostnamey=y GTButton 0,54,5,y,pingwidth,fontheight+4,"Localhost:",$10 WinWidth=x+(serverwidth*3)+portwidth+numberswidth+9+3+connectwidth+8+disconnectwidth+8+90 y+fontheight+5 GTButton 0,55,120,y,firstperson,fontheight+4,"1:",$10 GTButton 0,56,180,y,firstperson,fontheight+4,"2:",$10 GTButton 0,57,240,y,firstperson,fontheight+4,"3:",$10 GTButton 0,58,300,y,firstperson,fontheight+4,"4:",$10 GTButton 0,59,360,y,firstperson,fontheight+4,"5:",$10 GTButton 0,60,420,y,firstperson,fontheight+4,"6:",$10 GTButton 0,61,480,y,firstperson,fontheight+4,"7:",$10 GTButton 0,62,540,y,firstperson,fontheight+4,"8:",$10 For i= 1To8 pingprintx(i)=86+ i*60 Next pingprinty=y GTString 0,64,50,215,550,fontheight+4,"Send:",1,67,"" WinHeight=y+fontheight+5 ;WinX=WBWidth/2-(WinWidth/2) ;WinY=WBHeight/2-(WinHeight/2) WinTitle$="UDP Chat" ;ScreenTitle$="UDP Send "+Chr$(169)+"1997." Window 0,0,10,630,245,$0002|$0004|$0008,WinTitle$,1,2 Use Window 0:Activate 0:AttachGTList 0,0:WTitle WinTitle$,ScreenTitle$:Menus Off WindowOutput0 WindowInput 0 a$=Localhost_Name {} ; WLocate 5+pingwidth+15,hostnamey+3 Print a$ WLocate 10,y+3 Print"Ping Time-VBL" CNIF #COMMS_DEBUG=0 If FindPort_(PORT_NAME.s) Exit{"UDP_Chat already running!"} EndIf CEND rexxport.l=CreateMsgPort(PORT_NAME.s) If rexxport=0 Exit{"Unable to open Rexxport!"} EndIf rexxmsg.l=CreateRexxMsg(rexxport,"rexx",PORT_NAME.s) Print_String {"Type in data, and hit return to send."} Return