فهرست منبع

Merge flip_world from https://github.com/jblanked/FlipWorld

Willy-JL 9 ماه پیش
والد
کامیت
f6a904354b

+ 1 - 2
flip_world/README.md

@@ -80,10 +80,9 @@ NPCs are friendly characters that players can interact with. Currently, you can
 
 
 **v0.6**
 **v0.6**
 - New game features
 - New game features
-- Custom Controller Support
 
 
 **v0.7**
 **v0.7**
-- ???
+- New game features
 
 
 **v0.8**
 **v0.8**
 - Multiplayer support
 - Multiplayer support

+ 17 - 3
flip_world/alloc/alloc.c

@@ -16,7 +16,8 @@ void *global_app;
 void flip_world_show_submenu()
 void flip_world_show_submenu()
 {
 {
     FlipWorldApp *app = (FlipWorldApp *)global_app;
     FlipWorldApp *app = (FlipWorldApp *)global_app;
-    if (app->submenu) {
+    if (app->submenu)
+    {
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
     }
     }
 }
 }
@@ -51,10 +52,17 @@ FlipWorldApp *flip_world_app_alloc()
     {
     {
         return NULL;
         return NULL;
     }
     }
-    submenu_add_item(app->submenu, "Play", FlipWorldSubmenuIndexRun, callback_submenu_choices, app);
+    if (!easy_flipper_set_submenu(&app->submenu_game, FlipWorldViewGameSubmenu, "Play", callback_to_submenu, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    submenu_add_item(app->submenu, "Play", FlipWorldSubmenuIndexGameSubmenu, callback_submenu_choices, app);
     submenu_add_item(app->submenu, "About", FlipWorldSubmenuIndexMessage, callback_submenu_choices, app);
     submenu_add_item(app->submenu, "About", FlipWorldSubmenuIndexMessage, callback_submenu_choices, app);
     submenu_add_item(app->submenu, "Settings", FlipWorldSubmenuIndexSettings, callback_submenu_choices, app);
     submenu_add_item(app->submenu, "Settings", FlipWorldSubmenuIndexSettings, callback_submenu_choices, app);
     //
     //
+    submenu_add_item(app->submenu_game, "Tutorial", FlipWorldSubmenuIndexStory, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_game, "PvP", FlipWorldSubmenuIndexPvP, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_game, "PvE", FlipWorldSubmenuIndexPvE, callback_submenu_choices, app);
 
 
     // Switch to the main view
     // Switch to the main view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
@@ -77,6 +85,11 @@ void flip_world_app_free(FlipWorldApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewSubmenu);
         view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewSubmenu);
         submenu_free(app->submenu);
         submenu_free(app->submenu);
     }
     }
+    if (app->submenu_game)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewGameSubmenu);
+        submenu_free(app->submenu_game);
+    }
     // Free Widget(s)
     // Free Widget(s)
     if (app->widget_result)
     if (app->widget_result)
     {
     {
@@ -101,5 +114,6 @@ void flip_world_app_free(FlipWorldApp *app)
     furi_record_close(RECORD_GUI);
     furi_record_close(RECORD_GUI);
 
 
     // free the app
     // free the app
-    if (app) free(app);
+    if (app)
+        free(app);
 }
 }

+ 1 - 1
flip_world/app.c

@@ -24,7 +24,7 @@ int32_t flip_world_main(void *p)
         return -1;
         return -1;
     }
     }
 
 
-    if (!flipper_http_ping(fhttp))
+    if (!flipper_http_send_command(fhttp, HTTP_CMD_PING))
     {
     {
         FURI_LOG_E(TAG, "Failed to ping the device");
         FURI_LOG_E(TAG, "Failed to ping the device");
         flipper_http_free(fhttp);
         flipper_http_free(fhttp);

+ 1 - 1
flip_world/application.fam

@@ -17,5 +17,5 @@ App(
     ),
     ),
     fap_author="JBlanked",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipWorld",
     fap_weburl="https://github.com/jblanked/FlipWorld",
-    fap_version="0.5",
+    fap_version="0.7",
 )
 )

BIN
flip_world/assets/01-home.png


+ 16 - 2
flip_world/assets/CHANGELOG.md

@@ -1,3 +1,17 @@
+## 0.7 (2025-03-21)
+- Sped up player movement.
+- Added a Tutorial mode.
+- Fixed transition of worlds.
+
+## 0.6.1 (2025-03-15)
+- Switched the server backend to prepare for multiplayer in version 0.8.
+
+## 0.6 (2025-03-10)
+- Fixed saving of player attributes so that it works as intended.
+- Updated the player's level and strength as XP increases.
+- Started implementing multiplayer (requires FlipperHTTP v1.7).
+- Fixed the display of user stats when switching worlds.
+
 ## 0.5 (2025-01-31)
 ## 0.5 (2025-01-31)
 - Fixed saving errors.
 - Fixed saving errors.
 - Improved memory allocation.
 - Improved memory allocation.
@@ -5,8 +19,8 @@
 
 
 ## 0.4 (2025-01-23)
 ## 0.4 (2025-01-23)
 - Added an In-Game menu.
 - Added an In-Game menu.
-- Added New controls (HOLD OK to access the In-Game menu, PRESS BACK to exit the menu, and HOLD BACK to leave the game).
-- Added option to choose player weapon in the Game Settings.
+- Added new controls (HOLD OK to access the In-Game menu, PRESS BACK to exit the menu, and HOLD BACK to leave the game).
+- Added option to choose player's weapon in the Game Settings.
 - Added transition icon for switching worlds.
 - Added transition icon for switching worlds.
 - Doubled the size of each world (from 384x192 to 768x384).
 - Doubled the size of each world (from 384x192 to 768x384).
 - Improved memory allocation.
 - Improved memory allocation.

+ 1 - 2
flip_world/assets/README.md

@@ -79,10 +79,9 @@ NPCs are friendly characters that players can interact with. Currently, you can
 
 
 **v0.6**
 **v0.6**
 - New game features
 - New game features
-- Custom Controller Support
 
 
 **v0.7**
 **v0.7**
-- ???
+- New game features
 
 
 **v0.8**
 **v0.8**
 - Multiplayer support
 - Multiplayer support

+ 126 - 103
flip_world/callback/callback.c

@@ -11,7 +11,7 @@
 // FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
 // FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
 #ifdef DEVELOPMENT
 #ifdef DEVELOPMENT
 #define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
 #define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
-#define DEV_CRASH()                    furi_crash()
+#define DEV_CRASH() furi_crash()
 #else
 #else
 #define FURI_LOG_DEV(tag, format, ...)
 #define FURI_LOG_DEV(tag, format, ...)
 #define DEV_CRASH()
 #define DEV_CRASH()
@@ -430,7 +430,7 @@ static bool alloc_variable_item_list(void *context, uint32_t view_id)
             char _game_fps[8];
             char _game_fps[8];
             if (load_char("Game-FPS", _game_fps, sizeof(_game_fps)))
             if (load_char("Game-FPS", _game_fps, sizeof(_game_fps)))
             {
             {
-                                int index = is_str(_game_fps, "30") ? 0 : is_str(_game_fps, "60") ? 1
+                int index = is_str(_game_fps, "30") ? 0 : is_str(_game_fps, "60") ? 1
                                                       : is_str(_game_fps, "120")  ? 2
                                                       : is_str(_game_fps, "120")  ? 2
                                                       : is_str(_game_fps, "240")  ? 3
                                                       : is_str(_game_fps, "240")  ? 3
                                                                                   : 0;
                                                                                   : 0;
@@ -441,20 +441,19 @@ static bool alloc_variable_item_list(void *context, uint32_t view_id)
             if (load_char("Game-VGM-X", _game_vgm_x, sizeof(_game_vgm_x)))
             if (load_char("Game-VGM-X", _game_vgm_x, sizeof(_game_vgm_x)))
             {
             {
                 int vgm_x = atoi(_game_vgm_x);
                 int vgm_x = atoi(_game_vgm_x);
-                int index = vgm_x == -2 ? 0 :
-                            vgm_x == -1 ? 1 :
-                            vgm_x == 0  ? 2 :
-                            vgm_x == 1  ? 3 :
-                            vgm_x == 2  ? 4 :
-                            vgm_x == 3  ? 5 :
-                            vgm_x == 4  ? 6 :
-                            vgm_x == 5  ? 7 :
-                            vgm_x == 6  ? 8 :
-                            vgm_x == 7  ? 9 :
-                            vgm_x == 8  ? 10 :
-                            vgm_x == 9  ? 11 :
-                            vgm_x == 10 ? 12 :
-                                          2;
+                int index = vgm_x == -2 ? 0 : vgm_x == -1 ? 1
+                                          : vgm_x == 0    ? 2
+                                          : vgm_x == 1    ? 3
+                                          : vgm_x == 2    ? 4
+                                          : vgm_x == 3    ? 5
+                                          : vgm_x == 4    ? 6
+                                          : vgm_x == 5    ? 7
+                                          : vgm_x == 6    ? 8
+                                          : vgm_x == 7    ? 9
+                                          : vgm_x == 8    ? 10
+                                          : vgm_x == 9    ? 11
+                                          : vgm_x == 10   ? 12
+                                                          : 2;
                 variable_item_set_current_value_index(app->variable_item_game_vgm_x, index);
                 variable_item_set_current_value_index(app->variable_item_game_vgm_x, index);
                 variable_item_set_current_value_text(app->variable_item_game_vgm_x, vgm_levels[index]);
                 variable_item_set_current_value_text(app->variable_item_game_vgm_x, vgm_levels[index]);
             }
             }
@@ -462,41 +461,43 @@ static bool alloc_variable_item_list(void *context, uint32_t view_id)
             if (load_char("Game-VGM-Y", _game_vgm_y, sizeof(_game_vgm_y)))
             if (load_char("Game-VGM-Y", _game_vgm_y, sizeof(_game_vgm_y)))
             {
             {
                 int vgm_y = atoi(_game_vgm_y);
                 int vgm_y = atoi(_game_vgm_y);
-                int index = vgm_y == -2 ? 0 :
-                            vgm_y == -1 ? 1 :
-                            vgm_y == 0  ? 2 :
-                            vgm_y == 1  ? 3 :
-                            vgm_y == 2  ? 4 :
-                            vgm_y == 3  ? 5 :
-                            vgm_y == 4  ? 6 :
-                            vgm_y == 5  ? 7 :
-                            vgm_y == 6  ? 8 :
-                            vgm_y == 7  ? 9 :
-                            vgm_y == 8  ? 10 :
-                            vgm_y == 9  ? 11 :
-                            vgm_y == 10 ? 12 :
-                                          2;
+                int index = vgm_y == -2 ? 0 : vgm_y == -1 ? 1
+                                          : vgm_y == 0    ? 2
+                                          : vgm_y == 1    ? 3
+                                          : vgm_y == 2    ? 4
+                                          : vgm_y == 3    ? 5
+                                          : vgm_y == 4    ? 6
+                                          : vgm_y == 5    ? 7
+                                          : vgm_y == 6    ? 8
+                                          : vgm_y == 7    ? 9
+                                          : vgm_y == 8    ? 10
+                                          : vgm_y == 9    ? 11
+                                          : vgm_y == 10   ? 12
+                                                          : 2;
                 variable_item_set_current_value_index(app->variable_item_game_vgm_y, index);
                 variable_item_set_current_value_index(app->variable_item_game_vgm_y, index);
                 variable_item_set_current_value_text(app->variable_item_game_vgm_y, vgm_levels[index]);
                 variable_item_set_current_value_text(app->variable_item_game_vgm_y, vgm_levels[index]);
             }
             }
             char _game_screen_always_on[8];
             char _game_screen_always_on[8];
             if (load_char("Game-Screen-Always-On", _game_screen_always_on, sizeof(_game_screen_always_on)))
             if (load_char("Game-Screen-Always-On", _game_screen_always_on, sizeof(_game_screen_always_on)))
             {
             {
-                int index = is_str(_game_screen_always_on, "No") ? 0 : is_str(_game_screen_always_on, "Yes") ? 1 : 0;
+                int index = is_str(_game_screen_always_on, "No") ? 0 : is_str(_game_screen_always_on, "Yes") ? 1
+                                                                                                             : 0;
                 variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[index]);
                 variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[index]);
                 variable_item_set_current_value_index(app->variable_item_game_screen_always_on, index);
                 variable_item_set_current_value_index(app->variable_item_game_screen_always_on, index);
             }
             }
             char _game_sound_on[8];
             char _game_sound_on[8];
             if (load_char("Game-Sound-On", _game_sound_on, sizeof(_game_sound_on)))
             if (load_char("Game-Sound-On", _game_sound_on, sizeof(_game_sound_on)))
             {
             {
-                int index = is_str(_game_sound_on, "No") ? 0 : is_str(_game_sound_on, "Yes") ? 1 : 0;
+                int index = is_str(_game_sound_on, "No") ? 0 : is_str(_game_sound_on, "Yes") ? 1
+                                                                                             : 0;
                 variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[index]);
                 variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[index]);
                 variable_item_set_current_value_index(app->variable_item_game_sound_on, index);
                 variable_item_set_current_value_index(app->variable_item_game_sound_on, index);
             }
             }
             char _game_vibration_on[8];
             char _game_vibration_on[8];
             if (load_char("Game-Vibration-On", _game_vibration_on, sizeof(_game_vibration_on)))
             if (load_char("Game-Vibration-On", _game_vibration_on, sizeof(_game_vibration_on)))
             {
             {
-                int index = is_str(_game_vibration_on, "No") ? 0 : is_str(_game_vibration_on, "Yes") ? 1 : 0;
+                int index = is_str(_game_vibration_on, "No") ? 0 : is_str(_game_vibration_on, "Yes") ? 1
+                                                                                                     : 0;
                 variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[index]);
                 variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[index]);
                 variable_item_set_current_value_index(app->variable_item_game_vibration_on, index);
                 variable_item_set_current_value_index(app->variable_item_game_vibration_on, index);
             }
             }
