浏览代码

Develop game part

nordsoft 3 年之前
父节点
当前提交
dd45d1a9cf

+ 5 - 5
client/CServerHandler.cpp

@@ -117,7 +117,10 @@ extern std::string NAME;
 CServerHandler::CServerHandler()
 	: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)
 {
-	uuid = boost::uuids::to_string(boost::uuids::random_generator()());
+	if(settings["server"]["uuid"].isNull() || settings["server"]["uuid"].String().empty())
+		uuid = boost::uuids::to_string(boost::uuids::random_generator()());
+	else
+		uuid = settings["server"]["uuid"].String();
 	applier = std::make_shared<CApplier<CBaseForLobbyApply>>();
 	registerTypesLobbyPacks(*applier);
 }
@@ -352,10 +355,7 @@ bool CServerHandler::isGuest() const
 
 ui16 CServerHandler::getDefaultPort()
 {
-	if(settings["session"]["serverport"].Integer())
-		return static_cast<ui16>(settings["session"]["serverport"].Integer());
-	else
-		return static_cast<ui16>(settings["server"]["port"].Integer());
+	return static_cast<ui16>(settings["server"]["port"].Integer());
 }
 
 std::string CServerHandler::getDefaultPortStr()

+ 24 - 2
config/schemas/settings.json

@@ -253,7 +253,7 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default": {},
-			"required" : [ "server", "port", "localInformation", "playerAI", "friendlyAI","neutralAI", "enemyAI", "reconnect", "uuid", "names" ],
+			"required" : [ "server", "port", "serverport", "localInformation", "playerAI", "friendlyAI","neutralAI", "enemyAI", "reconnect", "uuid", "names", "lobby", "host" ],
 			"properties" : {
 				"server" : {
 					"type":"string",
@@ -263,6 +263,10 @@
 					"type" : "number",
 					"default" : 3030
 				},
+				"serverport" : {
+					"type" : "number",
+					"default" : 5002
+				},
 				"localInformation" : {
 					"type" : "number",
 					"default" : 2
@@ -291,14 +295,32 @@
 					"type" : "string",
 					"default" : ""
 				},
+				"host" : {
+					"type" : "object",
+					"additionalProperties" : false,
+					"default" : 
+					{
+						"host" : true
+					},
+					"required" : [ "host", "uuid", "connections" ],
+					"properties" : {
+						"uuid" : { "type" : "string" },
+						"connections" : { "type" : "number" },
+						"host" : {"type" : "boolean", "default" : false}
+					}
+				},
 				"names" : {
 					"type" : "array",
-					"default" : {},
+					"default" : [],
 					"items":
 					{
 						"type" : "string",
 						"default" : ""
 					}
+				},
+				"lobby" : {
+					"type" : "boolean",
+					"default" : false
 				}
 			}
 		},

+ 25 - 1
launcher/lobby/lobby_moc.cpp

@@ -76,6 +76,7 @@ void SocketLobby::connected()
 void SocketLobby::disconnected()
 {
 	isConnected = false;
+	emit disconnect();
 	emit text("Disconnected!");
 }
 
@@ -105,6 +106,7 @@ Lobby::Lobby(QWidget *parent) :
 
 	connect(&socketLobby, SIGNAL(text(QString)), this, SLOT(chatMessage(QString)));
 	connect(&socketLobby, SIGNAL(receive(QString)), this, SLOT(dispatchMessage(QString)));
+	connect(&socketLobby, SIGNAL(disconnect()), this, SLOT(onDisconnected()));
 }
 
 Lobby::~Lobby()
@@ -189,9 +191,26 @@ void Lobby::serverCommand(const ServerCommand & command) try
 		ui->roomChat->setPlainText(resText);
 		break;
 
-	case START:
+	case START: {
+		protocolAssert(args.size() == 1);
 		//actually start game
+		Settings node = settings.write["server"];
+		node["lobby"].Bool() = true;
+		node["server"].String() = ui->hostEdit->text().toStdString();
+		node["serverport"].Integer() = ui->portEdit->text().toInt();
+		node["uuid"].String() = args[0].toStdString();
+		//node["names"].Vector().clear();
+		//node["names"].Vector().pushBack(username.toStdString());
 		break;
+		}
+
+	case HOST: {
+		protocolAssert(args.size() == 2);
+		Settings node = settings.write["server"]["host"];
+		node["uuid"].String() = args[0].toStdString();
+		node["connections"].Integer() = args[1].toInt();
+		break;
+		}
 
 	case CHAT:
 		protocolAssert(args.size() > 1);
