Преглед изворни кода

Final 0.1 edits

- fixed furi_check when leaving the game/changing levels
- allocated 4096 bytes for game (instead of 1024)
- use app_instance global variable instead of assigning an app context to each level
jblanked пре 1 година
родитељ
комит
5c4d163f71
9 измењених фајлова са 245 додато и 146 уклоњено
  1. 0 1
      alloc/alloc.c
  2. 63 43
      callback/callback.c
  3. 2 1
      flip_world.c
  4. 2 1
      flip_world.h
  5. 11 7
      game/game.c
  6. 42 33
      game/level.c
  7. 0 1
      game/level.h
  8. 124 58
      game/world.c
  9. 1 1
      game/world.h

+ 0 - 1
alloc/alloc.c

@@ -7,7 +7,6 @@
  */
 static uint32_t callback_exit_app(void *context)
 {
-    // Exit the application
     UNUSED(context);
     return VIEW_NONE; // Return VIEW_NONE to exit the app
 }

+ 63 - 43
callback/callback.c

@@ -28,13 +28,14 @@ static int32_t game_app(void *p)
         FURI_LOG_E("Game", "Failed to allocate game manager");
         return -1;
     }
+
+    // Setup game engine settings...
     GameEngineSettings settings = game_engine_settings_init();
     settings.target_fps = game_fps_choices_2[game_fps_index];
     settings.show_fps = game.show_fps;
     settings.always_backlight = strstr(game_screen_always_on_choices[game_screen_always_on_index], "Yes") != NULL;
     settings.frame_callback = frame_cb;
     settings.context = game_manager;
-
     GameEngine *engine = game_engine_alloc(settings);
     if (!engine)
     {
@@ -44,25 +45,36 @@ static int32_t game_app(void *p)
     }
     game_manager_engine_set(game_manager, engine);
 
+    // Allocate custom game context if needed
     void *game_context = NULL;
     if (game.context_size > 0)
     {
         game_context = malloc(game.context_size);
         game_manager_game_context_set(game_manager, game_context);
     }
+
+    // Start the game
     game.start(game_manager, game_context);
 
+    // 1) Run the engine
     game_engine_run(engine);
+
+    // 2) Stop the game FIRST, so it can do any internal cleanup
+    game.stop(game_context);
+
+    // 3) Now free the engine
     game_engine_free(engine);
 
+    // 4) Now free the manager
     game_manager_free(game_manager);
 
-    game.stop(game_context);
+    // 5) Finally, free your custom context if it was allocated
     if (game_context)
     {
         free(game_context);
     }
 
+    // 6) Check for leftover entities
     int32_t entities = entities_get_count();
     if (entities != 0)
     {
@@ -457,15 +469,6 @@ static void free_about_view(void *context)
         app->view_about = NULL;
     }
 }
-static void free_main_view(void *context)
-{
-    FlipWorldApp *app = (FlipWorldApp *)context;
-    if (!app)
-    {
-        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
-        return;
-    }
-}
 
 static void free_text_input_view(void *context)
 {
@@ -557,7 +560,6 @@ static void free_submenu_settings(void *context)
         app->submenu_settings = NULL;
     }
 }
-static FlipWorldApp *app_instance = NULL;
 static FuriThreadId thread_id;
 static bool game_thread_running = false;
 void free_all_views(void *context, bool should_free_variable_item_list, bool should_free_submenu_settings)
@@ -573,8 +575,8 @@ void free_all_views(void *context, bool should_free_variable_item_list, bool sho
         free_variable_item_list(app);
     }
     free_about_view(app);
-    free_main_view(app);
     free_text_input_view(app);
+
     // free game thread
     if (game_thread_running)
     {
@@ -643,9 +645,6 @@ static bool flip_world_fetch_world_list(DataLoaderModel *model)
             furi_string_free(world_list);
             furi_string_free(first_world);
             FURI_LOG_I(TAG, "World already exists");
-            fhttp.state = IDLE;
-            model->data_state = DataStateParsed;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
             flipper_http_deinit();
             // free game thread
             if (game_thread_running)
@@ -654,14 +653,19 @@ static bool flip_world_fetch_world_list(DataLoaderModel *model)
                 furi_thread_flags_set(thread_id, WorkerEvtStop);
                 furi_thread_free(thread_id);
             }