@@ -737,7 +738,8 @@ void free_all_views(void *context, bool should_free_variable_item_list, bool sho
         }
         }
     }
     }
 
 
-    if (should_free_submenu_settings) free_submenu_settings(app);
+    if (should_free_submenu_settings)
+        free_submenu_settings(app);
 }
 }
 static bool fetch_world_list(FlipperHTTP *fhttp)
 static bool fetch_world_list(FlipperHTTP *fhttp)
 {
 {
@@ -760,7 +762,7 @@ static bool fetch_world_list(FlipperHTTP *fhttp)
     snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
     snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
 
 
     fhttp->save_received_data = true;
     fhttp->save_received_data = true;
-    return flipper_http_get_request_with_headers(fhttp, "https://www.flipsocial.net/api/world/v5/list/10/", "{\"Content-Type\":\"application/json\"}");
+    return flipper_http_request(fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/list/10/", "{\"Content-Type\":\"application/json\"}", NULL);
 }
 }
 // we will load the palyer stats from the API and save them
 // we will load the palyer stats from the API and save them
 // in player_spawn game method, it will load the player stats that we saved
 // in player_spawn game method, it will load the player stats that we saved
@@ -780,7 +782,7 @@ static bool fetch_player_stats(FlipperHTTP *fhttp)
         return false;
         return false;
     }
     }
     char url[128];
     char url[128];
-    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/game-stats/%s/", username);
+    snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/user/game-stats/%s/", username);
 
 
     // ensure the folders exist
     // ensure the folders exist
     char directory_path[128];
     char directory_path[128];
@@ -795,7 +797,7 @@ static bool fetch_player_stats(FlipperHTTP *fhttp)
 
 
     snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player/player_stats.json");
     snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player/player_stats.json");
     fhttp->save_received_data = true;
     fhttp->save_received_data = true;
-    return flipper_http_get_request_with_headers(fhttp, url, "{\"Content-Type\":\"application/json\"}");
+    return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
 }
 }
 
 
 // static bool fetch_app_update(FlipperHTTP *fhttp)
 // static bool fetch_app_update(FlipperHTTP *fhttp)
@@ -807,7 +809,7 @@ static bool fetch_player_stats(FlipperHTTP *fhttp)
 //         return false;
 //         return false;
 //     }
 //     }
 
 
-//     return flipper_http_get_request_with_headers(fhttp, "https://www.flipsocial.net/api/app/last-updated/flip_world/", "{\"Content-Type\":\"application/json\"}");
+//     return flipper_http_get_request_with_headers(fhttp, "https://www.jblanked.com/flipper/api/app/last-updated/flip_world/", "{\"Content-Type\":\"application/json\"}");
 // }
 // }
 
 
 // static bool parse_app_update(FlipperHTTP *fhttp)
 // static bool parse_app_update(FlipperHTTP *fhttp)
@@ -919,7 +921,7 @@ static bool _fetch_game(DataLoaderModel *model)
         }
         }
         char payload[256];
         char payload[256];
         snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
         snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
-        return flipper_http_post_request_with_headers(model->fhttp, "https://www.flipsocial.net/api/user/login/", "{\"Content-Type\":\"application/json\"}", payload);
+        return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/login/", "{\"Content-Type\":\"application/json\"}", payload);
     }
     }
     else if (model->request_index == 1)
     else if (model->request_index == 1)
     {
     {
@@ -957,7 +959,7 @@ static bool _fetch_game(DataLoaderModel *model)
             char payload[172];
             char payload[172];
             snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
             snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
             model->title = "Registering...";
             model->title = "Registering...";
-            return flipper_http_post_request_with_headers(model->fhttp, "https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", payload);
+            return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/register/", "{\"Content-Type\":\"application/json\"}", payload);
         }
         }
         else
         else
         {
         {
@@ -1012,10 +1014,10 @@ static bool _fetch_game(DataLoaderModel *model)
 
 
         model->fhttp->save_received_data = true;
         model->fhttp->save_received_data = true;
         char url[128];
         char url[128];
-        snprintf(url, sizeof(url), "https://www.flipsocial.net/api/world/v5/get/world/%s/", furi_string_get_cstr(first_world));
+        snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/v5/get/world/%s/", furi_string_get_cstr(first_world));
         furi_string_free(world_list);
         furi_string_free(world_list);
         furi_string_free(first_world);
         furi_string_free(first_world);
-        return flipper_http_get_request_with_headers(model->fhttp, url, "{\"Content-Type\":\"application/json\"}");
+        return flipper_http_request(model->fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
     }
     }
     FURI_LOG_E(TAG, "Unknown request index");
     FURI_LOG_E(TAG, "Unknown request index");
     return false;
     return false;
@@ -1177,83 +1179,104 @@ static void switch_to_view_get_game(FlipWorldApp *app)
     generic_switch_to_view(app, "Starting Game..", _fetch_game, _parse_game, 5, callback_to_submenu, FlipWorldViewLoader);
     generic_switch_to_view(app, "Starting Game..", _fetch_game, _parse_game, 5, callback_to_submenu, FlipWorldViewLoader);
 }
 }
 
 
-void callback_submenu_choices(void *context, uint32_t index)
+static void run(FlipWorldApp *app)
 {
 {
-    FlipWorldApp *app = (FlipWorldApp *)context;
     if (!app)
     if (!app)
     {
     {
         FURI_LOG_E(TAG, "FlipWorldApp is NULL");
         FURI_LOG_E(TAG, "FlipWorldApp is NULL");
         return;
         return;
     }
     }
-    switch (index)
+    free_all_views(app, true, true);
+    if (!is_enough_heap(60000))
     {
     {
-    case FlipWorldSubmenuIndexRun:
-        free_all_views(app, true, true);
-        if (!is_enough_heap(60000))
+        easy_flipper_dialog("Error", "Not enough heap memory.\nPlease restart your Flipper.");
+        return;
+    }
+    // check if logged in
+    if (is_logged_in() || is_logged_in_to_flip_social())
+    {
+        FlipperHTTP *fhttp = flipper_http_alloc();
+        if (!fhttp)
         {
         {
-            easy_flipper_dialog("Error", "Not enough heap memory.\nPlease restart your Flipper.");
+            FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
+            easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
             return;
             return;
         }
         }
-        // check if logged in
-        if (is_logged_in() || is_logged_in_to_flip_social())
+        bool fetch_world_list_i()
         {
         {
-            FlipperHTTP *fhttp = flipper_http_alloc();
-            if (!fhttp)
-            {
-                FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
-                easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
-                return;
-            }
-            bool fetch_world_list_i()
-            {
-                return fetch_world_list(fhttp);
-            }
-            bool parse_world_list_i()
-            {
-                return fhttp->state != ISSUE;
-            }
-
-            bool fetch_player_stats_i()
-            {
-                return fetch_player_stats(fhttp);
-            }
-
-            if (!alloc_message_view(app, MessageStateLoading))
-            {
-                FURI_LOG_E(TAG, "Failed to allocate message view");
-                return;
-            }
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
+            return fetch_world_list(fhttp);
+        }
+        bool parse_world_list_i()
+        {
+            return fhttp->state != ISSUE;
+        }
 
 
-            // Make the request
-            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))
-            {
-                FURI_LOG_E(HTTP_TAG, "Failed to make request");
-                flipper_http_free(fhttp);
-            }
-            else
-            {
-                flipper_http_free(fhttp);
-            }
+        bool fetch_player_stats_i()
+        {
+            return fetch_player_stats(fhttp);
+        }
 
 
-            if (!alloc_submenu_settings(app))
-            {
-                FURI_LOG_E(TAG, "Failed to allocate settings view");
-                return;
-            }
+        if (!alloc_message_view(app, MessageStateLoading))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate message view");
+            return;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
 
 
-            if (!start_game_thread(app))
-            {
-                FURI_LOG_E(TAG, "Failed to start game thread");
-                easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
-                return;
-            }
-            
+        // Make the request
+        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))
+        {
+            FURI_LOG_E(HTTP_TAG, "Failed to make request");
+            flipper_http_free(fhttp);
         }
         }
         else
         else
         {
         {
-            switch_to_view_get_game(app);
+            flipper_http_free(fhttp);
+        }
+
+        if (!alloc_submenu_settings(app))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate settings view");
+            return;
+        }
+
+        if (!start_game_thread(app))
+        {
+            FURI_LOG_E(TAG, "Failed to start game thread");
+            easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+            return;
         }
         }
