RPGMaker VX Online Game – Parte I

Con l’introduzione dello scripting in RGSS in RPGMaker XP, si sono aperte molte frontiere sulla personalizzazione dei nostri giochi. Col tempo sono apparsi molti script per rendere i propri giochi più interessanti e sempre più stupefacenti. La semplicità con la quale si può creare un gioco con RPGMaker ha fatto in modo di avvicinare sia principianti che utenti avanzati nella programmazione a creare nuove esperienze di gioco.

Gli MMORPG, ovvero Massive(ly) Multiplayer Online Role-Playing Game, sono l’esperienza di gioco più coinvolgente di questo secolo, giocare online ti permette di abbattere i limiti fisici, giocare con altre persone ed entrare e vivere nel mondo fantastico del titolo online a cui stiamo giocando.

Anche su RPGMaker molti hanno tentato di realizzare un’esperienza di gioco online, sono tanti gli scripts che sono stati realizzati. In principio per RPGMaker XP vi fu Netplay e Netplayplus, seguiti da tanti altri, compresi alcuni tentativi italiani.

Oggi vi propongo la traduzione di un articolo inglese, di sorlokreaves che utilizzando le sue conoscenze di JAVA e Ruby, ha creato un semplicissimo sistema client/server per RPGMaker VX utilizzando i socket del protocollo TCP.

L’articolo si rivolge a chi ha già alcuni rudimenti di programmazione e vuole realizzare un sistema online nel proprio gioco.

Step 1 : Casi d’uso

Cominciamo col creare un progetto nuovo su RPGMaker VX. Create una mappa iniziale, mettete qualche tile di “terra”. Dopo aggiungete un NPC (evento) con questi comandi:

  • Messaggio: “Apertura Socket”
  • Script
    s = TCPSocket.open('127.0.0.1', 7689)
    s.close()
  • Messaggio: “Successo”

Adesso, provate il gioco e parlate con l’evento, dovreste vedere questo messaggio di errore e il gioco crasherà.

No "sockets" library significa che non esiste nessuna classe TCPSocket, il che farà crashare il nostro script da due linee

No “sockets” library significa che non esiste nessuna classe TCPSocket, il che farà crashare il nostro script da due linee

Cos’è appena successo? Bene, prima abbiamo provato a creare un socket. Questo socket assumeva che noi avessimo un server che stava girando su 127.0.0.1 (indirizzo di localhost, vuol dire che il server girava su questo computer) e che era collegato alla porta 7689. Siccome nessun server è stato lanciato, ci si aspetta un crash. -comunque abbiamo ottenuto un diverso messaggio di errore. Se guardate il messaggio d’errore, vedrete che RPGMaker VX non è riuscito a trovare l’oggetto “TCPSocket”. In altre parole RPGMaker VX non da supporto nativo alle librerie Ruby che gestiscono il networking (comunicazione online).-
Nemmeno RPGMaker XP offriva questo supporto nativamente, e l’autore si interroga come RMX-OS (script per RPGMaker XP, che permette di creare giochi online) lo abbia potuto ottenere.
Se avete già esperienza con i linguaggi di programmazione, sapete che di solito la parte di networking è collegata direttamente al sistema operativo. Se siete curiosi di vedere come Ruby si connetta attraverso i socket, potete scaricare il sorgente di Ruby 1.8 e analizzare il file thread.rb
Scarica Ruby qui: http://ftp.de.debian.org/debian/pool/main/r/ruby1.8/ruby1.8_1.8.7.249.orig.tar.gz

Step 2 : Prendendo in prestito la Libreria di Codice

Se avete familiarità con l’ambiente di scripting di RGSS, avrete notato che non supporta la primitiva “require”. Questo ha senso dal momento che “require” cerca il percorso alla libreria standard, potresti avviare delle librerie di Ruby che i videogiocatori non hanno. C’è da dire però che RPGMaker è in grado di eseguire codice nativo dalle DLLs. Per esempio questa funzionerà bene:

Win32API.new(DLL, 'connect', 'ppl', 'l').call(1, 2, 3)

Potremmo considerare di combinare insieme le DLL per creare insieme una “finta” libreria per il networking. Eventualmente potremmo migrare ad una cosiddetta libreria nativa, così potremmo guadagnare i benefici di primitive di basso livello. Un esempio è dato dalla libreria:
http://rubyforge.org/projects/win32utils/