-            // free_all_views(app_instance, true, true);
-            FuriThread *thread = furi_thread_alloc_ex("game", 1024, game_app, app_instance);
+            if (!app_instance)
+            {
+                FURI_LOG_E(TAG, "app_instance is NULL");
+                easy_flipper_dialog("Error", "app_instance is NULL. Press BACK to return.");
+                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+                return "app_instance is NULL";
+            }
+            FuriThread *thread = furi_thread_alloc_ex("game", 4096, game_app, app_instance);
             if (!thread)
             {
                 view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
                 FURI_LOG_E(TAG, "Failed to allocate game thread");
                 easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
-                furi_thread_free(thread);
                 return false;
             }
             furi_thread_start(thread);
@@ -702,12 +706,17 @@ static char *flip_world_parse_world_list(DataLoaderModel *model)
             furi_thread_flags_set(thread_id, WorkerEvtStop);
             furi_thread_free(thread_id);
         }
-        // free_all_views(app_instance, true, true);
-        FuriThread *thread = furi_thread_alloc_ex("game", 1024, game_app, app_instance);
+        if (!app_instance)
+        {
+            FURI_LOG_E(TAG, "app_instance is NULL");
+            easy_flipper_dialog("Error", "app_instance is NULL. Press BACK to return.");
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+            return "app_instance is NULL";
+        }
+        FuriThread *thread = furi_thread_alloc_ex("game", 4096, game_app, app_instance);
         if (!thread)
         {
             FURI_LOG_E(TAG, "Failed to allocate game thread");
-            furi_thread_free(thread);
             return "Failed to allocate game thread";
         }
         furi_thread_start(thread);
@@ -843,7 +852,6 @@ static bool flip_world_fetch_game(DataLoaderModel *model)
             view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
             FURI_LOG_E(TAG, "Failed to load world list");
             easy_flipper_dialog("Error", "Failed to load world list. Go to game settings to download packs.");
-            furi_string_free(world_list);
             return false;
         }
         FuriString *first_world = get_json_array_value_furi("worlds", 0, world_list);
@@ -852,7 +860,7 @@ static bool flip_world_fetch_game(DataLoaderModel *model)
             view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
             FURI_LOG_E(TAG, "Failed to get first world");
             easy_flipper_dialog("Error", "Failed to get first world. Go to game settings to download packs.");
-            furi_string_free(first_world);
+            furi_string_free(world_list);
             return false;
         }
         if (world_exists(furi_string_get_cstr(first_world)))
@@ -860,9 +868,8 @@ static bool flip_world_fetch_game(DataLoaderModel *model)
             furi_string_free(world_list);
             furi_string_free(first_world);
             FURI_LOG_I(TAG, "World already exists");
-            fhttp.state = IDLE;
-            model->data_state = DataStateParsed;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
+
+            // view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
             flipper_http_deinit();
             // free game thread
             if (game_thread_running)
@@ -871,14 +878,19 @@ static bool flip_world_fetch_game(DataLoaderModel *model)
                 furi_thread_flags_set(thread_id, WorkerEvtStop);
                 furi_thread_free(thread_id);
             }
-            // free_all_views(app_instance, true, true);
-            FuriThread *thread = furi_thread_alloc_ex("game", 1024, game_app, app_instance);
+            if (!app_instance)
+            {
+                FURI_LOG_E(TAG, "app_instance is NULL");
+                easy_flipper_dialog("Error", "app_instance is NULL. Press BACK to return.");
+                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+                return "app_instance is NULL";
+            }
+            FuriThread *thread = furi_thread_alloc_ex("game", 4096, game_app, app_instance);
             if (!thread)
             {
                 view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
                 FURI_LOG_E(TAG, "Failed to allocate game thread");
                 easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
-                furi_thread_free(thread);
                 return false;
             }
             furi_thread_start(thread);