@@ -231,6 +250,11 @@ catch(const ProtocolError & e)
 	chatMessage(QString("System error: %1").arg(e.what()));
 }
 
+void Lobby::onDisconnected()
+{
+	ui->stackedWidget->setCurrentWidget(ui->sessionsPage);
+	ui->connectButton->setChecked(false);
+}
 
 void Lobby::chatMessage(QString txt)
 {

+ 5 - 1
launcher/lobby/lobby_moc.h

@@ -17,7 +17,7 @@ enum ProtocolConsts
 	GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, READY,
 
 	//server consts
-	SESSIONS, CREATED, JOINED, KICKED, ERROR, CHAT, START, STATUS
+	SESSIONS, CREATED, JOINED, KICKED, ERROR, CHAT, START, STATUS, HOST
 };
 
 const QMap<ProtocolConsts, QString> ProtocolStrings
@@ -37,6 +37,7 @@ const QMap<ProtocolConsts, QString> ProtocolStrings
 	{JOINED, "JOIN"}, //session_name:username
 	{KICKED, "KICK"}, //session_name:username
 	{START, "START"}, //session_name:uuid
+	{HOST, "HOST"}, //host_uuid:players_count
 	{STATUS, "STATUS"}, //joined_players:player_name:is_ready
 	{ERROR, "ERROR"},
 	{CHAT, "MSG"} //username:message
@@ -69,6 +70,7 @@ signals:
 
 	void text(QString);
 	void receive(QString);
+	void disconnect();
 
 public slots:
 
@@ -113,6 +115,8 @@ private slots:
 
 	void on_buttonReady_clicked();
 
+	void onDisconnected();
+
 private:
 	Ui::Lobby *ui;
 	SocketLobby socketLobby;

+ 155 - 22
launcher/proxyServer.py

@@ -1,4 +1,9 @@
+from calendar import c
+from distutils.command.clean import clean
+from pickletools import bytes8
 import socket
+import re
+import uuid
 from threading import Thread
 
 # server's IP address
@@ -8,6 +13,18 @@ SERVER_PORT = 5002 # port we want to use
 # initialize list/set of all connected client's sockets
 client_sockets = dict()
 
+
+class GameConnection:
+    server: socket
+    client: socket
+    serverInit = False
+    clientInit = False
+    messageQueueIn = []
+    messageQueueOut = []
+
+    def __init__(self) -> None:
+        pass
+
 class Session:
     total = 1
     joined = 0
@@ -15,7 +32,9 @@ class Session:
     protected = False
     name: str
     host: socket
+    host_uuid: str
     players = []
+    connections = []
     started = False
 
     def __init__(self, host: socket, name: str) -> None:
@@ -38,8 +57,44 @@ class Session:
 
         self.players.remove(player)
         self.joined -= 1
-        
-        
+
+    def addConnection(self, conn: socket, isServer: bool) -> GameConnection:
+        #find uninitialized server connection
+        for gc in self.connections:
+            if isServer and not gc.serverInit:
+                gc.server = conn
+                gc.serverInit = True
+                return gc
+            if not isServer and not gc.clientInit:
+                gc.client = conn
+                gc.clientInit = True
+                return gc
+            
+        #no existing connection - create the new one
+        gc = GameConnection()
+        if isServer:
+            gc.server = conn
+            gc.serverInit = True
+        else:
+            gc.client = conn
+            gc.clientInit = True
+        self.connections.append(gc)
+        return gc
+
+    def validPipe(self, conn) -> bool:
+        for gc in self.connections:
+            if gc.server == conn or gc.client == conn:
+                return gc.serverInit and gc.clientInit
+        return False
+
+    def getPipe(self, conn) -> socket:
+        for gc in self.connections:
+            if gc.server == conn:
+                return gc.client
+            if gc.client == conn:
+                return gc.server
+
+     
 # create a TCP socket
 s = socket.socket()
 # make the port as reusable port