+    }
+    else
+    {
+        switch_to_view_get_game(app);
+    }
+}
+
+void callback_submenu_choices(void *context, uint32_t index)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case FlipWorldSubmenuIndexGameSubmenu:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewGameSubmenu);
+        break;
+    case FlipWorldSubmenuIndexStory:
+        game_mode_index = 2; // GAME_MODE_STORY
+        run(app);
+        break;
+    case FlipWorldSubmenuIndexPvP:
+        game_mode_index = 1; // GAME_MODE_PVP
+        easy_flipper_dialog("Unavailable", "\nPvP mode is not ready yet.\nPress BACK to return.");
+        break;
+    case FlipWorldSubmenuIndexPvE:
+        game_mode_index = 0; // GAME_MODE_PVE
+        run(app);
         break;
         break;
     case FlipWorldSubmenuIndexMessage:
     case FlipWorldSubmenuIndexMessage:
         // About menu.
         // About menu.
@@ -1615,7 +1638,7 @@ static bool _fetch_worlds(DataLoaderModel *model)
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
     snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list_full.json");
     snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list_full.json");
     model->fhttp->save_received_data = true;
     model->fhttp->save_received_data = true;
-    return flipper_http_get_request_with_headers(model->fhttp, "https://www.flipsocial.net/api/world/v5/get/10/", "{\"Content-Type\":\"application/json\"}");
+    return flipper_http_request(model->fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/get/10/", "{\"Content-Type\":\"application/json\"}", NULL);
 }
 }
 static char *_parse_worlds(DataLoaderModel *model)
 static char *_parse_worlds(DataLoaderModel *model)
 {
 {
@@ -1797,7 +1820,7 @@ void loader_draw_callback(Canvas *canvas, void *model)
     }
     }
 
 
     DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
     DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
-    SerialState http_state = data_loader_model->fhttp->state;
+    HTTPState http_state = data_loader_model->fhttp->state;
     DataState data_state = data_loader_model->data_state;
     DataState data_state = data_loader_model->data_state;
     char *title = data_loader_model->title;
     char *title = data_loader_model->title;
 
 

+ 1 - 0
flip_world/flip_world.c