@@ -1027,9 +1039,8 @@ static char *flip_world_parse_game(DataLoaderModel *model)
         }
         else
         {
-            fhttp.state = IDLE;
-            model->data_state = DataStateParsed;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
+
+            // view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
             flipper_http_deinit();
             // free game thread
             if (game_thread_running)
@@ -1038,12 +1049,17 @@ static char *flip_world_parse_game(DataLoaderModel *model)
                 furi_thread_flags_set(thread_id, WorkerEvtStop);
                 furi_thread_free(thread_id);
             }
-            // free_all_views(app_instance, true, true);
-            FuriThread *thread = furi_thread_alloc_ex("game", 1024, game_app, app_instance);
+            if (!app_instance)
+            {
+                FURI_LOG_E(TAG, "app_instance is NULL");
+                easy_flipper_dialog("Error", "app_instance is NULL. Press BACK to return.");
+                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+                return "app_instance is NULL";
+            }
+            FuriThread *thread = furi_thread_alloc_ex("game", 4096, game_app, app_instance);
             if (!thread)
             {
                 FURI_LOG_E(TAG, "Failed to allocate game thread");
-                furi_thread_free(thread);
                 easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
                 view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
                 return "Failed to allocate game thread";
@@ -1060,9 +1076,8 @@ static char *flip_world_parse_game(DataLoaderModel *model)
     }
     else if (model->request_index == 3)
     {
-        fhttp.state = IDLE;
-        model->data_state = DataStateParsed;
-        view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
+
+        // view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu);
         flipper_http_deinit();
         // free game thread
         if (game_thread_running)
@@ -1071,12 +1086,17 @@ static char *flip_world_parse_game(DataLoaderModel *model)
             furi_thread_flags_set(thread_id, WorkerEvtStop);
             furi_thread_free(thread_id);
         }
-        // free_all_views(app_instance, true, true);
-        FuriThread *thread = furi_thread_alloc_ex("game", 1024, game_app, app_instance);
+        if (!app_instance)
+        {
+            FURI_LOG_E(TAG, "app_instance is NULL");
+            easy_flipper_dialog("Error", "app_instance is NULL. Press BACK to return.");
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+            return "app_instance is NULL";
+        }
+        FuriThread *thread = furi_thread_alloc_ex("game", 4096, game_app, app_instance);
         if (!thread)
         {
             FURI_LOG_E(TAG, "Failed to allocate game thread");
-            furi_thread_free(thread);
             easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
             view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
             return "Failed to allocate game thread";
@@ -1090,7 +1110,7 @@ static char *flip_world_parse_game(DataLoaderModel *model)
     view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
     return "Unknown error";
 }
-static void flip_world_switch_to_view_get_game(FlipWorldApp *app)
+void flip_world_switch_to_view_get_game(FlipWorldApp *app)
 {
     flip_world_generic_switch_to_view(app, "Starting Game..", flip_world_fetch_game, flip_world_parse_game, 5, callback_to_submenu, FlipWorldViewLoader);
 }

+ 2 - 1
flip_world.c

@@ -3,4 +3,5 @@ char *game_fps_choices[] = {"30", "60", "120", "240"};
 const float game_fps_choices_2[] = {30.0, 60.0, 120.0, 240.0};
 int game_fps_index = 1;
 char *game_screen_always_on_choices[] = {"No", "Yes"};
-int game_screen_always_on_index = 1;
+int game_screen_always_on_index = 1;
+FlipWorldApp *app_instance = NULL;

+ 2 - 1
flip_world.h

@@ -77,4 +77,5 @@ extern char *game_fps_choices[];
 extern const float game_fps_choices_2[];
 extern int game_fps_index;
 extern char *game_screen_always_on_choices[];
-extern int game_screen_always_on_index;
+extern int game_screen_always_on_index;
+extern FlipWorldApp *app_instance;

