#include "server.h" #include "util.h" #include "pacman.pb.h" #include "block.h" #include "anyoption.h" #include #include #include Server::Server(QWidget *parent) : SceneHolder(parent), m_bindaddress(QHostAddress::Any), m_port(Constants::Networking::port), m_numbots(0) { /* determine max players by using order array */ for(m_maxplayers = 0; Color::order[m_maxplayers] != Color::none; ++m_maxplayers); } bool Server::run() { qDebug() << "[Server] Running server..."; qDebug() << "[Server] Max players:" << m_maxplayers; qDebug() << "[Server] Number of bots:" << m_numbots; if (!waitForClientConnections()) return false; qDebug() << "[Server] Creating map..."; Transmission::map_t map = Util::createDemoMap(); Util::placeActors(map, m_maxplayers, Color::order); Util::makePoints(map); updateMap(map); sendUpdate(map); Util::deleteMap(map); map = NULL; QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(tick())); timer->start(Constants::tick); return true; } void Server::tick() { qDebug() << "[Tick] Doing server update"; Transmission::map_t map = calculateUpdates(); updateMap(map); sendUpdate(map); Util::deleteMap(map); } Transmission::map_t Server::calculateUpdates() { Transmission::map_t map = Util::createEmptyMap(); //TODO: ai //m_actorMovements[Color::blue] = Actor::Movement((qrand() % 4) + 1); //m_actorMovements[Color::green] = Actor::Movement((qrand() % 4) + 1); QMutableMapIterator i(m_actorMovements); while (i.hasNext()) { i.next(); int turn = 0; invalid_direction: ++turn; Actor *actor = m_actors.value(i.key()); QPoint mapPosition = CoordToMapPosition(actor->pos().toPoint()); qDebug() << "[Calc] Actor wants to move: color=" << i.key() << "pos=" << mapPosition << "direction=" << i.value(); QPoint newMapPosition = mapPosition; switch (i.value()) { case Actor::Up: newMapPosition += QPoint(0, -1); break; case Actor::Down: newMapPosition += QPoint(0, 1); break; case Actor::Left: newMapPosition += QPoint(-1, 0); break; case Actor::Right: newMapPosition += QPoint(1, 0); break; case Actor::None: break; default: Q_ASSERT(false); } if (newMapPosition.x() < 0) newMapPosition.setX(0); if (newMapPosition.x() >= visualMap.size()) newMapPosition.setX(visualMap.size() - 1); if (newMapPosition.y() < 0) newMapPosition.setY(0); if (newMapPosition.y() >= visualMap[newMapPosition.x()].size()) newMapPosition.setY(visualMap[newMapPosition.x()].size() - 1); // // TODO: support actors eating each other // TODO: old item - REMOVE THAT? GameEntity *oldItem = visualMap[mapPosition.x()][mapPosition.y()]; /* check if there's an item at new location of actor */ GameEntity *item = visualMap[newMapPosition.x()][newMapPosition.y()]; if (item != NULL && oldItem != item) { qDebug() << "[Calc] Found item at new actor location"; if (!item->checkEnter(actor)) { /* movement invalid. e.g. move against wall */ newMapPosition = mapPosition; qDebug() << "[Calc] Item blocks actor"; } else { /* apply actions of entering this field */ bool survive = item->enter(actor); if (!survive) { map[newMapPosition.x()][newMapPosition.y()] = Transmission::empty | actor->color(); } } } // /* movement didn't work - e.g. was blocked */ if (mapPosition == newMapPosition) { /* */ if (turn == 1 && i.value() != actor->direction()) { /* set direction back to last known direction and try again */ qDebug() << "[Calc] Movement was blocked. Trying last known actor direction"; m_actorMovements[i.key()] = actor->direction(); goto invalid_direction; } else { /* second turn didn't work too -> stop movement */ m_actorMovements[i.key()] = Actor::None; qDebug() << "[Calc] No good direction known. Movement stopped"; } } map[newMapPosition.x()][newMapPosition.y()] |= Transmission::pacman | i.key() | Util::actorMovementToTransmission(i.value()); /* DEBUG: uncomments to disable auto-movement */ //m_actorMovements[i.key()] = Actor::None; if (i.value() == Actor::None) { /* set actor to non-moving */ m_actorMovements[i.key()] = Actor::None; i.remove(); } } return map; } bool Server::waitForClientConnections() { // server must stay alive as long as sockets (qt parent mem mechanism) QTcpServer *tcpSrv = new QTcpServer(this); tcpSrv->listen(m_bindaddress, m_port); if (!tcpSrv->isListening()) { qCritical() << "Error while creating socket:" << qPrintable(tcpSrv->errorString()); return false; } qDebug() << "[Server] Listening on:" << qPrintable(QString("%1:%2").arg(tcpSrv->serverAddress().toString()) .arg(tcpSrv->serverPort())); qDebug() << "[Server] Waiting for clients"; ProtoBuf::Init packet; for (unsigned int i = 0; i < m_maxplayers; ++i) { bool connectionAvailable = tcpSrv->waitForNewConnection(-1); Q_ASSERT(connectionAvailable); Q_UNUSED(connectionAvailable); QTcpSocket *socket = tcpSrv->nextPendingConnection(); connect(socket, SIGNAL(readyRead()), this, SLOT(keyPressUpdate())); /* assign color */ Color::Color color = Color::order[i]; m_clientConnections[color] = socket; /* notify player of color */ packet.set_color(color); packet.set_maxplayers(m_maxplayers); Util::sendPacket(packet, socket); qDebug() << "[Connect] New Player: color=" << color; } qDebug() << "[Server] All Clients connected"; return true; } void Server::sendUpdate(Transmission::map_t map) { m_updatepacket.Clear(); for (unsigned int x = 0; x < Constants::map_size.width; ++x) { for (unsigned int y = 0; y < Constants::map_size.height; ++y) m_updatepacket.add_field(map[x][y]); } for(unsigned i = 0; i < m_maxplayers; ++i) { m_updatepacket.add_round_points(m_actors.value(Color::order[i])->getRoundPoints()); m_updatepacket.add_game_points(m_actors.value(Color::order[i])->getGamePoints()); } QSharedPointer data = Util::createPacket(m_updatepacket); foreach(QTcpSocket *socket, m_clientConnections) { if (!Util::sendPacket(data.data(), socket)) { qDebug() << "[sendUpdate] Error while sending data to client, exiting..."; qApp->quit(); } } } void Server::keyPressUpdate() { ProtoBuf::KeyPressUpdate packet; QMapIterator i(m_clientConnections); while (i.hasNext()) { i.next(); Color::Color color = i.key(); QTcpSocket *socket = i.value(); QDataStream in(i.value()); while (socket->bytesAvailable() > (qint64)sizeof(qint64)) { QSharedPointer data = Util::receivePacket(socket); bool worked = packet.ParseFromArray(data->data(), data->size()); Q_ASSERT(worked); Q_UNUSED(worked); Transmission::field_t direction = packet.newkey(); qDebug() << "[KeyPress] actor=" << color << "direction=" << direction; m_actorMovements[color] = Util::transmissionMovementToActor(direction); } } } bool Server::parseCommandline() { AnyOption opt; opt.setVerbose(); /* usage strings must remain valid until parsing is done */ QString exec = QFileInfo(qApp->applicationFilePath()).fileName(); QByteArray usage; QTextStream out(&usage, QIODevice::ReadWrite | QIODevice::Text); out << "Usage: " << exec << " [OPTION]" << endl << "Usage: " << exec << " -h" << endl << endl; out << " -b, --bind " << endl << " Specifies the ip address on which the server listens for connections" << endl << " Default: " << m_bindaddress.toString() << endl << endl; opt.setOption("bind", 'b'); out << " -p, --port " << endl << " Specifies the port on which the server listens for connections" << endl << " Default: " << m_port << endl << endl; opt.setOption("port", 'p'); out << " -m, --maxplayers [1.." << m_maxplayers << "]" << endl << " Specifies the maximum number of players/pacmans" << endl << " Default: " << m_maxplayers << endl << endl; opt.setOption("maxplayers", 'm'); out << " --bots [0..maxplayers-1]" << endl << " Specifies the number of AI pacmans/bots" << endl << " Default: " << m_numbots << endl << endl; opt.setOption("bots"); out << " -h, --help" << endl << " Prints this help message" << endl; opt.setFlag("help", 'h'); out.flush(); opt.addUsage(usage.constData()); opt.processCommandArgs(qApp->argc(), qApp->argv()); if (opt.getFlag("help") || opt.getFlag('h')) { opt.printUsage(); return false; } if (opt.getValue("port") != NULL) { bool ok; m_port = QString(opt.getValue("port")).toUInt(&ok); if (!ok || m_port < 1 || m_port > 65535) { qCritical() << "Invalid port-option:" << opt.getValue("port") << endl << "Port must be between 1 and 65535"; return false; } } if (opt.getValue("bind") != NULL) { m_bindaddress = opt.getValue("bind"); if (m_bindaddress.isNull()) { qCritical() << "Invalid bind-option:" << opt.getValue("bind") << endl << "Bind address must be an ip address"; return false; } } if (opt.getValue("maxplayers") != NULL) { bool ok; unsigned int maxplayers = QString(opt.getValue("maxplayers")).toUInt(&ok); if (!ok || maxplayers < 1 || maxplayers > m_maxplayers) { qCritical() << "Invalid maxplayers-option:" << opt.getValue("maxplayers") << endl << "Maxplayers must be between 1 and" << m_maxplayers; return false; } m_maxplayers = maxplayers; } if (opt.getValue("bots") != NULL) { bool ok; unsigned int numbots = QString(opt.getValue("bots")).toUInt(&ok); if (!ok || numbots >= m_maxplayers) { qCritical() << "Invalid numbots-options:" << opt.getValue("bots") << endl << "AI pacmans/bots must be between 0 and" << m_maxplayers - 1; return false; } m_numbots = numbots; } return true; } bool Constants::server = true; int main(int argc, char ** argv) { /* Verify that the version of the library that we linked against is * compatible with the version of the headers we compiled against. */ GOOGLE_PROTOBUF_VERIFY_VERSION; QApplication app(argc, argv, false); app.setApplicationName("Pacman Server"); app.setWindowIcon(QIcon(":/appicon")); qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime())); int ret = 0; Server server; if (!ret && !server.parseCommandline()) ret = 1; if (!ret && !server.run()) ret = 1; if (!ret) ret = app.exec(); /* Delete all global objects allocated by libprotobuf */ google::protobuf::ShutdownProtobufLibrary(); return ret; }