@@ -10,6 +10,7 @@ int player_sprite_index = 1;
 char *vgm_levels[] = {"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};
 char *vgm_levels[] = {"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};
 int vgm_x_index = 2;
 int vgm_x_index = 2;
 int vgm_y_index = 2;
 int vgm_y_index = 2;
+int game_mode_index = 0;
 float atof_(const char *nptr) { return (float)strtod(nptr, NULL); }
 float atof_(const char *nptr) { return (float)strtod(nptr, NULL); }
 float atof_furi(const FuriString *nptr) { return atof_(furi_string_get_cstr(nptr)); }
 float atof_furi(const FuriString *nptr) { return atof_(furi_string_get_cstr(nptr)); }
 bool is_str(const char *src, const char *dst) { return strcmp(src, dst) == 0; }
 bool is_str(const char *src, const char *dst) { return strcmp(src, dst) == 0; }

+ 8 - 2
flip_world/flip_world.h

@@ -15,13 +15,16 @@
 //
 //
 
 
 #define TAG "FlipWorld"
 #define TAG "FlipWorld"
-#define VERSION 0.5
+#define VERSION 0.7
 #define VERSION_TAG TAG " " FAP_VERSION
 #define VERSION_TAG TAG " " FAP_VERSION
 
 
 // Define the submenu items for our FlipWorld application
 // Define the submenu items for our FlipWorld application
 typedef enum
 typedef enum
 {
 {
-    FlipWorldSubmenuIndexRun, // Click to run the FlipWorld application
+    FlipWorldSubmenuIndexPvE,
+    FlipWorldSubmenuIndexStory,
+    FlipWorldSubmenuIndexPvP,
+    FlipWorldSubmenuIndexGameSubmenu,
     FlipWorldSubmenuIndexMessage,
     FlipWorldSubmenuIndexMessage,
     FlipWorldSubmenuIndexSettings,
     FlipWorldSubmenuIndexSettings,
     FlipWorldSubmenuIndexWiFiSettings,
     FlipWorldSubmenuIndexWiFiSettings,
@@ -33,6 +36,7 @@ typedef enum
 typedef enum
 typedef enum
 {
 {
     FlipWorldViewSubmenu,          // The submenu
     FlipWorldViewSubmenu,          // The submenu
+    FlipWorldViewGameSubmenu,      // The game submenu
     FlipWorldViewMessage,          // The about, loading screen
     FlipWorldViewMessage,          // The about, loading screen
     FlipWorldViewSettings,         // The settings screen
     FlipWorldViewSettings,         // The settings screen
     FlipWorldViewVariableItemList, // The variable item list screen
     FlipWorldViewVariableItemList, // The variable item list screen
@@ -58,6 +62,7 @@ typedef struct
     ViewDispatcher *view_dispatcher;       // Switches between our views
     ViewDispatcher *view_dispatcher;       // Switches between our views
     View *view_message;                    // The about, loading screen
     View *view_message;                    // The about, loading screen
     Submenu *submenu;                      // The submenu
     Submenu *submenu;                      // The submenu
+    Submenu *submenu_game;                 // The game submenu
     Submenu *submenu_settings;             // The settings submenu
     Submenu *submenu_settings;             // The settings submenu
     VariableItemList *variable_item_list;  // The variable item list (settngs)
     VariableItemList *variable_item_list;  // The variable item list (settngs)
     VariableItem *variable_item_wifi_ssid; // The variable item for WiFi SSID
     VariableItem *variable_item_wifi_ssid; // The variable item for WiFi SSID
@@ -92,6 +97,7 @@ extern int player_sprite_index;
 extern char *vgm_levels[];
 extern char *vgm_levels[];
 extern int vgm_x_index;
 extern int vgm_x_index;
 extern int vgm_y_index;
 extern int vgm_y_index;
+extern int game_mode_index;
 float atof_(const char *nptr);
 float atof_(const char *nptr);
 float atof_furi(const FuriString *nptr);
 float atof_furi(const FuriString *nptr);
 bool is_str(const char *src, const char *dst);
 bool is_str(const char *src, const char *dst);

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 539 - 787
flip_world/flipper_http/flipper_http.c


+ 108 - 273
flip_world/flipper_http/flipper_http.h

@@ -23,8 +23,6 @@
 #define FURI_LOG_I(tag, msg, ...)
 #define FURI_LOG_I(tag, msg, ...)
 //
 //
 
 
-// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
-
 #define HTTP_TAG "FlipWorld"              // change this to your app name
 #define HTTP_TAG "FlipWorld"              // change this to your app name
 #define http_tag "flip_world"             // change this to your app id
 #define http_tag "flip_world"             // change this to your app id
 #define UART_CH (momentum_settings.uart_esp_channel)    // UART channel
 #define UART_CH (momentum_settings.uart_esp_channel)    // UART channel
@@ -46,7 +44,7 @@ typedef enum
     RECEIVING, // Receiving data
     RECEIVING, // Receiving data
     SENDING,   // Sending data
     SENDING,   // Sending data
     ISSUE,     // Issue with connection
     ISSUE,     // Issue with connection
-} SerialState;
+} HTTPState;
 
 
 // Event Flags for UART Worker Thread
 // Event Flags for UART Worker Thread
 typedef enum
 typedef enum
@@ -55,97 +53,58 @@ typedef enum
     WorkerEvtRxDone = (1 << 1),
     WorkerEvtRxDone = (1 << 1),
 } WorkerEvtFlags;
 } WorkerEvtFlags;
 
 
-// FlipperHTTP Structure
-typedef struct
+typedef enum
 {
 {
-    FuriStreamBuffer *flipper_http_stream;  // Stream buffer for UART communication
-    FuriHalSerialHandle *serial_handle;     // Serial handle for UART communication
-    FuriThread *rx_thread;                  // Worker thread for UART
-    FuriThreadId rx_thread_id;              // Worker thread ID
-    FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
-    void *callback_context;                 // Context for the callback
-    SerialState state;                      // State of the UART
-
-    // variable to store the last received data from the UART
-    char *last_response;
-    char file_path[256]; // Path to save the received data
-
-    // Timer-related members
-    FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
-
-    bool started_receiving_get; // Indicates if a GET request has started
-    bool just_started_get;      // Indicates if GET data reception has just started
-
-    bool started_receiving_post; // Indicates if a POST request has started
-    bool just_started_post;      // Indicates if POST data reception has just started
-
-    bool started_receiving_put; // Indicates if a PUT request has started
-    bool just_started_put;      // Indicates if PUT data reception has just started
-
-    bool started_receiving_delete; // Indicates if a DELETE request has started
-    bool just_started_delete;      // Indicates if DELETE data reception has just started
+    GET,    // GET request
+    POST,   // POST request
+    PUT,    // PUT request
+    DELETE, // DELETE request
+    //
+    BYTES,      // Stream bytes to file
+    BYTES_POST, // Stream bytes to file after a POST request
+} HTTPMethod;
 
 
-    // Buffer to hold the raw bytes received from the UART
-    uint8_t *received_bytes;
-    size_t received_bytes_len; // Length of the received bytes
-    bool is_bytes_request;     // Flag to indicate if the request is for bytes
-    bool save_bytes;           // Flag to save the received data to a file
-    bool save_received_data;   // Flag to save the received data to a file
-
-    bool just_started_bytes; // Indicates if bytes data reception has just started
+typedef enum
+{
+    HTTP_CMD_WIFI_CONNECT,
+    HTTP_CMD_WIFI_DISCONNECT,
+    HTTP_CMD_IP_ADDRESS,
+    HTTP_CMD_IP_WIFI,
+    HTTP_CMD_SCAN,
+    HTTP_CMD_LIST_COMMANDS,
+    HTTP_CMD_LED_ON,
+    HTTP_CMD_LED_OFF,
+    HTTP_CMD_PING
+} HTTPCommand; // list of non-input commands
 
 
-    char rx_line_buffer[RX_LINE_BUFFER_SIZE];
-    uint8_t file_buffer[FILE_BUFFER_SIZE];
-    size_t file_buffer_len;
+// FlipperHTTP Structure
+typedef struct
+{
+    FuriStreamBuffer *flipper_http_stream;    // Stream buffer for UART communication
+    FuriHalSerialHandle *serial_handle;       // Serial handle for UART communication
+    FuriThread *rx_thread;                    // Worker thread for UART
+    FuriThreadId rx_thread_id;                // Worker thread ID
+    FlipperHTTP_Callback handle_rx_line_cb;   // Callback for received lines
+    void *callback_context;                   // Context for the callback
+    HTTPState state;                          // State of the UART
+    HTTPMethod method;                        // HTTP method
+    char *last_response;                      // variable to store the last received data from the UART
+    char file_path[256];                      // Path to save the received data
+    FuriTimer *get_timeout_timer;             // Timer for HTTP request timeout
+    bool started_receiving;                   // Indicates if a request has started
+    bool just_started;                        // Indicates if data reception has just started
+    bool is_bytes_request;                    // Flag to indicate if the request is for bytes
+    bool save_bytes;                          // Flag to save the received data to a file
+    bool save_received_data;                  // Flag to save the received data to a file
+    bool just_started_bytes;                  // Indicates if bytes data reception has just started
+    size_t bytes_received;                    // Number of bytes received
+    char rx_line_buffer[RX_LINE_BUFFER_SIZE]; // Buffer for received lines
+    uint8_t file_buffer[FILE_BUFFER_SIZE];    // Buffer for file data
+    size_t file_buffer_len;                   // Length of the file buffer
+    size_t content_length;                    // Length of the content received
+    int status_code;                          // HTTP status code
 } FlipperHTTP;
 } FlipperHTTP;
 
 
-// fhttp.last_response holds the last received data from the UART
-
-// Function to append received data to file
-// make sure to initialize the file path before calling this function
-bool flipper_http_append_to_file(
-    const void *data,
-    size_t data_size,
-    bool start_new_file,
-    char *file_path);
-
-FuriString *flipper_http_load_from_file(char *file_path);
-FuriString *flipper_http_load_from_file_with_limit(char *file_path, size_t limit);
-
-// UART worker thread
-/**
- * @brief      Worker thread to handle UART data asynchronously.
- * @return     0
- * @param      context   The context to pass to the callback.
- * @note       This function will handle received data asynchronously via the callback.
- */
-// UART worker thread
-int32_t flipper_http_worker(void *context);
-
-// Timer callback function
-/**
- * @brief      Callback function for the GET timeout timer.
- * @return     0
- * @param      context   The context to pass to the callback.
- * @note       This function will be called when the GET request times out.
- */
-void get_timeout_timer_callback(void *context);
-
-// UART RX Handler Callback (Interrupt Context)
-/**
- * @brief      A private callback function to handle received data asynchronously.
- * @return     void
- * @param      handle    The UART handle.
- * @param      event     The event type.
- * @param      context   The context to pass to the callback.
- * @note       This function will handle received data asynchronously via the callback.
- */
-void _flipper_http_rx_callback(
-    FuriHalSerialHandle *handle,
-    FuriHalSerialRxEvent event,
-    void *context);
-
-// UART initialization function
 /**
 /**
  * @brief      Initialize UART.
  * @brief      Initialize UART.
  * @return     FlipperHTTP context if the UART was initialized successfully, NULL otherwise.
  * @return     FlipperHTTP context if the UART was initialized successfully, NULL otherwise.
@@ -153,7 +112,6 @@ void _flipper_http_rx_callback(
  */
  */
 FlipperHTTP *flipper_http_alloc();
 FlipperHTTP *flipper_http_alloc();
 
 
-// Deinitialize UART
 /**
 /**
  * @brief      Deinitialize UART.
  * @brief      Deinitialize UART.
  * @return     void
  * @return     void
@@ -162,55 +120,49 @@ FlipperHTTP *flipper_http_alloc();
  */
  */
 void flipper_http_free(FlipperHTTP *fhttp);
 void flipper_http_free(FlipperHTTP *fhttp);
 
 
-// Function to send data over UART with newline termination
-/**
- * @brief      Send data over UART with newline termination.
- * @return     true if the data was sent successfully, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @param      data  The data to send over UART.
- * @note       The data will be sent over UART with a newline character appended.
- */
-bool flipper_http_send_data(FlipperHTTP *fhttp, const char *data);
-
-// Function to send a PING request
 /**
 /**
- * @brief      Send a PING request to check if the Wifi Dev Board is connected.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- * @note       This is best used to check if the Wifi Dev Board is connected.
- * @note       The state will remain INACTIVE until a PONG is received.
+ * @brief      Append received data to a file.
+ * @return     true if the data was appended successfully, false otherwise.
+ * @param      data        The data to append to the file.
+ * @param      data_size   The size of the data to append.
+ * @param      start_new_file  Flag to indicate if a new file should be created.
+ * @param      file_path   The path to the file.
+ * @note       Make sure to initialize the file path before calling this function.
  */
  */
-bool flipper_http_ping(FlipperHTTP *fhttp);
+bool flipper_http_append_to_file(const void *data, size_t data_size, bool start_new_file, char *file_path);
 
 
-// Function to list available commands
 /**
 /**
- * @brief      Send a command to list available commands.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
+ * @brief      Load data from a file.
+ * @return     The loaded data as a FuriString.
+ * @param      file_path The path to the file to load.
  */
  */
-bool flipper_http_list_commands(FlipperHTTP *fhttp);
+FuriString *flipper_http_load_from_file(char *file_path);
 
 
-// Function to turn on the LED
 /**
 /**
- * @brief      Allow the LED to display while processing.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
+ * @brief      Load data from a file with a size limit.
+ * @return     The loaded data as a FuriString.
+ * @param      file_path The path to the file to load.
+ * @param      limit     The size limit for loading data.
  */
  */
-bool flipper_http_led_on(FlipperHTTP *fhttp);
+FuriString *flipper_http_load_from_file_with_limit(char *file_path, size_t limit);
 
 
-// Function to turn off the LED
 /**
 /**
- * @brief      Disable the LED from displaying while processing.
- * @return     true if the request was successful, false otherwise.
+ * @brief Perform a task while displaying a loading screen
  * @param fhttp The FlipperHTTP context
  * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
+ * @param http_request The function to send the request
+ * @param parse_response The function to parse the response
+ * @param success_view_id The view ID to switch to on success
+ * @param failure_view_id The view ID to switch to on failure
+ * @param view_dispatcher The view dispatcher to use
+ * @return
  */
  */
-bool flipper_http_led_off(FlipperHTTP *fhttp);
+void flipper_http_loading_task(FlipperHTTP *fhttp,
+                               bool (*http_request)(void),
+                               bool (*parse_response)(void),
+                               uint32_t success_view_id,
+                               uint32_t failure_view_id,
+                               ViewDispatcher **view_dispatcher);
 
 
-// Function to parse JSON data
 /**
 /**
  * @brief      Parse JSON data.
  * @brief      Parse JSON data.
  * @return     true if the JSON data was parsed successfully, false otherwise.
  * @return     true if the JSON data was parsed successfully, false otherwise.
@@ -221,7 +173,6 @@ bool flipper_http_led_off(FlipperHTTP *fhttp);
  */
  */
 bool flipper_http_parse_json(FlipperHTTP *fhttp, const char *key, const char *json_data);
 bool flipper_http_parse_json(FlipperHTTP *fhttp, const char *key, const char *json_data);
 
 
-// Function to parse JSON array data
 /**
 /**
  * @brief      Parse JSON array data.
  * @brief      Parse JSON array data.
  * @return     true if the JSON array data was parsed successfully, false otherwise.
  * @return     true if the JSON array data was parsed successfully, false otherwise.
@@ -233,184 +184,68 @@ bool flipper_http_parse_json(FlipperHTTP *fhttp, const char *key, const char *js
  */
  */
 bool flipper_http_parse_json_array(FlipperHTTP *fhttp, const char *key, int index, const char *json_data);
 bool flipper_http_parse_json_array(FlipperHTTP *fhttp, const char *key, int index, const char *json_data);
 
 
-// Function to scan for WiFi networks
-/**
- * @brief      Send a command to scan for WiFi networks.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- */
-bool flipper_http_scan_wifi(FlipperHTTP *fhttp);
-
-// Function to save WiFi settings (returns true if successful)
-/**
- * @brief      Send a command to save WiFi settings.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- */
-bool flipper_http_save_wifi(FlipperHTTP *fhttp, const char *ssid, const char *password);
-
-// Function to get IP address of WiFi Devboard
-/**
- * @brief      Send a command to get the IP address of the WiFi Devboard
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- */
-bool flipper_http_ip_address(FlipperHTTP *fhttp);
-
-// Function to get IP address of the connected WiFi network
-/**
- * @brief      Send a command to get the IP address of the connected WiFi network.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- */
-bool flipper_http_ip_wifi(FlipperHTTP *fhttp);
-
-// Function to disconnect from WiFi (returns true if successful)
-/**
- * @brief      Send a command to disconnect from WiFi.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- */
-bool flipper_http_disconnect_wifi(FlipperHTTP *fhttp);
-
-// Function to connect to WiFi (returns true if successful)
-/**
- * @brief      Send a command to connect to WiFi.
- * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @note       The received data will be handled asynchronously via the callback.
- */
-bool flipper_http_connect_wifi(FlipperHTTP *fhttp);
-
-// Function to send a GET request
 /**
 /**
- * @brief      Send a GET request to the specified URL.
- * @return     true if the request was successful, false otherwise.
+ * @brief Process requests and parse JSON data asynchronously
  * @param fhttp The FlipperHTTP context
  * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the GET request to.
- * @note       The received data will be handled asynchronously via the callback.
+ * @param http_request The function to send the request
+ * @param parse_json The function to parse the JSON
+ * @return true if successful, false otherwise
  */
  */
-bool flipper_http_get_request(FlipperHTTP *fhttp, const char *url);
+bool flipper_http_process_response_async(FlipperHTTP *fhttp, bool (*http_request)(void), bool (*parse_json)(void));
 
 
-// Function to send a GET request with headers
 /**
 /**
- * @brief      Send a GET request to the specified URL.
+ * @brief      Send a request to the specified URL.
  * @return     true if the request was successful, false otherwise.
  * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the GET request to.
- * @param      headers  The headers to send with the GET request.
+ * @param      fhttp The FlipperHTTP context
+ * @param      method The HTTP method to use.
+ * @param      url  The URL to send the request to.
+ * @param      headers  The headers to send with the request.
+ * @param      payload  The data to send with the request.
  * @note       The received data will be handled asynchronously via the callback.
  * @note       The received data will be handled asynchronously via the callback.
  */
  */
-bool flipper_http_get_request_with_headers(FlipperHTTP *fhttp, const char *url, const char *headers);
+bool flipper_http_request(FlipperHTTP *fhttp, HTTPMethod method, const char *url, const char *headers, const char *payload);
 
 
-// Function to send a GET request with headers and return bytes
 /**
 /**
- * @brief      Send a GET request to the specified URL.
+ * @brief      Send a command to save WiFi settings.
  * @return     true if the request was successful, false otherwise.
  * @return     true if the request was successful, false otherwise.
  * @param fhttp The FlipperHTTP context
  * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the GET request to.
- * @param      headers  The headers to send with the GET request.
  * @note       The received data will be handled asynchronously via the callback.
  * @note       The received data will be handled asynchronously via the callback.
  */
  */
-bool flipper_http_get_request_bytes(FlipperHTTP *fhttp, const char *url, const char *headers);
+bool flipper_http_save_wifi(FlipperHTTP *fhttp, const char *ssid, const char *password);
 
 
-// Function to send a POST request with headers
 /**
 /**
- * @brief      Send a POST request to the specified URL.
+ * @brief      Send a command.
  * @return     true if the request was successful, false otherwise.
  * @return     true if the request was successful, false otherwise.
- * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the POST request to.
- * @param      headers  The headers to send with the POST request.
- * @param      data  The data to send with the POST request.
+ * @param      fhttp The FlipperHTTP context
+ * @param      command The command to send.
  * @note       The received data will be handled asynchronously via the callback.
  * @note       The received data will be handled asynchronously via the callback.
  */
  */
-bool flipper_http_post_request_with_headers(
-    FlipperHTTP *fhttp,
-    const char *url,
-    const char *headers,
-    const char *payload);
+bool flipper_http_send_command(FlipperHTTP *fhttp, HTTPCommand command);
 
 
-// Function to send a POST request with headers and return bytes
 /**
 /**
- * @brief      Send a POST request to the specified URL.
- * @return     true if the request was successful, false otherwise.
+ * @brief      Send data over UART with newline termination.
+ * @return     true if the data was sent successfully, false otherwise.
  * @param fhttp The FlipperHTTP context
  * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the POST request to.
- * @param      headers  The headers to send with the POST request.
- * @param      payload  The data to send with the POST request.
- * @note       The received data will be handled asynchronously via the callback.
+ * @param      data  The data to send over UART.
+ * @note       The data will be sent over UART with a newline character appended.
  */
  */
-bool flipper_http_post_request_bytes(FlipperHTTP *fhttp, const char *url, const char *headers, const char *payload);
+bool flipper_http_send_data(FlipperHTTP *fhttp, const char *data);
 
 
-// Function to send a PUT request with headers
 /**
 /**
- * @brief      Send a PUT request to the specified URL.
+ * @brief      Send a request to the specified URL to start a WebSocket connection.
  * @return     true if the request was successful, false otherwise.
  * @return     true if the request was successful, false otherwise.
  * @param fhttp The FlipperHTTP context
  * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the PUT request to.
- * @param      headers  The headers to send with the PUT request.
- * @param      data  The data to send with the PUT request.
+ * @param      url  The URL to send the WebSocket request to.
+ * @param port The port to connect to
+ * @param headers The headers to send with the WebSocket request
  * @note       The received data will be handled asynchronously via the callback.
  * @note       The received data will be handled asynchronously via the callback.
  */
  */
-bool flipper_http_put_request_with_headers(
-    FlipperHTTP *fhttp,
-    const char *url,
-    const char *headers,
-    const char *payload);
+bool flipper_http_websocket_start(FlipperHTTP *fhttp, const char *url, uint16_t port, const char *headers);
 
 
-// Function to send a DELETE request with headers
 /**
 /**
- * @brief      Send a DELETE request to the specified URL.
+ * @brief      Send a request to stop the WebSocket connection.
  * @return     true if the request was successful, false otherwise.
  * @return     true if the request was successful, false otherwise.
  * @param fhttp The FlipperHTTP context
  * @param fhttp The FlipperHTTP context
- * @param      url  The URL to send the DELETE request to.
- * @param      headers  The headers to send with the DELETE request.
- * @param      data  The data to send with the DELETE request.
  * @note       The received data will be handled asynchronously via the callback.
  * @note       The received data will be handled asynchronously via the callback.
  */
  */
-bool flipper_http_delete_request_with_headers(
-    FlipperHTTP *fhttp,
-    const char *url,
-    const char *headers,
-    const char *payload);
-
-// Function to handle received data asynchronously
-/**
- * @brief      Callback function to handle received data asynchronously.
- * @return     void
- * @param      line     The received line.
- * @param      context  The FlipperHTTP context.
- * @note       The received data will be handled asynchronously via the callback and handles the state of the UART.
- */
-void flipper_http_rx_callback(const char *line, void *context);
-
-/**
- * @brief Process requests and parse JSON data asynchronously
- * @param fhttp The FlipperHTTP context
- * @param http_request The function to send the request
- * @param parse_json The function to parse the JSON
- * @return true if successful, false otherwise
- */
-bool flipper_http_process_response_async(FlipperHTTP *fhttp, bool (*http_request)(void), bool (*parse_json)(void));
-
-/**
- * @brief Perform a task while displaying a loading screen
- * @param fhttp The FlipperHTTP context
- * @param http_request The function to send the request
- * @param parse_response The function to parse the response
- * @param success_view_id The view ID to switch to on success
- * @param failure_view_id The view ID to switch to on failure
- * @param view_dispatcher The view dispatcher to use
- * @return
- */
-void flipper_http_loading_task(FlipperHTTP *fhttp,
-                               bool (*http_request)(void),
-                               bool (*parse_response)(void),
-                               uint32_t success_view_id,
-                               uint32_t failure_view_id,
-                               ViewDispatcher **view_dispatcher);
+bool flipper_http_websocket_stop(FlipperHTTP *fhttp);

+ 68 - 65
flip_world/game/draw.c

@@ -95,69 +95,79 @@ static void draw_menu(GameManager *manager, Canvas *canvas)
         0,
         0,
         &I_icon_menu_128x64px);
         &I_icon_menu_128x64px);
 
 
