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()
: 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()

View File

@ -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
}
}
},

View File

@ -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)
{

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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);