game.c 48 KB

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