-    // draw menu options
-    switch (game_context->menu_screen)
+    if (game_context->game_mode == GAME_MODE_STORY)
     {
     {
-    case GAME_MENU_INFO:
-        // draw info
-        // first option is highlighted
-        char health[32];
-        char xp[32];
-        char level[32];
-        char strength[32];
-
-        snprintf(level, sizeof(level), "Level   : %ld", game_context->player_context->level);
-        snprintf(health, sizeof(health), "Health  : %ld", game_context->player_context->health);
-        snprintf(xp, sizeof(xp), "XP      : %ld", game_context->player_context->xp);
-        snprintf(strength, sizeof(strength), "Strength: %ld", game_context->player_context->strength);
         canvas_set_font(canvas, FontPrimary);
         canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str(canvas, 7, 16, game_context->player_context->username);
+        canvas_draw_str(canvas, 45, 15, "Tutorial");
         canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
         canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
-        canvas_draw_str(canvas, 7, 30, level);
-        canvas_draw_str(canvas, 7, 37, health);
-        canvas_draw_str(canvas, 7, 44, xp);
-        canvas_draw_str(canvas, 7, 51, strength);
-
-        // draw a box around the selected option
-        canvas_draw_frame(canvas, 80, 18, 36, 30);
-        canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str(canvas, 86, 30, "Info");
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 86, 42, "More");
-        break;
-    case GAME_MENU_MORE:
-        // draw settings
-        switch (game_context->menu_selection)
+        canvas_draw_str(canvas, 24, 35, "Press BACK to exit");
+    }
+    else
+    {
+        // draw menu options
+        switch (game_context->menu_screen)
         {
         {
-        case 0:
+        case GAME_MENU_INFO:
+            // draw info
             // first option is highlighted
             // first option is highlighted
+            char health[32];
+            char xp[32];
+            char level[32];
+            char strength[32];
+
+            snprintf(level, sizeof(level), "Level   : %ld", game_context->player_context->level);
+            snprintf(health, sizeof(health), "Health  : %ld", game_context->player_context->health);
+            snprintf(xp, sizeof(xp), "XP      : %ld", game_context->player_context->xp);
+            snprintf(strength, sizeof(strength), "Strength: %ld", game_context->player_context->strength);
+            canvas_set_font(canvas, FontPrimary);
+            canvas_draw_str(canvas, 7, 16, game_context->player_context->username);
+            canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+            canvas_draw_str(canvas, 7, 30, level);
+            canvas_draw_str(canvas, 7, 37, health);
+            canvas_draw_str(canvas, 7, 44, xp);
+            canvas_draw_str(canvas, 7, 51, strength);
+
+            // draw a box around the selected option
+            canvas_draw_frame(canvas, 80, 18, 36, 30);
+            canvas_set_font(canvas, FontPrimary);
+            canvas_draw_str(canvas, 86, 30, "Info");
+            canvas_set_font(canvas, FontSecondary);
+            canvas_draw_str(canvas, 86, 42, "More");
+            break;
+        case GAME_MENU_MORE:
+            // draw settings
+            switch (game_context->menu_selection)
+            {
+            case 0:
+                // first option is highlighted
+                break;
+            case 1:
+                // second option is highlighted
+                break;
+            default:
+                break;
+            }
+
+            canvas_set_font(canvas, FontPrimary);
+            canvas_draw_str(canvas, 7, 16, VERSION_TAG);
+            canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+            canvas_draw_str_multi(canvas, 7, 25, "Developed by\nJBlanked and Derek \nJamison. Graphics\nfrom Pr3!\n\nwww.github.com/jblanked");
+
+            // draw a box around the selected option
+            canvas_draw_frame(canvas, 80, 18, 36, 30);
+            canvas_set_font(canvas, FontSecondary);
+            canvas_draw_str(canvas, 86, 30, "Info");
+            canvas_set_font(canvas, FontPrimary);
+            canvas_draw_str(canvas, 86, 42, "More");
             break;
             break;
-        case 1:
-            // second option is highlighted
+        case GAME_MENU_NPC:
+            // draw NPC dialog
+            canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+            canvas_draw_str(canvas, 7, 16, game_context->message);
             break;
             break;
         default:
         default:
             break;
             break;
         }
         }
-
-        canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str(canvas, 7, 16, VERSION_TAG);
-        canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
-        canvas_draw_str_multi(canvas, 7, 25, "Developed by\nJBlanked and Derek \nJamison. Graphics\nfrom Pr3!\n\nwww.github.com/jblanked");
-
-        // draw a box around the selected option
-        canvas_draw_frame(canvas, 80, 18, 36, 30);
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 86, 30, "Info");
-        canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str(canvas, 86, 42, "More");
-        break;
-    case GAME_MENU_NPC:
-        // draw NPC dialog
-        canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
-        canvas_draw_str(canvas, 7, 16, game_context->message);
-        break;
-    default:
-        break;
     }
     }
 }
 }
 
 
