RoomScreen.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. #include "RoomScreen.h"
  2. #include <sstream>
  3. #include "BackgroundImageHandler.h"
  4. #include "Bancho.h"
  5. #include "BanchoNetworking.h"
  6. #include "BanchoUsers.h"
  7. #include "Beatmap.h"
  8. #include "CBaseUIButton.h"
  9. #include "CBaseUIContainer.h"
  10. #include "CBaseUILabel.h"
  11. #include "CBaseUITextbox.h"
  12. #include "Chat.h"
  13. #include "Database.h"
  14. #include "Downloader.h"
  15. #include "Engine.h"
  16. #include "HUD.h"
  17. #include "Keyboard.h"
  18. #include "Lobby.h"
  19. #include "MainMenu.h"
  20. #include "ModSelector.h"
  21. #include "Mouse.h"
  22. #include "Osu.h"
  23. #include "PromptScreen.h"
  24. #include "RankingScreen.h"
  25. #include "Replay.h"
  26. #include "ResourceManager.h"
  27. #include "RichPresence.h"
  28. #include "Skin.h"
  29. #include "SkinImage.h"
  30. #include "SongBrowser.h"
  31. #include "SoundEngine.h"
  32. #include "UIAvatar.h"
  33. #include "UIButton.h"
  34. #include "UICheckbox.h"
  35. #include "UIContextMenu.h"
  36. #include "UISongBrowserSongButton.h"
  37. #include "UIUserContextMenu.h"
  38. void UIModList::draw(Graphics *g) {
  39. std::vector<SkinImage *> mods;
  40. if(*m_flags & ModFlags::Nightcore)
  41. mods.push_back(bancho.osu->getSkin()->getSelectionModNightCore());
  42. else if(*m_flags & ModFlags::DoubleTime)
  43. mods.push_back(bancho.osu->getSkin()->getSelectionModDoubleTime());
  44. bool ht_enabled = *m_flags & ModFlags::HalfTime;
  45. if(ht_enabled && bancho.prefer_daycore)
  46. mods.push_back(bancho.osu->getSkin()->getSelectionModDayCore());
  47. else if(ht_enabled)
  48. mods.push_back(bancho.osu->getSkin()->getSelectionModHalfTime());
  49. if(*m_flags & ModFlags::Perfect)
  50. mods.push_back(bancho.osu->getSkin()->getSelectionModPerfect());
  51. else if(*m_flags & ModFlags::SuddenDeath)
  52. mods.push_back(bancho.osu->getSkin()->getSelectionModSuddenDeath());
  53. if(*m_flags & ModFlags::NoFail) mods.push_back(bancho.osu->getSkin()->getSelectionModNoFail());
  54. if(*m_flags & ModFlags::Easy) mods.push_back(bancho.osu->getSkin()->getSelectionModEasy());
  55. if(*m_flags & ModFlags::TouchDevice) mods.push_back(bancho.osu->getSkin()->getSelectionModTD());
  56. if(*m_flags & ModFlags::Hidden) mods.push_back(bancho.osu->getSkin()->getSelectionModHidden());
  57. if(*m_flags & ModFlags::HardRock) mods.push_back(bancho.osu->getSkin()->getSelectionModHardRock());
  58. if(*m_flags & ModFlags::Relax) mods.push_back(bancho.osu->getSkin()->getSelectionModRelax());
  59. if(*m_flags & ModFlags::Autoplay) mods.push_back(bancho.osu->getSkin()->getSelectionModAutoplay());
  60. if(*m_flags & ModFlags::SpunOut) mods.push_back(bancho.osu->getSkin()->getSelectionModSpunOut());
  61. if(*m_flags & ModFlags::Autopilot) mods.push_back(bancho.osu->getSkin()->getSelectionModAutopilot());
  62. if(*m_flags & ModFlags::Target) mods.push_back(bancho.osu->getSkin()->getSelectionModTarget());
  63. if(*m_flags & ModFlags::ScoreV2) mods.push_back(bancho.osu->getSkin()->getSelectionModScorev2());
  64. g->setColor(0xffffffff);
  65. Vector2 modPos = m_vPos;
  66. for(auto mod : mods) {
  67. float target_height = getSize().y;
  68. float scaling_factor = target_height / mod->getSize().y;
  69. float target_width = mod->getSize().x * scaling_factor;
  70. Vector2 fixed_pos = modPos;
  71. fixed_pos.x += (target_width / 2);
  72. fixed_pos.y += (target_height / 2);
  73. mod->draw(g, fixed_pos, scaling_factor);
  74. // Overlap mods a bit, just like peppy's client does
  75. modPos.x += target_width * 0.6;
  76. }
  77. }
  78. bool UIModList::isVisible() { return *m_flags != 0; }
  79. #define INIT_LABEL(label_name, default_text, is_big) \
  80. do { \
  81. label_name = new CBaseUILabel(0, 0, 0, 0, "label", default_text); \
  82. label_name->setFont(is_big ? lfont : font); \
  83. label_name->setSizeToContent(0, 0); \
  84. label_name->setDrawFrame(false); \
  85. label_name->setDrawBackground(false); \
  86. } while(0)
  87. #define ADD_ELEMENT(element) \
  88. do { \
  89. element->setPos(10, settings_y); \
  90. m_settings->getContainer()->addBaseUIElement(element); \
  91. settings_y += element->getSize().y + 20; \
  92. } while(0)
  93. #define ADD_BUTTON(button, label) \
  94. do { \
  95. button->setPos(label->getSize().x + 20, label->getPos().y - 7); \
  96. m_settings->getContainer()->addBaseUIElement(button); \
  97. } while(0)
  98. RoomScreen::RoomScreen(Osu *osu) : OsuScreen(osu) {
  99. font = engine->getResourceManager()->getFont("FONT_DEFAULT");
  100. lfont = osu->getSubTitleFont();
  101. m_pauseButton = new MainMenuPauseButton(0, 0, 0, 0, "pause_btn", "");
  102. m_pauseButton->setClickCallback(fastdelegate::MakeDelegate(osu->m_mainMenu, &MainMenu::onPausePressed));
  103. addBaseUIElement(m_pauseButton);
  104. m_settings = new CBaseUIScrollView(0, 0, 0, 0, "room_settings");
  105. m_settings->setDrawFrame(false); // it's off by 1 pixel, turn it OFF
  106. m_settings->setDrawBackground(true);
  107. m_settings->setBackgroundColor(0xdd000000);
  108. m_settings->setHorizontalScrolling(false);
  109. addBaseUIElement(m_settings);
  110. INIT_LABEL(m_room_name, "Multiplayer room", true);
  111. INIT_LABEL(m_host, "Host: None", false); // XXX: make it an UIUserLabel
  112. INIT_LABEL(m_room_name_iptl, "Room name", false);
  113. m_room_name_ipt = new CBaseUITextbox(0, 0, m_settings->getSize().x, 40, "");
  114. m_room_name_ipt->setText("Multiplayer room");
  115. m_change_password_btn = new UIButton(osu, 0, 0, 190, 40, "change_password_btn", "Change password");
  116. m_change_password_btn->setColor(0xff0e94b5);
  117. m_change_password_btn->setUseDefaultSkin();
  118. m_change_password_btn->setClickCallback(fastdelegate::MakeDelegate(this, &RoomScreen::onChangePasswordClicked));
  119. INIT_LABEL(m_win_condition, "Win condition: Score", false);
  120. m_change_win_condition_btn = new UIButton(osu, 0, 0, 240, 40, "change_win_condition_btn", "Win condition: Score");
  121. m_change_win_condition_btn->setColor(0xff00ff00);
  122. m_change_win_condition_btn->setUseDefaultSkin();
  123. m_change_win_condition_btn->setClickCallback(
  124. fastdelegate::MakeDelegate(this, &RoomScreen::onChangeWinConditionClicked));
  125. INIT_LABEL(map_label, "Beatmap", true);
  126. m_select_map_btn = new UIButton(osu, 0, 0, 130, 40, "select_map_btn", "Select map");
  127. m_select_map_btn->setColor(0xff0e94b5);
  128. m_select_map_btn->setUseDefaultSkin();
  129. m_select_map_btn->setClickCallback(fastdelegate::MakeDelegate(this, &RoomScreen::onSelectMapClicked));
  130. INIT_LABEL(m_map_title, "(no map selected)", false);
  131. INIT_LABEL(m_map_stars, "", false);
  132. INIT_LABEL(m_map_attributes, "", false);
  133. INIT_LABEL(m_map_attributes2, "", false);
  134. INIT_LABEL(mods_label, "Mods", true);
  135. m_select_mods_btn = new UIButton(osu, 0, 0, 180, 40, "select_mods_btn", "Select mods [F1]");
  136. m_select_mods_btn->setColor(0xff0e94b5);
  137. m_select_mods_btn->setUseDefaultSkin();
  138. m_select_mods_btn->setClickCallback(fastdelegate::MakeDelegate(this, &RoomScreen::onSelectModsClicked));
  139. m_freemod = new UICheckbox(m_osu, 0, 0, 200, 50, "allow_freemod", "Freemod");
  140. m_freemod->setDrawFrame(false);
  141. m_freemod->setDrawBackground(false);
  142. m_freemod->setChangeCallback(fastdelegate::MakeDelegate(this, &RoomScreen::onFreemodCheckboxChanged));
  143. m_mods = new UIModList(&bancho.room.mods);
  144. INIT_LABEL(m_no_mods_selected, "No mods selected.", false);
  145. m_ready_btn = new UIButton(osu, 0, 0, 300, 50, "start_game_btn", "Start game");
  146. m_ready_btn->setColor(0xff00ff00);
  147. m_ready_btn->setUseDefaultSkin();
  148. m_ready_btn->setClickCallback(fastdelegate::MakeDelegate(this, &RoomScreen::onReadyButtonClick));
  149. m_ready_btn->is_loading = true;
  150. auto player_list_label = new CBaseUILabel(50, 50, 0, 0, "label", "Player list");
  151. player_list_label->setFont(lfont);
  152. player_list_label->setSizeToContent(0, 0);
  153. player_list_label->setDrawFrame(false);
  154. player_list_label->setDrawBackground(false);
  155. addBaseUIElement(player_list_label);
  156. m_slotlist = new CBaseUIScrollView(50, 90, 0, 0, "slot_list");
  157. m_slotlist->setDrawFrame(true);
  158. m_slotlist->setDrawBackground(true);
  159. m_slotlist->setBackgroundColor(0xdd000000);
  160. m_slotlist->setHorizontalScrolling(false);
  161. addBaseUIElement(m_slotlist);
  162. m_contextMenu = new UIContextMenu(m_osu, 50, 50, 150, 0, "", m_settings);
  163. addBaseUIElement(m_contextMenu);
  164. updateLayout(m_osu->getScreenSize());
  165. }
  166. void RoomScreen::draw(Graphics *g) {
  167. if(!m_bVisible) return;
  168. // XXX: Add convar for toggling room backgrounds
  169. SongBrowser::drawSelectedBeatmapBackgroundImage(g, m_osu, 1.0);
  170. OsuScreen::draw(g);
  171. // Update avatar visibility status
  172. for(auto elm : m_slotlist->getContainer()->getElements()) {
  173. if(elm->getName() == UString("avatar")) {
  174. // NOTE: Not checking horizontal visibility
  175. auto avatar = (UIAvatar *)elm;
  176. bool is_below_top = avatar->getPos().y + avatar->getSize().y >= m_slotlist->getPos().y;
  177. bool is_above_bottom = avatar->getPos().y <= m_slotlist->getPos().y + m_slotlist->getSize().y;
  178. avatar->on_screen = is_below_top && is_above_bottom;
  179. }
  180. }
  181. // Not technically drawing below this line, just checking for map download progress
  182. if(bancho.room.map_id == 0) return;
  183. if(bancho.room.map_id == -1) {
  184. m_map_title->setText("Host is selecting a map...");
  185. m_map_title->setSizeToContent(0, 0);
  186. m_ready_btn->is_loading = true;
  187. return;
  188. }
  189. auto search = mapset_by_mapid.find(bancho.room.map_id);
  190. if(search == mapset_by_mapid.end()) return;
  191. u32 set_id = search->second;
  192. if(set_id == 0) {
  193. auto error_str = UString::format("Could not find beatmapset for map ID %d", bancho.room.map_id);
  194. m_map_title->setText(error_str);
  195. m_map_title->setSizeToContent(0, 0);
  196. m_ready_btn->is_loading = true;
  197. return;
  198. }
  199. // Download mapset
  200. float progress = -1.f;
  201. download_beatmapset(set_id, &progress);
  202. if(progress == -1.f) {
  203. auto error_str = UString::format("Failed to download beatmapset %d :(", set_id);
  204. m_map_title->setText(error_str);
  205. m_map_title->setSizeToContent(0, 0);
  206. m_ready_btn->is_loading = true;
  207. bancho.room.map_id = 0; // don't try downloading it again
  208. } else if(progress < 1.f) {
  209. auto text = UString::format("Downloading... %.2f%%", progress * 100.f);
  210. m_map_title->setText(text.toUtf8());
  211. m_map_title->setSizeToContent(0, 0);
  212. m_ready_btn->is_loading = true;
  213. } else {
  214. std::stringstream ss;
  215. ss << MCENGINE_DATA_DIR "maps/" << std::to_string(set_id) << "/";
  216. auto mapset_path = ss.str();
  217. // XXX: Make a permanent database for auto-downloaded songs, so we can load them like osu!.db's
  218. m_osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path);
  219. m_osu->m_songBrowser2->updateSongButtonSorting();
  220. debugLog("Finished loading beatmapset %d.\n", set_id);
  221. on_map_change(false);
  222. }
  223. }
  224. void RoomScreen::mouse_update(bool *propagate_clicks) {
  225. if(!m_bVisible || m_osu->m_songBrowser2->isVisible()) return;
  226. const bool room_name_changed = m_room_name_ipt->getText() != bancho.room.name;
  227. if(bancho.room.is_host() && room_name_changed) {
  228. // XXX: should only update 500ms after last input
  229. bancho.room.name = m_room_name_ipt->getText();
  230. Packet packet;
  231. packet.id = MATCH_CHANGE_SETTINGS;
  232. bancho.room.pack(&packet);
  233. send_packet(packet);
  234. }
  235. m_pauseButton->setPaused(!m_osu->getSelectedBeatmap()->isPreviewMusicPlaying());
  236. m_contextMenu->mouse_update(propagate_clicks);
  237. if(!*propagate_clicks) return;
  238. OsuScreen::mouse_update(propagate_clicks);
  239. }
  240. void RoomScreen::onKeyDown(KeyboardEvent &key) {
  241. if(!m_bVisible || m_osu->m_songBrowser2->isVisible()) return;
  242. if(key.getKeyCode() == KEY_ESCAPE) {
  243. key.consume();
  244. ragequit();
  245. return;
  246. }
  247. if(key.getKeyCode() == KEY_F1) {
  248. key.consume();
  249. if(bancho.room.freemods || bancho.room.is_host()) {
  250. m_osu->m_modSelector->setVisible(!m_osu->m_modSelector->isVisible());
  251. m_bVisible = !m_osu->m_modSelector->isVisible();
  252. }
  253. return;
  254. }
  255. OsuScreen::onKeyDown(key);
  256. }
  257. void RoomScreen::onKeyUp(KeyboardEvent &key) {
  258. if(!m_bVisible || m_osu->m_songBrowser2->isVisible()) return;
  259. OsuScreen::onKeyUp(key);
  260. }
  261. void RoomScreen::onChar(KeyboardEvent &key) {
  262. if(!m_bVisible || m_osu->m_songBrowser2->isVisible()) return;
  263. OsuScreen::onChar(key);
  264. }
  265. void RoomScreen::onResolutionChange(Vector2 newResolution) { updateLayout(newResolution); }
  266. CBaseUIContainer *RoomScreen::setVisible(bool visible) {
  267. // NOTE: Calling setVisible(false) does not quit the room! Call ragequit() instead.
  268. m_bVisible = visible;
  269. return this;
  270. }
  271. void RoomScreen::updateSettingsLayout(Vector2 newResolution) {
  272. const bool is_host = bancho.room.is_host();
  273. int settings_y = 10;
  274. m_settings->getContainer()->empty();
  275. m_settings->setPos(round(newResolution.x * 0.6), 0);
  276. m_settings->setSize(round(newResolution.x * 0.4), newResolution.y);
  277. // Room name (title)
  278. m_room_name->setText(bancho.room.name);
  279. m_room_name->setSizeToContent();
  280. ADD_ELEMENT(m_room_name);
  281. if(is_host) {
  282. ADD_BUTTON(m_change_password_btn, m_room_name);
  283. }
  284. // Host name
  285. if(!is_host) {
  286. UString host_str = "Host: None";
  287. if(bancho.room.host_id > 0) {
  288. auto host = get_user_info(bancho.room.host_id);
  289. host_str = UString::format("Host: %s", host->name.toUtf8());
  290. }
  291. m_host->setText(host_str);
  292. ADD_ELEMENT(m_host);
  293. }
  294. if(is_host) {
  295. // Room name (input)
  296. ADD_ELEMENT(m_room_name_iptl);
  297. m_room_name_ipt->setSize(m_settings->getSize().x - 20, 40);
  298. ADD_ELEMENT(m_room_name_ipt);
  299. }
  300. // Win condition
  301. if(bancho.room.win_condition == SCOREV1) {
  302. m_win_condition->setText("Win condition: Score");
  303. } else if(bancho.room.win_condition == ACCURACY) {
  304. m_win_condition->setText("Win condition: Accuracy");
  305. } else if(bancho.room.win_condition == COMBO) {
  306. m_win_condition->setText("Win condition: Combo");
  307. } else if(bancho.room.win_condition == SCOREV2) {
  308. m_win_condition->setText("Win condition: ScoreV2");
  309. }
  310. if(is_host) {
  311. m_change_win_condition_btn->setText(m_win_condition->getText());
  312. ADD_ELEMENT(m_change_win_condition_btn);
  313. } else {
  314. ADD_ELEMENT(m_win_condition);
  315. }
  316. // Beatmap
  317. settings_y += 10;
  318. ADD_ELEMENT(map_label);
  319. if(is_host) {
  320. ADD_BUTTON(m_select_map_btn, map_label);
  321. }
  322. ADD_ELEMENT(m_map_title);
  323. if(!m_ready_btn->is_loading) {
  324. ADD_ELEMENT(m_map_stars);
  325. ADD_ELEMENT(m_map_attributes);
  326. ADD_ELEMENT(m_map_attributes2);
  327. }
  328. // Mods
  329. settings_y += 10;
  330. ADD_ELEMENT(mods_label);
  331. if(is_host || bancho.room.freemods) {
  332. ADD_BUTTON(m_select_mods_btn, mods_label);
  333. }
  334. if(is_host) {
  335. m_freemod->setChecked(bancho.room.freemods);
  336. ADD_ELEMENT(m_freemod);
  337. }
  338. if(bancho.room.mods == 0) {
  339. ADD_ELEMENT(m_no_mods_selected);
  340. } else {
  341. m_mods->m_flags = &bancho.room.mods;
  342. m_mods->setSize(300, 90);
  343. ADD_ELEMENT(m_mods);
  344. }
  345. // Ready button
  346. int nb_ready = 0;
  347. bool is_ready = false;
  348. for(u8 i = 0; i < 16; i++) {
  349. if(bancho.room.slots[i].has_player() && bancho.room.slots[i].is_ready()) {
  350. nb_ready++;
  351. if(bancho.room.slots[i].player_id == bancho.user_id) {
  352. is_ready = true;
  353. }
  354. }
  355. }
  356. if(is_host && is_ready && nb_ready > 1) {
  357. auto force_start_str = UString::format("Force start (%d/%d)", nb_ready, bancho.room.nb_players);
  358. if(bancho.room.all_players_ready()) {
  359. force_start_str = UString("Start game");
  360. }
  361. m_ready_btn->setText(force_start_str);
  362. m_ready_btn->setColor(0xff00ff00);
  363. } else {
  364. m_ready_btn->setText(is_ready ? "Not ready" : "Ready!");
  365. m_ready_btn->setColor(is_ready ? 0xffff0000 : 0xff00ff00);
  366. }
  367. ADD_ELEMENT(m_ready_btn);
  368. m_settings->setScrollSizeToContent();
  369. }
  370. void RoomScreen::updateLayout(Vector2 newResolution) {
  371. setSize(newResolution);
  372. updateSettingsLayout(newResolution);
  373. const float dpiScale = Osu::getUIScale(m_osu);
  374. m_pauseButton->setSize(30 * dpiScale, 30 * dpiScale);
  375. m_pauseButton->setPos(round(newResolution.x * 0.6) - m_pauseButton->getSize().x * 2 - 10 * dpiScale,
  376. m_pauseButton->getSize().y + 10 * dpiScale);
  377. // XXX: Display detailed user presence
  378. m_slotlist->setSize(newResolution.x * 0.6 - 200, newResolution.y * 0.6 - 110);
  379. m_slotlist->clear();
  380. int y_total = 10;
  381. for(int i = 0; i < 16; i++) {
  382. if(bancho.room.slots[i].has_player()) {
  383. auto user_info = get_user_info(bancho.room.slots[i].player_id);
  384. auto username = user_info->name;
  385. if(bancho.room.slots[i].is_player_playing()) {
  386. username = UString::format("[playing] %s", user_info->name.toUtf8());
  387. }
  388. const float SLOT_HEIGHT = 40.f;
  389. auto avatar = new UIAvatar(bancho.room.slots[i].player_id, 10, y_total, SLOT_HEIGHT, SLOT_HEIGHT);
  390. m_slotlist->getContainer()->addBaseUIElement(avatar);
  391. auto user_box = new UIUserLabel(m_osu, bancho.room.slots[i].player_id, username);
  392. user_box->setFont(lfont);
  393. user_box->setPos(avatar->getRelPos().x + avatar->getSize().x + 10, y_total);
  394. auto color = 0xffffffff;
  395. if(bancho.room.slots[i].is_ready()) {
  396. color = 0xff00ff00;
  397. } else if(bancho.room.slots[i].no_map()) {
  398. color = 0xffdd0000;
  399. }
  400. user_box->setTextColor(color);
  401. user_box->setSizeToContent();
  402. user_box->setSize(user_box->getSize().x, SLOT_HEIGHT);
  403. m_slotlist->getContainer()->addBaseUIElement(user_box);
  404. auto user_mods = new UIModList(&bancho.room.slots[i].mods);
  405. user_mods->setPos(user_box->getPos().x + user_box->getSize().x + 30, y_total);
  406. user_mods->setSize(350, SLOT_HEIGHT);
  407. m_slotlist->getContainer()->addBaseUIElement(user_mods);
  408. y_total += SLOT_HEIGHT + 5;
  409. }
  410. }
  411. m_slotlist->setScrollSizeToContent();
  412. }
  413. // Exit to main menu
  414. void RoomScreen::ragequit() {
  415. m_bVisible = false;
  416. bancho.match_started = false;
  417. Packet packet;
  418. packet.id = EXIT_ROOM;
  419. send_packet(packet);
  420. m_osu->m_modSelector->resetMods();
  421. m_osu->m_modSelector->updateButtons();
  422. bancho.room = Room();
  423. m_osu->m_mainMenu->setVisible(true);
  424. m_osu->m_chat->removeChannel("#multiplayer");
  425. m_osu->m_chat->updateVisibility();
  426. ConVars::sv_cheats.setValue(!bancho.submit_scores());
  427. }
  428. void RoomScreen::process_beatmapset_info_response(Packet packet) {
  429. u32 map_id = packet.extra_int;
  430. if(packet.size == 0) {
  431. bancho.osu->m_room->mapset_by_mapid[map_id] = 0;
  432. return;
  433. }
  434. // {set_id}.osz|{artist}|{title}|{creator}|{status}|10.0|{last_update}|{set_id}|0|0|0|0|0
  435. char *saveptr = NULL;
  436. char *str = strtok_r((char *)packet.memory, "|", &saveptr);
  437. if(!str) return;
  438. // Do nothing with beatmapset filename
  439. str = strtok_r(NULL, "|", &saveptr);
  440. if(!str) return;
  441. // Do nothing with beatmap artist
  442. str = strtok_r(NULL, "|", &saveptr);
  443. if(!str) return;
  444. // Do nothing with beatmap title
  445. str = strtok_r(NULL, "|", &saveptr);
  446. if(!str) return;
  447. // Do nothing with beatmap creator
  448. str = strtok_r(NULL, "|", &saveptr);
  449. if(!str) return;
  450. // Do nothing with beatmap status
  451. str = strtok_r(NULL, "|", &saveptr);
  452. if(!str) return;
  453. // Do nothing with beatmap rating
  454. str = strtok_r(NULL, "|", &saveptr);
  455. if(!str) return;
  456. // Do nothing with beatmap last update
  457. str = strtok_r(NULL, "|", &saveptr);
  458. if(!str) return;
  459. bancho.osu->m_room->mapset_by_mapid[map_id] = strtoul(str, NULL, 10);
  460. // Do nothing with the rest
  461. }
  462. void RoomScreen::on_map_change(bool download) {
  463. // Results screen has map background and such showing, so prevent map from changing while we're on it.
  464. if(m_osu->m_rankingScreen->isVisible()) return;
  465. debugLog("Map changed to ID %d, MD5 %s: %s\n", bancho.room.map_id, bancho.room.map_md5.hash,
  466. bancho.room.map_name.toUtf8());
  467. m_ready_btn->is_loading = true;
  468. // Deselect current map
  469. m_pauseButton->setPaused(true);
  470. m_osu->m_songBrowser2->m_selectedBeatmap->deselect();
  471. if(bancho.room.map_id == 0) {
  472. m_map_title->setText("(no map selected)");
  473. m_map_title->setSizeToContent(0, 0);
  474. m_ready_btn->is_loading = true;
  475. } else {
  476. auto beatmap = m_osu->getSongBrowser()->getDatabase()->getBeatmapDifficulty(bancho.room.map_md5);
  477. if(beatmap != nullptr) {
  478. m_osu->m_songBrowser2->onDifficultySelected(beatmap, false);
  479. m_map_title->setText(bancho.room.map_name);
  480. m_map_title->setSizeToContent(0, 0);
  481. auto attributes = UString::format("AR: %.1f, CS: %.1f, HP: %.1f, OD: %.1f", beatmap->getAR(),
  482. beatmap->getCS(), beatmap->getHP(), beatmap->getOD());
  483. m_map_attributes->setText(attributes);
  484. m_map_attributes->setSizeToContent(0, 0);
  485. auto attributes2 = UString::format("Length: %d seconds, BPM: %d (%d - %d)", beatmap->getLengthMS() / 1000,
  486. beatmap->getMostCommonBPM(), beatmap->getMinBPM(), beatmap->getMaxBPM());
  487. m_map_attributes2->setText(attributes2);
  488. m_map_attributes2->setSizeToContent(0, 0);
  489. auto stars = UString::format("Star rating: %.2f*", beatmap->getStarsNomod());
  490. m_map_stars->setText(stars);
  491. m_map_stars->setSizeToContent(0, 0);
  492. m_ready_btn->is_loading = false;
  493. Packet packet;
  494. packet.id = MATCH_HAS_BEATMAP;
  495. send_packet(packet);
  496. } else if(download) {
  497. // Request beatmap info - automatically starts download
  498. auto path = UString::format("/web/osu-search-set.php?b=%d&u=%s&h=%s", bancho.room.map_id,
  499. bancho.username.toUtf8(), bancho.pw_md5.toUtf8());
  500. APIRequest request;
  501. request.type = GET_BEATMAPSET_INFO;
  502. request.path = path;
  503. request.mime = NULL;
  504. request.extra_int = (u32)bancho.room.map_id;
  505. send_api_request(request);
  506. m_map_title->setText("Loading...");
  507. m_map_title->setSizeToContent(0, 0);
  508. m_ready_btn->is_loading = true;
  509. Packet packet;
  510. packet.id = MATCH_NO_BEATMAP;
  511. send_packet(packet);
  512. } else {
  513. m_map_title->setText("Failed to load map. Is it catch/taiko/mania?");
  514. m_map_title->setSizeToContent(0, 0);
  515. m_ready_btn->is_loading = true;
  516. bancho.room.map_id = 0; // prevents trying to load it again
  517. Packet packet;
  518. packet.id = MATCH_NO_BEATMAP;
  519. send_packet(packet);
  520. }
  521. }
  522. updateLayout(m_osu->getScreenSize());
  523. }
  524. void RoomScreen::on_room_joined(Room room) {
  525. ConVars::sv_cheats.setValue(false);
  526. bancho.room = room;
  527. debugLog("Joined room #%d\nPlayers:\n", room.id);
  528. for(int i = 0; i < 16; i++) {
  529. if(room.slots[i].has_player()) {
  530. auto user_info = get_user_info(room.slots[i].player_id);
  531. debugLog("- %s\n", user_info->name.toUtf8());
  532. }
  533. }
  534. on_map_change(true);
  535. // Currently we can only join rooms from the lobby.
  536. // If we add ability to join from links, you would need to hide all other
  537. // screens, kick the player out of the song they're currently playing, etc.
  538. m_osu->m_lobby->setVisible(false);
  539. updateLayout(m_osu->getScreenSize());
  540. m_bVisible = true;
  541. RichPresence::setBanchoStatus(m_osu, room.name.toUtf8(), MULTIPLAYER);
  542. m_osu->m_chat->addChannel("#multiplayer");
  543. m_osu->m_chat->updateVisibility();
  544. m_osu->m_modSelector->resetMods();
  545. m_osu->m_modSelector->enableModsFromFlags(bancho.room.mods);
  546. }
  547. void RoomScreen::on_room_updated(Room room) {
  548. if(bancho.is_playing_a_multi_map() || !bancho.is_in_a_multi_room()) return;
  549. bool was_host = bancho.room.is_host();
  550. bool map_changed = bancho.room.map_id != room.map_id;
  551. bancho.room = room;
  552. Slot *player_slot = nullptr;
  553. for(int i = 0; i < 16; i++) {
  554. if(bancho.room.slots[i].player_id == bancho.user_id) {
  555. player_slot = &bancho.room.slots[i];
  556. break;
  557. }
  558. }
  559. if(player_slot == nullptr) {
  560. // Player got kicked
  561. ragequit();
  562. return;
  563. }
  564. if(map_changed) {
  565. on_map_change(true);
  566. }
  567. if(!was_host && bancho.room.is_host()) {
  568. m_room_name_ipt->setText(bancho.room.name);
  569. }
  570. m_osu->m_modSelector->updateButtons();
  571. m_osu->m_modSelector->resetMods();
  572. m_osu->m_modSelector->enableModsFromFlags(bancho.room.mods | player_slot->mods);
  573. updateLayout(m_osu->getScreenSize());
  574. }
  575. void RoomScreen::on_match_started(Room room) {
  576. bancho.room = room;
  577. if(m_osu->getSelectedBeatmap() == nullptr) {
  578. debugLog("We received MATCH_STARTED without being ready, wtf!\n");
  579. return;
  580. }
  581. last_packet_tms = time(NULL);
  582. if(m_osu->getSelectedBeatmap()->play()) {
  583. m_bVisible = false;
  584. bancho.match_started = true;
  585. m_osu->m_songBrowser2->m_bHasSelectedAndIsPlaying = true;
  586. m_osu->onPlayStart();
  587. m_osu->m_chat->updateVisibility();
  588. } else {
  589. ragequit(); // map failed to load
  590. }
  591. }
  592. void RoomScreen::on_match_score_updated(Packet *packet) {
  593. i32 update_tms = read<u32>(packet);
  594. u8 slot_id = read<u8>(packet);
  595. if(slot_id > 15) return;
  596. auto slot = &bancho.room.slots[slot_id];
  597. slot->last_update_tms = update_tms;
  598. slot->num300 = read<u16>(packet);
  599. slot->num100 = read<u16>(packet);
  600. slot->num50 = read<u16>(packet);
  601. slot->num_geki = read<u16>(packet);
  602. slot->num_katu = read<u16>(packet);
  603. slot->num_miss = read<u16>(packet);
  604. slot->total_score = read<u32>(packet);
  605. slot->max_combo = read<u16>(packet);
  606. slot->current_combo = read<u16>(packet);
  607. slot->is_perfect = read<u8>(packet);
  608. slot->current_hp = read<u8>(packet);
  609. slot->tag = read<u8>(packet);
  610. bool is_scorev2 = read<u8>(packet);
  611. if(is_scorev2) {
  612. slot->sv2_combo = read<f64>(packet);
  613. slot->sv2_bonus = read<f64>(packet);
  614. }
  615. bancho.osu->m_hud->updateScoreboard(true);
  616. }
  617. void RoomScreen::on_all_players_loaded() {
  618. bancho.room.all_players_loaded = true;
  619. m_osu->m_chat->updateVisibility();
  620. }
  621. void RoomScreen::on_player_failed(i32 slot_id) {
  622. if(slot_id < 0 || slot_id > 15) return;
  623. bancho.room.slots[slot_id].died = true;
  624. }
  625. // All players have finished.
  626. void RoomScreen::on_match_finished() {
  627. if(!bancho.is_playing_a_multi_map()) return;
  628. memcpy(bancho.last_scores, bancho.room.slots, sizeof(bancho.room.slots));
  629. m_osu->onPlayEnd(false, false);
  630. bancho.match_started = false;
  631. m_osu->m_rankingScreen->setVisible(true);
  632. m_osu->m_chat->updateVisibility();
  633. }
  634. void RoomScreen::on_all_players_skipped() { bancho.room.all_players_skipped = true; }
  635. void RoomScreen::on_player_skip(i32 user_id) {
  636. for(int i = 0; i < 16; i++) {
  637. if(bancho.room.slots[i].player_id == user_id) {
  638. bancho.room.slots[i].skipped = true;
  639. break;
  640. }
  641. }
  642. }
  643. void RoomScreen::on_match_aborted() {
  644. if(!bancho.is_playing_a_multi_map()) return;
  645. m_osu->onPlayEnd(false, true);
  646. m_bVisible = true;
  647. bancho.match_started = false;
  648. }
  649. void RoomScreen::onClientScoreChange(bool force) {
  650. if(!bancho.is_playing_a_multi_map()) return;
  651. // Update at most once every 250ms
  652. bool should_update = difftime(time(NULL), last_packet_tms) > 0.25;
  653. if(!should_update && !force) return;
  654. Packet packet;
  655. packet.id = UPDATE_MATCH_SCORE;
  656. write_u32(&packet, (i32)m_osu->getSelectedBeatmap()->getTime());
  657. u8 slot_id = 0;
  658. for(u8 i = 0; i < 16; i++) {
  659. if(bancho.room.slots[i].player_id == bancho.user_id) {
  660. slot_id = i;
  661. break;
  662. }
  663. }
  664. write_u8(&packet, slot_id);
  665. write_u16(&packet, (u16)m_osu->getScore()->getNum300s());
  666. write_u16(&packet, (u16)m_osu->getScore()->getNum100s());
  667. write_u16(&packet, (u16)m_osu->getScore()->getNum50s());
  668. write_u16(&packet, (u16)m_osu->getScore()->getNum300gs());
  669. write_u16(&packet, (u16)m_osu->getScore()->getNum100ks());
  670. write_u16(&packet, (u16)m_osu->getScore()->getNumMisses());
  671. write_u32(&packet, (i32)m_osu->getScore()->getScore());
  672. write_u16(&packet, (u16)m_osu->getScore()->getCombo());
  673. write_u16(&packet, (u16)m_osu->getScore()->getComboMax());
  674. write_u8(&packet, m_osu->getScore()->getNumSliderBreaks() == 0 && m_osu->getScore()->getNumMisses() == 0 &&
  675. m_osu->getScore()->getNum50s() == 0 && m_osu->getScore()->getNum100s() == 0);
  676. write_u8(&packet, m_osu->getSelectedBeatmap()->getHealth() * 200);
  677. write_u8(&packet, 0); // 4P, not supported
  678. write_u8(&packet, m_osu->getModScorev2());
  679. send_packet(packet);
  680. last_packet_tms = time(NULL);
  681. }
  682. void RoomScreen::onReadyButtonClick() {
  683. if(m_ready_btn->is_loading) return;
  684. engine->getSound()->play(m_osu->getSkin()->getMenuHit());
  685. int nb_ready = 0;
  686. bool is_ready = false;
  687. for(u8 i = 0; i < 16; i++) {
  688. if(bancho.room.slots[i].has_player() && bancho.room.slots[i].is_ready()) {
  689. nb_ready++;
  690. if(bancho.room.slots[i].player_id == bancho.user_id) {
  691. is_ready = true;
  692. }
  693. }
  694. }
  695. if(bancho.room.is_host() && is_ready && nb_ready > 1) {
  696. Packet packet;
  697. packet.id = START_MATCH;
  698. send_packet(packet);
  699. } else {
  700. Packet packet;
  701. packet.id = is_ready ? MATCH_NOT_READY : MATCH_READY;
  702. send_packet(packet);
  703. }
  704. }
  705. void RoomScreen::onSelectModsClicked() {
  706. engine->getSound()->play(m_osu->getSkin()->getMenuHit());
  707. m_osu->m_modSelector->setVisible(true);
  708. m_bVisible = false;
  709. }
  710. void RoomScreen::onSelectMapClicked() {
  711. if(!bancho.room.is_host()) return;
  712. engine->getSound()->play(m_osu->getSkin()->getMenuHit());
  713. Packet packet;
  714. packet.id = MATCH_CHANGE_SETTINGS;
  715. bancho.room.map_id = -1;
  716. bancho.room.map_name = "";
  717. bancho.room.map_md5 = "";
  718. bancho.room.pack(&packet);
  719. send_packet(packet);
  720. m_osu->m_songBrowser2->setVisible(true);
  721. }
  722. void RoomScreen::onChangePasswordClicked() {
  723. m_osu->m_prompt->prompt("New password:", fastdelegate::MakeDelegate(this, &RoomScreen::set_new_password));
  724. }
  725. void RoomScreen::onChangeWinConditionClicked() {
  726. m_contextMenu->setVisible(false);
  727. m_contextMenu->begin();
  728. m_contextMenu->addButton("Score V1", SCOREV1);
  729. m_contextMenu->addButton("Score V2", SCOREV2);
  730. m_contextMenu->addButton("Accuracy", ACCURACY);
  731. m_contextMenu->addButton("Combo", COMBO);
  732. m_contextMenu->end(false, false);
  733. m_contextMenu->setPos(engine->getMouse()->getPos());
  734. m_contextMenu->setClickCallback(fastdelegate::MakeDelegate(this, &RoomScreen::onWinConditionSelected));
  735. m_contextMenu->setVisible(true);
  736. }
  737. void RoomScreen::onWinConditionSelected(UString win_condition_str, int win_condition) {
  738. bancho.room.win_condition = win_condition;
  739. Packet packet;
  740. packet.id = MATCH_CHANGE_SETTINGS;
  741. bancho.room.pack(&packet);
  742. send_packet(packet);
  743. updateLayout(m_osu->getScreenSize());
  744. }
  745. void RoomScreen::set_new_password(UString new_password) {
  746. bancho.room.has_password = new_password.length() > 0;
  747. bancho.room.password = new_password;
  748. Packet packet;
  749. packet.id = CHANGE_ROOM_PASSWORD;
  750. bancho.room.pack(&packet);
  751. send_packet(packet);
  752. }
  753. void RoomScreen::onFreemodCheckboxChanged(CBaseUICheckbox *checkbox) {
  754. if(!bancho.room.is_host()) return;
  755. bancho.room.freemods = checkbox->isChecked();
  756. Packet packet;
  757. packet.id = MATCH_CHANGE_SETTINGS;
  758. bancho.room.pack(&packet);
  759. send_packet(packet);
  760. on_room_updated(bancho.room);
  761. }