diff --git a/3dzavr b/3dzavr index f80ebaf..5217578 160000 --- a/3dzavr +++ b/3dzavr @@ -1 +1 @@ -Subproject commit f80ebaf4522310a7560c19e6da2a3a8f36f2eb16 +Subproject commit 5217578e1500cbe567ddf13a44fffd60e3ad445c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a018a5..d51135b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,8 @@ add_executable(${CMAKE_PROJECT_NAME} ShooterConsts.h network/ShooterMsgType.h network/ShooterMsgType.cpp + network/Chat.cpp + network/Chat.h # 3d engine: 3dzavr/engine/Consts.h 3dzavr/engine/math/Vec4D.h diff --git a/Shooter.cpp b/Shooter.cpp index 50c2afb..b6b116e 100644 --- a/Shooter.cpp +++ b/Shooter.cpp @@ -8,9 +8,9 @@ #include "3dzavr/engine/animation/Animations.h" #include "ShooterConsts.h" #include "3dzavr/engine/io/SoundController.h" +#include "network/Chat.h" using namespace std; - // Read server/client settings and start both. // If client doesn't connect to the localhost - server doesn't start. void Shooter::initNetwork() { @@ -65,6 +65,7 @@ void Shooter::initNetwork() { client->setRemoveBonusCallBack([this](const ObjectNameTag &bonusName) { removeBonus(bonusName); }); client->setChangeEnemyWeaponCallBack( [this](const std::string &weaponName, sf::Uint16 id) { changeEnemyWeapon(weaponName, id); }); + } void Shooter::start() { @@ -96,7 +97,7 @@ void Shooter::start() { camera->translateToPoint(player->position() + Vec3D{0, 1.8, 0}); player->attach(camera); world->addBody(player); - + // Waiting for connect and updating server if it's same window while (client->isWorking() && !client->connected()) { client->update(); @@ -130,11 +131,11 @@ void Shooter::start() { server->stop(); this->exit(); }, "Exit", 5, 5, ShooterConsts::MAIN_MENU_GUI, {0, 66}, {0, 86}, {0, 46}, Consts::MEDIUM_FONT, {255, 255, 255}); + client->setChatManager(chat); } void Shooter::update() { // This code executed every time step: - server->update(); client->update(); @@ -142,41 +143,86 @@ void Shooter::update() { if (!screen->hasFocus()) { return; } - - if (keyboard->isKeyTapped(sf::Keyboard::Escape)) { - inGame = !inGame; - screen->setMouseCursorVisible(!inGame); + if (keyboard->isKeyTapped(sf::Keyboard::Enter)) { + if (isTypingMessage) { + client->sendMessage(message); + message = ""; + } + isTypingMessage = !isTypingMessage; } + if (!isTypingMessage) { + if (keyboard->isKeyTapped(sf::Keyboard::Escape)) { + inGame = !inGame; + screen->setMouseCursorVisible(!inGame); + } - if (keyboard->isKeyTapped(sf::Keyboard::O)) { - setGlEnable(!glEnable()); + if (keyboard->isKeyTapped(sf::Keyboard::O)) { + setGlEnable(!glEnable()); + } + + if (keyboard->isKeyTapped(sf::Keyboard::Tab)) { + setDebugInfo(!showDebugInfo()); + } + + if (keyboard->isKeyTapped(sf::Keyboard::P)) { + screen->startRender(); + } + + if (keyboard->isKeyTapped(sf::Keyboard::L)) { + screen->stopRender(); + } } - - if (keyboard->isKeyTapped(sf::Keyboard::Tab)) { - setDebugInfo(!showDebugInfo()); - } - - if (keyboard->isKeyTapped(sf::Keyboard::P)) { - screen->startRender(); - } - - if (keyboard->isKeyTapped(sf::Keyboard::L)) { - screen->stopRender(); - } - + if (inGame) { screen->setTitle(ShooterConsts::PROJECT_NAME); - playerController->update(); + + if (isTypingMessage) { + string symbols = screen->getInputSymbols(); + for (char s : symbols) { + if (s == (char)8) {//backspace + message = message.substr(0, message.size() - 1); + } + else if (s == (char)27) {//escape + message = ""; //FIXME: не работает потому что isKeyTapped имеют задержку, + isTypingMessage = false; //т. е. этот код выполняется после нажатия на ESC, + } // но при следующем цикле при проверке isKeyTapped(ESC) возвращается TRUE + else if (message.length() < ShooterConsts::MAX_MESSAGE_LENGTH && s!=(char)13) {//13=enter + message += s; + } + } + } + else { + playerController->update(); + } + } else { mainMenu.update(); } - + setUpdateWorld(inGame); // background sounds and music control if (SoundController::getStatus(SoundTag("background")) != sf::Sound::Status::Playing) { SoundController::loadAndPlay(SoundTag("background"), ShooterConsts::BACK_NOISE); } + + +} + +void Shooter::drawChat() { + sf::Color chatColor = isTypingMessage? sf::Color(50, 50, 50, 255) : sf::Color(50, 50, 50, chat->update(Time::deltaTime())); + string chatText = isTypingMessage ? chat->getChat() : chat->getChatPreview(); + + screen->drawText(chatText, Vec2D{ 0, (double)screen->height()*0.25 }, 20, chatColor); + + if (isTypingMessage){ + screen->drawTetragon( + Vec2D{ (double)screen->width() * 0.05, (double)screen->height() * 0.7 }, + Vec2D{ (double)screen->width() * 0.95, (double)screen->height() * 0.7 }, + Vec2D{ (double)screen->width() * 0.95, (double)screen->height() * 0.7+40 }, + Vec2D{ (double)screen->width() * 0.05, (double)screen->height() * 0.7+40 }, sf::Color(150, 150, 150, 150)); + screen->drawText(message, Vec2D{(double)screen->width() * 0.05, (double)screen->height() * 0.7}, 30, sf::Color(0, 0, 0, 255)); + } } void Shooter::gui() { @@ -192,6 +238,7 @@ void Shooter::gui() { // health player stats drawPlayerStats(); drawStatsTable(); + drawChat(); } void Shooter::drawStatsTable() { diff --git a/Shooter.h b/Shooter.h index 27a481c..6ff4c27 100644 --- a/Shooter.h +++ b/Shooter.h @@ -26,6 +26,9 @@ private: std::shared_ptr server = std::make_shared(); std::shared_ptr client = std::make_shared(player); + std::shared_ptr chat = std::make_shared(); + bool isTypingMessage = false; + string message = ""; bool inGame = false; int fireTraces = 0; @@ -34,6 +37,7 @@ private: void start() override; void update() override; void gui() override; + void drawChat(); void play(); void drawPlayerStats(); void drawStatsTable(); diff --git a/ShooterConsts.h b/ShooterConsts.h index 7bd1985..d25c5fc 100644 --- a/ShooterConsts.h +++ b/ShooterConsts.h @@ -15,6 +15,7 @@ namespace ShooterConsts { const double SLOW_MO_COEFFICIENT = 5; const double FIRE_DISTANCE = 1000; const double BONUS_RECHARGE_TIME = 30; + const int MAX_MESSAGE_LENGTH = 70; const std::string PLAYER_NAME = "Player"; const std::string PROJECT_NAME = "Shooter"; diff --git a/network/Chat.cpp b/network/Chat.cpp new file mode 100644 index 0000000..e69b9e3 --- /dev/null +++ b/network/Chat.cpp @@ -0,0 +1,39 @@ +#include "Chat.h" +#include +#include +void ChatManager::addNewMessage(std::string author, std::string message) { + hide = 7.0; + messages.push_back(message); + authors.push_back(author); + isChatUpdate = true; + if (messages.size() > 20) { + messages.erase(messages.begin()); + } +} +int ChatManager::update(double delta) { + hide = std::max(hide-delta, 0.0); + return std::min((int)(hide * 255.0), 255); + +} +std::string ChatManager::getChat() { + updateChat(); return chatStr; +} +std::string ChatManager::getChatPreview() { + updateChat(); return chatStrPrev; +} + +void ChatManager::updateChat() { + if (isChatUpdate) { + isChatUpdate = false; + int size = messages.size(); + chatStr = ""; + chatStrPrev = ""; + for (int messageIndex = size - 1; messageIndex >= 0; messageIndex--) + { + if (messageIndex > size - 6) { + chatStrPrev += authors[messageIndex] + ": " + messages[messageIndex] + "\n"; + } + chatStr += authors[messageIndex] + ": " + messages[messageIndex] + "\n"; + } + } +} diff --git a/network/Chat.h b/network/Chat.h new file mode 100644 index 0000000..9a568f5 --- /dev/null +++ b/network/Chat.h @@ -0,0 +1,23 @@ +//#ifndef SHOOTER_SHOOTERSERVER_H +#ifndef CHAT_H +#define CHAT_H +#include +#include +using namespace std; + +class ChatManager final { +private: + std::vector messages; + std::vector authors; + bool isChatUpdate = true; + std::string chatStr = ""; + std::string chatStrPrev = ""; + double hide = 0.0; + void updateChat(); +public: + void addNewMessage(std::string author, std::string message); + int update(double delta); + std::string getChat(); + std::string getChatPreview(); +}; +#endif \ No newline at end of file diff --git a/network/ShooterClient.cpp b/network/ShooterClient.cpp index fe25f7c..08d22a4 100644 --- a/network/ShooterClient.cpp +++ b/network/ShooterClient.cpp @@ -29,7 +29,6 @@ void ShooterClient::processInit(sf::Packet &packet) { if (_spawnPlayerCallBack != nullptr) { _spawnPlayerCallBack(targetId); } - _players[targetId]->translateToPoint(Vec3D{x, y, z}); _players[targetId]->setHealth(health); _players[targetId]->setKills(kills); @@ -46,7 +45,7 @@ void ShooterClient::processUpdate(sf::Packet &packet) { while (packet >> targetId >> x >> y >> z >> health >> bodyAngle >> headAngle >> playerName) { if (_players.count(targetId)) { std::string name = "Enemy_" + std::to_string(targetId); - + Vec3D newPosition = Vec3D{x, y, z}; bool isAnimate = (_players[targetId]->position() - newPosition).sqrAbs() > 0.2; @@ -123,6 +122,18 @@ void ShooterClient::processDisconnect(sf::Uint16 targetId) { } } +void ShooterClient::sendMessage(string message){ + + if (message.length() == 0) + return; + chatManager->addNewMessage(_player->playerNickName(), message); + sf::Packet packet; + packet << MsgType::Custom << ShooterMsgType::newMessage << message; + _socket.send(packet, _socket.serverId()); +} +void ShooterClient::newMessage(string message, string name) { + chatManager->addNewMessage(name, message); +} void ShooterClient::processCustomPacket(sf::Packet &packet) { sf::Uint16 buffId[2]; @@ -131,6 +142,7 @@ void ShooterClient::processCustomPacket(sf::Packet &packet) { ShooterMsgType type; packet >> type; + string name, message; switch (type) { case ShooterMsgType::Kill: @@ -227,6 +239,11 @@ void ShooterClient::processCustomPacket(sf::Packet &packet) { _changeEnemyWeaponCallBack(tmp, buffId[0]); } break; + case ShooterMsgType::newMessage: + + packet >> name >> message; + newMessage(message, name); + break; default: Log::log("ShooterClient::processCustomPacket: unknown message type " + std::to_string(static_cast(type))); @@ -304,7 +321,7 @@ ShooterClient::setChangeEnemyWeaponCallBack(std::function& listing = dirResponse.getListing(); - if (listing.size()!=0) { + + if (listing.size() != 0) { for (std::vector::const_iterator it = listing.begin(); it != listing.end(); ++it) Log::log("- "+*it); @@ -327,12 +345,12 @@ void ShooterClient::requestMap(std::string clientIp, std::string *current_map) { std::string map_path = listing.at(0); map_path = "./obj/maps"+map_path.substr(map_path.find("/")); Log::log("Map set to: "+map_path); - *current_map = map_path; + *current_map = map_path; } } else { Log::log("there is no map file"); } - } + } ftp.disconnect(); } else { diff --git a/network/ShooterClient.h b/network/ShooterClient.h index e8e6738..1f9e12a 100644 --- a/network/ShooterClient.h +++ b/network/ShooterClient.h @@ -8,6 +8,7 @@ #include "../3dzavr/engine/network/ClientUDP.h" #include "../player/Player.h" #include +#include "Chat.h" class ShooterClient final : public ClientUDP { private: @@ -22,9 +23,15 @@ private: std::function _addBonusCallBack; std::function _removeBonusCallBack; std::function _changeEnemyWeaponCallBack; + + std::shared_ptr chatManager; public: explicit ShooterClient(std::shared_ptr player) : _player(player) {}; + void sendMessage(std::string message); + + void newMessage(std::string message, std::string name); + void updatePacket() override; void setSpawnPlayerCallBack(std::function spawn); @@ -59,9 +66,11 @@ public: void changeWeapon(const std::string &weaponName); + void setChatManager(std::shared_ptr chat) { chatManager = chat; }; + void addPlayer(sf::Uint16 id, std::shared_ptr player); - void requestMap(std::string clientIp, std::string *current_map); + static void requestMap(const std::string& clientIp, std::string *current_map); [[nodiscard]] std::map> const &players() const { return _players; } diff --git a/network/ShooterMsgType.h b/network/ShooterMsgType.h index 91290e8..a49e079 100644 --- a/network/ShooterMsgType.h +++ b/network/ShooterMsgType.h @@ -14,7 +14,8 @@ enum class ShooterMsgType { InitBonuses, AddBonus, RemoveBonus, - ChangeWeapon + ChangeWeapon, + newMessage }; sf::Packet &operator<<(sf::Packet &packet, ShooterMsgType type); diff --git a/network/ShooterServer.cpp b/network/ShooterServer.cpp index c50fa95..350c692 100644 --- a/network/ShooterServer.cpp +++ b/network/ShooterServer.cpp @@ -79,6 +79,7 @@ void ShooterServer::processCustomPacket(sf::Packet &packet, sf::Uint16 senderId) double damage; std::string tmp; double newHealth; + std::string message; ShooterMsgType type; packet >> type; @@ -140,6 +141,18 @@ void ShooterServer::processCustomPacket(sf::Packet &packet, sf::Uint16 senderId) } } + break; + case ShooterMsgType::newMessage: + + packet >> message; + sendPacket << MsgType::Custom << ShooterMsgType::newMessage << _players[senderId]->playerNickName() << message; + if (message.length() == 0) + break; + for (auto& player : _players) { + if (player.first != senderId) { + _socket.send(sendPacket, player.first); + } + } break; default: Log::log("ShooterServer::processCustomPacket: unknown message type " + diff --git a/player/Player.h b/player/Player.h index 6df4eef..c77e659 100644 --- a/player/Player.h +++ b/player/Player.h @@ -17,6 +17,7 @@ #include "../weapon/Rifle.h" #include "../ShooterConsts.h" + class Player final : public RigidBody { private: double _health = ShooterConsts::HEALTH_MAX;