@@ -47,7 +102,7 @@ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 # bind the socket to the address we specified
 s.bind((SERVER_HOST, SERVER_PORT))
 # listen for upcoming connections
-s.listen(5)
+s.listen(10)
 print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")
 
 # list of active sessions
@@ -57,17 +112,18 @@ sessions = dict()
 def handleDisconnection(client: socket):
     sender = client_sockets[client]
     if sender["joined"]:
-        if sender["session"].host == client:
-            #destroy the session, sending messages inside the function
-            deleteSession(sender["session"])
-        else:
-            sender["session"].leave(client)
-            sender["joined"] = False
-            message = f":>>KICK:{sender['session'].name}:{sender['username']}"
-            for client_socket in sender["session"].players:
-                client_socket.send(message.encode())
-        updateStatus(sender["session"])
-        updateSessions()
+        if not sender["session"].started:
+            if sender["session"].host == client:
+                #destroy the session, sending messages inside the function
+                deleteSession(sender["session"])
+            else:
+                sender["session"].leave(client)
+                sender["joined"] = False
+                message = f":>>KICK:{sender['session'].name}:{sender['username']}"
+                for client_socket in sender["session"].players:
+                    client_socket.send(message.encode())
+            updateStatus(sender["session"])
+            updateSessions()
 
     client.close()
     sender["valid"] = False
@@ -85,9 +141,13 @@ def broadcast(clients: list, message: str):
 
 
 def sendSessions(client: socket):
-    msg = f":>>SESSIONS:{len(sessions.keys())}"
+    msg2 = ""
+    counter = 0
     for s in sessions.values():
-        msg += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
+        if not s.started:
+            msg2 += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
+            counter += 1
+    msg = f":>>SESSIONS:{counter}{msg2}"
 
     send(client, msg)
 
@@ -114,10 +174,79 @@ def updateStatus(session: Session):
     broadcast(session.players, msg)
 
 
-def dispatch(client: socket, sender: dict, msg: str):
-    if msg == '':
+def startSession(session: Session):
+    session.started = True
+    session.host_uuid = str(uuid.uuid4())
+    hostMessage = f":>>HOST:{session.host_uuid}:{session.joined - 1}" #one client will be connected locally
+    for player in session.players:
+        client_sockets[player]['uuid'] = str(uuid.uuid4())
+        msg = f":>>START:{client_sockets[player]['uuid']}"
+        send(player, msg)
+    
+    #host message must be after start message
+    send(session.host, hostMessage)
+
+
+def dispatch(client: socket, sender: dict, arr: bytes):
+    if len(arr) == 0:
         return
 
+    msg = str(arr)
+    print(msg)
+    if msg[-1] == '\n' or (msg[-1] == '\'' and msg[-2] == '\n'):
+        sender["prevmessage"] += msg
+        return
+    else:
+        msg = f"{sender['prevmessage']}{msg}"
+        sender["prevmessage"] = ""
+
+    #check for game mode connection
+    _gameModeIdentifier = msg.partition('Aiya!')
+    if _gameModeIdentifier[0] != '' and _gameModeIdentifier[1] == 'Aiya!':
+        sender["aiya"] = True
+
+    if sender["aiya"]:
+        _uuid = msg.partition('$')[2]
+        match = re.search(r"\((\w+)\)", msg)
+        _appType = ''
+        if match != None:
+            _appType = match.group(1)
+        if not _uuid == '' and not _appType == '':
+            #search for uuid
+            for session in sessions.values():
+                if session.started:
+                    if _uuid.find(session.host_uuid) != -1 and _appType == "server":
+                        gc = session.addConnection(client, True)
+                        for qmsg in gc.messageQueueIn:
+                            send(gc.server, qmsg)
+                        sender["session"] = session
+                        sender["game"] = True
+                        if not gc.clientInit:
+                            gc.messageQueueOut.append(arr)
+                        return
+
+                    if _appType == "client":
+                        for p in session.players:
+                            if _uuid.find(client_sockets[p]["uuid"]) != -1:
+                                #client connection
+                                gc = session.addConnection(client, False)
+                                for qmsg in gc.messageQueueOut:
+                                    send(gc.client, qmsg)
+                                sender["session"] = session
+                                sender["game"] = True
+                                if not gc.serverInit:
+                                    gc.messageQueueIn.append(arr)
+                                    return
+                                break
+
+    #game mode
+    if sender["game"] and sender["session"].validPipe(client):
+        sender["session"].getPipe(client).send(arr)
+        return
+        
+    
+    #lobby mode
+    msg = arr.decode()
     _open = msg.partition('<')
     _close = _open[2].partition('>')
     if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '':
