diff --git a/mod.json b/mod.json index 67f6be7..a354349 100755 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "geode": "5.3.0", + "geode": "5.5.1", "gd": { "win": "2.2081", "android": "2.2081", diff --git a/server/src/types/swap.ts b/server/src/types/swap.ts index f557cad..6e83066 100755 --- a/server/src/types/swap.ts +++ b/server/src/types/swap.ts @@ -75,54 +75,82 @@ export class Swap { this.currentlySwapping = true this.isSwapEnding = ending this.closeReason = reason - log.debug(this.swapOrder) + log.debug(`[Swap] Turn ${this.currentTurn}/${this.totalTurns} started for lobby ${this.lobbyCode}. Swap order: ${this.swapOrder}`) emitToLobby(this.serverState, this.lobbyCode, Packet.TimeToSwapPacket, {}) } addLevel(level: LevelData, accId: number) { + if (!this.currentlySwapping) { + log.warn(`[Swap] Received late or duplicate level from ${accId} when not swapping. Ignoring.`) + return + } + const idx = this.swapOrder.indexOf(accId) - this.levels.push( - { - accountID: parseInt(offsetArray(this.swapOrder, 1)[idx]), - level - } - ) + if (idx === -1) { + log.error(`[Swap] Player ${accId} is not in the swapOrder list!`) + return + } + + const targetAccId = parseInt(offsetArray(this.swapOrder, 1)[idx] as string) + + // FIX: Prevent duplicate submissions messing up the array length. + // If they already submitted, overwrite it. Otherwise, add it. + const existingIdx = this.levels.findIndex(l => l.accountID === targetAccId) + + if (existingIdx !== -1) { + log.warn(`[Swap] Player ${accId} sent level data AGAIN. Overwriting previous submission.`) + this.levels[existingIdx] = { accountID: targetAccId, level } + } else { + this.levels.push({ accountID: targetAccId, level }) + } + + log.info(`[Swap] Received level from ${accId} (Size: ${level.levelString.length} bytes). Forwarding to player ${targetAccId}. (${this.levels.length}/${this.swapOrder.length})`) + this.checkSwap() } checkSwap() { if (!this.currentlySwapping) return - this.lobby.accounts.forEach((acc, index) => { - if (this.serverState.lobbies[this.lobbyCode].accounts.findIndex( - lobbyAcc => lobbyAcc.accountID === acc.accountID - ) !== -1) return - - this.levels.splice(index, 1) - }) - if (getLength(this.levels) < this.lobby.accounts.length) return + + const currentLobbyAccounts = this.serverState.lobbies[this.lobbyCode].accounts + + // FIX: Instead of checking array length (which breaks on duplicates or disconnects), + // we strictly check: Does EVERY player currently in the lobby have a level assigned to them? + let allReady = true + for (const acc of currentLobbyAccounts) { + const hasLevelForThisPlayer = this.levels.some(l => l.accountID === acc.accountID) + if (!hasLevelForThisPlayer) { + allReady = false + break + } + } + + if (!allReady) { + // We are still waiting for someone to finish uploading their level + return + } + + // Everyone has submitted their level! We can proceed. this.currentlySwapping = false + log.info(`[Swap] All levels received for lobby ${this.lobbyCode}. Executing swap!`) + + // Clean up: only send levels to players who are actually still in the lobby + const validLevels = this.levels.filter(l => + currentLobbyAccounts.some(acc => acc.accountID === l.accountID) + ) if (!this.isSwapEnding) { - emitToLobby(this.serverState, this.lobbyCode, Packet.ReceiveSwappedLevelPacket, { levels: this.levels }) + emitToLobby(this.serverState, this.lobbyCode, Packet.ReceiveSwappedLevelPacket, { levels: validLevels }) this.levels = [] if (this.currentTurn >= this.totalTurns) { this.swapEnded = true setTimeout(() => emitToLobby(this.serverState, this.lobbyCode, Packet.SwapEndedPacket, {}), 750) // 0.75 seconds - return } this.scheduleNextSwap() } - // else { - // this.swapEnded = true - // this.levels = offsetArray(this.levels, this.totalTurns - this.currentTurn) - // emitToLobby(this.serverState, this.lobbyCode, Packet.ReceiveSwappedLevelPacket, { levels: this.levels }) - // Object.values(this.serverState.sockets[this.lobbyCode]).forEach(socket => { - // socket.close(1000, this.closeReason) - // }) - // } } scheduleNextSwap() { @@ -136,4 +164,3 @@ export class Swap { clearTimeout(this.timeout) } } - diff --git a/src/layers/ChatPanel.cpp b/src/layers/ChatPanel.cpp index 9174ded..0d4451d 100755 --- a/src/layers/ChatPanel.cpp +++ b/src/layers/ChatPanel.cpp @@ -13,12 +13,17 @@ ChatPanel* ChatPanel::create() { } void ChatPanel::initialize() { - // initialize the listeners n' stuff if they havent already if (!hasInitialized) { auto& nm = NetworkManager::get(); nm.on([](MessageSentPacket packet) { messages.push_back(packet.message); - messagesQueue.push_back(std::move(packet.message)); + messagesQueue.push_back(packet.message); + + // Уведомление о новом сообщении + Notification::create( + fmt::format("{} sent a message", packet.message.author.name), + CCSprite::createWithSpriteFrameName("GJ_chatBtn_001.png") + )->show(); }); hasInitialized = true; @@ -31,7 +36,6 @@ bool ChatPanel::init() { } this->setTitle("Chat"); - ChatPanel::initialize(); auto scrollContainer = CCScale9Sprite::create("square02b_001.png"); @@ -44,7 +48,6 @@ bool ChatPanel::init() { scrollLayer = ScrollLayer::create(scrollContainer->getContentSize() - 10.f); scrollLayer->ignoreAnchorPointForPosition(false); - // stolen from https://github.com/HJfod/LevelTrashcan/blob/main/src/TrashcanPopup.cpp#L41 scrollLayer->m_contentLayer->setLayout( ColumnLayout::create() ->setAxisReverse(true) @@ -53,19 +56,16 @@ bool ChatPanel::init() { ->setGap(0) ); scrollContainer->addChildAtPosition(scrollLayer, Anchor::Center); - m_mainLayer->addChildAtPosition(scrollContainer, Anchor::Center, ccp(0, 5.f)); auto inputContainer = CCNode::create(); - inputContainer->setContentSize({ - scrollContainer->getContentWidth(), - 75.f - }); - inputContainer->setAnchorPoint({ - 0.5f, 0.f - }); + inputContainer->setContentSize({ scrollContainer->getContentWidth(), 75.f }); + inputContainer->setAnchorPoint({ 0.5f, 0.f }); messageInput = TextInput::create(inputContainer->getContentWidth(), "Send a message to the lobby!", "chatFont.fnt"); + + // --- ИСПРАВЛЕНИЕ: Разрешаем любые символы (кириллицу, спецсимволы) --- + messageInput->setCommonFilter(CommonFilter::Any); auto sendMsgBtn = CCMenuItemExt::createSpriteExtraWithFrameName( "GJ_chatBtn_001.png", @@ -82,10 +82,7 @@ bool ChatPanel::init() { inputContainer->addChild(messageInput); inputContainer->addChild(msgBtnMenu); - - inputContainer->setLayout( - RowLayout::create() - ); + inputContainer->setLayout(RowLayout::create()); m_mainLayer->addChildAtPosition(inputContainer, Anchor::Bottom, ccp(0, 10.f)); for (auto message : ChatPanel::messages) { @@ -101,12 +98,7 @@ void ChatPanel::renderMessage(Message const& message) { auto msgNode = CCNode::create(); auto msgText = TextArea::create( fmt::format("{}: {}", message.author.name, message.message), - "chatFont.fnt", - 0.5f, - scrollLayer->getContentWidth(), - {0.f, 0.f}, - 17.f, - false + "chatFont.fnt", 0.5f, scrollLayer->getContentWidth(), {0.f, 0.f}, 17.f, false ); msgText->setAnchorPoint({ 0.f, 0.f }); msgNode->setContentHeight(msgText->m_label->m_lines->count() * 17.f); @@ -114,9 +106,7 @@ void ChatPanel::renderMessage(Message const& message) { msgText->setPosition({ 0.f, 0.f }); msgNode->addChild(msgText); - scrollLayer->m_contentLayer->addChild( - msgNode - ); + scrollLayer->m_contentLayer->addChild(msgNode); scrollLayer->m_contentLayer->updateLayout(); } @@ -129,7 +119,6 @@ void ChatPanel::updateMessages(float dt) { void ChatPanel::clearMessages() { messages.clear(); - auto& nm = NetworkManager::get(); nm.unbind(); hasInitialized = false; @@ -148,7 +137,6 @@ void ChatPanel::sendMessage() { void ChatPanel::keyDown(cocos2d::enumKeyCodes keycode, double timestamp) { if (keycode == cocos2d::KEY_Enter && CCIMEDispatcher::sharedDispatcher()->hasDelegate()) { - log::debug("sending via keybind"); sendMessage(); } else { Popup::keyDown(keycode, timestamp); diff --git a/src/layers/Lobby.cpp b/src/layers/Lobby.cpp index 0f2f0d4..351fcb0 100755 --- a/src/layers/Lobby.cpp +++ b/src/layers/Lobby.cpp @@ -1,22 +1,16 @@ #include "Lobby.hpp" #include - #include #include #include - #include "LobbySettings.hpp" #include "ChatPanel.hpp" - #include +// Factory method to create a player cell PlayerCell* PlayerCell::create(Account account, float width, bool canKick) { auto ret = new PlayerCell; - if (ret->init( - account, - width, - canKick - )) { + if (ret->init(account, width, canKick)) { ret->autorelease(); return ret; } @@ -24,14 +18,13 @@ PlayerCell* PlayerCell::create(Account account, float width, bool canKick) { return nullptr; } +// Initializes the player cell UI (icon, name, kick button) bool PlayerCell::init(Account account, float width, bool canKick) { m_account = account; - this->setContentSize({ - width, - CELL_HEIGHT - }); + this->setContentSize({ width, CELL_HEIGHT }); + // Create player icon based on account data auto player = SimplePlayer::create(0); auto gm = GameManager::get(); @@ -39,6 +32,7 @@ bool PlayerCell::init(Account account, float width, bool canKick) { player->setColor(gm->colorForIdx(account.color1)); player->setSecondColor(gm->colorForIdx(account.color2)); + // Disable glow if color3 is not set if (account.color3 == -1) { player->disableGlowOutline(); } else { @@ -46,39 +40,48 @@ bool PlayerCell::init(Account account, float width, bool canKick) { } player->setScale(0.65f); - player->setPosition({ 25.f, CELL_HEIGHT / 2.f}); + player->setPosition({ 25.f, CELL_HEIGHT / 2.f }); player->setAnchorPoint({ 0.5f, 0.5f }); this->addChild(player); + // Create player name label auto nameLabel = CCLabelBMFont::create(account.name.c_str(), "bigFont.fnt"); nameLabel->limitLabelWidth(225.f, 0.8f, 0.1f); - nameLabel->setPosition({ - 45.f, CELL_HEIGHT / 2.f - }); nameLabel->setAnchorPoint({ 0.f, 0.5f }); - nameLabel->ignoreAnchorPointForPosition(false); - this->addChild(nameLabel); + // Clicking the name opens their profile + auto nameBtn = CCMenuItemExt::createSpriteExtra( + nameLabel, + [this](CCObject*) { + ProfilePage::create(m_account.accountID, false)->show(); + } + ); + nameBtn->setAnchorPoint({ 0.f, 0.5f }); + nameBtn->setPosition({ 45.f, CELL_HEIGHT / 2.f }); + + auto cellMenu = CCMenu::create(); + cellMenu->setPosition({ 0.f, 0.f }); + cellMenu->setContentSize({ width, CELL_HEIGHT }); + cellMenu->addChild(nameBtn); + // Add kick button if current user is host and not kicking themselves if (canKick && account.userID != GameManager::get()->m_playerUserID.value()) { auto kickSpr = CCSprite::createWithSpriteFrameName("accountBtn_removeFriend_001.png"); kickSpr->setScale(0.725f); auto kickBtn = CCMenuItemSpriteExtra::create( kickSpr, this, menu_selector(PlayerCell::onKickUser) ); - auto kickMenu = CCMenu::create(); - kickMenu->addChild(kickBtn); - kickMenu->setPosition( - width - 25.f, CELL_HEIGHT / 2.f - ); - kickMenu->setAnchorPoint({ 0.5f, 0.5f }); - this->addChild(kickMenu); + kickBtn->setPosition({ width - 25.f, CELL_HEIGHT / 2.f }); + cellMenu->addChild(kickBtn); } + this->addChild(cellMenu); + return true; } +// Sends a kick request to the server void PlayerCell::onKickUser(CCObject* sender) { geode::createQuickPopup( "Kick User", @@ -86,13 +89,13 @@ void PlayerCell::onKickUser(CCObject* sender) { "Close", "Kick", [this](auto, bool btn2) { if (!btn2) return; - auto& nm = NetworkManager::get(); nm.send(KickUserPacket::create(m_account.userID)); } ); } +// Factory method for LobbyLayer LobbyLayer* LobbyLayer::create(std::string code) { auto ret = new LobbyLayer(); if (ret->init(code)) { @@ -103,8 +106,10 @@ LobbyLayer* LobbyLayer::create(std::string code) { return nullptr; } +// Initializes the lobby layer (background, buttons, UI) bool LobbyLayer::init(std::string code) { lobbyCode = code; + m_alive = std::make_shared(true); ChatPanel::initialize(); @@ -114,18 +119,16 @@ bool LobbyLayer::init(std::string code) { mainLayer = CCNode::create(); mainLayer->setContentSize(size); + // Setup background background = CCSprite::create("GJ_gradientBG.png"); - background->setScaleX( - size.width / background->getContentWidth() - ); - background->setScaleY( - size.height / background->getContentHeight() - ); + background->setScaleX(size.width / background->getContentWidth()); + background->setScaleY(size.height / background->getContentHeight()); background->setAnchorPoint({ 0, 0 }); background->setColor({ 0, 102, 255 }); background->setZOrder(-10); mainLayer->addChild(background); + // Setup close and disconnect buttons auto closeBtnSprite = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); closeBtn = CCMenuItemExt::createSpriteExtra( closeBtnSprite, [this](CCObject* target) { @@ -141,38 +144,34 @@ bool LobbyLayer::init(std::string code) { onDisconnect(target); } ); + auto closeMenu = CCMenu::create(); closeMenu->addChild(closeBtn); closeMenu->addChild(disconnectBtn); - closeMenu->setLayout( - RowLayout::create() - ); + closeMenu->setLayout(RowLayout::create()); closeMenu->setPosition({ 45, size.height - 25 }); mainLayer->addChild(closeMenu); geode::addSideArt(mainLayer, SideArt::Bottom); geode::addSideArt(mainLayer, SideArt::TopRight); + // Setup start button auto startBtnSpr = CCSprite::create("swap-btn.png"_spr); startBtnSpr->setScale(0.3f); startBtn = CCMenuItemSpriteExtra::create( startBtnSpr, this, menu_selector(LobbyLayer::onStart) ); + auto startMenu = CCMenu::create(); startMenu->setZOrder(5); startMenu->addChild(startBtn); - startMenu->setPosition({ - size.width / 2, 30.f - }); - + startMenu->setPosition({ size.width / 2, 30.f }); mainLayer->addChild(startMenu); + // Setup chat, settings and refresh buttons auto msgButtonSpr = CCSprite::create("messagesBtn.png"_spr); auto msgButton = CCMenuItemExt::createSpriteExtra( - msgButtonSpr, - [](CCObject*) { - ChatPanel::create()->show(); - } + msgButtonSpr, [](CCObject*) { ChatPanel::create()->show(); } ); auto settingsBtnSpr = CCSprite::createWithSpriteFrameName("GJ_optionsBtn02_001.png"); @@ -181,11 +180,11 @@ bool LobbyLayer::init(std::string code) { ); auto refreshBtn = CCMenuItemExt::createSpriteExtraWithFrameName( - "GJ_getSongInfoBtn_001.png", - 1.f, - [this](CCObject* sender) { - SwapManager::get().getLobbyInfo([this](LobbyInfo info) { - refresh(info); + "GJ_getSongInfoBtn_001.png", 1.f, + [this, alive = m_alive](CCObject* sender) { + SwapManager::get().getLobbyInfo([this, alive](LobbyInfo info) { + if (!*alive) return; + refresh(info); }); } ); @@ -196,33 +195,27 @@ bool LobbyLayer::init(std::string code) { bottomMenu->addChild(msgButton); bottomMenu->addChild(settingsBtn); bottomMenu->addChild(refreshBtn); - bottomMenu->setPosition( - size.width - 25.f, bottomMenu->getChildrenCount() * 25.f - ); - - bottomMenu->setLayout( - ColumnLayout::create() - ->setAxisReverse(true) - ); + bottomMenu->setPosition(size.width - 25.f, bottomMenu->getChildrenCount() * 25.f); + bottomMenu->setLayout(ColumnLayout::create()->setAxisReverse(true)); + // Setup Discord button auto discordSpr = CCSprite::createWithSpriteFrameName("gj_discordIcon_001.png"); discordSpr->setScale(1.25f); auto discordBtn = CCMenuItemExt::createSpriteExtra( discordSpr, - [this](CCObject* target) { + [](CCObject* target) { geode::createQuickPopup( "Discord Server", - "We have a Discord Server! If you have questions, want to suggest a feature, or find someone to swap with, join here! \nPlease not that you need to be at least 13 years of age to join our server.", + "We have a Discord Server! If you have questions, want to suggest a feature, or find someone to swap with, join here!", "Cancel", "Join!", - [this](auto, bool btn2) { + [](auto, bool btn2) { if (!btn2) return; - CCApplication::get()->openURL( - Mod::get()->getMetadata().getLinks().getCommunityURL()->c_str() - ); + CCApplication::get()->openURL(Mod::get()->getMetadata().getLinks().getCommunityURL()->c_str()); } ); } ); + auto discordMenu = CCMenu::create(); discordMenu->setPosition({ 25.f, 25.f }); discordMenu->addChild(discordBtn); @@ -233,28 +226,36 @@ bool LobbyLayer::init(std::string code) { this->addChild(mainLayer); this->addChild(bottomMenu); - SwapManager::get().getLobbyInfo([this](LobbyInfo info) { - refresh(info, true); + // Load initial lobby info + SwapManager::get().getLobbyInfo([this, alive = m_alive](LobbyInfo info) { + if (!*alive) return; + refresh(info, true); }); + registerListeners(); return true; } +// Binds network packet listeners void LobbyLayer::registerListeners() { auto& nm = NetworkManager::get(); - nm.on([this](LobbyUpdatedPacket packet) { + + nm.on([this, alive = m_alive](LobbyUpdatedPacket packet) { + if (!*alive) return; this->refresh(std::move(packet.info)); }); - nm.on([this](SwapStartedPacket packet) { + + nm.on([](SwapStartedPacket packet) { auto& sm = SwapManager::get(); sm.startSwap(std::move(packet)); NetworkManager::get().unbind(); }); + nm.showDisconnectPopup = true; - nm.setDisconnectCallback([this](std::string reason) { + nm.setDisconnectCallback([this, alive = m_alive](std::string reason) { + if (!*alive) return; unregisterListeners(); - auto& nm = NetworkManager::get(); nm.isConnected = false; nm.showDisconnectPopup = false; @@ -262,79 +263,97 @@ void LobbyLayer::registerListeners() { }); } +// Unbinds network packet listeners void LobbyLayer::unregisterListeners() { auto& nm = NetworkManager::get(); nm.unbind(); + nm.unbind(); + nm.setDisconnectCallback(nullptr); } +// Destructor LobbyLayer::~LobbyLayer() { + if (m_alive) { + *m_alive = false; + } unregisterListeners(); - // if (mainLayer) mainLayer->release(); } +// Updates UI with new lobby data from the server void LobbyLayer::refresh(LobbyInfo info, bool isFirstRefresh) { isOwner = GameManager::get()->m_playerUserID == info.settings.owner.userID; - - // loadingCircle = LoadingCircle::create(); - // loadingCircle->setParentLayer(this); - // loadingCircle->setFade(true); - // loadingCircle->show(); - auto size = CCDirector::sharedDirector()->getWinSize(); auto listWidth = size.width / 1.5f; if (!mainLayer) return; - // mainLayer->retain(); + // --- Join/Leave Notifications Logic --- if (isFirstRefresh) { - titleLabel = CCLabelBMFont::create( - info.settings.name.c_str(), - "bigFont.fnt" - ); - titleLabel->limitLabelWidth(275.f, 1.f, 0.1f); + m_currentPlayers.clear(); + for (auto& acc : info.accounts) { + m_currentPlayers.push_back(acc.userID); + } + } else { + std::vector newPlayers; + for (auto& acc : info.accounts) newPlayers.push_back(acc.userID); - auto menu = CCMenu::create(); - menu->setPosition({ - size.width / 2, size.height - 25 - }); + for (auto& acc : info.accounts) { + if (std::find(m_currentPlayers.begin(), m_currentPlayers.end(), acc.userID) == m_currentPlayers.end()) { + Notification::create(fmt::format("{} joined", acc.name), CCSprite::createWithSpriteFrameName("GJ_sStarsIcon_001.png"))->show(); + } + } + for (int id : m_currentPlayers) { + if (std::find(newPlayers.begin(), newPlayers.end(), id) == newPlayers.end()) { + Notification::create("Someone left", CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"))->show(); + } + } + m_currentPlayers = newPlayers; + } - menu->addChild( - CCMenuItemExt::createSpriteExtra( - titleLabel, - [info](CCObject* sender) { - geode::utils::clipboard::write( - info.code - ); - Notification::create("Copied code to clipboard")->show(); - } - ) + // Title label (Only create on first refresh) + if (isFirstRefresh) { + titleLabel = CCLabelBMFont::create(info.settings.name.c_str(), "bigFont.fnt"); + titleLabel->limitLabelWidth(275.f, 1.f, 0.1f); + + auto titleBtn = CCMenuItemExt::createSpriteExtra( + titleLabel, + [info](CCObject* sender) { + geode::utils::clipboard::write(info.code); + Notification::create("Copied code to clipboard")->show(); + } ); + auto menu = CCMenu::create(); + menu->setPosition({ size.width / 2, size.height - 25 }); + menu->addChild(titleBtn); mainLayer->addChild(menu); } - if (titleLabel) titleLabel->setString( - fmt::format("{} ({})", - info.settings.name, - Mod::get()->getSettingValue("hide-code") ? "......" : info.code - ).c_str() - ); + // Update title text (Hide code if setting is enabled) + if (titleLabel) { + titleLabel->setString( + fmt::format("{} ({})", info.settings.name, Mod::get()->getSettingValue("hide-code") ? "......" : info.code).c_str() + ); + } + // Rebuild the player list if (!playerList && !isFirstRefresh) return; if (playerList) playerList->removeFromParent(); - using namespace geode::utils; playerListItems = CCArray::create(); - for (auto acc : info.accounts) { - playerListItems->addObject( - PlayerCell::create( - acc, - listWidth, - isOwner - ) - ); + for (auto& acc : info.accounts) { + auto cell = PlayerCell::create(acc, listWidth, isOwner); + if (cell) playerListItems->addObject(cell); + } + + if (playerListItems->count() == 0) { + playerList = nullptr; + return; } + playerList = ListView::create(playerListItems, PlayerCell::CELL_HEIGHT, listWidth); + if (!playerList) return; + playerList->setPosition({ size.width / 2, size.height / 2 - 10.f }); playerList->setAnchorPoint({ 0.5f, 0.5f }); playerList->ignoreAnchorPointForPosition(false); @@ -343,25 +362,25 @@ void LobbyLayer::refresh(LobbyInfo info, bool isFirstRefresh) { mainLayer->addChild(playerList); - if (!mainLayer->getChildByIDRecursive("list-bg")) { - auto listBG = CCLayerColor::create({ 0, 0, 0, 85 }); + // Recreate or update list background + auto listBG = static_cast(mainLayer->getChildByIDRecursive("list-bg")); + if (!listBG) { + listBG = CCLayerColor::create({ 0, 0, 0, 85 }); listBG->ignoreAnchorPointForPosition(false); listBG->setAnchorPoint({ 0.5f, 0.5f }); - listBG->setPosition( - playerList->getPosition() - ); listBG->setZOrder(-1); listBG->setID("list-bg"); - listBG->setContentSize(playerList->getContentSize()); mainLayer->addChild(listBG); } + listBG->setPosition(playerList->getPosition()); + listBG->setContentSize(playerList->getContentSize()); - settingsBtn->setVisible(isOwner); - startBtn->setVisible(isOwner); - - // loadingCircle->fadeAndRemove(); + // Toggle button visibility based on host status + if (settingsBtn) settingsBtn->setVisible(isOwner); + if (startBtn) startBtn->setVisible(isOwner); } +// Triggered when host clicks start void LobbyLayer::onStart(CCObject* sender) { if (!isOwner) return; @@ -377,10 +396,13 @@ void LobbyLayer::onStart(CCObject* sender) { ); } +// Triggered when host clicks settings void LobbyLayer::onSettings(CCObject* sender) { auto& lm = SwapManager::get(); - lm.getLobbyInfo([this](LobbyInfo info) { - auto settingsPopup = LobbySettingsPopup::create(info.settings, [this](LobbySettings settings) { + lm.getLobbyInfo([this, alive = m_alive](LobbyInfo info) { + if (!*alive) return; + auto settingsPopup = LobbySettingsPopup::create(info.settings, [this, alive](LobbySettings settings) { + if (!*alive) return; auto& lm = SwapManager::get(); lm.updateLobby(settings); }); @@ -388,65 +410,36 @@ void LobbyLayer::onSettings(CCObject* sender) { }); } +// Draws borders and corners around the player list void LobbyLayer::createBorders() { - // SIDES // - #define CREATE_SIDE() CCSprite::createWithSpriteFrameName("GJ_table_side_001.png") - + const int SIDE_OFFSET = 7; const int TOP_BOTTOM_OFFSET = 8; - // TOP // - auto topSide = CREATE_SIDE(); - topSide->setScaleY( - playerList->getContentWidth() / topSide->getContentHeight() - ); + topSide->setScaleY(playerList->getContentWidth() / topSide->getContentHeight()); topSide->setRotation(90.f); - topSide->setPosition({ - playerList->m_width / 2, - playerList->m_height + TOP_BOTTOM_OFFSET - }); + topSide->setPosition({ playerList->m_width / 2, playerList->m_height + TOP_BOTTOM_OFFSET }); topSide->setID("top-border"); topSide->setZOrder(3); - // BOTTOM // - auto bottomSide = CREATE_SIDE(); - bottomSide->setScaleY( - playerList->getContentWidth() / bottomSide->getContentHeight() - ); + bottomSide->setScaleY(playerList->getContentWidth() / bottomSide->getContentHeight()); bottomSide->setRotation(-90.f); - bottomSide->setPosition({ - playerList->m_width / 2, - 0 - TOP_BOTTOM_OFFSET - }); + bottomSide->setPosition({ playerList->m_width / 2, 0.f - TOP_BOTTOM_OFFSET }); bottomSide->setID("bottom-border"); bottomSide->setZOrder(3); - // LEFT // - auto leftSide = CREATE_SIDE(); - leftSide->setScaleY( - (playerList->getContentHeight() + TOP_BOTTOM_OFFSET) / leftSide->getContentHeight() - ); - leftSide->setPosition({ - -SIDE_OFFSET, - playerList->m_height / 2 - }); + leftSide->setScaleY((playerList->getContentHeight() + TOP_BOTTOM_OFFSET) / leftSide->getContentHeight()); + leftSide->setPosition({ -SIDE_OFFSET, playerList->m_height / 2 }); leftSide->setID("left-border"); - // RIGHT // - auto rightSide = CREATE_SIDE(); - rightSide->setScaleY( - (playerList->getContentHeight() + TOP_BOTTOM_OFFSET) / rightSide->getContentHeight() - ); + rightSide->setScaleY((playerList->getContentHeight() + TOP_BOTTOM_OFFSET) / rightSide->getContentHeight()); rightSide->setRotation(180.f); - rightSide->setPosition({ - playerList->m_width + SIDE_OFFSET, - playerList->m_height / 2 - }); + rightSide->setPosition({ playerList->m_width + SIDE_OFFSET, playerList->m_height / 2 }); rightSide->setID("right-border"); playerList->addChild(topSide); @@ -454,45 +447,29 @@ void LobbyLayer::createBorders() { playerList->addChild(leftSide); playerList->addChild(rightSide); - // CORNERS // - #define CREATE_CORNER() CCSprite::createWithSpriteFrameName("GJ_table_corner_001.png") - // TOP-LEFT // - auto topLeftCorner = CREATE_CORNER(); - topLeftCorner->setPosition({ - leftSide->getPositionX(), topSide->getPositionY() - }); + topLeftCorner->setPosition({ leftSide->getPositionX(), topSide->getPositionY() }); topLeftCorner->setZOrder(2); topLeftCorner->setID("top-left-corner"); - // TOP-RIGHT // auto topRightCorner = CREATE_CORNER(); topRightCorner->setFlipX(true); - topRightCorner->setPosition({ - rightSide->getPositionX(), topSide->getPositionY() - }); + topRightCorner->setPosition({ rightSide->getPositionX(), topSide->getPositionY() }); topRightCorner->setZOrder(2); topRightCorner->setID("top-right-corner"); - // BOTTOM-LEFT // - auto bottomLeftCorner = CREATE_CORNER(); bottomLeftCorner->setFlipY(true); - bottomLeftCorner->setPosition({ - leftSide->getPositionX(), bottomSide->getPositionY() - }); + bottomLeftCorner->setPosition({ leftSide->getPositionX(), bottomSide->getPositionY() }); bottomLeftCorner->setZOrder(2); bottomLeftCorner->setID("bottom-left-corner"); - // BOTTOM-RIGHT // auto bottomRightCorner = CREATE_CORNER(); bottomRightCorner->setFlipX(true); bottomRightCorner->setFlipY(true); - bottomRightCorner->setPosition({ - rightSide->getPositionX(), bottomSide->getPositionY() - }); + bottomRightCorner->setPosition({ rightSide->getPositionX(), bottomSide->getPositionY() }); bottomRightCorner->setZOrder(2); bottomRightCorner->setID("bottom-right-corner"); @@ -502,33 +479,31 @@ void LobbyLayer::createBorders() { playerList->addChild(bottomRightCorner); } +// Disconnects from the server and lobby completely void LobbyLayer::onDisconnect(CCObject* sender) { geode::createQuickPopup( "Disconnect", "Are you sure you want to disconnect from the lobby and server?", "Cancel", "Yes", - [this](auto, bool btn2) { + [](auto, bool btn2) { if (!btn2) return; - auto& nm = NetworkManager::get(); nm.showDisconnectPopup = false; - auto& lm = SwapManager::get(); lm.disconnectLobby(); - cr::utils::popScene(); } ); } +// Called when player clicks the back arrow (Leaves menu, keeps connection alive) void LobbyLayer::keyBackClicked() { geode::createQuickPopup( "Leave Layer?", "Are you sure you want to leave the layer? This will not disconnect you and will allow you to do other things ingame. You will be booted to the level page when the swap begins.", "No", "Yes", - [this](auto, bool btn2) { + [](auto, bool btn2) { if (!btn2) return; - cr::utils::popScene(); } ); diff --git a/src/layers/Lobby.hpp b/src/layers/Lobby.hpp index 254c9f4..5e678c0 100755 --- a/src/layers/Lobby.hpp +++ b/src/layers/Lobby.hpp @@ -1,13 +1,15 @@ +#pragma once + #include #include - #include +#include using namespace geode::prelude; class CR_DLL PlayerCell : public CCLayer { protected: - Account m_account; + Account m_account{}; bool init(Account account, float width, bool canKick); void onKickUser(CCObject*); @@ -20,32 +22,26 @@ class CR_DLL LobbyLayer : public CCLayer { protected: std::string lobbyCode; bool isOwner = false; - std::string lobbyNspace; - - CCMenuItemSpriteExtra* closeBtn; - CCSprite* background; - - CCArray* playerListItems; - CustomListView* playerList; - - CCLabelBMFont* titleLabel; - - CCMenuItemSpriteExtra* settingsBtn; - CCMenuItemSpriteExtra* startBtn; - - LoadingCircle* loadingCircle; - CCNode* mainLayer; + std::shared_ptr m_alive; + std::vector m_currentPlayers; + + CCMenuItemSpriteExtra* closeBtn = nullptr; + CCSprite* background = nullptr; + CCArray* playerListItems = nullptr; + CustomListView* playerList = nullptr; + CCLabelBMFont* titleLabel = nullptr; + CCMenuItemSpriteExtra* settingsBtn = nullptr; + CCMenuItemSpriteExtra* startBtn = nullptr; + LoadingCircle* loadingCircle = nullptr; + CCNode* mainLayer = nullptr; bool init(std::string code); void keyBackClicked(); void createBorders(); - void refresh(LobbyInfo info, bool isFirstRefresh = false); - void registerListeners(); void unregisterListeners(); - void onDisconnect(CCObject*); void onStart(CCObject*); void onSettings(CCObject*); @@ -54,4 +50,3 @@ class CR_DLL LobbyLayer : public CCLayer { public: static LobbyLayer* create(std::string code); }; - diff --git a/src/types/lobby.hpp b/src/types/lobby.hpp index 9e69d01..82936fb 100755 --- a/src/types/lobby.hpp +++ b/src/types/lobby.hpp @@ -31,6 +31,7 @@ struct LobbySettings { // std::string password; int turns; int minutesPerTurn; + int tag = 0; Account owner; bool isPublic;