@@ -169,25 +179,18 @@ void background_render(Canvas *canvas, GameManager *manager)
     GameContext *game_context = game_manager_game_context_get(manager);
     GameContext *game_context = game_manager_game_context_get(manager);
     if (!game_context->is_menu_open)
     if (!game_context->is_menu_open)
     {
     {
-
         // get player position
         // get player position
         Vector posi = entity_pos_get(game_context->player);
         Vector posi = entity_pos_get(game_context->player);
 
 
         // draw username over player's head
         // draw username over player's head
         draw_username(canvas, posi, game_context->player_context->username);
         draw_username(canvas, posi, game_context->player_context->username);
 
 
-        // draw switch world icon
         if (game_context->is_switching_level)
         if (game_context->is_switching_level)
-        {
-            canvas_draw_icon(
-                canvas,
-                0,
-                0,
-                &I_icon_world_change_128x64px);
-        }
-
-        // Draw user stats
-        draw_user_stats(canvas, (Vector){0, 50}, manager);
+            // draw switch world icon
+            canvas_draw_icon(canvas, 0, 0, &I_icon_world_change_128x64px);
+        else
+            // Draw user stats
+            draw_user_stats(canvas, (Vector){0, 50}, manager);
     }
     }
     else
     else
     {
     {

+ 38 - 22
flip_world/game/enemy.c

@@ -95,6 +95,7 @@ static void enemy_render(Entity *self, GameManager *manager, Canvas *canvas, voi
         return;
         return;
 
 
     EntityContext *enemy_context = (EntityContext *)context;
     EntityContext *enemy_context = (EntityContext *)context;
+    GameContext *game_context = game_manager_game_context_get(manager);
 
 
     // Get the position of the enemy
     // Get the position of the enemy
     Vector pos = entity_pos_get(self);
     Vector pos = entity_pos_get(self);
@@ -118,17 +119,22 @@ static void enemy_render(Entity *self, GameManager *manager, Canvas *canvas, voi
         current_sprite = enemy_context->sprite_right;
         current_sprite = enemy_context->sprite_right;
     }
     }
 
 
-    // Draw enemy sprite relative to camera, centered on the enemy's position
-    canvas_draw_sprite(
-        canvas,
-        current_sprite,
-        pos.x - camera_x - (enemy_context->size.x / 2),
-        pos.y - camera_y - (enemy_context->size.y / 2));
-
-    // draw health of enemy
-    char health_str[32];
-    snprintf(health_str, sizeof(health_str), "%.0f", (double)enemy_context->health);
-    draw_username(canvas, pos, health_str);
+    // no enemies in story mode for now
+    if (game_context->game_mode != GAME_MODE_STORY || (game_context->game_mode == GAME_MODE_STORY && game_context->tutorial_step == 4))
+    {
+
+        // Draw enemy sprite relative to camera, centered on the enemy's position
+        canvas_draw_sprite(
+            canvas,
+            current_sprite,
+            pos.x - camera_x - (enemy_context->size.x / 2),
+            pos.y - camera_y - (enemy_context->size.y / 2));
+
+        // draw health of enemy
+        char health_str[32];
+        snprintf(health_str, sizeof(health_str), "%.0f", (double)enemy_context->health);
+        draw_username(canvas, pos, health_str);
+    }
 }
 }
 
 
 static void atk_notify(GameContext *game_context, EntityContext *enemy_context, bool player_attacked)
 static void atk_notify(GameContext *game_context, EntityContext *enemy_context, bool player_attacked)