@@ -230,10 +359,14 @@ def dispatch(client: socket, sender: dict, msg: str):
             sender["session"].leave(client)
             sender["joined"] = False
             updateStatus(sender["session"])
-        updateSessions()
+        updateSessions() 
 
+    if tag == "READY" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
+        if sender["session"].joined > 0 and sender["session"].host == client:
+            startSession(sender["session"])
+            updateSessions()
 
-    dispatch(client, sender, _nextTag[1] + _nextTag[2])
+    dispatch(client, sender, (_nextTag[1] + _nextTag[2]).encode())
 
 
 def listen_for_client(cs):
@@ -244,7 +377,7 @@ def listen_for_client(cs):
     while True:
         try:
             # keep listening for a message from `cs` socket
-            msg = cs.recv(2048).decode()
+            msg = cs.recv(2048)
         except Exception as e:
             # client no longer connected
             print(f"[!] Error: {e}")
@@ -259,7 +392,7 @@ while True:
     client_socket, client_address = s.accept()
     print(f"[+] {client_address} connected.")
     # add the new connected client to connected sockets
-    client_sockets[client_socket] = {"address": client_address, "auth": False, "username": ""}
+    client_sockets[client_socket] = {"address": client_address, "auth": False, "username": "", "joined": False, "aiya": False, "prevmessage": "", "game": False}
     # start a new thread that listens for each client's messages
     t = Thread(target=listen_for_client, args=(client_socket,))
     # make the thread daemon so it ends whenever the main thread ends

+ 40 - 0
server/CVCMIServer.cpp

@@ -170,6 +170,7 @@ void CVCMIServer::run()
 #endif
 
 		startAsyncAccept();
+		establishRemoteConnections();
 
 #if defined(VCMI_ANDROID)
 		CAndroidVMHelper vmHelper;
@@ -195,6 +196,45 @@ void CVCMIServer::run()
 		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
 }
 
+void CVCMIServer::establishRemoteConnections()
+{
+	if(settings["server"]["lobby"].isNull() || !settings["server"]["lobby"].Bool())
+		return;
+	
+	Settings node = settings.write["server"];
+	node["lobby"].Bool() = false;
+	
+	uuid = settings["server"]["host"]["uuid"].String();
+	int numOfConnections = settings["server"]["host"]["connections"].Integer();
+	auto address = settings["server"]["server"].String();
+	int port = settings["server"]["serverport"].Integer();
+	
+	for(int i = 0; i < numOfConnections; ++i)
+		connectToRemote(address, port);
+}
+
+void CVCMIServer::connectToRemote(const std::string & addr, int port)
+{
+	std::shared_ptr<CConnection> c;
+	int attempts = 10;
+	while(!c && attempts--)
+	{
+		try
+		{
+			logNetwork->info("Establishing connection...");
+			c = std::make_shared<CConnection>(addr, port, SERVER_NAME, uuid);
+		}
+		catch(...)
+		{
+			logNetwork->error("\nCannot establish connection! Retrying within 1 second");
+			boost::this_thread::sleep(boost::posix_time::seconds(1));
+		}
+	}
+	
+	connections.insert(c);
+	c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
+}
+
 void CVCMIServer::threadAnnounceLobby()
 {
 	while(state != EServerState::SHUTDOWN)

+ 2 - 0
server/CVCMIServer.h

@@ -74,6 +74,8 @@ public:
 	void prepareToRestart();
 	void startGameImmidiately();
 
+	void establishRemoteConnections();
+	void connectToRemote(const std::string & addr, int port);
 	void startAsyncAccept();
 	void connectionAccepted(const boost::system::error_code & ec);
 	void threadHandleClient(std::shared_ptr<CConnection> c);