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:
parent
aac859a030
commit
dd45d1a9cf
@ -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)
|
||||
{
|
||||
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,9 +355,6 @@ 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());
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
@ -39,6 +58,42 @@ 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()
|
||||
@ -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,6 +112,7 @@ sessions = dict()
|
||||
def handleDisconnection(client: socket):
|
||||
sender = client_sockets[client]
|
||||
if sender["joined"]:
|
||||
if not sender["session"].started:
|
||||
if sender["session"].host == client:
|
||||
#destroy the session, sending messages inside the function
|
||||
deleteSession(sender["session"])
|
||||
@ -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] == '':
|
||||
@ -232,8 +361,12 @@ def dispatch(client: socket, sender: dict, msg: str):
|
||||
updateStatus(sender["session"])
|
||||
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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user