Se la estraete e l’analizzate noterete che il codice sorgente ha molti require tag. Se volete utilizzarla dovrete essenzialmente sostituire queste con funzionalità compatibili.

Comunque per questo tutorial utilizzeremo la libreria Win32 API utilizzata da Blizzard in RMX-OS, che dice di aver preso da Ruby 1.8.1. Questo non creerà problemi di licenza, poichè Ruby permette l’utilizzo di copie di librerie modificate e ridistribuite.

Il codice di networking di RMX-OS è costruito con chiamate a DLL, questo è il codice da copiare:

#==============================================================================
# ** Module Win32 - Handles numerical based data.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

module Win32

  #----------------------------------------------------------------------------
  # ● Retrieves data from a pointer.
  #----------------------------------------------------------------------------
  def copymem(len)
    buf = "\0" * len
    Win32API.new('kernel32', 'RtlMoveMemory', 'ppl', '').call(buf, self, len)
    buf
  end
  
end

# Extends the numeric class.
class Numeric
  include Win32
end

# Extends the string class.
class String
  include Win32
end

#==============================================================================
# ** Module Winsock - Maps out the functions held in the Winsock DLL.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

module Winsock

  DLL = 'ws2_32'

  #----------------------------------------------------------------------------
  # * Accept Connection
  #----------------------------------------------------------------------------
  def self.accept(*args)
    Win32API.new(DLL, 'accept', 'ppl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Bind
  #----------------------------------------------------------------------------
  def self.bind(*args)
    Win32API.new(DLL, 'bind', 'ppl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Close Socket
  #----------------------------------------------------------------------------
  def self.closesocket(*args)
    Win32API.new(DLL, 'closesocket', 'p', 'l').call(*args)
  end  
  #----------------------------------------------------------------------------
  # * Connect
  #----------------------------------------------------------------------------
  def self.connect(*args)
    Win32API.new(DLL, 'connect', 'ppl', 'l').call(*args)
  end    
  #----------------------------------------------------------------------------
  # * Get host (Using Adress)
  #----------------------------------------------------------------------------
  def self.gethostbyaddr(*args)
    Win32API.new(DLL, 'gethostbyaddr', 'pll', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Get host (Using Name)
  #---------------------------------------------------------------------------- 
  def self.gethostbyname(*args)
    Win32API.new(DLL, 'gethostbyname', 'p', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Get host's Name
  #----------------------------------------------------------------------------
  def self.gethostname(*args)
    Win32API.new(DLL, 'gethostname', 'pl', '').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Get Server (Using Name)
  #----------------------------------------------------------------------------
  def self.getservbyname(*args)
    Win32API.new(DLL, 'getservbyname', 'pp', 'p').call(*args)
  end
  #----------------------------------------------------------------------------
  # * HT OnL
  #----------------------------------------------------------------------------
  def self.htonl(*args)
    Win32API.new(DLL, 'htonl', 'l', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * HT OnS
  #----------------------------------------------------------------------------
  def self.htons(*args)
    Win32API.new(DLL, 'htons', 'l', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Inet Adress
  #----------------------------------------------------------------------------
  def self.inet_addr(*args)
    Win32API.new(DLL, 'inet_addr', 'p', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Inet NtOA
  #----------------------------------------------------------------------------
  def self.inet_ntoa(*args)
    Win32API.new(DLL, 'inet_ntoa', 'l', 'p').call(*args)
  end  
  #----------------------------------------------------------------------------
  # * Listen
  #----------------------------------------------------------------------------
  def self.listen(*args)
    Win32API.new(DLL, 'listen', 'pl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Recieve
  #----------------------------------------------------------------------------
  def self.recv(*args)
    Win32API.new(DLL, 'recv', 'ppll', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Select
  #----------------------------------------------------------------------------
  def self.select(*args)
    Win32API.new(DLL, 'select', 'lpppp', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Send
  #----------------------------------------------------------------------------
  def self.send(*args)
    Win32API.new(DLL, 'send', 'ppll', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Set Socket Options
  #----------------------------------------------------------------------------
  def self.setsockopt(*args)
    Win32API.new(DLL, 'setsockopt', 'pllpl', 'l').call(*args)
  end  
  #----------------------------------------------------------------------------
  # * Shutdown
  #----------------------------------------------------------------------------
  def self.shutdown(*args)
    Win32API.new(DLL, 'shutdown', 'pl', 'l').call(*args)
  end
  #----------------------------------------------------------------------------
  # * Socket
  #----------------------------------------------------------------------------
  def self.socket(*args)
    Win32API.new(DLL, 'socket', 'lll', 'l').call(*args)  
  end
  #----------------------------------------------------------------------------
  # * Get Last Error
  #----------------------------------------------------------------------------
  def self.WSAGetLastError(*args)
    Win32API.new(DLL, 'WSAGetLastError', '', 'l').call(*args)
  end
 
end

#==============================================================================
# ** Socket - Creates and manages sockets.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

class Socket

  #----------------------------------------------------------------------------
  # ● Constants
  #----------------------------------------------------------------------------
  AF_UNSPEC                 = 0  
  AF_UNIX                   = 1
  AF_INET                   = 2
  AF_IPX                    = 6
  AF_APPLETALK              = 16

  PF_UNSPEC                 = 0  
  PF_UNIX                   = 1
  PF_INET                   = 2
  PF_IPX                    = 6
  PF_APPLETALK              = 16

  SOCK_STREAM               = 1
  SOCK_DGRAM                = 2
  SOCK_RAW                  = 3
  SOCK_RDM                  = 4
  SOCK_SEQPACKET            = 5
  
  IPPROTO_IP                = 0
  IPPROTO_ICMP              = 1
  IPPROTO_IGMP              = 2
  IPPROTO_GGP               = 3
  IPPROTO_TCP               = 6
  IPPROTO_PUP               = 12
  IPPROTO_UDP               = 17
  IPPROTO_IDP               = 22
  IPPROTO_ND                = 77
  IPPROTO_RAW               = 255
  IPPROTO_MAX               = 256

  SOL_SOCKET                = 65535
  
  SO_DEBUG                  = 1
  SO_REUSEADDR              = 4
  SO_KEEPALIVE              = 8
  SO_DONTROUTE              = 16
  SO_BROADCAST              = 32
  SO_LINGER                 = 128
  SO_OOBINLINE              = 256
  SO_RCVLOWAT               = 4100
  SO_SNDTIMEO               = 4101
  SO_RCVTIMEO               = 4102
  SO_ERROR                  = 4103
  SO_TYPE                   = 4104
  SO_SNDBUF                 = 4097
  SO_RCVBUF                 = 4098
  SO_SNDLOWAT               = 4099
  
  TCP_NODELAY               = 1
  
  MSG_OOB                   = 1
  MSG_PEEK                  = 2
  MSG_DONTROUTE             = 4
  
  IP_OPTIONS                = 1
  IP_DEFAULT_MULTICAST_LOOP = 1
  IP_DEFAULT_MULTICAST_TTL  = 1
  IP_MULTICAST_IF           = 2
  IP_MULTICAST_TTL          = 3
  IP_MULTICAST_LOOP         = 4
  IP_ADD_MEMBERSHIP         = 5
  IP_DROP_MEMBERSHIP        = 6
  IP_TTL                    = 7
  IP_TOS                    = 8
  IP_MAX_MEMBERSHIPS        = 20

  EAI_ADDRFAMILY            = 1
  EAI_AGAIN                 = 2
  EAI_BADFLAGS              = 3
  EAI_FAIL                  = 4
  EAI_FAMILY                = 5
  EAI_MEMORY                = 6
  EAI_NODATA                = 7
  EAI_NONAME                = 8
  EAI_SERVICE               = 9
  EAI_SOCKTYPE              = 10
  EAI_SYSTEM                = 11
  EAI_BADHINTS              = 12
  EAI_PROTOCOL              = 13
  EAI_MAX                   = 14

  AI_PASSIVE                = 1
  AI_CANONNAME              = 2
  AI_NUMERICHOST            = 4
  AI_MASK                   = 7
  AI_ALL                    = 256
  AI_V4MAPPED_CFG           = 512
  AI_ADDRCONFIG             = 1024
  AI_DEFAULT                = 1536
  AI_V4MAPPED               = 2048
  
  #----------------------------------------------------------------------------
  # ● Returns the associated IP address for the given hostname.
  #----------------------------------------------------------------------------  
  def self.getaddress(host)
    gethostbyname(host)[3].unpack('C4').join('.')
  end
  #----------------------------------------------------------------------------
  # ● Returns the associated IP address for the given hostname.
  #----------------------------------------------------------------------------  
  def self.getservice(serv)
    case serv
    when Numeric
      return serv
    when String
      return getservbyname(serv)
    else
      raise 'Please us an interger or string for services.'
    end
  end
  #----------------------------------------------------------------------------
  # ● Returns information about the given hostname.
  #----------------------------------------------------------------------------
  def self.gethostbyname(name)
    raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0
    host = ptr.copymem(16).unpack('iissi')
    [host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)]
  end
  #----------------------------------------------------------------------------
  # ● Returns the user's hostname.
  #----------------------------------------------------------------------------  
  def self.gethostname
    buf = "\0" * 256
    Winsock.gethostname(buf, 256)
    buf.strip
  end
  #----------------------------------------------------------------------------
  # ● Returns information about the given service.
  #----------------------------------------------------------------------------
  def self.getservbyname(name)
    case name
    when /echo/i
      return 7
    when /daytime/i
      return 13
    when /ftp/i
      return 21
    when /telnet/i
      return 23
    when /smtp/i
      return 25
    when /time/i
      return 37
    when /http/i
      return 80
    when /pop/i
      return 110
    else
      raise 'Service not recognized.'
    end
  end
  #----------------------------------------------------------------------------
  # ● Creates an INET-sockaddr struct.
  #----------------------------------------------------------------------------  
  def self.sockaddr_in(port, host)
    begin
      [AF_INET, getservice(port)].pack('sn') + gethostbyname(host)[3] + [].pack('x8')
    rescue
    end
  end
  #----------------------------------------------------------------------------
  # ● Creates a new socket and connects it to the given host and port.
  #----------------------------------------------------------------------------  
  def self.open(*args)
    socket = new(*args)
    if block_given?
      begin
        yield socket
      ensure
        socket.close
      end
    end
    nil
  end
  #----------------------------------------------------------------------------
  # ● Creates a new socket.
  #----------------------------------------------------------------------------  
  def initialize(domain, type, protocol)
    SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1
    @fd
  end
  #----------------------------------------------------------------------------
  # ● Accepts incoming connections.
  #----------------------------------------------------------------------------  
  def accept(flags = 0)
    buf = "\0" * 16
    SocketError.check if Winsock.accept(@fd, buf, flags) == -1
    buf
  end
  #----------------------------------------------------------------------------
  # ● Binds a socket to the given sockaddr.
  #----------------------------------------------------------------------------  
  def bind(sockaddr)
    SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Closes a socket.
  #----------------------------------------------------------------------------  
  def close
    SocketError.check if (ret = Winsock.closesocket(@fd)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Connects a socket to the given sockaddr.
  #----------------------------------------------------------------------------  
  def connect(sockaddr)
    SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Listens for incoming connections.
  #----------------------------------------------------------------------------  
  def listen(backlog)
    SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Checks waiting data's status.
  #----------------------------------------------------------------------------  
  def select(timeout)
    SocketError.check if (ret = Winsock.select(1, [1, @fd].pack('ll'), 0, 0, [timeout, timeout * 1000000].pack('ll'))) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Checks if data is waiting.
  #----------------------------------------------------------------------------  
  def ready?
    not select(0) == 0
  end  
  #----------------------------------------------------------------------------
  # ● Reads data from socket.
  #----------------------------------------------------------------------------  
  def read(len)
    buf = "\0" * len
    Win32API.new('msvcrt', '_read', 'lpl', 'l').call(@fd, buf, len)
    buf
  end
  #----------------------------------------------------------------------------
  # ● Returns recieved data.
  #----------------------------------------------------------------------------  
  def recv(len, flags = 0)
    buf = "\0" * len
    SocketError.check if Winsock.recv(@fd, buf, buf.size, flags) == -1
    buf
  end
  #----------------------------------------------------------------------------
  # ● Sends data to a host.
  #----------------------------------------------------------------------------  
  def send(data, flags = 0)
    SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1
    ret
  end
  #----------------------------------------------------------------------------
  # ● Writes data to socket.
  #----------------------------------------------------------------------------  
  def write(data)
    Win32API.new('msvcrt', '_write', 'lpl', 'l').call(@fd, data, 1)
  end

end

#==============================================================================
# ** TCPSocket - Creates and manages TCP sockets.
#------------------------------------------------------------------------------
# Author    Ruby
# Version   1.8.1
#==============================================================================

class TCPSocket < Socket

  #----------------------------------------------------------------------------
  # ● Creates a new socket and connects it to the given host and port.
  #----------------------------------------------------------------------------  
  def self.open(*args)
    socket = new(*args)
    if block_given?
      begin
        yield socket
      ensure
        socket.close
      end
    end
    nil
  end
  #----------------------------------------------------------------------------
  # ● Creates a new socket and connects it to the given host and port.
  #----------------------------------------------------------------------------  
  def initialize(host, port)
    super(AF_INET, SOCK_STREAM, IPPROTO_TCP)
    connect(Socket.sockaddr_in(port, host))
  end
  
end

#==============================================================================
# ** SocketError
#------------------------------------------------------------------------------
# Default exception class for sockets.
#==============================================================================

class SocketError < StandardError
  
  ENOASSOCHOST = 'getaddrinfo: no address associated with hostname.'
  
  def self.check
    errno = Winsock.WSAGetLastError
    raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno })
  end
  
end

Nell’editor degli scripts (F11), fate tasto destro su “Main”, e scegliete “Inserisci”, scrivete “Ruby Library Code” nel nome e incollate il codice precedente. Scegliete OK e salvate il gioco.

Se provate il gioco adesso, finirete con l’avere un errore. Quindi cancellate l’evento che abbiamo creato in precedenza, e aggiungete il codice che segue direttamente nello script Main, esattamente dopo Graphics.freeze

s = TCPSocket.open('127.0.0.1', 7689)
s.close()

Adesso salva il gioco, e avvia il game testing. Otterrai il seguente messaggio d’errore:

Questo errore significa che la libreria di codice è stata caricata, ma non ha trovato il server. Stiamo facendo progressi!

Questo errore significa che la libreria di codice è stata caricata, ma non ha trovato il server. Stiamo facendo progressi!

Questo è il tipo d’errore che stavamo cercando! Congratulazioni, la struttura base del codice TCP che hai aggiunto era corretta.

 Step 3 : Inizializzare e Testare la Connessione con gli NPCs

Abbiamo bisogno di testare una connessione TCP -inoltre dovremmo sistemare il bug che non permette agli NPC di attivare il nostro codice. Questi sono i nostri prossimi compiti.

Se hai trovato nel codice l’origine dell’errore, sei arrivato a queste linee di codice:

  def self.check
    errno = Winsock.WSAGetLastError
    raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno== errno })
  end

L’errore è da riteneresi nello statement “raise”. Se non capisci la logica a blocchi di Ruby, questo ti sconforterà e non vorrai proseguire. Ma prova questa piccola astrazione :

 def self.check
    errno = Winsock.WSAGetLastError
    constName = Errno.constants.detect { |c| Errno.const_get(c).new.errno== errno }
    raise Errno.const_get(constName)
  end

Prova questo codice ancora e noterai che l’errore sta nuovamente sulla linea di “raise”. Il programmatore originale voleva trovare gli errori di Win32 attraverso il loro ID, ma non ha messo in conto che un socket inizializzato a null avrebbe potuto generare un errore senza codice ID. Per risolvere basta questo:

def self.check
      errno = Winsock.WSAGetLastError
      constName = Errno.constants.detect {|c| Errno.const_get(c).new.errno == errno }
      if constName
        raise Errno.const_get(constName)
      else
        raise "Unknown network error code: #{errno}"
      end
  end

Adesso se proverai ad avviare il codice dell’evento, vedrai l’errore “Unknown network error code”.
A questo punto dovremmo dire che TCPSocket.open() non è tecnicamente corretto, poichè si aspetta che noi passiamo alla funzione un blocco di codice da eseguire. Questo potrebbe andare bene per una procedura socket immediata come ricevere l’ora attuale, o le condizioni meteo da un server centrale, ma noi abbiamo bisogno di una connessione persistente con il server per il nostro engine. Come ti aspetterai il nostro codice supererà il limite del comando Script dell’Evento. Quindi, cambia il codice script dell’evento con:

tcptest()

…e aggiungi il codice seguente al modulo “Main”, direttamente prima dello statement “begin”

  def tcptest
    #Createa a socket
    s = TCPSocket.new('127.0.0.1', 7689)

    #Send a test message
    s.send("Testing...\n")
    #Receive a result from the server
    msg = ''
    while
      buffer = s.recv(1024)    #Read UP TO 1024 bytes
      buffer.gsub!(0.chr, '') #Remove null bytes
      msg += buffer            #Append received data
      break if buffer.count("\n")>0   #Stop if we've reached the newline
    end
    #Done; close the socket, print our message
    s.close()
    print "Received: #{msg}"
  end

Il nostro codice è molto semplice, abbiamo usato “new” invece di “open”, e abbiamo usato “send” e “recv” per mandare e ricevere i dati. Un problema di recv, è che non aspetterà che il server abbia finito di comunicare la risposta per intero, così abbiamo bisogno di memorizzare l’intero messaggio di bit non appena vengono ricevuti. D’altronde una delle garanzie di TCP è che non riceveremo messaggi disordinati, questo è un punto che ci risparmierà molti mal di testa.

Adesso quello che abbiamo bisogno è il server. Credo che Ruby non sia molto indicato come codice client\server, proveremo così a scriverne uno in Java. Un problema con Java è che compilare il codice ed eseguirlo potrebbe essere difficoltoso. Piuttosto che fornirvi un tutorial completo su Eclipse o javac, ti chiederò di scaricare e installare Textpad.

NB: Alcuni utenti hanno segnalato che è necessario anche scaricare JDK. Apri una finestra di comando, e scrivi javac -version. Se ottieni un messaggio d’errore, allora devi scaricare JDK.

Adesso crea un nuovo file, chiamalo esattamente “SimpleServer.java“, e dentro scrivi questo codice:

import java.io.*;
import java.net.*;

class SimpleServer {
  public static void main(String argv[]) throws Exception {
    String clientSentence;
    String capitalizedSentence;
    ServerSocket welcomeSocket = new ServerSocket(7689);

    for ( ; ; ) {
      System.out.println("Waiting for connection...");
      Socket connectionSocket = welcomeSocket.accept();
      System.out.println("  >Socket connected");

      BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
      DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
      clientSentence = inFromClient.readLine();

      System.out.println("  >Received: " + clientSentence);
      capitalizedSentence = clientSentence.toUpperCase() + '\n';
      outToClient.writeBytes(capitalizedSentence);
      System.out.println("  >Replied: " + capitalizedSentence);
    }
  }
}

 

Abbastanza semplice? Abbiamo ignorato la parte di controllo degli errori, sarà relativamente semplice far crashare il server. Ma per una demo veloce, ce la farà. Il server legge le frasi mandate dal client, e le riporta in maiuscolo. Poichè abbiamo mandato la stringa “Testing…” attraverso il client, il server restituirà “TESTING…”.

Per compilare il codice del server, in TextPad, clicca su “Tools-> External Tools-> Compile Java”.
TextPadSe il codice non contiene errori, vedrai il messaggio di avvenuta compilazione. Adesso per avviare il server, clicca su “Tools->External Tools->Run Java Application”, a questo punto apparirà una finestra di comando con su scritto “Waiting for connection…”.ServerQuesto significa che il server è pronto per accettare una connessione alla porta 7689. Adesso avvia il gioco, parla con l’evento NPC, e vedrai un messaggio con la risposta del server:

Il messaggio del server è arrivato al client

Il messaggio del server è arrivato al client

La finestra del server avrà ulteriori messaggi:

Log del Server

Log del Server

Dal momento che abbiamo chiuso la connessione, il server sta aspettando un’altra connessione in ingresso. Potremmo ri-parlare con l’evento, oppure chiudere e aprire il gioco di nuovo e parlare con l’evento. Avremo bisogno che il client tenga la connessione per altri messaggi, ma per adesso abbiamo provato che il networking di RPGMaker VX funziona. Premi CTRL+C per chiudere la finestra, premi Y se necessario per confermare la chiusura del server.

Step 4 : Un passo indietro

A questo punto è opportuno fermarsi un attimo e capire cosa abbiamo realizzato. C’è stato molto “codice incollato”, ma ecco cosa abbiamo fatto:

  • Abbiamo creato un server che può “ricevere” connessioni sulla porta 7689, e un client che può richiedere una connessione a questa porta.
  • Una volta connessi, client e server possono mandare e ricevere una sequenza di caratteri.

E questo è quanto. Aggiungendo altri clients, qualche tipo di sincronizzazione e partizionando i nostri dati in “messaggi” o “eventi di gioco”, sono solo dettagli di quello che il server TCP in realtà può fare. Per esempio possiamo mandare le coordinate di un evento, di un giocatore. In questo modo possiamo trattare i giocatori come eventi NPC in altri giochi del RPG che volete realizzare. Possiamo mandare tiles, per permettere ai giocatori di costruire le loro case, o personalizzare gli avatar di ogni giocatore, oppure coordinare un battle system. Tutte queste cose possono essere realizzate mandando stringhe al server TCP.

Step 5 : Una Prova leggermente più impressionante

Continua nella seconda parte…