@@ -204,6 +210,11 @@ static void enemy_collision(Entity *self, Entity *other, GameManager *manager, v
     furi_check(enemy_context, "Enemy collision: EntityContext is NULL");
     furi_check(enemy_context, "Enemy collision: EntityContext is NULL");
     GameContext *game_context = game_manager_game_context_get(manager);
     GameContext *game_context = game_manager_game_context_get(manager);
     furi_check(game_context, "Enemy collision: GameContext is NULL");
     furi_check(game_context, "Enemy collision: GameContext is NULL");
+    if (game_context->game_mode == GAME_MODE_STORY && game_context->tutorial_step != 4)
+    {
+        // FURI_LOG_I("Game", "Enemy collision: No enemies in story mode");
+        return;
+    }
     // Check if the enemy collided with the player
     // Check if the enemy collided with the player
     if (entity_description_get(other) == &player_desc)
     if (entity_description_get(other) == &player_desc)
     {
     {
@@ -237,6 +248,11 @@ static void enemy_collision(Entity *self, Entity *other, GameManager *manager, v
         // Handle Player Attacking Enemy (Press OK, facing enemy, and enemy not facing player)
         // Handle Player Attacking Enemy (Press OK, facing enemy, and enemy not facing player)
         if (player_is_facing_enemy && game_context->last_button == GameKeyOk && !enemy_is_facing_player)
         if (player_is_facing_enemy && game_context->last_button == GameKeyOk && !enemy_is_facing_player)
         {
         {
+            if (game_context->game_mode == GAME_MODE_STORY && game_context->tutorial_step == 4)
+            {
+                // FURI_LOG_I("Game", "Player attacked enemy '%s'!", enemy_context->id);
+                game_context->tutorial_step++;
+            }
             // Reset last button
             // Reset last button
             game_context->last_button = -1;
             game_context->last_button = -1;
 
 
@@ -263,7 +279,6 @@ static void enemy_collision(Entity *self, Entity *other, GameManager *manager, v
 
 
                 if (enemy_context->health <= 0)
                 if (enemy_context->health <= 0)
                 {
                 {
-                    FURI_LOG_I("Game", "Enemy '%s' is dead.. resetting enemy position and health", enemy_context->id);
                     enemy_context->state = ENTITY_DEAD;
                     enemy_context->state = ENTITY_DEAD;
 
 
                     // Reset enemy position and health
                     // Reset enemy position and health
@@ -278,12 +293,11 @@ static void enemy_collision(Entity *self, Entity *other, GameManager *manager, v
                 }
                 }
                 else
                 else
                 {
                 {
-                    FURI_LOG_I("Game", "Enemy '%s' took %f damage from player", enemy_context->id, (double)game_context->player_context->strength);
                     enemy_context->state = ENTITY_ATTACKED;
                     enemy_context->state = ENTITY_ATTACKED;
                     // Vector old_pos = entity_pos_get(self);
                     // Vector old_pos = entity_pos_get(self);
                     //  Bounce the enemy back by X units opposite their last movement direction
                     //  Bounce the enemy back by X units opposite their last movement direction
                     enemy_pos.x -= game_context->player_context->dx * enemy_context->radius + game_context->icon_offset;
                     enemy_pos.x -= game_context->player_context->dx * enemy_context->radius + game_context->icon_offset;
-                    enemy_pos.y -= game_context->player_context->dy * enemy_context->radius + game_context->icon_offset;
+                    // enemy_pos.y -= game_context->player_context->dy * enemy_context->radius + game_context->icon_offset;
                     entity_pos_set(self, enemy_pos);
                     entity_pos_set(self, enemy_pos);
 
 
                     // Reset enemy's movement direction to prevent immediate re-collision
                     // Reset enemy's movement direction to prevent immediate re-collision
@@ -332,7 +346,7 @@ static void enemy_collision(Entity *self, Entity *other, GameManager *manager, v
 
 
                     // Bounce the player back by X units opposite their last movement direction
                     // Bounce the player back by X units opposite their last movement direction
                     player_pos.x -= game_context->player_context->dx * enemy_context->radius + game_context->icon_offset;
                     player_pos.x -= game_context->player_context->dx * enemy_context->radius + game_context->icon_offset;
-                    player_pos.y -= game_context->player_context->dy * enemy_context->radius + game_context->icon_offset;
+                    // player_pos.y -= game_context->player_context->dy * enemy_context->radius + game_context->icon_offset;
                     entity_pos_set(other, player_pos);
                     entity_pos_set(other, player_pos);
 
 
                     // Reset player's movement direction to prevent immediate re-collision
                     // Reset player's movement direction to prevent immediate re-collision
@@ -350,10 +364,6 @@ static void enemy_collision(Entity *self, Entity *other, GameManager *manager, v
             game_context->player_context->dy = 0;
             game_context->player_context->dy = 0;
         }
         }
 
 
-        // Reset enemy's state
-        enemy_context->state = ENTITY_IDLE;
-        enemy_context->elapsed_move_timer = 0.0f;
-
         if (game_context->player_context->state == ENTITY_DEAD)
         if (game_context->player_context->state == ENTITY_DEAD)
         {
         {
             // Reset player's position and health
             // Reset player's position and health
@@ -437,12 +447,18 @@ static void enemy_update(Entity *self, GameManager *manager, void *context)
 
 
     case ENTITY_MOVING_TO_END:
     case ENTITY_MOVING_TO_END:
     case ENTITY_MOVING_TO_START:
     case ENTITY_MOVING_TO_START:
+    case ENTITY_ATTACKED:
     {
     {
-        // Determine the target position based on the current state
-        Vector target_position = (enemy_context->state == ENTITY_MOVING_TO_END) ? enemy_context->end_position : enemy_context->start_position;
-
         // Get current position
         // Get current position
         Vector current_pos = entity_pos_get(self);
         Vector current_pos = entity_pos_get(self);
+        if (enemy_context->state == ENTITY_ATTACKED)
+        {
+            // set direction again
+            enemy_context->state = enemy_context->direction == ENTITY_LEFT ? ENTITY_MOVING_TO_START : ENTITY_MOVING_TO_END;
+        }
+
+        // Determine the target position based on the current state
+        Vector target_position = (enemy_context->state == ENTITY_MOVING_TO_END) ? enemy_context->end_position : enemy_context->start_position;
         Vector direction_vector = {0, 0};
         Vector direction_vector = {0, 0};
 
 
         // Calculate direction towards the target
         // Calculate direction towards the target

+ 49 - 19
flip_world/game/game.c

@@ -21,6 +21,8 @@ static void game_start(GameManager *game_manager, void *ctx)
     game_context->enemy_count = 0;
     game_context->enemy_count = 0;
     game_context->npc_count = 0;
     game_context->npc_count = 0;
 
 
+    game_context->game_mode = game_mode_index;
+
     // set all levels to NULL
     // set all levels to NULL
     for (int i = 0; i < MAX_LEVELS; i++)
     for (int i = 0; i < MAX_LEVELS; i++)
         game_context->levels[i] = NULL;
         game_context->levels[i] = NULL;
@@ -33,20 +35,34 @@ static void game_start(GameManager *game_manager, void *ctx)
     for (int i = 0; i < MAX_NPCS; i++)
     for (int i = 0; i < MAX_NPCS; i++)
         game_context->npcs[i] = NULL;
         game_context->npcs[i] = NULL;
 
 
-    // attempt to allocate all levels
-    for (int i = 0; i < MAX_LEVELS; i++)
+    if (game_context->game_mode == GAME_MODE_PVE)
     {
     {
-        if (!allocate_level(game_manager, i))
+        // attempt to allocate all levels
+        for (int i = 0; i < MAX_LEVELS; i++)
         {
         {
-            if (i == 0)
+            if (!allocate_level(game_manager, i))
             {
             {
-                game_context->levels[0] = game_manager_add_level(game_manager, training_world());
-                game_context->level_count = 1;
+                if (i == 0)
+                {
+                    game_context->levels[0] = game_manager_add_level(game_manager, training_world());
+                    game_context->level_count = 1;
+                }
+                break;
             }
             }
-            break;
+            else
+                game_context->level_count++;
         }
         }
-        else
-            game_context->level_count++;
+    }
+    else if (game_context->game_mode == GAME_MODE_STORY)
+    {
+        // show tutorial only for now
+        game_context->levels[0] = game_manager_add_level(game_manager, training_world());
+        game_context->level_count = 1;
+    }
+    else if (game_context->game_mode == GAME_MODE_PVP)
+    {
+        // show pvp menu
+        easy_flipper_dialog("Unavailable", "\nPvP mode is not ready yet.\nPress BACK to return.");
     }
     }
 
 
     // imu
     // imu
@@ -82,16 +98,23 @@ static void game_stop(void *ctx)
         level_clear(game_context->levels[game_context->current_level]);
         level_clear(game_context->levels[game_context->current_level]);
     }
     }
 
 
-    if (game_context->player_context)
+    PlayerContext *player_context = malloc(sizeof(PlayerContext));
+    if (!player_context)
     {
     {
-        if (!game_context->ended_early)
-            easy_flipper_dialog(
-                "Game Over",
-                "Thanks for playing FlipWorld!\nHit BACK then wait for\nthe game to save.");
-        else
-            easy_flipper_dialog(
-                "Game Over", "Ran out of memory so the\ngame ended early.\nHit BACK to exit.");
+        FURI_LOG_E("Game", "Failed to allocate PlayerContext");
+        return;
+    }
+
+    if (!game_context->ended_early)
+        easy_flipper_dialog(
+            "Game Over",
+            "Thanks for playing FlipWorld!\nHit BACK then wait for\nthe game to save.");
+    else
+        easy_flipper_dialog(
+            "Game Over", "Ran out of memory so the\ngame ended early.\nHit BACK to exit.");
 
 
+    if (load_player_context(player_context))
+    {
         ViewPort *view_port = view_port_alloc();
         ViewPort *view_port = view_port_alloc();
         view_port_draw_callback_set(view_port, thanks, NULL);
         view_port_draw_callback_set(view_port, thanks, NULL);
         Gui *gui = furi_record_open(RECORD_GUI);
         Gui *gui = furi_record_open(RECORD_GUI);
@@ -99,9 +122,9 @@ static void game_stop(void *ctx)
         uint32_t tick_count = furi_get_tick();
         uint32_t tick_count = furi_get_tick();
         furi_delay_ms(800);
         furi_delay_ms(800);
 
 
-        save_player_context_api(game_context->player_context);
+        save_player_context_api(player_context);
 
 
-        const uint32_t delay = 2500;
+        const uint32_t delay = 3500;
         tick_count = (tick_count + delay) - furi_get_tick();
         tick_count = (tick_count + delay) - furi_get_tick();
         if (tick_count <= delay)
         if (tick_count <= delay)
         {
         {
@@ -115,6 +138,13 @@ static void game_stop(void *ctx)
         gui_remove_view_port(gui, view_port);
         gui_remove_view_port(gui, view_port);
         furi_record_close(RECORD_GUI);
         furi_record_close(RECORD_GUI);
     }
     }
+
+    // free the player context
+    if (player_context)
+    {
+        free(player_context);
+        player_context = NULL;
+    }
 }
 }
 
 
 /*
 /*

+ 2 - 7
flip_world/game/level.c

@@ -182,16 +182,11 @@ static void level_start(Level *level, GameManager *manager, void *context)
         // furi_delay_ms(1000);
         // furi_delay_ms(1000);
         game_context->is_switching_level = false;
         game_context->is_switching_level = false;
     }
     }
-    /*
-       adjust the player's position n such based on icon count
-       the more icons to draw, the slower the player moves
-       so we'll increase the player's speed as the icon count increases
-       by 0.1 for every 8 icons
-   */
+
     game_context->icon_offset = 0;
     game_context->icon_offset = 0;
     if (!game_context->imu_present)
     if (!game_context->imu_present)
     {
     {
-        game_context->icon_offset += ((game_context->icon_count / 8) / 10);
+        game_context->icon_offset += ((game_context->icon_count / 10) / 15);
     }
     }
     player_spawn(level, manager);
     player_spawn(level, manager);
 }
 }

+ 11 - 7
flip_world/game/npc.c

@@ -86,6 +86,7 @@ static void npc_render(Entity *self, GameManager *manager, Canvas *canvas, void
         return;
         return;
 
 
     EntityContext *npc_context = (EntityContext *)context;
     EntityContext *npc_context = (EntityContext *)context;
+    GameContext *game_context = game_manager_game_context_get(manager);
 
 
     // Get the position of the NPC
     // Get the position of the NPC
     Vector pos = entity_pos_get(self);
     Vector pos = entity_pos_get(self);
@@ -107,13 +108,16 @@ static void npc_render(Entity *self, GameManager *manager, Canvas *canvas, void
     {
     {
         current_sprite = npc_context->sprite_right;
         current_sprite = npc_context->sprite_right;
     }
     }
-
-    // Draw NPC sprite relative to camera, centered on the NPC's position
-    canvas_draw_sprite(
-        canvas,
-        current_sprite,
-        pos.x - camera_x - (npc_context->size.x / 2),
-        pos.y - camera_y - (npc_context->size.y / 2));
+    // no NPCs in story mode for now
+    if (game_context->game_mode != GAME_MODE_STORY)
+    {
+        // Draw NPC sprite relative to camera, centered on the NPC's position
+        canvas_draw_sprite(
+            canvas,
+            current_sprite,
+            pos.x - camera_x - (npc_context->size.x / 2),
+            pos.y - camera_y - (npc_context->size.y / 2));
+    }
 }
 }
 
 
 // NPC collision function
 // NPC collision function

+ 136 - 21
flip_world/game/player.c

@@ -11,6 +11,7 @@ static Level *next_level(GameManager *manager)
     if (!game_context)
     if (!game_context)
     {
     {
         FURI_LOG_E(TAG, "Failed to get game context");
         FURI_LOG_E(TAG, "Failed to get game context");
+        game_context->is_switching_level = false;
         return NULL;
         return NULL;
     }
     }
     // check if there are more levels to load
     // check if there are more levels to load
@@ -22,9 +23,13 @@ static Level *next_level(GameManager *manager)
             if (!allocate_level(manager, game_context->current_level))
             if (!allocate_level(manager, game_context->current_level))
             {
             {
                 FURI_LOG_E(TAG, "Failed to allocate level %d", game_context->current_level);
                 FURI_LOG_E(TAG, "Failed to allocate level %d", game_context->current_level);
+                game_context->is_switching_level = false;
+                furi_delay_ms(100);
                 return NULL;
                 return NULL;
             }
             }
         }
         }
+        game_context->is_switching_level = false;
+        furi_delay_ms(100);
         return game_context->levels[game_context->current_level];
         return game_context->levels[game_context->current_level];
     }
     }
     for (int i = game_context->current_level + 1; i < game_context->level_count; i++)
     for (int i = game_context->current_level + 1; i < game_context->level_count; i++)
@@ -34,15 +39,36 @@ static Level *next_level(GameManager *manager)
             if (!allocate_level(manager, i))
             if (!allocate_level(manager, i))
             {
             {
                 FURI_LOG_E(TAG, "Failed to allocate level %d", i);
                 FURI_LOG_E(TAG, "Failed to allocate level %d", i);
+                game_context->is_switching_level = false;
+                furi_delay_ms(100);
                 return NULL;
                 return NULL;
             }
             }
         }
         }
         game_context->current_level = i;
         game_context->current_level = i;
+        game_context->is_switching_level = false;
+        furi_delay_ms(100);
         return game_context->levels[i];
         return game_context->levels[i];
     }
     }
+    game_context->is_switching_level = false;
+    furi_delay_ms(100);
     return NULL;
     return NULL;
 }
 }
 
 
+// Update player stats based on XP using iterative method
+static int get_player_level_iterative(uint32_t xp)
+{
+    int level = 1;
+    uint32_t xp_required = 100; // Base XP for level 2
+
+    while (level < 100 && xp >= xp_required) // Maximum level supported
+    {
+        level++;
+        xp_required = (uint32_t)(xp_required * 1.5); // 1.5 growth factor per level
+    }
+
+    return level;
+}
+
 void player_spawn(Level *level, GameManager *manager)
 void player_spawn(Level *level, GameManager *manager)
 {
 {
     if (!level || !manager)
     if (!level || !manager)
@@ -135,21 +161,6 @@ void player_spawn(Level *level, GameManager *manager)
 
 
     pctx->start_position = entity_pos_get(game_context->player);
     pctx->start_position = entity_pos_get(game_context->player);
 
 
-    // Update player stats based on XP using iterative method
-    int get_player_level_iterative(uint32_t xp)
-    {
-        int level = 1;
-        uint32_t xp_required = 100; // Base XP for level 2
-
-        while (level < 100 && xp >= xp_required) // Maximum level supported
-        {
-            level++;
-            xp_required = (uint32_t)(xp_required * 1.5); // 1.5 growth factor per level
-        }
-
-        return level;
-    }
-
     // Determine the player's level based on XP
     // Determine the player's level based on XP
     pctx->level = get_player_level_iterative(pctx->xp);
     pctx->level = get_player_level_iterative(pctx->xp);
 
 
@@ -214,6 +225,11 @@ static void player_update(Entity *self, GameManager *manager, void *context)
     player->old_position = pos;
     player->old_position = pos;
     GameContext *game_context = game_manager_game_context_get(manager);
     GameContext *game_context = game_manager_game_context_get(manager);
 
 
+    // Determine the player's level based on XP
+    player->level = get_player_level_iterative(player->xp);
+    player->strength = 10 + (player->level * 1);           // 1 strength per level
+    player->max_health = 100 + ((player->level - 1) * 10); // 10 health per level
+
     // Store previous direction
     // Store previous direction
     int prev_dx = player->dx;
     int prev_dx = player->dx;
     int prev_dy = player->dy;
     int prev_dy = player->dy;
@@ -251,7 +267,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
 
 
         if (!game_context->is_menu_open)
         if (!game_context->is_menu_open)
         {
         {
-            pos.y -= (2 + game_context->icon_offset);
+            pos.y -= (1 + game_context->icon_offset);
             player->dy = -1;
             player->dy = -1;
             player->direction = ENTITY_UP;
             player->direction = ENTITY_UP;
         }
         }
@@ -272,7 +288,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
 
 
         if (!game_context->is_menu_open)
         if (!game_context->is_menu_open)
         {
         {
-            pos.y += (2 + game_context->icon_offset);
+            pos.y += (1 + game_context->icon_offset);
             player->dy = 1;
             player->dy = 1;
             player->direction = ENTITY_DOWN;
             player->direction = ENTITY_DOWN;
         }
         }
@@ -293,7 +309,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
 
 
         if (!game_context->is_menu_open)
         if (!game_context->is_menu_open)
         {
         {
-            pos.x -= (2 + game_context->icon_offset);
+            pos.x -= (1 + game_context->icon_offset);
             player->dx = -1;
             player->dx = -1;
             player->direction = ENTITY_LEFT;
             player->direction = ENTITY_LEFT;
         }
         }
@@ -316,7 +332,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
 
 
         if (!game_context->is_menu_open)
         if (!game_context->is_menu_open)
         {
         {
-            pos.x += (2 + game_context->icon_offset);
+            pos.x += (1 + game_context->icon_offset);
             player->dx = 1;
             player->dx = 1;
             player->direction = ENTITY_RIGHT;
             player->direction = ENTITY_RIGHT;
         }
         }
@@ -345,6 +361,7 @@ static void player_update(Entity *self, GameManager *manager, void *context)
         {
         {
             game_context->is_switching_level = true;
             game_context->is_switching_level = true;
             save_player_context(player);
             save_player_context(player);
+            furi_delay_ms(100);
             game_manager_next_level_set(manager, next_level(manager));
             game_manager_next_level_set(manager, next_level(manager));
             return;
             return;
         }
         }
@@ -377,12 +394,50 @@ static void player_update(Entity *self, GameManager *manager, void *context)
         {
         {
             if (!game_context->is_menu_open)
             if (!game_context->is_menu_open)
             {
             {
+                save_player_context(player);
+                furi_delay_ms(100);
                 game_manager_game_stop(manager);
                 game_manager_game_stop(manager);
                 return;
                 return;
             }
             }
         }
         }
     }
     }
 
 
