1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Develop game part

This commit is contained in:
nordsoft 2022-10-25 05:27:53 +04:00
parent aac859a030
commit dd45d1a9cf
7 changed files with 256 additions and 31 deletions

View File

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

View File

@ -253,7 +253,7 @@
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"default": {}, "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" : { "properties" : {
"server" : { "server" : {
"type":"string", "type":"string",
@ -263,6 +263,10 @@
"type" : "number", "type" : "number",
"default" : 3030 "default" : 3030
}, },
"serverport" : {
"type" : "number",
"default" : 5002
},
"localInformation" : { "localInformation" : {
"type" : "number", "type" : "number",
"default" : 2 "default" : 2
@ -291,14 +295,32 @@
"type" : "string", "type" : "string",
"default" : "" "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" : { "names" : {
"type" : "array", "type" : "array",
"default" : {}, "default" : [],
"items": "items":
{ {
"type" : "string", "type" : "string",
"default" : "" "default" : ""
} }
},
"lobby" : {
"type" : "boolean",
"default" : false
} }
} }
}, },

View File

@ -76,6 +76,7 @@ void SocketLobby::connected()
void SocketLobby::disconnected() void SocketLobby::disconnected()
{ {
isConnected = false; isConnected = false;
emit disconnect();
emit text("Disconnected!"); emit text("Disconnected!");
} }
@ -105,6 +106,7 @@ Lobby::Lobby(QWidget *parent) :
connect(&socketLobby, SIGNAL(text(QString)), this, SLOT(chatMessage(QString))); connect(&socketLobby, SIGNAL(text(QString)), this, SLOT(chatMessage(QString)));
connect(&socketLobby, SIGNAL(receive(QString)), this, SLOT(dispatchMessage(QString))); connect(&socketLobby, SIGNAL(receive(QString)), this, SLOT(dispatchMessage(QString)));
connect(&socketLobby, SIGNAL(disconnect()), this, SLOT(onDisconnected()));
} }
Lobby::~Lobby() Lobby::~Lobby()
@ -189,9 +191,26 @@ void Lobby::serverCommand(const ServerCommand & command) try
ui->roomChat->setPlainText(resText); ui->roomChat->setPlainText(resText);
break; break;
case START: case START: {
protocolAssert(args.size() == 1);
//actually start game //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; 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: case CHAT:
protocolAssert(args.size() > 1); protocolAssert(args.size() > 1);
@ -231,6 +250,11 @@ catch(const ProtocolError & e)
chatMessage(QString("System error: %1").arg(e.what())); 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) void Lobby::chatMessage(QString txt)
{ {

View File

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

View File

@ -1,4 +1,9 @@
from calendar import c
from distutils.command.clean import clean
from pickletools import bytes8
import socket import socket
import re
import uuid
from threading import Thread from threading import Thread
# server's IP address # 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 # initialize list/set of all connected client's sockets
client_sockets = dict() client_sockets = dict()
class GameConnection:
server: socket
client: socket
serverInit = False
clientInit = False
messageQueueIn = []
messageQueueOut = []
def __init__(self) -> None:
pass
class Session: class Session:
total = 1 total = 1
joined = 0 joined = 0
@ -15,7 +32,9 @@ class Session:
protected = False protected = False
name: str name: str
host: socket host: socket
host_uuid: str
players = [] players = []
connections = []
started = False started = False
def __init__(self, host: socket, name: str) -> None: def __init__(self, host: socket, name: str) -> None:
@ -39,6 +58,42 @@ class Session:
self.players.remove(player) self.players.remove(player)
self.joined -= 1 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 # create a TCP socket
s = socket.socket() s = socket.socket()
@ -47,7 +102,7 @@ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind the socket to the address we specified # bind the socket to the address we specified
s.bind((SERVER_HOST, SERVER_PORT)) s.bind((SERVER_HOST, SERVER_PORT))
# listen for upcoming connections # listen for upcoming connections
s.listen(5) s.listen(10)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}") print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")
# list of active sessions # list of active sessions
@ -57,6 +112,7 @@ sessions = dict()
def handleDisconnection(client: socket): def handleDisconnection(client: socket):
sender = client_sockets[client] sender = client_sockets[client]
if sender["joined"]: if sender["joined"]:
if not sender["session"].started:
if sender["session"].host == client: if sender["session"].host == client:
#destroy the session, sending messages inside the function #destroy the session, sending messages inside the function
deleteSession(sender["session"]) deleteSession(sender["session"])
@ -85,9 +141,13 @@ def broadcast(clients: list, message: str):
def sendSessions(client: socket): def sendSessions(client: socket):
msg = f":>>SESSIONS:{len(sessions.keys())}" msg2 = ""
counter = 0
for s in sessions.values(): 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) send(client, msg)
@ -114,10 +174,79 @@ def updateStatus(session: Session):
broadcast(session.players, msg) broadcast(session.players, msg)
def dispatch(client: socket, sender: dict, msg: str): def startSession(session: Session):
if msg == '': 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 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('<') _open = msg.partition('<')
_close = _open[2].partition('>') _close = _open[2].partition('>')
if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '': if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '':
@ -232,8 +361,12 @@ def dispatch(client: socket, sender: dict, msg: str):
updateStatus(sender["session"]) 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): def listen_for_client(cs):
@ -244,7 +377,7 @@ def listen_for_client(cs):
while True: while True:
try: try:
# keep listening for a message from `cs` socket # keep listening for a message from `cs` socket
msg = cs.recv(2048).decode() msg = cs.recv(2048)
except Exception as e: except Exception as e:
# client no longer connected # client no longer connected
print(f"[!] Error: {e}") print(f"[!] Error: {e}")
@ -259,7 +392,7 @@ while True:
client_socket, client_address = s.accept() client_socket, client_address = s.accept()
print(f"[+] {client_address} connected.") print(f"[+] {client_address} connected.")
# add the new connected client to connected sockets # 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 # start a new thread that listens for each client's messages
t = Thread(target=listen_for_client, args=(client_socket,)) t = Thread(target=listen_for_client, args=(client_socket,))
# make the thread daemon so it ends whenever the main thread ends # make the thread daemon so it ends whenever the main thread ends

View File

@ -170,6 +170,7 @@ void CVCMIServer::run()
#endif #endif
startAsyncAccept(); startAsyncAccept();
establishRemoteConnections();
#if defined(VCMI_ANDROID) #if defined(VCMI_ANDROID)
CAndroidVMHelper vmHelper; CAndroidVMHelper vmHelper;
@ -195,6 +196,45 @@ void CVCMIServer::run()
boost::this_thread::sleep(boost::posix_time::milliseconds(50)); 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() void CVCMIServer::threadAnnounceLobby()
{ {
while(state != EServerState::SHUTDOWN) while(state != EServerState::SHUTDOWN)

View File

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