From 373d700ab3f82be028cd92afcf15e58a71b5c6c1 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Wed, 29 Jun 2022 21:11:29 -0700 Subject: [PATCH 01/21] fix buggy frame editing, plus some lobby improvements --- src/ui/Field.cpp | 69 +++++++++----------- src/ui/Frame.cpp | 2 +- src/ui/MainMenu.cpp | 149 +++++++++++++++++++++++++++++++++----------- src/ui/Widget.cpp | 2 +- 4 files changed, 143 insertions(+), 79 deletions(-) diff --git a/src/ui/Field.cpp b/src/ui/Field.cpp index 7dce138e5..f4d6ff8a2 100644 --- a/src/ui/Field.cpp +++ b/src/ui/Field.cpp @@ -72,6 +72,7 @@ void Field::activate() { if (activated) { deactivate(); } else { + cursorflash = ticks; activated = true; inputstr = text; inputlen = textlen; @@ -347,11 +348,21 @@ Field::result_t Field::process(SDL_Rect _size, SDL_Rect _actualSize, const bool result.highlighted = false; result.highlightTime = SDL_GetTicks(); result.entered = false; - if (!editable) { - if (activated) { - result.entered = true; + if (activated) { + if (!editable || inputstr != text) { deactivate(); - } + } else if (editable) { + if (Input::keys[SDL_SCANCODE_RETURN] || Input::keys[SDL_SCANCODE_KP_ENTER]) { + Input::keys[SDL_SCANCODE_RETURN] = 0; + Input::keys[SDL_SCANCODE_KP_ENTER] = 0; + result.entered = true; + deactivate(); + } + if (Input::keys[SDL_SCANCODE_ESCAPE]) { + Input::keys[SDL_SCANCODE_ESCAPE] = 0; + deactivate(); + } + } } if (disabled) { highlightTime = result.highlightTime; @@ -361,7 +372,14 @@ Field::result_t Field::process(SDL_Rect _size, SDL_Rect _actualSize, const bool if (!usable) { highlightTime = result.highlightTime; highlighted = false; - return result; + if (!activated) { + return result; + } else if (mousestatus[SDL_BUTTON_LEFT]) { + //mousestatus[SDL_BUTTON_LEFT] = 0; + if (activated) { + deactivate(); + } + } } _size.x += std::max(0, size.x - _actualSize.x); @@ -370,6 +388,12 @@ Field::result_t Field::process(SDL_Rect _size, SDL_Rect _actualSize, const bool _size.h = std::min(size.h, _size.h - size.y + _actualSize.y) + std::min(0, size.y - _actualSize.y); if (_size.w <= 0 || _size.h <= 0) { highlightTime = result.highlightTime; + if (mousestatus[SDL_BUTTON_LEFT]) { + //mousestatus[SDL_BUTTON_LEFT] = 0; + if (activated) { + deactivate(); + } + } return result; } @@ -397,37 +421,6 @@ Field::result_t Field::process(SDL_Rect _size, SDL_Rect _actualSize, const bool Sint32 omousey = (inputs.getMouse(mouseowner, Inputs::OY) / (float)yres) * (float)Frame::virtualScreenY; #endif - if (activated && editable) { - if (inputstr != text) { - result.entered = true; - deactivate(); - if (inputstr == nullptr) { - SDL_StopTextInput(); - } - } - if (Input::keys[SDL_SCANCODE_RETURN] || Input::keys[SDL_SCANCODE_KP_ENTER]) { - Input::keys[SDL_SCANCODE_RETURN] = 0; - Input::keys[SDL_SCANCODE_KP_ENTER] = 0; - result.entered = true; - deactivate(); - } - if (Input::keys[SDL_SCANCODE_ESCAPE]) { - Input::keys[SDL_SCANCODE_ESCAPE] = 0; - result.entered = true; - deactivate(); - } - - /*if (selectAll) { - if (mainEngine->getAnyKeyStatus()) { - const char* keys = lastkeypressed; - if (keys) { - text = keys; - selectAll = false; - } - } - }*/ - } - #if !defined(NINTENDO) && !defined(EDITOR) if (rectContainsPoint(_size, omousex, omousey) && inputs.getVirtualMouse(mouseowner)->draw_cursor) { result.highlighted = highlighted = true; @@ -443,15 +436,11 @@ Field::result_t Field::process(SDL_Rect _size, SDL_Rect _actualSize, const bool if (!result.highlighted && mousestatus[SDL_BUTTON_LEFT]) { //mousestatus[SDL_BUTTON_LEFT] = 0; if (activated) { - result.entered = true; deactivate(); } } else if (result.highlighted && mousestatus[SDL_BUTTON_LEFT]) { mousestatus[SDL_BUTTON_LEFT] = 0; activate(); - /*if (doubleclick_mousestatus[SDL_BUTTON_LEFT]) { - selectAll = true; - }*/ } } #else diff --git a/src/ui/Frame.cpp b/src/ui/Frame.cpp index a0880226f..7cb9db281 100644 --- a/src/ui/Frame.cpp +++ b/src/ui/Frame.cpp @@ -1340,7 +1340,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } } - if (fieldResult.entered || (destWidget && field->isSelected())) { + if (fieldResult.entered) { result.usable = usable = false; if (field->getCallback()) { (*field->getCallback())(*field); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 7ef2332dd..f265ca49d 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -853,7 +853,9 @@ namespace MainMenu { soundActivate(); Frame* frame = createPrompt("text_field_prompt"); - assert(frame); + if (!frame) { + return nullptr; + } auto text_box = frame->addImage( SDL_Rect{(364 - 246) / 2, 32, 246, 36}, @@ -866,6 +868,8 @@ namespace MainMenu { auto field = frame->addField("field", field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); + field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); field->setEditable(true); field->setScroll(true); field->setGuide(guide_text); @@ -936,7 +940,9 @@ namespace MainMenu { soundActivate(); Frame* frame = createPrompt("binary_prompt"); - assert(frame); + if (!frame) { + return nullptr; + } auto text = frame->addField("text", 128); text->setSize(SDL_Rect{30, 28, 304, 46}); @@ -992,7 +998,9 @@ namespace MainMenu { soundActivate(); Frame* frame = createPrompt("mono_prompt"); - assert(frame); + if (!frame) { + return nullptr; + } auto text = frame->addField("text", 128); text->setSize(SDL_Rect{30, 28, 304, 46}); @@ -1028,7 +1036,9 @@ namespace MainMenu { soundActivate(); Frame* frame = createPrompt(name, small); - assert(frame); + if (!frame) { + return nullptr; + } auto text = frame->addField("text", 128); text->setSize(SDL_Rect{30, 12, frame->getSize().w - 60, frame->getSize().h - 34}); @@ -3026,6 +3036,8 @@ namespace MainMenu { auto field = frame.addField((fullname + "_text_field").c_str(), field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); + field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); field->setEditable(true); field->setScroll(true); field->setGuide(tip); @@ -3035,6 +3047,10 @@ namespace MainMenu { field->setHJustify(Field::justify_t::LEFT); field->setVJustify(Field::justify_t::CENTER); field->setCallback(callback); + field->setTickCallback([](Widget& widget){ + auto field = static_cast(&widget); + (*field->getCallback())(*field); + }); field->setColor(makeColor(166, 123, 81, 255)); field->setBackgroundSelectAllColor(makeColor(52, 30, 22, 255)); field->setBackgroundActivatedColor(makeColor(52, 30, 22, 255)); @@ -4451,7 +4467,7 @@ namespace MainMenu { y += settingsAddSubHeader(*settings_subwindow, y, "lan", "LAN"); y += settingsAddField(*settings_subwindow, y, "port_number", "Port", "The port number to use when hosting a LAN lobby.", - buf, [](Field& field){soundActivate(); allSettings.port_number = (Uint16)strtol(field.getText(), nullptr, 10);}); + buf, [](Field& field){allSettings.port_number = (Uint16)strtol(field.getText(), nullptr, 10);}); y += settingsAddSubHeader(*settings_subwindow, y, "crossplay", "Crossplay"); y += settingsAddBooleanWithCustomizeOption(*settings_subwindow, y, "crossplay", "Crossplay Enabled", @@ -5622,6 +5638,8 @@ namespace MainMenu { /******************************************************************************/ + static Frame* toggleLobbyChatWindow(); + struct LobbyChatMessage { Uint32 timestamp; Uint32 color; @@ -5651,9 +5669,15 @@ namespace MainMenu { if (!lobby) { return; } + auto frame = lobby->findFrame("chat window"); if (!frame) { - return; + frame = toggleLobbyChatWindow(); + assert(frame); + } + + if (add_to_list) { + playSound(238, 64); } const int w = frame->getSize().w; @@ -5705,13 +5729,13 @@ namespace MainMenu { } } - static void toggleLobbyChatWindow() { + static Frame* toggleLobbyChatWindow() { assert(main_menu_frame); auto lobby = main_menu_frame->findFrame("lobby"); assert(lobby); auto frame = lobby->findFrame("chat window"); if (frame) { frame->removeSelf(); - return; + return nullptr; } const SDL_Rect size = lobby->getSize(); @@ -5852,13 +5876,24 @@ namespace MainMenu { chat_buffer->setSize(SDL_Rect{4, h - 32, w - 8, 32}); chat_buffer->setHJustify(Field::justify_t::LEFT); chat_buffer->setVJustify(Field::justify_t::CENTER); + chat_buffer->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + chat_buffer->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); chat_buffer->setFont(lobby_chat_font->c_str()); chat_buffer->setColor(makeColor(201, 162, 100, 255)); chat_buffer->setEditable(true); chat_buffer->setCallback([](Field& field){ sendChatMessageOverNet(field.getText()); field.setText(""); + field.activate(); + }); + chat_buffer->setTickCallback([](Widget& widget){ + auto field = static_cast(&widget); + if (!field->isActivated()) { + field->setText(""); + } }); + chat_buffer->setWidgetSearchParent(frame->getName()); + chat_buffer->setWidgetBack("close"); auto chat_tooltip = frame->addField("tooltip", 128); chat_tooltip->setSize(SDL_Rect{4, h - 32, w - 8, 32}); @@ -5904,6 +5939,11 @@ namespace MainMenu { } ready->select(); }); + close_button->setWidgetSearchParent(frame->getName()); + close_button->setWidgetDown("buffer"); + close_button->setWidgetBack("close"); + + return frame; } static void disconnectFromLobby() { @@ -6517,7 +6557,6 @@ namespace MainMenu { sendPacketSafe(net_sock, -1, net_packet, i - 1); } addLobbyChatMessage(0xffffffff, (char*)(&net_packet->data[4])); - playSound(238, 64); continue; } @@ -7007,7 +7046,6 @@ namespace MainMenu { // got a chat message else if (packetId == 'CMSG') { addLobbyChatMessage(0xffffffff, (char*)(&net_packet->data[4])); - playSound(238, 64); continue; } @@ -8945,6 +8983,8 @@ namespace MainMenu { auto name_field = card->addField("name", 128); name_field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); + name_field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + name_field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); name_field->setScroll(true); name_field->setGuide((std::string("Enter a name for Player ") + std::to_string(index + 1)).c_str()); name_field->setFont(smallfont_outline); @@ -9526,9 +9566,12 @@ namespace MainMenu { no_one_logged_in = false; } if (no_one_logged_in) { - if (input.binary("MenuCancel")) { - assert(main_menu_frame); - auto lobby = main_menu_frame->findFrame("lobby"); assert(lobby); + assert(main_menu_frame); + if (player == clientnum && + input.binary("MenuCancel") && + !inputstr && + !main_menu_frame->findSelectedWidget(player)) { + auto lobby = main_menu_frame->findFrame("lobby"); assert(lobby); auto back = lobby->findFrame("back"); assert(back); auto back_button = back->findButton("back_button"); assert(back_button); back_button->select(); @@ -9577,7 +9620,7 @@ namespace MainMenu { start_func(player); return; } - if (inputs.getPlayerIDAllowedKeyboard() == player && input.consumeBinaryToggle("KeyboardLogin")) { + if (inputs.getPlayerIDAllowedKeyboard() == player && !inputstr && input.consumeBinaryToggle("KeyboardLogin")) { input.consumeBindingsSharedWithBinding("KeyboardLogin"); #ifndef NINTENDO // release any controller assigned to this player @@ -10155,11 +10198,36 @@ namespace MainMenu { banner->setBorder(2); { auto back_button = createBackWidget(banner, [](Button&){ - soundCancel(); - disconnectFromLobby(); - destroyMainMenu(); - currentLobbyType = LobbyType::None; - createMainMenu(false); + if (currentLobbyType == LobbyType::LobbyLocal) { + soundCancel(); + disconnectFromLobby(); + destroyMainMenu(); + currentLobbyType = LobbyType::None; + createMainMenu(false); + } else { + binaryPrompt( + "Are you sure you want to leave\nthis lobby?", + "Yes", "No", + [](Button&){ // yes + soundActivate(); + disconnectFromLobby(); + destroyMainMenu(); + currentLobbyType = LobbyType::None; + createMainMenu(false); + }, + [](Button& button){ // no + soundCancel(); + closeBinary(); +#ifndef NINTENDO + // release any controller assigned to this player + const int index = button.getOwner(); + if (inputs.hasController(index)) { + inputs.removeControllerWithDeviceID(inputs.getControllerID(index)); + Input::inputs[index].refresh(); + } +#endif + }); + } }); back_button->setDrawCallback([](const Widget& widget, SDL_Rect pos){ if (currentLobbyType == LobbyType::None) { @@ -10220,8 +10288,9 @@ namespace MainMenu { chat_button->setFont(smallfont_outline); chat_button->setCallback([](Button& button){ soundActivate(); - toggleLobbyChatWindow(); + (void)toggleLobbyChatWindow(); }); + chat_button->setWidgetBack("back"); } } @@ -11010,12 +11079,11 @@ namespace MainMenu { addLobby(lobby); } }); - checkbox->setWidgetBack("names"); + checkbox->setWidgetBack("filter_settings"); std::string next_name = std::string("filter_checkbox") + std::to_string(index + 1); std::string prev_name = std::string("filter_checkbox") + std::to_string(index - 1); checkbox->setWidgetDown(next_name.c_str()); checkbox->setWidgetUp(prev_name.c_str()); - checkbox->setWidgetLeft("names"); ++index; } @@ -13032,7 +13100,7 @@ namespace MainMenu { }; Option options[] = { {"Leaderboards", "LEADERBOARDS", archivesLeaderboards}, - {"Dungeon Compendium", "ACHIEVEMENTS", archivesDungeonCompendium}, + //{"Dungeon Compendium", "ACHIEVEMENTS", archivesDungeonCompendium}, {"Story Introduction", "STORY INTRODUCTION", archivesStoryIntroduction}, {"Credits", "CREDITS", archivesCredits}, {"Back to Main Menu", "BACK TO MAIN MENU", archivesBackToMainMenu} @@ -13886,20 +13954,22 @@ namespace MainMenu { }, false, false); // yellow buttons // prompt timeout - prompt->setTickCallback([](Widget& widget){ - const int seconds = (resolution_timeout - ticks) / TICKS_PER_SECOND; - if ((int)resolution_timeout - (int)ticks > 0) { - auto prompt = static_cast(&widget); - auto text = prompt->findField("text"); - char buf[256]; - snprintf(buf, sizeof(buf), fmt, seconds + 1); - text->setText(buf); - } else { - soundCancel(); - closeBinary(); - resetResolution(); - } - }); + if (prompt) { + prompt->setTickCallback([](Widget& widget){ + const int seconds = (resolution_timeout - ticks) / TICKS_PER_SECOND; + if ((int)resolution_timeout - (int)ticks > 0) { + auto prompt = static_cast(&widget); + auto text = prompt->findField("text"); + char buf[256]; + snprintf(buf, sizeof(buf), fmt, seconds + 1); + text->setText(buf); + } else { + soundCancel(); + closeBinary(); + resetResolution(); + } + }); + } // at the end so that old_video is not overwritten resolution_changed = false; @@ -14857,7 +14927,12 @@ namespace MainMenu { snprintf(text, sizeof(text), "Please reconnect your controller.\n\n\n\n"); } + // at this point the prompt should ALWAYS open + // because we already handled the case where one exists above. + auto prompt = textPrompt("controller_prompt", text, prompt_tick_callback, false); + assert(prompt); + prompt->setOwner(player); auto header_size = prompt->getActualSize(); header_size.h = 80; diff --git a/src/ui/Widget.cpp b/src/ui/Widget.cpp index f841d77ba..2877c51a7 100644 --- a/src/ui/Widget.cpp +++ b/src/ui/Widget.cpp @@ -127,7 +127,7 @@ Widget* Widget::handleInput() { // move to another widget and activate it for (auto& action : widgetActions) { if (!action.second.empty()) { - if (input.consumeBinaryToggle(action.first.c_str())) { + if (input.consumeBinaryToggle(action.first.c_str()) && !inputstr) { root = root ? root : findSearchRoot(); Widget* result = root->findWidget(action.second.c_str(), true); if (result && !result->disabled) { From d67a1f7a4b54fb3b4f15ccec64d3baf858ff69f0 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 5 Jul 2022 17:45:40 -0700 Subject: [PATCH 02/21] some lobby stuff --- src/main.cpp | 12 ++++---- src/main.hpp | 2 +- src/prng.cpp | 7 ++++- src/prng.hpp | 5 +++ src/ui/MainMenu.cpp | 75 +++++++++++++++++++++++++++++++++++++++------ 5 files changed, 83 insertions(+), 18 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index da1baf44d..ac43efe6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -533,19 +533,19 @@ static std::unordered_map unique_traces; #include #endif -void stackTrace() { +std::string stackTrace() { #ifndef NDEBUG if (!ENABLE_STACK_TRACES) { - return; + return ""; } #ifdef LINUX // perform stack trace - constexpr unsigned int STACK_SIZE = 8; + constexpr unsigned int STACK_SIZE = 16; void* array[STACK_SIZE]; size_t size = backtrace(array, STACK_SIZE); if (size < 4) { - return; + return ""; } char** symbols = backtrace_symbols(array, size); @@ -560,7 +560,7 @@ void stackTrace() { // free backtrace table free(symbols); - printlog("STACK TRACE: %s", trace.c_str()); + return trace; #endif #endif } @@ -573,7 +573,7 @@ void stackTraceUnique() { #ifdef LINUX // perform stack trace - constexpr unsigned int STACK_SIZE = 8; + constexpr unsigned int STACK_SIZE = 16; void* array[STACK_SIZE]; size_t size = backtrace(array, STACK_SIZE); if (size < 4) { diff --git a/src/main.hpp b/src/main.hpp index 5430cfb9a..c39b18095 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -838,7 +838,7 @@ extern SteamGlobalStat_t g_SteamAPIGlobalStats[1]; #define getHeightOfFont(A) TTF_FontHeight(A) #endif // NINTENDO -void stackTrace(); +std::string stackTrace(); void stackTraceUnique(); void finishStackTraceUnique(); extern bool ENABLE_STACK_TRACES; \ No newline at end of file diff --git a/src/prng.cpp b/src/prng.cpp index 23290e8ab..8ecc7c0da 100644 --- a/src/prng.cpp +++ b/src/prng.cpp @@ -228,7 +228,12 @@ int BaronyRNG::getSeed(void* out, size_t size) const { void BaronyRNG::getBytes(void* data_, size_t size) { #ifndef NDEBUG - //stackTrace(); + /*if (this == &local_rng) { + std::string str = stackTrace(); + if (!str.empty() && str.find("gameLogic") == std::string::npos) { + printlog(str.c_str()); + } + }*/ #endif if (!seeded) { printlog("rng not seeded, seeding by unix time"); diff --git a/src/prng.hpp b/src/prng.hpp index 494d7e2d6..c7bce3968 100644 --- a/src/prng.hpp +++ b/src/prng.hpp @@ -18,6 +18,11 @@ static uint8_t marker[256]; class BaronyRNG { public: + BaronyRNG() = default; + BaronyRNG(const BaronyRNG&) = default; + BaronyRNG(BaronyRNG&&) = default; + ~BaronyRNG() = default; + void seedTime(); // seed according to a 32-bit time value void seedBytes(const void*, size_t); // seed given byte buffer (uses 256 bytes at most) void getBytes(void*, size_t); // fill a buffer with pseudo-random bytes diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index f265ca49d..941ad10b9 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -868,7 +868,7 @@ namespace MainMenu { auto field = frame->addField("field", field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); - field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); field->setEditable(true); field->setScroll(true); @@ -879,6 +879,7 @@ namespace MainMenu { field->setHJustify(Field::justify_t::LEFT); field->setVJustify(Field::justify_t::CENTER); field->setColor(makeColor(166, 123, 81, 255)); + field->setBackgroundColor(makeColor(52, 30, 22, 255)); field->setBackgroundSelectAllColor(makeColor(52, 30, 22, 255)); field->setBackgroundActivatedColor(makeColor(52, 30, 22, 255)); field->setWidgetSearchParent(field->getParent()->getName()); @@ -3036,7 +3037,7 @@ namespace MainMenu { auto field = frame.addField((fullname + "_text_field").c_str(), field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); - field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); field->setEditable(true); field->setScroll(true); @@ -3052,6 +3053,7 @@ namespace MainMenu { (*field->getCallback())(*field); }); field->setColor(makeColor(166, 123, 81, 255)); + field->setBackgroundColor(makeColor(52, 30, 22, 255)); field->setBackgroundSelectAllColor(makeColor(52, 30, 22, 255)); field->setBackgroundActivatedColor(makeColor(52, 30, 22, 255)); field->setWidgetSearchParent(frame.getParent()->getName()); @@ -5876,7 +5878,7 @@ namespace MainMenu { chat_buffer->setSize(SDL_Rect{4, h - 32, w - 8, 32}); chat_buffer->setHJustify(Field::justify_t::LEFT); chat_buffer->setVJustify(Field::justify_t::CENTER); - chat_buffer->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + chat_buffer->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); chat_buffer->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); chat_buffer->setFont(lobby_chat_font->c_str()); chat_buffer->setColor(makeColor(201, 162, 100, 255)); @@ -8983,7 +8985,7 @@ namespace MainMenu { auto name_field = card->addField("name", 128); name_field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); - name_field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + name_field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); name_field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); name_field->setScroll(true); name_field->setGuide((std::string("Enter a name for Player ") + std::to_string(index + 1)).c_str()); @@ -8991,6 +8993,7 @@ namespace MainMenu { name_field->setText(stats[index]->name); name_field->setSize(SDL_Rect{90, 34, 146, 28}); name_field->setColor(makeColor(166, 123, 81, 255)); + name_field->setBackgroundColor(makeColor(52, 30, 22, 255)); name_field->setBackgroundSelectAllColor(makeColor(52, 30, 22, 255)); name_field->setBackgroundActivatedColor(makeColor(52, 30, 22, 255)); name_field->setHJustify(Field::justify_t::LEFT); @@ -9045,7 +9048,7 @@ namespace MainMenu { randomize_name->setColor(makeColor(255, 255, 255, 255)); randomize_name->setHighlightColor(makeColor(255, 255, 255, 255)); randomize_name->setBackground("*images/ui/Main Menus/Play/PlayerCreation/Finalize_Icon_Randomize_00.png"); - randomize_name->setSize(SDL_Rect{244, 26, 40, 44}); + randomize_name->setSize(SDL_Rect{236, 22, 54, 54}); randomize_name->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); randomize_name->addWidgetAction("MenuStart", "ready"); randomize_name->setWidgetBack("back_button"); @@ -9252,7 +9255,7 @@ namespace MainMenu { randomize_class->setColor(makeColor(255, 255, 255, 255)); randomize_class->setHighlightColor(makeColor(255, 255, 255, 255)); randomize_class->setBackground("*images/ui/Main Menus/Play/PlayerCreation/Finalize_Icon_Randomize_00.png"); - randomize_class->setSize(SDL_Rect{244, 230, 40, 44}); + randomize_class->setSize(SDL_Rect{236, 226, 54, 54}); randomize_class->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); randomize_class->addWidgetAction("MenuStart", "ready"); randomize_class->setWidgetBack("back_button"); @@ -9814,13 +9817,12 @@ namespace MainMenu { } } else if (backdrop->path == "*images/ui/Main Menus/Play/PlayerCreation/UI_Ready_Window00.png") { playersInLobby[c] = true; + atLeastOnePlayer = true; } else { playersInLobby[c] = true; + atLeastOnePlayer = true; allReady = false; } - if (isPlayerSignedIn(c)) { - atLeastOnePlayer = true; - } } if (allReady && atLeastOnePlayer) { createCountdownTimer(); @@ -10041,7 +10043,7 @@ namespace MainMenu { countdown->setHJustify(Field::justify_t::LEFT); countdown->setVJustify(Field::justify_t::TOP); countdown->setFont(timer_font); - countdown->setSize(SDL_Rect{(Frame::virtualScreenX - 40) / 2, 0, 300, 200}); + countdown->setSize(SDL_Rect{(Frame::virtualScreenX - 40) / 2, 64, 300, 200}); countdown->setTickCallback([](Widget& widget){ auto countdown = static_cast(&widget); if (ticks >= countdown_end) { @@ -10258,6 +10260,7 @@ namespace MainMenu { image->draw(nullptr, SDL_Rect{x - w / 2, y - h / 2, w, h}, viewport); } }); + back_button->setWidgetRight("lobby_name"); auto back_frame = back_button->getParent(); back_frame->setTickCallback([](Widget& widget){ @@ -10279,6 +10282,57 @@ namespace MainMenu { button->setDisabled(!allCardsClosed); }); + // lobby type + const char* type_str; + switch (type) { + default: + case LobbyType::LobbyLocal: type_str = "Local Lobby"; break; + case LobbyType::LobbyLAN: type_str = "LAN Lobby (Host)"; break; + case LobbyType::LobbyOnline: type_str = "Online Lobby (Host)"; break; + case LobbyType::LobbyJoined: type_str = directConnect ? + "LAN Lobby (Joined)" : "Online Lobby (Joined)"; break; + } + auto label = banner->addField("label", 128); + label->setHJustify(Field::justify_t::LEFT); + label->setVJustify(Field::justify_t::CENTER); + label->setSize(SDL_Rect{96, 8, 256, 48}); + label->setFont(bigfont_outline); + label->setText(type_str); + + // lobby name + if (type != LobbyType::LobbyLocal) { + auto text_box = banner->addImage( + SDL_Rect{(Frame::virtualScreenX - 246) / 2, 16, 246, 36}, + 0xffffffff, + "*images/ui/Main Menus/TextField_00.png", + "text_box" + ); + + constexpr int field_buffer_size = 64; + + auto field = banner->addField("lobby_name", field_buffer_size); + field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); + field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); + field->setEditable(type != LobbyType::LobbyJoined); + field->setScroll(true); + field->setGuide("Set a public name for this lobby."); + field->setSize(SDL_Rect{(Frame::virtualScreenX - 246) / 2, 20, 242, 28}); + field->setFont(smallfont_outline); + field->setHJustify(Field::justify_t::LEFT); + field->setVJustify(Field::justify_t::CENTER); + field->setColor(makeColor(166, 123, 81, 255)); + field->setBackgroundColor(makeColor(52, 30, 22, 255)); + field->setBackgroundSelectAllColor(makeColor(52, 30, 22, 255)); + field->setBackgroundActivatedColor(makeColor(52, 30, 22, 255)); + field->setWidgetSearchParent(field->getParent()->getName()); + field->setWidgetBack("back"); + field->setWidgetRight("chat"); + field->setWidgetLeft("back"); + //field->setText(currentLobbyName); // TODO epic lobby name EOS.currentLobbyName + } + + // chat button if (type != LobbyType::LobbyLocal) { auto chat_button = banner->addButton("chat"); chat_button->setSize(SDL_Rect{Frame::virtualScreenX - 144, 16, 128, 32}); @@ -10291,6 +10345,7 @@ namespace MainMenu { (void)toggleLobbyChatWindow(); }); chat_button->setWidgetBack("back"); + chat_button->setWidgetLeft("lobby_name"); } } From 55329fd811338ddc2cb06d7c04e5ac1ac124f233 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 5 Jul 2022 22:08:37 -0700 Subject: [PATCH 03/21] character card stuff --- src/ui/Frame.cpp | 12 +- src/ui/MainMenu.cpp | 354 +++++++++++++++----------------------------- 2 files changed, 129 insertions(+), 237 deletions(-) diff --git a/src/ui/Frame.cpp b/src/ui/Frame.cpp index 7cb9db281..dc4013e90 100644 --- a/src/ui/Frame.cpp +++ b/src/ui/Frame.cpp @@ -910,8 +910,12 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: if (selection == -1) { if (input.consumeBinaryToggle("MenuUp") || input.consumeBinaryToggle("MenuDown") || + input.consumeBinaryToggle("MenuRight") || + input.consumeBinaryToggle("MenuLeft") || input.consumeBinaryToggle("AltMenuUp") || - input.consumeBinaryToggle("AltMenuUDown")) { + input.consumeBinaryToggle("AltMenuDown") || + input.consumeBinaryToggle("AltMenuRight") || + input.consumeBinaryToggle("AltMenuLeft")) { selection = 0; scrollToSelection(); auto entry = list[selection]; @@ -920,7 +924,8 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } } } else { - if (input.consumeBinaryToggle("MenuUp") || input.consumeBinaryToggle("AltMenuUp")) { + if (input.consumeBinaryToggle("MenuUp") || input.consumeBinaryToggle("AltMenuUp") || + input.consumeBinaryToggle("MenuLeft") || input.consumeBinaryToggle("AltMenuLeft")) { selection = std::max(0, selection - 1); scrollToSelection(); auto entry = list[selection]; @@ -928,7 +933,8 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: (*entry->selected)(*entry); } } - if (input.consumeBinaryToggle("MenuDown") || input.consumeBinaryToggle("AltMenuDown")) { + if (input.consumeBinaryToggle("MenuDown") || input.consumeBinaryToggle("AltMenuDown") || + input.consumeBinaryToggle("MenuRight") || input.consumeBinaryToggle("AltMenuRight")) { selection = std::min((int)list.size() - 1, selection + 1); scrollToSelection(); auto entry = list[selection]; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 941ad10b9..4fc337ee0 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -427,8 +427,8 @@ namespace MainMenu { static void characterCardGameSettingsMenu(int index); static void characterCardLobbySettingsMenu(int index); - static void characterCardRaceMenu(int index); - static void characterCardClassMenu(int index); + static void characterCardRaceMenu(int index, bool details); + static void characterCardClassMenu(int index, bool details); static void createControllerPrompt(int index, bool show_player_text, void (*after_func)()); static void createCharacterCard(int index); @@ -869,7 +869,7 @@ namespace MainMenu { auto field = frame->addField("field", field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); - field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); + field->setButtonsOffset(SDL_Rect{11, 0, 0, 0}); field->setEditable(true); field->setScroll(true); field->setGuide(guide_text); @@ -3038,7 +3038,7 @@ namespace MainMenu { auto field = frame.addField((fullname + "_text_field").c_str(), field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); - field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); + field->setButtonsOffset(SDL_Rect{11, 0, 0, 0}); field->setEditable(true); field->setScroll(true); field->setGuide(tip); @@ -5879,7 +5879,7 @@ namespace MainMenu { chat_buffer->setHJustify(Field::justify_t::LEFT); chat_buffer->setVJustify(Field::justify_t::CENTER); chat_buffer->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); - chat_buffer->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); + chat_buffer->setButtonsOffset(SDL_Rect{11, 0, 0, 0}); chat_buffer->setFont(lobby_chat_font->c_str()); chat_buffer->setColor(makeColor(201, 162, 100, 255)); chat_buffer->setEditable(true); @@ -8216,8 +8216,8 @@ namespace MainMenu { }*/ } - static void characterCardRaceMenu(int index) { - auto card = initCharacterCard(index, 488); + static void characterCardRaceMenu(int index, bool details) { + auto card = initCharacterCard(index, details ? 664 : 488); static void (*back_fn)(int) = [](int index){ createCharacterCard(index); @@ -8237,7 +8237,9 @@ namespace MainMenu { auto backdrop = card->addImage( card->getActualSize(), 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_Window_01.png", + details ? + "*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/RaceSelect_ScrollList_Details.png": + "*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/RaceSelect_ScrollList.png", "backdrop" ); @@ -8247,27 +8249,23 @@ namespace MainMenu { header->setText("RACE SELECTION"); header->setJustify(Field::justify_t::CENTER); - static const char* dlcRaces1[] = { + static const char* races[] = { + "Human", "Skeleton", "Vampire", "Succubus", - "Goatman" - }; - - static const char* dlcRaces2[] = { + "Goatman", "Automaton", "Incubus", "Goblin", - "Insectoid" + "Insectoid", }; + static constexpr int num_races = sizeof(races) / sizeof(races[0]); static auto race_fn = [](Button& button, int index){ Frame* frame = static_cast(button.getParent()); - std::vector allRaces = { "Human" }; - allRaces.insert(allRaces.end(), std::begin(dlcRaces1), std::end(dlcRaces1)); - allRaces.insert(allRaces.end(), std::begin(dlcRaces2), std::end(dlcRaces2)); - for (int c = 0; c < allRaces.size(); ++c) { - auto race = allRaces[c]; + for (int c = 0; c < num_races; ++c) { + auto race = races[c]; if (strcmp(button.getName(), race) == 0) { stats[index]->playerRace = c; if (stats[index]->playerRace == RACE_SUCCUBUS) { @@ -8303,48 +8301,67 @@ namespace MainMenu { sendPlayerOverNet(); }; - auto human = card->addButton("Human"); - human->setSize(SDL_Rect{54, 80, 30, 30}); - human->setIcon("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/Fill_Round_00.png"); - human->setStyle(Button::style_t::STYLE_RADIO); - human->setBorder(0); - human->setColor(0); - human->setBorderColor(0); - human->setHighlightColor(0); - human->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); - human->addWidgetAction("MenuStart", "confirm"); - human->setWidgetBack("back_button"); - human->setWidgetRight("appearances"); - if (enabledDLCPack1) { - human->setWidgetDown(dlcRaces1[0]); - } - else if (enabledDLCPack2) { - human->setWidgetDown(dlcRaces2[0]); - } - else { - human->setWidgetDown("disable_abilities"); + auto subframe = card->addFrame("subframe"); + subframe->setSize(details ? + SDL_Rect{38, 382, 234, 106}: + SDL_Rect{38, 68, 234, 208}); + subframe->setActualSize(SDL_Rect{0, 0, 234, 36 * num_races}); + subframe->setBorder(0); + subframe->setColor(0); + + for (int c = 0; c < num_races; ++c) { + auto race = subframe->addButton(races[c]); + race->setSize(SDL_Rect{0, c * 36 + 3, 30, 30}); + race->setBackground("*#images/ui/Main Menus/sublist_item-unpicked.png"); + if (!enabledDLCPack1 && c >= 1 && c <= 4) { + race->setIcon("*#images/ui/Main Menus/sublist_item-locked.png"); + race->setDisabled(true); + } + else if (!enabledDLCPack2 && c >= 5 && c <= 8) { + race->setIcon("*#images/ui/Main Menus/sublist_item-locked.png"); + race->setDisabled(true); + } + else { + race->setIcon("*#images/ui/Main Menus/sublist_item-picked.png"); + race->setDisabled(false); + } + race->setStyle(Button::style_t::STYLE_RADIO); + race->setBorder(0); + race->setColor(0); + race->setBorderColor(0); + race->setHighlightColor(0); + race->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + race->addWidgetAction("MenuStart", "confirm"); + race->setWidgetBack("back_button"); + race->setWidgetRight("appearances"); + if (c < num_races - 1) { + race->setWidgetDown(races[c + 1]); + } + if (c > 0) { + race->setWidgetUp(races[c - 1]); + } + switch (index) { + case 0: race->setCallback([](Button& button){soundToggle(); race_fn(button, 0);}); break; + case 1: race->setCallback([](Button& button){soundToggle(); race_fn(button, 1);}); break; + case 2: race->setCallback([](Button& button){soundToggle(); race_fn(button, 2);}); break; + case 3: race->setCallback([](Button& button){soundToggle(); race_fn(button, 3);}); break; + } + if (stats[index]->playerRace == c) { + race->setPressed(true); + race->select(); + } + + auto label = subframe->addField((std::string(races[c]) + "_label").c_str(), 64); + label->setColor(makeColor(166, 123, 81, 255)); + label->setText(races[c]); + label->setFont(smallfont_outline); + label->setSize(SDL_Rect{32, c * 36, 64, 36}); + label->setHJustify(Field::justify_t::LEFT); + label->setVJustify(Field::justify_t::CENTER); } - switch (index) { - case 0: human->setCallback([](Button& button){soundToggle(); race_fn(button, 0);}); break; - case 1: human->setCallback([](Button& button){soundToggle(); race_fn(button, 1);}); break; - case 2: human->setCallback([](Button& button){soundToggle(); race_fn(button, 2);}); break; - case 3: human->setCallback([](Button& button){soundToggle(); race_fn(button, 3);}); break; - } - if (stats[index]->playerRace == RACE_HUMAN) { - human->setPressed(true); - human->select(); - } - - auto human_label = card->addField("human_label", 32); - human_label->setColor(makeColor(166, 123, 81, 255)); - human_label->setText("Human"); - human_label->setFont(smallfont_outline); - human_label->setSize(SDL_Rect{86, 78, 66, 36}); - human_label->setHJustify(Button::justify_t::LEFT); - human_label->setVJustify(Button::justify_t::CENTER); - - auto appearances = card->addFrame("appearances"); - appearances->setSize(SDL_Rect{152, 78, 122, 36}); + + auto appearances = subframe->addFrame("appearances"); + appearances->setSize(SDL_Rect{128, 0, 122, 36}); appearances->setActualSize(SDL_Rect{0, 4, 122, 36}); appearances->setFont("fonts/pixel_maz.ttf#32#2"); appearances->setBorder(0); @@ -8357,16 +8374,8 @@ namespace MainMenu { appearances->addWidgetMovement("MenuListConfirm", "appearances"); appearances->addWidgetAction("MenuStart", "confirm"); appearances->setWidgetBack("back_button"); - appearances->setWidgetLeft("Human"); - if (enabledDLCPack2) { - appearances->setWidgetDown(dlcRaces2[0]); - } - else if (enabledDLCPack1) { - appearances->setWidgetDown(dlcRaces1[0]); - } - else { - appearances->setWidgetDown("disable_abilities"); - } + appearances->setWidgetLeft(races[0]); + appearances->setWidgetDown(races[1]); appearances->setTickCallback([](Widget& widget){ auto frame = static_cast(&widget); auto card = static_cast(frame->getParent()); @@ -8408,9 +8417,9 @@ namespace MainMenu { appearance_selected->disabled = true; appearance_selected->ontop = true; - auto appearance_uparrow = card->addButton("appearance_uparrow"); - appearance_uparrow->setSize(SDL_Rect{198, 58, 32, 20}); - appearance_uparrow->setBackground("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_ButtonUp_00.png"); + auto appearance_uparrow = subframe->addButton("appearance_uparrow"); + appearance_uparrow->setSize(SDL_Rect{96, 0, 20, 32}); + appearance_uparrow->setBackground("*images/ui/Main Menus/sublist_item-pickleft.png"); appearance_uparrow->setHighlightColor(makeColor(255, 255, 255, 255)); appearance_uparrow->setColor(makeColor(255, 255, 255, 255)); appearance_uparrow->setDisabled(true); @@ -8425,9 +8434,9 @@ namespace MainMenu { button.select(); }); - auto appearance_downarrow = card->addButton("appearance_downarrow"); - appearance_downarrow->setSize(SDL_Rect{198, 114, 32, 20}); - appearance_downarrow->setBackground("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_ButtonDown_00.png"); + auto appearance_downarrow = subframe->addButton("appearance_downarrow"); + appearance_downarrow->setSize(SDL_Rect{214, 0, 20, 32}); + appearance_downarrow->setBackground("*images/ui/Main Menus/sublist_item-pickright.png"); appearance_downarrow->setHighlightColor(makeColor(255, 255, 255, 255)); appearance_downarrow->setColor(makeColor(255, 255, 255, 255)); appearance_downarrow->setDisabled(true); @@ -8478,144 +8487,23 @@ namespace MainMenu { } } - if (enabledDLCPack1) { - for (int c = 0; c < 4; ++c) { - auto race = card->addButton(dlcRaces1[c]); - race->setSize(SDL_Rect{38, 130 + 38 * c, 30, 30}); - race->setIcon("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/Fill_Round_00.png"); - race->setStyle(Button::style_t::STYLE_RADIO); - race->setBorder(0); - race->setColor(0); - race->setBorderColor(0); - race->setHighlightColor(0); - race->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); - race->addWidgetAction("MenuStart", "confirm"); - race->setWidgetBack("back_button"); - if (enabledDLCPack2) { - race->setWidgetRight(dlcRaces2[c]); - } - if (c > 0) { - race->setWidgetUp(dlcRaces1[c - 1]); - } else { - race->setWidgetUp("Human"); - } - if (c < 3) { - race->setWidgetDown(dlcRaces1[c + 1]); - } else { - race->setWidgetDown("disable_abilities"); - } - switch (index) { - case 0: race->setCallback([](Button& button){soundToggle(); race_fn(button, 0);}); break; - case 1: race->setCallback([](Button& button){soundToggle(); race_fn(button, 1);}); break; - case 2: race->setCallback([](Button& button){soundToggle(); race_fn(button, 2);}); break; - case 3: race->setCallback([](Button& button){soundToggle(); race_fn(button, 3);}); break; - } - - if (stats[index]->playerRace == RACE_SKELETON + c) { - race->setPressed(true); - race->select(); - } - - auto label = card->addField((std::string(dlcRaces1[c]) + "_label").c_str(), 64); - label->setSize(SDL_Rect{70, 132 + 38 * c, 104, 26}); - label->setVJustify(Field::justify_t::CENTER); - label->setHJustify(Field::justify_t::LEFT); - label->setColor(makeColor(180, 133, 13, 255)); - label->setFont(smallfont_outline); - label->setText(dlcRaces1[c]); - } - } else { - auto blockers = card->addImage( - SDL_Rect{40, 132, 26, 140}, - 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_Blocker_00.png", - "blockers" - ); - for (int c = 0; c < 4; ++c) { - auto label = card->addField((std::string(dlcRaces1[c]) + "_label").c_str(), 64); - label->setSize(SDL_Rect{70, 132 + 38 * c, 104, 26}); - label->setVJustify(Field::justify_t::CENTER); - label->setHJustify(Field::justify_t::LEFT); - label->setColor(makeColor(70, 62, 59, 255)); - label->setFont(smallfont_outline); - label->setText(dlcRaces1[c]); - } - } - - if (enabledDLCPack2) { - for (int c = 0; c < 4; ++c) { - auto race = card->addButton(dlcRaces2[c]); - race->setSize(SDL_Rect{172, 130 + 38 * c, 30, 30}); - race->setIcon("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/Fill_Round_00.png"); - race->setStyle(Button::style_t::STYLE_RADIO); - race->setBorder(0); - race->setColor(0); - race->setBorderColor(0); - race->setHighlightColor(0); - race->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); - race->addWidgetAction("MenuStart", "confirm"); - race->setWidgetBack("back_button"); - if (enabledDLCPack1) { - race->setWidgetLeft(dlcRaces1[c]); - } - if (c > 0) { - race->setWidgetUp(dlcRaces2[c - 1]); - } else { - race->setWidgetUp("appearances"); - } - if (c < 3) { - race->setWidgetDown(dlcRaces2[c + 1]); - } else { - race->setWidgetDown("disable_abilities"); - } - switch (index) { - case 0: race->setCallback([](Button& button){soundToggle(); race_fn(button, 0);}); break; - case 1: race->setCallback([](Button& button){soundToggle(); race_fn(button, 1);}); break; - case 2: race->setCallback([](Button& button){soundToggle(); race_fn(button, 2);}); break; - case 3: race->setCallback([](Button& button){soundToggle(); race_fn(button, 3);}); break; - } - - if (stats[index]->playerRace == RACE_AUTOMATON + c) { - race->setPressed(true); - race->select(); - } + auto bottom = card->addFrame("bottom"); + bottom->setSize(details ? + SDL_Rect{0, 494, 324, 170}: + SDL_Rect{0, 282, 324, 170}); + bottom->setBorder(0); + bottom->setColor(0); - auto label = card->addField((std::string(dlcRaces2[c]) + "_label").c_str(), 64); - label->setSize(SDL_Rect{202, 132 + 38 * c, 104, 26}); - label->setVJustify(Field::justify_t::CENTER); - label->setHJustify(Field::justify_t::LEFT); - label->setColor(makeColor(223, 44, 149, 255)); - label->setFont(smallfont_outline); - label->setText(dlcRaces2[c]); - } - } else { - auto blockers = card->addImage( - SDL_Rect{174, 132, 26, 140}, - 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_Blocker_00.png", - "blockers" - ); - for (int c = 0; c < 4; ++c) { - auto label = card->addField((std::string(dlcRaces2[c]) + "_label").c_str(), 64); - label->setSize(SDL_Rect{202, 132 + 38 * c, 104, 26}); - label->setVJustify(Field::justify_t::CENTER); - label->setHJustify(Field::justify_t::LEFT); - label->setColor(makeColor(70, 62, 59, 255)); - label->setFont(smallfont_outline); - label->setText(dlcRaces2[c]); - } - } - - auto disable_abilities_text = card->addField("disable_abilities_text", 256); - disable_abilities_text->setSize(SDL_Rect{44, 274, 154, 64}); + auto disable_abilities_text = bottom->addField("disable_abilities_text", 256); + disable_abilities_text->setSize(SDL_Rect{44, 0, 154, 48}); disable_abilities_text->setFont(smallfont_outline); disable_abilities_text->setColor(makeColor(166, 123, 81, 255)); disable_abilities_text->setText("Disable monster\nrace abilities"); disable_abilities_text->setHJustify(Field::justify_t::LEFT); disable_abilities_text->setVJustify(Field::justify_t::CENTER); - auto disable_abilities = card->addButton("disable_abilities"); - disable_abilities->setSize(SDL_Rect{194, 284, 44, 44}); + auto disable_abilities = bottom->addButton("disable_abilities"); + disable_abilities->setSize(SDL_Rect{194, 2, 44, 44}); disable_abilities->setIcon("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/Fill_Checked_00.png"); disable_abilities->setColor(0); disable_abilities->setBorderColor(0); @@ -8626,15 +8514,7 @@ namespace MainMenu { disable_abilities->addWidgetAction("MenuStart", "confirm"); disable_abilities->setWidgetBack("back_button"); disable_abilities->setWidgetDown("show_race_info"); - if (enabledDLCPack2) { - disable_abilities->setWidgetUp(dlcRaces2[sizeof(dlcRaces2) / sizeof(dlcRaces2[0]) - 1]); - } - else if (enabledDLCPack1) { - disable_abilities->setWidgetUp(dlcRaces1[sizeof(dlcRaces1) / sizeof(dlcRaces1[0]) - 1]); - } - else { - disable_abilities->setWidgetUp("appearances"); - } + disable_abilities->setWidgetUp(races[num_races - 1]); if (stats[index]->playerRace != RACE_HUMAN) { disable_abilities->setPressed(stats[index]->appearance != 0); } @@ -8651,7 +8531,7 @@ namespace MainMenu { case 3: disable_abilities->setCallback([](Button& button){disable_abilities_fn(button, 3);}); break; } - auto male_button = card->addButton("male"); + auto male_button = bottom->addButton("male"); if (stats[index]->sex == MALE) { male_button->setColor(makeColor(255, 255, 255, 255)); male_button->setHighlightColor(makeColor(255, 255, 255, 255)); @@ -8660,7 +8540,7 @@ namespace MainMenu { male_button->setHighlightColor(makeColor(127, 127, 127, 255)); } male_button->setBackground("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_ButtonMale_00.png"); - male_button->setSize(SDL_Rect{44, 344, 58, 52}); + male_button->setSize(SDL_Rect{44, details ? 48 : 60, 58, 52}); male_button->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); male_button->addWidgetAction("MenuStart", "confirm"); male_button->setWidgetBack("back_button"); @@ -8674,7 +8554,7 @@ namespace MainMenu { case 3: male_button->setCallback([](Button& button){soundActivate(); male_button_fn(button, 3);}); break; } - auto female_button = card->addButton("female"); + auto female_button = bottom->addButton("female"); if (stats[index]->sex == FEMALE) { female_button->setColor(makeColor(255, 255, 255, 255)); female_button->setHighlightColor(makeColor(255, 255, 255, 255)); @@ -8683,7 +8563,7 @@ namespace MainMenu { female_button->setHighlightColor(makeColor(127, 127, 127, 255)); } female_button->setBackground("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_ButtonFemale_00.png"); - female_button->setSize(SDL_Rect{106, 344, 58, 52}); + female_button->setSize(SDL_Rect{106, details ? 48 : 60, 58, 52}); female_button->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); female_button->addWidgetAction("MenuStart", "confirm"); female_button->setWidgetBack("back_button"); @@ -8698,19 +8578,25 @@ namespace MainMenu { case 3: female_button->setCallback([](Button& button){soundActivate(); female_button_fn(button, 3);}); break; } - auto show_race_info = card->addButton("show_race_info"); + auto show_race_info = bottom->addButton("show_race_info"); show_race_info->setFont(smallfont_outline); show_race_info->setText("Show Race\nInfo"); show_race_info->setColor(makeColor(255, 255, 255, 255)); show_race_info->setHighlightColor(makeColor(255, 255, 255, 255)); show_race_info->setBackground("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_ButtonShowDetails_00.png"); - show_race_info->setSize(SDL_Rect{168, 344, 110, 52}); + show_race_info->setSize(SDL_Rect{168, details ? 48 : 60, 110, 52}); show_race_info->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); show_race_info->addWidgetAction("MenuStart", "confirm"); show_race_info->setWidgetBack("back_button"); show_race_info->setWidgetUp("disable_abilities"); show_race_info->setWidgetDown("confirm"); show_race_info->setWidgetLeft("female"); + switch (index) { + case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, true);}); break; + case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, true);}); break; + case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, true);}); break; + case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, true);}); break; + } /*auto confirm = card->addButton("confirm"); confirm->setFont(bigfont_outline); @@ -8731,7 +8617,7 @@ namespace MainMenu { }*/ } - static void characterCardClassMenu(int index) { + static void characterCardClassMenu(int index, bool details) { auto reduced_class_list = reducedClassList(index); auto card = initCharacterCard(index, 488); @@ -8986,7 +8872,7 @@ namespace MainMenu { auto name_field = card->addField("name", 128); name_field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); name_field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); - name_field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); + name_field->setButtonsOffset(SDL_Rect{11, 0, 0, 0}); name_field->setScroll(true); name_field->setGuide((std::string("Enter a name for Player ") + std::to_string(index + 1)).c_str()); name_field->setFont(smallfont_outline); @@ -9164,10 +9050,10 @@ namespace MainMenu { race_button->setWidgetUp("game_settings"); race_button->setWidgetDown("class"); switch (index) { - case 0: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0);}); break; - case 1: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1);}); break; - case 2: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2);}); break; - case 3: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3);}); break; + case 0: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, false);}); break; + case 1: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, false);}); break; + case 2: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, false);}); break; + case 3: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, false);}); break; } static auto randomize_class_fn = [](Button& button, int index){ @@ -9322,19 +9208,19 @@ namespace MainMenu { switch (index) { case 0: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 0);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(0);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(0, false);}); break; case 1: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 1);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(1);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(1, false);}); break; case 2: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 2);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(2);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(2, false);}); break; case 3: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 3);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(3);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(3, false);}); break; } (*class_button->getTickCallback())(*class_button); @@ -10312,12 +10198,12 @@ namespace MainMenu { auto field = banner->addField("lobby_name", field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); - field->setSelectorOffset(SDL_Rect{-4, -4, 4, 4}); + field->setSelectorOffset(SDL_Rect{-7, -7, 7, 7}); field->setButtonsOffset(SDL_Rect{8, 0, 0, 0}); field->setEditable(type != LobbyType::LobbyJoined); field->setScroll(true); field->setGuide("Set a public name for this lobby."); - field->setSize(SDL_Rect{(Frame::virtualScreenX - 246) / 2, 20, 242, 28}); + field->setSize(SDL_Rect{(Frame::virtualScreenX - 242) / 2, 20, 242, 28}); field->setFont(smallfont_outline); field->setHJustify(Field::justify_t::LEFT); field->setVJustify(Field::justify_t::CENTER); From 6f91f9f1b2d64195addab3027cbe5bfec0982c24 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Thu, 7 Jul 2022 12:35:52 -0700 Subject: [PATCH 04/21] fix minotaur timer + some lobby UI stuff --- src/colors.hpp | 8 +- src/draw.cpp | 10 +- src/interface/drawminimap.cpp | 83 +++----- src/ui/Button.hpp | 3 + src/ui/Frame.cpp | 384 ++++++++++++++++++++-------------- src/ui/Frame.hpp | 4 + src/ui/MainMenu.cpp | 107 ++++++++-- src/ui/Slider.hpp | 3 + 8 files changed, 367 insertions(+), 235 deletions(-) diff --git a/src/colors.hpp b/src/colors.hpp index d8503ba28..2e01bf387 100644 --- a/src/colors.hpp +++ b/src/colors.hpp @@ -36,4 +36,10 @@ constexpr Uint32 uint32ColorPlayer1 = makeColorRGB(64, 255, 64); constexpr Uint32 uint32ColorPlayer2 = makeColorRGB(86, 180, 233); constexpr Uint32 uint32ColorPlayer3 = makeColorRGB(240, 228, 66); constexpr Uint32 uint32ColorPlayer4 = makeColorRGB(204, 121, 167); -constexpr Uint32 uint32ColorPlayerX = makeColorRGB(191, 191, 191); \ No newline at end of file +constexpr Uint32 uint32ColorPlayerX = makeColorRGB(191, 191, 191); + +constexpr Uint32 uint32ColorPlayer1_Ally = makeColorRGB(32, 127, 32); +constexpr Uint32 uint32ColorPlayer2_Ally = makeColorRGB(43, 90, 116); +constexpr Uint32 uint32ColorPlayer3_Ally = makeColorRGB(120, 114, 33); +constexpr Uint32 uint32ColorPlayer4_Ally = makeColorRGB(102, 60, 83); +constexpr Uint32 uint32ColorPlayerX_Ally = makeColorRGB(95, 95, 95); \ No newline at end of file diff --git a/src/draw.cpp b/src/draw.cpp index b8d265f9b..4acae2eb4 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -1506,7 +1506,7 @@ void drawEntities3D(view_t* camera, int mode) { if ( !entity->flags[OVERDRAW] ) { - if ( !map.vismap[y + x * map.height] ) + if ( !map.vismap[y + x * map.height] && !entity->monsterEntityRenderAsTelepath ) { continue; } @@ -2845,12 +2845,16 @@ void occlusionCulling(map_t& map, const view_t& camera) #if !defined(EDITOR) && !defined(NDEBUG) static ConsoleVariable cvar("/skipculling", false); - if (*cvar) + const bool disabled = *cvar; +#else + const bool disabled = false; +#endif + + if (disabled) { memset(map.vismap, 1, sizeof(bool) * size); return; } -#endif // clear vismap const int camx = std::min(std::max(0, (int)camera.x), (int)map.width - 1); diff --git a/src/interface/drawminimap.cpp b/src/interface/drawminimap.cpp index ae4354376..104a0bb30 100644 --- a/src/interface/drawminimap.cpp +++ b/src/interface/drawminimap.cpp @@ -403,67 +403,52 @@ void drawMinimap(const int player, SDL_Rect rect) } if ( drawMonsterAlly >= 0 || foundplayer >= 0 || entity->sprite == 239) { - Uint32 color = makeColor(255, 0, 0, 255); - - if (entity->sprite == 239) - { - if (players[player] == nullptr) - { + Uint32 color; + if ( foundplayer >= 0 ) { + if ( players[player] && players[player]->entity + && players[player]->entity->creatureShadowTaggedThisUid == entity->getUID() ) { + color = uint32ColorPlayerX; // grey + } else { + switch ( foundplayer ) { + case 0: color = uint32ColorPlayer1; break; + case 1: color = uint32ColorPlayer2; break; + case 2: color = uint32ColorPlayer3; break; + case 3: color = uint32ColorPlayer4; break; + default: color = uint32ColorPlayerX; break; + } + } + } else if ( entity->sprite == 239 ) { + if (!players[player] || !players[player]->entity) { continue; } - if ( ticks % 120 - ticks % 60 ) - { - if ( !minotaur_timer ) - { + if ( ticks % 120 - ticks % 60 ) { + if ( !minotaur_timer ) { playSound(116, 64); } minotaur_timer = 1; - } - else - { + } else { minotaur_timer = 0; + continue; } - } - - if ( foundplayer >= 0 ) { - switch ( foundplayer ) - { - case 0: color = uint32ColorPlayer1; break; - case 1: color = uint32ColorPlayer2; break; - case 2: color = uint32ColorPlayer3; break; - case 3: color = uint32ColorPlayer4; break; - default: color = uint32ColorPlayerX; break; - } - if ( players[player] && players[player]->entity - && players[player]->entity->creatureShadowTaggedThisUid == entity->getUID() ) { - color = uint32ColorPlayerX; // grey - } - } else if ( entity->sprite == 239 ) { color = makeColor(255, 0, 0, 255); } else { - switch ( drawMonsterAlly ) { - case 0: - color = makeColor(32, 127, 32, 255); // green - break; - case 1: - color = makeColor(43, 90, 116, 255); // sky blue - break; - case 2: - color = makeColor(120, 114, 33, 255); // yellow - break; - case 3: - color = makeColor(102, 60, 83, 255); // pink - break; - default: - color = makeColor(96, 96, 96, 255); // grey - break; - } if ( players[player] && players[player]->entity && players[player]->entity->creatureShadowTaggedThisUid == entity->getUID() ) { - color = makeColor(96, 96, 96, 255); // grey + color = uint32ColorPlayerX_Ally; // grey + } else { + switch ( drawMonsterAlly ) { + case 0: uint32ColorPlayer1_Ally; break; + case 1: uint32ColorPlayer2_Ally; break; + case 2: uint32ColorPlayer3_Ally; break; + case 3: uint32ColorPlayer4_Ally; break; + default: uint32ColorPlayerX_Ally; break; + } } } + const real_t zoom = entity->sprite == 239 ? + minimapObjectZoom / 50.0: + minimapObjectZoom / 100.0; const real_t x = ((entity->x / 16.0) - xmin) * unitX + rect.x; const real_t y = ((entity->y / 16.0) - ymin) * unitY + rect.y; const real_t ang = entity->yaw; @@ -482,8 +467,8 @@ void drawMinimap(const int player, SDL_Rect rect) for (int c = 0; c < num_vertices; ++c) { const real_t vx = v[c][0] * cos(ang) - v[c][1] * sin(ang); const real_t vy = v[c][0] * sin(ang) + v[c][1] * cos(ang); - const real_t sx = vx * unitX * (minimapObjectZoom / 100.0); - const real_t sy = vy * unitY * (minimapObjectZoom / 100.0); + const real_t sx = vx * unitX * zoom; + const real_t sy = vy * unitY * zoom; glVertex2f(x + sx, Frame::virtualScreenY - (y + sy)); } glEnd(); diff --git a/src/ui/Button.hpp b/src/ui/Button.hpp index 4a3cb23fb..3b4d9b46b 100644 --- a/src/ui/Button.hpp +++ b/src/ui/Button.hpp @@ -97,6 +97,7 @@ class Button : public Widget { const char* getBackgroundActivated() const { return backgroundActivated.c_str(); } SDL_Rect getTextOffset() const { return textOffset; } Uint32 getColor() const { return color; } + const bool isOntop() const { return ontop; } void setBorder(int _border) { border = _border; } void setPos(int x, int y) { size.x = x; size.y = y; } @@ -119,6 +120,7 @@ class Button : public Widget { void setHJustify(const int _justify) { hjustify = static_cast(_justify); } void setVJustify(const int _justify) { vjustify = static_cast(_justify); } void setTextOffset(const SDL_Rect& offset) { textOffset = offset; } + void setOntop(const bool _ontop) { ontop = _ontop; } private: void (*callback)(Button&) = nullptr; //!< native callback for clicking @@ -140,4 +142,5 @@ class Button : public Widget { justify_t hjustify = CENTER; //!< horizontal text justification justify_t vjustify = CENTER; //!< vertical text justification SDL_Rect textOffset{0, 0, 0, 0}; //!< offset used by label test + bool ontop = false; //!< whether the button is drawn ontop of others }; diff --git a/src/ui/Frame.cpp b/src/ui/Frame.cpp index dc4013e90..7f934b13f 100644 --- a/src/ui/Frame.cpp +++ b/src/ui/Frame.cpp @@ -24,6 +24,19 @@ static const int _virtualScreenDefaultWidth = 1280; int Frame::_virtualScreenX = 0; int Frame::_virtualScreenY = 0; +static int getMouseOwnerPauseMenu() { +#ifndef EDITOR + if (gamePaused) { + for (int i = 0; i < MAXPLAYERS; ++i) { + if (inputs.bPlayerUsingKeyboardControl(i)) { + return i; + } + } + } +#endif + return clientnum; +} + #ifndef EDITOR #include "../net.hpp" ConsoleCommand myCmd("/resizegui", "change gui size", @@ -409,21 +422,7 @@ void Frame::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectorisOntop() ) - { - continue; + if ( !field->isOntop() ) { + field->draw(_size, scroll, selectedWidgets); } - field->draw(_size, scroll, selectedWidgets); } // draw buttons for (auto button : buttons) { - button->draw(_size, scroll, selectedWidgets); + if ( !button->isOntop() ) { + button->draw(_size, scroll, selectedWidgets); + } } // draw sliders for (auto slider : sliders) { - slider->draw(_size, scroll, selectedWidgets); + if ( !slider->isOntop() ) { + slider->draw(_size, scroll, selectedWidgets); + } } // draw subframes @@ -679,13 +680,25 @@ void Frame::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectordraw(_size, scroll, selectedWidgets); } + // draw "on top" sliders + for (auto slider : sliders) { + if ( slider->isOntop() ) { + slider->draw(_size, scroll, selectedWidgets); + } + } + + // draw "on top" buttons + for (auto button : buttons) { + if ( button->isOntop() ) { + button->draw(_size, scroll, selectedWidgets); + } + } + // draw "on top" fields for ( auto field : fields ) { - if ( !field->isOntop() ) - { - continue; + if ( field->isOntop() ) { + field->draw(_size, scroll, selectedWidgets); } - field->draw(_size, scroll, selectedWidgets); } // draw "on top" images @@ -822,21 +835,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: fullSize.h += (actualSize.w > size.w) ? sliderSize : 0; fullSize.w += (actualSize.h > size.h) ? sliderSize : 0; - int mouseowner_pausemenu = clientnum; -#ifndef EDITOR - if ( gamePaused ) - { - for ( int i = 0; i < MAXPLAYERS; ++i ) - { - if ( inputs.bPlayerUsingKeyboardControl(i) ) - { - mouseowner_pausemenu = i; - break; - } - } - } -#endif - int mouseowner = intro ? clientnum : (gamePaused ? mouseowner_pausemenu : owner); + const int mouseowner = intro ? clientnum : (gamePaused ? getMouseOwnerPauseMenu() : owner); #ifdef EDITOR Sint32 mousex = (::mousex / (float)xres) * (float)Frame::virtualScreenX; @@ -953,12 +952,36 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } } + // process "ontop" fields + for (int i = fields.size() - 1; i >= 0; --i) { + Field* field = fields[i]; + if (field->isOntop()) { + processField(_size, *field, destWidget, result); + } + } + + // process "ontop" buttons + for (int i = buttons.size() - 1; i >= 0; --i) { + Button* button = buttons[i]; + if (button->isOntop()) { + processButton(_size, *button, destWidget, result); + } + } + + // process "ontop" (widget) sliders + for (int i = sliders.size() - 1; i >= 0; --i) { + Slider* slider = sliders[i]; + if (slider->isOntop()) { + processSlider(_size, *slider, destWidget, result); + } + } + // process frames { for (int i = frames.size() - 1; i >= 0; --i) { Frame* frame = frames[i]; - result_t frameResult = frame->process(_size, actualSize, selectedWidgets, usable); - usable = result.usable = frameResult.usable; + result_t frameResult = frame->process(_size, actualSize, selectedWidgets, result.usable); + result.usable = frameResult.usable; if (!frameResult.removed) { if (frameResult.tooltip != nullptr) { result = frameResult; @@ -971,19 +994,19 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } // scroll with right stick - if (usable && allowScrolling && allowScrollBinds) { + if (result.usable && allowScrolling && allowScrollBinds) { Input& input = Input::inputs[owner]; // x scroll if (this->actualSize.w > size.w) { if (input.binary("MenuScrollRight")) { this->actualSize.x += std::min(this->actualSize.x + 5, this->actualSize.w - _size.w); - usable = result.usable = false; + result.usable = false; syncScroll(); } else if (input.binary("MenuScrollLeft")) { this->actualSize.x -= std::max(this->actualSize.x - 5, 0); - usable = result.usable = false; + result.usable = false; syncScroll(); } } @@ -992,12 +1015,12 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: if (this->actualSize.h > size.h) { if (input.binary("MenuScrollDown")) { this->actualSize.y = std::min(this->actualSize.y + 5, this->actualSize.h - _size.h); - usable = result.usable = false; + result.usable = false; syncScroll(); } else if (input.binary("MenuScrollUp")) { this->actualSize.y = std::max(this->actualSize.y - 5, 0); - usable = result.usable = false; + result.usable = false; syncScroll(); } } @@ -1010,7 +1033,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: #endif // scroll with mouse wheel - if (parent != nullptr && !hollow && mouseActive && rectContainsPoint(fullSize, omousex, omousey) && usable) { + if (parent != nullptr && !hollow && mouseActive && rectContainsPoint(fullSize, omousex, omousey) && result.usable) { bool mwheeldown = false; bool mwheelup = false; if (allowScrolling && allowScrollBinds) { @@ -1021,7 +1044,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: mwheelup = true; } if (mwheeldown || mwheelup) { - usable = result.usable = false; + result.usable = false; // x scroll with mouse wheel if (this->actualSize.w > size.w) { @@ -1125,7 +1148,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: this->actualSize.x = std::min(std::max(0, this->actualSize.x), std::max(0, this->actualSize.w - _size.w)); syncScroll(); } - usable = result.usable = false; + result.usable = false; ticks = -1; // hack to fix sliders in drop downs } else { if ( mouseActive && rectContainsPoint(handleRect, omousex, omousey) ) { @@ -1133,7 +1156,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: draggingHSlider = true; oldSliderX = this->actualSize.x; } - usable = result.usable = false; + result.usable = false; ticks = -1; // hack to fix sliders in drop downs } else if ( mouseActive && rectContainsPoint(sliderRect, omousex, omousey) ) { if (mousestatus[SDL_BUTTON_LEFT]) { @@ -1142,7 +1165,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: syncScroll(); mousestatus[SDL_BUTTON_LEFT] = 0; } - usable = result.usable = false; + result.usable = false; ticks = -1; // hack to fix sliders in drop downs } } @@ -1179,7 +1202,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: this->actualSize.y = std::min(std::max(0, this->actualSize.y), std::max(0, this->actualSize.h - _size.h)); syncScroll(); } - usable = result.usable = false; + result.usable = false; ticks = -1; // hack to fix sliders in drop downs } else { if ( mouseActive && rectContainsPoint(handleRect, omousex, omousey) ) { @@ -1187,7 +1210,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: draggingVSlider = true; oldSliderY = this->actualSize.y; } - usable = result.usable = false; + result.usable = false; ticks = -1; // hack to fix sliders in drop downs } else if ( mouseActive && rectContainsPoint(sliderRect, omousex, omousey) ) { if (mousestatus[SDL_BUTTON_LEFT]) { @@ -1196,72 +1219,39 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: syncScroll(); mousestatus[SDL_BUTTON_LEFT] = 0; } - usable = result.usable = false; + result.usable = false; ticks = -1; // hack to fix sliders in drop downs } } } } - // process buttons - { - for (int i = buttons.size() - 1; i >= 0; --i) { - Button* button = buttons[i]; - if (!destWidget) { - destWidget = button->handleInput(); - } - - Button::result_t buttonResult = button->process(_size, actualSize, usable); - if (usable && buttonResult.highlighted) { - result.highlightTime = buttonResult.highlightTime; - result.tooltip = buttonResult.tooltip; - if (mouseActive) { - button->select(); - } - if (buttonResult.clicked) { - button->activate(); - } - result.usable = usable = false; - } - - if (destWidget && button->isSelected()) { - button->deselect(); - } + // process (widget) sliders + for (int i = sliders.size() - 1; i >= 0; --i) { + Slider* slider = sliders[i]; + if (!slider->isOntop()) { + processSlider(_size, *slider, destWidget, result); } } - // process (widget) sliders - { - for (int i = sliders.size() - 1; i >= 0; --i) { - Slider* slider = sliders[i]; - - if (!destWidget && !slider->isActivated()) { - destWidget = slider->handleInput(); - } else { - result.usable = usable = slider->control() ? usable : false; - } - - Slider::result_t sliderResult = slider->process(_size, actualSize, usable); - if (usable && sliderResult.highlighted) { - result.highlightTime = sliderResult.highlightTime; - result.tooltip = sliderResult.tooltip; - if (mouseActive) { - slider->select(); - } - if (sliderResult.clicked) { - slider->fireCallback(); - } - result.usable = usable = false; - } + // process buttons + for (int i = buttons.size() - 1; i >= 0; --i) { + Button* button = buttons[i]; + if (!button->isOntop()) { + processButton(_size, *button, destWidget, result); + } + } - if (destWidget && slider->isSelected()) { - slider->deselect(); - } - } + // process fields + for (int i = fields.size() - 1; i >= 0; --i) { + Field* field = fields[i]; + if (!field->isOntop()) { + processField(_size, *field, destWidget, result); + } } // process the frame's list entries - if (usable && list.size() > 0) { + if (result.usable && list.size() > 0) { for (int i = 0; i < list.size(); ++i) { entry_t* entry = list[i]; if (entry->suicide) { @@ -1305,7 +1295,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } } } - result.usable = usable = false; + result.usable = false; } else { entry->highlightTime = SDL_GetTicks(); entry->highlighted = false; @@ -1314,66 +1304,19 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } } - // process fields - { - for (int i = fields.size() - 1; i >= 0; --i) { - Field* field = fields[i]; - - // widget capture input - if (field->isActivated()) { -#ifndef EDITOR - if (inputs.hasController(field->getOwner())) { - if (input.consumeBinaryToggle("MenuConfirm") || - input.consumeBinaryToggle("MenuCancel")) { - field->deactivate(); - } - } -#endif - } - else if (!destWidget) { - destWidget = field->handleInput(); - } - - Field::result_t fieldResult = field->process(_size, actualSize, usable); - if (usable && fieldResult.highlighted) { - result.highlightTime = fieldResult.highlightTime; - result.tooltip = fieldResult.tooltip; - if (mouseActive && field->isEditable()) { - field->select(); - } - if (field->isSelected()) { - result.usable = usable = false; - } - } - - if (fieldResult.entered) { - result.usable = usable = false; - if (field->getCallback()) { - (*field->getCallback())(*field); - } else { - printlog("modified field with no callback"); - } - } - - if (destWidget && field->isSelected()) { - field->deselect(); - } - } - } - // scroll with arrows or left stick - if (usable && allowScrolling && allowScrollBinds && scrollWithLeftControls) { + if (result.usable && allowScrolling && allowScrollBinds && scrollWithLeftControls) { Input& input = Input::inputs[owner]; // x scroll if (this->actualSize.w > size.w) { if (input.binaryToggle("MenuRight") || input.binaryToggle("AltMenuRight")) { scrollInertiaX += .15; - usable = result.usable = false; + result.usable = false; } else if (input.binaryToggle("MenuLeft") || input.binaryToggle("AltMenuLeft")) { scrollInertiaX -= .15; - usable = result.usable = false; + result.usable = false; } } @@ -1381,24 +1324,24 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: if (this->actualSize.h > size.h) { if (input.binaryToggle("MenuDown") || input.binaryToggle("AltMenuDown")) { scrollInertiaY += .15; - usable = result.usable = false; + result.usable = false; } else if (input.binaryToggle("MenuUp") || input.binaryToggle("AltMenuUp")) { scrollInertiaY -= .15; - usable = result.usable = false; + result.usable = false; } } } if ( mouseActive && rectContainsPoint(_size, omousex, omousey) && !hollow ) { //messagePlayer(0, "%d: %s", getOwner(), getName()); - if (clickable && usable) { + if (clickable && result.usable) { if (mousestatus[SDL_BUTTON_LEFT]) { mousestatus[SDL_BUTTON_LEFT] = 0; activate(); } } - result.usable = usable = false; + result.usable = false; } if (toBeDeleted) { @@ -1413,6 +1356,123 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: return result; } +void Frame::processField(const SDL_Rect& _size, Field& field, Widget*& destWidget, result_t& result) { + Input& input = Input::inputs[owner]; + +#ifdef EDITOR + const bool mouseActive = true; +#else + const int mouseowner = intro ? clientnum : (gamePaused ? getMouseOwnerPauseMenu() : owner); + const bool mouseActive = inputs.getVirtualMouse(mouseowner)->draw_cursor || mousexrel || mouseyrel; +#endif + + // widget capture input + if (field.isActivated()) { +#ifndef EDITOR + if (inputs.hasController(field.getOwner())) { + if (input.consumeBinaryToggle("MenuConfirm") || + input.consumeBinaryToggle("MenuCancel")) { + field.deactivate(); + } + } +#endif + } + else if (!destWidget) { + destWidget = field.handleInput(); + } + + Field::result_t fieldResult = field.process(_size, actualSize, result.usable); + if (result.usable && fieldResult.highlighted) { + result.highlightTime = fieldResult.highlightTime; + result.tooltip = fieldResult.tooltip; + if (mouseActive && field.isEditable()) { + field.select(); + } + if (field.isSelected()) { + result.usable = false; + } + } + + if (fieldResult.entered) { + result.usable = false; + if (field.getCallback()) { + (*field.getCallback())(field); + } else { + printlog("modified field with no callback"); + } + } + + if (destWidget && field.isSelected()) { + field.deselect(); + } +} + +void Frame::processButton(const SDL_Rect& _size, Button& button, Widget*& destWidget, result_t& result) { + Input& input = Input::inputs[owner]; + +#ifdef EDITOR + const bool mouseActive = true; +#else + const int mouseowner = intro ? clientnum : (gamePaused ? getMouseOwnerPauseMenu() : owner); + const bool mouseActive = inputs.getVirtualMouse(mouseowner)->draw_cursor || mousexrel || mouseyrel; +#endif + + if (!destWidget) { + destWidget = button.handleInput(); + } + + Button::result_t buttonResult = button.process(_size, actualSize, result.usable); + if (result.usable && buttonResult.highlighted) { + result.highlightTime = buttonResult.highlightTime; + result.tooltip = buttonResult.tooltip; + if (mouseActive) { + button.select(); + } + if (buttonResult.clicked) { + button.activate(); + } + result.usable = false; + } + + if (destWidget && button.isSelected()) { + button.deselect(); + } +} + +void Frame::processSlider(const SDL_Rect& _size, Slider& slider, Widget*& destWidget, result_t& result) { + Input& input = Input::inputs[owner]; + +#ifdef EDITOR + const bool mouseActive = true; +#else + const int mouseowner = intro ? clientnum : (gamePaused ? getMouseOwnerPauseMenu() : owner); + const bool mouseActive = inputs.getVirtualMouse(mouseowner)->draw_cursor || mousexrel || mouseyrel; +#endif + + if (!destWidget && !slider.isActivated()) { + destWidget = slider.handleInput(); + } else { + result.usable = slider.control() ? result.usable : false; + } + + Slider::result_t sliderResult = slider.process(_size, actualSize, result.usable); + if (result.usable && sliderResult.highlighted) { + result.highlightTime = sliderResult.highlightTime; + result.tooltip = sliderResult.tooltip; + if (mouseActive) { + slider.select(); + } + if (sliderResult.clicked) { + slider.fireCallback(); + } + result.usable = false; + } + + if (destWidget && slider.isSelected()) { + slider.deselect(); + } +} + void Frame::postprocess() { #if !defined(EDITOR) && !defined(NDEBUG) static ConsoleVariable cvar("/disableframetick", false); diff --git a/src/ui/Frame.hpp b/src/ui/Frame.hpp index 416dfefde..0fdce6506 100644 --- a/src/ui/Frame.hpp +++ b/src/ui/Frame.hpp @@ -438,6 +438,10 @@ class Frame : public Widget { bool capturesMouseImpl(SDL_Rect& _size, SDL_Rect& _actualSize, bool realtime) const; SDL_Rect getRelativeMousePositionImpl(SDL_Rect& _size, SDL_Rect& _actualSize, bool realtime) const; + + void processField(const SDL_Rect& _size, Field& field, Widget*& destWidget, result_t& result); + void processButton(const SDL_Rect& _size, Button& button, Widget*& destWidget, result_t& result); + void processSlider(const SDL_Rect& _size, Slider& slider, Widget*& destWidget, result_t& result); }; // root frame object diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 4fc337ee0..16e874194 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -795,6 +795,9 @@ namespace MainMenu { } static Frame* createPrompt(const char* name, bool small = true) { + if (!main_menu_frame) { + return nullptr; + } if (main_menu_frame->findFrame(name)) { return nullptr; } @@ -8262,8 +8265,45 @@ namespace MainMenu { }; static constexpr int num_races = sizeof(races) / sizeof(races[0]); + static const char* race_descs[] = { + "Human\n" + "Traits\n" + "None\n\n" + + "Resistances Weaknesses\n" + "None None\n\n" + + "Friendly With:\n" + "Humans, Automatons", + + "Skeleton\n" + "Traits\n" + "\x1E Does not Hunger or Starve\n" + "\x1E HP and MP Regeneration\n" + " reduced by 75%\n" + "\x1E Self-Resurrects for 75MP\n" + "\x1E Immune to Burning\n" + "\x1E Swim speed reduced by 50%\n\n" + + "Resistances Weaknesses\n" + "\x1E Swords \x1E Maces\n" + "\x1E Ranged \x1E Polearms\n" + "\x1E Axes \x1E Smite\n" + "\x1E Magic\n\n" + + "Friendly With\n" + "\x1E Ghouls, Automatons", + }; + + if (details) { + auto details_text = card->addField("details", 1024); + details_text->setFont(smallfont_no_outline); + details_text->setSize(SDL_Rect{40, 68, 242, 298}); + details_text->setText(race_descs[stats[index]->playerRace]); + } + static auto race_fn = [](Button& button, int index){ - Frame* frame = static_cast(button.getParent()); + auto frame = static_cast(button.getParent()); for (int c = 0; c < num_races; ++c) { auto race = races[c]; if (strcmp(button.getName(), race) == 0) { @@ -8299,6 +8339,12 @@ namespace MainMenu { stats[index]->clearStats(); initClass(index); sendPlayerOverNet(); + + auto card = static_cast(frame->getParent()); + auto details_text = card->findField("details"); + if (details_text) { + details_text->setText(race_descs[stats[index]->playerRace]); + } }; auto subframe = card->addFrame("subframe"); @@ -8311,7 +8357,7 @@ namespace MainMenu { for (int c = 0; c < num_races; ++c) { auto race = subframe->addButton(races[c]); - race->setSize(SDL_Rect{0, c * 36 + 3, 30, 30}); + race->setSize(SDL_Rect{0, c * 36 + 2, 30, 30}); race->setBackground("*#images/ui/Main Menus/sublist_item-unpicked.png"); if (!enabledDLCPack1 && c >= 1 && c <= 4) { race->setIcon("*#images/ui/Main Menus/sublist_item-locked.png"); @@ -8327,13 +8373,15 @@ namespace MainMenu { } race->setStyle(Button::style_t::STYLE_RADIO); race->setBorder(0); - race->setColor(0); + race->setColor(0xffffffff); race->setBorderColor(0); - race->setHighlightColor(0); + race->setHighlightColor(0xffffffff); race->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); race->addWidgetAction("MenuStart", "confirm"); race->setWidgetBack("back_button"); - race->setWidgetRight("appearances"); + if (c == 0) { + race->setWidgetRight("appearances"); + } if (c < num_races - 1) { race->setWidgetDown(races[c + 1]); } @@ -8355,13 +8403,13 @@ namespace MainMenu { label->setColor(makeColor(166, 123, 81, 255)); label->setText(races[c]); label->setFont(smallfont_outline); - label->setSize(SDL_Rect{32, c * 36, 64, 36}); + label->setSize(SDL_Rect{32, c * 36, 96, 36}); label->setHJustify(Field::justify_t::LEFT); label->setVJustify(Field::justify_t::CENTER); } auto appearances = subframe->addFrame("appearances"); - appearances->setSize(SDL_Rect{128, 0, 122, 36}); + appearances->setSize(SDL_Rect{102, 0, 122, 36}); appearances->setActualSize(SDL_Rect{0, 4, 122, 36}); appearances->setFont("fonts/pixel_maz.ttf#32#2"); appearances->setBorder(0); @@ -8415,15 +8463,15 @@ namespace MainMenu { "selection_box" ); appearance_selected->disabled = true; - appearance_selected->ontop = true; auto appearance_uparrow = subframe->addButton("appearance_uparrow"); - appearance_uparrow->setSize(SDL_Rect{96, 0, 20, 32}); + appearance_uparrow->setSize(SDL_Rect{92, 2, 20, 32}); appearance_uparrow->setBackground("*images/ui/Main Menus/sublist_item-pickleft.png"); appearance_uparrow->setHighlightColor(makeColor(255, 255, 255, 255)); appearance_uparrow->setColor(makeColor(255, 255, 255, 255)); appearance_uparrow->setDisabled(true); appearance_uparrow->setInvisible(true); + appearance_uparrow->setOntop(true); appearance_uparrow->setCallback([](Button& button){ auto card = static_cast(button.getParent()); auto appearances = card->findFrame("appearances"); assert(appearances); @@ -8435,12 +8483,13 @@ namespace MainMenu { }); auto appearance_downarrow = subframe->addButton("appearance_downarrow"); - appearance_downarrow->setSize(SDL_Rect{214, 0, 20, 32}); + appearance_downarrow->setSize(SDL_Rect{214, 2, 20, 32}); appearance_downarrow->setBackground("*images/ui/Main Menus/sublist_item-pickright.png"); appearance_downarrow->setHighlightColor(makeColor(255, 255, 255, 255)); appearance_downarrow->setColor(makeColor(255, 255, 255, 255)); appearance_downarrow->setDisabled(true); appearance_downarrow->setInvisible(true); + appearance_downarrow->setOntop(true); appearance_downarrow->setCallback([](Button& button){ auto card = static_cast(button.getParent()); auto appearances = card->findFrame("appearances"); assert(appearances); @@ -8580,7 +8629,11 @@ namespace MainMenu { auto show_race_info = bottom->addButton("show_race_info"); show_race_info->setFont(smallfont_outline); - show_race_info->setText("Show Race\nInfo"); + if (details) { + show_race_info->setText("Hide Race\nInfo"); + } else { + show_race_info->setText("Show Race\nInfo"); + } show_race_info->setColor(makeColor(255, 255, 255, 255)); show_race_info->setHighlightColor(makeColor(255, 255, 255, 255)); show_race_info->setBackground("*images/ui/Main Menus/Play/PlayerCreation/RaceSelection/UI_RaceSelection_ButtonShowDetails_00.png"); @@ -8591,11 +8644,20 @@ namespace MainMenu { show_race_info->setWidgetUp("disable_abilities"); show_race_info->setWidgetDown("confirm"); show_race_info->setWidgetLeft("female"); - switch (index) { - case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, true);}); break; - case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, true);}); break; - case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, true);}); break; - case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, true);}); break; + if (details) { + switch (index) { + case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, false);}); break; + case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, false);}); break; + case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, false);}); break; + case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, false);}); break; + } + } else { + switch (index) { + case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, true);}); break; + case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, true);}); break; + case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, true);}); break; + case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, true);}); break; + } } /*auto confirm = card->addButton("confirm"); @@ -11323,7 +11385,7 @@ namespace MainMenu { list->setWidgetBack("back_button"); list->setWidgetUp("online_tab"); list->setWidgetRight("players"); - list->setWidgetDown("enter_code"); + list->setWidgetDown("filter_settings"); list->addSyncScrollTarget("players"); list->addSyncScrollTarget("pings"); list->select(); @@ -11361,7 +11423,7 @@ namespace MainMenu { list->setWidgetUp("online_tab"); list->setWidgetRight("pings"); list->setWidgetLeft("names"); - list->setWidgetDown("enter_code"); + list->setWidgetDown("filter_settings"); list->addSyncScrollTarget("names"); list->addSyncScrollTarget("pings"); } @@ -11397,7 +11459,7 @@ namespace MainMenu { list->setWidgetBack("back_button"); list->setWidgetUp("online_tab"); list->setWidgetLeft("players"); - list->setWidgetDown("enter_code"); + list->setWidgetDown("filter_settings"); list->addSyncScrollTarget("names"); list->addSyncScrollTarget("players"); } @@ -11493,6 +11555,7 @@ namespace MainMenu { soundActivate(); } else { soundCancel(); + button.select(); } }); @@ -13625,14 +13688,18 @@ namespace MainMenu { static void mainQuitToDesktop(Button& button) { static const char* quit_messages[][3] { {"You want to leave, eh?\nThen get out and don't come back!", "Fine geez", "Never!"}, + {"Did the wittle gobwins\nhurt your feewings?", "Yes", "No"}, {"Just cancel your plans.\nI'll wait.", "Good luck", "Sure"}, {"You couldn't kill the lich anyway.", "You're right", "Oh yeah?"}, + {"Mad cuz bad!\nGit gud!", "I am anger", "Okay"}, {"The gnomes are laughing at you!\nAre you really gonna take that?", "Yeah :(", "No way!"}, {"Don't go now! There's a\nboulder trap around the corner!", "Kill me", "Oh thanks"}, {"I'll tell your parents\nyou said a bad word.", "Poop", "Please no"}, {"Please don't leave!\nThere's more treasure to loot!", "Don't care", "More loot!"}, {"Just be glad I can't summon\nthe minotaur in real life.", "Too bad", "Point taken"}, - {"I'd leave too.\nThis game looks just like Minecraft.", "lol", "Ouch"} + {"Off to leave a salty review I see?", "... yeah", "No way!"}, + {"I'd leave too.\nThis game looks just like Minecraft.", "lol", "Ouch"}, + {"Okay, I see how it is.\nSee if I'm still here tomorrow.", "Whatever", "I love you!"}, }; constexpr int num_quit_messages = sizeof(quit_messages) / (sizeof(const char*) * 3); diff --git a/src/ui/Slider.hpp b/src/ui/Slider.hpp index 152f94e8b..b7018882e 100644 --- a/src/ui/Slider.hpp +++ b/src/ui/Slider.hpp @@ -88,6 +88,7 @@ class Slider : public Widget { const char* getHandleImageActivated() const { return handleImageActivated.c_str(); } const char* getHandleImage() const { return handleImage.c_str(); } const char* getRailImage() const { return railImage.c_str(); } + const bool isOntop() const { return ontop; } void setOrientation(orientation_t o) { orientation = o; } void setValue(float _value) { value = _value; } @@ -104,6 +105,7 @@ class Slider : public Widget { void setHandleImageActivated(const char* _image) { handleImageActivated = _image; } void setHandleImage(const char* _image) { handleImage = _image; } void setRailImage(const char* _image) { railImage = _image; } + void setOntop(const bool _ontop) { ontop = _ontop; } private: void (*callback)(Slider&) = nullptr; //!< native callback for clicking @@ -124,4 +126,5 @@ class Slider : public Widget { std::string handleImageActivated; //!< image to use for the handle (when activated) std::string handleImage; //!< image to use for the handle std::string railImage; //!< image to use for the rail + bool ontop = false; //!< whether the slider is drawn ontop of others }; From 63cd62df7fda7e3788e088655882439ebc5587d7 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 12 Jul 2022 12:25:55 -0700 Subject: [PATCH 05/21] colored lines, race descriptions, fix softlock on res change, fix gui screen height < 720p --- src/ui/Field.cpp | 18 +- src/ui/Field.hpp | 15 +- src/ui/Frame.cpp | 3 +- src/ui/MainMenu.cpp | 517 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 510 insertions(+), 43 deletions(-) diff --git a/src/ui/Field.cpp b/src/ui/Field.cpp index f4d6ff8a2..342435f86 100644 --- a/src/ui/Field.cpp +++ b/src/ui/Field.cpp @@ -196,10 +196,11 @@ void Field::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectorsecond; + if (parent && static_cast(parent)->getOpacity() < 100.0) { Uint8 r, g, b, a; - ::getColor(color, &r, &g, &b, &a); + ::getColor(blendColor, &r, &g, &b, &a); a *= static_cast(parent)->getOpacity() / 100.0; - if ( a > 0 ) + if( a > 0 ) { - text->drawColor(src, scaledDest, viewport, makeColor( r, g, b, a)); + text->drawColor(src, scaledDest, viewport, makeColor(r, g, b, a)); } } else { - text->drawColor(src, scaledDest, viewport, color); + text->drawColor(src, scaledDest, viewport, blendColor); } // draw cursor if (!nexttoken && showCursor && activated) { SDL_Rect cursorSize{scaledDest.x + scaledDest.w - 2, scaledDest.y, 2, scaledDest.h}; - white->drawColor(nullptr, cursorSize, viewport, color); + white->drawColor(nullptr, cursorSize, viewport, blendColor); } - - ++currentLine; } while ((token = nexttoken) != NULL); free(buf); diff --git a/src/ui/Field.hpp b/src/ui/Field.hpp index 95172952d..b717fc16d 100644 --- a/src/ui/Field.hpp +++ b/src/ui/Field.hpp @@ -92,13 +92,21 @@ class Field : public Widget { //! add a key value pair to the highlighted word map //! @param word the word 'index' in the sentence (first word is 0) //! @param color the color to set the word to - void addWordToHighlight(int word, Uint32 color) { wordsToHighlight[word] = color; } + void addWordToHighlight(int word, Uint32 color) { wordsToHighlight[word] = color; } //! gets map for highlighted words - const std::map& getWordsToHighlight() const { return wordsToHighlight; } + const std::map& getWordsToHighlight() const { return wordsToHighlight; } //! reset the highlighted word map - void clearWordsToHighlight() { wordsToHighlight.clear(); } + void clearWordsToHighlight() { wordsToHighlight.clear(); } + + //! add a key value pair to the colored line map + //! @param line the line number associated with the color + //! @param color the color to set the line to + void addColorToLine(int line, Uint32 color) { linesToColor[line] = color; } + + //! reset the line color map + void clearLinesToColor() { linesToColor.clear(); } static const int TEXT_HIGHLIGHT_WORDS_PER_LINE = 10000; @@ -168,4 +176,5 @@ class Field : public Widget { void (*callback)(Field&) = nullptr; //!< the callback to use after text is entered bool ontop = false; //!< whether the field is drawn ontop of others std::map wordsToHighlight; //!< word indexes in the field matching the keys in the map will be colored with the mapped value + std::map linesToColor; //!< lines that have a particular color }; diff --git a/src/ui/Frame.cpp b/src/ui/Frame.cpp index 7f934b13f..f0e207d98 100644 --- a/src/ui/Frame.cpp +++ b/src/ui/Frame.cpp @@ -21,6 +21,7 @@ const Sint32 Frame::sliderSize = 16; static const int _virtualScreenDefaultWidth = 1280; +static const int _virtualScreenMinHeight = 720; int Frame::_virtualScreenX = 0; int Frame::_virtualScreenY = 0; @@ -128,7 +129,7 @@ void Frame::guiInit() { const int defaultWidth = _virtualScreenDefaultWidth; const int vsize = (yres * defaultWidth) / xres; _virtualScreenX = defaultWidth; - _virtualScreenY = vsize; + _virtualScreenY = std::max(vsize, _virtualScreenMinHeight); } fboInit(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 16e874194..748c4720e 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -8266,44 +8266,439 @@ namespace MainMenu { static constexpr int num_races = sizeof(races) / sizeof(races[0]); static const char* race_descs[] = { - "Human\n" + // Human + "\n" "Traits\n" - "None\n\n" - - "Resistances Weaknesses\n" - "None None\n\n" - - "Friendly With:\n" + "None\n" + "\n" + "Resistances\n" + "None\n" + "\n" + "Friendly With\n" "Humans, Automatons", - "Skeleton\n" + // Skeleton + "\n" "Traits\n" "\x1E Does not Hunger or Starve\n" "\x1E HP and MP Regeneration\n" " reduced by 75%\n" "\x1E Self-Resurrects for 75MP\n" "\x1E Immune to Burning\n" - "\x1E Swim speed reduced by 50%\n\n" + "\x1E Swim speed reduced by 50%\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\x1E Ranged\n" + "\x1E Axes\n" + "\x1E Magic\n" + "\n" + "Friendly With\n" + "\x1E Ghouls, Automatons", + + // Vampire + "\n" + "Racial Spells\n" + "\x1E Bloodletting, Levitation\n" + "Traits\n" + "\x1E Uses HP when out of MP to\n" + " cast and sustain spells\n" + "\x1E Can only sustain hunger\n" + " with Blood Vials\n" + "\x1E Kills may drop Blood Vials\n" + "\x1E Bloodletting / Assassinate\n" + " kills drop Blood Vials\n" + "Resistances\n" + "\x1E Swords\n" + "\x1E Ranged\n" + "\x1E Axes\n" + "\x1E Magic\n" + "Friendly With\n" + "\x1E Vampires, Automatons", - "Resistances Weaknesses\n" - "\x1E Swords \x1E Maces\n" - "\x1E Ranged \x1E Polearms\n" - "\x1E Axes \x1E Smite\n" - "\x1E Magic\n\n" + // Succubus + "\n" + "Racial Spells\n" + "\x1E Teleport, Polymorph\n" + "Traits\n" + "\x1E Cursed equipment can be\n" + " removed; gives bonuses\n" + "\x1E Blessed equipment is not\n" + " removable; gives bonuses\n" + "\x1E +MP from Strangulation\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Succubi, Incubi,\n" + " Automatons", + // Goatman + "\n" + "Traits\n" + "\x1E Afflicted with Alcoholism,\n" + " causing Hangovers\n" + "\x1E Immune to Drunk dizziness\n" + "\x1E +STR +CHR while Drunk\n" + "\x1E Can recruit fellow Drunks\n" + "\x1E Eats Tins without an Opener\n" + "\x1E Immune to Greasy effect\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\n" + "\n" + "\n" + "\n" "Friendly With\n" - "\x1E Ghouls, Automatons", + "\x1E Goatmen, Automatons", + + // Automaton + "\n" + "Racial Spells\n" + "\x1E Salvage\n" + "Traits\n" + "\x1E Requires Heat (HT) to\n" + " survive and cast spells\n" + "\x1E Regen HT with a hot boiler\n" + + " fueled by gems and paper\n" + "\x1E Can remove cursed items\n" + "\x1E Immune to Burning\n" + "\x1E +20 to Tinkering Repairs\n" + "\x1E Welcomed by Shopkeepers\n" + "Resistances\n" + "\x1E Ranged\n" + "\x1E Unarmed\n" + "\n" + "Friendly With\n" + "\x1E Automatons, Humans", + + // Incubus + "\n" + "Racial Spells\n" + "\x1E Teleport, Arcane Mark\n" + "Traits\n" + "\x1E Cursed equipment can be\n" + " removed; gives bonuses\n" + "\x1E Blessed equipment is not\n" + " removable; gives bonuses\n" + "\x1E +MP from Strangulation\n" + "\n" + "Resistances\n" + "\x1E Magic\n" + "\x1E Polearms\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Succubi, Incubi,\n" + " Automatons", + + // Goblin + "\n" + "Traits\n" + "\x1E Worn Equipment has lower\n" + " chance to degrade\n" + "\x1E Raising any Melee Weapon\n" + " Skill raises all of them\n" + "\x1E Weapon Skills raise slower\n" + "\x1E Cannot Memorize new spells\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\x1E Unarmed\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Goblins, Automatons", + + // Insectoid + "\n" + "Racial Spells\n" + "\x1E Flutter, Dash, Spray Acid\n" + "Traits\n" + "\x1E Requires Energy (EN) to\n" + " survive and cast spells\n" + "\x1E Regain EN by consuming\n" + " food and sweet liquids\n" + "\x1E Immune to Poison\n" + "\x1E Immune to rotten food\n" + "Resistances\n" + "\x1E Maces\n" + "\x1E Unarmed\n" + "\n" + "Friendly With\n" + "\x1E Insectoids, Scarabs\n" + " Scorpions, Automatons\n", + }; + + static const char* race_descs_right[] = { + // Human + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "None", + + // Skeleton + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Maces\n" + "\x1E Polearms\n" + "\x1E Smite", + + // Vampire + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Maces\n" + "\x1E Polearms\n" + "\x1E Water\n" + "\x1E Smite", + + // Succubus + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Magic\n" + "\x1E Polearms\n" + "\x1E Smite", + + // Goatman + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Polearms\n" + "\x1E Axes\n" + "\x1E Ranged\n" + "\x1E Magic", + + // Automaton + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Axes\n" + "\x1E Maces\n" + "\x1E Magic", + + // Incubus + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Ranged\n" + "\x1E Swords\n" + "\x1E Smite", + + // Goblin + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Axes\n" + "\x1E Polearms\n" + "\x1E Ranged", + + // Insectoid + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Axes\n" + "\x1E Polearms\n" + "\x1E Ranged", }; + constexpr Uint32 color_human = makeColorRGB(169, 185, 212); + constexpr Uint32 color_dlc1 = makeColorRGB(241, 129, 78); + constexpr Uint32 color_dlc2 = makeColorRGB(255, 53, 206); + constexpr Uint32 color_traits = makeColorRGB(184, 146, 109); + constexpr Uint32 color_pro = makeColorRGB(88, 203, 255); + constexpr Uint32 color_con = makeColorRGB(255, 56, 56); + constexpr Uint32 color_energy = makeColorRGB(21, 255, 0); + constexpr Uint32 color_heat = makeColorRGB(241, 129, 78); + + static auto update_details_text = [](Frame& card){ + const int index = card.getOwner(); + const int race = stats[index]->playerRace; + + // color title + Uint32 color_race; + if (race >= RACE_SKELETON && race <= RACE_GOATMAN) { + color_race = color_dlc1; + } + else if (race >= RACE_AUTOMATON && race <= RACE_INSECTOID) { + color_race = color_dlc2; + } + else { + color_race = color_human; + } + + auto details_title = card.findField("details_title"); + if (details_title) { + details_title->clearLinesToColor(); + details_title->setText(races[race]); + details_title->setColor(color_race); + } + auto details_text = card.findField("details"); + if (details_text) { + details_text->clearLinesToColor(); + details_text->setText(race_descs[race]); + + switch (race) { + default: return; + case RACE_HUMAN: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(4, color_pro); + details_text->addColorToLine(7, color_pro); + break; + case RACE_SKELETON: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(9, color_pro); + details_text->addColorToLine(15, color_pro); + break; + case RACE_VAMPIRE: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(11, color_pro); + details_text->addColorToLine(16, color_pro); + break; + case RACE_SUCCUBUS: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(15, color_pro); + break; + case RACE_GOATMAN: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(16, color_pro); + break; + case RACE_AUTOMATON: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(12, color_pro); + details_text->addColorToLine(16, color_pro); + break; + case RACE_INCUBUS: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(15, color_pro); + break; + case RACE_GOBLIN: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(9, color_pro); + details_text->addColorToLine(14, color_pro); + break; + case RACE_INSECTOID: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(14, color_pro); + break; + } + } + auto details_text_right = card.findField("details_right"); + if (details_text_right) { + details_text_right->clearLinesToColor(); + details_text_right->setText(race_descs_right[race]); + + // we expect first word in the right column to always be "Weaknesses" + int c; + for (c = 0; race_descs_right[race][c] == '\n'; ++c); + details_text_right->addColorToLine(c, color_con); + } + }; + if (details) { + const auto font = smallfont_no_outline; + + auto details_title = card->addField("details_title", 1024); + details_title->setFont(font); + details_title->setSize(SDL_Rect{40, 68, 242, 298}); + details_title->setHJustify(Field::justify_t::CENTER); + auto details_text = card->addField("details", 1024); - details_text->setFont(smallfont_no_outline); + details_text->setFont(font); details_text->setSize(SDL_Rect{40, 68, 242, 298}); - details_text->setText(race_descs[stats[index]->playerRace]); + + auto details_text_right = card->addField("details_right", 1024); + details_text_right->setFont(font); + details_text_right->setSize(SDL_Rect{161, 68, 121, 298}); + + update_details_text(*card); } static auto race_fn = [](Button& button, int index){ - auto frame = static_cast(button.getParent()); + auto frame = static_cast(button.getParent()); assert(frame); for (int c = 0; c < num_races; ++c) { auto race = races[c]; if (strcmp(button.getName(), race) == 0) { @@ -8340,12 +8735,9 @@ namespace MainMenu { initClass(index); sendPlayerOverNet(); - auto card = static_cast(frame->getParent()); - auto details_text = card->findField("details"); - if (details_text) { - details_text->setText(race_descs[stats[index]->playerRace]); - } - }; + auto card = static_cast(frame->getParent()); assert(card); + update_details_text(*card); + }; auto subframe = card->addFrame("subframe"); subframe->setSize(details ? @@ -8384,10 +8776,17 @@ namespace MainMenu { } if (c < num_races - 1) { race->setWidgetDown(races[c + 1]); + } else { + race->setWidgetDown("disable_abilities"); } if (c > 0) { race->setWidgetUp(races[c - 1]); } + race->setGlyphPosition(Widget::glyph_position_t::CENTERED); + race->addWidgetAction("MenuPageLeft", "male"); + race->addWidgetAction("MenuPageRight", "female"); + race->addWidgetAction("MenuAlt1", "disable_abilities"); + race->addWidgetAction("MenuAlt2", "show_race_info"); switch (index) { case 0: race->setCallback([](Button& button){soundToggle(); race_fn(button, 0);}); break; case 1: race->setCallback([](Button& button){soundToggle(); race_fn(button, 1);}); break; @@ -8400,7 +8799,13 @@ namespace MainMenu { } auto label = subframe->addField((std::string(races[c]) + "_label").c_str(), 64); - label->setColor(makeColor(166, 123, 81, 255)); + if (c >= 1 && c <= 4) { + label->setColor(color_dlc1); + } else if (c >= 5 && c <= 8) { + label->setColor(color_dlc2); + } else { + label->setColor(color_human); + } label->setText(races[c]); label->setFont(smallfont_outline); label->setSize(SDL_Rect{32, c * 36, 96, 36}); @@ -8422,6 +8827,10 @@ namespace MainMenu { appearances->addWidgetMovement("MenuListConfirm", "appearances"); appearances->addWidgetAction("MenuStart", "confirm"); appearances->setWidgetBack("back_button"); + appearances->addWidgetAction("MenuPageLeft", "male"); + appearances->addWidgetAction("MenuPageRight", "female"); + appearances->addWidgetAction("MenuAlt1", "disable_abilities"); + appearances->addWidgetAction("MenuAlt2", "show_race_info"); appearances->setWidgetLeft(races[0]); appearances->setWidgetDown(races[1]); appearances->setTickCallback([](Widget& widget){ @@ -8481,6 +8890,12 @@ namespace MainMenu { appearances->activateSelection(); button.select(); }); + appearance_uparrow->addWidgetAction("MenuStart", "confirm"); + appearance_uparrow->setWidgetBack("back_button"); + appearance_uparrow->addWidgetAction("MenuPageLeft", "male"); + appearance_uparrow->addWidgetAction("MenuPageRight", "female"); + appearance_uparrow->addWidgetAction("MenuAlt1", "disable_abilities"); + appearance_uparrow->addWidgetAction("MenuAlt2", "show_race_info"); auto appearance_downarrow = subframe->addButton("appearance_downarrow"); appearance_downarrow->setSize(SDL_Rect{214, 2, 20, 32}); @@ -8500,6 +8915,12 @@ namespace MainMenu { appearances->activateSelection(); button.select(); }); + appearance_downarrow->addWidgetAction("MenuStart", "confirm"); + appearance_downarrow->setWidgetBack("back_button"); + appearance_downarrow->addWidgetAction("MenuPageLeft", "male"); + appearance_downarrow->addWidgetAction("MenuPageRight", "female"); + appearance_downarrow->addWidgetAction("MenuAlt1", "disable_abilities"); + appearance_downarrow->addWidgetAction("MenuAlt2", "show_race_info"); static const char* appearance_names[] = { "Landguard", "Northborn", "Firebrand", "Hardbred", @@ -8521,7 +8942,7 @@ namespace MainMenu { for (int c = 0; c < num_appearances; ++c) { auto name = appearance_names[c]; auto entry = appearances->addEntry(std::to_string(c).c_str(), true); - entry->color = makeColor(166, 123, 81, 255); + entry->color = color_human; entry->text = name; switch (index) { case 0: entry->click = [](Frame::entry_t& entry){soundActivate(); appearance_fn(entry, 0); entry.parent.activate();}; break; @@ -8579,6 +9000,10 @@ namespace MainMenu { case 2: disable_abilities->setCallback([](Button& button){disable_abilities_fn(button, 2);}); break; case 3: disable_abilities->setCallback([](Button& button){disable_abilities_fn(button, 3);}); break; } + disable_abilities->addWidgetAction("MenuPageLeft", "male"); + disable_abilities->addWidgetAction("MenuPageRight", "female"); + disable_abilities->addWidgetAction("MenuAlt1", "disable_abilities"); + disable_abilities->addWidgetAction("MenuAlt2", "show_race_info"); auto male_button = bottom->addButton("male"); if (stats[index]->sex == MALE) { @@ -8602,6 +9027,10 @@ namespace MainMenu { case 2: male_button->setCallback([](Button& button){soundActivate(); male_button_fn(button, 2);}); break; case 3: male_button->setCallback([](Button& button){soundActivate(); male_button_fn(button, 3);}); break; } + male_button->addWidgetAction("MenuPageLeft", "male"); + male_button->addWidgetAction("MenuPageRight", "female"); + male_button->addWidgetAction("MenuAlt1", "disable_abilities"); + male_button->addWidgetAction("MenuAlt2", "show_race_info"); auto female_button = bottom->addButton("female"); if (stats[index]->sex == FEMALE) { @@ -8626,6 +9055,10 @@ namespace MainMenu { case 2: female_button->setCallback([](Button& button){soundActivate(); female_button_fn(button, 2);}); break; case 3: female_button->setCallback([](Button& button){soundActivate(); female_button_fn(button, 3);}); break; } + female_button->addWidgetAction("MenuPageLeft", "male"); + female_button->addWidgetAction("MenuPageRight", "female"); + female_button->addWidgetAction("MenuAlt1", "disable_abilities"); + female_button->addWidgetAction("MenuAlt2", "show_race_info"); auto show_race_info = bottom->addButton("show_race_info"); show_race_info->setFont(smallfont_outline); @@ -8659,6 +9092,10 @@ namespace MainMenu { case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, true);}); break; } } + show_race_info->addWidgetAction("MenuPageLeft", "male"); + show_race_info->addWidgetAction("MenuPageRight", "female"); + show_race_info->addWidgetAction("MenuAlt1", "disable_abilities"); + show_race_info->addWidgetAction("MenuAlt2", "show_race_info"); /*auto confirm = card->addButton("confirm"); confirm->setFont(bigfont_outline); @@ -8887,7 +9324,9 @@ namespace MainMenu { static void createCharacterCard(int index) { auto lobby = main_menu_frame->findFrame("lobby"); - assert(lobby); + if (!lobby) { + return; + } if (multiplayer == CLIENT) { sendSvFlagsOverNet(); @@ -10241,16 +10680,16 @@ namespace MainMenu { "LAN Lobby (Joined)" : "Online Lobby (Joined)"; break; } auto label = banner->addField("label", 128); - label->setHJustify(Field::justify_t::LEFT); + label->setHJustify(Field::justify_t::CENTER); label->setVJustify(Field::justify_t::CENTER); - label->setSize(SDL_Rect{96, 8, 256, 48}); + label->setSize(SDL_Rect{(Frame::virtualScreenX - 256) / 2, 8, 256, 48}); label->setFont(bigfont_outline); label->setText(type_str); // lobby name if (type != LobbyType::LobbyLocal) { auto text_box = banner->addImage( - SDL_Rect{(Frame::virtualScreenX - 246) / 2, 16, 246, 36}, + SDL_Rect{96, 16, 246, 36}, 0xffffffff, "*images/ui/Main Menus/TextField_00.png", "text_box" @@ -10265,7 +10704,7 @@ namespace MainMenu { field->setEditable(type != LobbyType::LobbyJoined); field->setScroll(true); field->setGuide("Set a public name for this lobby."); - field->setSize(SDL_Rect{(Frame::virtualScreenX - 242) / 2, 20, 242, 28}); + field->setSize(SDL_Rect{98, 20, 242, 28}); field->setFont(smallfont_outline); field->setHJustify(Field::justify_t::LEFT); field->setVJustify(Field::justify_t::CENTER); @@ -13700,6 +14139,12 @@ namespace MainMenu { {"Off to leave a salty review I see?", "... yeah", "No way!"}, {"I'd leave too.\nThis game looks just like Minecraft.", "lol", "Ouch"}, {"Okay, I see how it is.\nSee if I'm still here tomorrow.", "Whatever", "I love you!"}, + {"Don't quit now, you were\ngoing to win the next run.", "Don't lie", "Really?"}, + {"A wimpy adventurer\nwould quit right now.", "Wimp time", "Am not!"}, + {"An adventurer is trapped\nand they need your help!", "Who cares", "I'll do it!"}, + {"A quitter says what?", "What?", "Not today"}, + {"You won't quit.\nYou're just bluffing.", "Bet", "Ya got me."}, + {"Say one more thing and\nI'm kicking you out of this game.", "Thing", "..."}, }; constexpr int num_quit_messages = sizeof(quit_messages) / (sizeof(const char*) * 3); @@ -13954,11 +14399,21 @@ namespace MainMenu { [](Button&){ // yes soundActivate(); closeBinary(); + + assert(main_menu_frame); + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); + settingsSelect(*settings_subwindow, {Setting::Type::Dropdown, "device"}); }, [](Button&){ // no soundCancel(); closeBinary(); resetResolution(); + + assert(main_menu_frame); + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); + settingsSelect(*settings_subwindow, {Setting::Type::Dropdown, "device"}); }, false, false); // yellow buttons // prompt timeout From c2aec17da8120544c9fd41af55af171121400b37 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 12 Jul 2022 18:09:19 -0700 Subject: [PATCH 06/21] various lobby improvements --- src/ui/MainMenu.cpp | 1213 +++++++++++++++++++++++-------------------- 1 file changed, 640 insertions(+), 573 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 748c4720e..9d2372b3f 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -428,7 +428,7 @@ namespace MainMenu { static void characterCardGameSettingsMenu(int index); static void characterCardLobbySettingsMenu(int index); static void characterCardRaceMenu(int index, bool details); - static void characterCardClassMenu(int index, bool details); + static void characterCardClassMenu(int index); static void createControllerPrompt(int index, bool show_player_text, void (*after_func)()); static void createCharacterCard(int index); @@ -7529,8 +7529,341 @@ namespace MainMenu { "shaman", "hunter" }; + static const char* races[] = { + "Human", + "Skeleton", + "Vampire", + "Succubus", + "Goatman", + "Automaton", + "Incubus", + "Goblin", + "Insectoid", + }; + static constexpr int num_races = sizeof(races) / sizeof(races[0]); + + static const char* race_descs[] = { + // Human + "\n" + "Traits\n" + "None\n" + "\n" + "Resistances\n" + "None\n" + "\n" + "Friendly With\n" + "Humans, Automatons", + + // Skeleton + "\n" + "Traits\n" + "\x1E Does not Hunger or Starve\n" + "\x1E HP and MP Regeneration\n" + " reduced by 75%\n" + "\x1E Self-Resurrects for 75MP\n" + "\x1E Immune to Burning\n" + "\x1E Swim speed reduced by 50%\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\x1E Ranged\n" + "\x1E Axes\n" + "\x1E Magic\n" + "\n" + "Friendly With\n" + "\x1E Ghouls, Automatons", + + // Vampire + "\n" + "Racial Spells\n" + "\x1E Bloodletting, Levitation\n" + "Traits\n" + "\x1E Uses HP when out of MP to\n" + " cast and sustain spells\n" + "\x1E Can only sustain hunger\n" + " with Blood Vials\n" + "\x1E Kills may drop Blood Vials\n" + "\x1E Bloodletting / Assassinate\n" + " kills drop Blood Vials\n" + "Resistances\n" + "\x1E Swords\n" + "\x1E Ranged\n" + "\x1E Axes\n" + "\x1E Magic\n" + "Friendly With\n" + "\x1E Vampires, Automatons", + + // Succubus + "\n" + "Racial Spells\n" + "\x1E Teleport, Polymorph\n" + "Traits\n" + "\x1E Cursed equipment can be\n" + " removed; gives bonuses\n" + "\x1E Blessed equipment is not\n" + " removable; gives bonuses\n" + "\x1E +MP from Strangulation\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Succubi, Incubi,\n" + " Automatons", + + // Goatman + "\n" + "Traits\n" + "\x1E Afflicted with Alcoholism,\n" + " causing Hangovers\n" + "\x1E Immune to Drunk dizziness\n" + "\x1E +STR +CHR while Drunk\n" + "\x1E Can recruit fellow Drunks\n" + "\x1E Eats Tins without an Opener\n" + "\x1E Immune to Greasy effect\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\n" + "\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Goatmen, Automatons", + + // Automaton + "\n" + "Racial Spells\n" + "\x1E Salvage\n" + "Traits\n" + "\x1E Requires Heat (HT) to\n" + " survive and cast spells\n" + "\x1E Regen HT with a hot boiler\n" + + " fueled by gems and paper\n" + "\x1E Can remove cursed items\n" + "\x1E Immune to Burning\n" + "\x1E +20 to Tinkering Repairs\n" + "\x1E Welcomed by Shopkeepers\n" + "Resistances\n" + "\x1E Ranged\n" + "\x1E Unarmed\n" + "\n" + "Friendly With\n" + "\x1E Automatons, Humans", + + // Incubus + "\n" + "Racial Spells\n" + "\x1E Teleport, Arcane Mark\n" + "Traits\n" + "\x1E Cursed equipment can be\n" + " removed; gives bonuses\n" + "\x1E Blessed equipment is not\n" + " removable; gives bonuses\n" + "\x1E +MP from Strangulation\n" + "\n" + "Resistances\n" + "\x1E Magic\n" + "\x1E Polearms\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Succubi, Incubi,\n" + " Automatons", + + // Goblin + "\n" + "Traits\n" + "\x1E Worn Equipment has lower\n" + " chance to degrade\n" + "\x1E Raising any Melee Weapon\n" + " Skill raises all of them\n" + "\x1E Weapon Skills raise slower\n" + "\x1E Cannot Memorize new spells\n" + "\n" + "Resistances\n" + "\x1E Swords\n" + "\x1E Unarmed\n" + "\n" + "\n" + "Friendly With\n" + "\x1E Goblins, Automatons", + + // Insectoid + "\n" + "Racial Spells\n" + "\x1E Flutter, Dash, Spray Acid\n" + "Traits\n" + "\x1E Requires Energy (EN) to\n" + " survive and cast spells\n" + "\x1E Regain EN by consuming\n" + " food and sweet liquids\n" + "\x1E Immune to Poison\n" + "\x1E Immune to rotten food\n" + "Resistances\n" + "\x1E Maces\n" + "\x1E Unarmed\n" + "\n" + "Friendly With\n" + "\x1E Insectoids, Scarabs\n" + " Scorpions, Automatons\n", + }; + + static const char* race_descs_right[] = { + // Human + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "None", + + // Skeleton + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Maces\n" + "\x1E Polearms\n" + "\x1E Smite", + + // Vampire + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Maces\n" + "\x1E Polearms\n" + "\x1E Water\n" + "\x1E Smite", + + // Succubus + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Magic\n" + "\x1E Polearms\n" + "\x1E Smite", + + // Goatman + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Polearms\n" + "\x1E Axes\n" + "\x1E Ranged\n" + "\x1E Magic", + + // Automaton + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Axes\n" + "\x1E Maces\n" + "\x1E Magic", + + // Incubus + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Ranged\n" + "\x1E Swords\n" + "\x1E Smite", + + // Goblin + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Axes\n" + "\x1E Polearms\n" + "\x1E Ranged", + + // Insectoid + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "Weaknesses\n" + "\x1E Axes\n" + "\x1E Polearms\n" + "\x1E Ranged", + }; + constexpr int num_classes = sizeof(classes_in_order) / sizeof(classes_in_order[0]); + constexpr Uint32 color_human = makeColorRGB(169, 185, 212); + constexpr Uint32 color_dlc1 = makeColorRGB(241, 129, 78); + constexpr Uint32 color_dlc2 = makeColorRGB(255, 53, 206); + constexpr Uint32 color_traits = makeColorRGB(184, 146, 109); + constexpr Uint32 color_pro = makeColorRGB(88, 203, 255); + constexpr Uint32 color_con = makeColorRGB(255, 56, 56); + constexpr Uint32 color_energy = makeColorRGB(21, 255, 0); + constexpr Uint32 color_heat = makeColorRGB(241, 129, 78); + std::vector reducedClassList(int index) { std::vector result; result.reserve(num_classes); @@ -7543,69 +7876,229 @@ namespace MainMenu { return result; } - static auto male_button_fn = [](Button& button, int index) { + static void update_details_text(Frame& card) { + const int index = card.getOwner(); + const int race = stats[index]->playerRace; + + // color title + Uint32 color_race; + if (race >= RACE_SKELETON && race <= RACE_GOATMAN) { + color_race = color_dlc1; + } + else if (race >= RACE_AUTOMATON && race <= RACE_INSECTOID) { + color_race = color_dlc2; + } + else { + color_race = color_human; + } + + auto details_title = card.findField("details_title"); + if (details_title) { + details_title->clearLinesToColor(); + details_title->setText(races[race]); + details_title->setColor(color_race); + } + auto details_text = card.findField("details"); + if (details_text) { + details_text->clearLinesToColor(); + details_text->setText(race_descs[race]); + + switch (race) { + default: return; + case RACE_HUMAN: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(4, color_pro); + details_text->addColorToLine(7, color_pro); + break; + case RACE_SKELETON: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(9, color_pro); + details_text->addColorToLine(15, color_pro); + break; + case RACE_VAMPIRE: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(11, color_pro); + details_text->addColorToLine(16, color_pro); + break; + case RACE_SUCCUBUS: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(15, color_pro); + break; + case RACE_GOATMAN: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(16, color_pro); + break; + case RACE_AUTOMATON: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(12, color_pro); + details_text->addColorToLine(16, color_pro); + break; + case RACE_INCUBUS: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(15, color_pro); + break; + case RACE_GOBLIN: + details_text->addColorToLine(1, color_traits); + details_text->addColorToLine(9, color_pro); + details_text->addColorToLine(14, color_pro); + break; + case RACE_INSECTOID: + details_text->addColorToLine(1, color_pro); + details_text->addColorToLine(3, color_traits); + details_text->addColorToLine(10, color_pro); + details_text->addColorToLine(14, color_pro); + break; + } + } + auto details_text_right = card.findField("details_right"); + if (details_text_right) { + details_text_right->clearLinesToColor(); + details_text_right->setText(race_descs_right[race]); + + // we expect first word in the right column to always be "Weaknesses" + int c; + for (c = 0; race_descs_right[race][c] == '\n'; ++c); + details_text_right->addColorToLine(c, color_con); + } + } + + static void race_button_fn(Button& button, int index) { + auto frame = static_cast(button.getParent()); assert(frame); + for (int c = 0; c < num_races; ++c) { + auto race = races[c]; + if (strcmp(button.getName(), race) == 0) { + stats[index]->playerRace = c; + if (stats[index]->playerRace == RACE_SUCCUBUS) { + stats[index]->appearance = 0; + stats[index]->sex = FEMALE; + auto card = static_cast(frame->getParent()); assert(card); + auto bottom = card->findFrame("bottom"); assert(bottom); + auto female = bottom->findButton("female"); + auto male = bottom->findButton("male"); + female->setColor(makeColor(255, 255, 255, 255)); + female->setHighlightColor(makeColor(255, 255, 255, 255)); + male->setColor(makeColor(127, 127, 127, 255)); + male->setHighlightColor(makeColor(127, 127, 127, 255)); + } + else if (stats[index]->playerRace == RACE_INCUBUS) { + stats[index]->appearance = 0; + stats[index]->sex = MALE; + auto card = static_cast(frame->getParent()); assert(card); + auto bottom = card->findFrame("bottom"); assert(bottom); + auto female = bottom->findButton("female"); + auto male = bottom->findButton("male"); + female->setColor(makeColor(127, 127, 127, 255)); + female->setHighlightColor(makeColor(127, 127, 127, 255)); + male->setColor(makeColor(255, 255, 255, 255)); + male->setHighlightColor(makeColor(255, 255, 255, 255)); + } + else if (stats[index]->playerRace == RACE_HUMAN) { + stats[index]->appearance = RNG.uniform(0, NUMAPPEARANCES - 1); + auto appearances = frame->findFrame("appearances"); + if (appearances) { + appearances->setSelection(stats[index]->appearance); + appearances->scrollToSelection(); + } + } + else { + stats[index]->appearance = 0; + } + } else { + auto other_button = frame->findButton(race); + if (other_button) { + other_button->setPressed(false); + } + } + } + auto disable_abilities = frame->findButton("disable_abilities"); + if (disable_abilities) { + disable_abilities->setPressed(false); + } + stats[index]->clearStats(); + initClass(index); + sendPlayerOverNet(); + + auto card = static_cast(frame->getParent()); + if (card) { + update_details_text(*card); + } + } + + static void male_button_fn(Button& button, int index) { button.setColor(makeColor(255, 255, 255, 255)); button.setHighlightColor(makeColor(255, 255, 255, 255)); - auto card = static_cast(button.getParent()); - auto female = card->findButton("female"); + auto bottom = static_cast(button.getParent()); assert(bottom); + auto card = static_cast(bottom->getParent()); assert(card); + auto female = bottom->findButton("female"); if (female) { female->setColor(makeColor(127, 127, 127, 255)); female->setHighlightColor(makeColor(127, 127, 127, 255)); } stats[index]->sex = MALE; if (stats[index]->playerRace == RACE_SUCCUBUS) { - auto succubus = card->findButton("Succubus"); - if (succubus) { - succubus->setPressed(false); - } - if (enabledDLCPack2) { - stats[index]->playerRace = RACE_INCUBUS; - auto race = card->findButton("race"); - if (race) { - race->setText("Incubus"); - } - auto incubus = card->findButton("Incubus"); - if (incubus) { - incubus->setPressed(true); - } - if (client_classes[index] == CLASS_MESMER && stats[index]->appearance == 0) { - if (isCharacterValidFromDLC(*stats[index], client_classes[index]) != VALID_OK_CHARACTER) { - client_classes[index] = CLASS_PUNISHER; - auto class_button = card->findButton("class"); - if (class_button) { - class_button->setIcon("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Icon_Punisher_00.png"); - } - } - } - } else { - stats[index]->playerRace = RACE_HUMAN; - auto race = card->findButton("race"); - if (race) { - race->setText("Human"); - } - auto human = card->findButton("Human"); - if (human) { - human->setPressed(true); - } - } + auto subframe = card->findFrame("subframe"); + auto succubus = subframe ? subframe->findButton("Succubus") : nullptr; + if (succubus) { + succubus->setPressed(false); + } + if (enabledDLCPack2) { + stats[index]->playerRace = RACE_INCUBUS; + auto race = card->findButton("race"); + if (race) { + race->setText("Incubus"); + } + auto incubus = subframe ? subframe->findButton("Incubus") : nullptr; + if (incubus) { + incubus->setPressed(true); + } + if (client_classes[index] == CLASS_MESMER && stats[index]->appearance == 0) { + if (isCharacterValidFromDLC(*stats[index], client_classes[index]) != VALID_OK_CHARACTER) { + client_classes[index] = CLASS_PUNISHER; + auto class_button = card->findButton("class"); + if (class_button) { + class_button->setIcon("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Icon_Punisher_00.png"); + } + } + } + } else { + stats[index]->playerRace = RACE_HUMAN; + auto race = card->findButton("race"); + if (race) { + race->setText("Human"); + } + auto human = subframe ? subframe->findButton("Human") : nullptr; + if (human) { + human->setPressed(true); + } + } } stats[index]->clearStats(); initClass(index); sendPlayerOverNet(); - }; + update_details_text(*card); + } - static auto female_button_fn = [](Button& button, int index) { + static void female_button_fn(Button& button, int index) { button.setColor(makeColor(255, 255, 255, 255)); button.setHighlightColor(makeColor(255, 255, 255, 255)); - auto card = static_cast(button.getParent()); - auto male = card->findButton("male"); + auto bottom = static_cast(button.getParent()); assert(bottom); + auto card = static_cast(bottom->getParent()); assert(card); + auto male = bottom->findButton("male"); if (male) { male->setColor(makeColor(127, 127, 127, 255)); male->setHighlightColor(makeColor(127, 127, 127, 255)); } stats[index]->sex = FEMALE; if (stats[index]->playerRace == RACE_INCUBUS) { - auto incubus = card->findButton("Incubus"); + auto subframe = card->findFrame("subframe"); + auto incubus = subframe ? subframe->findButton("Incubus") : nullptr; if (incubus) { incubus->setPressed(false); } @@ -7615,7 +8108,7 @@ namespace MainMenu { if (race) { race->setText("Succubus"); } - auto succubus = card->findButton("Succubus"); + auto succubus = subframe ? subframe->findButton("Succubus") : nullptr; if (succubus) { succubus->setPressed(true); } @@ -7634,7 +8127,7 @@ namespace MainMenu { if (race) { race->setText("Human"); } - auto human = card->findButton("Human"); + auto human = subframe ? subframe->findButton("Human") : nullptr; if (human) { human->setPressed(true); } @@ -7643,7 +8136,8 @@ namespace MainMenu { stats[index]->clearStats(); initClass(index); sendPlayerOverNet(); - }; + update_details_text(*card); + } static Frame* initCharacterCard(int index, int height) { auto lobby = main_menu_frame->findFrame("lobby"); @@ -8252,432 +8746,6 @@ namespace MainMenu { header->setText("RACE SELECTION"); header->setJustify(Field::justify_t::CENTER); - static const char* races[] = { - "Human", - "Skeleton", - "Vampire", - "Succubus", - "Goatman", - "Automaton", - "Incubus", - "Goblin", - "Insectoid", - }; - static constexpr int num_races = sizeof(races) / sizeof(races[0]); - - static const char* race_descs[] = { - // Human - "\n" - "Traits\n" - "None\n" - "\n" - "Resistances\n" - "None\n" - "\n" - "Friendly With\n" - "Humans, Automatons", - - // Skeleton - "\n" - "Traits\n" - "\x1E Does not Hunger or Starve\n" - "\x1E HP and MP Regeneration\n" - " reduced by 75%\n" - "\x1E Self-Resurrects for 75MP\n" - "\x1E Immune to Burning\n" - "\x1E Swim speed reduced by 50%\n" - "\n" - "Resistances\n" - "\x1E Swords\n" - "\x1E Ranged\n" - "\x1E Axes\n" - "\x1E Magic\n" - "\n" - "Friendly With\n" - "\x1E Ghouls, Automatons", - - // Vampire - "\n" - "Racial Spells\n" - "\x1E Bloodletting, Levitation\n" - "Traits\n" - "\x1E Uses HP when out of MP to\n" - " cast and sustain spells\n" - "\x1E Can only sustain hunger\n" - " with Blood Vials\n" - "\x1E Kills may drop Blood Vials\n" - "\x1E Bloodletting / Assassinate\n" - " kills drop Blood Vials\n" - "Resistances\n" - "\x1E Swords\n" - "\x1E Ranged\n" - "\x1E Axes\n" - "\x1E Magic\n" - "Friendly With\n" - "\x1E Vampires, Automatons", - - // Succubus - "\n" - "Racial Spells\n" - "\x1E Teleport, Polymorph\n" - "Traits\n" - "\x1E Cursed equipment can be\n" - " removed; gives bonuses\n" - "\x1E Blessed equipment is not\n" - " removable; gives bonuses\n" - "\x1E +MP from Strangulation\n" - "\n" - "Resistances\n" - "\x1E Swords\n" - "\n" - "\n" - "\n" - "Friendly With\n" - "\x1E Succubi, Incubi,\n" - " Automatons", - - // Goatman - "\n" - "Traits\n" - "\x1E Afflicted with Alcoholism,\n" - " causing Hangovers\n" - "\x1E Immune to Drunk dizziness\n" - "\x1E +STR +CHR while Drunk\n" - "\x1E Can recruit fellow Drunks\n" - "\x1E Eats Tins without an Opener\n" - "\x1E Immune to Greasy effect\n" - "\n" - "Resistances\n" - "\x1E Swords\n" - "\n" - "\n" - "\n" - "\n" - "Friendly With\n" - "\x1E Goatmen, Automatons", - - // Automaton - "\n" - "Racial Spells\n" - "\x1E Salvage\n" - "Traits\n" - "\x1E Requires Heat (HT) to\n" - " survive and cast spells\n" - "\x1E Regen HT with a hot boiler\n" - - " fueled by gems and paper\n" - "\x1E Can remove cursed items\n" - "\x1E Immune to Burning\n" - "\x1E +20 to Tinkering Repairs\n" - "\x1E Welcomed by Shopkeepers\n" - "Resistances\n" - "\x1E Ranged\n" - "\x1E Unarmed\n" - "\n" - "Friendly With\n" - "\x1E Automatons, Humans", - - // Incubus - "\n" - "Racial Spells\n" - "\x1E Teleport, Arcane Mark\n" - "Traits\n" - "\x1E Cursed equipment can be\n" - " removed; gives bonuses\n" - "\x1E Blessed equipment is not\n" - " removable; gives bonuses\n" - "\x1E +MP from Strangulation\n" - "\n" - "Resistances\n" - "\x1E Magic\n" - "\x1E Polearms\n" - "\n" - "\n" - "Friendly With\n" - "\x1E Succubi, Incubi,\n" - " Automatons", - - // Goblin - "\n" - "Traits\n" - "\x1E Worn Equipment has lower\n" - " chance to degrade\n" - "\x1E Raising any Melee Weapon\n" - " Skill raises all of them\n" - "\x1E Weapon Skills raise slower\n" - "\x1E Cannot Memorize new spells\n" - "\n" - "Resistances\n" - "\x1E Swords\n" - "\x1E Unarmed\n" - "\n" - "\n" - "Friendly With\n" - "\x1E Goblins, Automatons", - - // Insectoid - "\n" - "Racial Spells\n" - "\x1E Flutter, Dash, Spray Acid\n" - "Traits\n" - "\x1E Requires Energy (EN) to\n" - " survive and cast spells\n" - "\x1E Regain EN by consuming\n" - " food and sweet liquids\n" - "\x1E Immune to Poison\n" - "\x1E Immune to rotten food\n" - "Resistances\n" - "\x1E Maces\n" - "\x1E Unarmed\n" - "\n" - "Friendly With\n" - "\x1E Insectoids, Scarabs\n" - " Scorpions, Automatons\n", - }; - - static const char* race_descs_right[] = { - // Human - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "None", - - // Skeleton - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Maces\n" - "\x1E Polearms\n" - "\x1E Smite", - - // Vampire - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Maces\n" - "\x1E Polearms\n" - "\x1E Water\n" - "\x1E Smite", - - // Succubus - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Magic\n" - "\x1E Polearms\n" - "\x1E Smite", - - // Goatman - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Polearms\n" - "\x1E Axes\n" - "\x1E Ranged\n" - "\x1E Magic", - - // Automaton - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Axes\n" - "\x1E Maces\n" - "\x1E Magic", - - // Incubus - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Ranged\n" - "\x1E Swords\n" - "\x1E Smite", - - // Goblin - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Axes\n" - "\x1E Polearms\n" - "\x1E Ranged", - - // Insectoid - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "Weaknesses\n" - "\x1E Axes\n" - "\x1E Polearms\n" - "\x1E Ranged", - }; - - constexpr Uint32 color_human = makeColorRGB(169, 185, 212); - constexpr Uint32 color_dlc1 = makeColorRGB(241, 129, 78); - constexpr Uint32 color_dlc2 = makeColorRGB(255, 53, 206); - constexpr Uint32 color_traits = makeColorRGB(184, 146, 109); - constexpr Uint32 color_pro = makeColorRGB(88, 203, 255); - constexpr Uint32 color_con = makeColorRGB(255, 56, 56); - constexpr Uint32 color_energy = makeColorRGB(21, 255, 0); - constexpr Uint32 color_heat = makeColorRGB(241, 129, 78); - - static auto update_details_text = [](Frame& card){ - const int index = card.getOwner(); - const int race = stats[index]->playerRace; - - // color title - Uint32 color_race; - if (race >= RACE_SKELETON && race <= RACE_GOATMAN) { - color_race = color_dlc1; - } - else if (race >= RACE_AUTOMATON && race <= RACE_INSECTOID) { - color_race = color_dlc2; - } - else { - color_race = color_human; - } - - auto details_title = card.findField("details_title"); - if (details_title) { - details_title->clearLinesToColor(); - details_title->setText(races[race]); - details_title->setColor(color_race); - } - auto details_text = card.findField("details"); - if (details_text) { - details_text->clearLinesToColor(); - details_text->setText(race_descs[race]); - - switch (race) { - default: return; - case RACE_HUMAN: - details_text->addColorToLine(1, color_traits); - details_text->addColorToLine(4, color_pro); - details_text->addColorToLine(7, color_pro); - break; - case RACE_SKELETON: - details_text->addColorToLine(1, color_traits); - details_text->addColorToLine(9, color_pro); - details_text->addColorToLine(15, color_pro); - break; - case RACE_VAMPIRE: - details_text->addColorToLine(1, color_pro); - details_text->addColorToLine(3, color_traits); - details_text->addColorToLine(11, color_pro); - details_text->addColorToLine(16, color_pro); - break; - case RACE_SUCCUBUS: - details_text->addColorToLine(1, color_pro); - details_text->addColorToLine(3, color_traits); - details_text->addColorToLine(10, color_pro); - details_text->addColorToLine(15, color_pro); - break; - case RACE_GOATMAN: - details_text->addColorToLine(1, color_traits); - details_text->addColorToLine(10, color_pro); - details_text->addColorToLine(16, color_pro); - break; - case RACE_AUTOMATON: - details_text->addColorToLine(1, color_pro); - details_text->addColorToLine(3, color_traits); - details_text->addColorToLine(12, color_pro); - details_text->addColorToLine(16, color_pro); - break; - case RACE_INCUBUS: - details_text->addColorToLine(1, color_pro); - details_text->addColorToLine(3, color_traits); - details_text->addColorToLine(10, color_pro); - details_text->addColorToLine(15, color_pro); - break; - case RACE_GOBLIN: - details_text->addColorToLine(1, color_traits); - details_text->addColorToLine(9, color_pro); - details_text->addColorToLine(14, color_pro); - break; - case RACE_INSECTOID: - details_text->addColorToLine(1, color_pro); - details_text->addColorToLine(3, color_traits); - details_text->addColorToLine(10, color_pro); - details_text->addColorToLine(14, color_pro); - break; - } - } - auto details_text_right = card.findField("details_right"); - if (details_text_right) { - details_text_right->clearLinesToColor(); - details_text_right->setText(race_descs_right[race]); - - // we expect first word in the right column to always be "Weaknesses" - int c; - for (c = 0; race_descs_right[race][c] == '\n'; ++c); - details_text_right->addColorToLine(c, color_con); - } - }; - if (details) { const auto font = smallfont_no_outline; @@ -8697,48 +8765,6 @@ namespace MainMenu { update_details_text(*card); } - static auto race_fn = [](Button& button, int index){ - auto frame = static_cast(button.getParent()); assert(frame); - for (int c = 0; c < num_races; ++c) { - auto race = races[c]; - if (strcmp(button.getName(), race) == 0) { - stats[index]->playerRace = c; - if (stats[index]->playerRace == RACE_SUCCUBUS) { - stats[index]->appearance = 0; - stats[index]->sex = FEMALE; - } - else if (stats[index]->playerRace == RACE_INCUBUS) { - stats[index]->appearance = 0; - stats[index]->sex = MALE; - } - else if (stats[index]->playerRace == RACE_HUMAN) { - stats[index]->appearance = RNG.rand() % NUMAPPEARANCES; - auto appearances = frame->findFrame("appearances"); assert(appearances); - appearances->setSelection(stats[index]->appearance); - appearances->scrollToSelection(); - } - else { - stats[index]->appearance = 0; - } - } else { - auto other_button = frame->findButton(race); - if (other_button) { - other_button->setPressed(false); - } - } - } - auto disable_abilities = frame->findButton("disable_abilities"); - if (disable_abilities) { - disable_abilities->setPressed(false); - } - stats[index]->clearStats(); - initClass(index); - sendPlayerOverNet(); - - auto card = static_cast(frame->getParent()); assert(card); - update_details_text(*card); - }; - auto subframe = card->addFrame("subframe"); subframe->setSize(details ? SDL_Rect{38, 382, 234, 106}: @@ -8788,10 +8814,10 @@ namespace MainMenu { race->addWidgetAction("MenuAlt1", "disable_abilities"); race->addWidgetAction("MenuAlt2", "show_race_info"); switch (index) { - case 0: race->setCallback([](Button& button){soundToggle(); race_fn(button, 0);}); break; - case 1: race->setCallback([](Button& button){soundToggle(); race_fn(button, 1);}); break; - case 2: race->setCallback([](Button& button){soundToggle(); race_fn(button, 2);}); break; - case 3: race->setCallback([](Button& button){soundToggle(); race_fn(button, 3);}); break; + case 0: race->setCallback([](Button& button){soundToggle(); race_button_fn(button, 0);}); break; + case 1: race->setCallback([](Button& button){soundToggle(); race_button_fn(button, 1);}); break; + case 2: race->setCallback([](Button& button){soundToggle(); race_button_fn(button, 2);}); break; + case 3: race->setCallback([](Button& button){soundToggle(); race_button_fn(button, 3);}); break; } if (stats[index]->playerRace == c) { race->setPressed(true); @@ -9116,9 +9142,9 @@ namespace MainMenu { }*/ } - static void characterCardClassMenu(int index, bool details) { + static void characterCardClassMenu(int index) { auto reduced_class_list = reducedClassList(index); - auto card = initCharacterCard(index, 488); + auto card = initCharacterCard(index, 446); static void (*back_fn)(int) = [](int index){ createCharacterCard(index); @@ -9138,7 +9164,7 @@ namespace MainMenu { auto backdrop = card->addImage( card->getActualSize(), 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Window_01.png", + "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Window_04.png", "backdrop" ); @@ -9156,9 +9182,9 @@ namespace MainMenu { class_name_header->setVJustify(Field::justify_t::BOTTOM);*/ auto textbox = card->addImage( - SDL_Rect{46, 116, 186, 36}, + SDL_Rect{42, 68, 192, 46}, 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_SearchBar_00.png", + "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Box_ClassName_04.png", "textbox" ); @@ -9171,8 +9197,8 @@ namespace MainMenu { }; auto class_name = card->addField("class_name", 64); - class_name->setSize(SDL_Rect{48, 120, 182, 28}); - class_name->setHJustify(Field::justify_t::LEFT); + class_name->setSize(SDL_Rect{42, 68, 192, 46}); + class_name->setHJustify(Field::justify_t::CENTER); class_name->setVJustify(Field::justify_t::CENTER); class_name->setFont(smallfont_outline); switch (index) { @@ -9185,14 +9211,14 @@ namespace MainMenu { auto subframe = card->addFrame("subframe"); subframe->setScrollBarsEnabled(false); - subframe->setSize(SDL_Rect{34, 160, 226, 258}); + subframe->setSize(SDL_Rect{34, 124, 226, 254}); subframe->setActualSize(SDL_Rect{0, 0, 226, std::max(258, 6 + 54 * (int)(classes.size() / 4 + ((classes.size() % 4) ? 1 : 0)))}); subframe->setBorder(0); subframe->setColor(0); if (subframe->getActualSize().h > subframe->getSize().h) { auto slider = card->addSlider("scroll_slider"); - slider->setRailSize(SDL_Rect{260, 160, 30, 266}); + slider->setRailSize(SDL_Rect{260, 118, 30, 266}); slider->setHandleSize(SDL_Rect{0, 0, 34, 34}); slider->setRailImage("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_00.png"); slider->setHandleImage("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_SliderB_00.png"); @@ -9222,13 +9248,16 @@ namespace MainMenu { auto class_info = card->addButton("class_info"); class_info->setColor(makeColor(255, 255, 255, 255)); class_info->setHighlightColor(makeColor(255, 255, 255, 255)); - class_info->setSize(SDL_Rect{236, 110, 48, 48}); - class_info->setBackground("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_MagnifyingGlass_00.png"); + class_info->setSize(SDL_Rect{238, 68, 46, 46}); + class_info->setBackground("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Button_Info_00.png"); class_info->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); class_info->addWidgetAction("MenuStart", "confirm"); class_info->addWidgetAction("MenuAlt1", "class_info"); class_info->setWidgetBack("back_button"); + const int current_class = std::min(std::max(0, client_classes[index]), num_classes - 1); + auto current_class_name = classes_in_order[current_class + 1]; + const std::string prefix = "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/"; for (int c = reduced_class_list.size() - 1; c >= 0; --c) { auto name = reduced_class_list[c]; @@ -9243,7 +9272,11 @@ namespace MainMenu { } button->setIcon((prefix + full_class.image).c_str()); button->setSize(SDL_Rect{8 + (c % 4) * 54, 6 + (c / 4) * 54, 54, 54}); - button->setColor(makeColor(255, 255, 255, 255)); + if (strcmp(name, current_class_name)) { + button->setColor(makeColor(127, 127, 127, 255)); + } else { + button->setColor(makeColor(255, 255, 255, 255)); + } button->setHighlightColor(makeColor(255, 255, 255, 255)); button->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); if (c > 0) { @@ -9268,25 +9301,45 @@ namespace MainMenu { static auto button_fn = [](Button& button, int index){ soundActivate(); + + // figure out which class button we clicked based on name int c = 0; for (; c < num_classes; ++c) { if (strcmp(button.getName(), classes_in_order[c]) == 0) { break; } } - if (c >= 1) { - --c; // exclude the random "class" + + // dim all buttons + auto frame = static_cast(button.getParent()); + for (auto button : frame->getButtons()) { + button->setColor(makeColor(127, 127, 127, 255)); + } + + if (c > 0) { + // when selecting anything but random class... + --c; // discount the "random class" option client_classes[index] = c; + button.setColor(makeColor(255, 255, 255, 255)); // highlight this button } else { + // when selecting random class... auto reduced_class_list = reducedClassList(index); - auto random_class = reduced_class_list[(RNG.rand() % (reduced_class_list.size() - 1)) + 1]; - for (int c = 0; c < num_classes; ++c) { + auto random_class = reduced_class_list[RNG.uniform(1, reduced_class_list.size() - 1)]; + for (int c = 1; c < num_classes; ++c) { if (strcmp(random_class, classes_in_order[c]) == 0) { - client_classes[index] = c; + client_classes[index] = c - 1; + + // highlight selected class button + auto frame = static_cast(button.getParent()); + auto button = frame->findButton(random_class); + if (button) { + button->setColor(makeColor(255, 255, 255, 255)); + } break; } } } + stats[index]->clearStats(); initClass(index); sendPlayerOverNet(); @@ -9444,7 +9497,8 @@ namespace MainMenu { static auto randomize_name_fn = [](Button& button, int index) { auto& names = stats[index]->sex == sex_t::MALE ? randomPlayerNamesMale : randomPlayerNamesFemale; - auto name = names[RNG.rand() % names.size()].c_str(); + auto choice = RNG.uniform(0, names.size() - 1); + auto name = names[choice].c_str(); name_field_fn(name, index); auto card = static_cast(button.getParent()); auto field = card->findField("name"); assert(field); @@ -9476,7 +9530,12 @@ namespace MainMenu { case 3: game_settings->setCallback([](Button&){soundActivate(); characterCardLobbySettingsMenu(3);}); break; } - auto male_button = card->addButton("male"); + auto bottom = card->addFrame("bottom"); + bottom->setSize(SDL_Rect{42, 166, 120, 52}); + bottom->setBorder(0); + bottom->setColor(0); + + auto male_button = bottom->addButton("male"); if (stats[index]->sex == MALE) { male_button->setColor(makeColor(255, 255, 255, 255)); male_button->setHighlightColor(makeColor(255, 255, 255, 255)); @@ -9485,7 +9544,7 @@ namespace MainMenu { male_button->setHighlightColor(makeColor(127, 127, 127, 255)); } male_button->setBackground("*images/ui/Main Menus/Play/PlayerCreation/Finalize_Button_Male_00.png"); - male_button->setSize(SDL_Rect{42, 166, 58, 52}); + male_button->setSize(SDL_Rect{0, 0, 58, 52}); male_button->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); male_button->addWidgetAction("MenuStart", "ready"); male_button->setWidgetBack("back_button"); @@ -9499,7 +9558,7 @@ namespace MainMenu { case 3: male_button->setCallback([](Button& button){soundActivate(); male_button_fn(button, 3);}); break; } - auto female_button = card->addButton("female"); + auto female_button = bottom->addButton("female"); if (stats[index]->sex == FEMALE) { female_button->setColor(makeColor(255, 255, 255, 255)); female_button->setHighlightColor(makeColor(255, 255, 255, 255)); @@ -9508,7 +9567,7 @@ namespace MainMenu { female_button->setHighlightColor(makeColor(127, 127, 127, 255)); } female_button->setBackground("*images/ui/Main Menus/Play/PlayerCreation/Finalize_Button_Female_00.png"); - female_button->setSize(SDL_Rect{104, 166, 58, 52}); + female_button->setSize(SDL_Rect{62, 0, 58, 52}); female_button->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); female_button->addWidgetAction("MenuStart", "ready"); female_button->setWidgetBack("back_button"); @@ -9563,16 +9622,16 @@ namespace MainMenu { auto card = static_cast(button.getParent()); // select a random sex - stats[index]->sex = (sex_t)(RNG.rand() % 2); + stats[index]->sex = (sex_t)(RNG.getU8() % 2); // select a random race // there are 9 legal races that the player can select from the start. if (enabledDLCPack1 && enabledDLCPack2) { - stats[index]->playerRace = RNG.rand() % NUMPLAYABLERACES; + stats[index]->playerRace = RNG.uniform(0, NUMPLAYABLERACES - 1); } else if (enabledDLCPack1) { - stats[index]->playerRace = RNG.rand() % 5; + stats[index]->playerRace = RNG.uniform(0, 4); } else if (enabledDLCPack2) { - stats[index]->playerRace = RNG.rand() % 5; + stats[index]->playerRace = RNG.uniform(0, 4); if (stats[index]->playerRace > 0) { stats[index]->playerRace += 4; } @@ -9599,33 +9658,38 @@ namespace MainMenu { } // choose a random appearance + const int appearance_choice = RNG.uniform(0, NUMAPPEARANCES - 1); if (stats[index]->playerRace == RACE_HUMAN) { - stats[index]->appearance = RNG.rand() % NUMAPPEARANCES; + stats[index]->appearance = appearance_choice; } else { - stats[index]->appearance = 0; RNG.rand(); + stats[index]->appearance = 0; } // update sex buttons after race selection: // we might have chosen a succubus or incubus - auto male_button = card->findButton("male"); - auto female_button = card->findButton("female"); - if (male_button && female_button) { - if (stats[index]->sex == MALE) { - male_button->setColor(makeColor(255, 255, 255, 255)); - male_button->setHighlightColor(makeColor(255, 255, 255, 255)); - female_button->setColor(makeColor(127, 127, 127, 255)); - female_button->setHighlightColor(makeColor(127, 127, 127, 255)); - } else { - female_button->setColor(makeColor(255, 255, 255, 255)); - female_button->setHighlightColor(makeColor(255, 255, 255, 255)); - male_button->setColor(makeColor(127, 127, 127, 255)); - male_button->setHighlightColor(makeColor(127, 127, 127, 255)); - } + auto bottom = card->findFrame("bottom"); + if (bottom) { + auto male_button = bottom->findButton("male"); + auto female_button = bottom->findButton("female"); + if (male_button && female_button) { + if (stats[index]->sex == MALE) { + male_button->setColor(makeColor(255, 255, 255, 255)); + male_button->setHighlightColor(makeColor(255, 255, 255, 255)); + female_button->setColor(makeColor(127, 127, 127, 255)); + female_button->setHighlightColor(makeColor(127, 127, 127, 255)); + } else { + female_button->setColor(makeColor(255, 255, 255, 255)); + female_button->setHighlightColor(makeColor(255, 255, 255, 255)); + male_button->setColor(makeColor(127, 127, 127, 255)); + male_button->setHighlightColor(makeColor(127, 127, 127, 255)); + } + } } // select a random class - auto reduced_class_list = reducedClassList(index); - auto random_class = reduced_class_list[(RNG.rand() % (reduced_class_list.size() - 1)) + 1]; + const auto reduced_class_list = reducedClassList(index); + const auto class_choice = RNG.uniform(1, reduced_class_list.size() - 1); + const auto random_class = reduced_class_list[class_choice]; for (int c = 1; c < num_classes; ++c) { if (strcmp(random_class, classes_in_order[c]) == 0) { client_classes[index] = c - 1; @@ -9709,19 +9773,19 @@ namespace MainMenu { switch (index) { case 0: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 0);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(0, false);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(0);}); break; case 1: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 1);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(1, false);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(1);}); break; case 2: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 2);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(2, false);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(2);}); break; case 3: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 3);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(3, false);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(3);}); break; } (*class_button->getTickCallback())(*class_button); @@ -10500,8 +10564,8 @@ namespace MainMenu { playerSlotsLocked[c] = false; stats[c]->playerRace = RACE_HUMAN; - stats[c]->sex = static_cast(RNG.rand() % 2); - stats[c]->appearance = RNG.rand() % NUMAPPEARANCES; + stats[c]->sex = static_cast(RNG.getU8() % 2); + stats[c]->appearance = RNG.uniform(0, NUMAPPEARANCES - 1); stats[c]->clearStats(); client_classes[c] = 0; initClass(c); @@ -10509,7 +10573,8 @@ namespace MainMenu { // random name auto& names = stats[c]->sex == sex_t::MALE ? randomPlayerNamesMale : randomPlayerNamesFemale; - auto name = names[RNG.rand() % names.size()].c_str(); + const int choice = RNG.uniform(0, names.size() - 1); + auto name = names[choice].c_str(); size_t len = strlen(name); len = std::min(sizeof(Stat::name) - 1, len); memcpy(stats[c]->name, name, len); @@ -14154,7 +14219,7 @@ namespace MainMenu { quit_motd = 0; } if (quit_motd < 0) { - quit_motd = RNG.rand() % num_quit_messages; + quit_motd = RNG.uniform(0, num_quit_messages - 1); } binaryPrompt( @@ -14201,7 +14266,8 @@ namespace MainMenu { if (ingame) { doEndgame(); } - playMusic(intromusic[RNG.rand() % (NUMINTROMUSIC - 1)], true, false, false); + const int music = RNG.uniform(0, NUMINTROMUSIC - 2); + playMusic(intromusic[music], true, false, false); createTitleScreen(); } else if (main_menu_fade_destination == FadeDestination::RootMainMenu) { @@ -14209,7 +14275,8 @@ namespace MainMenu { if (ingame) { doEndgame(); } - playMusic(intromusic[RNG.rand() % (NUMINTROMUSIC - 1)], true, false, false); + const int music = RNG.uniform(0, NUMINTROMUSIC - 2); + playMusic(intromusic[music], true, false, false); createMainMenu(false); } else if (main_menu_fade_destination == FadeDestination::Victory) { @@ -15075,7 +15142,7 @@ namespace MainMenu { } const char* eulogy; - switch (RNG.rand() % 10) { + switch (RNG.uniform(0, 9)) { default: case 0: eulogy = "We hardly knew ye."; break; case 1: eulogy = "Rest In Peace."; break; From cbba156689e686d6068195c0bedbb0d8844d78e8 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 12 Jul 2022 21:09:00 -0700 Subject: [PATCH 07/21] more lobby improvements --- src/ui/MainMenu.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++--- src/ui/Slider.cpp | 7 +++- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 9d2372b3f..5d05c1331 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -8772,6 +8772,59 @@ namespace MainMenu { subframe->setActualSize(SDL_Rect{0, 0, 234, 36 * num_races}); subframe->setBorder(0); subframe->setColor(0); + subframe->setTickCallback([](Widget& widget){ + auto subframe = static_cast(&widget); assert(subframe); + auto card = static_cast(widget.getParent()); assert(card); + auto gradient = card->findImage("gradient"); assert(gradient); + + const auto grad_size = gradient->pos.h / 2; + const auto size = subframe->getSize(); + const auto asize = subframe->getActualSize(); + const float fade = ((asize.h - grad_size) - (asize.y + size.h)) / (float)grad_size; + const float b_fade = std::min(std::max(0.f, fade), 1.f); + gradient->color = makeColor(255, 255, 255, 127 * b_fade); + }); + + auto slider = card->addSlider("scroll_slider"); + slider->setRailSize(details ? SDL_Rect{278, 376, 12, 118} : SDL_Rect{278, 62, 12, 220}); + slider->setHandleSize(SDL_Rect{0, 0, 20, 28}); + slider->setHandleImage("*images/ui/Sliders/HUD_Magic_Slider_Emerald_01.png"); + slider->setOrientation(Slider::orientation_t::SLIDER_VERTICAL); + slider->setBorder(14); + slider->setMinValue(0.f); + slider->setMaxValue(subframe->getActualSize().h - subframe->getSize().h); + slider->setCallback([](Slider& slider){ + Frame* frame = static_cast(slider.getParent()); + Frame* subframe = frame->findFrame("subframe"); assert(subframe); + auto actualSize = subframe->getActualSize(); + actualSize.y = slider.getValue(); + subframe->setActualSize(actualSize); + }); + slider->setTickCallback([](Widget& widget){ + Slider* slider = static_cast(&widget); + Frame* frame = static_cast(slider->getParent()); + Frame* subframe = frame->findFrame("subframe"); assert(subframe); + auto actualSize = subframe->getActualSize(); + slider->setValue(actualSize.y); + }); + slider->setWidgetSearchParent(card->getName()); + slider->setWidgetLeft(races[0]); + slider->setWidgetBack("back_button"); + slider->setGlyphPosition(Widget::glyph_position_t::CENTERED); + slider->setHideSelectors(true); + + auto hover_image = subframe->addImage( + SDL_Rect{0, 4 + 36 * stats[index]->playerRace, 234, 30}, + 0xffffffff, + "*#images/ui/Main Menus/Play/PlayerCreation/RaceSelection/sublist_item-hover.png", + "hover"); + + auto gradient = card->addImage( + SDL_Rect{38, details ? 446 : 234, 234, 42}, + 0xffffffff, + "*#images/ui/Main Menus/Play/PlayerCreation/RaceSelection/sublist_gradient.png", + "gradient"); + gradient->ontop = true; for (int c = 0; c < num_races; ++c) { auto race = subframe->addButton(races[c]); @@ -8802,9 +8855,10 @@ namespace MainMenu { } if (c < num_races - 1) { race->setWidgetDown(races[c + 1]); - } else { - race->setWidgetDown("disable_abilities"); } + /*else { + race->setWidgetDown("disable_abilities"); + }*/ if (c > 0) { race->setWidgetUp(races[c - 1]); } @@ -8823,6 +8877,14 @@ namespace MainMenu { race->setPressed(true); race->select(); } + race->setTickCallback([](Widget& widget){ + if (widget.isSelected()) { + auto button = static_cast(&widget); assert(button); + auto subframe = static_cast(widget.getParent()); assert(subframe); + auto hover = subframe->findImage("hover"); assert(hover); + hover->pos.y = button->getSize().y; + } + }); auto label = subframe->addField((std::string(races[c]) + "_label").c_str(), 64); if (c >= 1 && c <= 4) { @@ -8861,14 +8923,14 @@ namespace MainMenu { appearances->setWidgetDown(races[1]); appearances->setTickCallback([](Widget& widget){ auto frame = static_cast(&widget); - auto card = static_cast(frame->getParent()); + auto parent = static_cast(frame->getParent()); auto backdrop = frame->findImage("background"); assert(backdrop); auto box = frame->findImage("selection_box"); assert(box); box->disabled = !frame->isSelected(); box->pos.y = frame->getActualSize().y; backdrop->pos.y = frame->getActualSize().y + 4; - auto appearance_uparrow = card->findButton("appearance_uparrow"); - auto appearance_downarrow = card->findButton("appearance_downarrow"); + auto appearance_uparrow = parent->findButton("appearance_uparrow"); + auto appearance_downarrow = parent->findButton("appearance_downarrow"); if (frame->isActivated()) { appearance_uparrow->setDisabled(false); appearance_uparrow->setInvisible(false); @@ -8882,6 +8944,10 @@ namespace MainMenu { appearance_downarrow->setDisabled(true); appearance_downarrow->setInvisible(true); } + if (widget.isSelected()) { + auto hover = parent->findImage("hover"); assert(hover); + hover->pos.y = frame->getSize().y + 2; + } }); auto appearance_backdrop = appearances->addImage( @@ -8916,6 +8982,14 @@ namespace MainMenu { appearances->activateSelection(); button.select(); }); + appearance_uparrow->setTickCallback([](Widget& widget){ + if (widget.isSelected()) { + auto button = static_cast(&widget); assert(button); + auto subframe = static_cast(widget.getParent()); assert(subframe); + auto hover = subframe->findImage("hover"); assert(hover); + hover->pos.y = button->getSize().y; + } + }); appearance_uparrow->addWidgetAction("MenuStart", "confirm"); appearance_uparrow->setWidgetBack("back_button"); appearance_uparrow->addWidgetAction("MenuPageLeft", "male"); @@ -8941,6 +9015,14 @@ namespace MainMenu { appearances->activateSelection(); button.select(); }); + appearance_downarrow->setTickCallback([](Widget& widget){ + if (widget.isSelected()) { + auto button = static_cast(&widget); assert(button); + auto subframe = static_cast(widget.getParent()); assert(subframe); + auto hover = subframe->findImage("hover"); assert(hover); + hover->pos.y = button->getSize().y; + } + }); appearance_downarrow->addWidgetAction("MenuStart", "confirm"); appearance_downarrow->setWidgetBack("back_button"); appearance_downarrow->addWidgetAction("MenuPageLeft", "male"); @@ -10622,7 +10704,7 @@ namespace MainMenu { index * Frame::virtualScreenX / 4, 0, Frame::virtualScreenX / 4, - Frame::virtualScreenY - card->getSize().h / 2 + Frame::virtualScreenY * 3 / 4 }); auto backdrop = card->findImage("backdrop"); const char* invite_window = "*images/ui/Main Menus/Play/PlayerCreation/UI_Invite_Window00.png"; diff --git a/src/ui/Slider.cpp b/src/ui/Slider.cpp index 8396b15b3..95152f1fe 100644 --- a/src/ui/Slider.cpp +++ b/src/ui/Slider.cpp @@ -48,7 +48,10 @@ void Slider::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector 0 && _railSize.h > 0) { - if (railImage.empty()) { + auto& imageToUse = activated ? + (handleImageActivated.empty() ? handleImage : handleImageActivated) : + handleImage; + if (railImage.empty() && imageToUse.empty()) { Uint8 r, g, b, a; ::getColor(color, &r, &g, &b, &a); r = (r / 3) * 2; @@ -56,7 +59,7 @@ void Slider::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectordrawColor(nullptr, _railSize, viewport, darkColor); - } else { + } else if (!railImage.empty()) { Frame::image_t image; image.path = railImage; image.color = focused ? highlightColor : color; From ce03c85469057e92504812e50b80998ec6667a88 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Fri, 15 Jul 2022 16:32:28 -0700 Subject: [PATCH 08/21] class descriptions --- src/interface/interface.cpp | 2 +- src/ui/Button.cpp | 2 +- src/ui/MainMenu.cpp | 672 ++++++++++++++++++++++++++++++++---- 3 files changed, 611 insertions(+), 65 deletions(-) diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index b76c392ff..2b5e6cae4 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -810,7 +810,7 @@ int loadConfig(char* filename) // open the config file if ( (fp = FileIO::open(filename, "rb")) == NULL ) { - printlog("warning: config file '%s' does not exist!\n", filename); + printlog("note: config file '%s' does not exist!\n", filename); defaultConfig(); //Set up the game with the default config. return 0; } diff --git a/src/ui/Button.cpp b/src/ui/Button.cpp index 5b99bff3c..5920026e6 100644 --- a/src/ui/Button.cpp +++ b/src/ui/Button.cpp @@ -178,7 +178,7 @@ void Button::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectordraw(§ion, scaledPos, viewport); + iconImg->drawColor(§ion, scaledPos, viewport, focused ? highlightColor : color); } } else if (!text.empty()) { Font* _font = Font::get(font.c_str()); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 5d05c1331..b00023e35 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -427,8 +427,8 @@ namespace MainMenu { static void characterCardGameSettingsMenu(int index); static void characterCardLobbySettingsMenu(int index); - static void characterCardRaceMenu(int index, bool details); - static void characterCardClassMenu(int index); + static void characterCardRaceMenu(int index, bool details, int selection); + static void characterCardClassMenu(int index, bool details, int selection); static void createControllerPrompt(int index, bool show_player_text, void (*after_func)()); static void createCharacterCard(int index); @@ -7855,7 +7855,7 @@ namespace MainMenu { constexpr int num_classes = sizeof(classes_in_order) / sizeof(classes_in_order[0]); - constexpr Uint32 color_human = makeColorRGB(169, 185, 212); + constexpr Uint32 color_dlc0 = makeColorRGB(169, 185, 212); constexpr Uint32 color_dlc1 = makeColorRGB(241, 129, 78); constexpr Uint32 color_dlc2 = makeColorRGB(255, 53, 206); constexpr Uint32 color_traits = makeColorRGB(184, 146, 109); @@ -7889,7 +7889,7 @@ namespace MainMenu { color_race = color_dlc2; } else { - color_race = color_human; + color_race = color_dlc0; } auto details_title = card.findField("details_title"); @@ -8713,9 +8713,11 @@ namespace MainMenu { }*/ } - static void characterCardRaceMenu(int index, bool details) { + static void characterCardRaceMenu(int index, bool details, int selection) { auto card = initCharacterCard(index, details ? 664 : 488); + static int race_selection[MAXPLAYERS]; + static void (*back_fn)(int) = [](int index){ createCharacterCard(index); auto lobby = main_menu_frame->findFrame("lobby"); assert(lobby); @@ -8875,7 +8877,11 @@ namespace MainMenu { } if (stats[index]->playerRace == c) { race->setPressed(true); + } + if ((stats[index]->playerRace == c && selection == -1) || + (selection >= 0 && selection == c)) { race->select(); + race->scrollParent(); } race->setTickCallback([](Widget& widget){ if (widget.isSelected()) { @@ -8883,6 +8889,7 @@ namespace MainMenu { auto subframe = static_cast(widget.getParent()); assert(subframe); auto hover = subframe->findImage("hover"); assert(hover); hover->pos.y = button->getSize().y; + race_selection[widget.getOwner()] = (hover->pos.y - 2) / 36; } }); @@ -8892,7 +8899,7 @@ namespace MainMenu { } else if (c >= 5 && c <= 8) { label->setColor(color_dlc2); } else { - label->setColor(color_human); + label->setColor(color_dlc0); } label->setText(races[c]); label->setFont(smallfont_outline); @@ -8947,6 +8954,7 @@ namespace MainMenu { if (widget.isSelected()) { auto hover = parent->findImage("hover"); assert(hover); hover->pos.y = frame->getSize().y + 2; + race_selection[widget.getOwner()] = (hover->pos.y - 2) / 36; } }); @@ -8988,6 +8996,7 @@ namespace MainMenu { auto subframe = static_cast(widget.getParent()); assert(subframe); auto hover = subframe->findImage("hover"); assert(hover); hover->pos.y = button->getSize().y; + race_selection[widget.getOwner()] = (hover->pos.y - 2) / 36; } }); appearance_uparrow->addWidgetAction("MenuStart", "confirm"); @@ -9021,6 +9030,7 @@ namespace MainMenu { auto subframe = static_cast(widget.getParent()); assert(subframe); auto hover = subframe->findImage("hover"); assert(hover); hover->pos.y = button->getSize().y; + race_selection[widget.getOwner()] = (hover->pos.y - 2) / 36; } }); appearance_downarrow->addWidgetAction("MenuStart", "confirm"); @@ -9050,7 +9060,7 @@ namespace MainMenu { for (int c = 0; c < num_appearances; ++c) { auto name = appearance_names[c]; auto entry = appearances->addEntry(std::to_string(c).c_str(), true); - entry->color = color_human; + entry->color = color_dlc0; entry->text = name; switch (index) { case 0: entry->click = [](Frame::entry_t& entry){soundActivate(); appearance_fn(entry, 0); entry.parent.activate();}; break; @@ -9187,17 +9197,17 @@ namespace MainMenu { show_race_info->setWidgetLeft("female"); if (details) { switch (index) { - case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, false);}); break; - case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, false);}); break; - case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, false);}); break; - case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, false);}); break; + case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, false, race_selection[0]);}); break; + case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, false, race_selection[1]);}); break; + case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, false, race_selection[2]);}); break; + case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, false, race_selection[3]);}); break; } } else { switch (index) { - case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, true);}); break; - case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, true);}); break; - case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, true);}); break; - case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, true);}); break; + case 0: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, true, race_selection[0]);}); break; + case 1: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, true, race_selection[1]);}); break; + case 2: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, true, race_selection[2]);}); break; + case 3: show_race_info->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, true, race_selection[3]);}); break; } } show_race_info->addWidgetAction("MenuPageLeft", "male"); @@ -9224,9 +9234,11 @@ namespace MainMenu { }*/ } - static void characterCardClassMenu(int index) { + static void characterCardClassMenu(int index, bool details, int selection) { auto reduced_class_list = reducedClassList(index); - auto card = initCharacterCard(index, 446); + auto card = initCharacterCard(index, details? 664 : 446); + + static int class_selection[MAXPLAYERS]; static void (*back_fn)(int) = [](int index){ createCharacterCard(index); @@ -9246,7 +9258,9 @@ namespace MainMenu { auto backdrop = card->addImage( card->getActualSize(), 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Window_04.png", + details ? + "*#images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Details.png": + "*#images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Window_04.png", "backdrop" ); @@ -9263,46 +9277,536 @@ namespace MainMenu { class_name_header->setHJustify(Field::justify_t::CENTER); class_name_header->setVJustify(Field::justify_t::BOTTOM);*/ - auto textbox = card->addImage( - SDL_Rect{42, 68, 192, 46}, - 0xffffffff, - "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Box_ClassName_04.png", - "textbox" - ); + if (!details) { + auto textbox = card->addImage( + SDL_Rect{42, 68, 192, 46}, + 0xffffffff, + "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Box_ClassName_04.png", + "textbox" + ); + } - static auto class_name_fn = [](Field& field, int index){ - int i = std::min(std::max(0, client_classes[index] + 1), num_classes - 1); - auto find = classes.find(classes_in_order[i]); - if (find != classes.end()) { - field.setText(find->second.name); - } - }; + if (details) { + static const char* class_descs[] = { + "Barbarian\n" + "A skilled combatant. What\n" + "they lack in armor they make\n" + "up for in strength and\n" + "fighting prowess.\n" + "Barbarians can quickly\n" + "dispatch lesser foes before\n" + "taking any hits by using the\n" + "right weapon for the job. A\n" + "surprise assault is the key\n" + "to a swift victory. Staying\n" + "light-footed will allow a\n" + "Barbarian to avoid damage.", + + + "Warrior\n" + "The trained soldier. They\n" + "are heavily armored and\n" + "make capable leaders.\n" + "Warriors are well equipped\n" + "for most fights, assuming\n" + "they know when to use each\n" + "of their weapons. But hubris\n" + "is the downfall of many\n" + "Warriors, whether they\n" + "waddle too slowly near\n" + "traps, or allowing magic to\n" + "strike through their armor.", + + + "Healer\n" + "A talented physician. Though\n" + "they are poor fighters, they\n" + "come stocked with medical\n" + "supplies and other healing\n" + "abilities.\n" + "Compared to other magic\n" + "users, the Healer is a more\n" + "durable and well-rounded\n" + "hero as they grow in\n" + "experience. With care, they\n" + "may become a very tough\n" + "spellcaster.", + + + "Rogue\n" + "The professional hooligan.\n" + "Dextrous and skilled thieves,\n" + "though lacking in power and\n" + "equipment.\n" + "Even skilled Rogues succumb\n" + "trying to live by the blade.\n" + "But their deft hand can pick\n" + "ammo from traps, and using\n" + "stealth, a patient Rogue\n" + "survives by picking off tough\n" + "foes from afar, practicing\n" + "ambushes against soft foes.", + + + "Wanderer\n" + "The hardened traveler. Low\n" + "in armor and combat ability,\n" + "but well-equipped for the\n" + "dungeon.\n" + "A Wanderer's ample food\n" + "supply provides a patient\n" + "start to their adventure,\n" + "allowing them to play it\n" + "safe. But their hardy\n" + "nature transforms them\n" + "into very durable fighters\n" + "as their quest labors on.", + + + "Cleric\n" + "Students of the church.\n" + "Fairly well equipped and\n" + "able in many ways, they are\n" + "well-rounded adventurers.\n" + "While Clerics start with no\n" + "spells, their training has\n" + "prepared them to learn\n" + "quickly. A wise Cleric will\n" + "make use of magic as it\n" + "becomes available without\n" + "forsaking their martial\n" + "training.", + + + "Merchant\n" + "A seasoned trader. They\n" + "are skilled in the market\n" + "and adept at identifying\n" + "foreign artifacts.\n" + "While decently equipped for\n" + "a fight, Merchant explorers\n" + "will survive longer if they\n" + "develop skills which keep foes\n" + "at a distance, especially\n" + "adopting followers and\n" + "crafting skills to which they\n" + "are naturally inclined.", + + + "Wizard\n" + "The wise magician. Though\n" + "frail, they are extremely\n" + "well-versed in magic.\n" + "Many young adventurers\n" + "find early success with\n" + "powerful spells. However,\n" + "most mighty magic users\n" + "cannot cast indefinitely. To\n" + "succeed as a Wizard, one\n" + "must learn when it is enough\n" + "to finish foes off with a stiff\n" + "whack from a polearm.", + + + "Arcanist\n" + "A cunning spellcaster. Less\n" + "magically adept than the\n" + "Wizard, but hardier and\n" + "better equipped.\n" + "Due to having mundane and\n" + "magical ranged attacks at\n" + "their disposal, successful\n" + "Arcanists rely on mobility to\n" + "defeat threats from a\n" + "distance. Adding special\n" + "ammo or spells will allow the\n" + "Arcanist to improve in power.", + + + "Joker\n" + "The wild card. Jokers come\n" + "with very little equipment,\n" + "but they have a few tricks\n" + "up their sleeves,\n" + "nonetheless.\n" + "Jokers tend to gravitate\n" + "toward magical trickery\n" + "and commanding followers,\n" + "but their chaotic nature\n" + "results in a lack of focus.\n" + "Best to improvise and stay\n" + "flexible.", + + + "Sexton\n" + "A temple officer who serves\n" + "unseen, using stealth and\n" + "magic to slip their way\n" + "through the dungeon with\n" + "the aid of a few rare tools.\n" + "Sextons are fastidious\n" + "planners, and their diverse\n" + "talents bring success when\n" + "they make time to approach\n" + "each problem thoughtfully.\n" + "Sextons who panic fail to use\n" + "the tools at their disposal.", + + + "Ninja\n" + "A highly specialized assassin.\n" + "They ambush foes with\n" + "swords or ranged weapons,\n" + "using a few other tricks to\n" + "get out of bad situations.\n" + "Ninjas do well to find backup\n" + "blades. Their fragile sword\n" + "is sharp, but a break at the\n" + "wrong time can be fatal.\n" + "To improve their chances, a\n" + "Ninja must remain in control\n" + "of how a fight begins.", + + + "Monk\n" + "Disciplined and hardy. They\n" + "have little in the way of\n" + "offensive training and\n" + "material goods, but can rely\n" + "on their excellent fortitude\n" + "and adaptability.\n" + "The Monk is exceptional at\n" + "blocking attacks, and is very\n" + "slow to hunger. Approaching\n" + "challenges patiently plays\n" + "to the Monk's strengths. Keep\n" + "a torch or shield at hand.", + + + "Conjurer\n" + "A frail but adept magic\n" + "user, able to conjure allies\n" + "with a reliable spell.\n" + "Unavailable to any other\n" + "class, the Conjure Skeleton\n" + "spell provides a persistent\n" + "companion, even if it is killed.\n" + "The Conjured allies grow in\n" + "power, so long as they are\n" + "permitted to kill foes and\n" + "grow in experience. Nurture\n" + "these allies to succeed.", + + + "Accursed\n" + "The Accursed suffer from\n" + "bestial hunger, but gain\n" + "supernatural magic power\n" + "and speed. An arcane\n" + "library found deep within\n" + "the dungeon may have a\n" + "cure.\n" + "While afflicted, the Accursed\n" + "must move quickly and reap\n" + "blood to evade starvation.\n" + "Very powerful, but those\n" + "lacking expertise will fail.", + + + "Mesmer\n" + "The Mesmer uses the Charm\n" + "spell and leadership ability\n" + "to enlist powerful allies.\n" + "Mesmers may only Charm one\n" + "at a time, so they should\n" + "be strategic with recruiting.\n" + "Charmed allies get stronger\n" + "with experience, so nurturing\n" + "them can be wise. The Charm\n" + "spell is difficult to learn;\n" + "Successful Mesmers must\n" + "practice other kinds of magic.", + + + "Brewer\n" + "A talented alchemist who is\n" + "also comfortable with the\n" + "relationships and bar-room\n" + "brawls that a good brew will\n" + "bring.\n" + "Successful Brewers make it\n" + "a priority to collect, brew,\n" + "and duplicate potions so\n" + "they are never short on\n" + "supplies. A backpack full of\n" + "bottles allows the Brewer to\n" + "adapt with short notice.", + + + "Mechanist\n" + "A skilled craftsman, the\n" + "Mechanist uses a toolkit to\n" + "make and maintain\n" + "mechanical weapons, letting\n" + "contraptions do the dirty\n" + "work.\n" + "Successful Mechanists use\n" + "foresight to plan ahead and\n" + "are prepared with the right\n" + "tool for any problem. Those\n" + "who try to rely on strength\n" + "and speed may struggle.", + + + "Punisher\n" + "The Punisher picks unfair\n" + "fights, toying with foes using\n" + "dark magic and a whip\n" + "before making the execution,\n" + "or releasing one's inner\n" + "demons to do the job.\n" + "Punishers are not durable\n" + "and must maintain control\n" + "in combat. Staying mobile is\n" + "the key to exploiting their\n" + "unique whip's longer attack\n" + "range.", + + + "Shaman\n" + "The Shaman is a mystic whose\n" + "connection to nature spirits\n" + "allows them to shapeshift\n" + "into bestial forms. Each\n" + "form's talents provide\n" + "diverse advantages in the\n" + "dungeon.\n" + "While transformed, Shamans\n" + "make different friends, and\n" + "can tolerate different food.\n" + "Being a beast will influence\n" + "how the Shaman grows.", + + + "Hunter\n" + "Equipped to track and bring\n" + "down foes from afar, the\n" + "Hunter uses special arrows\n" + "and a magic boomerang to\n" + "ensure they never have to\n" + "fight toe-to-toe.\n" + "Hunters are frail and\n" + "must avoid being backed into\n" + "a corner. Staying hidden\n" + "and using ammo wisely is the\n" + "key to their survival.", + }; + static constexpr int num_class_descs = sizeof(class_descs) / sizeof(class_descs[0]); + + static auto class_desc_fn = [](Field& field, int index){ + const int i = std::min(std::max(0, client_classes[index]), num_class_descs - 1); + field.setText(class_descs[i]); + if (i < CLASS_CONJURER) { + field.addColorToLine(0, color_dlc0); + } else if (i < CLASS_MACHINIST) { + field.addColorToLine(0, color_dlc1); + } else { + field.addColorToLine(0, color_dlc2); + } + }; - auto class_name = card->addField("class_name", 64); - class_name->setSize(SDL_Rect{42, 68, 192, 46}); - class_name->setHJustify(Field::justify_t::CENTER); - class_name->setVJustify(Field::justify_t::CENTER); - class_name->setFont(smallfont_outline); - switch (index) { - case 0: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 0);}); break; - case 1: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 1);}); break; - case 2: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 2);}); break; - case 3: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 3);}); break; + auto class_desc = card->addField("class_desc", 1024); + class_desc->setSize(SDL_Rect{42, 68, 240, 218}); + class_desc->setFont(smallfont_no_outline); + switch (index) { + case 0: class_desc->setTickCallback([](Widget& widget){class_desc_fn(*static_cast(&widget), 0);}); break; + case 1: class_desc->setTickCallback([](Widget& widget){class_desc_fn(*static_cast(&widget), 1);}); break; + case 2: class_desc->setTickCallback([](Widget& widget){class_desc_fn(*static_cast(&widget), 2);}); break; + case 3: class_desc->setTickCallback([](Widget& widget){class_desc_fn(*static_cast(&widget), 3);}); break; + } + (*class_desc->getTickCallback())(*class_desc); + + // stats definitions + const char* class_stats_text[] = { + "STR", "DEX", "CON", "INT", "PER", "CHR" + }; + constexpr int num_class_stats = sizeof(class_stats_text) / sizeof(class_stats_text[0]); + constexpr SDL_Rect bottom{44, 306, 236, 68}; + constexpr int column = bottom.w / num_class_stats; + + // character attribute ratings + constexpr Uint32 good = makeColorRGB(0, 193, 255); + constexpr Uint32 decent = makeColorRGB(158, 208, 223); + constexpr Uint32 average = makeColorRGB(192, 192, 192); + constexpr Uint32 poor = makeColorRGB(255, 159, 56); + constexpr Uint32 bad = makeColorRGB(255, 56, 56); + static constexpr Uint32 class_stat_colors[][num_class_stats] = { + {good, decent, bad, bad, decent, decent}, // barbarian + {good, bad, good, bad, bad, good}, // warrior + {average, average, decent, good, decent, average}, // healer + {bad, good, bad, bad, good, decent}, // rogue + {decent, average, decent, average, decent, bad}, // wanderer + {average, bad, decent, decent, average, average}, // cleric + {average, bad, decent, average, decent, good}, // merchant + {bad, average, bad, good, good, decent}, // wizard + {bad, decent, bad, decent, decent, bad}, // arcanist + {average, average, average, average, average, average}, // joker + {good, good, average, good, average, average}, // sexton + {good, good, decent, average, average, bad}, // ninja + {decent, bad, good, average, bad, bad}, // monk + {bad, bad, decent, good, decent, decent}, // conjurer + {average, average, bad, good, good, average}, // accursed + {average, average, bad, good, decent, good}, // mesmer + {average, average, bad, decent, bad, decent}, // brewer + {bad, decent, bad, average, good, average}, // mechanist + {decent, average, bad, average, decent, decent}, // punisher + {average, average, average, average, average, average}, // shaman + {bad, good, bad, average, good, average}, // hunter + }; + + // difficulty star ratings + static constexpr int difficulty[][2] = { + {3, 1}, // barbarian + {4, 2}, // warrior + {4, 2}, // healer + {2, 2}, // rogue + {4, 1}, // wanderer + {3, 2}, // cleric + {3, 2}, // merchant + {3, 2}, // wizard + {2, 2}, // arcanist + {1, 3}, // joker + {3, 4}, // sexton + {2, 2}, // ninja + {5, 1}, // monk + {3, 3}, // conjurer + {1, 3}, // accursed + {3, 3}, // mesmer + {2, 5}, // brewer + {2, 5}, // mechanist + {2, 4}, // punisher + {2, 4}, // shaman + {2, 2}, // hunter + }; + + for (int c = 0; c < num_class_stats; ++c) { + static auto class_stat_fn = [](Field& field, int index){ + const int i = std::min(std::max(0, client_classes[index]), num_class_descs - 1); + const int s = (int)strtol(field.getName(), nullptr, 10); + field.setColor(class_stat_colors[i][s]); + }; + static char buf[16]; + snprintf(buf, sizeof(buf), "%d", c); + auto class_stat = card->addField(buf, 16); + class_stat->setSize(SDL_Rect{ + bottom.x + column * c, bottom.y, column, bottom.h}); + class_stat->setHJustify(Field::justify_t::CENTER); + class_stat->setVJustify(Field::justify_t::TOP); + class_stat->setFont(smallfont_outline); + class_stat->setText(class_stats_text[c]); + switch (index) { + case 0: class_stat->setTickCallback([](Widget& widget){class_stat_fn(*static_cast(&widget), 0);}); break; + case 1: class_stat->setTickCallback([](Widget& widget){class_stat_fn(*static_cast(&widget), 1);}); break; + case 2: class_stat->setTickCallback([](Widget& widget){class_stat_fn(*static_cast(&widget), 2);}); break; + case 3: class_stat->setTickCallback([](Widget& widget){class_stat_fn(*static_cast(&widget), 3);}); break; + } + (*class_stat->getTickCallback())(*class_stat); + } + + // difficulty header + constexpr SDL_Rect difficulty_size{76, 306, 172, 64}; + auto difficulty_header = card->addField("difficulty_header", 128); + difficulty_header->setFont(smallfont_outline); + difficulty_header->setColor(makeColorRGB(209, 166, 161)); + difficulty_header->setText("Survival\nComplexity"); + difficulty_header->setHJustify(Field::justify_t::LEFT); + difficulty_header->setVJustify(Field::justify_t::BOTTOM); + difficulty_header->setSize(difficulty_size); + + // difficulty stars + static constexpr int star_buf_size = 32; + static auto stars_fn = [](Field& field, int index){ + const int i = std::min(std::max(0, client_classes[index]), num_class_descs - 1); + const char* lines[2]; + for (int c = 0; c < 2; ++c) { + switch (difficulty[i][c]) { + default: + case 1: lines[c] = "*"; field.addColorToLine(c, c == 0 ? bad : good); break; + case 2: lines[c] = "**"; field.addColorToLine(c, c == 0 ? poor : decent); break; + case 3: lines[c] = "***"; field.addColorToLine(c, c == 0 ? average : average); break; + case 4: lines[c] = "****"; field.addColorToLine(c, c == 0 ? decent : decent); break; + case 5: lines[c] = "*****"; field.addColorToLine(c, c == 0 ? good : bad); break; + } + } + char buf[star_buf_size]; + snprintf(buf, sizeof(buf), "%s\n%s", lines[0], lines[1]); + field.setText(buf); + }; + auto difficulty_stars = card->addField("difficulty_stars", star_buf_size); + difficulty_stars->setFont(smallfont_outline); + difficulty_stars->setHJustify(Field::justify_t::RIGHT); + difficulty_stars->setVJustify(Field::justify_t::BOTTOM); + difficulty_stars->setSize(difficulty_size); + switch (index) { + case 0: difficulty_stars->setTickCallback([](Widget& widget){stars_fn(*static_cast(&widget), 0);}); break; + case 1: difficulty_stars->setTickCallback([](Widget& widget){stars_fn(*static_cast(&widget), 1);}); break; + case 2: difficulty_stars->setTickCallback([](Widget& widget){stars_fn(*static_cast(&widget), 2);}); break; + case 3: difficulty_stars->setTickCallback([](Widget& widget){stars_fn(*static_cast(&widget), 3);}); break; + } + (*difficulty_stars->getTickCallback())(*difficulty_stars); + } else { + static auto class_name_fn = [](Field& field, int index){ + const int i = std::min(std::max(0, client_classes[index] + 1), num_classes - 1); + auto find = classes.find(classes_in_order[i]); + if (find != classes.end()) { + field.setText(find->second.name); + } + if (i - 1 < CLASS_CONJURER) { + field.setColor(color_dlc0); + } else if (i - 1 < CLASS_MACHINIST) { + field.setColor(color_dlc1); + } else { + field.setColor(color_dlc2); + } + }; + + auto class_name = card->addField("class_name", 64); + class_name->setSize(SDL_Rect{42, 68, 192, 46}); + class_name->setHJustify(Field::justify_t::CENTER); + class_name->setVJustify(Field::justify_t::CENTER); + class_name->setFont(smallfont_outline); + switch (index) { + case 0: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 0);}); break; + case 1: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 1);}); break; + case 2: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 2);}); break; + case 3: class_name->setTickCallback([](Widget& widget){class_name_fn(*static_cast(&widget), 3);}); break; + } + (*class_name->getTickCallback())(*class_name); } - (*class_name->getTickCallback())(*class_name); + + const int height = std::max(254, 6 + 54 * (int)(reduced_class_list.size() / 4 + ((reduced_class_list.size() % 4) ? 1 : 0))); auto subframe = card->addFrame("subframe"); subframe->setScrollBarsEnabled(false); - subframe->setSize(SDL_Rect{34, 124, 226, 254}); - subframe->setActualSize(SDL_Rect{0, 0, 226, std::max(258, 6 + 54 * (int)(classes.size() / 4 + ((classes.size() % 4) ? 1 : 0)))}); + if (details) { + subframe->setSize(SDL_Rect{34, 382, 226, 214}); + } else { + subframe->setSize(SDL_Rect{34, 124, 226, 254}); + } + subframe->setActualSize(SDL_Rect{0, 0, 226, height}); subframe->setBorder(0); subframe->setColor(0); if (subframe->getActualSize().h > subframe->getSize().h) { auto slider = card->addSlider("scroll_slider"); - slider->setRailSize(SDL_Rect{260, 118, 30, 266}); + if (details) { + slider->setRailSize(SDL_Rect{260, 382, 30, 214}); + slider->setRailImage("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_01.png"); + } else { + slider->setRailSize(SDL_Rect{260, 118, 30, 266}); + slider->setRailImage("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_00.png"); + } slider->setHandleSize(SDL_Rect{0, 0, 34, 34}); - slider->setRailImage("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_00.png"); slider->setHandleImage("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_SliderB_00.png"); slider->setOrientation(Slider::orientation_t::SLIDER_VERTICAL); slider->setBorder(24); @@ -9330,16 +9834,37 @@ namespace MainMenu { auto class_info = card->addButton("class_info"); class_info->setColor(makeColor(255, 255, 255, 255)); class_info->setHighlightColor(makeColor(255, 255, 255, 255)); - class_info->setSize(SDL_Rect{238, 68, 46, 46}); + if (details) { + class_info->setSize(SDL_Rect{266, 38, 46, 46}); + } else { + class_info->setSize(SDL_Rect{238, 68, 46, 46}); + } class_info->setBackground("*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_Button_Info_00.png"); class_info->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); class_info->addWidgetAction("MenuStart", "confirm"); - class_info->addWidgetAction("MenuAlt1", "class_info"); + class_info->addWidgetAction("MenuAlt2", "class_info"); class_info->setWidgetBack("back_button"); + class_info->setGlyphPosition(Widget::glyph_position_t::BOTTOM_RIGHT); + if (details) { + switch (index) { + case 0: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(0, false, class_selection[0]);}); break; + case 1: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(1, false, class_selection[1]);}); break; + case 2: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(2, false, class_selection[2]);}); break; + case 3: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(3, false, class_selection[3]);}); break; + } + } else { + switch (index) { + case 0: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(0, true, class_selection[0]);}); break; + case 1: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(1, true, class_selection[1]);}); break; + case 2: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(2, true, class_selection[2]);}); break; + case 3: class_info->setCallback([](Button& button){soundActivate(); characterCardClassMenu(3, true, class_selection[3]);}); break; + } + } const int current_class = std::min(std::max(0, client_classes[index]), num_classes - 1); auto current_class_name = classes_in_order[current_class + 1]; + bool selected_button = false; const std::string prefix = "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/"; for (int c = reduced_class_list.size() - 1; c >= 0; --c) { auto name = reduced_class_list[c]; @@ -9354,10 +9879,16 @@ namespace MainMenu { } button->setIcon((prefix + full_class.image).c_str()); button->setSize(SDL_Rect{8 + (c % 4) * 54, 6 + (c / 4) * 54, 54, 54}); - if (strcmp(name, current_class_name)) { - button->setColor(makeColor(127, 127, 127, 255)); - } else { + if (!strcmp(name, current_class_name)) { button->setColor(makeColor(255, 255, 255, 255)); + } else { + button->setColor(makeColor(127, 127, 127, 255)); + } + if ((!selection && !strcmp(name, current_class_name)) || + (selection && c == selection)) { + button->select(); + button->scrollParent(); + selected_button = true; } button->setHighlightColor(makeColor(255, 255, 255, 255)); button->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); @@ -9378,7 +9909,7 @@ namespace MainMenu { button->setWidgetDown(reduced_class_list[reduced_class_list.size() - 1]); } button->addWidgetAction("MenuStart", "confirm"); - button->addWidgetAction("MenuAlt1", "class_info"); + button->addWidgetAction("MenuAlt2", "class_info"); button->setWidgetBack("back_button"); static auto button_fn = [](Button& button, int index){ @@ -9433,10 +9964,25 @@ namespace MainMenu { case 2: button->setCallback([](Button& button){button_fn(button, 2);}); break; case 3: button->setCallback([](Button& button){button_fn(button, 3);}); break; } + + button->setTickCallback([](Widget& widget){ + auto button = static_cast(&widget); + if (button->isSelected()) { + for (int c = 0; c < num_classes; ++c) { + if (strcmp(button->getName(), classes_in_order[c]) == 0) { + class_selection[widget.getOwner()] = c; + break; + } + } + } + }); } - auto first_button = subframe->findButton(reduced_class_list[0]); assert(first_button); - first_button->select(); + if (!selected_button) { + auto first_button = subframe->findButton(reduced_class_list[0]); + assert(first_button); + first_button->select(); + } /*auto confirm = card->addButton("confirm"); confirm->setColor(makeColor(255, 255, 255, 255)); @@ -9447,7 +9993,7 @@ namespace MainMenu { confirm->setFont(bigfont_outline); confirm->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); confirm->addWidgetAction("MenuStart", "confirm"); - confirm->addWidgetAction("MenuAlt1", "class_info"); + confirm->addWidgetAction("MenuAlt2", "class_info"); confirm->setWidgetBack("back_button"); switch (index) { case 0: confirm->setCallback([](Button&){soundActivate(); back_fn(0);}); break; @@ -9692,10 +10238,10 @@ namespace MainMenu { race_button->setWidgetUp("game_settings"); race_button->setWidgetDown("class"); switch (index) { - case 0: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, false);}); break; - case 1: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, false);}); break; - case 2: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, false);}); break; - case 3: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, false);}); break; + case 0: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(0, false, -1);}); break; + case 1: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(1, false, -1);}); break; + case 2: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(2, false, -1);}); break; + case 3: race_button->setCallback([](Button&){soundActivate(); characterCardRaceMenu(3, false, -1);}); break; } static auto randomize_class_fn = [](Button& button, int index){ @@ -9855,19 +10401,19 @@ namespace MainMenu { switch (index) { case 0: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 0);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(0);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(0, false, 0);}); break; case 1: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 1);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(1);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(1, false, 0);}); break; case 2: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 2);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(2);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(2, false, 0);}); break; case 3: class_button->setTickCallback([](Widget& widget){class_button_fn(*static_cast(&widget), 3);}); - class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(3);}); + class_button->setCallback([](Button&){soundActivate(); characterCardClassMenu(3, false, 0);}); break; } (*class_button->getTickCallback())(*class_button); From 5e046f3194e37481b8652faa96802b441bb9bda5 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 19 Jul 2022 01:53:08 -0700 Subject: [PATCH 09/21] lobby invite, cancel join, lobby settings window --- src/game.cpp | 12 +- src/menu.cpp | 7 - src/steam.cpp | 93 ++------ src/steam.hpp | 6 +- src/ui/MainMenu.cpp | 514 +++++++++++++++++++++++++++++++++----------- src/ui/MainMenu.hpp | 2 +- 6 files changed, 414 insertions(+), 220 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 8589f9b8c..813df9449 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5826,6 +5826,8 @@ int main(int argc, char** argv) { for (c = 1; c < argc; c++) { + cmd_line += argv[c]; + cmd_line += " "; if ( argv[c] != NULL ) { if ( !strcmp(argv[c], "-windowed") ) @@ -6096,14 +6098,8 @@ int main(int argc, char** argv) printTextFormattedAlpha(font16x16_bmp, (xres / 2) - strlen("Turning Wheel") * 9, yres / 2 + 128, std::min(std::max(0, logoalpha), 255), "Turning Wheel"); if ( logoalpha >= 255 && !fadeout ) { - if ( !skipintro && !strcmp(classtoquickstart, "") ) - { - MainMenu::beginFade(MainMenu::FadeDestination::IntroStoryScreen); - } - else - { - MainMenu::beginFade(MainMenu::FadeDestination::TitleScreen); - } + fadeout = true; + fadefinished = false; } bool skipButtonPressed = false; diff --git a/src/menu.cpp b/src/menu.cpp index 9177ec80d..aa21b9e53 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -10641,13 +10641,6 @@ void doEndgame() { } } - // finish handling invite -#ifdef STEAMWORKS - if ( stillConnectingToLobby ) - { - processLobbyInvite(); - } -#endif #if defined USE_EOS if ( !directConnect ) { diff --git a/src/steam.cpp b/src/steam.cpp index 3a84dd0e4..a6d531127 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -31,6 +31,8 @@ #include "lobbies.hpp" #endif +#define STEAMDEBUG + #ifdef STEAMWORKS Uint32 numSteamLobbies = 0; @@ -54,7 +56,7 @@ void* lobbyToConnectTo = NULL; // CSteamID of the game lobby that user has been void* steamIDGameServer = NULL; // CSteamID to the current game server uint32_t steamServerIP = 0; // ipv4 address for the current game server uint16_t steamServerPort = 0; // port number for the current game server -char pchCmdLine[1024] = { 0 }; // for game join requests +std::string cmd_line; // for game join requests // menu stuff bool connectingToLobby = false, connectingToLobbyWindow = false; @@ -1333,7 +1335,6 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) } #ifdef STEAMWORKS -#define STEAMDEBUG /*------------------------------------------------------------------------------- @@ -1499,16 +1500,6 @@ void steam_OnLobbyDataUpdatedCallback( void* pCallback ) } } - // finish processing lobby invite? - if ( stillConnectingToLobby ) - { - stillConnectingToLobby = false; - - void processLobbyInvite(); - processLobbyInvite(); - return; - } - // update current lobby info void* tempSteamID = cpp_LobbyDataUpdated_pCallback_m_ulSteamIDLobby(pCallback); //TODO: BUGGER VOID POINTER. if ( currentLobby ) @@ -1762,88 +1753,47 @@ void steam_OnGameJoinRequested( void* pCallback ) printlog( "OnGameJoinRequested\n" ); #endif - // return to a state where we can join the lobby - if ( !intro ) - { - buttonEndGameConfirm(NULL); - } - else if ( multiplayer != SINGLE ) - { - buttonDisconnect(NULL); - } - - // close current window - if ( subwindow ) - { - if ( score_window ) - { - // reset class loadout - stats[0]->sex = static_cast(0); - stats[0]->appearance = 0; - stats[0]->playerRace = RACE_HUMAN; - strcpy(stats[0]->name, ""); - stats[0]->type = HUMAN; - client_classes[0] = 0; - stats[0]->clearStats(); - initClass(0); - } - score_window = 0; - gamemods_window = 0; - lobby_window = false; - settings_window = false; - charcreation_step = 0; - subwindow = 0; - if ( SDL_IsTextInputActive() ) - { - SDL_StopTextInput(); - } - } - list_FreeAll(&button_l); - deleteallbuttons = true; - - if ( lobbyToConnectTo ) - { - cpp_Free_CSteamID(lobbyToConnectTo); //TODO: Utter bodge. + if (lobbyToConnectTo) { + cpp_Free_CSteamID(lobbyToConnectTo); } lobbyToConnectTo = cpp_GameJoinRequested_m_steamIDLobby(pCallback); - processLobbyInvite(); + MainMenu::receivedInvite(lobbyToConnectTo); } //Helper func. //TODO: Bugger. void cpp_SteamMatchmaking_JoinLobbyPCH(const char* pchLobbyID) { - CSteamID steamIDLobby( (uint64)atoll( pchLobbyID ) ); - if ( steamIDLobby.IsValid() ) - { - SteamAPICall_t steamAPICall = SteamMatchmaking()->JoinLobby(steamIDLobby); - steam_server_client_wrapper->m_SteamCallResultLobbyEntered_Set(steamAPICall); + CSteamID steamIDLobby(std::stoull(std::string(pchLobbyID))); + if (steamIDLobby.IsValid()) { + MainMenu::receivedInvite(&steamIDLobby); + } else { + printlog("lobby id for invite invalid"); } } -// searches (char pchCmdLine[]) for a connect lobby command -void steam_ConnectToLobby() +// checks command line arg for a connect lobby command +void steam_ConnectToLobby(const char* arg) { #ifdef STEAMDEBUG printlog( "ConnectToLobby\n" ); #endif + printlog(arg); // parse out the connect char pchLobbyID[1024] = ""; // look for +connect_lobby command - const char* pchConnectLobbyParam = "+connect_lobby"; - const char* pchConnectLobby = strstr( pchCmdLine, pchConnectLobbyParam ); - if ( pchConnectLobby ) - { + const char pchConnectLobbyParam[] = "+connect_lobby"; + const char* pchConnectLobby = strstr(arg, pchConnectLobbyParam); + if (pchConnectLobby) { // address should be right after the +connect_lobby, +1 on the end to skip the space - strcpy( pchLobbyID, (char*)(pchConnectLobby + strlen(pchConnectLobbyParam) + 1 )); + snprintf(pchLobbyID, sizeof(pchLobbyID), "%s", (char*)(pchConnectLobby + sizeof(pchConnectLobbyParam))); } // join lobby - if ( pchLobbyID[0] ) - { - //c_SteamMatchmaking_JoinLobbyPCH( pchLobbyID, &steam_OnLobbyEntered ); - cpp_SteamMatchmaking_JoinLobbyPCH( pchLobbyID); + if (pchLobbyID[0]) { + printlog(pchLobbyID); + cpp_SteamMatchmaking_JoinLobbyPCH(pchLobbyID); } } @@ -1910,7 +1860,6 @@ void steam_OnLobbyEntered( void* pCallback, bool bIOFailure ) case k_EChatRoomEnterResponseRatelimitExceeded: connectingToLobbyStatus = LobbyHandler_t::EResult_LobbyFailures::LOBBY_TOO_MANY_JOINS; break; // Join failed - to many join attempts in a very short period of time default: connectingToLobbyStatus = LobbyHandler_t::EResult_LobbyFailures::LOBBY_UNHANDLED_ERROR; break; } - //openFailedConnectionWindow(CLIENT); return; } diff --git a/src/steam.hpp b/src/steam.hpp index 191ab8442..509b4e7c5 100644 --- a/src/steam.hpp +++ b/src/steam.hpp @@ -21,7 +21,7 @@ void steam_OnLobbyDataUpdatedCallback(void* pCallback); void steam_OnLobbyCreated(void* pCallback, bool bIOFailure); void processLobbyInvite(); void steam_OnGameJoinRequested(void* pCallback); -void steam_ConnectToLobby(); +void steam_ConnectToLobby(const char* arg); void steam_OnLobbyEntered(void* pCallback, bool bIOFailure); void steam_GameServerPingOnServerResponded(void* steamID); void steam_OnP2PSessionConnectFail(void* pCallback); @@ -39,10 +39,12 @@ extern void* steamIDRemote[MAXPLAYERS]; //TODO: Bugger void pointer. extern bool requestingLobbies; +#include + extern bool serverLoadingSaveGame; // determines whether lobbyToConnectTo is loading a savegame or not extern void* currentLobby; // CSteamID to the current game lobby extern void* lobbyToConnectTo; // CSteamID of the game lobby that user has been invited to -extern char pchCmdLine[1024]; // for game join requests +extern std::string cmd_line; // for game join requests #ifdef STEAMWORKS extern char currentLobbyName[32]; extern ELobbyType currentLobbyType; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index b00023e35..e1a0547cb 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -107,6 +107,7 @@ namespace MainMenu { static LobbyType currentLobbyType = LobbyType::None; static bool playersInLobby[MAXPLAYERS]; static bool playerSlotsLocked[MAXPLAYERS]; + static void* saved_invite_lobby = nullptr; bool story_active = false; bool isCutsceneActive() { @@ -380,11 +381,6 @@ namespace MainMenu { connectingToLobby = false; connectingToLobbyWindow = false; joinLobbyWaitingForHostResponse = false; - if (lobbyToConnectTo) { - // cancel lobby invitation acceptance - cpp_Free_CSteamID(lobbyToConnectTo); - lobbyToConnectTo = nullptr; - } #endif #ifdef USE_EOS EOS.bRequestingLobbies = false; @@ -394,6 +390,64 @@ namespace MainMenu { #endif } + static void flushP2PPackets(int msMin, int msMax) { + if (!directConnect) { + if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { +#ifdef STEAMWORKS + CSteamID newSteamID; + + // if we got a packet, flush any remaining packets from the queue. + Uint32 startTicks = SDL_GetTicks(); + Uint32 checkTicks = startTicks; + while ((checkTicks - startTicks) < msMin) { + SteamAPI_RunCallbacks(); + Uint32 packetlen = 0; + if (SteamNetworking()->IsP2PPacketAvailable(&packetlen, 0)) { + packetlen = std::min(packetlen, NET_PACKET_SIZE - 1); + Uint32 bytesRead = 0; + char buffer[NET_PACKET_SIZE]; + if (SteamNetworking()->ReadP2PPacket(buffer, packetlen, &bytesRead, &newSteamID, 0)) { + checkTicks = SDL_GetTicks(); // found a packet, extend the wait time. + } + buffer[4] = '\0'; + if ( (int)buffer[3] < '0' + && (int)buffer[0] == 0 + && (int)buffer[1] == 0 + && (int)buffer[2] == 0 ) { + printlog("[Steam Lobby]: Clearing P2P packet queue: received: %d", (int)buffer[3]); + } else { + printlog("[Steam Lobby]: Clearing P2P packet queue: received: %s", buffer); + } + } + SDL_Delay(10); + if ((SDL_GetTicks() - startTicks) > msMax) { + break; + } + } +#endif + } + else if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { +#if defined USE_EOS + EOS_ProductUserId newRemoteProductId = nullptr; + + // if we got a packet, flush any remaining packets from the queue. + Uint32 startTicks = SDL_GetTicks(); + Uint32 checkTicks = startTicks; + while ((checkTicks - startTicks) < msMin) { + EOS_Platform_Tick(EOS.PlatformHandle); + if (EOS.HandleReceivedMessagesAndIgnore(&newRemoteProductId)) { + checkTicks = SDL_GetTicks(); // found a packet, extend the wait time. + } + SDL_Delay(10); + if ((SDL_GetTicks() - startTicks) > msMax) { + break; + } + } +#endif // USE_EOS + } + } + } + /******************************************************************************/ static void settingsUI(Button&); @@ -425,7 +479,7 @@ namespace MainMenu { static void mainQuitToMainMenu(Button&); static void mainQuitToDesktop(Button&); - static void characterCardGameSettingsMenu(int index); + static void characterCardGameFlagsMenu(int index); static void characterCardLobbySettingsMenu(int index); static void characterCardRaceMenu(int index, bool details, int selection); static void characterCardClassMenu(int index, bool details, int selection); @@ -994,10 +1048,49 @@ namespace MainMenu { closePrompt("binary_prompt"); } - static Frame* monoPrompt( + static Frame* cancellablePrompt( + const char* name, const char* window_text, - const char* okay_text, - void (*okay_callback)(Button&) + const char* cancel_text, + void (*tick_callback)(Widget&), + void (*cancel_callback)(Button&) + ) { + soundActivate(); + + Frame* frame = createPrompt(name); + if (!frame) { + return nullptr; + } + + auto text = frame->addField("text", 128); + text->setSize(SDL_Rect{30, 16, frame->getSize().w - 60, 64}); + text->setFont(smallfont_no_outline); + text->setText(window_text); + text->setJustify(Field::justify_t::CENTER); + text->setHideSelectors(true); + text->setHideGlyphs(true); + text->setTickCallback(tick_callback); + + auto cancel = frame->addButton("cancel"); + cancel->setSize(SDL_Rect{(frame->getActualSize().w - 130) / 2, 82, 130, 52}); + cancel->setBackground("*images/ui/Main Menus/Disconnect/UI_Disconnect_Button_Abandon00.png"); + cancel->setColor(makeColor(255, 255, 255, 255)); + cancel->setHighlightColor(makeColor(255, 255, 255, 255)); + cancel->setTextColor(makeColor(255, 255, 255, 255)); + cancel->setTextHighlightColor(makeColor(255, 255, 255, 255)); + cancel->setFont(smallfont_outline); + cancel->setText(cancel_text); + cancel->setCallback(cancel_callback); + cancel->setWidgetBack("cancel"); + cancel->select(); + + return frame; + } + + static Frame* monoPrompt( + const char* window_text, + const char* okay_text, + void (*okay_callback)(Button&) ) { soundActivate(); @@ -1057,8 +1150,8 @@ namespace MainMenu { return frame; } - static void closeText(const char* name = "text_prompt") { - closePrompt(name); + static void closeText() { + closePrompt("text_prompt"); } void connectionErrorPrompt(const char* str) { @@ -6641,7 +6734,7 @@ namespace MainMenu { auto error_code = static_cast(LobbyHandler_t::LOBBY_JOIN_TIMEOUT); auto error_str = LobbyHandler_t::getLobbyJoinFailedConnectString(error_code); disconnectFromLobby(); - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt(error_str.c_str()); connectingToLobbyStatus = EResult::k_EResultOK; } @@ -6655,7 +6748,7 @@ namespace MainMenu { auto error_code = static_cast(LobbyHandler_t::LOBBY_JOIN_TIMEOUT); auto error_str = LobbyHandler_t::getLobbyJoinFailedConnectString(error_code); disconnectFromLobby(); - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt(error_str.c_str()); EOS.ConnectingToLobbyStatus = static_cast(EOS_EResult::EOS_Success); } @@ -6733,58 +6826,7 @@ namespace MainMenu { clientnum = 0; printlog("connection attempt denied by server, error code: %d.\n", error); multiplayer = SINGLE; - if (!directConnect) { - if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { -#ifdef STEAMWORKS - // if we got a packet, flush any remaining packets from the queue. - Uint32 startTicks = SDL_GetTicks(); - Uint32 checkTicks = startTicks; - while ((checkTicks - startTicks) < 2000) { - SteamAPI_RunCallbacks(); - Uint32 packetlen = 0; - if (SteamNetworking()->IsP2PPacketAvailable(&packetlen, 0)) { - packetlen = std::min(packetlen, NET_PACKET_SIZE - 1); - Uint32 bytesRead = 0; - char buffer[NET_PACKET_SIZE]; - if (SteamNetworking()->ReadP2PPacket(buffer, packetlen, &bytesRead, &newSteamID, 0)) { - checkTicks = SDL_GetTicks(); // found a packet, extend the wait time. - } - buffer[4] = '\0'; - if ( (int)buffer[3] < '0' - && (int)buffer[0] == 0 - && (int)buffer[1] == 0 - && (int)buffer[2] == 0 ) { - printlog("[Steam Lobby]: Clearing P2P packet queue: received: %d", (int)buffer[3]); - } else { - printlog("[Steam Lobby]: Clearing P2P packet queue: received: %s", buffer); - } - } - SDL_Delay(10); - if ((SDL_GetTicks() - startTicks) > 5000) { - // hard break at 3 seconds. - break; - } - } -#endif - } else if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { -#if defined USE_EOS - // if we got a packet, flush any remaining packets from the queue. - Uint32 startTicks = SDL_GetTicks(); - Uint32 checkTicks = startTicks; - while ((checkTicks - startTicks) < 2000) { - EOS_Platform_Tick(EOS.PlatformHandle); - if (EOS.HandleReceivedMessagesAndIgnore(&newRemoteProductId)) { - checkTicks = SDL_GetTicks(); // found a packet, extend the wait time. - } - SDL_Delay(10); - if ((SDL_GetTicks() - startTicks) > 5000) { - // hard break at 3 seconds. - break; - } - } -#endif // USE_EOS - } - } + //flushP2PPackets(2000, 5000); #ifdef STEAMWORKS if (!directConnect) { @@ -6815,7 +6857,7 @@ namespace MainMenu { } // display error message - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt(error_str); // reset connection @@ -6846,7 +6888,7 @@ namespace MainMenu { } // open lobby - closeText("connect_prompt"); + closePrompt("connect_prompt"); createLobby(LobbyType::LobbyJoined); // TODO subscribe to mods! @@ -7246,8 +7288,8 @@ namespace MainMenu { sendPacket(net_sock, -1, net_packet, 0); } - static bool connectToServer(const char* address, LobbyType lobbyType) { - if (!address || address[0] == '\0') { + static bool connectToServer(const char* address, void* pLobby, LobbyType lobbyType) { + if ((!address || address[0] == '\0') && (!pLobby || lobbyType != LobbyType::LobbyOnline)) { soundError(); return false; } @@ -7256,7 +7298,7 @@ namespace MainMenu { client_keepalive[0] = ticks; // open wait prompt - textPrompt("connect_prompt", "", [](Widget& widget){ + cancellablePrompt("connect_prompt", "", "Cancel", [](Widget& widget){ char buf[256]; int diff = ticks - client_keepalive[0]; int part = diff % TICKS_PER_SECOND; @@ -7298,27 +7340,19 @@ namespace MainMenu { // record CSteamID of lobby owner (and everybody else) // shouldn't this be in steam.cpp? - int lobbyMembers = SteamMatchmaking()->GetNumLobbyMembers(*static_cast(::currentLobby)); for (int c = 0; c < MAXPLAYERS; ++c) { if (steamIDRemote[c]) { cpp_Free_CSteamID(steamIDRemote[c]); steamIDRemote[c] = NULL; } } + const int lobbyMembers = SteamMatchmaking()->GetNumLobbyMembers(*static_cast(::currentLobby)); for (int c = 0; c < lobbyMembers; ++c) { steamIDRemote[c] = cpp_SteamMatchmaking_GetLobbyMember(currentLobby, c); } } sendJoinRequest(); - } else { - resetLobbyJoinFlowState(); - - // close current window - auto frame = static_cast(widget.getParent()); - auto dimmer = static_cast(frame->getParent()); - dimmer->removeSelf(); - connectionErrorPrompt("Failed to join lobby."); } return; } @@ -7351,6 +7385,10 @@ namespace MainMenu { } #endif } + }, + [](Button&){ // cancel + disconnectFromLobby(); + closePrompt("connect_prompt"); }); // setup game state @@ -7369,44 +7407,64 @@ namespace MainMenu { // initialize connection if (lobbyType == LobbyType::LobbyOnline) { #ifdef STEAMWORKS - const char steam_str[] = "steam:"; - if (strncmp(address, steam_str, sizeof(steam_str) - 1) == 0) { - auto lobby = getLobbySteamID(address); - if (lobby) { - connectingToLobby = true; - connectingToLobbyWindow = true; - joinLobbyWaitingForHostResponse = true; - selectedSteamLobby = (int)strtol(address + 6, nullptr, 10); - LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); - LobbyHandler.steamValidateAndJoinLobby(*lobby); - return true; - } + { + CSteamID* lobby = nullptr; + if (address) { + const char steam_str[] = "steam:"; + if (strncmp(address, steam_str, sizeof(steam_str) - 1) == 0) { + lobby = getLobbySteamID(address); + } + } + else if (pLobby) { + lobby = static_cast(pLobby); + } + if (lobby) { + connectingToLobby = true; + connectingToLobbyWindow = true; + joinLobbyWaitingForHostResponse = true; + //selectedSteamLobby = (int)strtol(address + 6, nullptr, 10); + LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); + LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); + + flushP2PPackets(100, 200); + LobbyHandler.steamValidateAndJoinLobby(*lobby); + return true; + } } #endif #ifdef USE_EOS - const char epic_str[] = "epic:"; - if (strncmp(address, epic_str, sizeof(epic_str) - 1) == 0) { - auto lobby = getLobbyEpic(address); - if (lobby) { - EOS.bConnectingToLobby = true; - EOS.bConnectingToLobbyWindow = true; - EOS.bJoinLobbyWaitingForHostResponse = true; + { + EOSFuncs::LobbyData_t* lobby = nullptr; + if (address) { + const char epic_str[] = "epic:"; + if (strncmp(address, epic_str, sizeof(epic_str) - 1) == 0) { + lobby = getLobbyEpic(address); + } + } + else if (pLobby) { + lobby = static_cast(pLobby); + } + if (lobby) { + EOS.bConnectingToLobby = true; + EOS.bConnectingToLobbyWindow = true; + EOS.bJoinLobbyWaitingForHostResponse = true; LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); - strncpy(EOS.currentLobbyName, lobby->LobbyAttributes.lobbyName.c_str(), 31); - - // EOS.searchLobbies() nukes the lobby list, so we need to copy this. - std::string lobbyId = lobby->LobbyId.c_str(); - EOS.searchLobbies( - EOSFuncs::LobbyParameters_t::LobbySearchOptions::LOBBY_SEARCH_BY_LOBBYID, - EOSFuncs::LobbyParameters_t::LobbyJoinOptions::LOBBY_JOIN_FIRST_SEARCH_RESULT, - lobbyId.c_str()); - return true; - } - } + strncpy(EOS.currentLobbyName, lobby->LobbyAttributes.lobbyName.c_str(), 31); + + flushP2PPackets(100, 200); + + // EOS.searchLobbies() nukes the lobby list, so we need to copy this. + std::string lobbyId = lobby->LobbyId.c_str(); + EOS.searchLobbies( + EOSFuncs::LobbyParameters_t::LobbySearchOptions::LOBBY_SEARCH_BY_LOBBYID, + EOSFuncs::LobbyParameters_t::LobbyJoinOptions::LOBBY_JOIN_FIRST_SEARCH_RESULT, + lobbyId.c_str()); + return true; + } + } #endif - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt("Failed to connect to lobby.\nInvalid room code."); multiplayer = SINGLE; disconnectFromLobby(); @@ -7448,7 +7506,7 @@ namespace MainMenu { char buf[1024]; snprintf(buf, sizeof(buf), "Failed to resolve host at:\n%s", address); printlog(buf); - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt(buf); multiplayer = SINGLE; disconnectFromLobby(); @@ -7461,7 +7519,7 @@ namespace MainMenu { char buf[1024]; snprintf(buf, sizeof(buf), "Failed to open UDP socket."); printlog(buf); - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt(buf); multiplayer = SINGLE; disconnectFromLobby(); @@ -7474,7 +7532,7 @@ namespace MainMenu { } // connection initiation failed for unknown reason - closeText("connect_prompt"); + closePrompt("connect_prompt"); connectionErrorPrompt("Failed to connect to lobby"); multiplayer = SINGLE; disconnectFromLobby(); @@ -8168,7 +8226,7 @@ namespace MainMenu { return card; } - static void characterCardGameSettingsMenu(int index) { + static void characterCardGameFlagsMenu(int index) { bool local = currentLobbyType == LobbyType::LobbyLocal; auto card = initCharacterCard(index, 664); @@ -8339,6 +8397,176 @@ namespace MainMenu { } static void characterCardLobbySettingsMenu(int index) { + bool local = currentLobbyType != LobbyType::LobbyOnline; + + auto card = initCharacterCard(index, 424); + + static void (*back_fn)(int) = [](int index){ + createCharacterCard(index); + auto lobby = main_menu_frame->findFrame("lobby"); assert(lobby); + auto card = lobby->findFrame((std::string("card") + std::to_string(index)).c_str()); assert(card); + auto button = card->findButton("game_settings"); assert(button); + button->select(); + }; + + switch (index) { + case 0: (void)createBackWidget(card,[](Button&){soundCancel(); back_fn(0);}); break; + case 1: (void)createBackWidget(card,[](Button&){soundCancel(); back_fn(1);}); break; + case 2: (void)createBackWidget(card,[](Button&){soundCancel(); back_fn(2);}); break; + case 3: (void)createBackWidget(card,[](Button&){soundCancel(); back_fn(3);}); break; + } + + auto backdrop = card->addImage( + card->getActualSize(), + 0xffffffff, + "*images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Window00.png", + "backdrop" + ); + + auto header = card->addField("header", 64); + header->setSize(SDL_Rect{30, 8, 264, 50}); + header->setFont(smallfont_outline); + header->setText("LOBBY SETTINGS"); + header->setJustify(Field::justify_t::CENTER); + + auto custom_difficulty = card->addButton("custom_difficulty"); + custom_difficulty->setColor(makeColor(255, 255, 255, 255)); + custom_difficulty->setHighlightColor(makeColor(255, 255, 255, 255)); + custom_difficulty->setSize(SDL_Rect{102, 68, 120, 48}); + custom_difficulty->setBackground("*images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Customize00A.png"); + custom_difficulty->setFont(smallfont_outline); + custom_difficulty->setText("Game Flags"); + custom_difficulty->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + custom_difficulty->addWidgetAction("MenuStart", "confirm"); + custom_difficulty->setWidgetBack("back_button"); + custom_difficulty->setWidgetUp("hard"); + custom_difficulty->setWidgetDown("invite"); + custom_difficulty->setWidgetRight("custom"); + switch (index) { + case 0: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(0);}); break; + case 1: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(1);}); break; + case 2: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(2);}); break; + case 3: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(3);}); break; + } + + auto invite_label = card->addField("invite_label", 64); + invite_label->setSize(SDL_Rect{82, 146, 122, 26}); + invite_label->setFont(smallfont_outline); + invite_label->setText("Invite Only"); + invite_label->setJustify(Field::justify_t::CENTER); + if (local) { + invite_label->setColor(makeColor(70, 62, 59, 255)); + + auto invite = card->addImage( + SDL_Rect{204, 146, 26, 26}, + 0xffffffff, + "*images/ui/Main Menus/sublist_item-locked.png", + "invite" + ); + } else { + invite_label->setColor(makeColor(166, 123, 81, 255)); + + auto invite = card->addButton("invite"); + invite->setSize(SDL_Rect{204, 146, 30, 30}); + invite->setBackground("*images/ui/Main Menus/sublist_item-unpicked.png"); + invite->setIcon("*images/ui/Main Menus/sublist_item-picked.png"); + invite->setStyle(Button::style_t::STYLE_RADIO); + invite->setBorder(0); + invite->setColor(0); + invite->setBorderColor(0); + invite->setHighlightColor(0); + invite->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + invite->addWidgetAction("MenuStart", "confirm"); + invite->setWidgetBack("back_button"); + invite->setWidgetUp("custom"); + invite->setWidgetDown("friends"); + if (index != 0) { + invite->setCallback([](Button&){soundError();}); + } else { + } + } + + auto friends_label = card->addField("friends_label", 64); + friends_label->setSize(SDL_Rect{82, 178, 122, 26}); + friends_label->setFont(smallfont_outline); + friends_label->setText("Friends Only"); + friends_label->setJustify(Field::justify_t::CENTER); + if (local) { + friends_label->setColor(makeColor(70, 62, 59, 255)); + + auto friends = card->addImage( + SDL_Rect{204, 178, 26, 26}, + 0xffffffff, + "*images/ui/Main Menus/sublist_item-locked.png", + "friends" + ); + } else { + friends_label->setColor(makeColor(166, 123, 81, 255)); + + auto friends = card->addButton("friends"); + friends->setSize(SDL_Rect{202, 176, 30, 30}); + friends->setBackground("*images/ui/Main Menus/sublist_item-unpicked.png"); + friends->setIcon("*images/ui/Main Menus/sublist_item-picked.png"); + friends->setStyle(Button::style_t::STYLE_RADIO); + friends->setBorder(0); + friends->setColor(0); + friends->setBorderColor(0); + friends->setHighlightColor(0); + friends->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + friends->addWidgetAction("MenuStart", "confirm"); + friends->setWidgetBack("back_button"); + friends->setWidgetUp("invite"); + friends->setWidgetDown("open"); + if (index != 0) { + friends->setCallback([](Button&){soundError();}); + } else { + } + } + + auto open_label = card->addField("open_label", 64); + open_label->setSize(SDL_Rect{82, 210, 122, 26}); + open_label->setFont(smallfont_outline); + open_label->setText("Open Lobby"); + open_label->setJustify(Field::justify_t::CENTER); + if (local) { + open_label->setColor(makeColor(70, 62, 59, 255)); + + auto open = card->addImage( + SDL_Rect{204, 210, 26, 26}, + 0xffffffff, + "*images/ui/Main Menus/sublist_item-locked.png", + "open" + ); + } else { + open_label->setColor(makeColor(166, 123, 81, 255)); + + auto open = card->addButton("open"); + open->setSize(SDL_Rect{202, 208, 30, 30}); + open->setBackground("*images/ui/Main Menus/sublist_item-unpicked.png"); + open->setIcon("*images/ui/Main Menus/sublist_item-picked.png"); + open->setStyle(Button::style_t::STYLE_RADIO); + open->setBorder(0); + open->setColor(0); + open->setBorderColor(0); + open->setHighlightColor(0); + open->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + open->addWidgetAction("MenuStart", "confirm"); + open->setWidgetBack("back_button"); + open->setWidgetUp("friends"); + open->setWidgetDown("confirm"); + if (index != 0) { + open->setCallback([](Button&){soundError();}); + } else { + } + } + } + + static void characterCardLobbySettingsMenuOLD(int index) { + /* + * NOTE: This is the old lobby settings menu that includes + * Difficulty options. It is disabled for now! + */ + bool local = currentLobbyType == LobbyType::LobbyLocal; auto card = initCharacterCard(index, 580); @@ -8514,10 +8742,10 @@ namespace MainMenu { custom_difficulty->setWidgetDown("invite"); custom_difficulty->setWidgetRight("custom"); switch (index) { - case 0: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameSettingsMenu(0);}); break; - case 1: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameSettingsMenu(1);}); break; - case 2: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameSettingsMenu(2);}); break; - case 3: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameSettingsMenu(3);}); break; + case 0: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(0);}); break; + case 1: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(1);}); break; + case 2: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(2);}); break; + case 3: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(3);}); break; } auto custom = card->addButton("custom"); @@ -9734,7 +9962,7 @@ namespace MainMenu { case 1: lines[c] = "*"; field.addColorToLine(c, c == 0 ? bad : good); break; case 2: lines[c] = "**"; field.addColorToLine(c, c == 0 ? poor : decent); break; case 3: lines[c] = "***"; field.addColorToLine(c, c == 0 ? average : average); break; - case 4: lines[c] = "****"; field.addColorToLine(c, c == 0 ? decent : decent); break; + case 4: lines[c] = "****"; field.addColorToLine(c, c == 0 ? decent : poor); break; case 5: lines[c] = "*****"; field.addColorToLine(c, c == 0 ? good : bad); break; } } @@ -11896,7 +12124,7 @@ namespace MainMenu { { #endif // lobby list has returned - closeText("lobby_list_request"); + closePrompt("lobby_list_request"); #if defined(STEAMWORKS) for (Uint32 c = 0; c < numSteamLobbies; ++c) { @@ -12390,10 +12618,10 @@ namespace MainMenu { const char* address = closeTextField(); // only valid for one frame if (guide == ipaddr) { soundActivate(); - (void)connectToServer(address, LobbyType::LobbyLAN); + (void)connectToServer(address, nullptr, LobbyType::LobbyLAN); } else if (guide == roomcode) { soundActivate(); - (void)connectToServer(address, LobbyType::LobbyOnline); + (void)connectToServer(address, nullptr, LobbyType::LobbyOnline); } else { soundError(); } @@ -12438,7 +12666,7 @@ namespace MainMenu { if (selectedLobby >= 0 && selectedLobby < lobbies.size()) { const auto& lobby = lobbies[selectedLobby]; if (!lobby.locked) { - if (connectToServer(lobby.address.c_str(), + if (connectToServer(lobby.address.c_str(), nullptr, directConnect ? LobbyType::LobbyLAN : LobbyType::LobbyOnline)) { // we only want to deselect the button if the // "connecting to server" prompt actually raises @@ -14906,6 +15134,10 @@ namespace MainMenu { const int music = RNG.uniform(0, NUMINTROMUSIC - 2); playMusic(intromusic[music], true, false, false); createMainMenu(false); + if (saved_invite_lobby) { + connectToServer(nullptr, saved_invite_lobby, LobbyType::LobbyOnline); + saved_invite_lobby = nullptr; + } } else if (main_menu_fade_destination == FadeDestination::Victory) { doEndgame(); @@ -15133,6 +15365,10 @@ namespace MainMenu { resolution_changed = false; } + if (fadeout && fadealpha >= 255) { + handleFadeFinished(ingame); + } + if (!main_menu_frame) { if (ingame) { if (movie) { @@ -15179,10 +15415,6 @@ namespace MainMenu { #endif } - if (fadeout && fadealpha >= 255) { - handleFadeFinished(ingame); - } - // if no controller is connected, you can always connect one just for the main menu. if (!ingame && currentLobbyType == LobbyType::None) { if (!inputs.hasController(getMenuOwner())) { @@ -15295,6 +15527,14 @@ namespace MainMenu { createMainMenu(false); soundActivate(); }); + +#ifdef STEAMWORKS + if (!cmd_line.empty()) { + printlog(cmd_line.c_str()); + steam_ConnectToLobby(cmd_line.c_str()); + cmd_line = ""; + } +#endif // STEAMWORKS } #ifdef STEAMWORKS @@ -15969,8 +16209,22 @@ namespace MainMenu { } } - void receivedInvite() { - assert(0 && "Received an invite. Behavior goes here!"); + void receivedInvite(void* lobby) { + if (intro) { + if (multiplayer) { + disconnectFromLobby(); + destroyMainMenu(); + createMainMenu(false); + } + connectToServer(nullptr, lobby, LobbyType::LobbyOnline); + } else { + saved_invite_lobby = lobby; + disconnectFromLobby(); + destroyMainMenu(); + createDummyMainMenu(); + beginFade(FadeDestination::RootMainMenu); + pauseGame(2, 0); + } } bool isPlayerSignedIn(int index) { diff --git a/src/ui/MainMenu.hpp b/src/ui/MainMenu.hpp index 11ff20ff7..eed518101 100644 --- a/src/ui/MainMenu.hpp +++ b/src/ui/MainMenu.hpp @@ -89,7 +89,7 @@ namespace MainMenu { void openGameoverWindow(int player, bool tutorial = false); void connectionErrorPrompt(const char* str); void disconnectedFromServer(const char* text); - void receivedInvite(); + void receivedInvite(void*); void handleScanPacket(); void setupSplitscreen(); // resizes player game views } From e60b036f07acfcbf690721f23f03716be1997286 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 19 Jul 2022 02:09:11 -0700 Subject: [PATCH 10/21] fix compile error w/out steamworks --- src/game.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game.cpp b/src/game.cpp index 813df9449..33676fee1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5826,8 +5826,10 @@ int main(int argc, char** argv) { for (c = 1; c < argc; c++) { +#ifdef STEAMWORKS cmd_line += argv[c]; cmd_line += " "; +#endif if ( argv[c] != NULL ) { if ( !strcmp(argv[c], "-windowed") ) From 40024223f5ce8ea38f5ac93b6aa109193dcd427f Mon Sep 17 00:00:00 2001 From: SheridanR Date: Wed, 20 Jul 2022 01:20:46 -0700 Subject: [PATCH 11/21] roomcodes, player account names in lobby, bugfixes --- src/eos.cpp | 31 +++++- src/eos.hpp | 1 + src/lobbies.cpp | 16 +-- src/menu.cpp | 2 +- src/player.cpp | 54 +++++++++++ src/player.hpp | 2 + src/steam.cpp | 54 +++++++++-- src/steam.hpp | 2 +- src/ui/MainMenu.cpp | 231 +++++++++++++++++++++++++++++++------------- 9 files changed, 306 insertions(+), 87 deletions(-) diff --git a/src/eos.cpp b/src/eos.cpp index 543166f48..6abd42a09 100644 --- a/src/eos.cpp +++ b/src/eos.cpp @@ -27,6 +27,7 @@ #include "interface/ui.hpp" #include "lobbies.hpp" #include "prng.hpp" +#include "ui/MainMenu.hpp" EOSFuncs EOS; @@ -400,14 +401,16 @@ void EOS_CALL EOSFuncs::OnCreateLobbyFinished(const EOS_Lobby_CreateLobbyCallbac EOSFuncs::logError("OnCreateLobbyFinished: null data"); return; } - else if ( data->ResultCode == EOS_EResult::EOS_Success ) + + EOS.CurrentLobbyData.LobbyCreationResult = data->ResultCode; + if ( data->ResultCode == EOS_EResult::EOS_Success ) { EOS.CurrentLobbyData.LobbyId = data->LobbyId; EOS.CurrentLobbyData.LobbyAttributes.lobbyName = EOS.CurrentUserInfo.Name + "'s lobby"; strncpy(EOS.currentLobbyName, EOS.CurrentLobbyData.LobbyAttributes.lobbyName.c_str(), 31); - Uint32 keygen = local_rng.rand() % (1679615 + 1); // limit of 'zzzz' as base-36 string + Uint32 keygen = local_rng.uniform(0, (36 * 36 * 36 * 36) - 1); // limit of 'zzzz' as base-36 string EOS.CurrentLobbyData.LobbyAttributes.gameJoinKey = EOS.getLobbyCodeFromGameKey(keygen); std::chrono::system_clock::duration epochDuration = std::chrono::system_clock::now().time_since_epoch(); EOS.CurrentLobbyData.LobbyAttributes.lobbyCreationTime = std::chrono::duration_cast(epochDuration).count(); @@ -895,11 +898,21 @@ void EOS_CALL EOSFuncs::OnMemberStatusReceived(const EOS_Lobby_LobbyMemberStatus { if ( data->CurrentStatus == EOS_ELobbyMemberStatus::EOS_LMS_CLOSED || (data->CurrentStatus == EOS_ELobbyMemberStatus::EOS_LMS_KICKED - && (data->TargetUserId == EOS.CurrentUserInfo.getProductUserIdHandle())) + && data->TargetUserId == EOS.CurrentUserInfo.getProductUserIdHandle()) ) { // if lobby closed or we got kicked, then clear data. LobbyLeaveCleanup(EOS.CurrentLobbyData); + switch (data->CurrentStatus) { + case EOS_ELobbyMemberStatus::EOS_LMS_CLOSED: + //MainMenu::disconnectedFromServer("The host has shutdown the lobby."); + break; + case EOS_ELobbyMemberStatus::EOS_LMS_KICKED: + //MainMenu::disconnectedFromServer("You have been kicked\nfrom the online lobby."); + break; + default: + break; + } } else { @@ -907,7 +920,6 @@ void EOS_CALL EOSFuncs::OnMemberStatusReceived(const EOS_Lobby_LobbyMemberStatus EOSFuncs::logInfo("OnMemberStatusReceived: received user: %s, event: %d, updating lobby", EOSFuncs::Helpers_t::shortProductIdToString(data->TargetUserId).c_str(), static_cast(data->CurrentStatus)); - return; } } break; @@ -1817,6 +1829,17 @@ void EOSFuncs::searchLobbies(LobbyParameters_t::LobbySearchOptions searchType, { LobbySearchResults.lastResultWasFiltered = false; + if ( LobbySearchResults.useLobbyCode ) + { + for ( int c = 0; c < 4 && EOS.lobbySearchByCode[c] != 0; ++c ) + { + if ( EOS.lobbySearchByCode[c] >= 'A' && EOS.lobbySearchByCode[c] <= 'Z' ) + { + EOS.lobbySearchByCode[c] = 'a' + (EOS.lobbySearchByCode[c] - 'A'); // to lowercase. + } + } + } + LobbyHandle = EOS_Platform_GetLobbyInterface(PlatformHandle); logInfo("searchLobbies: starting search"); EOS_Lobby_CreateLobbySearchOptions CreateSearchOptions = {}; diff --git a/src/eos.hpp b/src/eos.hpp index 06c322254..35fb72487 100644 --- a/src/eos.hpp +++ b/src/eos.hpp @@ -276,6 +276,7 @@ class EOSFuncs bool bLobbyHasBasicDetailsRead = false; bool bAwaitingLeaveCallback = false; bool bAwaitingCreationCallback = false; + EOS_EResult LobbyCreationResult = EOS_EResult::EOS_Success; bool bDenyLobbyJoinEvent = false; class PlayerLobbyData_t { diff --git a/src/lobbies.cpp b/src/lobbies.cpp index b9f43884b..40034564f 100644 --- a/src/lobbies.cpp +++ b/src/lobbies.cpp @@ -61,6 +61,15 @@ std::string LobbyHandler_t::getLobbyJoinFailedConnectString(int result) snprintf(buf, 1023, "Failed to join lobby:\n\nLobby is full."); break; #ifdef USE_EOS +#ifdef STEAMWORKS + case static_cast(EOS_EResult::EOS_InvalidUser): + snprintf(buf, 1023, "Failed to join lobby:\n\nCrossplay not enabled."); + break; +#else + case static_cast(EOS_EResult::EOS_InvalidUser): + snprintf(buf, 1023, "Failed to join lobby:\n\nNot connected to Epic Online."); + break; +#endif case static_cast(EOS_EResult::EOS_NotFound): snprintf(buf, 1023, "Failed to join lobby:\n\nLobby no longer exists."); break; @@ -713,13 +722,6 @@ void LobbyHandler_t::searchLobbyWithFilter(button_t* my) { #ifdef USE_EOS EOS.LobbySearchResults.showLobbiesInProgress = LobbyHandler.filterShowInProgressLobbies; - for ( int c = 0; c < 4 && EOS.lobbySearchByCode[c] != 0; ++c ) - { - if ( EOS.lobbySearchByCode[c] >= 'A' && EOS.lobbySearchByCode[c] <= 'Z' ) - { - EOS.lobbySearchByCode[c] = 'a' + (EOS.lobbySearchByCode[c] - 'A'); // to lowercase. - } - } if ( strcmp(EOS.lobbySearchByCode, "") != 0 ) { diff --git a/src/menu.cpp b/src/menu.cpp index aa21b9e53..e620cde9c 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -11278,7 +11278,7 @@ void openSteamLobbyWaitWindow(button_t* my) #ifdef STEAMWORKS //c_SteamMatchmaking_RequestLobbyList(); //SteamMatchmaking()->RequestLobbyList(); //TODO: Is this sufficient for it to work? - cpp_SteamMatchmaking_RequestLobbyList(); + //cpp_SteamMatchmaking_RequestLobbyList(); #endif LobbyHandler.selectedLobbyInList = 0; diff --git a/src/player.cpp b/src/player.cpp index 340c43f39..a7a0820c5 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -23,6 +23,7 @@ #include "ui/GameUI.hpp" #include "ui/Frame.hpp" #include "ui/Slider.hpp" +#include "lobbies.hpp" #ifdef NINTENDO #include "nintendo/baronynx.hpp" @@ -5537,3 +5538,56 @@ void Player::clearGUIPointers() } skillSheetEntryFrames[playernum].legendFrame = nullptr; } + +const char* Player::getAccountName() const { + if (directConnect) { + switch (playernum) { + case 0: return "Player 1"; + case 1: return "Player 2"; + case 2: return "Player 3"; + case 3: return "Player 4"; + default: return "Player X"; + } + } else { + if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { +#ifdef STEAMWORKS + if (isLocalPlayer()) { + return SteamFriends()->GetPersonaName(); + } else { + for (int remoteIDIndex = 0; remoteIDIndex < MAXPLAYERS; ++remoteIDIndex) { + if (steamIDRemote[remoteIDIndex]) { + const char* memberNumChar = SteamMatchmaking()->GetLobbyMemberData( + *static_cast(currentLobby), + *static_cast(steamIDRemote[remoteIDIndex]), + "clientnum"); + if (memberNumChar) { + std::string str = memberNumChar; + if (!str.empty()) { + int memberNum = std::stoi(str); + if (memberNum >= 0 && memberNum < MAXPLAYERS && memberNum == playernum) { + return SteamFriends()->GetFriendPersonaName( + *static_cast(steamIDRemote[remoteIDIndex])); + } + } + } + } + } + } +#endif + } + else if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { +#if defined USE_EOS + if (isLocalPlayer()) { + return EOS.CurrentUserInfo.Name.c_str(); + } else { + for (auto& player : EOS.CurrentLobbyData.playersInLobby) { + if (player.clientNumber == playernum) { + return player.name.c_str(); + } + } + } +#endif + } + } + return "Unknown"; +} \ No newline at end of file diff --git a/src/player.hpp b/src/player.hpp index 79275e8e4..569e2c9fa 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -616,6 +616,8 @@ class Player void init(); void cleanUpOnEntityRemoval(); + const char* getAccountName() const; + view_t& camera() const { return *cam; } const int camera_x1() const { return cam->winx; } const int camera_x2() const { return cam->winx + cam->winw; } diff --git a/src/steam.cpp b/src/steam.cpp index a6d531127..4ee668952 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -35,6 +35,7 @@ #ifdef STEAMWORKS +static std::string roomkey_cached; Uint32 numSteamLobbies = 0; int selectedSteamLobby = 0; char lobbyText[MAX_STEAM_LOBBIES][64]; @@ -679,12 +680,19 @@ SteamAPICall_t cpp_SteamMatchmaking_RequestAppTicket() return m_SteamCallResultEncryptedAppTicket; } -SteamAPICall_t cpp_SteamMatchmaking_RequestLobbyList() +SteamAPICall_t cpp_SteamMatchmaking_RequestLobbyList(const char* roomkey) { + if (roomkey) { + SteamMatchmaking()->AddRequestLobbyListStringFilter("roomkey", roomkey, ELobbyComparison::k_ELobbyComparisonEqual); + roomkey_cached = roomkey; + } else { + roomkey_cached = ""; + } SteamMatchmaking()->AddRequestLobbyListDistanceFilter(ELobbyDistanceFilter::k_ELobbyDistanceFilterWorldwide); SteamMatchmaking()->AddRequestLobbyListNearValueFilter("lobbyCreationTime", SteamUtils()->GetServerRealTime()); - SteamMatchmaking()->AddRequestLobbyListNumericalFilter("lobbyModifiedTime", - SteamUtils()->GetServerRealTime() - 8, k_ELobbyComparisonEqualToOrGreaterThan); + auto realtime = SteamUtils()->GetServerRealTime(); + SteamMatchmaking()->AddRequestLobbyListNumericalFilter("lobbyModifiedTime", + realtime - 8, k_ELobbyComparisonEqualToOrGreaterThan); SteamAPICall_t m_SteamCallResultLobbyMatchList = SteamMatchmaking()->RequestLobbyList(); steam_server_client_wrapper->m_SteamCallResultLobbyMatchList_Set(m_SteamCallResultLobbyMatchList); return m_SteamCallResultLobbyMatchList; @@ -1397,6 +1405,7 @@ void steam_OnLobbyMatchListCallback( void* pCallback, bool bIOFailure ) { // we had a Steam I/O failure - we probably timed out talking to the Steam back-end servers // doesn't matter in this case, we can just act if no lobbies were received + printlog("steam_OnLobbyMatchListCallback() failed - are we disconnected from steam?"); } // lobbies are returned in order of closeness to the user, so add them to the list in that order @@ -1409,10 +1418,12 @@ void steam_OnLobbyMatchListCallback( void* pCallback, bool bIOFailure ) lobbyIDs[iLobby] = steamIDLobby; // pull some info from the lobby metadata (name, players, etc) - const char* lobbyName = SteamMatchmaking()->GetLobbyData(*static_cast(steamIDLobby), "name"); //TODO: Again with the void pointers. - const char* lobbyVersion = SteamMatchmaking()->GetLobbyData(*static_cast(steamIDLobby), "ver"); //TODO: VOID. - int numPlayers = SteamMatchmaking()->GetNumLobbyMembers(*static_cast(steamIDLobby)); //TODO MORE VOID POINTERS. - const char* lobbyNumMods = SteamMatchmaking()->GetLobbyData(*static_cast(steamIDLobby), "svNumMods"); //TODO: VOID. + auto lobby = *static_cast(steamIDLobby); + const char* lobbyTime = SteamMatchmaking()->GetLobbyData(lobby, "lobbyModifiedTime"); + const char* lobbyName = SteamMatchmaking()->GetLobbyData(lobby, "name"); + const char* lobbyVersion = SteamMatchmaking()->GetLobbyData(lobby, "ver"); + const int numPlayers = SteamMatchmaking()->GetNumLobbyMembers(lobby); + const char* lobbyNumMods = SteamMatchmaking()->GetLobbyData(lobby, "svNumMods"); int numMods = atoi(lobbyNumMods); string versionText = lobbyVersion; if ( versionText == "" ) @@ -1455,6 +1466,13 @@ void steam_OnLobbyMatchListCallback( void* pCallback, bool bIOFailure ) lobbyPlayers[iLobby] = 0; } } + if (!roomkey_cached.empty()) { + roomkey_cached = ""; + if (numSteamLobbies) { + multiplayer = SINGLE; + MainMenu::receivedInvite(lobbyIDs[0]); + } + } } //Helper func. //TODO: Bugger it! @@ -1532,6 +1550,22 @@ void* cpp_LobbyCreated_Lobby(void* pCallback) return id; } +static std::string generateRoomKey(Uint32 key) +{ + const char allChars[37] = "0123456789abcdefghijklmnopqrstuvwxyz"; + std::string code = ""; + while ( key != 0 ) + { + code += (allChars[key % 36]); + key /= 36; + } + while ( code.size() < 4 ) + { + code += '0'; + } + return code; +} + void steam_OnLobbyCreated( void* pCallback, bool bIOFailure ) { #ifdef STEAMDEBUG @@ -1555,6 +1589,12 @@ void steam_OnLobbyCreated( void* pCallback, bool bIOFailure ) // set the game version of the lobby SteamMatchmaking()->SetLobbyData(*lobby, "ver", VERSION); + // set room key + Uint32 keygen = local_rng.uniform(0, (36 * 36 * 36 * 36) - 1); // limit of 'zzzz' as base-36 string + auto key = generateRoomKey(keygen); + SteamMatchmaking()->SetLobbyData(*lobby, "roomkey", key.c_str()); + printlog("Steam room key is: %s", key.c_str()); + // set lobby server flags char svFlagsChar[16]; snprintf(svFlagsChar, 15, "%d", svFlags); diff --git a/src/steam.hpp b/src/steam.hpp index 509b4e7c5..b92f2a97a 100644 --- a/src/steam.hpp +++ b/src/steam.hpp @@ -60,7 +60,7 @@ extern int connectingToLobbyStatus; //These are all an utter bodge. //They should not exist, but potato. //TODO: Remove all of these wrappers and access the steam stuff directly. -SteamAPICall_t cpp_SteamMatchmaking_RequestLobbyList(); +SteamAPICall_t cpp_SteamMatchmaking_RequestLobbyList(const char* roomkey); SteamAPICall_t cpp_SteamMatchmaking_JoinLobby(CSteamID steamIDLobby); SteamAPICall_t cpp_SteamMatchmaking_CreateLobby(ELobbyType eLobbyType, int cMaxMembers); SteamAPICall_t cpp_SteamMatchmaking_RequestAppTicket(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index e1a0547cb..e5ec712ce 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -5754,6 +5754,7 @@ namespace MainMenu { const Uint32 seconds = time(NULL) / seconds_in_day; if (add_to_list) { + playSound(238, 64); lobby_chat_messages.emplace_back(LobbyChatMessage{seconds, color, msg}); if (lobby_chat_messages.size() > lobby_chat_max_messages) { lobby_chat_messages.pop_front(); @@ -5771,11 +5772,7 @@ namespace MainMenu { auto frame = lobby->findFrame("chat window"); if (!frame) { frame = toggleLobbyChatWindow(); - assert(frame); - } - - if (add_to_list) { - playSound(238, 64); + return; } const int w = frame->getSize().w; @@ -5785,7 +5782,7 @@ namespace MainMenu { auto subframe_size = subframe->getActualSize(); int y = subframe_size.h; - static ConsoleVariable timestamp_messages("/chat_timestamp", true); + static ConsoleVariable timestamp_messages("/chat_timestamp", false); char buf[1024]; const Uint32 hour = seconds / 3600; @@ -5807,8 +5804,9 @@ namespace MainMenu { field->setColor(color); field->setText(buf); - (void)snprintf(buf, sizeof(buf), "[%.2u:%.2u:%.2u]", hour, min, sec); - field->setTooltip(buf); + //char tooltip_buf[32]; + //(void)snprintf(tooltip_buf, sizeof(tooltip_buf), "[%.2u:%.2u:%.2u]", hour, min, sec); + //field->setTooltip(tooltip_buf); const int new_w = std::max(subframe_size.w, text_w + 8); @@ -5980,9 +5978,12 @@ namespace MainMenu { chat_buffer->setColor(makeColor(201, 162, 100, 255)); chat_buffer->setEditable(true); chat_buffer->setCallback([](Field& field){ - sendChatMessageOverNet(field.getText()); - field.setText(""); - field.activate(); + auto text = field.getText(); + if (text && *text) { + sendChatMessageOverNet(text); + field.setText(""); + field.activate(); + } }); chat_buffer->setTickCallback([](Widget& widget){ auto field = static_cast(&widget); @@ -6045,10 +6046,6 @@ namespace MainMenu { } static void disconnectFromLobby() { - if (multiplayer == SINGLE) { - return; - } - if (multiplayer == SERVER) { // send disconnect message to clients for (int c = 1; c < MAXPLAYERS; c++) { @@ -6140,12 +6137,16 @@ namespace MainMenu { net_packet->address.port = net_clients[c - 1].port; sendPacketSafe(net_sock, -1, net_packet, c - 1); } - char shortname[32] = { 0 }; - strncpy(shortname, stats[i]->name, 22); char buf[1024]; - snprintf(buf, sizeof(buf), language[1376], shortname); + snprintf(buf, sizeof(buf), "*** %s has timed out ***", players[i]->getAccountName()); addLobbyChatMessage(0xffffffff, buf); + + if (directConnect) { + createWaitingStone(i); + } else { + createInviteButton(i); + } continue; } } @@ -6172,14 +6173,18 @@ namespace MainMenu { } if (hostHasLostP2P) { -#if defined(STEAMWORKS) - auto error_code = connectingToLobbyStatus; -#elif defined(USE_EOS) - auto error_code = EOS.ConnectingToLobbyStatus; -#else - auto error_code = -1; // just so this compiles always -#endif - auto error_str = LobbyHandler_t::getLobbyJoinFailedConnectString(error_code); + int error_code = -1; + if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { +#ifdef USE_EOS + error_code = EOS.ConnectingToLobbyStatus; +#endif // USE_EOS + } + else if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { +#ifdef STEAMWORKS + error_code = connectingToLobbyStatus; +#endif //STEAMWORKS + } + auto error_str = LobbyHandler_t::getLobbyJoinFailedConnectString(error_code); disconnectFromLobby(); destroyMainMenu(); createMainMenu(false); @@ -6319,9 +6324,13 @@ namespace MainMenu { return; } + char fmt[1024]; + int len = snprintf(fmt, sizeof(fmt), "%s: %s", players[clientnum]->getAccountName(), msg); + len = std::min((int)sizeof(fmt), len); + strcpy((char*)net_packet->data, "CMSG"); - strcat((char*)(net_packet->data), msg); - net_packet->len = 4 + strlen(msg) + 1; + strcat((char*)(net_packet->data), fmt); + net_packet->len = 4 + len + 1; net_packet->data[net_packet->len - 1] = 0; // send packet @@ -6334,7 +6343,7 @@ namespace MainMenu { net_packet->address.port = net_clients[i - 1].port; sendPacketSafe(net_sock, -1, net_packet, i - 1); } - addLobbyChatMessage(0xffffffff, msg); + addLobbyChatMessage(0xffffffff, fmt); } else if (multiplayer == CLIENT) { net_packet->address.host = net_server.host; net_packet->address.port = net_server.port; @@ -6592,6 +6601,10 @@ namespace MainMenu { // finally, open a player card! if (playerNum >= 1 && playerNum < MAXPLAYERS) { createReadyStone(playerNum, false, false); + + char buf[1024]; + snprintf(buf, sizeof(buf), "*** %s has joined the game ***", players[playerNum]->getAccountName()); + addLobbyChatMessage(0xffffffff, buf); } continue; @@ -6681,12 +6694,16 @@ namespace MainMenu { sendPacketSafe(net_sock, -1, net_packet, c - 1); } - char shortname[32] = { 0 }; - strncpy(shortname, stats[player]->name, 22); - char buf[1024]; - snprintf(buf, sizeof(buf), language[1376], shortname); + snprintf(buf, sizeof(buf), "*** %s has left the game ***", players[player]->getAccountName()); addLobbyChatMessage(0xffffffff, buf); + + if (directConnect) { + createWaitingStone(player); + } else { + createInviteButton(player); + } + continue; } @@ -7022,11 +7039,8 @@ namespace MainMenu { stats[player]->playerRace = net_packet->data[8]; strcpy(stats[player]->name, (char*)(&net_packet->data[9])); - char shortname[32] = { 0 }; - strncpy(shortname, stats[player]->name, 22); - char buf[1024]; - snprintf(buf, sizeof(buf), language[1388], shortname); + snprintf(buf, sizeof(buf), "*** %s has joined the game ***", players[player]->getAccountName()); addLobbyChatMessage(0xffffffff, buf); if (player != clientnum) { @@ -7079,7 +7093,7 @@ namespace MainMenu { connectionErrorPrompt("You have been kicked\nfrom the remote server."); } else { char buf[1024]; - snprintf(buf, sizeof(buf), language[1120], stats[playerDisconnected]->name); + snprintf(buf, sizeof(buf), "*** %s has left the game ***", players[playerDisconnected]->getAccountName()); addLobbyChatMessage(0xffffffff, buf); if (directConnect) { createWaitingStone(playerDisconnected); @@ -7127,6 +7141,38 @@ namespace MainMenu { handlePacketsAsClient(); } doKeepAlive(); + + // push username to lobby + if (multiplayer != SINGLE && !directConnect) { + if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { +#ifdef USE_EOS + if (EOS.CurrentLobbyData.currentLobbyIsValid()) { + if (EOS.CurrentLobbyData.getClientnumMemberAttribute(EOS.CurrentUserInfo.getProductUserIdHandle()) < 0) { + if (EOS.CurrentLobbyData.assignClientnumMemberAttribute(EOS.CurrentUserInfo.getProductUserIdHandle(), clientnum)) { + EOS.CurrentLobbyData.modifyLobbyMemberAttributeForCurrentUser(); + } + } + } +#endif + } + + if (LobbyHandler.getP2PType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { +#ifdef STEAMWORKS + if (currentLobby) { + const char* memberNumChar = SteamMatchmaking()->GetLobbyMemberData( + *static_cast(currentLobby), SteamUser()->GetSteamID(), "clientnum"); + if (memberNumChar) { + std::string str = memberNumChar; + if (str.empty() || std::to_string(clientnum) != str) { + SteamMatchmaking()->SetLobbyMemberData(*static_cast(currentLobby), + "clientnum", std::to_string(clientnum).c_str()); + printlog("[STEAM Lobbies]: Updating clientnum %d to lobby member data", clientnum); + } + } + } +#endif + } + } } static void setupNetGameAsServer() { @@ -7150,10 +7196,10 @@ namespace MainMenu { } static void finalizeOnlineLobby() { - closeNetworkInterfaces(); - directConnect = false; if (LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { #ifdef STEAMWORKS + closeNetworkInterfaces(); + directConnect = false; for ( int c = 0; c < MAXPLAYERS; c++ ) { if ( steamIDRemote[c] ) { cpp_Free_CSteamID(steamIDRemote[c]); @@ -7162,10 +7208,6 @@ namespace MainMenu { } ::currentLobbyType = k_ELobbyTypePublic; cpp_SteamMatchmaking_CreateLobby(::currentLobbyType, MAXPLAYERS); -#endif - } else if (LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { -#ifdef USE_EOS - EOS.createLobby(); #endif } setupNetGameAsServer(); @@ -7414,6 +7456,17 @@ namespace MainMenu { if (strncmp(address, steam_str, sizeof(steam_str) - 1) == 0) { lobby = getLobbySteamID(address); } + else if ((char)tolower((int)address[0]) == 's' && strlen(address) == 5) { + connectingToLobby = true; + connectingToLobbyWindow = true; + joinLobbyWaitingForHostResponse = true; + LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); + LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); + flushP2PPackets(100, 200); + requestingLobbies = true; + cpp_SteamMatchmaking_RequestLobbyList(address + 1); + return true; + } } else if (pLobby) { lobby = static_cast(pLobby); @@ -7440,6 +7493,23 @@ namespace MainMenu { if (strncmp(address, epic_str, sizeof(epic_str) - 1) == 0) { lobby = getLobbyEpic(address); } + else if ((char)tolower((int)address[0]) == 'e' && strlen(address) == 5) { + memcpy(EOS.lobbySearchByCode, address + 1, 4); + EOS.lobbySearchByCode[4] = '\0'; + EOS.LobbySearchResults.useLobbyCode = true; + EOS.bConnectingToLobby = true; + EOS.bConnectingToLobbyWindow = true; + EOS.bJoinLobbyWaitingForHostResponse = true; + LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); + LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); + flushP2PPackets(100, 200); + EOS.searchLobbies( + EOSFuncs::LobbyParameters_t::LobbySearchOptions::LOBBY_SEARCH_ALL, + EOSFuncs::LobbyParameters_t::LobbyJoinOptions::LOBBY_JOIN_FIRST_SEARCH_RESULT, + ""); + EOS.LobbySearchResults.useLobbyCode = false; + return true; + } } else if (pLobby) { lobby = static_cast(pLobby); @@ -11214,7 +11284,11 @@ namespace MainMenu { } else { strcpy(shortname, stats[player]->name); } - field->setText(shortname); + + char buf[128]; + snprintf(buf, sizeof(buf), "%s\n(%s)", shortname, players[player]->getAccountName()); + + field->setText(buf); }); if (local) { @@ -11512,7 +11586,6 @@ namespace MainMenu { soundCancel(); disconnectFromLobby(); destroyMainMenu(); - currentLobbyType = LobbyType::None; createMainMenu(false); } else { binaryPrompt( @@ -11522,7 +11595,6 @@ namespace MainMenu { soundActivate(); disconnectFromLobby(); destroyMainMenu(); - currentLobbyType = LobbyType::None; createMainMenu(false); }, [](Button& button){ // no @@ -12100,15 +12172,6 @@ namespace MainMenu { } #endif - // set state -#ifdef STEAMWORKS - requestingLobbies = true; -#endif -#if defined USE_EOS - EOS.bRequestingLobbies = true; -#endif - LobbyHandler.selectedLobbyInList = 0; - // create new window textPrompt("lobby_list_request", "Requesting lobby list...", [](Widget& widget){ @@ -12131,7 +12194,7 @@ namespace MainMenu { LobbyInfo info; info.name = lobbyText[c]; info.players = lobbyPlayers[c]; - info.ping = 100; // TODO + info.ping = 50; // TODO info.locked = false; // TODO info.flags = 0; // TODO info.address = "steam:" + std::to_string(c); @@ -12144,7 +12207,7 @@ namespace MainMenu { LobbyInfo info; info.name = lobby.LobbyAttributes.lobbyName; info.players = MAXPLAYERS - lobby.FreeSlots; - info.ping = 100; // TODO + info.ping = 50; // TODO info.locked = lobby.LobbyAttributes.gameCurrentLevel != -1; info.flags = lobby.LobbyAttributes.serverFlags; info.address = "epic:" + std::to_string(c); @@ -12155,10 +12218,13 @@ namespace MainMenu { }); // request new lobbies + LobbyHandler.selectedLobbyInList = 0; #ifdef STEAMWORKS - cpp_SteamMatchmaking_RequestLobbyList(); + requestingLobbies = true; + cpp_SteamMatchmaking_RequestLobbyList(nullptr); #endif #ifdef USE_EOS + EOS.bRequestingLobbies = true; #ifdef STEAMWORKS if ( EOS.CurrentUserInfo.bUserLoggedIn ) { @@ -13607,6 +13673,33 @@ namespace MainMenu { "host_lan_image" ); + auto host_online_fn = [](Button&){ + soundActivate(); +#ifdef USE_EOS + if (LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { + closeNetworkInterfaces(); + directConnect = false; + EOS.createLobby(); + textPrompt("host_eos_prompt", "Creating online lobby...", + [](Widget&){ + if (EOS.CurrentLobbyData.bAwaitingCreationCallback) { + return; + } else { + if (EOS.CurrentLobbyData.LobbyCreationResult == EOS_EResult::EOS_Success) { + createLobby(LobbyType::LobbyOnline); + } else { + closePrompt("host_eos_prompt"); + monoPrompt("Failed to create lobby", "Okay", + [](Button&){soundCancel(); closeMono();}); + } + } + }); + } +#else + createLobby(LobbyType::LobbyOnline); +#endif + }; + auto host_online_button = window->addButton("host_online"); host_online_button->setSize(SDL_Rect{96, 232, 164, 62}); host_online_button->setBackground("*images/ui/Main Menus/Play/NewGameConnectivity/ButtonStandard/Button_Standard_Default_00.png"); @@ -13628,7 +13721,7 @@ namespace MainMenu { host_online_button->setWidgetUp("host_lan"); host_online_button->setWidgetDown("join"); #if defined(STEAMWORKS) || defined(USE_EOS) - host_online_button->setCallback([](Button&){soundActivate(); createLobby(LobbyType::LobbyOnline);}); + host_online_button->setCallback(host_online_fn); #else host_online_button->setCallback([](Button&){soundError();}); #endif @@ -15843,13 +15936,17 @@ namespace MainMenu { } void disconnectedFromServer(const char* text) { - // when a player is disconnected from the server in-game - multiplayer = SINGLE; - disconnectFromLobby(); - destroyMainMenu(); - createDummyMainMenu(); - disconnectPrompt(text); - pauseGame(2, 0); + // when a player is disconnected from the server + if (multiplayer != SINGLE) { + multiplayer = SINGLE; + disconnectFromLobby(); + destroyMainMenu(); + createDummyMainMenu(); + disconnectPrompt(text); + if (!intro) { + pauseGame(2, 0); + } + } } void openGameoverWindow(int player, bool tutorial) { From 35571fe36d5c40691682f4c6e4aaff29fa54ef79 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Fri, 22 Jul 2022 23:03:51 -0700 Subject: [PATCH 12/21] kick and limit players, invite to saved games --- src/eos.cpp | 1 + src/init_game.cpp | 5 - src/lobbies.cpp | 28 +- src/menu.cpp | 799 +------------------------------------------- src/net.cpp | 26 +- src/net.hpp | 2 +- src/steam.cpp | 141 ++++---- src/steam.hpp | 8 +- src/ui/MainMenu.cpp | 525 ++++++++++++++++++++++++++--- 9 files changed, 575 insertions(+), 960 deletions(-) diff --git a/src/eos.cpp b/src/eos.cpp index 6abd42a09..459298e87 100644 --- a/src/eos.cpp +++ b/src/eos.cpp @@ -920,6 +920,7 @@ void EOS_CALL EOSFuncs::OnMemberStatusReceived(const EOS_Lobby_LobbyMemberStatus EOSFuncs::logInfo("OnMemberStatusReceived: received user: %s, event: %d, updating lobby", EOSFuncs::Helpers_t::shortProductIdToString(data->TargetUserId).c_str(), static_cast(data->CurrentStatus)); + return; } } break; diff --git a/src/init_game.cpp b/src/init_game.cpp index cc681c6c8..61266f4e9 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -823,11 +823,6 @@ void deinitGame() cpp_Free_CSteamID(currentLobby); //TODO: Remove these bodges. currentLobby = NULL; } - if ( lobbyToConnectTo ) - { - cpp_Free_CSteamID(lobbyToConnectTo); - lobbyToConnectTo = NULL; - } for ( c = 0; c < MAXPLAYERS; c++ ) { if ( steamIDRemote[c] ) diff --git a/src/lobbies.cpp b/src/lobbies.cpp index 40034564f..c69aaed99 100644 --- a/src/lobbies.cpp +++ b/src/lobbies.cpp @@ -34,51 +34,51 @@ std::string LobbyHandler_t::getLobbyJoinFailedConnectString(int result) switch ( result ) { case EResult_LobbyFailures::LOBBY_GAME_IN_PROGRESS: - snprintf(buf, 1023, "Failed to join lobby:\n\nGame in progress not joinable."); + snprintf(buf, 1023, "Failed to join lobby:\nGame in progress not joinable."); break; case EResult_LobbyFailures::LOBBY_USING_SAVEGAME: - snprintf(buf, 1023, "Failed to join lobby:\n\nCompatible save required."); + snprintf(buf, 1023, "Failed to join lobby:\nCompatible save required."); break; case EResult_LobbyFailures::LOBBY_NOT_USING_SAVEGAME: - snprintf(buf, 1023, "Failed to join lobby:\n\nOnly new characters allowed."); + snprintf(buf, 1023, "Failed to join lobby:\nOnly new characters allowed."); break; case EResult_LobbyFailures::LOBBY_WRONG_SAVEGAME: - snprintf(buf, 1023, "Failed to join lobby:\n\nIncompatible save game."); + snprintf(buf, 1023, "Failed to join lobby:\nIncompatible save game."); break; case EResult_LobbyFailures::LOBBY_JOIN_CANCELLED: - snprintf(buf, 1023, "Lobby join cancelled.\n\nSafely leaving lobby."); + snprintf(buf, 1023, "Lobby join cancelled.\nSafely leaving lobby."); break; case EResult_LobbyFailures::LOBBY_JOIN_TIMEOUT: - snprintf(buf, 1023, "Failed to join lobby:\n\nTimeout waiting for server."); + snprintf(buf, 1023, "Failed to join lobby:\nTimeout waiting for server."); break; case EResult_LobbyFailures::LOBBY_NO_OWNER: - snprintf(buf, 1023, "Failed to join lobby:\n\nNo host found for lobby."); + snprintf(buf, 1023, "Failed to join lobby:\nNo host found for lobby."); break; case EResult_LobbyFailures::LOBBY_NOT_FOUND: - snprintf(buf, 1023, "Failed to join lobby:\n\nLobby no longer exists."); + snprintf(buf, 1023, "Failed to join lobby:\nLobby no longer exists."); break; case EResult_LobbyFailures::LOBBY_TOO_MANY_PLAYERS: - snprintf(buf, 1023, "Failed to join lobby:\n\nLobby is full."); + snprintf(buf, 1023, "Failed to join lobby:\nLobby is full."); break; #ifdef USE_EOS #ifdef STEAMWORKS case static_cast(EOS_EResult::EOS_InvalidUser): - snprintf(buf, 1023, "Failed to join lobby:\n\nCrossplay not enabled."); + snprintf(buf, 1023, "Failed to join lobby:\nCrossplay not enabled."); break; #else case static_cast(EOS_EResult::EOS_InvalidUser): - snprintf(buf, 1023, "Failed to join lobby:\n\nNot connected to Epic Online."); + snprintf(buf, 1023, "Failed to join lobby:\nNot connected to Epic Online."); break; #endif case static_cast(EOS_EResult::EOS_NotFound): - snprintf(buf, 1023, "Failed to join lobby:\n\nLobby no longer exists."); + snprintf(buf, 1023, "Failed to join lobby:\nLobby no longer exists."); break; case static_cast(EOS_EResult::EOS_Lobby_TooManyPlayers): - snprintf(buf, 1023, "Failed to join lobby:\n\nLobby is full."); + snprintf(buf, 1023, "Failed to join lobby:\nLobby is full."); break; #endif default: - snprintf(buf, 1023, "Failed to join lobby:\n\nError code: %d.", result); + snprintf(buf, 1023, "Failed to join lobby:\nError code: %d.", result); break; } printlog("[Lobbies Error]: %s", buf); diff --git a/src/menu.cpp b/src/menu.cpp index e620cde9c..6cd466c09 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -11717,82 +11717,7 @@ void buttonEndGameConfirmSave(button_t* my) // generic close window button void buttonCloseSubwindow(button_t* my) { - int c; - for ( c = 0; c < 512; c++ ) - { - keystatus[c] = 0; - } - if ( !subwindow ) - { - return; - } - - loadGameSaveShowRectangle = 0; - singleplayerSavegameFreeSlot = -1; // clear this value when closing window - multiplayerSavegameFreeSlot = -1; // clear this value when closing window - directoryPath = ""; - gamemodsWindowClearVariables(); - if ( score_window || score_leaderboard_window ) - { - // reset class loadout - stats[0]->sex = static_cast(0); - stats[0]->appearance = 0; - stats[0]->playerRace = RACE_HUMAN; - strcpy(stats[0]->name, ""); - stats[0]->type = HUMAN; - client_classes[0] = 0; - stats[0]->clearStats(); - initClass(0); - } - rebindkey = -1; -#ifdef STEAMWORKS - requestingLobbies = false; - connectingToLobbyWindow = false; - connectingToLobby = false; - joinLobbyWaitingForHostResponse = false; -#endif -#if defined USE_EOS - EOS.bRequestingLobbies = false; - EOS.bJoinLobbyWaitingForHostResponse = false; - EOS.bConnectingToLobby = false; - EOS.bConnectingToLobbyWindow = false; -#endif - -#if !defined STEAMWORKS && !defined USE_EOS - serialEnterWindow = false; -#endif - - score_window = 0; - score_leaderboard_window = 0; - gamemods_window = 0; - gameModeManager.Tutorial.Menu.close(); - gameModeManager.Tutorial.FirstTimePrompt.close(); - savegames_window = 0; - savegames_window_scroll = 0; - achievements_window = false; - achievements_window_page = 1; - lobby_window = false; - settings_window = false; - connect_window = 0; -#ifdef STEAMWORKS - if ( charcreation_step ) - { - if ( lobbyToConnectTo ) - { - // cancel lobby invitation acceptance - cpp_Free_CSteamID(lobbyToConnectTo); //TODO: Bugger this. - lobbyToConnectTo = NULL; - } - } -#endif - - charcreation_step = 0; - subwindow = 0; - if ( SDL_IsTextInputActive() ) - { - SDL_StopTextInput(); - } - playSound(138, 64); + return; // deprecated } void buttonCloseSettingsSubwindow(button_t* my) @@ -11822,231 +11747,7 @@ Uint32 charcreation_ticks = 0; // move player forward through creation dialogue void buttonContinue(button_t* my) { - if ( ticks - charcreation_ticks < TICKS_PER_SECOND / 10 ) - { - return; - } - charcreation_ticks = ticks; - if ( charcreation_step == 4 && !strcmp(stats[0]->name, "") ) - { - return; - } - - charcreation_step++; - if ( charcreation_step == 3 && stats[0]->playerRace != RACE_HUMAN ) - { - charcreation_step = 4; // skip appearance window - } - if ( charcreation_step == 4 ) - { - if (inputstr != stats[0]->name) - { - inputstr = stats[0]->name; -#ifdef NINTENDO - auto result = nxKeyboard("Enter your character's name"); - if (result.success) - { - strncpy(inputstr, result.str.c_str(), 21); - inputstr[21] = '\0'; - } -#endif - } - SDL_StartTextInput(); - inputlen = 22; - } - else if ( charcreation_step == 5 ) - { - // look for a gap in save game numbering - savegameCurrentFileIndex = 0; - for ( int fileNumber = 0; fileNumber < SAVE_GAMES_MAX; ++fileNumber ) - { - if ( !saveGameExists(true, fileNumber) ) - { - singleplayerSavegameFreeSlot = fileNumber; - break; - } - } - for ( int fileNumber = 0; fileNumber < SAVE_GAMES_MAX; ++fileNumber ) - { - if ( !saveGameExists(false, fileNumber) ) - { - multiplayerSavegameFreeSlot = fileNumber; - break; - } - } - if ( SDL_IsTextInputActive() ) - { - lastname = (string)stats[0]->name; - SDL_StopTextInput(); - } -#ifdef STEAMWORKS - if ( lobbyToConnectTo ) - { - charcreation_step = 0; - - // since we skip step 6 we never set the correct free slot. - reloadSavegamesList(false); - if ( multiplayerSavegameFreeSlot == -1 ) - { - savegameCurrentFileIndex = 0; - std::vector>::reverse_iterator it = savegamesList.rbegin(); - for ( ; it != savegamesList.rend(); ++it ) - { - std::tuple entry = *it; - if ( std::get<1>(entry) != SINGLE ) - { - savegameCurrentFileIndex = std::get<2>(entry); - break; - } - } - } - else - { - savegameCurrentFileIndex = multiplayerSavegameFreeSlot; - } - - // clear buttons - list_FreeAll(&button_l); - deleteallbuttons = true; - - // create new window - subwindow = 1; - subx1 = xres / 2 - 256; - subx2 = xres / 2 + 256; - suby1 = yres / 2 - 64; - suby2 = yres / 2 + 64; - strcpy(subtext, language[1447]); - - // close button - button_t* button = nullptr; - button = newButton(); - strcpy(button->label, "x"); - button->x = subx2 - 20; - button->y = suby1; - button->sizex = 20; - button->sizey = 20; - button->action = &openSteamLobbyWaitWindow; - button->visible = 1; - button->focused = 1; - button->key = SDL_SCANCODE_ESCAPE; - button->joykey = joyimpulses[INJOY_MENU_CANCEL]; - - // cancel button - button = newButton(); - strcpy(button->label, language[1316]); - button->sizex = strlen(language[1316]) * 12 + 8; - button->sizey = 20; - button->x = subx1 + (subx2 - subx1) / 2 - button->sizex / 2; - button->y = suby2 - 28; - button->action = &openSteamLobbyWaitWindow; - button->visible = 1; - button->focused = 1; - - connectingToLobby = true; - connectingToLobbyWindow = true; - strncpy( currentLobbyName, "", 31 ); - LobbyHandler.steamValidateAndJoinLobby(*static_cast(lobbyToConnectTo)); - cpp_Free_CSteamID(lobbyToConnectTo); //TODO: Bugger this. - lobbyToConnectTo = NULL; - } -#endif - } - else if ( charcreation_step == 6 ) - { - // store this character into previous character. - lastCreatedCharacterSex = stats[0]->sex; - lastCreatedCharacterClass = client_classes[0]; - lastCreatedCharacterAppearance = stats[0]->appearance; - lastCreatedCharacterRace = stats[0]->playerRace; - - if ( multiplayerselect != SINGLE ) - { - if ( multiplayerSavegameFreeSlot == -1 ) - { - savegameCurrentFileIndex = 0; - std::vector>::reverse_iterator it = savegamesList.rbegin(); - for ( ; it != savegamesList.rend(); ++it ) - { - std::tuple entry = *it; - if ( std::get<1>(entry) != SINGLE ) - { - savegameCurrentFileIndex = std::get<2>(entry); - break; - } - } - } - else - { - savegameCurrentFileIndex = multiplayerSavegameFreeSlot; - } - multiplayerSavegameFreeSlot = -1; - } - - if ( multiplayerselect == SINGLE ) - { - if ( singleplayerSavegameFreeSlot == -1 ) - { - savegameCurrentFileIndex = 0; - std::vector>::reverse_iterator it = savegamesList.rbegin(); - for ( ; it != savegamesList.rend(); ++it ) - { - std::tuple entry = *it; - if ( std::get<1>(entry) == SINGLE ) - { - savegameCurrentFileIndex = std::get<2>(entry); - break; - } - } - } - else - { - savegameCurrentFileIndex = singleplayerSavegameFreeSlot; - } - buttonStartSingleplayer(my); - singleplayerSavegameFreeSlot = -1; - } - else if ( multiplayerselect == SERVER || multiplayerselect == SERVERCROSSPLAY ) - { -#if (defined STEAMWORKS || defined USE_EOS) - directConnect = false; -#else - directConnect = true; -#endif -#ifdef STEAMWORKS - if ( multiplayerselect == SERVERCROSSPLAY ) - { - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); - } - else - { - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_STEAM; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); - } -#endif - buttonHostMultiplayer(my); - } - else if ( multiplayerselect == CLIENT ) - { -#if (defined STEAMWORKS || defined USE_EOS) - directConnect = false; - openSteamLobbyWaitWindow(my); -#else - directConnect = true; - buttonJoinMultiplayer(my); -#endif - } - else if ( multiplayerselect == DIRECTSERVER ) - { - directConnect = true; - buttonHostMultiplayer(my); - } - else if ( multiplayerselect == DIRECTCLIENT ) - { - directConnect = true; - buttonJoinMultiplayer(my); - } - } + return; // deprecated } // move player backward through creation dialogue @@ -12103,74 +11804,7 @@ void buttonStartSingleplayer(button_t* my) // host a multiplayer game void buttonHostMultiplayer(button_t* my) { - button_t* button; - - // refresh keepalive - int c; - for ( c = 0; c < MAXPLAYERS; c++ ) - { - client_keepalive[c] = ticks; - } - - if ( !directConnect ) - { - snprintf(portnumber_char, 6, "%d", DEFAULT_PORT); - buttonHostLobby(my); - } - else - { - // close current window - buttonCloseSubwindow(my); - list_FreeAll(&button_l); - deleteallbuttons = true; - - // open port window - connect_window = SERVER; - subwindow = 1; - subx1 = xres / 2 - 128; - subx2 = xres / 2 + 128; - suby1 = yres / 2 - 56; - suby2 = yres / 2 + 56; - strcpy(subtext, language[1448]); - - // close button - button = newButton(); - strcpy(button->label, "x"); - button->x = subx2 - 20; - button->y = suby1; - button->sizex = 20; - button->sizey = 20; - button->action = &buttonCloseSubwindow; - button->visible = 1; - button->focused = 1; - button->key = SDL_SCANCODE_ESCAPE; - button->joykey = joyimpulses[INJOY_MENU_CANCEL]; - - // host button - button = newButton(); - strcpy(button->label, language[1449]); - button->sizex = strlen(language[1449]) * 12 + 8; - button->sizey = 20; - button->x = subx2 - button->sizex - 4; - button->y = suby2 - 24; - button->action = &buttonHostLobby; - button->visible = 1; - button->focused = 1; - button->key = SDL_SCANCODE_RETURN; - button->joykey = joyimpulses[INJOY_MENU_NEXT]; - - // cancel button - button = newButton(); - strcpy(button->label, language[1316]); - button->sizex = strlen(language[1316]) * 12 + 8; - button->sizey = 20; - button->x = subx1 + 4; - button->y = suby2 - 24; - button->action = &buttonCloseSubwindow; - button->visible = 1; - button->focused = 1; - strcpy(portnumber_char, last_port); //Copy the last used port. - } + return; // deprecated } // join a multiplayer game @@ -12235,189 +11869,7 @@ void buttonJoinMultiplayer(button_t* my) // starts a lobby as host void buttonHostLobby(button_t* my) { - // deprecated - return; - - button_t* button; - char *portnumbererr; - int c; - - // close current window - buttonCloseSubwindow(my); - list_FreeAll(&button_l); - deleteallbuttons = true; - portnumber = (Uint16)strtol(portnumber_char, &portnumbererr, 10); // get the port number from the text field - list_FreeAll(&lobbyChatboxMessages); - - if ( *portnumbererr != '\0' || portnumber < 1024 ) - { - printlog("warning: invalid port number: %d\n", portnumber); - openFailedConnectionWindow(SERVER); - return; - } - - //newString(&lobbyChatboxMessages, 0xFFFFFFFF, language[1452]); - if ( loadingsavegame ) - { - //newString(&lobbyChatboxMessages, 0xFFFFFFFF, language[1453]); - } - - // close any existing net interfaces - closeNetworkInterfaces(); - - if ( !directConnect ) - { - if ( LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM ) - { -#if defined STEAMWORKS - for ( c = 0; c < MAXPLAYERS; c++ ) - { - if ( steamIDRemote[c] ) - { - cpp_Free_CSteamID(steamIDRemote[c]); //TODO: Bugger this. - steamIDRemote[c] = NULL; - } - } - currentLobbyType = k_ELobbyTypePrivate; - cpp_SteamMatchmaking_CreateLobby(currentLobbyType, MAXPLAYERS); -#endif - } - else if ( LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY ) - { -#ifdef USE_EOS - EOS.createLobby(); -#endif - } - } - else - { - // resolve host's address - if (SDLNet_ResolveHost(&net_server, NULL, portnumber) == -1) - { - printlog( "warning: resolving host at localhost:%d has failed.\n", portnumber); - openFailedConnectionWindow(SERVER); - return; - } - - // open sockets - if (!(net_sock = SDLNet_UDP_Open(portnumber))) - { - printlog( "warning: SDLNet_UDP_open has failed: %s\n", SDLNet_GetError()); - openFailedConnectionWindow(SERVER); - return; - } - if (!(net_tcpsock = SDLNet_TCP_Open(&net_server))) - { - printlog( "warning: SDLNet_TCP_open has failed: %s\n", SDLNet_GetError()); - openFailedConnectionWindow(SERVER); - return; - } - tcpset = SDLNet_AllocSocketSet(MAXPLAYERS); - SDLNet_TCP_AddSocket(tcpset, net_tcpsock); - } - - // allocate data for client connections - net_clients = (IPaddress*) malloc(sizeof(IPaddress) * MAXPLAYERS); - net_tcpclients = (TCPsocket*) malloc(sizeof(TCPsocket) * MAXPLAYERS); - for ( c = 0; c < MAXPLAYERS; c++ ) - { - net_tcpclients[c] = NULL; - } - - // allocate packet data - if (!(net_packet = SDLNet_AllocPacket(NET_PACKET_SIZE))) - { - printlog( "warning: packet allocation failed: %s\n", SDLNet_GetError()); - openFailedConnectionWindow(SERVER); - return; - } - - if ( directConnect ) - { - printlog( "server initialized successfully.\n"); - } - else - { - printlog( "steam lobby opened successfully.\n"); - } - - // open lobby window - multiplayer = SERVER; - lobby_window = true; - subwindow = 1; - subx1 = xres / 2 - 400; - subx2 = xres / 2 + 400; -#ifdef PANDORA - suby1 = yres / 2 - ((yres==480)?230:290); - suby2 = yres / 2 + ((yres==480)?230:290); -#else - suby1 = yres / 2 - 300; - suby2 = yres / 2 + 300; -#endif - if ( directConnect ) - { - strcpy(subtext, language[1454]); - strcat(subtext, portnumber_char); - strcat(subtext, language[1456]); - } - else - { - strcpy(subtext, language[1455]); - strcat(subtext, language[1456]); - } - - // start game button - button = newButton(); - strcpy(button->label, language[1457]); - button->sizex = strlen(language[1457]) * 12 + 8; - button->sizey = 20; - button->x = subx2 - button->sizex - 4; - button->y = suby2 - 24; - button->action = &buttonStartServer; - button->visible = 1; - button->focused = 1; - button->joykey = joyimpulses[INJOY_MENU_NEXT]; - - // disconnect button - button = newButton(); - strcpy(button->label, language[1311]); - button->sizex = strlen(language[1311]) * 12 + 8; - button->sizey = 20; - button->x = subx1 + 4; - button->y = suby2 - 24; - button->action = &buttonDisconnect; - button->visible = 1; - button->focused = 1; - button->key = SDL_SCANCODE_ESCAPE; - button->joykey = joyimpulses[INJOY_MENU_CANCEL]; - c = button->x + button->sizex + 4; - - // invite friends button - if ( !directConnect ) - { -#ifdef STEAMWORKS - if ( LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM ) - { - button = newButton(); - strcpy(button->label, language[1458]); - button->sizex = strlen(language[1458]) * 12 + 8; - button->sizey = 20; - button->x = c; - button->y = suby2 - 24; - button->action = &buttonInviteFriends; - button->visible = 1; - button->focused = 1; - } -#endif - } - - if ( loadingsavegame ) - { - loadGame(clientnum); - } - - strcpy(last_port, portnumber_char); - saveConfig("default.cfg"); + return; // deprecated } // joins a lobby as client @@ -13900,251 +13352,12 @@ void buttonOpenCharacterCreationWindow(button_t* my) void buttonLoadSingleplayerGame(button_t* button) { - loadGameSaveShowRectangle = 0; - savegamesList.clear(); - loadingsavegame = getSaveGameUniqueGameKey(true); - int mul = getSaveGameType(true); - - if ( mul == DIRECTSERVER ) - { - directConnect = true; - buttonHostMultiplayer(button); - } - else if ( mul == DIRECTCLIENT ) - { - directConnect = true; - buttonJoinMultiplayer(button); - } - else if ( mul == SINGLE ) - { - buttonStartSingleplayer(button); - } - else - { - directConnect = false; -#if defined(USE_EOS) || defined(STEAMWORKS) - if ( mul == SERVERCROSSPLAY ) - { - // if steamworks, the hosting type can change if crossplay is enabled or not. -#ifdef STEAMWORKS - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_STEAM; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); -#ifdef USE_EOS - if ( LobbyHandler.crossplayEnabled ) - { - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); - } -#endif -#elif defined USE_EOS - // if just eos, then force hosting settings to default. - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); -#endif - buttonHostMultiplayer(button); - } - else if ( mul == SERVER ) - { - if ( getSaveGameVersionNum(true) <= 335 ) - { - // legacy support for steam ver not remembering if crossplay or not. no action. - // starting with v3.3.6, (mul == SERVERCROSSPLAY) detects from the savefile. - } - else - { -#ifdef STEAMWORKS - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_STEAM; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); -#endif - } - buttonHostMultiplayer(button); - } - else if ( mul == CLIENT ) - { -#ifdef STEAMWORKS - if ( !lobbyToConnectTo ) - { - openSteamLobbyWaitWindow(button); - } - else - { - // close current window - list_FreeAll(&button_l); - deleteallbuttons = true; - - // create new window - subwindow = 1; - subx1 = xres / 2 - 256; - subx2 = xres / 2 + 256; - suby1 = yres / 2 - 64; - suby2 = yres / 2 + 64; - strcpy(subtext, language[1447]); - - // close button - button = newButton(); - strcpy(button->label, "x"); - button->x = subx2 - 20; - button->y = suby1; - button->sizex = 20; - button->sizey = 20; - button->action = &openSteamLobbyWaitWindow; - button->visible = 1; - button->focused = 1; - button->key = SDL_SCANCODE_ESCAPE; - button->joykey = joyimpulses[INJOY_MENU_CANCEL]; - - // cancel button - button = newButton(); - strcpy(button->label, language[1316]); - button->sizex = strlen(language[1316]) * 12 + 8; - button->sizey = 20; - button->x = subx1 + (subx2 - subx1) / 2 - button->sizex / 2; - button->y = suby2 - 28; - button->action = &openSteamLobbyWaitWindow; - button->visible = 1; - button->focused = 1; - - connectingToLobby = true; - connectingToLobbyWindow = true; - strncpy( currentLobbyName, "", 31 ); - LobbyHandler.steamValidateAndJoinLobby(*static_cast(lobbyToConnectTo)); - cpp_Free_CSteamID(lobbyToConnectTo); - lobbyToConnectTo = NULL; - } -#elif defined USE_EOS - openSteamLobbyWaitWindow(button); -#endif - } - else - { - buttonStartSingleplayer(button); - } -#endif - } + return; // deprecated } void buttonLoadMultiplayerGame(button_t* button) { - loadGameSaveShowRectangle = 0; - savegamesList.clear(); - loadingsavegame = getSaveGameUniqueGameKey(false); - int mul = getSaveGameType(false); - - if ( mul == DIRECTSERVER ) - { - directConnect = true; - buttonHostMultiplayer(button); - } - else if ( mul == DIRECTCLIENT ) - { - directConnect = true; - buttonJoinMultiplayer(button); - } - else if ( mul == SINGLE ) - { - buttonStartSingleplayer(button); - } - else - { - directConnect = false; -#if defined(USE_EOS) || defined(STEAMWORKS) - if ( mul == SERVERCROSSPLAY ) - { - // if steamworks, the hosting type can change if crossplay is enabled or not. -#ifdef STEAMWORKS - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_STEAM; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); -#ifdef USE_EOS - if ( LobbyHandler.crossplayEnabled ) - { - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); - } -#endif -#elif defined USE_EOS - // if just eos, then force hosting settings to default. - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); -#endif - buttonHostMultiplayer(button); - } - else if ( mul == SERVER ) - { - if ( getSaveGameVersionNum(false) <= 335 ) - { - // legacy support for steam ver not remembering if crossplay or not. no action. - // starting with v3.3.6, (mul == SERVERCROSSPLAY) detects from the savefile. - } - else - { -#ifdef STEAMWORKS - LobbyHandler.hostingType = LobbyHandler_t::LobbyServiceType::LOBBY_STEAM; - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_STEAM); -#endif - } - buttonHostMultiplayer(button); - } - else if ( mul == CLIENT ) - { -#ifdef STEAMWORKS - if ( !lobbyToConnectTo ) - { - openSteamLobbyWaitWindow(button); - } - else - { - list_FreeAll(&button_l); - deleteallbuttons = true; - - // create new window - subwindow = 1; - subx1 = xres / 2 - 256; - subx2 = xres / 2 + 256; - suby1 = yres / 2 - 64; - suby2 = yres / 2 + 64; - strcpy(subtext, language[1447]); - - // close button - button = newButton(); - strcpy(button->label, "x"); - button->x = subx2 - 20; - button->y = suby1; - button->sizex = 20; - button->sizey = 20; - button->action = &openSteamLobbyWaitWindow; - button->visible = 1; - button->focused = 1; - button->key = SDL_SCANCODE_ESCAPE; - button->joykey = joyimpulses[INJOY_MENU_CANCEL]; - - // cancel button - button = newButton(); - strcpy(button->label, language[1316]); - button->sizex = strlen(language[1316]) * 12 + 8; - button->sizey = 20; - button->x = subx1 + (subx2 - subx1) / 2 - button->sizex / 2; - button->y = suby2 - 28; - button->action = &openSteamLobbyWaitWindow; - button->visible = 1; - button->focused = 1; - - connectingToLobby = true; - connectingToLobbyWindow = true; - strncpy(currentLobbyName, "", 31); - LobbyHandler.steamValidateAndJoinLobby(*static_cast(lobbyToConnectTo)); - cpp_Free_CSteamID(lobbyToConnectTo); - lobbyToConnectTo = NULL; - } -#elif defined USE_EOS - openSteamLobbyWaitWindow(button); -#endif - } - else - { - buttonStartSingleplayer(button); - } -#endif - } + return; // deprecated } void buttonRandomCharacter(button_t* my) diff --git a/src/net.cpp b/src/net.cpp index 88b76fd16..4ce7191ff 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1397,7 +1397,7 @@ void sendAllyCommandClient(int player, Uint32 uid, int command, Uint8 x, Uint8 y sendPacket(net_sock, -1, net_packet, 0); } -NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult) +NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult, bool lockedSlots[4]) { printlog("processing lobby join request\n"); @@ -1415,7 +1415,7 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult) // client will enter any player spot for ( c = 1; c < MAXPLAYERS; c++ ) { - if ( client_disconnected[c] == true ) + if ( client_disconnected[c] == true && !lockedSlots[c] ) { break; // no more player slots } @@ -1425,7 +1425,7 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult) { // client is joining a particular player spot c = net_packet->data[48]; - if ( !client_disconnected[c] ) + if ( !client_disconnected[c] || lockedSlots[c] ) { c = MAXPLAYERS; // client wants to fill a space that is already filled } @@ -1516,18 +1516,20 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult) SDLNet_Write32(c, &net_packet->data[4]); for ( int x = 0; x < MAXPLAYERS; x++ ) { - net_packet->data[8 + x * (5 + 23) + 0] = client_classes[x]; // class - net_packet->data[8 + x * (5 + 23) + 1] = stats[x]->sex; // sex - net_packet->data[8 + x * (5 + 23) + 2] = client_disconnected[x]; // connectedness :p - net_packet->data[8 + x * (5 + 23) + 3] = (Uint8)stats[x]->appearance; // appearance - net_packet->data[8 + x * (5 + 23) + 4] = (Uint8)stats[x]->playerRace; // player race - char shortname[32] = ""; - strncpy(shortname, stats[x]->name, 22); - strcpy((char*)(&net_packet->data[8 + x * (5 + 23) + 5]), shortname); // name + net_packet->data[8 + x * (6 + 32) + 0] = client_disconnected[x]; // connectedness + net_packet->data[8 + x * (6 + 32) + 1] = lockedSlots[x]; // locked state + net_packet->data[8 + x * (6 + 32) + 2] = client_classes[x]; // class + net_packet->data[8 + x * (6 + 32) + 3] = stats[x]->sex; // sex + net_packet->data[8 + x * (6 + 32) + 4] = (Uint8)stats[x]->appearance; // appearance + net_packet->data[8 + x * (6 + 32) + 5] = (Uint8)stats[x]->playerRace; // player race + + char shortname[32]; + snprintf(shortname, sizeof(shortname), "%s", stats[x]->name); + memcpy(net_packet->data + 8 + x * (6 + 32) + 6, shortname, sizeof(shortname)); // name } net_packet->address.host = net_clients[c - 1].host; net_packet->address.port = net_clients[c - 1].port; - net_packet->len = 8 + MAXPLAYERS * (5 + 23); + net_packet->len = 8 + MAXPLAYERS * (6 + 32); if ( directConnect ) { sendPacketSafe(net_sock, -1, net_packet, 0); diff --git a/src/net.hpp b/src/net.hpp index a1df13a86..f44b33940 100644 --- a/src/net.hpp +++ b/src/net.hpp @@ -63,7 +63,7 @@ enum NetworkingLobbyJoinRequestResult : int NET_LOBBY_JOIN_DIRECTIP_FAILURE, NET_LOBBY_JOIN_DIRECTIP_SUCCESS }; -NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult); +NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult, bool lockedSlots[4]); Entity* receiveEntity(Entity* entity); void clientActions(Entity* entity); void clientHandleMessages(Uint32 framerateBreakInterval); diff --git a/src/steam.cpp b/src/steam.cpp index 4ee668952..dc8c04d14 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -49,11 +49,9 @@ Uint32 currentSvFlags = 0; #ifdef STEAMWORKS ELobbyType currentLobbyType = k_ELobbyTypePrivate; #endif -bool stillConnectingToLobby = false; +static bool handlingInvite = false; -bool serverLoadingSaveGame = false; // determines whether lobbyToConnectTo is loading a savegame or not void* currentLobby = NULL; // CSteamID to the current game lobby -void* lobbyToConnectTo = NULL; // CSteamID of the game lobby that user has been invited to void* steamIDGameServer = NULL; // CSteamID to the current game server uint32_t steamServerIP = 0; // ipv4 address for the current game server uint16_t steamServerPort = 0; // port number for the current game server @@ -1518,27 +1516,37 @@ void steam_OnLobbyDataUpdatedCallback( void* pCallback ) } } + void* tempSteamID = cpp_LobbyDataUpdated_pCallback_m_ulSteamIDLobby(pCallback); + // update current lobby info - void* tempSteamID = cpp_LobbyDataUpdated_pCallback_m_ulSteamIDLobby(pCallback); //TODO: BUGGER VOID POINTER. if ( currentLobby ) { - if ( (static_cast(currentLobby))->ConvertToUint64() == (static_cast(tempSteamID))->ConvertToUint64() ) + auto lobby = static_cast(currentLobby); + auto newlobby = static_cast(tempSteamID); + if ( lobby->ConvertToUint64() == newlobby->ConvertToUint64() ) { // extract the display name from the lobby metadata - const char* lobbyName = SteamMatchmaking()->GetLobbyData( *static_cast(currentLobby), "name" ); + const char* lobbyName = SteamMatchmaking()->GetLobbyData( *lobby, "name" ); if ( lobbyName ) { snprintf( currentLobbyName, 31, "%s", lobbyName ); } // get the server flags - const char* svFlagsChar = SteamMatchmaking()->GetLobbyData( *static_cast(currentLobby), "svFlags" ); + const char* svFlagsChar = SteamMatchmaking()->GetLobbyData( *lobby, "svFlags" ); if ( svFlagsChar ) { svFlags = atoi(svFlagsChar); } } } + if ( handlingInvite ) + { + // this is where invites are actually processed on steam. + // we do it here to ensure info about the savegame in the lobby is up-to-date. + handlingInvite = false; + MainMenu::receivedInvite(tempSteamID); + } cpp_Free_CSteamID(tempSteamID); } @@ -1704,79 +1712,50 @@ void steam_OnRequestEncryptedAppTicket(void* pCallback, bool bIOFailure) } #endif //USE_EOS -void processLobbyInvite() +bool processLobbyInvite(void* lobbyToConnectTo) { - if ( !intro ) - { - stillConnectingToLobby = true; - return; + assert(lobbyToConnectTo); + auto lobby = static_cast(lobbyToConnectTo); + const char* pchLoadingSaveGame = SteamMatchmaking()->GetLobbyData(*lobby, "loadingsavegame"); + assert(pchLoadingSaveGame && pchLoadingSaveGame[0]); + + Uint32 saveGameKey = atoi(pchLoadingSaveGame); // get the savegame key of the server. + Uint32 gameKey = getSaveGameUniqueGameKey(false); // maybe we were already loading a compatible save. + if (saveGameKey && saveGameKey == gameKey) { + loadingsavegame = saveGameKey; // save game matches! load game. } - if ( !lobbyToConnectTo ) - { - printlog( "warning: tried to process invitation to null lobby" ); - stillConnectingToLobby = false; - return; + else if (!saveGameKey) { + loadingsavegame = 0; // cancel our savegame load. when we enter the lobby, our character will be flushed. } - const char* loadingSaveGameChar = SteamMatchmaking()->GetLobbyData( *static_cast(lobbyToConnectTo), "loadingsavegame" ); - - if ( loadingSaveGameChar && loadingSaveGameChar[0] ) - { - Uint32 temp32 = atoi(loadingSaveGameChar); - Uint32 gameKey = getSaveGameUniqueGameKey(false); - if ( temp32 && temp32 == gameKey ) - { - loadingsavegame = temp32; - buttonLoadMultiplayerGame(NULL); + else { + // try reload from your other savefiles since this didn't match the default savegameIndex. + if (savegamesList.empty()) { + reloadSavegamesList(false); } - else if ( !temp32 ) - { - loadingsavegame = 0; - buttonOpenCharacterCreationWindow(NULL); - } - else - { - // try reload from your other savefiles since this didn't match the default savegameIndex. - if ( savegamesList.empty() ) - { - reloadSavegamesList(false); - } - bool foundSave = false; - for ( auto it = savegamesList.begin(); it != savegamesList.end(); ++it ) - { - auto entry = *it; - savegameCurrentFileIndex = std::get<2>(entry); - gameKey = getSaveGameUniqueGameKey(false, savegameCurrentFileIndex); - if ( std::get<1>(entry) != SINGLE && temp32 == gameKey ) - { - foundSave = true; - break; - } - } - - if ( !foundSave ) - { - savegameCurrentFileIndex = 0; - printlog("warning: received invitation to lobby with which you have an incompatible save game.\n"); - if ( lobbyToConnectTo ) - { - cpp_Free_CSteamID(lobbyToConnectTo); //TODO: Bodge this bodge! - } - lobbyToConnectTo = NULL; - } - else - { - loadingsavegame = temp32; - buttonLoadMultiplayerGame(NULL); + bool foundSave = false; + for (auto it = savegamesList.begin(); it != savegamesList.end(); ++it) { + auto entry = *it; + savegameCurrentFileIndex = std::get<2>(entry); + gameKey = getSaveGameUniqueGameKey(false, savegameCurrentFileIndex); + if (std::get<1>(entry) != SINGLE && saveGameKey == gameKey) { + foundSave = true; + break; } } - stillConnectingToLobby = false; + if (foundSave ) { + loadingsavegame = saveGameKey; + } else { + printlog("warning: received invitation to lobby with which you have no compatible save game.\n"); + return false; + } } - else - { - stillConnectingToLobby = true; - SteamMatchmaking()->RequestLobbyData(*static_cast(lobbyToConnectTo)); - printlog("warning: failed to determine whether lobby is using a saved game or not...\n"); + + if (loadingsavegame) { + auto info = getSaveGameInfo(false, savegameCurrentFileIndex); + loadGame(info.player_num); } + + return true; } //Helper func. //TODO: Bugger. @@ -1793,11 +1772,14 @@ void steam_OnGameJoinRequested( void* pCallback ) printlog( "OnGameJoinRequested\n" ); #endif - if (lobbyToConnectTo) { - cpp_Free_CSteamID(lobbyToConnectTo); - } - lobbyToConnectTo = cpp_GameJoinRequested_m_steamIDLobby(pCallback); - MainMenu::receivedInvite(lobbyToConnectTo); + handlingInvite = true; + auto lobby = cpp_GameJoinRequested_m_steamIDLobby(pCallback); + SteamMatchmaking()->RequestLobbyData(*lobby); + cpp_Free_CSteamID(lobby); + + //The invite is not actually passed to the rest of the game right here. + //This is because we need to gather data from the lobby about the save game. + //MainMenu::receivedInvite(lobby); } //Helper func. //TODO: Bugger. @@ -1805,7 +1787,10 @@ void cpp_SteamMatchmaking_JoinLobbyPCH(const char* pchLobbyID) { CSteamID steamIDLobby(std::stoull(std::string(pchLobbyID))); if (steamIDLobby.IsValid()) { - MainMenu::receivedInvite(&steamIDLobby); + //The invite is not actually passed to the rest of the game right here. + //This is because we need to gather data from the lobby about the save game. + handlingInvite = true; + SteamMatchmaking()->RequestLobbyData(steamIDLobby); } else { printlog("lobby id for invite invalid"); } diff --git a/src/steam.hpp b/src/steam.hpp index b92f2a97a..83bf9d33d 100644 --- a/src/steam.hpp +++ b/src/steam.hpp @@ -19,7 +19,6 @@ void steam_OnP2PSessionRequest(void* p_Callback); //TODO: Finalize porting. void steam_OnLobbyMatchListCallback(void* pCallback, bool bIOFailure); void steam_OnLobbyDataUpdatedCallback(void* pCallback); void steam_OnLobbyCreated(void* pCallback, bool bIOFailure); -void processLobbyInvite(); void steam_OnGameJoinRequested(void* pCallback); void steam_ConnectToLobby(const char* arg); void steam_OnLobbyEntered(void* pCallback, bool bIOFailure); @@ -27,6 +26,10 @@ void steam_GameServerPingOnServerResponded(void* steamID); void steam_OnP2PSessionConnectFail(void* pCallback); void steam_OnRequestEncryptedAppTicket(void* pCallback, bool bIOFailure); +// determine if the given lobby is using a savegame; if it is, load our compatible save. +// @return true if we are able to join the lobby, otherwise false (because no compatible save was found) +bool processLobbyInvite(void* lobby); + #define MAX_STEAM_LOBBIES 100 extern Uint32 numSteamLobbies; @@ -41,15 +44,12 @@ extern bool requestingLobbies; #include -extern bool serverLoadingSaveGame; // determines whether lobbyToConnectTo is loading a savegame or not extern void* currentLobby; // CSteamID to the current game lobby -extern void* lobbyToConnectTo; // CSteamID of the game lobby that user has been invited to extern std::string cmd_line; // for game join requests #ifdef STEAMWORKS extern char currentLobbyName[32]; extern ELobbyType currentLobbyType; extern bool connectingToLobby, connectingToLobbyWindow; -extern bool stillConnectingToLobby; extern bool joinLobbyWaitingForHostResponse; extern bool denyLobbyJoinEvent; extern int connectingToLobbyStatus; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index e5ec712ce..a223922c2 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -4550,35 +4550,43 @@ namespace MainMenu { if ((settings_subwindow = settingsSubwindowSetup(button)) == nullptr) { auto settings = main_menu_frame->findFrame("settings"); assert(settings); auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); - settingsSelect(*settings_subwindow, {Setting::Type::Boolean, "show_ip_address"}); + settingsSelect(*settings_subwindow, {Setting::Type::Field, "port_number"}); return; } int y = 0; +#if 0 y += settingsAddSubHeader(*settings_subwindow, y, "general", "General"); y += settingsAddBooleanOption(*settings_subwindow, y, "show_ip_address", "Streamer Mode", "If you're a streamer and know what doxxing is, definitely switch this on.", allSettings.show_ip_address_enabled, [](Button& button){soundToggle(); allSettings.show_ip_address_enabled = button.isPressed();}); +#endif + + char port_desc[1024]; + snprintf(port_desc, sizeof(port_desc), "The port number to use when hosting a LAN lobby. (Default: %d)", DEFAULT_PORT); char buf[16]; snprintf(buf, sizeof(buf), "%hu", (Uint16)allSettings.port_number); y += settingsAddSubHeader(*settings_subwindow, y, "lan", "LAN"); y += settingsAddField(*settings_subwindow, y, "port_number", "Port", - "The port number to use when hosting a LAN lobby.", - buf, [](Field& field){allSettings.port_number = (Uint16)strtol(field.getText(), nullptr, 10);}); + port_desc, buf, [](Field& field){allSettings.port_number = (Uint16)strtol(field.getText(), nullptr, 10);}); +#ifdef STEAMWORKS y += settingsAddSubHeader(*settings_subwindow, y, "crossplay", "Crossplay"); - y += settingsAddBooleanWithCustomizeOption(*settings_subwindow, y, "crossplay", "Crossplay Enabled", + y += settingsAddBooleanOption(*settings_subwindow, y, "crossplay", "Crossplay Enabled", "Enable crossplay through Epic Online Services", - false, [](Button&){soundWarning();}, [](Button&){soundWarning();}); + allSettings.crossplay_enabled, [](Button& button){soundToggle(); allSettings.crossplay_enabled = button.isPressed();}); hookSettings(*settings_subwindow, - {{Setting::Type::Boolean, "show_ip_address"}, - {Setting::Type::Field, "port_number"}, - {Setting::Type::BooleanWithCustomize, "crossplay"}}); + {{Setting::Type::Field, "port_number"}, + {Setting::Type::Boolean, "crossplay"}}); +#else + hookSettings(*settings_subwindow, + {{Setting::Type::Field, "port_number"}}); +#endif - settingsSubwindowFinalize(*settings_subwindow, y, {Setting::Type::Boolean, "show_ip_address"}); - settingsSelect(*settings_subwindow, {Setting::Type::Boolean, "show_ip_address"}); + settingsSubwindowFinalize(*settings_subwindow, y, {Setting::Type::Field, "port_number"}); + settingsSelect(*settings_subwindow, {Setting::Type::Field, "port_number"}); } static void settingsGame(Button& button) { @@ -6267,6 +6275,70 @@ namespace MainMenu { } } + static void kickPlayer(int index) { + if (multiplayer == SERVER) { + strcpy((char*)net_packet->data, "KICK"); + net_packet->address.host = net_clients[index - 1].host; + net_packet->address.port = net_clients[index - 1].port; + net_packet->len = 4; + sendPacketSafe(net_sock, -1, net_packet, index - 1); + + char buf[1024]; + snprintf(buf, sizeof(buf), "*** %s has been kicked ***", players[index]->getAccountName()); + addLobbyChatMessage(0xffffffff, buf); + + client_disconnected[index] = true; + +#ifdef STEAMWORKS + if (steamIDRemote[index - 1]) { + cpp_Free_CSteamID(steamIDRemote[index - 1]); + steamIDRemote[index - 1] = nullptr; + } +#endif + + // inform other players + for (int c = 1; c < MAXPLAYERS; ++c) { + if (client_disconnected[c]) { + continue; + } + strcpy((char*)net_packet->data, "DISC"); + net_packet->data[4] = index; + net_packet->address.host = net_clients[c - 1].host; + net_packet->address.port = net_clients[c - 1].port; + net_packet->len = 5; + sendPacketSafe(net_sock, -1, net_packet, c - 1); + } + + if (directConnect) { + createWaitingStone(index); + } else { + createInviteButton(index); + } + checkReadyStates(); + } + } + + static void lockSlot(int index, bool locked) { + if (multiplayer == SERVER) { + playerSlotsLocked[index] = locked; + if (locked) { + if (!client_disconnected[index]) { + kickPlayer(index); + } + createLockedStone(index); + } else { + if (client_disconnected[index]) { + if (directConnect) { + createWaitingStone(index); + } else { + createInviteButton(index); + } + } + } + checkReadyStates(); + } + } + static void sendReadyOverNet(int index, bool ready) { if (multiplayer != SERVER && multiplayer != CLIENT) { return; @@ -6550,7 +6622,7 @@ namespace MainMenu { } // process incoming join request - NetworkingLobbyJoinRequestResult result = lobbyPlayerJoinRequest(playerNum); + NetworkingLobbyJoinRequestResult result = lobbyPlayerJoinRequest(playerNum, playerSlotsLocked); // finalize connections for Steamworks / EOS if (result == NetworkingLobbyJoinRequestResult::NET_LOBBY_JOIN_P2P_FAILURE) { @@ -6894,12 +6966,15 @@ namespace MainMenu { // now set up everybody else for (int c = 0; c < MAXPLAYERS; c++) { - client_classes[c] = net_packet->data[8 + c * (5 + 23) + 0]; // class - stats[c]->sex = static_cast(net_packet->data[8 + c * (5 + 23) + 1]); // sex - client_disconnected[c] = net_packet->data[8 + c * (5 + 23) + 2]; // connectedness :p - stats[c]->appearance = net_packet->data[8 + c * (5 + 23) + 3]; // appearance - stats[c]->playerRace = net_packet->data[8 + c * (5 + 23) + 4]; // player race - strcpy(stats[c]->name, (char*)(&net_packet->data[8 + c * (5 + 23) + 5])); // name + + client_disconnected[c] = net_packet->data[8 + c * (6 + 32) + 0]; // connectedness + playerSlotsLocked[c] = net_packet->data[8 + c * (6 + 32) + 1]; // locked state + client_classes[c] = net_packet->data[8 + c * (6 + 32) + 2]; // class + stats[c]->sex = static_cast(net_packet->data[8 + c * (6 + 32) + 3]); // sex + stats[c]->appearance = net_packet->data[8 + c * (6 + 32) + 4]; // appearance + stats[c]->playerRace = net_packet->data[8 + c * (6 + 32) + 5]; // player race + snprintf(stats[c]->name, sizeof(stats[c]->name), "%s", (char*)(net_packet->data + 8 + c * (6 + 32) + 6)); // name + stats[c]->clearStats(); initClass(c); } @@ -7049,6 +7124,23 @@ namespace MainMenu { continue; } + // lock/unlock a player slot + else if (packetId == 'LOCK') { + int player = net_packet->data[4]; + bool locked = net_packet->data[5]; + playerSlotsLocked[player] = locked; + + // these functions automatically create locked + // stones instead if the player is now locked + if (directConnect) { + createWaitingStone(player); + } else { + createInviteButton(player); + } + checkReadyStates(); + continue; + } + // update player attributes else if (packetId == 'PLYR') { Uint8 player = net_packet->data[4]; @@ -7062,6 +7154,7 @@ namespace MainMenu { stats[player]->clearStats(); initClass(player); } + checkReadyStates(); continue; } @@ -7090,7 +7183,11 @@ namespace MainMenu { disconnectFromLobby(); destroyMainMenu(); createMainMenu(false); - connectionErrorPrompt("You have been kicked\nfrom the remote server."); + if (packetId == 'KICK') { + connectionErrorPrompt("You have been kicked\nfrom the remote server."); + } else { + connectionErrorPrompt("You have been disconnected\nfrom the remote server."); + } } else { char buf[1024]; snprintf(buf, sizeof(buf), "*** %s has left the game ***", players[playerDisconnected]->getAccountName()); @@ -7101,6 +7198,7 @@ namespace MainMenu { createInviteButton(playerDisconnected); } } + checkReadyStates(); continue; } @@ -7330,6 +7428,8 @@ namespace MainMenu { sendPacket(net_sock, -1, net_packet, 0); } + static char last_address[128] = ""; + static bool connectToServer(const char* address, void* pLobby, LobbyType lobbyType) { if ((!address || address[0] == '\0') && (!pLobby || lobbyType != LobbyType::LobbyOnline)) { soundError(); @@ -7358,7 +7458,7 @@ namespace MainMenu { text->setText(buf); // here is the connection polling loop for online lobbies - // TODO this should be moved to lobbies.cpp (actually it was largely lifted from there - put it back!) + // this should be moved to lobbies.cpp (actually it was largely lifted from there - put it back!) if (!directConnect) { #ifdef STEAMWORKS if (LobbyHandler.getJoiningType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { @@ -7457,6 +7557,8 @@ namespace MainMenu { lobby = getLobbySteamID(address); } else if ((char)tolower((int)address[0]) == 's' && strlen(address) == 5) { + // save address for next time + snprintf(last_address, sizeof(last_address), "%s", address); connectingToLobby = true; connectingToLobbyWindow = true; joinLobbyWaitingForHostResponse = true; @@ -7491,24 +7593,50 @@ namespace MainMenu { if (address) { const char epic_str[] = "epic:"; if (strncmp(address, epic_str, sizeof(epic_str) - 1) == 0) { - lobby = getLobbyEpic(address); + if (LobbyHandler.crossplayEnabled) { + lobby = getLobbyEpic(address); + } else { + closePrompt("connect_prompt"); +#ifdef STEAMWORKS + connectionErrorPrompt("Failed to connect to lobby.\nCrossplay not enabled."); +#else + connectionErrorPrompt("Failed to connect to lobby.\nNot connected to Epic Online."); +#endif + multiplayer = SINGLE; + disconnectFromLobby(); + return false; + } } else if ((char)tolower((int)address[0]) == 'e' && strlen(address) == 5) { - memcpy(EOS.lobbySearchByCode, address + 1, 4); - EOS.lobbySearchByCode[4] = '\0'; - EOS.LobbySearchResults.useLobbyCode = true; - EOS.bConnectingToLobby = true; - EOS.bConnectingToLobbyWindow = true; - EOS.bJoinLobbyWaitingForHostResponse = true; - LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); - LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); - flushP2PPackets(100, 200); - EOS.searchLobbies( - EOSFuncs::LobbyParameters_t::LobbySearchOptions::LOBBY_SEARCH_ALL, - EOSFuncs::LobbyParameters_t::LobbyJoinOptions::LOBBY_JOIN_FIRST_SEARCH_RESULT, - ""); - EOS.LobbySearchResults.useLobbyCode = false; - return true; + // save address for next time + snprintf(last_address, sizeof(last_address), "%s", address); + if (LobbyHandler.crossplayEnabled) { + memcpy(EOS.lobbySearchByCode, address + 1, 4); + EOS.lobbySearchByCode[4] = '\0'; + EOS.LobbySearchResults.useLobbyCode = true; + EOS.bConnectingToLobby = true; + EOS.bConnectingToLobbyWindow = true; + EOS.bJoinLobbyWaitingForHostResponse = true; + LobbyHandler.setLobbyJoinType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); + LobbyHandler.setP2PType(LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY); + flushP2PPackets(100, 200); + EOS.searchLobbies( + EOSFuncs::LobbyParameters_t::LobbySearchOptions::LOBBY_SEARCH_ALL, + EOSFuncs::LobbyParameters_t::LobbyJoinOptions::LOBBY_JOIN_FIRST_SEARCH_RESULT, + ""); + EOS.LobbySearchResults.useLobbyCode = false; + return true; + } else { + closePrompt("connect_prompt"); +#ifdef STEAMWORKS + connectionErrorPrompt("Failed to connect to lobby.\nCrossplay not enabled."); +#else + connectionErrorPrompt("Failed to connect to lobby.\nNot connected to Epic Online."); +#endif + multiplayer = SINGLE; + disconnectFromLobby(); + return false; + } } } else if (pLobby) { @@ -7566,9 +7694,8 @@ namespace MainMenu { port = DEFAULT_PORT; } - // TODO save address for next time - //strcpy(last_ip, connectaddress); - //saveConfig("default.cfg"); + // save address for next time + snprintf(last_address, sizeof(last_address), "%s", address); // resolve host's address printlog("resolving host's address at %s...\n", address); @@ -8467,9 +8594,11 @@ namespace MainMenu { } static void characterCardLobbySettingsMenu(int index) { - bool local = currentLobbyType != LobbyType::LobbyOnline; + const bool local = currentLobbyType == LobbyType::LobbyLocal || currentLobbyType == LobbyType::LobbyJoined; + const bool online = currentLobbyType == LobbyType::LobbyOnline; auto card = initCharacterCard(index, 424); + const std::string name = std::string("card") + std::to_string(index); static void (*back_fn)(int) = [](int index){ createCharacterCard(index); @@ -8506,11 +8635,11 @@ namespace MainMenu { custom_difficulty->setBackground("*images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Customize00A.png"); custom_difficulty->setFont(smallfont_outline); custom_difficulty->setText("Game Flags"); - custom_difficulty->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + custom_difficulty->setWidgetSearchParent(name.c_str()); custom_difficulty->addWidgetAction("MenuStart", "confirm"); custom_difficulty->setWidgetBack("back_button"); custom_difficulty->setWidgetUp("hard"); - custom_difficulty->setWidgetDown("invite"); + custom_difficulty->setWidgetDown(online ? "invite" : "player_count_2"); custom_difficulty->setWidgetRight("custom"); switch (index) { case 0: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(0);}); break; @@ -8524,7 +8653,7 @@ namespace MainMenu { invite_label->setFont(smallfont_outline); invite_label->setText("Invite Only"); invite_label->setJustify(Field::justify_t::CENTER); - if (local) { + if (!online) { invite_label->setColor(makeColor(70, 62, 59, 255)); auto invite = card->addImage( @@ -8545,14 +8674,24 @@ namespace MainMenu { invite->setColor(0); invite->setBorderColor(0); invite->setHighlightColor(0); - invite->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + invite->setWidgetSearchParent(name.c_str()); invite->addWidgetAction("MenuStart", "confirm"); invite->setWidgetBack("back_button"); invite->setWidgetUp("custom"); - invite->setWidgetDown("friends"); + invite->setWidgetDown("player_count_2"); if (index != 0) { invite->setCallback([](Button&){soundError();}); } else { + invite->setCallback([](Button& button){ + soundActivate(); + auto parent = static_cast(button.getParent()); + auto invite = parent->findButton("invite"); assert(invite); + auto friends = parent->findButton("friends"); assert(friends); + auto open = parent->findButton("open"); assert(open); + friends->setPressed(false); + open->setPressed(false); + // TODO set lobby state to invite-only + }); } } @@ -8561,7 +8700,7 @@ namespace MainMenu { friends_label->setFont(smallfont_outline); friends_label->setText("Friends Only"); friends_label->setJustify(Field::justify_t::CENTER); - if (local) { + if (!online) { friends_label->setColor(makeColor(70, 62, 59, 255)); auto friends = card->addImage( @@ -8582,7 +8721,7 @@ namespace MainMenu { friends->setColor(0); friends->setBorderColor(0); friends->setHighlightColor(0); - friends->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + friends->setWidgetSearchParent(name.c_str()); friends->addWidgetAction("MenuStart", "confirm"); friends->setWidgetBack("back_button"); friends->setWidgetUp("invite"); @@ -8590,6 +8729,16 @@ namespace MainMenu { if (index != 0) { friends->setCallback([](Button&){soundError();}); } else { + friends->setCallback([](Button& button){ + soundActivate(); + auto parent = static_cast(button.getParent()); + auto invite = parent->findButton("invite"); assert(invite); + auto friends = parent->findButton("friends"); assert(friends); + auto open = parent->findButton("open"); assert(open); + invite->setPressed(false); + open->setPressed(false); + // TODO set lobby state to searchable by friends only + }); } } @@ -8598,7 +8747,7 @@ namespace MainMenu { open_label->setFont(smallfont_outline); open_label->setText("Open Lobby"); open_label->setJustify(Field::justify_t::CENTER); - if (local) { + if (!online) { open_label->setColor(makeColor(70, 62, 59, 255)); auto open = card->addImage( @@ -8619,7 +8768,7 @@ namespace MainMenu { open->setColor(0); open->setBorderColor(0); open->setHighlightColor(0); - open->setWidgetSearchParent(((std::string("card") + std::to_string(index)).c_str())); + open->setWidgetSearchParent(name.c_str()); open->addWidgetAction("MenuStart", "confirm"); open->setWidgetBack("back_button"); open->setWidgetUp("friends"); @@ -8627,8 +8776,250 @@ namespace MainMenu { if (index != 0) { open->setCallback([](Button&){soundError();}); } else { + open->setCallback([](Button& button){ + soundActivate(); + auto parent = static_cast(button.getParent()); + auto invite = parent->findButton("invite"); assert(invite); + auto friends = parent->findButton("friends"); assert(friends); + auto open = parent->findButton("open"); assert(open); + invite->setPressed(false); + friends->setPressed(false); + // TODO set lobby state open to all + }); } } + + auto player_count_label = card->addField("player_count_label", 64); + player_count_label->setSize(SDL_Rect{40, 266, 116, 40}); + player_count_label->setFont(smallfont_outline); + player_count_label->setText("Set Player\nCount"); + player_count_label->setJustify(Field::justify_t::CENTER); + + for (int c = 0; c < 3; ++c) { + const std::string button_name = std::string("player_count_") + std::to_string(c + 2); + auto player_count = card->addButton(button_name.c_str()); + player_count->setSize(SDL_Rect{156 + 44 * c, 266, 40, 40}); + player_count->setFont(smallfont_outline); + player_count->setText(std::to_string(c + 2).c_str()); + player_count->setBorder(0); + player_count->setTextColor(uint32ColorWhite); + player_count->setTextHighlightColor(uint32ColorWhite); + player_count->setBackground("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00A.png"); + player_count->setBackgroundHighlighted("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00B_Highlighted.png"); + player_count->setBackgroundActivated("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00C_Pressed.png"); + player_count->setColor(uint32ColorWhite); + player_count->setWidgetSearchParent(name.c_str()); + player_count->addWidgetAction("MenuStart", "confirm"); + player_count->setWidgetBack("back_button"); + player_count->setWidgetUp(online ? "open" : "custom_difficulty"); + player_count->setWidgetLeft((std::string("player_count_") + std::to_string(c + 1)).c_str()); + player_count->setWidgetRight((std::string("player_count_") + std::to_string(c + 3)).c_str()); + player_count->setWidgetDown((std::string("kick_player_") + std::to_string(c + 2)).c_str()); + switch (c) { + default: soundError(); break; + case 0: + player_count->setCallback([](Button&){ + if (client_disconnected[2] && client_disconnected[3]) { + lockSlot(1, false); + lockSlot(2, true); + lockSlot(3, true); + soundActivate(); + } else { + if (!client_disconnected[2] && !client_disconnected[3]) { + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "This will kick %s\nand %s!", + players[2]->getAccountName(), players[3]->getAccountName()); + binaryPrompt(prompt, "Okay", "Go Back", + [](Button&){ // okay + lockSlot(1, false); + lockSlot(2, true); + lockSlot(3, true); + soundActivate(); + closeBinary(); + }, + [](Button&){ // go back + soundCancel(); + closeBinary(); + }); + } + else if (!client_disconnected[2]) { + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "This will kick %s.\nAre you sure?", players[2]->getAccountName()); + binaryPrompt(prompt, "Okay", "Go Back", + [](Button&){ // okay + lockSlot(1, false); + lockSlot(2, true); + lockSlot(3, true); + soundActivate(); + closeBinary(); + }, + [](Button&){ // go back + soundCancel(); + closeBinary(); + }); + } + else if (!client_disconnected[3]) { + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "This will kick %s.\nAre you sure?", players[3]->getAccountName()); + binaryPrompt(prompt, "Okay", "Go Back", + [](Button&){ // okay + lockSlot(1, false); + lockSlot(2, true); + lockSlot(3, true); + soundActivate(); + closeBinary(); + }, + [](Button&){ // go back + soundCancel(); + closeBinary(); + }); + } + } + }); + break; + case 1: + player_count->setCallback([](Button&){ + if (client_disconnected[3]) { + lockSlot(1, false); + lockSlot(2, false); + lockSlot(3, true); + soundActivate(); + } else { + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "This will kick %s.\nAre you sure?", players[3]->getAccountName()); + binaryPrompt(prompt, "Yes", "No", + [](Button&){ // yes + lockSlot(1, false); + lockSlot(2, false); + lockSlot(3, true); + soundActivate(); + closeBinary(); + }, + [](Button&){ // no + soundCancel(); + closeBinary(); + }); + } + }); + break; + case 2: + player_count->setCallback([](Button&){ + lockSlot(1, false); + lockSlot(2, false); + lockSlot(3, false); + soundActivate(); + }); + break; + } + } + + auto kick_player_label = card->addField("kick_player_label", 64); + kick_player_label->setSize(SDL_Rect{40, 310, 116, 40}); + kick_player_label->setFont(smallfont_outline); + kick_player_label->setText("Kick Player"); + kick_player_label->setJustify(Field::justify_t::CENTER); + + for (int c = 0; c < 3; ++c) { + const std::string button_name = std::string("kick_player_") + std::to_string(c + 2); + auto kick_player = card->addButton(button_name.c_str()); + kick_player->setSize(SDL_Rect{156 + 44 * c, 310, 40, 40}); + kick_player->setFont(smallfont_outline); + kick_player->setText(std::to_string(c + 2).c_str()); + kick_player->setBorder(0); + kick_player->setTextColor(uint32ColorWhite); + kick_player->setTextHighlightColor(uint32ColorWhite); + kick_player->setBackground("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00A.png"); + kick_player->setBackgroundHighlighted("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00B_Highlighted.png"); + kick_player->setBackgroundActivated("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00C_Pressed.png"); + kick_player->setColor(uint32ColorWhite); + kick_player->setWidgetSearchParent(name.c_str()); + kick_player->addWidgetAction("MenuStart", "confirm"); + kick_player->setWidgetBack("back_button"); + kick_player->setWidgetLeft((std::string("kick_player_") + std::to_string(c + 1)).c_str()); + kick_player->setWidgetRight((std::string("kick_player_") + std::to_string(c + 3)).c_str()); + kick_player->setWidgetUp((std::string("player_count_") + std::to_string(c + 2)).c_str()); + switch (c) { + default: soundError(); break; + case 0: + kick_player->setCallback([](Button&){ + if (client_disconnected[1]) { + soundError(); + return; + } + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "Are you sure you want\nto kick %s?", players[1]->getAccountName()); + binaryPrompt(prompt, "Yes", "No", + [](Button&){ // yes + soundActivate(); + closeBinary(); + kickPlayer(1); + }, + [](Button&){ // no + soundCancel(); + closeBinary(); + }); + }); + break; + case 1: + kick_player->setCallback([](Button&){ + if (client_disconnected[2]) { + soundError(); + return; + } + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "Are you sure you want\nto kick %s?", players[2]->getAccountName()); + binaryPrompt(prompt, "Yes", "No", + [](Button&){ // yes + soundActivate(); + closeBinary(); + kickPlayer(2); + }, + [](Button&){ // no + soundCancel(); + closeBinary(); + }); + }); + break; + case 2: + kick_player->setCallback([](Button&){ + if (client_disconnected[3]) { + soundError(); + return; + } + char prompt[1024]; + snprintf(prompt, sizeof(prompt), "Are you sure you want\nto kick %s?", players[3]->getAccountName()); + binaryPrompt(prompt, "Yes", "No", + [](Button&){ // yes + soundActivate(); + closeBinary(); + kickPlayer(3); + }, + [](Button&){ // no + soundCancel(); + closeBinary(); + }); + }); + break; + } + } + + if (local || loadingsavegame) { + player_count_label->setColor(makeColor(70, 62, 59, 255)); + kick_player_label->setColor(makeColor(70, 62, 59, 255)); + for (int c = 0; c < 3; ++c) { + auto player_count = card->findButton((std::string("player_count_") + std::to_string(c + 2)).c_str()); + player_count->setBackground("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00D_Gray.png"); + player_count->setBackgroundHighlighted("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00D_Gray.png"); + player_count->setDisabled(true); + auto kick_player = card->findButton((std::string("kick_player_") + std::to_string(c + 2)).c_str()); + kick_player->setBackground("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00D_Gray.png"); + kick_player->setBackgroundHighlighted("*#images/ui/Main Menus/Play/PlayerCreation/LobbySettings/UI_LobbySettings_Button_Tiny00D_Gray.png"); + kick_player->setDisabled(true); + } + } else { + player_count_label->setColor(makeColor(166, 123, 81, 255)); + kick_player_label->setColor(makeColor(166, 123, 81, 255)); + } } static void characterCardLobbySettingsMenuOLD(int index) { @@ -8756,7 +9147,7 @@ namespace MainMenu { normal->select(); } - // TODO on non-english languages, normal text will need to be used. + // NOTE: on non-english languages, normal text will need to be used. // here's the code for that, but how to gate it? /*auto hard_label = card->addField("hard_label", 64); hard_label->setSize(SDL_Rect{68, 178, 144, 26}); @@ -12191,12 +12582,15 @@ namespace MainMenu { #if defined(STEAMWORKS) for (Uint32 c = 0; c < numSteamLobbies; ++c) { + auto lobby = (CSteamID*)lobbyIDs[c]; + auto pchFlags = SteamMatchmaking()->GetLobbyData(*lobby, "svFlags"); + auto flags = (int)strtol(pchFlags, nullptr, 10); LobbyInfo info; info.name = lobbyText[c]; info.players = lobbyPlayers[c]; info.ping = 50; // TODO - info.locked = false; // TODO - info.flags = 0; // TODO + info.locked = false; // this will always be false because steam only reported joinable lobbies + info.flags = (Uint32)flags; info.address = "steam:" + std::to_string(c); addLobby(info); } @@ -12679,7 +13073,7 @@ namespace MainMenu { static const char* ipaddr = "Enter an IP address to connect to."; static const char* roomcode = "Enter the code to a lobby you wish to connect to."; guide = strcmp(button.getText(), "Enter IP\nAddress") ? roomcode : ipaddr; - textFieldPrompt("", guide, "Connect", "Cancel", + textFieldPrompt(last_address, guide, "Connect", "Cancel", [](Button&){ // connect const char* address = closeTextField(); // only valid for one frame if (guide == ipaddr) { @@ -12985,7 +13379,10 @@ namespace MainMenu { } }); - auto crossplay_fn = [](Button&){}; + auto crossplay_fn = [](Button& button){ + soundToggle(); + LobbyHandler.crossplayEnabled = button.isPressed(); + }; auto crossplay_label = window->addField("crossplay_label", 128); crossplay_label->setJustify(Field::justify_t::CENTER); @@ -13978,6 +14375,13 @@ namespace MainMenu { } } else if (info.multiplayer_type == CLIENT || info.multiplayer_type == DIRECTCLIENT) { loadGame(info.player_num); + for (int c = 0; c < MAXPLAYERS; ++c) { + if (info.players_connected[c]) { + playerSlotsLocked[c] = false; + } else { + playerSlotsLocked[c] = true; + } + } multiplayer = SINGLE; createDummyMainMenu(); createLobbyBrowser(button); @@ -14720,7 +15124,9 @@ namespace MainMenu { {"Controls", settingsControls}, }; if (intro) { +#ifndef NINTENDO tabs.push_back({"Online", settingsOnline}); +#endif } else { tabs.push_back({"Game", settingsGame}); } @@ -16313,7 +16719,20 @@ namespace MainMenu { destroyMainMenu(); createMainMenu(false); } +#ifdef STEAMWORKS + if (processLobbyInvite(lobby)) { // load any relevant save data + connectToServer(nullptr, lobby, LobbyType::LobbyOnline); + } else { + auto str = LobbyHandler.getLobbyJoinFailedConnectString(EResult_LobbyFailures::LOBBY_USING_SAVEGAME); + monoPrompt(str.c_str(), "Okay", + [](Button&){ + soundCancel(); + closeMono(); + }); + } +#else connectToServer(nullptr, lobby, LobbyType::LobbyOnline); +#endif } else { saved_invite_lobby = lobby; disconnectFromLobby(); From cf9ae13c56999b5f1eb2cae545ae17efa8ba3ec7 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sat, 23 Jul 2022 14:32:08 -0700 Subject: [PATCH 13/21] remove fonts from git (they belong in the data repo) also fix syntax error --- fonts/pixel_maz.ttf | Bin 28180 -> 0 bytes fonts/pixel_maz_large.ttf | Bin 26752 -> 0 bytes fonts/pixel_maz_multiline.ttf | Bin 28280 -> 0 bytes fonts/pixelmix.ttf | Bin 22184 -> 0 bytes fonts/pixelmix_bold.ttf | Bin 29508 -> 0 bytes src/steam.cpp | 5 +++-- 6 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 fonts/pixel_maz.ttf delete mode 100644 fonts/pixel_maz_large.ttf delete mode 100644 fonts/pixel_maz_multiline.ttf delete mode 100644 fonts/pixelmix.ttf delete mode 100644 fonts/pixelmix_bold.ttf diff --git a/fonts/pixel_maz.ttf b/fonts/pixel_maz.ttf deleted file mode 100644 index ecd92a986e22c83621a948a5034522a026504494..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28180 zcmeI53yfXIS;yzxd-whFdcEFt)^^VB+Hn%Ez3aHnyX{TmI8BqbshtM9lq4H_V{hWM zt@S2O3WTGolwd`rNJUWbXhnDk38;CLx<(=wC=@|JD^aNt3J`%*@dzYBJjBc9|C`4> zbMIMS+erhe%CXO#IdkUB%=dl&?=dsy?lp-qX24{oY)T1w4JDs<`7}Nee zy8n&CvnNlee3ja((B9#r&pftq>(0-s{_VzmodOF^AN@d3j;>=@X^rOONZ`eqE=>W*6qJ z-g4hjW9B|;OvewMIDT^JAFez7MPrWA=IDvVxf9R+$ZPY)bRAOtpM_G+kKg><_dPst z`@fhmhH1?24Vzu0fBy5obM!*(LbYT{Tz0xdIXzP{7pnTKGCJL*rTBQlmONo*44ko5 z^tpyi>1JC}Nyd{;n>JH%tzY=OOV!p+^Aa`OSd7WUZu^A)*t73}y{2kRy1|dZn3`_c z5hNzj9g{LLt>Y^O!kgD#?T%UoGC2rSwLee%8DoDgIl!F5Ph9f^E7_ z+;wH6>3WqV7yiYV3ol-H{=$O24~UE;cuvb5$~Uy|?#J{bXf>fW=a>;C<}k|dux za5lN{{b$YY4PO>I%MU-a?W{?r()6zR-Di?VbWxg8!S&CbWeI@_RyKiW}E89-01_;=^3+c|GYjQ*gt-zdSI-cojY*gR<%>19X+Dzrw^!w z$9)S*ZPE09t5Vz4z3G|qHTUoT(Ec;o?y)n~-3P|T$J4vc)b8JZrnY-*{J;U#YOjw} zBmUm`Q9tGmjk*1L-S2d*?Aw2)I(Eh!IDMKn_wAn?Ka-t4J$71j`0KN#)>M#~=7Op( z5GT}zIC0n6B)eaYDVZD}qhNA;a$Ms)uv>k1P2Ib1|6LmCI3w+D&GMc)A*$P};rCdU z_kELC-hVkPAGj=*ud0uD`*VD23iH6L_=C z=%^HW^;Ywu?lcy?D0?v_d%IpXnFvCa&dE?$%-~ju#6e8zL#ceeDbaq(WnNS!Zmg5` zljb6ZQ!yQC&A8UoiF`s;|8x-BV0^46r8HZi+{l~guyIRS&-TmOtu;RgwIaFauKj9z zdhE-pGVrbg46n`fZ9S)6<#v?z$TarR|EjZQ;1!n+DIL)DnpfoYMy92GI!9DsmT64a z%9niQwMx5HzE)*@O1U@SuLqZBcI+4&A1v>hoaksD9@#ZnKTGBdADS2)-MjaN7xwNg zne!%@`_Rbf#KU{{y8o6W2&>9xm*p+*h$*T-QN>XNhoGRMj@w?b5_ScURMAMvO4~J( zU4uIZJ9h0Hu2!pAVlJ4hS`|CsaLxBowS6GW#ugHqUKTStMSMkPrwRfy%3=V0cf2yQ zV^GcSbX_DS)1ijw0~tz+W9I7Q?wF1a=;$y*?>gW`)k8r~R4_onKvd90L6;e>Khj4* zUsRBb+c!EwjS;w|QEH@#7ab#qw2D&7+mv=GZC8pI2bZP4I$_|D(lt7%Kdf}E&UHGk z(z#~US+id0YNZ>LZq&J6`Kw`BDt7hiOjQtAhE$MFr%%V!AsFga86c!n07xg4jrx>M z#&o?=(%iXga+f4s%WlWH;%Qlv^B`lf9KoO|jKv(x5Q~o({so!HCK0C|DO2 z^it3p393v%*5NGOqFg_I<9m=Cybv&5}PCi#9VphjV$PV{G$?hopOsYt$GDTsiX zNE-cX)VsQ+!-#iUc=f0vosxE_F**iPq%=&YG>noXH8_scuG2$r z<2jU`_K_e`9+Sj|kd98QYkWFWm{s$C$gTN6L|s-+nYY71P8&4i*_`}REGuu^a`RV^ zRlz`RL^K;CSxrzd;R+zpYNJx@AlL27jIp_q@PCPCmH-7;>JOFAz55k@erj0;B5Og)x*E(_?_0(1hP9d; zPd{Xz(P*Q1s0D^PK}csKLQsNcgg9-kOT}>sDZnl4CMk9k2vHgCb$c$A6^{&yGu5gH zg?LRp6_zMd9RClz&Es#kXmaDjbTQ7@ONVh2;H*nR9F;W>+sP@ZD&!e3ANO)%<{g|L zDpgY3T{xswa!4@7I;7WwLwX}*SbW0=VaY&k2OjEmDS-7*Z-fj+o@%jMqSXP#V?&Ya zATWT{Vm2VrCs~Irp-ZI5jxNT0E|``$vLZlQDUgCw!EhwX*CEoQu0o_8j5J|s?B2k< zJFA)2A!=IJtm5d)Bh~lJj*goozYa*ZhgB+* z5dlm77uBnGCgXCr8O$OwHg-&9vpF79%$g*R!WI8pgzHgBQ-|hBE}qB6|Jn*Iw9rZ) zVp+P8a4Wlw@4T4L?Z=KfpUDKwi3K|r5Z^A|aSifNdyGA4e`Mc+pQ{N){6m2yIvvW? zX|1Y#7kB(_Olu+HCx~dz-hAr zkzNcc>>CtQ)%y9G3b_G(a-NPP<+%Mzb9EJVeN|(WM#s%u4crXeL!o2N5}^SKQk*N! z(?)MfiPUhS&db+*0}BUCxgA&W5d*4J@+n;BIvBA&FH*t@q(D@L_=&0Ml*aMEYa>Jk zsR)_UIr$YU9r;$rL&?S+8A;%$=Ds;Uhs@u4kWIrBIXTJ)7LP>clqL>&VQlQYO;mS`8y0D5# zkTo-sV`6IhM4XY$X;VDir|Igsc%%jG=tmi}@xZbG7J*S=4M-UTgJLOwK_ECVN*Nes zRsmm85O@t1UHB`iR@eQyYmf$L5Q@;OFeQN%paAJpCkSbksC{Q5DiMzr5dmaPBwsMK zS_ZxTh-fbR+1;PEq`6>cbBjPo-DAZC17HZqY_<%5i17nBXn}ZW0B8ZRFv6o&8Y^Z{ zG1y%4OzU9=LNfju?>=M(}{#ZLx^Z|>3bX#*0sA3O}J+f;SCgkjJBd}h-TLqUe~3+ z?K2FQ2o8+U3a*IeVGY;d#JQrG9a`G)41gh7CO9|?=N+$_a&!LJPq;b%O)(z|+E75o zN;7&G)QkvbY9Kg-si=t^y;4Trsr#r&KnQKH$i;Ka`g@hevqeliD{1j8k#!$8Llm>u zHz5iXUA`B0XpH>|1ENzTyjCf!W=2T)RZ_(v&+vdei^Ld(!e9+46wD@o^mz1soAsB! zzA`!F8tP0_0BExuP^@64V*aDWC>(OIwp1w^ijrMaOE1M!c!S2mE7YEJMU54oH1XJF z=XsV()vV5@np}+-R1#(t|CN&?8C1?%MIiU-;mRCEj{W(9Y_h)hmFsy-3c-8P%`-T$@(qE6V5UNic3sC zFJZJ|&9kbGN=q7-LWU$%cW>A*u~87vM|8NogQ|Gi5*yBF$f5D_Ac=2JjUbtW$~%s7Gi( zIjovedacS~A$b;!sY5tG9oWQn&xp#AO}utXND;P#P&L+T*DGZa8P=vsuU0y)bV6sU zJZra`xK@6%(rdU@ev8uUbY8FXT3v5bx}Aj+H;p}~>4n2Xr#76y#abUdBU5nEskEeY zKq>BtYZMG3*kQ*1hOh(Tcn)H?&q6c=Kn$@E3(-SLS%{_`2m@OXMtumQK7>&p;;0XC z)Q3RoLm>S^B;gK3GQMk-(m%wKPAR3{cBRyt_LvL{*@>2JSxOtxIj%E=Mx?4E<1jv) z)wGsdv+LUe=lPn$C>hxUt~lJ1Z%y#6X?~Msl=%anZEZ(YSROEEHIj?(SQVnzcuCN% z5&E5FJKMum5Jt6$ymp-xU1N&DYrFE`wOc1F>*FN=R%)pfCNW0&;P?P4jZm2eNOME# z!bdH2!y~QeXbslta5ckPLVlL9<}Fg~yv4?eZvUrYEut`QX-5y+qy1JNE7hm7jDr~k z#D*CKvo@~7q>FJPZyYuX+NZQ%C(42`gih>Mm(n;!A7b)u{gN1i76No8?>qq5OG@Ke zUa$moL>&}>j>z+t#7)CY?%n>TU85C{yjZIt&C1q=8NlWPy+ybL%m!@R7WTt4iuQ##tr(TujBAmpBk+)G(I; zA=vR<^M>8lHW%a$lNz+Odc+))PzYav2{!$dXex68cmP4?TeKYFvBWs$tFNiVeQ zt%Xe@nsAGu0ex5(v=z`aFFNJ&^QNdLxWFY8pE2SFmH8gqm^d5C4RA<3$27Y*!2 z%dn({7J2hyuWDX)-u8UDB~>jqg{-B`CsaHpVR*VR9>9;+s(>Fu2Z|VsF!b;a z4kRG?+LY2=ebKv z+h_@1(>1EjHC+Y+&2|TCP2)1kT5+<*%g)^EOb|0zVbfu8Q-8Z8>5F+$kVE11gu*Ld zo_*GlSwO|%z9Y@)31K>HWbAl0Xkq-=IL9`JuK|@IULtSQCV!ViqUHre!nXktg1NB@ zsEPxsh)hTUCXLf5DK3$68l*ECIv@xq}kB7rjR%Zvt+jiEn@IawX7%sA+!?IhG3XkP_R)(Hp=bbz5M1SXl+#U9$&vk zgILimA|ytP6uNZ1vRIwT!G)|s#CC5_Vr7i|XFM)3(9z-Khp#U@3#B+FA%2$hBM6qsRA>R5Ct6&1%;zE<<~=d5ymI6# zUa{?l{5&pGaKKg?aiH}lQc>Zx>gMgbk11jPQ5g;iJCh!_6;3@d#T|3_U=bu|Z#82>=UHw%%k1x5%y1y7Qe{^NETnt}e zXl0r-L=6v-BjV<0&0{I-@q|S;7iGa5qOD+UpcMduDKJBx$P88EJqPw`CmOOv+#llslk5~kkc{@07eJ`fg#y84X9lwgC=$fS_KCNm~!arGd2$G zQlR7F(QX@$@-P%5eH!5!HOyHjc<$2b$sjcD97fur#2)rr9+~`zZy#%VKpL2z6qtK`uvVUu-M%3`4DqwY@{ zaq!w(l>bTwAcz}LUrnAB-pt^Zs&QQWUz7GRImz{`#uTW zh~FmxH*f^wfH`FT*bsF9sJH%piM2iIhSpdr!b#Y8#Lo2rmQLWM?@%i);HMYsPIy#Y`V5q#4)3Hmuq$3{Z}h+ zE}!6d&gBJulAjmEVGE8Mc)&(D02^(Lp|s8g^14@FQl`QsmYzaDP<9Jh7v0BJOR5#c*f?OjzMcZ=1L~CZCR|>33?F&;s|00B8ZXPqq+{w^>{q&R|Jpe?66Tq z7)m2pB2dhAzf#a#kG+|lI-_7cPVhW-AA?ZR8VKNBZw>ny1@Pr;6@FMy z@;s_X#86vDfT%|wx-~!s45hj1DMDW9RZ=Fg=q(SzHc}oW_496)mhE~92CwSq@G1)_ z*7i%kf?Pk@Ql{~;PY8N~2+@*e5jxQcR9CvC}U}tPJ{0!@gMI+qQ)@ zzIc#k;V}YwwfiGV2|mL}zy<&dKynL+&YC&D>!=wF zvwn;|4d>=dDMK_J#U(S z#HQJ*OCbo4SrFjIXzU0zN?_k?Enq?2|MAs+oc4eUks@i}AtZ7Qi9wHoB!@(h-D6UU zHf5F|Y$MSq#8or9E_p#kR-W#AsQ=kgoBXo>VDmS<8`iPfG@P=|J`Jr|y@I%rQlOi8 z0wBb|v22eG;Z2FvDkEj-ek~#&6@r~Jb3B0Nz>B(3Qe}8xS2L8-7%GDmxTT@;O*rj}~&K3rnTt7=i@3m16F*#(aCYT!dHEFVb3uY}6afhqq02&|@@l)%S&k(v06BTruj6f^OU*S{y-_oTH->V5Q3!ntyp8v_ zg7(AX5Dy3Q0u&n;M5R69yPa?AK9@o=`ILrzF2jz#_!0PT?Je`xlP$T?lK22BN3e+F z{e!a3Zc!e91KONYy0p`q1-HO0JfI9Uw6lT=ct7B@;FnvDtr_D1ADDe`-fZ^4dTjFD z{G=K844onC48jHDE17moH`s@~+?&hQc@rLj;hM}ghjalFW&6CpBNlFm#4Z*K=f3V@ zv9L2W&YgtN1u=Z{_n#D(=rui&lhcB3mGupV)-xObCtGq_{2vdI-#*7%FE+$Lkzzse z7Gtv@gM!7vBn0VUAFBC#MVAsHF&zTTeR9LhhE<7S{ICSyJ#-&zu^7Oa`wWnHQ zwxxA}_MlU!4Y+d#+D-*|DiZ)z8Hv zEJO#fR>2pm3`+C;52syW(FFd6APSAD2aWtOKY~W2L?fh7!@zmK#t2Gj04?G^0TTBI zM3ANdBE^2!%?3pBAeva}BOD%oN^0^YSoSk5DJx8KS}z!31T>VeXcQLg1Fa0ZPV36w zVRv2;M&TWy5!!eNMmdXNr#Zuc&Olm5h7V|TcG2mWlVB~T(_4hxgB6TO9M0MJE|xJv zK|BF`(1k-q$pWo)Itfz%VVIY~pkUyUZAsTW1ag@WAY-f%bO-wToU>)I}|>H(GKsSprO!tiLb{{B`9(1JRIyB zSs;Qlf!h&pS454CSMoKZH?EBKSin}|@gfqw@nQ@?h|GXN1%h*uj~0yr0>YmO2uJ|o z0SPn!GLSfI$Pur`b&biTeppOmR}aK69fCda<{We5I!bCXxMGxJl{W=`jV;27=8(z8 zZz8|Y4EP1#Q05RCR5aXJOD@7Q=aT+6!Ij%oq|mQn!qwQp#rjw0E+B-vHJu!C@z%*p z z8k=Fdj6EvYL2aDA$=&y9BNFe~ryFM3rTitm@b0CgCe7fRzJ$*Ri}BbGtfqbcQaVxd zwiCJidptTnvC6$hAu`ELM)3+BAra%9k*nm$6eo*Pz9A zt+ei5mYusnpl~_7B?8QK*7smCF$;ESvq{V&=ROJLs^VZ~c{d*OET0V3C!o9n!oGS^ zJI_Na@jYp1fHu6XEwIIhNY(tpf!+8rorPviBSL{mA z0-)HfvcUDazZ3T&0~W%lumh97%|f_@(!**xpfSE0N(!zKCC(Dj;w)Kb4Q9MP`E=+|Egj(%i^xT>{H#Z^s?~k9;?*)%AY8toPQrjB1Ibj|# z%d+}8t^C>Mh}K;mOYI?1uIYn>M^>!|!k0zVW$@uU-Av)n7}W zO8;cMd;G!imnJF`&riHOdEex(ZhFV2Pi*?9&4)I>dd)|#`R6S^bL|b+K6~Bz>u$a7 zwdh#pPslVCQzHO7}N|R4ou0rEFop6}XE?v2NN&K4dc_CE4tl-M^ zr@r;u2rkd6`oliou4ff_T;lZKV)uCzAC**Dp>X#N@br<%<9rEpD&sI(%n8^Ht#Ea*yk%| zqVzeRZ!>F4zwPtwdiLu+-(j9E{jJY;n%m2>KHp{5lz-CayUnM{zvA;fW_#t`KHqB= zD+@l~XCAElrO)@9kG6%e4VkCge)!JgC!SfHKYU~B#KT!tBS=8)jy1zJLDd zxufa(XFs-g?(maGXBQuwTRb^`{8+kU`^;_p$jWWD!Zua7jq11Ux$mYMX6g^6#i#1^ z_Z&aAv~Owg$%9Mj{K<4SU0R$yG`BFj_(XdAv2@SAds18B$ng{DJ;#>j7LUy?so&Y7 z>D>#DzW2z|(urHAryo-jCv6kkZB~`HA3VO0lkmV1HO;N`)a=Rh;Nslu(%hl+$&>TP z4!h=E|JSx|?>c|`)TvVq{j`268l47ya{A(%eeo_RZv!tt8eJ<$pV=BEtf0lYif1Ce) z)qGk%<#AMH@3;5%>guq0QYEu$=RsX9O8Ms1@-fxfp&vq-(NCW2u|H$7SGl}}ZN7!u z>aA1nKK)P%z2)^nPpN(k<ZeKOEVYZ~ z36&k!k8z}S)c4pFYG~nzqxvNS__-wh-m0I1nO1wQ zjgu-1qu3smx|X+VbjR&Dic)w$ExJ*Mwo-FSy+g-AwK-=;GABw>QHczWNit9oMxXbb z>Q@%t%J{Bpdis<`a!RreBRi@4U@Oq`R{C0*cMf4E#aY+aDdlzuH@65qx9CTA?yPfU z-aIAVq!#T9;w{{OXLnfnfc;0suOP!z?u8oLyd)N^ln<&F(ru(8=ep~|$u9J;UF|GN z0@HeKUaig9e&^Kpw0e9-JseZ%w8n~#OkYG!Z$D1mT9Q23V;agz#}jhRr35ect^7+# w->*M^ch>xd`I7l{^Tni+v~@grY<@FC2ybT*N6BXZUS5W2YOHC^8f$< diff --git a/fonts/pixel_maz_large.ttf b/fonts/pixel_maz_large.ttf deleted file mode 100644 index 22c89e4a4a4814441520b71c74439b04722f3ff1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26752 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 KirQ-*+x#y!qHb6K diff --git a/fonts/pixel_maz_multiline.ttf b/fonts/pixel_maz_multiline.ttf deleted file mode 100644 index d616e101d91d92de6b535140b992bc52add88333..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28280 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 diff --git a/fonts/pixelmix.ttf b/fonts/pixelmix.ttf deleted file mode 100644 index 7e7b1620147aa2464ad92f1dd9ca72367573b237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22184 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 Bz6t;U diff --git a/src/steam.cpp b/src/steam.cpp index dc8c04d14..19692aefb 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -1773,9 +1773,10 @@ void steam_OnGameJoinRequested( void* pCallback ) #endif handlingInvite = true; - auto lobby = cpp_GameJoinRequested_m_steamIDLobby(pCallback); + auto pLobby = cpp_GameJoinRequested_m_steamIDLobby(pCallback); + auto lobby = static_cast(pLobby); SteamMatchmaking()->RequestLobbyData(*lobby); - cpp_Free_CSteamID(lobby); + cpp_Free_CSteamID(pLobby); //The invite is not actually passed to the rest of the game right here. //This is because we need to gather data from the lobby about the save game. From bcf44014d0395d5025296238da8433a4b5c5131c Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sat, 23 Jul 2022 15:19:39 -0700 Subject: [PATCH 14/21] upgrade github workflow to ubuntu-22.04 --- .github/workflows/build-linux_fmod_steam.yml | 2 +- .github/workflows/build-linux_fmod_steam_eos.yml | 4 ++-- src/ui/MainMenu.cpp | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-linux_fmod_steam.yml b/.github/workflows/build-linux_fmod_steam.yml index 608e460b3..763bcc0c6 100644 --- a/.github/workflows/build-linux_fmod_steam.yml +++ b/.github/workflows/build-linux_fmod_steam.yml @@ -15,7 +15,7 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/.github/workflows/build-linux_fmod_steam_eos.yml b/.github/workflows/build-linux_fmod_steam_eos.yml index d961c63c2..749536a48 100644 --- a/.github/workflows/build-linux_fmod_steam_eos.yml +++ b/.github/workflows/build-linux_fmod_steam_eos.yml @@ -15,7 +15,7 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -63,4 +63,4 @@ jobs: # Artifact name name: Editor_fmod_steam # optional # A file, directory or wildcard pattern that describes what to upload - path: build-editor/release/editor \ No newline at end of file + path: build-editor/release/editor diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index a223922c2..58f86cb33 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -8647,6 +8647,7 @@ namespace MainMenu { case 2: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(2);}); break; case 3: custom_difficulty->setCallback([](Button& button){soundActivate(); characterCardGameFlagsMenu(3);}); break; } + custom_difficulty->select(); auto invite_label = card->addField("invite_label", 64); invite_label->setSize(SDL_Rect{82, 146, 122, 26}); From 2c99c7f9d210d85748ebcd59912fe38901b53639 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sat, 23 Jul 2022 15:29:50 -0700 Subject: [PATCH 15/21] fix syntax error --- src/ui/MainMenu.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 58f86cb33..8293c8c2c 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -16724,7 +16724,8 @@ namespace MainMenu { if (processLobbyInvite(lobby)) { // load any relevant save data connectToServer(nullptr, lobby, LobbyType::LobbyOnline); } else { - auto str = LobbyHandler.getLobbyJoinFailedConnectString(EResult_LobbyFailures::LOBBY_USING_SAVEGAME); + const auto error = LobbyHandler_t::EResult_LobbyFailures::LOBBY_USING_SAVEGAME; + const auto str = LobbyHandler.getLobbyJoinFailedConnectString(error); monoPrompt(str.c_str(), "Okay", [](Button&){ soundCancel(); From 720b0d14867c1e7c6336a55994cefc55b17e4fb0 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sun, 24 Jul 2022 14:45:54 -0700 Subject: [PATCH 16/21] fix colors in class selection --- src/ui/MainMenu.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 8293c8c2c..cd6677898 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -10325,31 +10325,29 @@ namespace MainMenu { constexpr int column = bottom.w / num_class_stats; // character attribute ratings - constexpr Uint32 good = makeColorRGB(0, 193, 255); - constexpr Uint32 decent = makeColorRGB(158, 208, 223); - constexpr Uint32 average = makeColorRGB(192, 192, 192); - constexpr Uint32 poor = makeColorRGB(255, 159, 56); - constexpr Uint32 bad = makeColorRGB(255, 56, 56); + static constexpr Uint32 good = makeColorRGB(0, 192, 255); + static constexpr Uint32 average = makeColorRGB(191, 191, 191); + static constexpr Uint32 bad = makeColorRGB(255, 64, 0); static constexpr Uint32 class_stat_colors[][num_class_stats] = { - {good, decent, bad, bad, decent, decent}, // barbarian + {good, good, bad, bad, good, good}, // barbarian {good, bad, good, bad, bad, good}, // warrior - {average, average, decent, good, decent, average}, // healer - {bad, good, bad, bad, good, decent}, // rogue - {decent, average, decent, average, decent, bad}, // wanderer - {average, bad, decent, decent, average, average}, // cleric - {average, bad, decent, average, decent, good}, // merchant - {bad, average, bad, good, good, decent}, // wizard - {bad, decent, bad, decent, decent, bad}, // arcanist + {average, average, good, good, good, average}, // healer + {bad, good, bad, bad, good, good}, // rogue + {good, average, good, average, good, bad}, // wanderer + {average, bad, good, good, average, average}, // cleric + {average, bad, good, average, good, good}, // merchant + {bad, average, bad, good, good, good}, // wizard + {bad, good, bad, good, good, bad}, // arcanist {average, average, average, average, average, average}, // joker {good, good, average, good, average, average}, // sexton - {good, good, decent, average, average, bad}, // ninja - {decent, bad, good, average, bad, bad}, // monk - {bad, bad, decent, good, decent, decent}, // conjurer + {good, good, good, average, average, bad}, // ninja + {good, bad, good, average, bad, bad}, // monk + {bad, bad, good, good, good, good}, // conjurer {average, average, bad, good, good, average}, // accursed - {average, average, bad, good, decent, good}, // mesmer - {average, average, bad, decent, bad, decent}, // brewer - {bad, decent, bad, average, good, average}, // mechanist - {decent, average, bad, average, decent, decent}, // punisher + {average, average, bad, good, good, good}, // mesmer + {average, average, bad, good, bad, good}, // brewer + {bad, good, bad, average, good, average}, // mechanist + {good, average, bad, average, good, good}, // punisher {average, average, average, average, average, average}, // shaman {bad, good, bad, average, good, average}, // hunter }; @@ -10414,6 +10412,8 @@ namespace MainMenu { difficulty_header->setSize(difficulty_size); // difficulty stars + static constexpr Uint32 decent = makeColorRGB(64, 160, 191); + static constexpr Uint32 poor = makeColorRGB(191, 96, 64); static constexpr int star_buf_size = 32; static auto stars_fn = [](Field& field, int index){ const int i = std::min(std::max(0, client_classes[index]), num_class_descs - 1); From e1819ee13cf17a2c3edf7868ca5fceb5221e32b3 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sun, 24 Jul 2022 15:12:55 -0700 Subject: [PATCH 17/21] revise color code slightly --- src/ui/MainMenu.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index cd6677898..ef3f2d810 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -10325,29 +10325,31 @@ namespace MainMenu { constexpr int column = bottom.w / num_class_stats; // character attribute ratings - static constexpr Uint32 good = makeColorRGB(0, 192, 255); + static constexpr Uint32 good = makeColorRGB(0, 191, 255); + static constexpr Uint32 decent = makeColorRGB(0, 191, 255); static constexpr Uint32 average = makeColorRGB(191, 191, 191); + static constexpr Uint32 poor = makeColorRGB(255, 64, 0); static constexpr Uint32 bad = makeColorRGB(255, 64, 0); static constexpr Uint32 class_stat_colors[][num_class_stats] = { - {good, good, bad, bad, good, good}, // barbarian + {good, decent, bad, bad, decent, decent}, // barbarian {good, bad, good, bad, bad, good}, // warrior - {average, average, good, good, good, average}, // healer - {bad, good, bad, bad, good, good}, // rogue - {good, average, good, average, good, bad}, // wanderer - {average, bad, good, good, average, average}, // cleric - {average, bad, good, average, good, good}, // merchant - {bad, average, bad, good, good, good}, // wizard - {bad, good, bad, good, good, bad}, // arcanist + {average, average, decent, good, decent, average}, // healer + {bad, good, bad, bad, good, decent}, // rogue + {decent, average, decent, average, decent, bad}, // wanderer + {average, bad, decent, decent, average, average}, // cleric + {average, bad, decent, average, decent, good}, // merchant + {bad, average, bad, good, good, decent}, // wizard + {bad, decent, bad, decent, decent, bad}, // arcanist {average, average, average, average, average, average}, // joker {good, good, average, good, average, average}, // sexton - {good, good, good, average, average, bad}, // ninja - {good, bad, good, average, bad, bad}, // monk - {bad, bad, good, good, good, good}, // conjurer + {good, good, decent, average, average, bad}, // ninja + {decent, bad, good, average, bad, bad}, // monk + {bad, bad, decent, good, decent, decent}, // conjurer {average, average, bad, good, good, average}, // accursed - {average, average, bad, good, good, good}, // mesmer - {average, average, bad, good, bad, good}, // brewer - {bad, good, bad, average, good, average}, // mechanist - {good, average, bad, average, good, good}, // punisher + {average, average, bad, good, decent, good}, // mesmer + {average, average, bad, decent, bad, decent}, // brewer + {bad, decent, bad, average, good, average}, // mechanist + {decent, average, bad, average, decent, decent}, // punisher {average, average, average, average, average, average}, // shaman {bad, good, bad, average, good, average}, // hunter }; @@ -10412,8 +10414,6 @@ namespace MainMenu { difficulty_header->setSize(difficulty_size); // difficulty stars - static constexpr Uint32 decent = makeColorRGB(64, 160, 191); - static constexpr Uint32 poor = makeColorRGB(191, 96, 64); static constexpr int star_buf_size = 32; static auto stars_fn = [](Field& field, int index){ const int i = std::min(std::max(0, client_classes[index]), num_class_descs - 1); From 71e01f9f1767d81328d829d844cf8a09449b2be8 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sun, 24 Jul 2022 19:28:50 -0700 Subject: [PATCH 18/21] add banners and change player colors --- src/colors.hpp | 14 +++--- src/ui/MainMenu.cpp | 108 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/colors.hpp b/src/colors.hpp index 2e01bf387..04aed7d1e 100644 --- a/src/colors.hpp +++ b/src/colors.hpp @@ -33,13 +33,13 @@ constexpr Uint32 uint32ColorOrange = makeColor(255, 128, 0, 255); constexpr Uint32 uint32ColorYellow = makeColor(255, 255, 0, 255); constexpr Uint32 uint32ColorPlayer1 = makeColorRGB(64, 255, 64); -constexpr Uint32 uint32ColorPlayer2 = makeColorRGB(86, 180, 233); -constexpr Uint32 uint32ColorPlayer3 = makeColorRGB(240, 228, 66); -constexpr Uint32 uint32ColorPlayer4 = makeColorRGB(204, 121, 167); +constexpr Uint32 uint32ColorPlayer2 = makeColorRGB(64, 64, 255); +constexpr Uint32 uint32ColorPlayer3 = makeColorRGB(255, 64, 64); +constexpr Uint32 uint32ColorPlayer4 = makeColorRGB(255, 160, 255); constexpr Uint32 uint32ColorPlayerX = makeColorRGB(191, 191, 191); -constexpr Uint32 uint32ColorPlayer1_Ally = makeColorRGB(32, 127, 32); -constexpr Uint32 uint32ColorPlayer2_Ally = makeColorRGB(43, 90, 116); -constexpr Uint32 uint32ColorPlayer3_Ally = makeColorRGB(120, 114, 33); -constexpr Uint32 uint32ColorPlayer4_Ally = makeColorRGB(102, 60, 83); +constexpr Uint32 uint32ColorPlayer1_Ally = makeColorRGB(31, 127, 31); +constexpr Uint32 uint32ColorPlayer2_Ally = makeColorRGB(31, 31, 127); +constexpr Uint32 uint32ColorPlayer3_Ally = makeColorRGB(127, 31, 31); +constexpr Uint32 uint32ColorPlayer4_Ally = makeColorRGB(127, 80, 127); constexpr Uint32 uint32ColorPlayerX_Ally = makeColorRGB(95, 95, 95); \ No newline at end of file diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index ef3f2d810..dac93ccd2 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -20,6 +20,7 @@ #include "../classdescriptions.hpp" #include "../lobbies.hpp" #include "../interface/consolecommand.hpp" +#include "../interface/ui.hpp" #include "../eos.hpp" #include "../colors.hpp" @@ -8568,7 +8569,7 @@ namespace MainMenu { achievements->setColor(makeColor(180, 37, 37, 255)); achievements->setText("ACHIEVEMENTS DISABLED"); } else { - achievements->setColor(makeColor(40, 180, 37, 255)); + achievements->setColor(makeColor(37, 40, 180, 255)); achievements->setText("ACHIEVEMENTS ENABLED"); } }); @@ -9260,7 +9261,7 @@ namespace MainMenu { achievements->setColor(makeColor(180, 37, 37, 255)); achievements->setText("ACHIEVEMENTS DISABLED"); } else { - achievements->setColor(makeColor(40, 180, 37, 255)); + achievements->setColor(makeColor(37, 40, 180, 255)); achievements->setText("ACHIEVEMENTS ENABLED"); } }); @@ -16013,20 +16014,46 @@ namespace MainMenu { 100, }); start->setBorder(0); - start->setColor(makeColor(0, 0, 0, 127)); - start->setHighlightColor(makeColor(0, 0, 0, 127)); + start->setColor(makeColor(255, 255, 255, 255)); + start->setHighlightColor(makeColor(255, 255, 255, 255)); + start->setBackground("images/ui/Main Menus/StartGradient.png"); start->setFont(bigfont_outline); - start->setText("Press to Start\n "); - start->setButtonsOffset(SDL_Rect{0, 16, 0, 0}); + start->setText("Press to Start"); start->setGlyphPosition(Widget::glyph_position_t::CENTERED); + start->setButtonsOffset(SDL_Rect{0, 24, 0, 0}); start->setHideKeyboardGlyphs(false); + //start->setSelectorOffset(SDL_Rect{32, 32, -32, -32}); + start->setHideSelectors(true); start->addWidgetAction("MenuConfirm", "start"); - start->select(); start->setCallback([](Button&){ destroyMainMenu(); createMainMenu(false); soundActivate(); }); + start->setTickCallback([](Widget& widget){ + auto button = static_cast(&widget); + auto color = button->getColor(); + Uint8 r, g, b, a; + ::getColor(color, &r, &g, &b, &a); + static bool fadingDown = true; + if (fadingDown) { + if (a == 127) { + fadingDown = false; + } else { + --a; + } + } else { + if (a == 255) { + fadingDown = true; + } else { + ++a; + } + } + const Uint32 newColor = makeColor(r, g, b, a); + button->setColor(newColor); + button->setHighlightColor(newColor); + }); + start->select(); #ifdef STEAMWORKS if (!cmd_line.empty()) { @@ -16212,7 +16239,11 @@ namespace MainMenu { }); int back = c - 1 < 0 ? num_options - 1 : c - 1; int forward = c + 1 >= num_options ? 0 : c + 1; - button->setWidgetDown(options[forward].name); + if (ingame || c + 1 < num_options) { + button->setWidgetDown(options[forward].name); + } else { + button->setWidgetDown("banner1"); + } button->setWidgetUp(options[back].name); if (!ingame) { button->setWidgetBack("back_button"); @@ -16244,23 +16275,54 @@ namespace MainMenu { ); if (!ingame) { -#if 0 - for (int c = 0; c < 2; ++c) { + + const char* banner_images[][2] = { + { + "*#images/ui/Main Menus/Banners/UI_MainMenu_QoDPatchNotes1_base.png", + "*#images/ui/Main Menus/Banners/UI_MainMenu_QoDPatchNotes1_high.png", + }, + { + "#images/ui/Main Menus/Banners/UI_MainMenu_DiscordLink_base.png", + "#images/ui/Main Menus/Banners/UI_MainMenu_DiscordLink_high.png", + }, + }; + void(* banner_funcs[])(Button&) = { + [](Button&){ + // TODO QoD banner click + openURLTryWithOverlay("http://www.baronygame.com/"); + printlog("Clicked QoD banner"); + }, + [](Button&){ + openURLTryWithOverlay("https://discord.gg/xDhtaR9KA2"); + printlog("Clicked Discord banner"); + }, + }; + constexpr int num_banners = sizeof(banner_funcs) / sizeof(banner_funcs[0]); + for (int c = 0; c < num_banners; ++c) { std::string name = std::string("banner") + std::to_string(c + 1); - auto banner = main_menu_frame->addFrame(name.c_str()); - banner->setSize(SDL_Rect{ - (Frame::virtualScreenX - 472) / 2, - y, - 472, - 76 - }); - banner->setActualSize(SDL_Rect{0, 0, banner->getSize().w, banner->getSize().h}); - std::string background = std::string("*images/ui/Main Menus/Main/UI_MainMenu_EXBanner") + std::to_string(c + 1) + std::string(".png"); - banner->addImage(banner->getActualSize(), 0xffffffff, background.c_str()); + auto banner = main_menu_frame->addButton(name.c_str()); + banner->setSize(SDL_Rect{(Frame::virtualScreenX - 472) / 2, y, 472, 76}); + banner->setBackground(banner_images[c][0]); + banner->setBackgroundHighlighted(banner_images[c][1]); + banner->setCallback(banner_funcs[c]); + banner->setButtonsOffset(SDL_Rect{0, 8, 0, 0}); + //banner->setHideSelectors(true); + + if (c == 0) { + banner->setWidgetUp("Quit"); + } else { + banner->setWidgetUp((std::string("banner") + std::to_string(c + 0)).c_str()); + } + if (c == num_banners - 1) { + banner->setWidgetDown("Play Game"); + } else { + banner->setWidgetDown((std::string("banner") + std::to_string(c + 2)).c_str()); + } + banner->setWidgetBack("back_button"); + y += banner->getSize().h; y += 16; } -#endif auto copyright = main_menu_frame->addField("copyright", 64); copyright->setFont(bigfont_outline); @@ -16293,9 +16355,7 @@ namespace MainMenu { online_players->setFont(smallfont_outline); online_players->setHJustify(Field::justify_t::RIGHT); online_players->setVJustify(Field::justify_t::TOP); - online_players->setSize(SDL_Rect{ - Frame::virtualScreenX - 200, - 4, 200, 50}); + online_players->setSize(SDL_Rect{Frame::virtualScreenX - 200, 4, 200, 50}); online_players->setColor(0xffffffff); online_players->setTickCallback([](Widget& widget){ auto online_players = static_cast(&widget); From 34650b7c420cc59e3f39ac99d44d5d8ecc4eeff1 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Sun, 24 Jul 2022 23:50:40 -0700 Subject: [PATCH 19/21] added DLC prompt F6 to screenshot always works w/ gamepads --- src/game.cpp | 3 +- src/input.cpp | 1 + src/menu.cpp | 9 +- src/menu.hpp | 2 +- src/ui/MainMenu.cpp | 228 +++++++++++++++++++++++++++++++++----------- 5 files changed, 182 insertions(+), 61 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 33676fee1..38d0b32a1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -6594,7 +6594,8 @@ int main(int argc, char** argv) GO_SwapBuffers(screen); // screenshots - if ( Input::inputs[clientnum].consumeBinaryToggle("Screenshot") ) + if (Input::inputs[clientnum].consumeBinaryToggle("Screenshot") || + (inputs.hasController(clientnum) && Input::inputs[clientnum].consumeBinaryToggle("GamepadScreenshot"))) { takeScreenshot(); } diff --git a/src/input.cpp b/src/input.cpp index 02b3d4e98..0876c124c 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -133,6 +133,7 @@ void Input::defaultBindings() { inputs[c].gamepad_system_bindings.insert(std::make_pair("GamepadLoginB", (std::string("Pad") + std::to_string(c) + std::string("ButtonB")).c_str())); inputs[c].gamepad_system_bindings.insert(std::make_pair("GamepadLoginStart", (std::string("Pad") + std::to_string(c) + std::string("ButtonStart")).c_str())); + inputs[c].kb_system_bindings.insert(std::make_pair("GamepadScreenshot", "F6")); inputs[c].kb_system_bindings.insert(std::make_pair("HotbarSlot1", "1")); inputs[c].kb_system_bindings.insert(std::make_pair("HotbarSlot2", "2")); inputs[c].kb_system_bindings.insert(std::make_pair("HotbarSlot3", "3")); diff --git a/src/menu.cpp b/src/menu.cpp index 6cd466c09..254babcc6 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -15007,7 +15007,7 @@ void gamemodsWorkshopPreloadMod(int fileID, std::string modTitle) } } #else -size_t serialHash(std::string input) +size_t serialHash(const std::string& input) { if ( input.empty() || input.size() != 19 ) { @@ -15015,14 +15015,13 @@ size_t serialHash(std::string input) } int i = 0; size_t hash = 0; - for ( std::string::iterator it = input.begin(); it != input.end(); ++it ) + for ( auto it : input ) { - char c = *it; - if ( c == '\0' || c == '\n' ) + if ( it == '\0' || it == '\n' ) { break; } - hash += static_cast(c) * (i * i); + hash += static_cast(it) * (i * i); ++i; } return hash; diff --git a/src/menu.hpp b/src/menu.hpp index ca389c009..c68a4681d 100644 --- a/src/menu.hpp +++ b/src/menu.hpp @@ -127,7 +127,7 @@ void openSteamLobbyWaitWindow(button_t* my); #else void windowEnterSerialPrompt(); void windowSerialResult(int success); -size_t serialHash(std::string input); +size_t serialHash(const std::string& input); extern char serialInputText[64]; #endif diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index dac93ccd2..2d307eaa6 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -902,6 +902,7 @@ namespace MainMenu { static Frame* textFieldPrompt( const char* field_text, + const char* tip_text, const char* guide_text, const char* okay_text, const char* cancel_text, @@ -922,7 +923,27 @@ namespace MainMenu { "text_box" ); - constexpr int field_buffer_size = 64; + constexpr int field_buffer_size = 128; + + auto tip = frame->addField("tip", field_buffer_size); + tip->setSize(SDL_Rect{(364 - 242) / 2, 36, 242, 28}); + tip->setFont(smallfont_outline); + tip->setText(tip_text); + tip->setUserData(const_cast((const void*)tip_text)); + tip->setHJustify(Field::justify_t::LEFT); + tip->setVJustify(Field::justify_t::CENTER); + tip->setColor(makeColor(166, 123, 81, 255)); + tip->setBackgroundColor(makeColor(52, 30, 22, 255)); + tip->setTickCallback([](Widget& widget){ + auto tip = static_cast(&widget); + auto parent = static_cast(widget.getParent()); + auto field = parent->findField("field"); + if (field && (field->isActivated() || field->getText()[0] != '\0')) { + tip->setText(""); + } else { + tip->setText((const char*)tip->getUserData()); + } + }); auto field = frame->addField("field", field_buffer_size); field->setGlyphPosition(Widget::glyph_position_t::CENTERED_RIGHT); @@ -937,9 +958,6 @@ namespace MainMenu { field->setHJustify(Field::justify_t::LEFT); field->setVJustify(Field::justify_t::CENTER); field->setColor(makeColor(166, 123, 81, 255)); - field->setBackgroundColor(makeColor(52, 30, 22, 255)); - field->setBackgroundSelectAllColor(makeColor(52, 30, 22, 255)); - field->setBackgroundActivatedColor(makeColor(52, 30, 22, 255)); field->setWidgetSearchParent(field->getParent()->getName()); field->setWidgetBack("cancel"); field->setWidgetDown("okay"); @@ -1178,6 +1196,105 @@ namespace MainMenu { ); } + static void openDLCPrompt() { + textFieldPrompt("", "Enter DLC Key...", "Enter DLC Serial Key", "Confirm", "Cancel", + [](Button& button){ // okay + soundActivate(); + + static std::string text; + + auto frame = static_cast(button.getParent()); assert(frame); + auto field = frame->findField("field"); assert(field); + text = field->getText(); + closeTextField(); + + if (text.empty()) { + auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); + auto play = buttons->findButton("Play Game"); + play->select(); + } else { + static Uint32 window_ticks; + window_ticks = ticks; + textPrompt("dlc_check_window", "", [](Widget& widget){ + auto field = static_cast(&widget); + auto time = ticks - window_ticks; + if (time % TICKS_PER_SECOND < 10) { + field->setText("Verifying"); + } + else if (time % TICKS_PER_SECOND < 20) { + field->setText("Verifying."); + } + else if (time % TICKS_PER_SECOND < 30) { + field->setText("Verifying.."); + } + else if (time % TICKS_PER_SECOND < 40) { + field->setText("Verifying..."); + } + else { + field->setText("Verifying...."); + } + if (time > TICKS_PER_SECOND * 2) { + closePrompt("dlc_check_window"); + std::size_t DLCHash = serialHash(text); + + auto prompt = [](const char* text){ + monoPrompt(text, "Okay", [](Button&){ + soundActivate(); + closeMono(); + auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); + auto play = buttons->findButton("Play Game"); + play->select(); + }); + }; + + if (DLCHash == 144425) { + playSound(402, 92); + printlog("[LICENSE]: Myths and Outcasts DLC license key found."); + prompt("Myths and Outcasts DLC\nhas been unlocked!"); + enabledDLCPack1 = true; + + char path[PATH_MAX] = ""; + completePath(path, "mythsandoutcasts.key", outputdir); + + // write the serial file + File* fp = nullptr; + if (fp = FileIO::open(path, "wb")) { + fp->write(text.c_str(), sizeof(char), text.size()); + FileIO::close(fp); + } + } else if ( DLCHash == 135398 ) { + playSound(402, 92); + printlog("[LICENSE]: Legends and Pariahs DLC license key found."); + prompt("Legends and Pariahs DLC\nhas been unlocked!"); + enabledDLCPack2 = true; + + char path[PATH_MAX] = ""; + completePath(path, "legendsandpariahs.key", outputdir); + + // write the serial file + File* fp = nullptr; + if (fp = FileIO::open(path, "wb")) { + fp->write(text.c_str(), sizeof(char), text.size()); + FileIO::close(fp); + } + } else { + soundError(); + printlog("[LICENSE]: DLC license key invalid."); + prompt("Invalid license key."); + } + } + }); + } + }, + [](Button&){ // cancel + soundCancel(); + closeTextField(); + auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); + auto play = buttons->findButton("Play Game"); + play->select(); + }); + } + /******************************************************************************/ inline void InventorySorting::save() { @@ -9522,19 +9639,19 @@ namespace MainMenu { for (int c = 0; c < num_races; ++c) { auto race = subframe->addButton(races[c]); race->setSize(SDL_Rect{0, c * 36 + 2, 30, 30}); - race->setBackground("*#images/ui/Main Menus/sublist_item-unpicked.png"); if (!enabledDLCPack1 && c >= 1 && c <= 4) { - race->setIcon("*#images/ui/Main Menus/sublist_item-locked.png"); + race->setBackground("*#images/ui/Main Menus/sublist_item-locked.png"); race->setDisabled(true); } else if (!enabledDLCPack2 && c >= 5 && c <= 8) { - race->setIcon("*#images/ui/Main Menus/sublist_item-locked.png"); + race->setBackground("*#images/ui/Main Menus/sublist_item-locked.png"); race->setDisabled(true); } else { - race->setIcon("*#images/ui/Main Menus/sublist_item-picked.png"); + race->setBackground("*#images/ui/Main Menus/sublist_item-unpicked.png"); race->setDisabled(false); } + race->setIcon("*#images/ui/Main Menus/sublist_item-picked.png"); race->setStyle(Button::style_t::STYLE_RADIO); race->setBorder(0); race->setColor(0xffffffff); @@ -13071,21 +13188,23 @@ namespace MainMenu { refresh->setCallback(refresh_fn); static auto enter_code_fn = [](Button& button){ + static const char* guide_ipaddr = "Enter an IP address to connect to."; + static const char* guide_roomcode = "Enter the code to a lobby you wish to connect to."; static const char* guide; - static const char* ipaddr = "Enter an IP address to connect to."; - static const char* roomcode = "Enter the code to a lobby you wish to connect to."; - guide = strcmp(button.getText(), "Enter IP\nAddress") ? roomcode : ipaddr; - textFieldPrompt(last_address, guide, "Connect", "Cancel", + guide = directConnect ? guide_ipaddr : guide_roomcode; + static const char* tip_ipaddr = "Enter IP address..."; + static const char* tip_roomcode = "Enter roomcode..."; + static const char* tip; + tip = directConnect ? tip_ipaddr : tip_roomcode; + textFieldPrompt(last_address, tip, guide, "Connect", "Cancel", [](Button&){ // connect const char* address = closeTextField(); // only valid for one frame - if (guide == ipaddr) { + if (directConnect) { soundActivate(); (void)connectToServer(address, nullptr, LobbyType::LobbyLAN); - } else if (guide == roomcode) { + } else { soundActivate(); (void)connectToServer(address, nullptr, LobbyType::LobbyOnline); - } else { - soundError(); } }, [](Button&){ // cancel @@ -14923,17 +15042,17 @@ namespace MainMenu { // change "notification" section into subsection banner auto notification = main_menu_frame->findFrame("notification"); assert(notification); - auto image = notification->findImage("background"); assert(image); - image->path = "*images/ui/Main Menus/AdventureArchives/UI_AdventureArchives_TitleGraphic00.png"; - image->disabled = false; + const int note_y = notification->getSize().y; + notification->removeSelf(); + notification = main_menu_frame->addFrame("notification"); notification->setSize(SDL_Rect{ - (Frame::virtualScreenX - 204 * 2) / 2, - notification->getSize().y, - 204 * 2, - 43 * 2 - }); + (Frame::virtualScreenX - 204 * 2) / 2, note_y, 204 * 2, 43 * 2}); notification->setActualSize(SDL_Rect{0, 0, notification->getSize().w, notification->getSize().h}); - image->pos = notification->getActualSize(); + auto image = notification->addImage( + notification->getActualSize(), + 0xffffffff, + "*images/ui/Main Menus/AdventureArchives/UI_AdventureArchives_TitleGraphic00.png", + "background"); // add banner text to notification auto banner_text = notification->addField("text", 64); @@ -14944,12 +15063,9 @@ namespace MainMenu { banner_text->setSize(SDL_Rect{19 * 2, 15 * 2, 166 * 2, 12 * 2}); // disable banners - for (int c = 0; c < 2; ++c) { - std::string name = std::string("banner") + std::to_string(c + 1); - auto banner = main_menu_frame->findFrame(name.c_str()); - if (banner) { - banner->setDisabled(true); - } + auto banners = main_menu_frame->findFrame("banners"); + if (banners) { + banners->setDisabled(true); } // delete existing buttons @@ -15885,8 +16001,8 @@ namespace MainMenu { // just always enable DLC in debug. saves headaches #ifndef NDEBUG - enabledDLCPack1 = true; - enabledDLCPack2 = true; + //enabledDLCPack1 = true; + //enabledDLCPack2 = true; #endif #ifdef STEAMWORKS @@ -16129,18 +16245,8 @@ namespace MainMenu { if (!ingame) { auto notification = main_menu_frame->addFrame("notification"); - notification->setSize(SDL_Rect{ - (Frame::virtualScreenX - 236 * 2) / 2, - y, - 236 * 2, - 49 * 2 - }); + notification->setSize(SDL_Rect{(Frame::virtualScreenX - 236 * 2) / 2, y, 472, 98}); notification->setActualSize(SDL_Rect{0, 0, notification->getSize().w, notification->getSize().h}); - auto image = notification->addImage(notification->getActualSize(), 0xffffffff, - "*images/ui/Main Menus/Main/UI_MainMenu_EXNotification.png", "background"); -#if 1 - image->disabled = true; -#endif y += notification->getSize().h; y += 16; } @@ -16275,7 +16381,6 @@ namespace MainMenu { ); if (!ingame) { - const char* banner_images[][2] = { { "*#images/ui/Main Menus/Banners/UI_MainMenu_QoDPatchNotes1_base.png", @@ -16286,22 +16391,37 @@ namespace MainMenu { "#images/ui/Main Menus/Banners/UI_MainMenu_DiscordLink_high.png", }, }; - void(* banner_funcs[])(Button&) = { - [](Button&){ - // TODO QoD banner click - openURLTryWithOverlay("http://www.baronygame.com/"); - printlog("Clicked QoD banner"); + if (!enabledDLCPack1 && !enabledDLCPack2) { + banner_images[0][0] = "*#images/ui/Main Menus/Banners/UI_MainMenu_ComboBanner1_base.png"; + banner_images[0][1] = "*#images/ui/Main Menus/Banners/UI_MainMenu_ComboBanner1_high.png"; + } + else if (!enabledDLCPack1) { + banner_images[0][0] = "*#images/ui/Main Menus/Banners/UI_MainMenu_MnOBanner1_base.png"; + banner_images[0][1] = "*#images/ui/Main Menus/Banners/UI_MainMenu_MnOBanner1_high.png"; + } + else if (!enabledDLCPack2) { + banner_images[0][0] = "*#images/ui/Main Menus/Banners/UI_MainMenu_LnPBanner1_base.png"; + banner_images[0][1] = "*#images/ui/Main Menus/Banners/UI_MainMenu_LnPBanner1_high.png"; + } + void(*banner_funcs[])(Button&) = { + [](Button&){ // banner #1 + if (enabledDLCPack1 && enabledDLCPack2) { + openURLTryWithOverlay("http://www.baronygame.com/"); + } else { + openDLCPrompt(); + } }, - [](Button&){ + [](Button&){ // banner #2 openURLTryWithOverlay("https://discord.gg/xDhtaR9KA2"); - printlog("Clicked Discord banner"); }, }; constexpr int num_banners = sizeof(banner_funcs) / sizeof(banner_funcs[0]); + auto banners = main_menu_frame->addFrame("banners"); + banners->setSize(SDL_Rect{(Frame::virtualScreenX - 472) / 2, y, 472, Frame::virtualScreenY - y}); for (int c = 0; c < num_banners; ++c) { std::string name = std::string("banner") + std::to_string(c + 1); - auto banner = main_menu_frame->addButton(name.c_str()); - banner->setSize(SDL_Rect{(Frame::virtualScreenX - 472) / 2, y, 472, 76}); + auto banner = banners->addButton(name.c_str()); + banner->setSize(SDL_Rect{0, c * 92, 472, 76}); banner->setBackground(banner_images[c][0]); banner->setBackgroundHighlighted(banner_images[c][1]); banner->setCallback(banner_funcs[c]); From 00ba70a75d67de4c0369fa8222f90fa0543316d0 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Mon, 25 Jul 2022 00:01:22 -0700 Subject: [PATCH 20/21] fix compile error on non-drm-free builds --- src/ui/MainMenu.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 2d307eaa6..4f4f24218 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -1196,6 +1196,7 @@ namespace MainMenu { ); } +#if !defined(STEAMWORKS) && !defined(USE_EOS) static void openDLCPrompt() { textFieldPrompt("", "Enter DLC Key...", "Enter DLC Serial Key", "Confirm", "Cancel", [](Button& button){ // okay @@ -1294,6 +1295,7 @@ namespace MainMenu { play->select(); }); } +#endif /******************************************************************************/ @@ -16408,7 +16410,13 @@ namespace MainMenu { if (enabledDLCPack1 && enabledDLCPack2) { openURLTryWithOverlay("http://www.baronygame.com/"); } else { +#if defined(STEAMWORKS) + openURLTryWithOverlay("https://store.steampowered.com/dlc/371970/Barony/"); +#elif defined(USE_EOS) + openURLTryWithOverlay("https://store.epicgames.com/en-US/all-dlc/barony"); +#else openDLCPrompt(); +#endif } }, [](Button&){ // banner #2 From ce2a459ac5718c5694acda601093340d63c3ff57 Mon Sep 17 00:00:00 2001 From: SheridanR Date: Tue, 26 Jul 2022 10:41:35 -0700 Subject: [PATCH 21/21] bugfixing --- src/ui/Button.cpp | 6 +++++- src/ui/Button.hpp | 2 ++ src/ui/MainMenu.cpp | 32 ++++++++++++++++++++++---------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/ui/Button.cpp b/src/ui/Button.cpp index 5920026e6..1aac46e45 100644 --- a/src/ui/Button.cpp +++ b/src/ui/Button.cpp @@ -178,7 +178,11 @@ void Button::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectordrawColor(§ion, scaledPos, viewport, focused ? highlightColor : color); + if (iconColor) { + iconImg->drawColor(§ion, scaledPos, viewport, iconColor); + } else { + iconImg->drawColor(§ion, scaledPos, viewport, focused ? highlightColor : color); + } } } else if (!text.empty()) { Font* _font = Font::get(font.c_str()); diff --git a/src/ui/Button.hpp b/src/ui/Button.hpp index 3b4d9b46b..4f8d74465 100644 --- a/src/ui/Button.hpp +++ b/src/ui/Button.hpp @@ -110,6 +110,7 @@ class Button : public Widget { void setText(const char* _text) { text = _text; } void setFont(const char* _font) { font = _font; } void setIcon(const char* _icon); + void setIconColor(const Uint32& _color) { iconColor = _color; } void setTooltip(const char* _tooltip) { tooltip = _tooltip; } void setStyle(int _style) { style = static_cast(_style); } void setCallback(void (*const fn)(Button&)) { callback = fn; } @@ -134,6 +135,7 @@ class Button : public Widget { int border = 2; //!< size of the button border in pixels SDL_Rect size{0,0,0,0}; //!< size and position of the button within its parent frame Uint32 color = 0; //!< the button's color + Uint32 iconColor = 0xffffffff; //!< icon color Uint32 highlightColor = 0; //!< color used when the button is selected/highlighted Uint32 textColor = 0; //!< text color Uint32 textHighlightColor = 0; //!< text color used when the button is selected/highlighted diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 4f4f24218..6c6be771b 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -6987,7 +6987,7 @@ namespace MainMenu { Uint32 bytesRead = 0; if (!SteamNetworking()->ReadP2PPacket( net_packet->data, packetlen, - &bytesRead, &newSteamID, 0) || bytesRead != 8 + MAXPLAYERS * (5 + 23)) { + &bytesRead, &newSteamID, 0)) { continue; } net_packet->len = packetlen; @@ -7317,8 +7317,8 @@ namespace MainMenu { } else { createInviteButton(playerDisconnected); } + checkReadyStates(); } - checkReadyStates(); continue; } @@ -8688,7 +8688,7 @@ namespace MainMenu { achievements->setColor(makeColor(180, 37, 37, 255)); achievements->setText("ACHIEVEMENTS DISABLED"); } else { - achievements->setColor(makeColor(37, 40, 180, 255)); + achievements->setColor(makeColor(37, 90, 255, 255)); achievements->setText("ACHIEVEMENTS ENABLED"); } }); @@ -9380,7 +9380,7 @@ namespace MainMenu { achievements->setColor(makeColor(180, 37, 37, 255)); achievements->setText("ACHIEVEMENTS DISABLED"); } else { - achievements->setColor(makeColor(37, 40, 180, 255)); + achievements->setColor(makeColor(37, 90, 255, 255)); achievements->setText("ACHIEVEMENTS ENABLED"); } }); @@ -10687,6 +10687,7 @@ namespace MainMenu { case DLC::MythsAndOutcasts: button->setBackground((prefix + "ClassSelect_IconBGMyths_00.png").c_str()); break; case DLC::LegendsAndPariahs: button->setBackground((prefix + "ClassSelect_IconBGLegends_00.png").c_str()); break; } + button->setIconColor(0); button->setIcon((prefix + full_class.image).c_str()); button->setSize(SDL_Rect{8 + (c % 4) * 54, 6 + (c / 4) * 54, 54, 54}); if (!strcmp(name, current_class_name)) { @@ -11678,9 +11679,13 @@ namespace MainMenu { } static void checkReadyStates() { - assert(main_menu_frame); + if (!main_menu_frame) { + return; + } auto lobby = main_menu_frame->findFrame("lobby"); - assert(lobby); + if (!lobby) { + return; + } bool atLeastOnePlayer = false; bool allReady = true; @@ -14194,9 +14199,9 @@ namespace MainMenu { ); auto host_online_fn = [](Button&){ - soundActivate(); -#ifdef USE_EOS if (LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY) { +#ifdef USE_EOS + soundActivate(); closeNetworkInterfaces(); directConnect = false; EOS.createLobby(); @@ -14214,9 +14219,16 @@ namespace MainMenu { } } }); +#endif // USE_EOS } -#else - createLobby(LobbyType::LobbyOnline); + else if (LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM) { +#ifdef STEAMWORKS + soundActivate(); + createLobby(LobbyType::LobbyOnline); +#endif // STEAMWORKS + } +#if !defined(STEAMWORKS) && !defined(USE_EOS) + soundError(); #endif };