+    // adjust tutorial step
+    if (game_context->game_mode == GAME_MODE_STORY)
+    {
+        switch (game_context->tutorial_step)
+        {
+        case 0:
+            if (input.held & GameKeyLeft)
+                game_context->tutorial_step++;
+            break;
+        case 1:
+            if (input.held & GameKeyRight)
+                game_context->tutorial_step++;
+            break;
+        case 2:
+            if (input.held & GameKeyUp)
+                game_context->tutorial_step++;
+            break;
+        case 3:
+            if (input.held & GameKeyDown)
+                game_context->tutorial_step++;
+            break;
+        case 5:
+            if (input.held & GameKeyOk && game_context->is_menu_open)
+                game_context->tutorial_step++;
+            break;
+        case 6:
+            if (input.held & GameKeyBack)
+                game_context->tutorial_step++;
+            break;
+        case 7:
+            if (input.held & GameKeyBack)
+                game_context->tutorial_step++;
+            break;
+        }
+    }
+
     // Clamp the player's position to stay within world bounds
     // Clamp the player's position to stay within world bounds
     pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
     pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
     pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
     pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
@@ -401,11 +456,58 @@ static void player_update(Entity *self, GameManager *manager, void *context)
         player->state = ENTITY_MOVING;
         player->state = ENTITY_MOVING;
 }
 }
 
 
+static void draw_tutorial(Canvas *canvas, GameManager *manager)
+{
+    GameContext *game_context = game_manager_game_context_get(manager);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 45, 12, "Tutorial");
+    canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+    switch (game_context->tutorial_step)
+    {
+    case 0:
+        canvas_draw_str(canvas, 15, 20, "Press LEFT to move left");
+        break;
+    case 1:
+        canvas_draw_str(canvas, 15, 20, "Press RIGHT to move right");
+        break;
+    case 2:
+        canvas_draw_str(canvas, 15, 20, "Press UP to move up");
+        break;
+    case 3:
+        canvas_draw_str(canvas, 15, 20, "Press DOWN to move down");
+        break;
+    case 4:
+        canvas_draw_str(canvas, 0, 20, "Press OK + collide with an enemy to attack");
+        break;
+    case 5:
+        canvas_draw_str(canvas, 15, 20, "Hold OK to open the menu");
+        break;
+    case 6:
+        canvas_draw_str(canvas, 15, 20, "Press BACK to escape the menu");
+        break;
+    case 7:
+        canvas_draw_str(canvas, 15, 20, "Hold BACK to save and exit");
+        break;
+    case 8:
+        // end of tutorial so quit
+        game_context->tutorial_step = 0;
+        game_context->is_menu_open = false;
+        game_context->is_switching_level = true;
+        game_manager_game_stop(manager);
+        return;
+    default:
+        break;
+    }
+}
+
 static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
 static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
 {
 {
     if (!self || !context || !canvas || !manager)
     if (!self || !context || !canvas || !manager)
         return;
         return;
 
 
+    // Get game context
+    GameContext *game_context = game_manager_game_context_get(manager);
+
     // Get player context
     // Get player context
     PlayerContext *player = context;
     PlayerContext *player = context;
 
 
@@ -445,8 +547,21 @@ static void player_render(Entity *self, GameManager *manager, Canvas *canvas, vo
     // Draw the outer bounds adjusted by camera offset
     // Draw the outer bounds adjusted by camera offset
     canvas_draw_frame(canvas, -camera_x, -camera_y, WORLD_WIDTH, WORLD_HEIGHT);
     canvas_draw_frame(canvas, -camera_x, -camera_y, WORLD_WIDTH, WORLD_HEIGHT);
 
 
-    // render background
-    background_render(canvas, manager);
+    // render tutorial
+    if (game_context->game_mode == GAME_MODE_STORY)
+    {
+        draw_tutorial(canvas, manager);
+
+        if (game_context->is_menu_open)
+        {
+            background_render(canvas, manager);
+        }
+    }
+    else
+    {
+        // render background
+        background_render(canvas, manager);
+    }
 }
 }
 
 
 const EntityDescription player_desc = {
 const EntityDescription player_desc = {

+ 13 - 0
flip_world/game/player.h

@@ -62,6 +62,14 @@ typedef enum
     GAME_MENU_NPC,  // NPC dialog
     GAME_MENU_NPC,  // NPC dialog
 } GameMenuScreen;
 } GameMenuScreen;
 
 
+// game modes
+typedef enum
+{
+    GAME_MODE_PVE = 0,   // player(s) vs everyone
+    GAME_MODE_PVP = 1,   // player vs player
+    GAME_MODE_STORY = 2, // story mode
+} GameMode;
+
 typedef struct
 typedef struct
 {
 {
     PlayerContext *player_context;
     PlayerContext *player_context;
@@ -69,6 +77,7 @@ typedef struct
     Entity *enemies[MAX_ENEMIES];
     Entity *enemies[MAX_ENEMIES];
     Entity *npcs[MAX_NPCS];
     Entity *npcs[MAX_NPCS];
     Entity *player;
     Entity *player;
+    //
     float fps;
     float fps;
     int level_count;
     int level_count;
     int enemy_count;
     int enemy_count;
@@ -87,10 +96,14 @@ typedef struct
     GameMenuScreen menu_screen;
     GameMenuScreen menu_screen;
     uint8_t menu_selection;
     uint8_t menu_selection;
     //
     //
+    GameMode game_mode;
+    //
     int icon_count;
     int icon_count;
     int icon_offset;
     int icon_offset;
     //
     //
     char message[64];
     char message[64];
+    //
+    uint8_t tutorial_step;
 } GameContext;
 } GameContext;
 
 
 typedef struct
 typedef struct

+ 1 - 3
flip_world/game/storage.c

@@ -358,8 +358,6 @@ bool save_player_context_api(PlayerContext *player_context)
     // closing brace
     // closing brace
     furi_string_cat_str(json, "}");
     furi_string_cat_str(json, "}");
 
 
-    // save the json to API
-
     // create new JSON with username key (of just username), and game_stats key (of the all of the data)
     // create new JSON with username key (of just username), and game_stats key (of the all of the data)
     FuriString *json_data = furi_string_alloc();
     FuriString *json_data = furi_string_alloc();
     if (!json_data)
     if (!json_data)
@@ -378,7 +376,7 @@ bool save_player_context_api(PlayerContext *player_context)
     furi_string_free(json);
     furi_string_free(json);
 
 
     // save the json_data to the API
     // save the json_data to the API
-    if (!flipper_http_post_request_with_headers(fhttp, "https://www.flipsocial.net/api/user/update-game-stats/", "{\"Content-Type\":\"application/json\"}", furi_string_get_cstr(json_data)))
+    if (!flipper_http_request(fhttp, POST, "https://www.jblanked.com/flipper/api/user/update-game-stats/", "{\"Content-Type\": \"application/json\"}", furi_string_get_cstr(json_data)))
     {
     {
         FURI_LOG_E(TAG, "Failed to save player context to API");
         FURI_LOG_E(TAG, "Failed to save player context to API");
         furi_string_free(json_data);
         furi_string_free(json_data);

+ 3 - 9
flip_world/game/world.c

@@ -103,16 +103,10 @@ static void draw_town_world(Level *level, GameManager *manager, void *context)
     }
     }
     furi_string_free(json_data_str);
     furi_string_free(json_data_str);
     set_world(level, manager, "shadow_woods_v5");
     set_world(level, manager, "shadow_woods_v5");
-    /*
-      adjust the player's position n such based on icon count
-      the more icons to draw, the slower the player moves
-      so we'll increase the player's speed as the icon count increases
-      by 0.1 for every 8 icons
-  */
     game_context->icon_offset = 0;
     game_context->icon_offset = 0;
     if (!game_context->imu_present)
     if (!game_context->imu_present)
     {
     {
-        game_context->icon_offset += ((game_context->icon_count / 8) / 10);
+        game_context->icon_offset += ((game_context->icon_count / 10) / 15);
     }
     }
     player_spawn(level, manager);
     player_spawn(level, manager);
 }
 }
@@ -146,10 +140,10 @@ FuriString *fetch_world(const char *name)
     }
     }
 
 
     char url[256];
     char url[256];
-    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/world/v5/get/world/%s/", name);
+    snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/v5/get/world/%s/", name);
     snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", name);
     snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", name);
     fhttp->save_received_data = true;
     fhttp->save_received_data = true;
-    if (!flipper_http_get_request_with_headers(fhttp, url, "{\"Content-Type\": \"application/json\"}"))
+    if (!flipper_http_request(fhttp, GET, url, "{\"Content-Type\": \"application/json\"}", NULL))
     {
     {
         FURI_LOG_E("Game", "Failed to send HTTP request");
         FURI_LOG_E("Game", "Failed to send HTTP request");
         flipper_http_free(fhttp);
         flipper_http_free(fhttp);

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است