game.c 44 KB

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