callback.c 86 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185
  1. #include <callback/callback.h>
  2. #include <callback/loader.h>
  3. #include <callback/free.h>
  4. #include "engine/engine.h"
  5. #include "engine/game_engine.h"
  6. #include "engine/game_manager_i.h"
  7. #include "engine/level_i.h"
  8. #include "engine/entity_i.h"
  9. #include "game/storage.h"
  10. #include "alloc/alloc.h"
  11. #include <flip_storage/storage.h>
  12. static void frame_cb(GameEngine *engine, Canvas *canvas, InputState input, void *context)
  13. {
  14. UNUSED(engine);
  15. GameManager *game_manager = context;
  16. game_manager_input_set(game_manager, input);
  17. game_manager_update(game_manager);
  18. game_manager_render(game_manager, canvas);
  19. }
  20. static int32_t game_app(void *p)
  21. {
  22. UNUSED(p);
  23. GameManager *game_manager = game_manager_alloc();
  24. if (!game_manager)
  25. {
  26. FURI_LOG_E("Game", "Failed to allocate game manager");
  27. return -1;
  28. }
  29. // Setup game engine settings...
  30. GameEngineSettings settings = game_engine_settings_init();
  31. settings.target_fps = atof_(fps_choices_str[fps_index]);
  32. settings.show_fps = game.show_fps;
  33. settings.always_backlight = strstr(yes_or_no_choices[screen_always_on_index], "Yes") != NULL;
  34. settings.frame_callback = frame_cb;
  35. settings.context = game_manager;
  36. GameEngine *engine = game_engine_alloc(settings);
  37. if (!engine)
  38. {
  39. FURI_LOG_E("Game", "Failed to allocate game engine");
  40. game_manager_free(game_manager);
  41. return -1;
  42. }
  43. game_manager_engine_set(game_manager, engine);
  44. // Allocate custom game context if needed
  45. void *game_context = NULL;
  46. if (game.context_size > 0)
  47. {
  48. game_context = malloc(game.context_size);
  49. game_manager_game_context_set(game_manager, game_context);
  50. }
  51. // Start the game
  52. game.start(game_manager, game_context);
  53. // 1) Run the engine
  54. game_engine_run(engine);
  55. // 2) Stop the game FIRST, so it can do any internal cleanup
  56. game.stop(game_context);
  57. // 3) Now free the engine
  58. game_engine_free(engine);
  59. // 4) Now free the manager
  60. game_manager_free(game_manager);
  61. // 5) Finally, free your custom context if it was allocated
  62. if (game_context)
  63. {
  64. free(game_context);
  65. }
  66. // 6) Check for leftover entities
  67. int32_t entities = entities_get_count();
  68. if (entities != 0)
  69. {
  70. FURI_LOG_E("Game", "Memory leak detected: %ld entities still allocated", entities);
  71. return -1;
  72. }
  73. return 0;
  74. }
  75. static bool alloc_message_view(void *context, MessageState state);
  76. static bool alloc_text_input_view(void *context, char *title);
  77. static bool alloc_variable_item_list(void *context, uint32_t view_id);
  78. //
  79. static void callback_submenu_lobby_choices(void *context, uint32_t index);
  80. //
  81. static void wifi_settings_select(void *context, uint32_t index);
  82. static void updated_wifi_ssid(void *context);
  83. static void updated_wifi_pass(void *context);
  84. static void updated_username(void *context);
  85. static void updated_password(void *context);
  86. //
  87. static void fps_change(VariableItem *item);
  88. static void game_settings_select(void *context, uint32_t index);
  89. static void user_settings_select(void *context, uint32_t index);
  90. static void screen_on_change(VariableItem *item);
  91. static void sound_on_change(VariableItem *item);
  92. static void vibration_on_change(VariableItem *item);
  93. static void player_on_change(VariableItem *item);
  94. static void vgm_x_change(VariableItem *item);
  95. static void vgm_y_change(VariableItem *item);
  96. //
  97. static uint8_t timer_iteration = 0; // timer iteration for the loading screen
  98. static uint8_t timer_refresh = 5; // duration for timer to refresh
  99. //
  100. void waiting_loader_process_callback(FlipperHTTP *fhttp, void *context);
  101. static void waiting_lobby(void *context);
  102. static uint32_t lobby_index = -1;
  103. static char *lobby_list[10];
  104. static bool fetch_lobby(FlipperHTTP *fhttp, char *lobby_name);
  105. bool user_hit_back = false;
  106. static bool message_input_callback(InputEvent *event, void *context)
  107. {
  108. FlipWorldApp *app = (FlipWorldApp *)context;
  109. furi_check(app);
  110. if (event->key == InputKeyBack)
  111. {
  112. FURI_LOG_I(TAG, "Message view - BACK pressed");
  113. user_hit_back = true;
  114. }
  115. return true;
  116. }
  117. static int32_t waiting_app_callback(void *p)
  118. {
  119. FlipWorldApp *app = (FlipWorldApp *)p;
  120. furi_check(app);
  121. FlipperHTTP *fhttp = flipper_http_alloc();
  122. if (!fhttp)
  123. {
  124. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  125. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP");
  126. return -1;
  127. }
  128. user_hit_back = false;
  129. timer_iteration = 0;
  130. while (timer_iteration < 60 && !user_hit_back)
  131. {
  132. FURI_LOG_I(TAG, "Waiting for more players...");
  133. waiting_loader_process_callback(fhttp, app);
  134. FURI_LOG_I(TAG, "Waiting for more players... %d", timer_iteration);
  135. timer_iteration++;
  136. furi_delay_ms(1000 * timer_refresh);
  137. }
  138. // if we reach here, it means we timed out or the user hit back
  139. FURI_LOG_E(TAG, "No players joined within the timeout or user hit back");
  140. remove_player_from_lobby(fhttp);
  141. flipper_http_free(fhttp);
  142. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  143. return 0;
  144. }
  145. static void message_draw_callback(Canvas *canvas, void *model)
  146. {
  147. MessageModel *message_model = model;
  148. canvas_clear(canvas);
  149. if (message_model->message_state == MessageStateAbout)
  150. {
  151. canvas_draw_str(canvas, 0, 10, VERSION_TAG);
  152. canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
  153. canvas_draw_str(canvas, 0, 20, "Dev: JBlanked, codeallnight");
  154. canvas_draw_str(canvas, 0, 30, "GFX: the1anonlypr3");
  155. canvas_draw_str(canvas, 0, 40, "github.com/jblanked/FlipWorld");
  156. canvas_draw_str_multi(canvas, 0, 55, "The first open world multiplayer\ngame on the Flipper Zero.");
  157. }
  158. else if (message_model->message_state == MessageStateLoading)
  159. {
  160. canvas_set_font(canvas, FontPrimary);
  161. if (game_mode_index != 1)
  162. {
  163. canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Starting FlipWorld");
  164. canvas_set_font(canvas, FontSecondary);
  165. canvas_draw_str(canvas, 0, 50, "Please wait while your");
  166. canvas_draw_str(canvas, 0, 60, "game is started.");
  167. }
  168. else
  169. {
  170. canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Loading Lobbies");
  171. canvas_set_font(canvas, FontSecondary);
  172. canvas_draw_str(canvas, 0, 60, "Please wait....");
  173. }
  174. }
  175. // well this is only called once so let's make a while loop
  176. else if (message_model->message_state == MessageStateWaitingLobby)
  177. {
  178. canvas_draw_str(canvas, 0, 10, "Waiting for more players...");
  179. // time elapsed based on timer_iteration and timer_refresh
  180. // char str[32];
  181. // snprintf(str, sizeof(str), "Time elapsed: %d seconds", timer_iteration * timer_refresh);
  182. // canvas_draw_str(canvas, 0, 50, str);
  183. canvas_draw_str(canvas, 0, 60, "Press BACK to cancel.");
  184. canvas_commit(canvas); // make sure message is drawn
  185. }
  186. else
  187. {
  188. canvas_draw_str(canvas, 0, 10, "Unknown message state");
  189. }
  190. }
  191. // alloc
  192. static bool alloc_message_view(void *context, MessageState state)
  193. {
  194. FlipWorldApp *app = (FlipWorldApp *)context;
  195. furi_check(app);
  196. if (app->view_message)
  197. {
  198. FURI_LOG_E(TAG, "Message view already allocated");
  199. return false;
  200. }
  201. switch (state)
  202. {
  203. case MessageStateAbout:
  204. easy_flipper_set_view(&app->view_message, FlipWorldViewMessage, message_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app);
  205. break;
  206. case MessageStateLoading:
  207. easy_flipper_set_view(&app->view_message, FlipWorldViewMessage, message_draw_callback, NULL, NULL, &app->view_dispatcher, app);
  208. break;
  209. case MessageStateWaitingLobby:
  210. easy_flipper_set_view(&app->view_message, FlipWorldViewMessage, message_draw_callback, message_input_callback, NULL, &app->view_dispatcher, app);
  211. break;
  212. }
  213. if (!app->view_message)
  214. {
  215. FURI_LOG_E(TAG, "Failed to allocate message view");
  216. return false;
  217. }
  218. view_allocate_model(app->view_message, ViewModelTypeLockFree, sizeof(MessageModel));
  219. MessageModel *model = view_get_model(app->view_message);
  220. model->message_state = state;
  221. return true;
  222. }
  223. static bool alloc_text_input_view(void *context, char *title)
  224. {
  225. FlipWorldApp *app = (FlipWorldApp *)context;
  226. furi_check(app);
  227. if (!title)
  228. {
  229. FURI_LOG_E(TAG, "Title is NULL");
  230. return false;
  231. }
  232. app->text_input_buffer_size = 64;
  233. if (!app->text_input_buffer)
  234. {
  235. if (!easy_flipper_set_buffer(&app->text_input_buffer, app->text_input_buffer_size))
  236. {
  237. return false;
  238. }
  239. }
  240. if (!app->text_input_temp_buffer)
  241. {
  242. if (!easy_flipper_set_buffer(&app->text_input_temp_buffer, app->text_input_buffer_size))
  243. {
  244. return false;
  245. }
  246. }
  247. if (!app->text_input)
  248. {
  249. if (!easy_flipper_set_uart_text_input(
  250. &app->text_input,
  251. FlipWorldViewTextInput,
  252. title,
  253. app->text_input_temp_buffer,
  254. app->text_input_buffer_size,
  255. is_str(title, "SSID") ? updated_wifi_ssid : is_str(title, "Password") ? updated_wifi_pass
  256. : is_str(title, "Username-Login") ? updated_username
  257. : updated_password,
  258. callback_to_wifi_settings,
  259. &app->view_dispatcher,
  260. app))
  261. {
  262. return false;
  263. }
  264. if (!app->text_input)
  265. {
  266. return false;
  267. }
  268. char ssid[64];
  269. char pass[64];
  270. char username[64];
  271. char password[64];
  272. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  273. {
  274. if (is_str(title, "SSID"))
  275. {
  276. strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size);
  277. }
  278. else if (is_str(title, "Password"))
  279. {
  280. strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size);
  281. }
  282. else if (is_str(title, "Username-Login"))
  283. {
  284. strncpy(app->text_input_temp_buffer, username, app->text_input_buffer_size);
  285. }
  286. else if (is_str(title, "Password-Login"))
  287. {
  288. strncpy(app->text_input_temp_buffer, password, app->text_input_buffer_size);
  289. }
  290. }
  291. }
  292. return true;
  293. }
  294. static bool alloc_variable_item_list(void *context, uint32_t view_id)
  295. {
  296. FlipWorldApp *app = (FlipWorldApp *)context;
  297. if (!app)
  298. {
  299. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  300. return false;
  301. }
  302. char ssid[64];
  303. char pass[64];
  304. char username[64];
  305. char password[64];
  306. if (!app->variable_item_list)
  307. {
  308. switch (view_id)
  309. {
  310. case FlipWorldSubmenuIndexWiFiSettings:
  311. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, wifi_settings_select, callback_to_settings, &app->view_dispatcher, app))
  312. {
  313. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  314. return false;
  315. }
  316. if (!app->variable_item_list)
  317. {
  318. FURI_LOG_E(TAG, "Variable item list is NULL");
  319. return false;
  320. }
  321. if (!app->variable_item_wifi_ssid)
  322. {
  323. app->variable_item_wifi_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
  324. variable_item_set_current_value_text(app->variable_item_wifi_ssid, "");
  325. }
  326. if (!app->variable_item_wifi_pass)
  327. {
  328. app->variable_item_wifi_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  329. variable_item_set_current_value_text(app->variable_item_wifi_pass, "");
  330. }
  331. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  332. {
  333. variable_item_set_current_value_text(app->variable_item_wifi_ssid, ssid);
  334. // variable_item_set_current_value_text(app->variable_item_wifi_pass, pass);
  335. save_char("WiFi-SSID", ssid);
  336. save_char("WiFi-Password", pass);
  337. save_char("Flip-Social-Username", username);
  338. save_char("Flip-Social-Password", password);
  339. }
  340. break;
  341. case FlipWorldSubmenuIndexGameSettings:
  342. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, game_settings_select, callback_to_settings, &app->view_dispatcher, app))
  343. {
  344. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  345. return false;
  346. }
  347. if (!app->variable_item_list)
  348. {
  349. FURI_LOG_E(TAG, "Variable item list is NULL");
  350. return false;
  351. }
  352. if (!app->variable_item_game_download_world)
  353. {
  354. app->variable_item_game_download_world = variable_item_list_add(app->variable_item_list, "Install Official World Pack", 0, NULL, NULL);
  355. variable_item_set_current_value_text(app->variable_item_game_download_world, "");
  356. }
  357. if (!app->variable_item_game_player_sprite)
  358. {
  359. app->variable_item_game_player_sprite = variable_item_list_add(app->variable_item_list, "Weapon", 4, player_on_change, NULL);
  360. variable_item_set_current_value_index(app->variable_item_game_player_sprite, 1);
  361. variable_item_set_current_value_text(app->variable_item_game_player_sprite, player_sprite_choices[1]);
  362. }
  363. if (!app->variable_item_game_fps)
  364. {
  365. app->variable_item_game_fps = variable_item_list_add(app->variable_item_list, "FPS", 4, fps_change, NULL);
  366. variable_item_set_current_value_index(app->variable_item_game_fps, 0);
  367. variable_item_set_current_value_text(app->variable_item_game_fps, fps_choices_str[0]);
  368. }
  369. if (!app->variable_item_game_vgm_x)
  370. {
  371. app->variable_item_game_vgm_x = variable_item_list_add(app->variable_item_list, "VGM Horizontal", 12, vgm_x_change, NULL);
  372. variable_item_set_current_value_index(app->variable_item_game_vgm_x, 2);
  373. variable_item_set_current_value_text(app->variable_item_game_vgm_x, vgm_levels[2]);
  374. }
  375. if (!app->variable_item_game_vgm_y)
  376. {
  377. app->variable_item_game_vgm_y = variable_item_list_add(app->variable_item_list, "VGM Vertical", 12, vgm_y_change, NULL);
  378. variable_item_set_current_value_index(app->variable_item_game_vgm_y, 2);
  379. variable_item_set_current_value_text(app->variable_item_game_vgm_y, vgm_levels[2]);
  380. }
  381. if (!app->variable_item_game_screen_always_on)
  382. {
  383. app->variable_item_game_screen_always_on = variable_item_list_add(app->variable_item_list, "Keep Screen On?", 2, screen_on_change, NULL);
  384. variable_item_set_current_value_index(app->variable_item_game_screen_always_on, 1);
  385. variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[1]);
  386. }
  387. if (!app->variable_item_game_sound_on)
  388. {
  389. app->variable_item_game_sound_on = variable_item_list_add(app->variable_item_list, "Sound On?", 2, sound_on_change, NULL);
  390. variable_item_set_current_value_index(app->variable_item_game_sound_on, 0);
  391. variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[0]);
  392. }
  393. if (!app->variable_item_game_vibration_on)
  394. {
  395. app->variable_item_game_vibration_on = variable_item_list_add(app->variable_item_list, "Vibration On?", 2, vibration_on_change, NULL);
  396. variable_item_set_current_value_index(app->variable_item_game_vibration_on, 0);
  397. variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[0]);
  398. }
  399. char _game_player_sprite[8];
  400. if (load_char("Game-Player-Sprite", _game_player_sprite, sizeof(_game_player_sprite)))
  401. {
  402. int index = is_str(_game_player_sprite, "naked") ? 0 : is_str(_game_player_sprite, "sword") ? 1
  403. : is_str(_game_player_sprite, "axe") ? 2
  404. : is_str(_game_player_sprite, "bow") ? 3
  405. : 0;
  406. variable_item_set_current_value_index(app->variable_item_game_player_sprite, index);
  407. variable_item_set_current_value_text(
  408. app->variable_item_game_player_sprite,
  409. is_str(player_sprite_choices[index], "naked") ? "None" : player_sprite_choices[index]);
  410. }
  411. char _game_fps[8];
  412. if (load_char("Game-FPS", _game_fps, sizeof(_game_fps)))
  413. {
  414. int index = is_str(_game_fps, "30") ? 0 : is_str(_game_fps, "60") ? 1
  415. : is_str(_game_fps, "120") ? 2
  416. : is_str(_game_fps, "240") ? 3
  417. : 0;
  418. variable_item_set_current_value_text(app->variable_item_game_fps, fps_choices_str[index]);
  419. variable_item_set_current_value_index(app->variable_item_game_fps, index);
  420. }
  421. char _game_vgm_x[8];
  422. if (load_char("Game-VGM-X", _game_vgm_x, sizeof(_game_vgm_x)))
  423. {
  424. int vgm_x = atoi(_game_vgm_x);
  425. int index = vgm_x == -2 ? 0 : vgm_x == -1 ? 1
  426. : vgm_x == 0 ? 2
  427. : vgm_x == 1 ? 3
  428. : vgm_x == 2 ? 4
  429. : vgm_x == 3 ? 5
  430. : vgm_x == 4 ? 6
  431. : vgm_x == 5 ? 7
  432. : vgm_x == 6 ? 8
  433. : vgm_x == 7 ? 9
  434. : vgm_x == 8 ? 10
  435. : vgm_x == 9 ? 11
  436. : vgm_x == 10 ? 12
  437. : 2;
  438. variable_item_set_current_value_index(app->variable_item_game_vgm_x, index);
  439. variable_item_set_current_value_text(app->variable_item_game_vgm_x, vgm_levels[index]);
  440. }
  441. char _game_vgm_y[8];
  442. if (load_char("Game-VGM-Y", _game_vgm_y, sizeof(_game_vgm_y)))
  443. {
  444. int vgm_y = atoi(_game_vgm_y);
  445. int index = vgm_y == -2 ? 0 : vgm_y == -1 ? 1
  446. : vgm_y == 0 ? 2
  447. : vgm_y == 1 ? 3
  448. : vgm_y == 2 ? 4
  449. : vgm_y == 3 ? 5
  450. : vgm_y == 4 ? 6
  451. : vgm_y == 5 ? 7
  452. : vgm_y == 6 ? 8
  453. : vgm_y == 7 ? 9
  454. : vgm_y == 8 ? 10
  455. : vgm_y == 9 ? 11
  456. : vgm_y == 10 ? 12
  457. : 2;
  458. variable_item_set_current_value_index(app->variable_item_game_vgm_y, index);
  459. variable_item_set_current_value_text(app->variable_item_game_vgm_y, vgm_levels[index]);
  460. }
  461. char _game_screen_always_on[8];
  462. if (load_char("Game-Screen-Always-On", _game_screen_always_on, sizeof(_game_screen_always_on)))
  463. {
  464. int index = is_str(_game_screen_always_on, "No") ? 0 : is_str(_game_screen_always_on, "Yes") ? 1
  465. : 0;
  466. variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[index]);
  467. variable_item_set_current_value_index(app->variable_item_game_screen_always_on, index);
  468. }
  469. char _game_sound_on[8];
  470. if (load_char("Game-Sound-On", _game_sound_on, sizeof(_game_sound_on)))
  471. {
  472. int index = is_str(_game_sound_on, "No") ? 0 : is_str(_game_sound_on, "Yes") ? 1
  473. : 0;
  474. variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[index]);
  475. variable_item_set_current_value_index(app->variable_item_game_sound_on, index);
  476. }
  477. char _game_vibration_on[8];
  478. if (load_char("Game-Vibration-On", _game_vibration_on, sizeof(_game_vibration_on)))
  479. {
  480. int index = is_str(_game_vibration_on, "No") ? 0 : is_str(_game_vibration_on, "Yes") ? 1
  481. : 0;
  482. variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[index]);
  483. variable_item_set_current_value_index(app->variable_item_game_vibration_on, index);
  484. }
  485. break;
  486. case FlipWorldSubmenuIndexUserSettings:
  487. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, user_settings_select, callback_to_settings, &app->view_dispatcher, app))
  488. {
  489. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  490. return false;
  491. }
  492. if (!app->variable_item_list)
  493. {
  494. FURI_LOG_E(TAG, "Variable item list is NULL");
  495. return false;
  496. }
  497. // if logged in, show profile info, otherwise show login/register
  498. if (is_logged_in() || is_logged_in_to_flip_social())
  499. {
  500. if (!app->variable_item_user_username)
  501. {
  502. app->variable_item_user_username = variable_item_list_add(app->variable_item_list, "Username", 0, NULL, NULL);
  503. variable_item_set_current_value_text(app->variable_item_user_username, "");
  504. }
  505. if (!app->variable_item_user_password)
  506. {
  507. app->variable_item_user_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  508. variable_item_set_current_value_text(app->variable_item_user_password, "");
  509. }
  510. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  511. {
  512. variable_item_set_current_value_text(app->variable_item_user_username, username);
  513. variable_item_set_current_value_text(app->variable_item_user_password, "*****");
  514. }
  515. }
  516. else
  517. {
  518. if (!app->variable_item_user_username)
  519. {
  520. app->variable_item_user_username = variable_item_list_add(app->variable_item_list, "Username", 0, NULL, NULL);
  521. variable_item_set_current_value_text(app->variable_item_user_username, "");
  522. }
  523. if (!app->variable_item_user_password)
  524. {
  525. app->variable_item_user_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  526. variable_item_set_current_value_text(app->variable_item_user_password, "");
  527. }
  528. }
  529. break;
  530. }
  531. }
  532. return true;
  533. }
  534. static bool alloc_submenu_other(void *context, uint32_t view_id)
  535. {
  536. FlipWorldApp *app = (FlipWorldApp *)context;
  537. furi_check(app);
  538. if (app->submenu_other)
  539. {
  540. FURI_LOG_I(TAG, "Submenu already allocated");
  541. return true;
  542. }
  543. switch (view_id)
  544. {
  545. case FlipWorldViewSettings:
  546. if (!easy_flipper_set_submenu(&app->submenu_other, FlipWorldViewSubmenuOther, "Settings", callback_to_submenu, &app->view_dispatcher))
  547. {
  548. FURI_LOG_E(TAG, "Failed to allocate submenu settings");
  549. return false;
  550. }
  551. submenu_add_item(app->submenu_other, "WiFi", FlipWorldSubmenuIndexWiFiSettings, callback_submenu_choices, app);
  552. submenu_add_item(app->submenu_other, "Game", FlipWorldSubmenuIndexGameSettings, callback_submenu_choices, app);
  553. submenu_add_item(app->submenu_other, "User", FlipWorldSubmenuIndexUserSettings, callback_submenu_choices, app);
  554. return true;
  555. case FlipWorldViewLobby:
  556. return easy_flipper_set_submenu(&app->submenu_other, FlipWorldViewSubmenuOther, "Lobbies", callback_to_submenu, &app->view_dispatcher);
  557. default:
  558. return false;
  559. }
  560. }
  561. static bool alloc_game_submenu(void *context)
  562. {
  563. FlipWorldApp *app = (FlipWorldApp *)context;
  564. furi_check(app);
  565. if (!app->submenu_game)
  566. {
  567. if (!easy_flipper_set_submenu(&app->submenu_game, FlipWorldViewGameSubmenu, "Play", callback_to_submenu, &app->view_dispatcher))
  568. {
  569. return false;
  570. }
  571. if (!app->submenu_game)
  572. {
  573. return false;
  574. }
  575. submenu_add_item(app->submenu_game, "Tutorial", FlipWorldSubmenuIndexStory, callback_submenu_choices, app);
  576. submenu_add_item(app->submenu_game, "PvP (Beta)", FlipWorldSubmenuIndexPvP, callback_submenu_choices, app);
  577. submenu_add_item(app->submenu_game, "PvE", FlipWorldSubmenuIndexPvE, callback_submenu_choices, app);
  578. }
  579. return true;
  580. }
  581. static FuriThread *game_thread;
  582. static FuriThread *waiting_thread;
  583. static bool game_thread_running = false;
  584. static bool waiting_thread_running = false;
  585. static bool start_waiting_thread(void *context)
  586. {
  587. FlipWorldApp *app = (FlipWorldApp *)context;
  588. furi_check(app);
  589. // free game thread
  590. if (waiting_thread_running)
  591. {
  592. waiting_thread_running = false;
  593. if (waiting_thread)
  594. {
  595. furi_thread_flags_set(furi_thread_get_id(waiting_thread), WorkerEvtStop);
  596. furi_thread_join(waiting_thread);
  597. furi_thread_free(waiting_thread);
  598. }
  599. }
  600. // start waiting thread
  601. FuriThread *thread = furi_thread_alloc_ex("waiting_thread", 2048, waiting_app_callback, app);
  602. if (!thread)
  603. {
  604. FURI_LOG_E(TAG, "Failed to allocate waiting thread");
  605. easy_flipper_dialog("Error", "Failed to allocate waiting thread. Restart your Flipper.");
  606. return false;
  607. }
  608. furi_thread_start(thread);
  609. waiting_thread = thread;
  610. waiting_thread_running = true;
  611. return true;
  612. }
  613. static bool fetch_world_list(FlipperHTTP *fhttp)
  614. {
  615. if (!fhttp)
  616. {
  617. FURI_LOG_E(TAG, "fhttp is NULL");
  618. easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  619. return false;
  620. }
  621. // ensure flip_world directory exists
  622. char directory_path[128];
  623. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  624. Storage *storage = furi_record_open(RECORD_STORAGE);
  625. storage_common_mkdir(storage, directory_path);
  626. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  627. storage_common_mkdir(storage, directory_path);
  628. furi_record_close(RECORD_STORAGE);
  629. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  630. fhttp->save_received_data = true;
  631. return flipper_http_request(fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/list/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  632. }
  633. // we will load the palyer stats from the API and save them
  634. // in player_spawn game method, it will load the player stats that we saved
  635. static bool fetch_player_stats(FlipperHTTP *fhttp)
  636. {
  637. if (!fhttp)
  638. {
  639. FURI_LOG_E(TAG, "fhttp is NULL");
  640. easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  641. return false;
  642. }
  643. char username[64];
  644. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  645. {
  646. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  647. easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
  648. return false;
  649. }
  650. char url[128];
  651. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/user/game-stats/%s/", username);
  652. // ensure the folders exist
  653. char directory_path[128];
  654. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  655. Storage *storage = furi_record_open(RECORD_STORAGE);
  656. storage_common_mkdir(storage, directory_path);
  657. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
  658. storage_common_mkdir(storage, directory_path);
  659. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player");
  660. storage_common_mkdir(storage, directory_path);
  661. furi_record_close(RECORD_STORAGE);
  662. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player/player_stats.json");
  663. fhttp->save_received_data = true;
  664. return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  665. }
  666. // static bool fetch_app_update(FlipperHTTP *fhttp)
  667. // {
  668. // if (!fhttp)
  669. // {
  670. // FURI_LOG_E(TAG, "fhttp is NULL");
  671. // easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  672. // return false;
  673. // }
  674. // return flipper_http_get_request_with_headers(fhttp, "https://www.jblanked.com/flipper/api/app/last-updated/flip_world/", "{\"Content-Type\":\"application/json\"}");
  675. // }
  676. // static bool parse_app_update(FlipperHTTP *fhttp)
  677. // {
  678. // if (!fhttp)
  679. // {
  680. // FURI_LOG_E(TAG, "fhttp is NULL");
  681. // easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  682. // return false;
  683. // }
  684. // if (fhttp->last_response == NULL || strlen(fhttp->last_response) == 0)
  685. // {
  686. // FURI_LOG_E(TAG, "fhttp->last_response is NULL or empty");
  687. // easy_flipper_dialog("Error", "fhttp->last_response is NULL or empty. Press BACK to return.");
  688. // return false;
  689. // }
  690. // bool last_update_available = false;
  691. // char last_updated_old[32];
  692. // // load the previous last_updated
  693. // if (!load_char("last_updated", last_updated_old, sizeof(last_updated_old)))
  694. // {
  695. // FURI_LOG_E(TAG, "Failed to load last_updated");
  696. // // it's okay, we'll just update it
  697. // }
  698. // // save the new last_updated
  699. // save_char("last_updated", fhttp->last_response);
  700. // // compare the two
  701. // if (strlen(last_updated_old) == 0 || !is_str(last_updated_old, fhttp->last_response))
  702. // {
  703. // last_update_available = true;
  704. // }
  705. // if (last_update_available)
  706. // {
  707. // easy_flipper_dialog("Update Available", "An update is available. Press OK to update.");
  708. // return true;
  709. // }
  710. // else
  711. // {
  712. // easy_flipper_dialog("No Update Available", "No update is available. Press OK to continue.");
  713. // return false;
  714. // }
  715. // }
  716. static bool start_game_thread(void *context)
  717. {
  718. FlipWorldApp *app = (FlipWorldApp *)context;
  719. if (!app)
  720. {
  721. FURI_LOG_E(TAG, "app is NULL");
  722. easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
  723. return false;
  724. }
  725. // free everything but message_view
  726. free_variable_item_list(app);
  727. free_text_input_view(app);
  728. // free_submenu_other(app); // free lobby list or settings
  729. loader_view_free(app);
  730. free_game_submenu(app);
  731. // free game thread
  732. if (game_thread_running)
  733. {
  734. game_thread_running = false;
  735. if (game_thread)
  736. {
  737. furi_thread_flags_set(furi_thread_get_id(game_thread), WorkerEvtStop);
  738. furi_thread_join(game_thread);
  739. furi_thread_free(game_thread);
  740. }
  741. }
  742. // start game thread
  743. FuriThread *thread = furi_thread_alloc_ex("game", 2048, game_app, app);
  744. if (!thread)
  745. {
  746. FURI_LOG_E(TAG, "Failed to allocate game thread");
  747. easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
  748. return false;
  749. }
  750. furi_thread_start(thread);
  751. game_thread = thread;
  752. game_thread_running = true;
  753. return true;
  754. }
  755. // combine register, login, and world list fetch into one function to switch to the loader view
  756. static bool _fetch_game(DataLoaderModel *model)
  757. {
  758. FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
  759. if (!app)
  760. {
  761. FURI_LOG_E(TAG, "app is NULL");
  762. easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
  763. return false;
  764. }
  765. if (model->request_index == 0)
  766. {
  767. // login
  768. char username[64];
  769. char password[64];
  770. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  771. {
  772. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  773. view_dispatcher_switch_to_view(app->view_dispatcher,
  774. FlipWorldViewSubmenu); // just go back to the main menu for now
  775. easy_flipper_dialog("Error", "Failed to load saved username\nGo to user settings to update.");
  776. return false;
  777. }
  778. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  779. {
  780. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  781. view_dispatcher_switch_to_view(app->view_dispatcher,
  782. FlipWorldViewSubmenu); // just go back to the main menu for now
  783. easy_flipper_dialog("Error", "Failed to load saved password\nGo to settings to update.");
  784. return false;
  785. }
  786. char payload[256];
  787. snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
  788. return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/login/", "{\"Content-Type\":\"application/json\"}", payload);
  789. }
  790. else if (model->request_index == 1)
  791. {
  792. // check if login was successful
  793. char is_logged_in[8];
  794. if (!load_char("is_logged_in", is_logged_in, sizeof(is_logged_in)))
  795. {
  796. FURI_LOG_E(TAG, "Failed to load is_logged_in");
  797. easy_flipper_dialog("Error", "Failed to load is_logged_in\nGo to user settings to update.");
  798. view_dispatcher_switch_to_view(app->view_dispatcher,
  799. FlipWorldViewSubmenu); // just go back to the main menu for now
  800. return false;
  801. }
  802. if (is_str(is_logged_in, "false") && is_str(model->title, "Registering..."))
  803. {
  804. // register
  805. char username[64];
  806. char password[64];
  807. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  808. {
  809. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  810. easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
  811. view_dispatcher_switch_to_view(app->view_dispatcher,
  812. FlipWorldViewSubmenu); // just go back to the main menu for now
  813. return false;
  814. }
  815. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  816. {
  817. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  818. easy_flipper_dialog("Error", "Failed to load saved password. Go to settings to update.");
  819. view_dispatcher_switch_to_view(app->view_dispatcher,
  820. FlipWorldViewSubmenu); // just go back to the main menu for now
  821. return false;
  822. }
  823. char payload[172];
  824. snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
  825. model->title = "Registering...";
  826. return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/register/", "{\"Content-Type\":\"application/json\"}", payload);
  827. }
  828. else
  829. {
  830. model->title = "Fetching World List..";
  831. return fetch_world_list(model->fhttp);
  832. }
  833. }
  834. else if (model->request_index == 2)
  835. {
  836. model->title = "Fetching World List..";
  837. return fetch_world_list(model->fhttp);
  838. }
  839. else if (model->request_index == 3)
  840. {
  841. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  842. FuriString *world_list = flipper_http_load_from_file(model->fhttp->file_path);
  843. if (!world_list)
  844. {
  845. view_dispatcher_switch_to_view(app->view_dispatcher,
  846. FlipWorldViewSubmenu); // just go back to the main menu for now
  847. FURI_LOG_E(TAG, "Failed to load world list");
  848. easy_flipper_dialog("Error", "Failed to load world list. Go to game settings to download packs.");
  849. return false;
  850. }
  851. FuriString *first_world = get_json_array_value_furi("worlds", 0, world_list);
  852. if (!first_world)
  853. {
  854. view_dispatcher_switch_to_view(app->view_dispatcher,
  855. FlipWorldViewSubmenu); // just go back to the main menu for now
  856. FURI_LOG_E(TAG, "Failed to get first world");
  857. easy_flipper_dialog("Error", "Failed to get first world. Go to game settings to download packs.");
  858. furi_string_free(world_list);
  859. return false;
  860. }
  861. if (world_exists(furi_string_get_cstr(first_world)))
  862. {
  863. furi_string_free(world_list);
  864. furi_string_free(first_world);
  865. if (!start_game_thread(app))
  866. {
  867. FURI_LOG_E(TAG, "Failed to start game thread");
  868. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  869. view_dispatcher_switch_to_view(app->view_dispatcher,
  870. FlipWorldViewSubmenu); // just go back to the main menu for now
  871. return "Failed to start game thread";
  872. }
  873. return true;
  874. }
  875. 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));
  876. model->fhttp->save_received_data = true;
  877. char url[128];
  878. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/v5/get/world/%s/", furi_string_get_cstr(first_world));
  879. furi_string_free(world_list);
  880. furi_string_free(first_world);
  881. return flipper_http_request(model->fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  882. }
  883. FURI_LOG_E(TAG, "Unknown request index");
  884. return false;
  885. }
  886. static char *_parse_game(DataLoaderModel *model)
  887. {
  888. FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
  889. if (model->request_index == 0)
  890. {
  891. if (!model->fhttp->last_response)
  892. {
  893. save_char("is_logged_in", "false");
  894. // Go back to the main menu
  895. easy_flipper_dialog("Error", "Response is empty. Press BACK to return.");
  896. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  897. return "Response is empty...";
  898. }
  899. // Check for successful conditions
  900. if (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User found") != NULL)
  901. {
  902. save_char("is_logged_in", "true");
  903. model->title = "Login successful!";
  904. model->title = "Fetching World List..";
  905. return "Login successful!";
  906. }
  907. // Check if user not found
  908. if (strstr(model->fhttp->last_response, "User not found") != NULL)
  909. {
  910. save_char("is_logged_in", "false");
  911. model->title = "Registering...";
  912. return "Account not found...\nRegistering now.."; // if they see this an issue happened switching to register
  913. }
  914. // If not success, not found, check length conditions
  915. size_t resp_len = strlen(model->fhttp->last_response);
  916. if (resp_len == 0 || resp_len > 127)
  917. {
  918. // Empty or too long means failed login
  919. save_char("is_logged_in", "false");
  920. // Go back to the main menu
  921. easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
  922. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  923. return "Failed to login...";
  924. }
  925. // Handle any other unknown response as a failure
  926. save_char("is_logged_in", "false");
  927. // Go back to the main menu
  928. easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
  929. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  930. return "Failed to login...";
  931. }
  932. else if (model->request_index == 1)
  933. {
  934. if (is_str(model->title, "Registering..."))
  935. {
  936. // check registration response
  937. if (model->fhttp->last_response != NULL && (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User created") != NULL))
  938. {
  939. save_char("is_logged_in", "true");
  940. char username[64];
  941. char password[64];
  942. // load the username and password, then save them
  943. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  944. {
  945. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  946. easy_flipper_dialog("Error", "Failed to load Flip-Social-Username");
  947. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  948. return "Failed to load Flip-Social-Username";
  949. }
  950. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  951. {
  952. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  953. easy_flipper_dialog("Error", "Failed to load Flip-Social-Password");
  954. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  955. return "Failed to load Flip-Social-Password";
  956. }
  957. // load wifi ssid,pass then save
  958. char ssid[64];
  959. char pass[64];
  960. if (!load_char("WiFi-SSID", ssid, sizeof(ssid)))
  961. {
  962. FURI_LOG_E(TAG, "Failed to load WiFi-SSID");
  963. easy_flipper_dialog("Error", "Failed to load WiFi-SSID");
  964. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  965. return "Failed to load WiFi-SSID";
  966. }
  967. if (!load_char("WiFi-Password", pass, sizeof(pass)))
  968. {
  969. FURI_LOG_E(TAG, "Failed to load WiFi-Password");
  970. easy_flipper_dialog("Error", "Failed to load WiFi-Password");
  971. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  972. return "Failed to load WiFi-Password";
  973. }
  974. save_settings(ssid, pass, username, password);
  975. model->title = "Fetching World List..";
  976. return "Account created!";
  977. }
  978. else if (strstr(model->fhttp->last_response, "Username or password not provided") != NULL)
  979. {
  980. easy_flipper_dialog("Error", "Please enter your credentials.\nPress BACK to return.");
  981. view_dispatcher_switch_to_view(app->view_dispatcher,
  982. FlipWorldViewSubmenu); // just go back to the main menu for now
  983. return "Please enter your credentials.";
  984. }
  985. else if (strstr(model->fhttp->last_response, "User already exists") != NULL || strstr(model->fhttp->last_response, "Multiple users found") != NULL)
  986. {
  987. easy_flipper_dialog("Error", "Registration failed...\nUsername already exists.\nPress BACK to return.");
  988. view_dispatcher_switch_to_view(app->view_dispatcher,
  989. FlipWorldViewSubmenu); // just go back to the main menu for now
  990. return "Username already exists.";
  991. }
  992. else
  993. {
  994. easy_flipper_dialog("Error", "Registration failed...\nUpdate your credentials.\nPress BACK to return.");
  995. view_dispatcher_switch_to_view(app->view_dispatcher,
  996. FlipWorldViewSubmenu); // just go back to the main menu for now
  997. return "Registration failed...";
  998. }
  999. }
  1000. else
  1001. {
  1002. if (!start_game_thread(app))
  1003. {
  1004. FURI_LOG_E(TAG, "Failed to start game thread");
  1005. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1006. view_dispatcher_switch_to_view(app->view_dispatcher,
  1007. FlipWorldViewSubmenu); // just go back to the main menu for now
  1008. return "Failed to start game thread";
  1009. }
  1010. return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  1011. }
  1012. }
  1013. else if (model->request_index == 2)
  1014. {
  1015. return "Welcome to FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  1016. }
  1017. else if (model->request_index == 3)
  1018. {
  1019. if (!start_game_thread(app))
  1020. {
  1021. FURI_LOG_E(TAG, "Failed to start game thread");
  1022. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1023. view_dispatcher_switch_to_view(app->view_dispatcher,
  1024. FlipWorldViewSubmenu); // just go back to the main menu for now
  1025. return "Failed to start game thread";
  1026. }
  1027. return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  1028. }
  1029. easy_flipper_dialog("Error", "Unknown error. Press BACK to return.");
  1030. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
  1031. return "Unknown error";
  1032. }
  1033. static void switch_to_view_get_game(FlipWorldApp *app)
  1034. {
  1035. if (!loader_view_alloc(app))
  1036. {
  1037. FURI_LOG_E(TAG, "Failed to allocate view loader");
  1038. return;
  1039. }
  1040. loader_switch_to_view(app, "Starting Game..", _fetch_game, _parse_game, 5, callback_to_submenu, FlipWorldViewLoader);
  1041. }
  1042. static void run(FlipWorldApp *app)
  1043. {
  1044. if (!app)
  1045. {
  1046. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1047. return;
  1048. }
  1049. free_all_views(app, true, true, false);
  1050. // only need to check if they have 50k free (game needs about 38k currently)
  1051. if (!is_enough_heap(50000, false))
  1052. {
  1053. easy_flipper_dialog("Error", "Not enough heap memory.\nPlease restart your Flipper.");
  1054. return;
  1055. }
  1056. // check if logged in
  1057. if (is_logged_in() || is_logged_in_to_flip_social())
  1058. {
  1059. FlipperHTTP *fhttp = flipper_http_alloc();
  1060. if (!fhttp)
  1061. {
  1062. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1063. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1064. return;
  1065. }
  1066. bool fetch_world_list_i()
  1067. {
  1068. return fetch_world_list(fhttp);
  1069. }
  1070. bool parse_world_list_i()
  1071. {
  1072. return fhttp->state != ISSUE;
  1073. }
  1074. bool fetch_player_stats_i()
  1075. {
  1076. return fetch_player_stats(fhttp);
  1077. }
  1078. if (!alloc_message_view(app, MessageStateLoading))
  1079. {
  1080. FURI_LOG_E(TAG, "Failed to allocate message view");
  1081. return;
  1082. }
  1083. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1084. // Make the request
  1085. if (game_mode_index != 1) // not GAME_MODE_PVP
  1086. {
  1087. 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))
  1088. {
  1089. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  1090. flipper_http_free(fhttp);
  1091. }
  1092. else
  1093. {
  1094. flipper_http_free(fhttp);
  1095. }
  1096. if (!start_game_thread(app))
  1097. {
  1098. FURI_LOG_E(TAG, "Failed to start game thread");
  1099. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1100. return;
  1101. }
  1102. }
  1103. else
  1104. {
  1105. // load pvp info (this returns the lobbies available)
  1106. bool fetch_pvp_lobbies()
  1107. {
  1108. // ensure flip_world directory exists
  1109. char directory_path[128];
  1110. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  1111. Storage *storage = furi_record_open(RECORD_STORAGE);
  1112. storage_common_mkdir(storage, directory_path);
  1113. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp");
  1114. storage_common_mkdir(storage, directory_path);
  1115. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/lobbies");
  1116. storage_common_mkdir(storage, directory_path);
  1117. furi_record_close(RECORD_STORAGE);
  1118. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/pvp_lobbies.json");
  1119. storage_simply_remove_recursive(storage, fhttp->file_path); // ensure the file is empty
  1120. fhttp->save_received_data = true;
  1121. // 2 players max, 10 lobbies
  1122. return flipper_http_request(fhttp, GET, "https://www.jblanked.com/flipper/api/world/pvp/lobbies/2/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  1123. }
  1124. bool parse_pvp_lobbies()
  1125. {
  1126. free_submenu_other(app);
  1127. if (!alloc_submenu_other(app, FlipWorldViewLobby))
  1128. {
  1129. FURI_LOG_E(TAG, "Failed to allocate lobby submenu");
  1130. return false;
  1131. }
  1132. // add the lobbies to the submenu
  1133. FuriString *lobbies = flipper_http_load_from_file(fhttp->file_path);
  1134. if (!lobbies)
  1135. {
  1136. FURI_LOG_E(TAG, "Failed to load lobbies");
  1137. return false;
  1138. }
  1139. // parse the lobbies
  1140. for (uint32_t i = 0; i < 10; i++)
  1141. {
  1142. FuriString *lobby = get_json_array_value_furi("lobbies", i, lobbies);
  1143. if (!lobby)
  1144. {
  1145. FURI_LOG_I(TAG, "No more lobbies");
  1146. break;
  1147. }
  1148. FuriString *lobby_id = get_json_value_furi("id", lobby);
  1149. if (!lobby_id)
  1150. {
  1151. FURI_LOG_E(TAG, "Failed to get lobby id");
  1152. furi_string_free(lobby);
  1153. return false;
  1154. }
  1155. // add the lobby to the submenu
  1156. submenu_add_item(app->submenu_other, furi_string_get_cstr(lobby_id), FlipWorldSubmenuIndexLobby + i, callback_submenu_lobby_choices, app);
  1157. // add the lobby to the list
  1158. if (!easy_flipper_set_buffer(&lobby_list[i], 64))
  1159. {
  1160. FURI_LOG_E(TAG, "Failed to allocate lobby list");
  1161. furi_string_free(lobby);
  1162. furi_string_free(lobby_id);
  1163. return false;
  1164. }
  1165. snprintf(lobby_list[i], 64, "%s", furi_string_get_cstr(lobby_id));
  1166. furi_string_free(lobby);
  1167. furi_string_free(lobby_id);
  1168. }
  1169. furi_string_free(lobbies);
  1170. return true;
  1171. }
  1172. // load pvp lobbies and player stats
  1173. 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))
  1174. {
  1175. // unlike the pve/story, receiving data is necessary
  1176. // so send the user back to the main menu if it fails
  1177. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  1178. easy_flipper_dialog("Error", "Failed to make request. Press BACK to return.");
  1179. view_dispatcher_switch_to_view(app->view_dispatcher,
  1180. FlipWorldViewSubmenu);
  1181. flipper_http_free(fhttp);
  1182. }
  1183. else
  1184. {
  1185. flipper_http_free(fhttp);
  1186. }
  1187. // switch to the lobby submenu
  1188. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1189. }
  1190. }
  1191. else
  1192. {
  1193. switch_to_view_get_game(app);
  1194. }
  1195. }
  1196. void callback_submenu_choices(void *context, uint32_t index)
  1197. {
  1198. FlipWorldApp *app = (FlipWorldApp *)context;
  1199. furi_check(app);
  1200. switch (index)
  1201. {
  1202. case FlipWorldSubmenuIndexGameSubmenu:
  1203. free_all_views(app, true, true, true);
  1204. if (!alloc_game_submenu(app))
  1205. {
  1206. FURI_LOG_E(TAG, "Failed to allocate game submenu");
  1207. return;
  1208. }
  1209. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewGameSubmenu);
  1210. break;
  1211. case FlipWorldSubmenuIndexStory:
  1212. game_mode_index = 2; // GAME_MODE_STORY
  1213. run(app);
  1214. break;
  1215. case FlipWorldSubmenuIndexPvP:
  1216. game_mode_index = 1; // GAME_MODE_PVP
  1217. run(app);
  1218. break;
  1219. case FlipWorldSubmenuIndexPvE:
  1220. game_mode_index = 0; // GAME_MODE_PVE
  1221. run(app);
  1222. break;
  1223. case FlipWorldSubmenuIndexMessage:
  1224. // About menu.
  1225. free_all_views(app, true, true, true);
  1226. if (!alloc_message_view(app, MessageStateAbout))
  1227. {
  1228. FURI_LOG_E(TAG, "Failed to allocate message view");
  1229. return;
  1230. }
  1231. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1232. break;
  1233. case FlipWorldSubmenuIndexSettings:
  1234. free_all_views(app, true, true, true);
  1235. if (!alloc_submenu_other(app, FlipWorldViewSettings))
  1236. {
  1237. FURI_LOG_E(TAG, "Failed to allocate settings view");
  1238. return;
  1239. }
  1240. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1241. break;
  1242. case FlipWorldSubmenuIndexWiFiSettings:
  1243. free_all_views(app, true, false, true);
  1244. if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexWiFiSettings))
  1245. {
  1246. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  1247. return;
  1248. }
  1249. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1250. break;
  1251. case FlipWorldSubmenuIndexGameSettings:
  1252. free_all_views(app, true, false, true);
  1253. if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexGameSettings))
  1254. {
  1255. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  1256. return;
  1257. }
  1258. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1259. break;
  1260. case FlipWorldSubmenuIndexUserSettings:
  1261. free_all_views(app, true, false, true);
  1262. if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexUserSettings))
  1263. {
  1264. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  1265. return;
  1266. }
  1267. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1268. break;
  1269. default:
  1270. break;
  1271. }
  1272. }
  1273. static bool fetch_lobby(FlipperHTTP *fhttp, char *lobby_name)
  1274. {
  1275. if (!fhttp)
  1276. {
  1277. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1278. return false;
  1279. }
  1280. if (!lobby_name || strlen(lobby_name) == 0)
  1281. {
  1282. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  1283. return false;
  1284. }
  1285. char username[64];
  1286. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1287. {
  1288. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1289. return false;
  1290. }
  1291. // send the request to fetch the lobby details, with player_username
  1292. char url[128];
  1293. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/pvp/lobby/get/%s/%s/", lobby_name, username);
  1294. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/lobbies/%s.json", lobby_name);
  1295. fhttp->save_received_data = true;
  1296. if (!flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL))
  1297. {
  1298. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1299. return false;
  1300. }
  1301. fhttp->state = RECEIVING;
  1302. while (fhttp->state != IDLE)
  1303. {
  1304. furi_delay_ms(100);
  1305. }
  1306. return true;
  1307. }
  1308. static bool join_lobby(FlipperHTTP *fhttp, char *lobby_name)
  1309. {
  1310. if (!fhttp)
  1311. {
  1312. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1313. return false;
  1314. }
  1315. if (!lobby_name || strlen(lobby_name) == 0)
  1316. {
  1317. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  1318. return false;
  1319. }
  1320. char username[64];
  1321. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1322. {
  1323. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1324. return false;
  1325. }
  1326. char url[128];
  1327. char payload[128];
  1328. snprintf(payload, sizeof(payload), "{\"username\":\"%s\", \"game_id\":\"%s\"}", username, lobby_name);
  1329. save_char("pvp_lobby_name", lobby_name); // save the lobby name
  1330. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/pvp/lobby/join/");
  1331. if (!flipper_http_request(fhttp, POST, url, "{\"Content-Type\":\"application/json\"}", payload))
  1332. {
  1333. FURI_LOG_E(TAG, "Failed to join lobby");
  1334. return false;
  1335. }
  1336. fhttp->state = RECEIVING;
  1337. while (fhttp->state != IDLE)
  1338. {
  1339. furi_delay_ms(100);
  1340. }
  1341. return true;
  1342. }
  1343. static bool create_pvp_enemy(FuriString *lobby_details)
  1344. {
  1345. if (!lobby_details)
  1346. {
  1347. FURI_LOG_E(TAG, "Failed to load lobby details");
  1348. return false;
  1349. }
  1350. char current_user[64];
  1351. if (!load_char("Flip-Social-Username", current_user, sizeof(current_user)))
  1352. {
  1353. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1354. save_char("create_pvp_error", "Failed to load Flip-Social-Username");
  1355. return false;
  1356. }
  1357. for (uint8_t i = 0; i < 2; i++)
  1358. {
  1359. // parse the lobby details
  1360. FuriString *player_stats = get_json_array_value_furi("player_stats", i, lobby_details);
  1361. if (!player_stats)
  1362. {
  1363. FURI_LOG_E(TAG, "Failed to get player stats");
  1364. save_char("create_pvp_error", "Failed to get player stats array");
  1365. return false;
  1366. }
  1367. // available keys from player_stats
  1368. FuriString *username = get_json_value_furi("username", player_stats);
  1369. if (!username)
  1370. {
  1371. FURI_LOG_E(TAG, "Failed to get username");
  1372. save_char("create_pvp_error", "Failed to get username");
  1373. furi_string_free(player_stats);
  1374. return false;
  1375. }
  1376. // check if the username is the same as the current user
  1377. if (is_str(furi_string_get_cstr(username), current_user))
  1378. {
  1379. furi_string_free(player_stats);
  1380. furi_string_free(username);
  1381. continue; // skip the current user
  1382. }
  1383. FuriString *strength = get_json_value_furi("strength", player_stats);
  1384. FuriString *health = get_json_value_furi("health", player_stats);
  1385. FuriString *attack_timer = get_json_value_furi("attack_timer", player_stats);
  1386. if (!strength || !health || !attack_timer)
  1387. {
  1388. FURI_LOG_E(TAG, "Failed to get player stats");
  1389. save_char("create_pvp_error", "Failed to get player stats");
  1390. furi_string_free(player_stats);
  1391. furi_string_free(username);
  1392. if (strength)
  1393. furi_string_free(strength);
  1394. if (health)
  1395. furi_string_free(health);
  1396. if (attack_timer)
  1397. furi_string_free(attack_timer);
  1398. return false;
  1399. }
  1400. // create enemy data
  1401. FuriString *enemy_data = furi_string_alloc();
  1402. furi_string_printf(
  1403. enemy_data,
  1404. "{\"enemy_data\":[{\"id\":\"sword\",\"is_user\":\"true\",\"username\":\"%s\","
  1405. "\"index\":0,\"start_position\":{\"x\":350,\"y\":210},\"end_position\":{\"x\":350,\"y\":210},"
  1406. "\"move_timer\":1,\"speed\":1,\"attack_timer\":%f,\"strength\":%f,\"health\":%f}]}",
  1407. furi_string_get_cstr(username),
  1408. (double)atof_furi(attack_timer),
  1409. (double)atof_furi(strength),
  1410. (double)atof_furi(health));
  1411. char directory_path[128];
  1412. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  1413. Storage *storage = furi_record_open(RECORD_STORAGE);
  1414. storage_common_mkdir(storage, directory_path);
  1415. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  1416. storage_common_mkdir(storage, directory_path);
  1417. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/pvp_world");
  1418. storage_common_mkdir(storage, directory_path);
  1419. furi_record_close(RECORD_STORAGE);
  1420. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/pvp_world/pvp_world_enemy_data.json");
  1421. // remove the enemy_data file if it exists
  1422. storage_simply_remove_recursive(storage, directory_path);
  1423. File *file = storage_file_alloc(storage);
  1424. if (!storage_file_open(file, directory_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  1425. {
  1426. FURI_LOG_E("Game", "Failed to open file for writing: %s", directory_path);
  1427. save_char("create_pvp_error", "Failed to open file for writing");
  1428. storage_file_free(file);
  1429. furi_record_close(RECORD_STORAGE);
  1430. furi_string_free(enemy_data);
  1431. furi_string_free(player_stats);
  1432. furi_string_free(username);
  1433. furi_string_free(strength);
  1434. furi_string_free(health);
  1435. furi_string_free(attack_timer);
  1436. return false;
  1437. }
  1438. size_t data_size = furi_string_size(enemy_data);
  1439. if (storage_file_write(file, furi_string_get_cstr(enemy_data), data_size) != data_size)
  1440. {
  1441. FURI_LOG_E("Game", "Failed to write enemy_data");
  1442. save_char("create_pvp_error", "Failed to write enemy_data");
  1443. }
  1444. storage_file_close(file);
  1445. furi_string_free(enemy_data);
  1446. furi_string_free(player_stats);
  1447. furi_string_free(username);
  1448. furi_string_free(strength);
  1449. furi_string_free(health);
  1450. furi_string_free(attack_timer);
  1451. // player is found so break
  1452. break;
  1453. }
  1454. return true;
  1455. }
  1456. // since we aren't using FURI_LOG, we will use easy_flipper_dialog and the last_error_message
  1457. // char last_error_message[64];
  1458. static size_t lobby_count(FlipperHTTP *fhttp, FuriString *lobby)
  1459. {
  1460. if (!fhttp)
  1461. {
  1462. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1463. return -1;
  1464. }
  1465. if (!lobby)
  1466. {
  1467. FURI_LOG_E(TAG, "Lobby details are NULL");
  1468. return -1;
  1469. }
  1470. // check if the player is in the lobby
  1471. FuriString *player_count = get_json_value_furi("player_count", lobby);
  1472. if (!player_count)
  1473. {
  1474. FURI_LOG_E(TAG, "Failed to get player count");
  1475. return -1;
  1476. }
  1477. const size_t count = atoi(furi_string_get_cstr(player_count));
  1478. furi_string_free(player_count);
  1479. return count;
  1480. }
  1481. static bool in_lobby(FlipperHTTP *fhttp, FuriString *lobby)
  1482. {
  1483. if (!fhttp)
  1484. {
  1485. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1486. return false;
  1487. }
  1488. if (!lobby)
  1489. {
  1490. FURI_LOG_E(TAG, "Lobby details are NULL");
  1491. return false;
  1492. }
  1493. // check if the player is in the lobby
  1494. FuriString *is_in_game = get_json_value_furi("is_in_game", lobby);
  1495. if (!is_in_game)
  1496. {
  1497. FURI_LOG_E(TAG, "Failed to get is_in_game");
  1498. furi_string_free(is_in_game);
  1499. return false;
  1500. }
  1501. const bool in_game = is_str(furi_string_get_cstr(is_in_game), "true");
  1502. furi_string_free(is_in_game);
  1503. return in_game;
  1504. }
  1505. static bool start_ws(FlipperHTTP *fhttp, char *lobby_name)
  1506. {
  1507. if (!fhttp)
  1508. {
  1509. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1510. return false;
  1511. }
  1512. if (!lobby_name || strlen(lobby_name) == 0)
  1513. {
  1514. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  1515. return false;
  1516. }
  1517. fhttp->state = IDLE; // ensure it's set to IDLE for the next request
  1518. char websocket_url[128];
  1519. snprintf(websocket_url, sizeof(websocket_url), "ws://www.jblanked.com/ws/game/%s/", lobby_name);
  1520. if (!flipper_http_websocket_start(fhttp, websocket_url, 80, "{\"Content-Type\":\"application/json\"}"))
  1521. {
  1522. FURI_LOG_E(TAG, "Failed to start websocket");
  1523. return false;
  1524. }
  1525. fhttp->state = RECEIVING;
  1526. while (fhttp->state != IDLE)
  1527. {
  1528. furi_delay_ms(100);
  1529. }
  1530. return true;
  1531. }
  1532. // this will free both the fhttp and lobby
  1533. static void start_pvp(FlipperHTTP *fhttp, FuriString *lobby, void *context)
  1534. {
  1535. FlipWorldApp *app = (FlipWorldApp *)context;
  1536. furi_check(app, "FlipWorldApp is NULL");
  1537. // only thing left to do is create the enemy data and start the websocket session
  1538. if (!create_pvp_enemy(lobby))
  1539. {
  1540. FURI_LOG_E(TAG, "Failed to create pvp enemy context.");
  1541. easy_flipper_dialog("Error", "Failed to create pvp enemy context. Press BACK to return.");
  1542. flipper_http_free(fhttp);
  1543. furi_string_free(lobby);
  1544. return;
  1545. }
  1546. furi_string_free(lobby);
  1547. // start the websocket session
  1548. if (!start_ws(fhttp, lobby_list[lobby_index]))
  1549. {
  1550. FURI_LOG_E(TAG, "Failed to start websocket session");
  1551. easy_flipper_dialog("Error", "Failed to start websocket session. Press BACK to return.");
  1552. flipper_http_free(fhttp);
  1553. return;
  1554. }
  1555. flipper_http_free(fhttp);
  1556. // start the game thread
  1557. if (!start_game_thread(app))
  1558. {
  1559. FURI_LOG_E(TAG, "Failed to start game thread");
  1560. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1561. return;
  1562. }
  1563. };
  1564. void waiting_loader_process_callback(FlipperHTTP *fhttp, void *context)
  1565. {
  1566. FlipWorldApp *app = (FlipWorldApp *)context;
  1567. if (!app)
  1568. {
  1569. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1570. return;
  1571. }
  1572. if (!fhttp)
  1573. {
  1574. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1575. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1576. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1577. return;
  1578. }
  1579. // fetch the lobby details
  1580. if (!fetch_lobby(fhttp, lobby_list[lobby_index]))
  1581. {
  1582. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1583. flipper_http_free(fhttp);
  1584. easy_flipper_dialog("Error", "Failed to fetch lobby details. Press BACK to return.");
  1585. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1586. return;
  1587. }
  1588. // load the lobby details
  1589. FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
  1590. if (!lobby)
  1591. {
  1592. FURI_LOG_E(TAG, "Failed to load lobby details");
  1593. flipper_http_free(fhttp);
  1594. easy_flipper_dialog("Error", "Failed to load lobby details. Press BACK to return.");
  1595. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1596. return;
  1597. }
  1598. // get the player count
  1599. const size_t count = lobby_count(fhttp, lobby);
  1600. if (count == 2)
  1601. {
  1602. // break out of this and start the game
  1603. start_pvp(fhttp, lobby, app); // this will free both the fhttp and lobby
  1604. return;
  1605. }
  1606. furi_string_free(lobby);
  1607. }
  1608. static void waiting_lobby(void *context)
  1609. {
  1610. FlipWorldApp *app = (FlipWorldApp *)context;
  1611. furi_check(app, "waiting_lobby: FlipWorldApp is NULL");
  1612. if (!start_waiting_thread(app))
  1613. {
  1614. FURI_LOG_E(TAG, "Failed to start waiting thread");
  1615. easy_flipper_dialog("Error", "Failed to start waiting thread. Press BACK to return.");
  1616. return;
  1617. }
  1618. free_message_view(app);
  1619. if (!alloc_message_view(app, MessageStateWaitingLobby))
  1620. {
  1621. FURI_LOG_E(TAG, "Failed to allocate message view");
  1622. return;
  1623. }
  1624. // finally, switch to the waiting lobby view
  1625. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1626. };
  1627. static void callback_submenu_lobby_choices(void *context, uint32_t index)
  1628. {
  1629. /* Handle other game lobbies
  1630. 1. when clicked on, send request to fetch the selected game lobby details
  1631. 2. start the websocket session
  1632. 3. start the game thread (the rest will be handled by game_start and player_update)
  1633. */
  1634. FlipWorldApp *app = (FlipWorldApp *)context;
  1635. furi_check(app, "FlipWorldApp is NULL");
  1636. if (index >= FlipWorldSubmenuIndexLobby && index < FlipWorldSubmenuIndexLobby + 10)
  1637. {
  1638. lobby_index = index - FlipWorldSubmenuIndexLobby;
  1639. FlipperHTTP *fhttp = flipper_http_alloc();
  1640. if (!fhttp)
  1641. {
  1642. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1643. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1644. return;
  1645. }
  1646. // fetch the lobby details
  1647. if (!fetch_lobby(fhttp, lobby_list[lobby_index]))
  1648. {
  1649. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1650. easy_flipper_dialog("Error", "Failed to fetch lobby details. Press BACK to return.");
  1651. flipper_http_free(fhttp);
  1652. return;
  1653. }
  1654. // load the lobby details
  1655. FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
  1656. if (!lobby)
  1657. {
  1658. FURI_LOG_E(TAG, "Failed to load lobby details");
  1659. flipper_http_free(fhttp);
  1660. return;
  1661. }
  1662. // if there are no players, add the user to the lobby and make the user wait until another player joins
  1663. // if there is one player and it's the user, make the user wait until another player joins
  1664. // if there is one player and it's not the user, parse_lobby and start websocket
  1665. // if there are 2 players (which there shouldn't be at this point), show an error message saying the lobby is full
  1666. switch (lobby_count(fhttp, lobby))
  1667. {
  1668. case -1:
  1669. FURI_LOG_E(TAG, "Failed to get player count");
  1670. easy_flipper_dialog("Error", "Failed to get player count. Press BACK to return.");
  1671. flipper_http_free(fhttp);
  1672. furi_string_free(lobby);
  1673. return;
  1674. case 0:
  1675. // add the user to the lobby
  1676. if (!join_lobby(fhttp, lobby_list[lobby_index]))
  1677. {
  1678. FURI_LOG_E(TAG, "Failed to join lobby");
  1679. easy_flipper_dialog("Error", "Failed to join lobby. Press BACK to return.");
  1680. flipper_http_free(fhttp);
  1681. furi_string_free(lobby);
  1682. return;
  1683. }
  1684. // send the user to the waiting screen
  1685. waiting_lobby(app);
  1686. return;
  1687. case 1:
  1688. // check if the user is in the lobby
  1689. if (in_lobby(fhttp, lobby))
  1690. {
  1691. // send the user to the waiting screen
  1692. FURI_LOG_I(TAG, "User is in the lobby");
  1693. flipper_http_free(fhttp);
  1694. furi_string_free(lobby);
  1695. waiting_lobby(app);
  1696. return;
  1697. }
  1698. // add the user to the lobby
  1699. if (!join_lobby(fhttp, lobby_list[lobby_index]))
  1700. {
  1701. FURI_LOG_E(TAG, "Failed to join lobby");
  1702. easy_flipper_dialog("Error", "Failed to join lobby. Press BACK to return.");
  1703. flipper_http_free(fhttp);
  1704. furi_string_free(lobby);
  1705. return;
  1706. }
  1707. break;
  1708. case 2:
  1709. // show an error message saying the lobby is full
  1710. FURI_LOG_E(TAG, "Lobby is full");
  1711. easy_flipper_dialog("Error", "Lobby is full. Press BACK to return.");
  1712. flipper_http_free(fhttp);
  1713. furi_string_free(lobby);
  1714. return;
  1715. };
  1716. start_pvp(fhttp, lobby, app); // this will free both the fhttp and lobby, and start the game
  1717. }
  1718. }
  1719. static void updated_wifi_ssid(void *context)
  1720. {
  1721. FlipWorldApp *app = (FlipWorldApp *)context;
  1722. if (!app)
  1723. {
  1724. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1725. return;
  1726. }
  1727. // store the entered text
  1728. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  1729. // Ensure null-termination
  1730. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  1731. // save the setting
  1732. save_char("WiFi-SSID", app->text_input_buffer);
  1733. // update the variable item text
  1734. if (app->variable_item_wifi_ssid)
  1735. {
  1736. variable_item_set_current_value_text(app->variable_item_wifi_ssid, app->text_input_buffer);
  1737. // get value of password
  1738. char pass[64];
  1739. char username[64];
  1740. char password[64];
  1741. if (load_char("WiFi-Password", pass, sizeof(pass)))
  1742. {
  1743. if (strlen(pass) > 0 && strlen(app->text_input_buffer) > 0)
  1744. {
  1745. // save the settings
  1746. load_char("Flip-Social-Username", username, sizeof(username));
  1747. load_char("Flip-Social-Password", password, sizeof(password));
  1748. save_settings(app->text_input_buffer, pass, username, password);
  1749. // initialize the http
  1750. FlipperHTTP *fhttp = flipper_http_alloc();
  1751. if (fhttp)
  1752. {
  1753. // save the wifi if the device is connected
  1754. if (!flipper_http_save_wifi(fhttp, app->text_input_buffer, pass))
  1755. {
  1756. easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
  1757. }
  1758. // free the resources
  1759. flipper_http_free(fhttp);
  1760. }
  1761. else
  1762. {
  1763. easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
  1764. }
  1765. }
  1766. }
  1767. }
  1768. // switch to the settings view
  1769. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1770. }
  1771. static void updated_wifi_pass(void *context)
  1772. {
  1773. FlipWorldApp *app = (FlipWorldApp *)context;
  1774. if (!app)
  1775. {
  1776. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1777. return;
  1778. }
  1779. // store the entered text
  1780. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  1781. // Ensure null-termination
  1782. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  1783. // save the setting
  1784. save_char("WiFi-Password", app->text_input_buffer);
  1785. // update the variable item text
  1786. if (app->variable_item_wifi_pass)
  1787. {
  1788. // variable_item_set_current_value_text(app->variable_item_wifi_pass, app->text_input_buffer);
  1789. }
  1790. // get value of ssid
  1791. char ssid[64];
  1792. char username[64];
  1793. char password[64];
  1794. if (load_char("WiFi-SSID", ssid, sizeof(ssid)))
  1795. {
  1796. if (strlen(ssid) > 0 && strlen(app->text_input_buffer) > 0)
  1797. {
  1798. // save the settings
  1799. load_char("Flip-Social-Username", username, sizeof(username));
  1800. load_char("Flip-Social-Password", password, sizeof(password));
  1801. save_settings(ssid, app->text_input_buffer, username, password);
  1802. // initialize the http
  1803. FlipperHTTP *fhttp = flipper_http_alloc();
  1804. if (fhttp)
  1805. {
  1806. // save the wifi if the device is connected
  1807. if (!flipper_http_save_wifi(fhttp, ssid, app->text_input_buffer))
  1808. {
  1809. easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
  1810. }
  1811. // free the resources
  1812. flipper_http_free(fhttp);
  1813. }
  1814. else
  1815. {
  1816. easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
  1817. }
  1818. }
  1819. }
  1820. // switch to the settings view
  1821. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1822. }
  1823. static void updated_username(void *context)
  1824. {
  1825. FlipWorldApp *app = (FlipWorldApp *)context;
  1826. if (!app)
  1827. {
  1828. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1829. return;
  1830. }
  1831. // store the entered text
  1832. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  1833. // Ensure null-termination
  1834. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  1835. // save the setting
  1836. save_char("Flip-Social-Username", app->text_input_buffer);
  1837. // update the variable item text
  1838. if (app->variable_item_user_username)
  1839. {
  1840. variable_item_set_current_value_text(app->variable_item_user_username, app->text_input_buffer);
  1841. }
  1842. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList); // back to user settings
  1843. }
  1844. static void updated_password(void *context)
  1845. {
  1846. FlipWorldApp *app = (FlipWorldApp *)context;
  1847. if (!app)
  1848. {
  1849. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1850. return;
  1851. }
  1852. // store the entered text
  1853. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  1854. // Ensure null-termination
  1855. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  1856. // save the setting
  1857. save_char("Flip-Social-Password", app->text_input_buffer);
  1858. // update the variable item text
  1859. if (app->variable_item_user_password)
  1860. {
  1861. variable_item_set_current_value_text(app->variable_item_user_password, app->text_input_buffer);
  1862. }
  1863. // get value of username
  1864. char username[64];
  1865. char ssid[64];
  1866. char pass[64];
  1867. if (load_char("Flip-Social-Username", username, sizeof(username)))
  1868. {
  1869. if (strlen(username) > 0 && strlen(app->text_input_buffer) > 0)
  1870. {
  1871. // save the settings
  1872. load_char("WiFi-SSID", ssid, sizeof(ssid));
  1873. load_char("WiFi-Password", pass, sizeof(pass));
  1874. save_settings(ssid, pass, username, app->text_input_buffer);
  1875. }
  1876. }
  1877. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList); // back to user settings
  1878. }
  1879. static void wifi_settings_select(void *context, uint32_t index)
  1880. {
  1881. FlipWorldApp *app = (FlipWorldApp *)context;
  1882. if (!app)
  1883. {
  1884. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1885. return;
  1886. }
  1887. char ssid[64];
  1888. char pass[64];
  1889. char username[64];
  1890. char password[64];
  1891. switch (index)
  1892. {
  1893. case 0: // Input SSID
  1894. free_all_views(app, false, false, true);
  1895. if (!alloc_text_input_view(app, "SSID"))
  1896. {
  1897. FURI_LOG_E(TAG, "Failed to allocate text input view");
  1898. return;
  1899. }
  1900. // load SSID
  1901. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  1902. {
  1903. strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size - 1);
  1904. app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
  1905. }
  1906. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  1907. break;
  1908. case 1: // Input Password
  1909. free_all_views(app, false, false, true);
  1910. if (!alloc_text_input_view(app, "Password"))
  1911. {
  1912. FURI_LOG_E(TAG, "Failed to allocate text input view");
  1913. return;
  1914. }
  1915. // load password
  1916. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  1917. {
  1918. strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size - 1);
  1919. app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
  1920. }
  1921. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  1922. break;
  1923. default:
  1924. FURI_LOG_E(TAG, "Unknown configuration item index");
  1925. break;
  1926. }
  1927. }
  1928. static void fps_change(VariableItem *item)
  1929. {
  1930. uint8_t index = variable_item_get_current_value_index(item);
  1931. fps_index = index;
  1932. variable_item_set_current_value_text(item, fps_choices_str[index]);
  1933. variable_item_set_current_value_index(item, index);
  1934. save_char("Game-FPS", fps_choices_str[index]);
  1935. }
  1936. static void screen_on_change(VariableItem *item)
  1937. {
  1938. uint8_t index = variable_item_get_current_value_index(item);
  1939. screen_always_on_index = index;
  1940. variable_item_set_current_value_text(item, yes_or_no_choices[index]);
  1941. variable_item_set_current_value_index(item, index);
  1942. save_char("Game-Screen-Always-On", yes_or_no_choices[index]);
  1943. }
  1944. static void sound_on_change(VariableItem *item)
  1945. {
  1946. uint8_t index = variable_item_get_current_value_index(item);
  1947. sound_on_index = index;
  1948. variable_item_set_current_value_text(item, yes_or_no_choices[index]);
  1949. variable_item_set_current_value_index(item, index);
  1950. save_char("Game-Sound-On", yes_or_no_choices[index]);
  1951. }
  1952. static void vibration_on_change(VariableItem *item)
  1953. {
  1954. uint8_t index = variable_item_get_current_value_index(item);
  1955. vibration_on_index = index;
  1956. variable_item_set_current_value_text(item, yes_or_no_choices[index]);
  1957. variable_item_set_current_value_index(item, index);
  1958. save_char("Game-Vibration-On", yes_or_no_choices[index]);
  1959. }
  1960. static void player_on_change(VariableItem *item)
  1961. {
  1962. uint8_t index = variable_item_get_current_value_index(item);
  1963. player_sprite_index = index;
  1964. variable_item_set_current_value_text(item, is_str(player_sprite_choices[index], "naked") ? "None" : player_sprite_choices[index]);
  1965. variable_item_set_current_value_index(item, index);
  1966. save_char("Game-Player-Sprite", player_sprite_choices[index]);
  1967. }
  1968. static void vgm_x_change(VariableItem *item)
  1969. {
  1970. uint8_t index = variable_item_get_current_value_index(item);
  1971. vgm_x_index = index;
  1972. variable_item_set_current_value_text(item, vgm_levels[index]);
  1973. variable_item_set_current_value_index(item, index);
  1974. save_char("Game-VGM-X", vgm_levels[index]);
  1975. }
  1976. static void vgm_y_change(VariableItem *item)
  1977. {
  1978. uint8_t index = variable_item_get_current_value_index(item);
  1979. vgm_y_index = index;
  1980. variable_item_set_current_value_text(item, vgm_levels[index]);
  1981. variable_item_set_current_value_index(item, index);
  1982. save_char("Game-VGM-Y", vgm_levels[index]);
  1983. }
  1984. static bool _fetch_worlds(DataLoaderModel *model)
  1985. {
  1986. if (!model || !model->fhttp)
  1987. {
  1988. FURI_LOG_E(TAG, "model or fhttp is NULL");
  1989. return false;
  1990. }
  1991. char directory_path[128];
  1992. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  1993. Storage *storage = furi_record_open(RECORD_STORAGE);
  1994. storage_common_mkdir(storage, directory_path);
  1995. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  1996. storage_common_mkdir(storage, directory_path);
  1997. furi_record_close(RECORD_STORAGE);
  1998. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list_full.json");
  1999. model->fhttp->save_received_data = true;
  2000. return flipper_http_request(model->fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/get/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  2001. }
  2002. static char *_parse_worlds(DataLoaderModel *model)
  2003. {
  2004. UNUSED(model);
  2005. return "World Pack Installed";
  2006. }
  2007. static void switch_to_view_get_worlds(FlipWorldApp *app)
  2008. {
  2009. if (!loader_view_alloc(app))
  2010. {
  2011. FURI_LOG_E(TAG, "Failed to allocate view loader");
  2012. return;
  2013. }
  2014. loader_switch_to_view(app, "Fetching World Pack..", _fetch_worlds, _parse_worlds, 1, callback_to_submenu, FlipWorldViewLoader);
  2015. }
  2016. static void game_settings_select(void *context, uint32_t index)
  2017. {
  2018. FlipWorldApp *app = (FlipWorldApp *)context;
  2019. if (!app)
  2020. {
  2021. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2022. return;
  2023. }
  2024. switch (index)
  2025. {
  2026. case 0: // Download all world data as one huge json
  2027. switch_to_view_get_worlds(app);
  2028. case 1: // Player Sprite
  2029. break;
  2030. case 2: // Change FPS
  2031. break;
  2032. case 3: // VGM X
  2033. break;
  2034. case 4: // VGM Y
  2035. break;
  2036. case 5: // Screen Always On
  2037. break;
  2038. case 6: // Sound On
  2039. break;
  2040. case 7: // Vibration On
  2041. break;
  2042. }
  2043. }
  2044. static void user_settings_select(void *context, uint32_t index)
  2045. {
  2046. FlipWorldApp *app = (FlipWorldApp *)context;
  2047. if (!app)
  2048. {
  2049. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2050. return;
  2051. }
  2052. switch (index)
  2053. {
  2054. case 0: // Username
  2055. free_all_views(app, false, false, true);
  2056. if (!alloc_text_input_view(app, "Username-Login"))
  2057. {
  2058. FURI_LOG_E(TAG, "Failed to allocate text input view");
  2059. return;
  2060. }
  2061. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  2062. break;
  2063. case 1: // Password
  2064. free_all_views(app, false, false, true);
  2065. if (!alloc_text_input_view(app, "Password-Login"))
  2066. {
  2067. FURI_LOG_E(TAG, "Failed to allocate text input view");
  2068. return;
  2069. }
  2070. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  2071. break;
  2072. }
  2073. }