+ 11 - 7
game/game.c

@@ -14,7 +14,7 @@ static Level *get_next_level(GameManager *manager)
         if (levels[i] == current_level)
         {
             // check if i+1 is out of bounds, if so, return the first level
-            return levels[(i + 1) % level_count];
+            return levels[(i + 1) % level_count] ? levels[(i + 1) % level_count] : levels[0];
         }
     }
     return levels[0] ? levels[0] : game_manager_add_level(manager, generic_level("town_world", 0));
@@ -164,8 +164,7 @@ static void game_start(GameManager *game_manager, void *ctx)
     {
         FURI_LOG_E("Game", "Failed to load world list");
         levels[0] = game_manager_add_level(game_manager, generic_level("town_world", 0));
-        levels[1] = game_manager_add_level(game_manager, generic_level("tree_world", 1));
-        level_count = 2;
+        level_count = 1;
         return;
     }
     for (int i = 0; i < 10; i++)
@@ -190,10 +189,15 @@ static void game_start(GameManager *game_manager, void *ctx)
 static void game_stop(void *ctx)
 {
     UNUSED(ctx);
-    // GameContext *game_context = ctx;
-    //  Do some deinitialization here, for example you can save score to storage.
-    //  For simplicity, we will just print it.
-    // FURI_LOG_I("Game", "Your score: %lu", game_context->score);
+    // If you want to do other final logic (like saving scores), do it here.
+    // But do NOT free levels[] if the engine manages them.
+
+    // Just clear out your pointer array if you like (not strictly necessary)
+    for (int i = 0; i < level_count; i++)
+    {
+        levels[i] = NULL;
+    }
+    level_count = 0;
 }
 
 /*

+ 42 - 33
game/level.c

@@ -3,6 +3,12 @@
 static void level_start(Level *level, GameManager *manager, void *context)
 {
     UNUSED(manager);
+    if (!level || !context)
+    {
+        FURI_LOG_E("Game", "Level or context is NULL");
+        return;
+    }
+
     level_clear(level);
     player_spawn(level, manager);
     LevelContext *level_context = context;
@@ -11,76 +17,75 @@ static void level_start(Level *level, GameManager *manager, void *context)
     if (!world_exists(level_context->id))
     {
         FURI_LOG_E("Game", "World does not exist.. downloading now");
-        if (!level_context->app)
-        {
-            level_context->app = (FlipWorldApp *)malloc(sizeof(FlipWorldApp));
-            if (!level_context->app)
-            {
-                FURI_LOG_E("Game", "Failed to allocate FlipWorldApp");
-                free(level_context->app);
-                level_context->app = NULL;
-                return;
-            }
-        }
-        FuriString *world_data = fetch_world(level_context->id, level_context->app);
+        FuriString *world_data = fetch_world(level_context->id);
         if (!world_data)
         {
             FURI_LOG_E("Game", "Failed to fetch world data");
-            draw_town_world(level);
-            free(level_context->app);
-            level_context->app = NULL;
-            furi_string_free(world_data);
+            draw_tree_world(level);
             return;
         }
+
         if (!draw_json_world(level, furi_string_get_cstr(world_data)))
         {
             FURI_LOG_E("Game", "Failed to draw world");
-            draw_town_world(level);
+            draw_tree_world(level);
         }
-        free(level_context->app);
-        level_context->app = NULL;
+
+        // world_data is guaranteed non-NULL here
         furi_string_free(world_data);
         return;
     }
+
     // get the world data
     FuriString *world_data = load_furi_world(level_context->id);
     if (!world_data)
     {
         FURI_LOG_E("Game", "Failed to load world data");
-        draw_town_world(level);
-        furi_string_free(world_data);
+        draw_tree_world(level);
         return;
     }
+
     // draw the world
     if (!draw_json_world(level, furi_string_get_cstr(world_data)))
     {
         FURI_LOG_E("Game", "World exists but failed to draw.");
-        draw_town_world(level);
+        draw_tree_world(level);
     }
+
+    // world_data is guaranteed non-NULL here
     furi_string_free(world_data);
 }
 
 static LevelContext *level_context_generic;
+
 static LevelContext *level_generic_alloc(const char *id, int index)
 {
-    if (!level_context_generic)
+    if (level_context_generic == NULL)
     {
         level_context_generic = malloc(sizeof(LevelContext));
     }
     snprintf(level_context_generic->id, sizeof(level_context_generic->id), "%s", id);
     level_context_generic->index = index;
-    level_context_generic->app = NULL;
     return level_context_generic;
 }
+
 static void level_generic_free()
 {
-    if (level_context_generic)
+    if (level_context_generic != NULL)
     {
         free(level_context_generic);
         level_context_generic = NULL;
     }
 }
 
+static void level_free(Level *level, GameManager *manager, void *context)
+{
+    UNUSED(level);
+    UNUSED(manager);
+    UNUSED(context);
+    level_generic_free();
+}
+
 static void level_alloc_generic_world(Level *level, GameManager *manager, void *context)
 {
     UNUSED(manager);
@@ -90,24 +95,28 @@ static void level_alloc_generic_world(Level *level, GameManager *manager, void *
         FURI_LOG_E("Game", "Generic level context not set");
         return;
     }
+    if (!context)
+    {
+        FURI_LOG_E("Game", "Context is NULL");
+        return;
+    }
     LevelContext *level_context = context;
     snprintf(level_context->id, sizeof(level_context->id), "%s", level_context_generic->id);
     level_context->index = level_context_generic->index;
-    level_context->app = level_context_generic->app;
 }
 
-// Do NOT touch (this is for dynamic level creation)
 const LevelBehaviour _generic_level = {
-    .alloc = level_alloc_generic_world,   // called once, when level allocated
-    .free = NULL,                         // called once, when level freed
-    .start = level_start,                 // called when level is changed to this level
-    .stop = NULL,                         // called when level is changed from this level
-    .context_size = sizeof(LevelContext), // size of level context, will be automatically allocated and freed
+    .alloc = level_alloc_generic_world,
+    .free = level_free,
+    .start = level_start,
+    .stop = NULL,
+    .context_size = sizeof(LevelContext),
 };
 
 const LevelBehaviour *generic_level(const char *id, int index)
 {
+    // free any old context before allocating a new one
     level_generic_free();
     level_context_generic = level_generic_alloc(id, index);
     return &_generic_level;
-}
+}

+ 0 - 1
game/level.h

@@ -5,7 +5,6 @@ typedef struct
 {
     char id[64];
     int index;
-    FlipWorldApp *app;
 } LevelContext;
 
 const LevelBehaviour *generic_level(const char *id, int index);

+ 124 - 58
game/world.c

@@ -11,55 +11,90 @@ bool draw_json_world(Level *level, const char *json_data)
 {
     for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
     {
+        // 1) Get data array item
         char *data = get_json_array_value("json_data", i, json_data);
-        if (data == NULL)
+        if (!data)
         {
+            // Means we've reached the end of the array
             break;
         }
+
+        // 2) Extract all required fields
         char *icon = get_json_value("icon", data);
         char *x = get_json_value("x", data);
         char *y = get_json_value("y", data);
         char *amount = get_json_value("amount", data);
         char *horizontal = get_json_value("horizontal", data);
-        if (icon == NULL || x == NULL || y == NULL || amount == NULL || horizontal == NULL)
+
+        // 3) Check for any NULL pointers
+        if (!icon || !x || !y || !amount || !horizontal)
         {
-            FURI_LOG_E("Failed Data: ", data);
-            free(data);
-            free(icon);
-            free(x);
-            free(y);
-            free(amount);
-            free(horizontal);
+            FURI_LOG_E("Game", "Failed Data: %s", data);
+
+            // Free everything carefully
+            if (data)
+                free(data);
+            if (icon)
+                free(icon);
+            if (x)
+                free(x);
+            if (y)
+                free(y);
+            if (amount)
+                free(amount);
+            if (horizontal)
+                free(horizontal);
+
             level_clear(level);
             return false;
         }
+
+        // 4) Get the IconContext
         IconContext *icon_context = get_icon_context(icon);
-        if (icon_context == NULL)
+        if (!icon_context)
         {
-            FURI_LOG_E("Failed Icon: ", icon);
+            FURI_LOG_E("Game", "Failed Icon: %s", icon);
+
             free(data);
             free(icon);
             free(x);
             free(y);
             free(amount);
             free(horizontal);
+
             level_clear(level);
             return false;
         }
-        // if amount is less than 2, we spawn a single icon
-        if (atoi(amount) < 2)
+
+        // 5) Decide how many icons to spawn
+        int count = atoi(amount);
+        if (count < 2)
         {
-            spawn_icon(level, icon_context->icon, atoi(x), atoi(y), icon_context->width, icon_context->height);
-            free(data);
-            free(icon);
-            free(x);
-            free(y);
-            free(amount);
-            free(horizontal);
-            free(icon_context);
-            continue;
+            // Just one icon
+            spawn_icon(
+                level,
+                icon_context->icon,
+                atoi(x),
+                atoi(y),
+                icon_context->width,
+                icon_context->height);
+        }
+        else
+        {
+            // Spawn multiple in a line
+            bool is_horizontal = (strcmp(horizontal, "true") == 0);
+            spawn_icon_line(
+                level,
+                icon_context->icon,
+                atoi(x),
+                atoi(y),
+                icon_context->width,
+                icon_context->height,
+                count,
+                is_horizontal);
         }
-        spawn_icon_line(level, icon_context->icon, atoi(x), atoi(y), icon_context->width, icon_context->height, atoi(amount), strcmp(horizontal, "true") == 0);
+
+        // 6) Cleanup
         free(data);
         free(icon);
         free(x);
@@ -70,49 +105,70 @@ bool draw_json_world(Level *level, const char *json_data)
     }
     return true;
 }
+
 bool draw_json_world_furi(Level *level, FuriString *json_data)
 {
     for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
     {
+        // 1) Get data array item as FuriString
         FuriString *data = get_json_array_value_furi("json_data", i, json_data);
-        if (data == NULL)
+        if (!data)
         {
+            // Means we've reached the end of the array
             break;
         }
+
+        // 2) Extract all required fields
         FuriString *icon = get_json_value_furi("icon", data);
         FuriString *x = get_json_value_furi("x", data);
         FuriString *y = get_json_value_furi("y", data);
         FuriString *amount = get_json_value_furi("amount", data);
         FuriString *horizontal = get_json_value_furi("horizontal", data);
+
+        // 3) Check for any NULL pointers
         if (!icon || !x || !y || !amount || !horizontal)
         {
-            FURI_LOG_E("Failed Data: ", furi_string_get_cstr(data));
-            furi_string_free(data);
-            furi_string_free(icon);
-            furi_string_free(x);
-            furi_string_free(y);
-            furi_string_free(amount);
-            furi_string_free(horizontal);
+            FURI_LOG_E("Game", "Failed Data: %s", furi_string_get_cstr(data));
+
+            if (data)
+                furi_string_free(data);
+            if (icon)
+                furi_string_free(icon);
+            if (x)
+                furi_string_free(x);
+            if (y)
+                furi_string_free(y);
+            if (amount)
+                furi_string_free(amount);
+            if (horizontal)
+                furi_string_free(horizontal);
+
             level_clear(level);
             return false;
         }
+
+        // 4) Get the IconContext from a FuriString
         IconContext *icon_context = get_icon_context_furi(icon);
-        if (icon_context == NULL)
+        if (!icon_context)
         {
-            FURI_LOG_E("Failed Icon: ", furi_string_get_cstr(icon));
+            FURI_LOG_E("Game", "Failed Icon: %s", furi_string_get_cstr(icon));
+
             furi_string_free(data);
             furi_string_free(icon);
             furi_string_free(x);
             furi_string_free(y);
             furi_string_free(amount);
             furi_string_free(horizontal);
+
             level_clear(level);
-            free(icon_context);
             return false;
         }
-        // if amount is less than 2, we spawn a single icon
-        if (atoi(furi_string_get_cstr(amount)) < 2)
+
+        // 5) Decide how many icons to spawn
+        int count = atoi(furi_string_get_cstr(amount));
+        if (count < 2)
         {
+            // Just one icon
             spawn_icon(
                 level,
                 icon_context->icon,
@@ -120,24 +176,23 @@ bool draw_json_world_furi(Level *level, FuriString *json_data)
                 atoi(furi_string_get_cstr(y)),
                 icon_context->width,
                 icon_context->height);
-            furi_string_free(data);
-            furi_string_free(icon);
-            furi_string_free(x);
-            furi_string_free(y);
-            furi_string_free(amount);
-            furi_string_free(horizontal);
-            free(icon_context);
-            continue;
         }
-        spawn_icon_line(
-            level,
-            icon_context->icon,
-            atoi(furi_string_get_cstr(x)),
-            atoi(furi_string_get_cstr(y)),
-            icon_context->width,
-            icon_context->height,
-            atoi(furi_string_get_cstr(amount)),
-            furi_string_cmp(horizontal, "true") == 0);
+        else
+        {
+            // Spawn multiple in a line
+            bool is_horizontal = (furi_string_cmp(horizontal, "true") == 0);
+            spawn_icon_line(
+                level,
+                icon_context->icon,
+                atoi(furi_string_get_cstr(x)),
+                atoi(furi_string_get_cstr(y)),
+                icon_context->width,
+                icon_context->height,
+                count,
+                is_horizontal);
+        }
+
+        // 6) Clean up after every iteration
         furi_string_free(data);
         furi_string_free(icon);
         furi_string_free(x);
@@ -329,14 +384,21 @@ void draw_town_world(Level *level)
     free(tree_icon);
 }
 
-FuriString *fetch_world(char *name, void *app)
+FuriString *fetch_world(const char *name)
 {
-    if (!app || !name)
+    if (!name)
     {
-        FURI_LOG_E("Game", "App or name is NULL");
+        FURI_LOG_E("Game", "World name is NULL");
         return NULL;
     }
-    if (!flipper_http_init(flipper_http_rx_callback, app))
+    if (!app_instance)
+    {
+        // as long as the game is running, app_instance should be non-NULL
+        FURI_LOG_E("Game", "App instance is NULL");
+        return NULL;
+    }
+
+    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
     {
         FURI_LOG_E("Game", "Failed to initialize HTTP");
         return NULL;
@@ -348,8 +410,10 @@ FuriString *fetch_world(char *name, void *app)
     if (!flipper_http_get_request_with_headers(url, "{\"Content-Type\": \"application/json\"}"))
     {
         FURI_LOG_E("Game", "Failed to send HTTP request");
+        flipper_http_deinit();
         return NULL;
     }
+    fhttp.state = RECEIVING;
     furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
     while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
     {
@@ -360,9 +424,11 @@ FuriString *fetch_world(char *name, void *app)
     if (fhttp.state != IDLE)
     {
         FURI_LOG_E("Game", "Failed to receive world data");
+        flipper_http_deinit();
         return NULL;
     }
-    FuriString *returned_data = flipper_http_load_from_file(fhttp.file_path);
+    flipper_http_deinit();
+    FuriString *returned_data = load_furi_world(name);
     if (!returned_data)
     {
         FURI_LOG_E("Game", "Failed to load world data from file");

+ 1 - 1
game/world.h

@@ -16,4 +16,4 @@ void draw_tree_world(Level *level);
 void draw_town_world(Level *level);
 bool draw_json_world(Level *level, const char *json_data);
 bool draw_json_world_furi(Level *level, FuriString *json_data);
-FuriString *fetch_world(char *name, void *app);
+FuriString *fetch_world(const char *name);