game.c 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  1. #include <callback/game.h>
  2. //
  3. #include "engine/engine.h"
  4. #include "engine/game_engine.h"
  5. #include "engine/game_manager_i.h"
  6. #include "engine/level_i.h"
  7. #include "engine/entity_i.h"
  8. //
  9. #include "game/storage.h"
  10. //
  11. #include <callback/loader.h>
  12. #include <callback/free.h>
  13. #include <callback/alloc.h>
  14. #include <callback/callback.h>
  15. #include "alloc/alloc.h"
  16. #include <flip_storage/storage.h>
  17. bool user_hit_back = false;
  18. uint32_t lobby_index = -1;
  19. char *lobby_list[10];
  20. static uint8_t timer_iteration = 0; // timer iteration for the loading screen
  21. static uint8_t timer_refresh = 5; // duration for timer to refresh
  22. FuriThread *game_thread = NULL;
  23. FuriThread *waiting_thread = NULL;
  24. bool game_thread_running = false;
  25. bool waiting_thread_running = false;
  26. static void game_frame_cb(GameEngine *engine, Canvas *canvas, InputState input, void *context)
  27. {
  28. UNUSED(engine);
  29. GameManager *game_manager = context;
  30. game_manager_input_set(game_manager, input);
  31. game_manager_update(game_manager);
  32. game_manager_render(game_manager, canvas);
  33. }
  34. static int32_t game_app(void *p)
  35. {
  36. UNUSED(p);
  37. GameManager *game_manager = game_manager_alloc();
  38. if (!game_manager)
  39. {
  40. FURI_LOG_E("Game", "Failed to allocate game manager");
  41. return -1;
  42. }
  43. // Setup game engine settings...
  44. GameEngineSettings settings = game_engine_settings_init();
  45. settings.target_fps = atof_(fps_choices_str[fps_index]);
  46. settings.show_fps = game.show_fps;
  47. settings.always_backlight = strstr(yes_or_no_choices[screen_always_on_index], "Yes") != NULL;
  48. settings.frame_callback = game_frame_cb;
  49. settings.context = game_manager;
  50. GameEngine *engine = game_engine_alloc(settings);
  51. if (!engine)
  52. {
  53. FURI_LOG_E("Game", "Failed to allocate game engine");
  54. game_manager_free(game_manager);
  55. return -1;
  56. }
  57. game_manager_engine_set(game_manager, engine);
  58. // Allocate custom game context if needed
  59. void *game_context = NULL;
  60. if (game.context_size > 0)
  61. {
  62. game_context = malloc(game.context_size);
  63. game_manager_game_context_set(game_manager, game_context);
  64. }
  65. // Start the game
  66. game.start(game_manager, game_context);
  67. // 1) Run the engine
  68. game_engine_run(engine);
  69. // 2) Stop the game FIRST, so it can do any internal cleanup
  70. game.stop(game_context);
  71. // 3) Now free the engine
  72. game_engine_free(engine);
  73. // 4) Now free the manager
  74. game_manager_free(game_manager);
  75. // 5) Finally, free your custom context if it was allocated
  76. if (game_context)
  77. {
  78. free(game_context);
  79. }
  80. // 6) Check for leftover entities
  81. int32_t entities = entities_get_count();
  82. if (entities != 0)
  83. {
  84. FURI_LOG_E("Game", "Memory leak detected: %ld entities still allocated", entities);
  85. return -1;
  86. }
  87. return 0;
  88. }
  89. static int32_t game_waiting_app_callback(void *p)
  90. {
  91. FlipWorldApp *app = (FlipWorldApp *)p;
  92. furi_check(app);
  93. FlipperHTTP *fhttp = flipper_http_alloc();
  94. if (!fhttp)
  95. {
  96. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  97. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP");
  98. return -1;
  99. }
  100. user_hit_back = false;
  101. timer_iteration = 0;
  102. while (timer_iteration < 60 && !user_hit_back)
  103. {
  104. FURI_LOG_I(TAG, "Waiting for more players...");
  105. game_waiting_process(fhttp, app);
  106. FURI_LOG_I(TAG, "Waiting for more players... %d", timer_iteration);
  107. timer_iteration++;
  108. furi_delay_ms(1000 * timer_refresh);
  109. }
  110. // if we reach here, it means we timed out or the user hit back
  111. FURI_LOG_E(TAG, "No players joined within the timeout or user hit back");
  112. remove_player_from_lobby(fhttp);
  113. flipper_http_free(fhttp);
  114. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  115. return 0;
  116. }
  117. static bool game_start_waiting_thread(void *context)
  118. {
  119. FlipWorldApp *app = (FlipWorldApp *)context;
  120. furi_check(app);
  121. // free game thread
  122. if (waiting_thread_running)
  123. {
  124. waiting_thread_running = false;
  125. if (waiting_thread)
  126. {
  127. furi_thread_flags_set(furi_thread_get_id(waiting_thread), WorkerEvtStop);
  128. furi_thread_join(waiting_thread);
  129. furi_thread_free(waiting_thread);
  130. }
  131. }
  132. // start waiting thread
  133. FuriThread *thread = furi_thread_alloc_ex("waiting_thread", 2048, game_waiting_app_callback, app);
  134. if (!thread)
  135. {
  136. FURI_LOG_E(TAG, "Failed to allocate waiting thread");
  137. easy_flipper_dialog("Error", "Failed to allocate waiting thread. Restart your Flipper.");
  138. return false;
  139. }
  140. furi_thread_start(thread);
  141. waiting_thread = thread;
  142. waiting_thread_running = true;
  143. return true;
  144. }
  145. static bool game_fetch_world_list(FlipperHTTP *fhttp)
  146. {
  147. if (!fhttp)
  148. {
  149. FURI_LOG_E(TAG, "fhttp is NULL");
  150. easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  151. return false;
  152. }
  153. // ensure flip_world directory exists
  154. char directory_path[128];
  155. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  156. Storage *storage = furi_record_open(RECORD_STORAGE);
  157. storage_common_mkdir(storage, directory_path);
  158. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  159. storage_common_mkdir(storage, directory_path);
  160. furi_record_close(RECORD_STORAGE);
  161. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  162. fhttp->save_received_data = true;
  163. return flipper_http_request(fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/list/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  164. }
  165. // we will load the palyer stats from the API and save them
  166. // in player_spawn game method, it will load the player stats that we saved
  167. static bool game_fetch_player_stats(FlipperHTTP *fhttp)
  168. {
  169. if (!fhttp)
  170. {
  171. FURI_LOG_E(TAG, "fhttp is NULL");
  172. easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  173. return false;
  174. }
  175. char username[64];
  176. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  177. {
  178. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  179. easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
  180. return false;
  181. }
  182. char url[128];
  183. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/user/game-stats/%s/", username);
  184. // ensure the folders exist
  185. char directory_path[128];
  186. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  187. Storage *storage = furi_record_open(RECORD_STORAGE);
  188. storage_common_mkdir(storage, directory_path);
  189. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
  190. storage_common_mkdir(storage, directory_path);
  191. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player");
  192. storage_common_mkdir(storage, directory_path);
  193. furi_record_close(RECORD_STORAGE);
  194. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player/player_stats.json");
  195. fhttp->save_received_data = true;
  196. return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  197. }
  198. static bool game_thread_start(void *context)
  199. {
  200. FlipWorldApp *app = (FlipWorldApp *)context;
  201. if (!app)
  202. {
  203. FURI_LOG_E(TAG, "app is NULL");
  204. easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
  205. return false;
  206. }
  207. // free everything but message_view
  208. free_variable_item_list(app);
  209. free_text_input_view(app);
  210. // free_submenu_other(app); // free lobby list or settings
  211. loader_view_free(app);
  212. free_game_submenu(app);
  213. // free game thread
  214. if (game_thread_running)
  215. {
  216. game_thread_running = false;
  217. if (game_thread)
  218. {
  219. furi_thread_flags_set(furi_thread_get_id(game_thread), WorkerEvtStop);
  220. furi_thread_join(game_thread);
  221. furi_thread_free(game_thread);
  222. }
  223. }
  224. // start game thread
  225. FuriThread *thread = furi_thread_alloc_ex("game", 2048, game_app, app);
  226. if (!thread)
  227. {
  228. FURI_LOG_E(TAG, "Failed to allocate game thread");
  229. easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
  230. return false;
  231. }
  232. furi_thread_start(thread);
  233. game_thread = thread;
  234. game_thread_running = true;
  235. return true;
  236. }
  237. // combine register, login, and world list fetch into one function to switch to the loader view
  238. static bool game_fetch(DataLoaderModel *model)
  239. {
  240. FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
  241. if (!app)
  242. {
  243. FURI_LOG_E(TAG, "app is NULL");
  244. easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
  245. return false;
  246. }
  247. if (model->request_index == 0)
  248. {
  249. // login
  250. char username[64];
  251. char password[64];
  252. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  253. {
  254. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  255. view_dispatcher_switch_to_view(app->view_dispatcher,
  256. FlipWorldViewSubmenu); // just go back to the main menu for now
  257. easy_flipper_dialog("Error", "Failed to load saved username\nGo to user settings to update.");
  258. return false;
  259. }
  260. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  261. {
  262. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  263. view_dispatcher_switch_to_view(app->view_dispatcher,
  264. FlipWorldViewSubmenu); // just go back to the main menu for now
  265. easy_flipper_dialog("Error", "Failed to load saved password\nGo to settings to update.");
  266. return false;
  267. }
  268. char payload[256];
  269. snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
  270. return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/login/", "{\"Content-Type\":\"application/json\"}", payload);
  271. }
  272. else if (model->request_index == 1)
  273. {
  274. // check if login was successful
  275. char is_logged_in[8];
  276. if (!load_char("is_logged_in", is_logged_in, sizeof(is_logged_in)))
  277. {
  278. FURI_LOG_E(TAG, "Failed to load is_logged_in");
  279. easy_flipper_dialog("Error", "Failed to load is_logged_in\nGo to user settings to update.");
  280. view_dispatcher_switch_to_view(app->view_dispatcher,
  281. FlipWorldViewSubmenu); // just go back to the main menu for now
  282. return false;
  283. }
  284. if (is_str(is_logged_in, "false") && is_str(model->title, "Registering..."))
  285. {
  286. // register
  287. char username[64];
  288. char password[64];
  289. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  290. {
  291. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  292. easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
  293. view_dispatcher_switch_to_view(app->view_dispatcher,
  294. FlipWorldViewSubmenu); // just go back to the main menu for now
  295. return false;
  296. }
  297. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  298. {
  299. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  300. easy_flipper_dialog("Error", "Failed to load saved password. Go to settings to update.");
  301. view_dispatcher_switch_to_view(app->view_dispatcher,
  302. FlipWorldViewSubmenu); // just go back to the main menu for now
  303. return false;
  304. }
  305. char payload[172];
  306. snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
  307. model->title = "Registering...";
  308. return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/register/", "{\"Content-Type\":\"application/json\"}", payload);
  309. }
  310. else
  311. {
  312. model->title = "Fetching World List..";
  313. return game_fetch_world_list(model->fhttp);
  314. }
  315. }
  316. else if (model->request_index == 2)
  317. {
  318. model->title = "Fetching World List..";
  319. return game_fetch_world_list(model->fhttp);
  320. }
  321. else if (model->request_index == 3)
  322. {
  323. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  324. FuriString *world_list = flipper_http_load_from_file(model->fhttp->file_path);
  325. if (!world_list)
  326. {
  327. view_dispatcher_switch_to_view(app->view_dispatcher,
  328. FlipWorldViewSubmenu); // just go back to the main menu for now
  329. FURI_LOG_E(TAG, "Failed to load world list");
  330. easy_flipper_dialog("Error", "Failed to load world list. Go to game settings to download packs.");
  331. return false;
  332. }
  333. FuriString *first_world = get_json_array_value_furi("worlds", 0, world_list);
  334. if (!first_world)
  335. {
  336. view_dispatcher_switch_to_view(app->view_dispatcher,
  337. FlipWorldViewSubmenu); // just go back to the main menu for now
  338. FURI_LOG_E(TAG, "Failed to get first world");
  339. easy_flipper_dialog("Error", "Failed to get first world. Go to game settings to download packs.");
  340. furi_string_free(world_list);
  341. return false;
  342. }
  343. if (world_exists(furi_string_get_cstr(first_world)))
  344. {
  345. furi_string_free(world_list);
  346. furi_string_free(first_world);
  347. if (!game_thread_start(app))
  348. {
  349. FURI_LOG_E(TAG, "Failed to start game thread");
  350. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  351. view_dispatcher_switch_to_view(app->view_dispatcher,
  352. FlipWorldViewSubmenu); // just go back to the main menu for now
  353. return "Failed to start game thread";
  354. }
  355. return true;
  356. }
  357. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", furi_string_get_cstr(first_world));
  358. model->fhttp->save_received_data = true;
  359. char url[128];
  360. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/v5/get/world/%s/", furi_string_get_cstr(first_world));
  361. furi_string_free(world_list);
  362. furi_string_free(first_world);
  363. return flipper_http_request(model->fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  364. }
  365. FURI_LOG_E(TAG, "Unknown request index");
  366. return false;
  367. }
  368. static char *game_parse(DataLoaderModel *model)
  369. {
  370. FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
  371. if (model->request_index == 0)
  372. {
  373. if (!model->fhttp->last_response)
  374. {
  375. save_char("is_logged_in", "false");
  376. // Go back to the main menu
  377. easy_flipper_dialog("Error", "Response is empty. Press BACK to return.");
  378. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  379. return "Response is empty...";
  380. }
  381. // Check for successful conditions
  382. if (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User found") != NULL)
  383. {
  384. save_char("is_logged_in", "true");
  385. model->title = "Login successful!";
  386. model->title = "Fetching World List..";
  387. return "Login successful!";
  388. }
  389. // Check if user not found
  390. if (strstr(model->fhttp->last_response, "User not found") != NULL)
  391. {
  392. save_char("is_logged_in", "false");
  393. model->title = "Registering...";
  394. return "Account not found...\nRegistering now.."; // if they see this an issue happened switching to register
  395. }
  396. // If not success, not found, check length conditions
  397. size_t resp_len = strlen(model->fhttp->last_response);
  398. if (resp_len == 0 || resp_len > 127)
  399. {
  400. // Empty or too long means failed login
  401. save_char("is_logged_in", "false");
  402. // Go back to the main menu
  403. easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
  404. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  405. return "Failed to login...";
  406. }
  407. // Handle any other unknown response as a failure
  408. save_char("is_logged_in", "false");
  409. // Go back to the main menu
  410. easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
  411. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  412. return "Failed to login...";
  413. }
  414. else if (model->request_index == 1)
  415. {
  416. if (is_str(model->title, "Registering..."))
  417. {
  418. // check registration response
  419. if (model->fhttp->last_response != NULL && (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User created") != NULL))
  420. {
  421. save_char("is_logged_in", "true");
  422. char username[64];
  423. char password[64];
  424. // load the username and password, then save them
  425. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  426. {
  427. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  428. easy_flipper_dialog("Error", "Failed to load Flip-Social-Username");
  429. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  430. return "Failed to load Flip-Social-Username";
  431. }
  432. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  433. {
  434. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  435. easy_flipper_dialog("Error", "Failed to load Flip-Social-Password");
  436. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  437. return "Failed to load Flip-Social-Password";
  438. }
  439. // load wifi ssid,pass then save
  440. char ssid[64];
  441. char pass[64];
  442. if (!load_char("WiFi-SSID", ssid, sizeof(ssid)))
  443. {
  444. FURI_LOG_E(TAG, "Failed to load WiFi-SSID");
  445. easy_flipper_dialog("Error", "Failed to load WiFi-SSID");
  446. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  447. return "Failed to load WiFi-SSID";
  448. }
  449. if (!load_char("WiFi-Password", pass, sizeof(pass)))
  450. {
  451. FURI_LOG_E(TAG, "Failed to load WiFi-Password");
  452. easy_flipper_dialog("Error", "Failed to load WiFi-Password");
  453. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  454. return "Failed to load WiFi-Password";
  455. }
  456. save_settings(ssid, pass, username, password);
  457. model->title = "Fetching World List..";
  458. return "Account created!";
  459. }
  460. else if (strstr(model->fhttp->last_response, "Username or password not provided") != NULL)
  461. {
  462. easy_flipper_dialog("Error", "Please enter your credentials.\nPress BACK to return.");
  463. view_dispatcher_switch_to_view(app->view_dispatcher,
  464. FlipWorldViewSubmenu); // just go back to the main menu for now
  465. return "Please enter your credentials.";
  466. }
  467. else if (strstr(model->fhttp->last_response, "User already exists") != NULL || strstr(model->fhttp->last_response, "Multiple users found") != NULL)
  468. {
  469. easy_flipper_dialog("Error", "Registration failed...\nUsername already exists.\nPress BACK to return.");
  470. view_dispatcher_switch_to_view(app->view_dispatcher,
  471. FlipWorldViewSubmenu); // just go back to the main menu for now
  472. return "Username already exists.";
  473. }
  474. else
  475. {
  476. easy_flipper_dialog("Error", "Registration failed...\nUpdate your credentials.\nPress BACK to return.");
  477. view_dispatcher_switch_to_view(app->view_dispatcher,
  478. FlipWorldViewSubmenu); // just go back to the main menu for now
  479. return "Registration failed...";
  480. }
  481. }
  482. else
  483. {
  484. if (!game_thread_start(app))
  485. {
  486. FURI_LOG_E(TAG, "Failed to start game thread");
  487. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  488. view_dispatcher_switch_to_view(app->view_dispatcher,
  489. FlipWorldViewSubmenu); // just go back to the main menu for now
  490. return "Failed to start game thread";
  491. }
  492. return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  493. }
  494. }
  495. else if (model->request_index == 2)
  496. {
  497. return "Welcome to FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  498. }
  499. else if (model->request_index == 3)
  500. {
  501. if (!game_thread_start(app))
  502. {
  503. FURI_LOG_E(TAG, "Failed to start game thread");
  504. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  505. view_dispatcher_switch_to_view(app->view_dispatcher,
  506. FlipWorldViewSubmenu); // just go back to the main menu for now
  507. return "Failed to start game thread";
  508. }
  509. return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  510. }
  511. easy_flipper_dialog("Error", "Unknown error. Press BACK to return.");
  512. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
  513. return "Unknown error";
  514. }
  515. static void game_switch_to_view(FlipWorldApp *app)
  516. {
  517. if (!loader_view_alloc(app))
  518. {
  519. FURI_LOG_E(TAG, "Failed to allocate view loader");
  520. return;
  521. }
  522. loader_switch_to_view(app, "Starting Game..", game_fetch, game_parse, 5, callback_to_submenu, FlipWorldViewLoader);
  523. }
  524. bool game_start_ws(FlipperHTTP *fhttp, char *lobby_name)
  525. {
  526. if (!fhttp)
  527. {
  528. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  529. return false;
  530. }
  531. if (!lobby_name || strlen(lobby_name) == 0)
  532. {
  533. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  534. return false;
  535. }
  536. fhttp->state = IDLE; // ensure it's set to IDLE for the next request
  537. char websocket_url[128];
  538. snprintf(websocket_url, sizeof(websocket_url), "ws://www.jblanked.com/ws/game/%s/", lobby_name);
  539. if (!flipper_http_websocket_start(fhttp, websocket_url, 80, "{\"Content-Type\":\"application/json\"}"))
  540. {
  541. FURI_LOG_E(TAG, "Failed to start websocket");
  542. return false;
  543. }
  544. fhttp->state = RECEIVING;
  545. while (fhttp->state != IDLE)
  546. {
  547. furi_delay_ms(100);
  548. }
  549. return true;
  550. }
  551. // load pvp info (this returns the lobbies available)
  552. static bool game_fetch_lobbies(FlipperHTTP *fhttp, uint8_t max_players, uint8_t max_lobbies)
  553. {
  554. if (!fhttp)
  555. {
  556. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  557. return false;
  558. }
  559. char lobby_type[4];
  560. snprintf(lobby_type, sizeof(lobby_type), game_mode_index == 0 ? "pve" : "pvp");
  561. // ensure flip_world directory exists
  562. char directory_path[128];
  563. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  564. Storage *storage = furi_record_open(RECORD_STORAGE);
  565. storage_common_mkdir(storage, directory_path);
  566. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s", lobby_type);
  567. storage_common_mkdir(storage, directory_path);
  568. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s/lobbies", lobby_type);
  569. storage_common_mkdir(storage, directory_path);
  570. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s/%s_lobbies.json", lobby_type, lobby_type);
  571. storage_simply_remove_recursive(storage, fhttp->file_path); // ensure the file is empty
  572. furi_record_close(RECORD_STORAGE);
  573. //
  574. char url[128];
  575. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/%s/lobbies/%d/%d/", lobby_type, max_players, max_lobbies);
  576. fhttp->save_received_data = true;
  577. return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  578. }
  579. static bool game_parse_lobbies(FlipperHTTP *fhttp, uint8_t max_lobbies, void *context)
  580. {
  581. if (!fhttp)
  582. {
  583. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  584. return false;
  585. }
  586. FlipWorldApp *app = (FlipWorldApp *)context;
  587. furi_check(app);
  588. // add the lobbies to the submenu
  589. FuriString *lobbies = flipper_http_load_from_file(fhttp->file_path);
  590. if (!lobbies)
  591. {
  592. FURI_LOG_E(TAG, "Failed to load lobbies");
  593. return false;
  594. }
  595. // parse the lobbies
  596. for (uint32_t i = 0; i < max_lobbies; i++)
  597. {
  598. FuriString *lobby = get_json_array_value_furi("lobbies", i, lobbies);
  599. if (!lobby)
  600. {
  601. FURI_LOG_I(TAG, "No more lobbies");
  602. break;
  603. }
  604. FuriString *lobby_id = get_json_value_furi("id", lobby);
  605. if (!lobby_id)
  606. {
  607. FURI_LOG_E(TAG, "Failed to get lobby id");
  608. furi_string_free(lobby);
  609. return false;
  610. }
  611. // add the lobby to the submenu
  612. submenu_add_item(app->submenu_other, furi_string_get_cstr(lobby_id), FlipWorldSubmenuIndexLobby + i, callback_submenu_lobby_pvp_choices, app);
  613. // add the lobby to the list
  614. if (!easy_flipper_set_buffer(&lobby_list[i], 64))
  615. {
  616. FURI_LOG_E(TAG, "Failed to allocate lobby list");
  617. furi_string_free(lobby);
  618. furi_string_free(lobby_id);
  619. return false;
  620. }
  621. snprintf(lobby_list[i], 64, "%s", furi_string_get_cstr(lobby_id));
  622. furi_string_free(lobby);
  623. furi_string_free(lobby_id);
  624. }
  625. furi_string_free(lobbies);
  626. return true;
  627. }
  628. static bool game_lobbies(FlipperHTTP *fhttp, uint8_t max_players, uint8_t max_lobbies, void *context)
  629. {
  630. if (!fhttp || !context)
  631. {
  632. FURI_LOG_E(TAG, "FlipperHTTP or context is NULL");
  633. return false;
  634. }
  635. if (!game_fetch_lobbies(fhttp, max_players, max_lobbies))
  636. {
  637. FURI_LOG_E(TAG, "Failed to fetch lobbies");
  638. return false;
  639. }
  640. fhttp->state = RECEIVING;
  641. while (fhttp->state != IDLE)
  642. {
  643. // Wait for the request to be received
  644. furi_delay_ms(100);
  645. }
  646. return game_parse_lobbies(fhttp, max_lobbies, context);
  647. }
  648. void game_run(FlipWorldApp *app)
  649. {
  650. furi_check(app, "FlipWorldApp is NULL");
  651. free_all_views(app, true, true, false);
  652. // only need to check if they have 30k free (game needs about 12k currently)
  653. if (!is_enough_heap(30000, false))
  654. {
  655. const size_t min_free = memmgr_get_free_heap();
  656. char message[64];
  657. snprintf(message, sizeof(message), "Not enough heap memory.\nThere are %zu bytes free.", min_free);
  658. easy_flipper_dialog("Error", message);
  659. return;
  660. }
  661. // check if logged in
  662. if (is_logged_in() || is_logged_in_to_flip_social())
  663. {
  664. FlipperHTTP *fhttp = flipper_http_alloc();
  665. if (!fhttp)
  666. {
  667. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  668. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  669. return;
  670. }
  671. bool game_fetch_world_list_i()
  672. {
  673. return game_fetch_world_list(fhttp);
  674. }
  675. bool parse_world_list_i()
  676. {
  677. return fhttp->state != ISSUE;
  678. }
  679. bool game_fetch_player_stats_i()
  680. {
  681. return game_fetch_player_stats(fhttp);
  682. }
  683. if (!alloc_message_view(app, MessageStateLoading))
  684. {
  685. FURI_LOG_E(TAG, "Failed to allocate message view");
  686. return;
  687. }
  688. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  689. // Make the request
  690. if (game_mode_index == 2) // GAME_MODE_STORY
  691. {
  692. if (!flipper_http_process_response_async(fhttp, game_fetch_world_list_i, parse_world_list_i) || !flipper_http_process_response_async(fhttp, game_fetch_player_stats_i, set_player_context))
  693. {
  694. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  695. flipper_http_free(fhttp);
  696. }
  697. flipper_http_free(fhttp);
  698. if (!game_thread_start(app))
  699. {
  700. FURI_LOG_E(TAG, "Failed to start game thread");
  701. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  702. return;
  703. }
  704. }
  705. else
  706. {
  707. free_submenu_other(app);
  708. if (!alloc_submenu_other(app, FlipWorldViewLobby))
  709. {
  710. FURI_LOG_E(TAG, "Failed to allocate lobby submenu");
  711. return;
  712. }
  713. // load pvp lobbies and player stats
  714. if (!game_lobbies(
  715. fhttp,
  716. game_mode_index == 1 ? 2 : MAX_ENEMIES,
  717. 10,
  718. app))
  719. {
  720. // unlike the pve/story, receiving data is necessary
  721. // so send the user back to the main menu if it fails
  722. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  723. easy_flipper_dialog("Error", "Failed to make request. Press BACK to return.");
  724. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  725. flipper_http_free(fhttp);
  726. }
  727. else
  728. {
  729. flipper_http_free(fhttp);
  730. // switch to the lobby submenu
  731. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  732. }
  733. }
  734. }
  735. else
  736. {
  737. game_switch_to_view(app);
  738. }
  739. }
  740. bool game_fetch_lobby(FlipperHTTP *fhttp, const char *lobby_name)
  741. {
  742. if (!fhttp)
  743. {
  744. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  745. return false;
  746. }
  747. if (!lobby_name || strlen(lobby_name) == 0)
  748. {
  749. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  750. return false;
  751. }
  752. char lobby_type[4];
  753. snprintf(lobby_type, sizeof(lobby_type), game_mode_index == 0 ? "pve" : "pvp");
  754. char username[64];
  755. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  756. {
  757. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  758. return false;
  759. }
  760. // send the request to fetch the lobby details, with player_username
  761. char url[128];
  762. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/%s/lobby/get/%s/%s/", lobby_type, lobby_name, username);
  763. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/lobbies/%s.json", lobby_name);
  764. fhttp->save_received_data = true;
  765. if (!flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL))
  766. {
  767. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  768. return false;
  769. }
  770. fhttp->state = RECEIVING;
  771. while (fhttp->state != IDLE)
  772. {
  773. furi_delay_ms(100);
  774. }
  775. return true;
  776. }
  777. bool game_join_lobby(FlipperHTTP *fhttp, const char *lobby_name)
  778. {
  779. if (!fhttp)
  780. {
  781. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  782. return false;
  783. }
  784. if (!lobby_name || strlen(lobby_name) == 0)
  785. {
  786. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  787. return false;
  788. }
  789. char lobby_type[4];
  790. snprintf(lobby_type, sizeof(lobby_type), game_mode_index == 0 ? "pve" : "pvp");
  791. char username[64];
  792. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  793. {
  794. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  795. return false;
  796. }
  797. char url[128];
  798. char payload[128];
  799. snprintf(payload, sizeof(payload), "{\"username\":\"%s\", \"game_id\":\"%s\"}", username, lobby_name);
  800. save_char("pvp_lobby_name", lobby_name); // save the lobby name
  801. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/%s/lobby/join/", lobby_type);
  802. if (!flipper_http_request(fhttp, POST, url, "{\"Content-Type\":\"application/json\"}", payload))
  803. {
  804. FURI_LOG_E(TAG, "Failed to join lobby");
  805. return false;
  806. }
  807. fhttp->state = RECEIVING;
  808. while (fhttp->state != IDLE)
  809. {
  810. furi_delay_ms(100);
  811. }
  812. return true;
  813. }
  814. static bool game_create_pvp_enemy(FuriString *lobby_details)
  815. {
  816. if (!lobby_details)
  817. {
  818. FURI_LOG_E(TAG, "Failed to load lobby details");
  819. return false;
  820. }
  821. char current_user[64];
  822. if (!load_char("Flip-Social-Username", current_user, sizeof(current_user)))
  823. {
  824. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  825. save_char("create_pvp_error", "Failed to load Flip-Social-Username");
  826. return false;
  827. }
  828. for (uint8_t i = 0; i < 2; i++)
  829. {
  830. // parse the lobby details
  831. FuriString *player_stats = get_json_array_value_furi("player_stats", i, lobby_details);
  832. if (!player_stats)
  833. {
  834. FURI_LOG_E(TAG, "Failed to get player stats");
  835. save_char("create_pvp_error", "Failed to get player stats array");
  836. return false;
  837. }
  838. // available keys from player_stats
  839. FuriString *username = get_json_value_furi("username", player_stats);
  840. if (!username)
  841. {
  842. FURI_LOG_E(TAG, "Failed to get username");
  843. save_char("create_pvp_error", "Failed to get username");
  844. furi_string_free(player_stats);
  845. return false;
  846. }
  847. // check if the username is the same as the current user
  848. if (is_str(furi_string_get_cstr(username), current_user))
  849. {
  850. furi_string_free(player_stats);
  851. furi_string_free(username);
  852. continue; // skip the current user
  853. }
  854. FuriString *strength = get_json_value_furi("strength", player_stats);
  855. FuriString *health = get_json_value_furi("health", player_stats);
  856. FuriString *attack_timer = get_json_value_furi("attack_timer", player_stats);
  857. if (!strength || !health || !attack_timer)
  858. {
  859. FURI_LOG_E(TAG, "Failed to get player stats");
  860. save_char("create_pvp_error", "Failed to get player stats");
  861. furi_string_free(player_stats);
  862. furi_string_free(username);
  863. if (strength)
  864. furi_string_free(strength);
  865. if (health)
  866. furi_string_free(health);
  867. if (attack_timer)
  868. furi_string_free(attack_timer);
  869. return false;
  870. }
  871. // create enemy data
  872. FuriString *enemy_data = furi_string_alloc();
  873. furi_string_printf(
  874. enemy_data,
  875. "{\"enemy_data\":[{\"id\":\"sword\",\"is_user\":\"true\",\"username\":\"%s\","
  876. "\"index\":0,\"start_position\":{\"x\":350,\"y\":210},\"end_position\":{\"x\":350,\"y\":210},"
  877. "\"move_timer\":1,\"speed\":1,\"attack_timer\":%f,\"strength\":%f,\"health\":%f}]}",
  878. furi_string_get_cstr(username),
  879. (double)atof_furi(attack_timer),
  880. (double)atof_furi(strength),
  881. (double)atof_furi(health));
  882. char directory_path[128];
  883. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  884. Storage *storage = furi_record_open(RECORD_STORAGE);
  885. storage_common_mkdir(storage, directory_path);
  886. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  887. storage_common_mkdir(storage, directory_path);
  888. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/pvp_world");
  889. storage_common_mkdir(storage, directory_path);
  890. furi_record_close(RECORD_STORAGE);
  891. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/pvp_world/pvp_world_enemy_data.json");
  892. // remove the enemy_data file if it exists
  893. storage_simply_remove_recursive(storage, directory_path);
  894. File *file = storage_file_alloc(storage);
  895. if (!storage_file_open(file, directory_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  896. {
  897. FURI_LOG_E("Game", "Failed to open file for writing: %s", directory_path);
  898. save_char("create_pvp_error", "Failed to open file for writing");
  899. storage_file_free(file);
  900. furi_record_close(RECORD_STORAGE);
  901. furi_string_free(enemy_data);
  902. furi_string_free(player_stats);
  903. furi_string_free(username);
  904. furi_string_free(strength);
  905. furi_string_free(health);
  906. furi_string_free(attack_timer);
  907. return false;
  908. }
  909. size_t data_size = furi_string_size(enemy_data);
  910. if (storage_file_write(file, furi_string_get_cstr(enemy_data), data_size) != data_size)
  911. {
  912. FURI_LOG_E("Game", "Failed to write enemy_data");
  913. save_char("create_pvp_error", "Failed to write enemy_data");
  914. }
  915. storage_file_close(file);
  916. furi_string_free(enemy_data);
  917. furi_string_free(player_stats);
  918. furi_string_free(username);
  919. furi_string_free(strength);
  920. furi_string_free(health);
  921. furi_string_free(attack_timer);
  922. // player is found so break
  923. break;
  924. }
  925. return true;
  926. }
  927. size_t game_lobby_count(FlipperHTTP *fhttp, FuriString *lobby)
  928. {
  929. if (!fhttp)
  930. {
  931. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  932. return -1;
  933. }
  934. if (!lobby)
  935. {
  936. FURI_LOG_E(TAG, "Lobby details are NULL");
  937. return -1;
  938. }
  939. // check if the player is in the lobby
  940. FuriString *player_count = get_json_value_furi("player_count", lobby);
  941. if (!player_count)
  942. {
  943. FURI_LOG_E(TAG, "Failed to get player count");
  944. return -1;
  945. }
  946. const size_t count = atoi(furi_string_get_cstr(player_count));
  947. furi_string_free(player_count);
  948. return count;
  949. }
  950. bool game_in_lobby(FlipperHTTP *fhttp, FuriString *lobby)
  951. {
  952. if (!fhttp)
  953. {
  954. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  955. return false;
  956. }
  957. if (!lobby)
  958. {
  959. FURI_LOG_E(TAG, "Lobby details are NULL");
  960. return false;
  961. }
  962. // check if the player is in the lobby
  963. FuriString *is_in_game = get_json_value_furi("is_in_game", lobby);
  964. if (!is_in_game)
  965. {
  966. FURI_LOG_E(TAG, "Failed to get is_in_game");
  967. furi_string_free(is_in_game);
  968. return false;
  969. }
  970. const bool in_game = is_str(furi_string_get_cstr(is_in_game), "true");
  971. furi_string_free(is_in_game);
  972. return in_game;
  973. }
  974. // this will free both the fhttp and lobby
  975. void game_start(FlipperHTTP *fhttp, FuriString *lobby, void *context)
  976. {
  977. FlipWorldApp *app = (FlipWorldApp *)context;
  978. furi_check(app, "FlipWorldApp is NULL");
  979. if (game_mode_index == 1) // pvp (in pve, we will apennd the enemies as they are read from the websocket)
  980. {
  981. // only thing left to do is create the enemy data and start the websocket session
  982. if (!game_create_pvp_enemy(lobby))
  983. {
  984. FURI_LOG_E(TAG, "Failed to create pvp enemy context.");
  985. easy_flipper_dialog("Error", "Failed to create pvp enemy context. Press BACK to return.");
  986. flipper_http_free(fhttp);
  987. furi_string_free(lobby);
  988. return;
  989. }
  990. }
  991. furi_string_free(lobby);
  992. // start the websocket session
  993. if (!game_start_ws(fhttp, lobby_list[lobby_index]))
  994. {
  995. FURI_LOG_E(TAG, "Failed to start websocket session");
  996. easy_flipper_dialog("Error", "Failed to start websocket session. Press BACK to return.");
  997. flipper_http_free(fhttp);
  998. return;
  999. }
  1000. flipper_http_free(fhttp);
  1001. // start the game thread
  1002. if (!game_thread_start(app))
  1003. {
  1004. FURI_LOG_E(TAG, "Failed to start game thread");
  1005. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1006. return;
  1007. }
  1008. };
  1009. void game_waiting_process(FlipperHTTP *fhttp, void *context)
  1010. {
  1011. FlipWorldApp *app = (FlipWorldApp *)context;
  1012. furi_check(app, "FlipWorldApp is NULL");
  1013. if (!fhttp)
  1014. {
  1015. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1016. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1017. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1018. return;
  1019. }
  1020. // fetch the lobby details
  1021. if (!game_fetch_lobby(fhttp, lobby_list[lobby_index]))
  1022. {
  1023. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1024. flipper_http_free(fhttp);
  1025. easy_flipper_dialog("Error", "Failed to fetch lobby details. Press BACK to return.");
  1026. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1027. return;
  1028. }
  1029. // load the lobby details
  1030. FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
  1031. if (!lobby)
  1032. {
  1033. FURI_LOG_E(TAG, "Failed to load lobby details");
  1034. flipper_http_free(fhttp);
  1035. easy_flipper_dialog("Error", "Failed to load lobby details. Press BACK to return.");
  1036. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1037. return;
  1038. }
  1039. // get the player count
  1040. const size_t count = game_lobby_count(fhttp, lobby);
  1041. if (count == 2)
  1042. {
  1043. // break out of this and start the game
  1044. game_start(fhttp, lobby, app); // this will free both the fhttp and lobby
  1045. return;
  1046. }
  1047. furi_string_free(lobby);
  1048. }
  1049. void game_waiting_lobby(void *context)
  1050. {
  1051. FlipWorldApp *app = (FlipWorldApp *)context;
  1052. furi_check(app, "waiting_lobby: FlipWorldApp is NULL");
  1053. if (!game_start_waiting_thread(app))
  1054. {
  1055. FURI_LOG_E(TAG, "Failed to start waiting thread");
  1056. easy_flipper_dialog("Error", "Failed to start waiting thread. Press BACK to return.");
  1057. return;
  1058. }
  1059. free_message_view(app);
  1060. if (!alloc_message_view(app, MessageStateWaitingLobby))
  1061. {
  1062. FURI_LOG_E(TAG, "Failed to allocate message view");
  1063. return;
  1064. }
  1065. // finally, switch to the waiting lobby view
  1066. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1067. };