Просмотр исходного кода

add icon grouping (this saved 26k bytes)

jblanked 9 месяцев назад
Родитель
Сommit
a946635fc9
5 измененных файлов с 360 добавлено и 219 удалено
  1. 1 2
      game/draw.c
  2. 55 136
      game/icon.c
  3. 34 24
      game/icon.h
  4. 37 0
      game/player.c
  5. 233 57
      game/world.c

+ 1 - 2
game/draw.c

@@ -53,8 +53,7 @@ void spawn_icon(GameManager *manager, Level *level, const char *icon_id, float x
     snprintf(g_name, sizeof(g_name), "%s", icon_id);
     Entity *e = level_add_entity(level, &icon_desc);
     entity_pos_set(e, (Vector){x, y});
-    GameContext *game_context = game_manager_game_context_get(manager);
-    game_context->icon_count++;
+    UNUSED(manager);
 }
 // Draw a line of icons at a specific position (with collision detection)
 void spawn_icon_line(GameManager *manager, Level *level, const char *icon_id, float x, float y, uint8_t amount, bool horizontal, uint8_t spacing)

+ 55 - 136
game/icon.c

@@ -1,166 +1,85 @@
 #include "game/icon.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
 
-static IconContext *icon_context_generic = NULL;
+// --- Define the global variable so other modules can link to it.
+IconGroupContext *g_current_icon_group = NULL;
 
-static void icon_collision(Entity *self, Entity *other, GameManager *manager, void *context)
+// ---------------------------------------------------------------------
+// Icon Group Entity Implementation
+// ---------------------------------------------------------------------
+
+// Render callback: iterate over the group and draw each icon.
+static void icon_group_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
 {
-    UNUSED(manager);
     UNUSED(self);
-    IconContext *ictx = (IconContext *)context;
-    if (ictx && entity_description_get(other) == &player_desc)
+    UNUSED(manager);
+    IconGroupContext *igctx = (IconGroupContext *)context;
+    if (!igctx)
+    {
+        FURI_LOG_E("Game", "Icon group context is NULL in render");
+        return;
+    }
+    for (int i = 0; i < igctx->count; i++)
     {
-        PlayerContext *player = (PlayerContext *)entity_context_get(other);
-        if (player)
+        IconSpec *spec = &igctx->icons[i];
+        int x_pos = spec->pos.x - camera_x - (spec->size.x / 2);
+        int y_pos = spec->pos.y - camera_y - (spec->size.y / 2);
+        if (x_pos + spec->size.x < 0 || x_pos > SCREEN_WIDTH ||
+            y_pos + spec->size.y < 0 || y_pos > SCREEN_HEIGHT)
         {
-            // Set the player's old position to prevent collision
-            entity_pos_set(other, player->old_position);
-
-            // Reset movement to prevent re-collision
-            player->dx = 0;
-            player->dy = 0;
+            continue;
         }
+        canvas_draw_icon(canvas, x_pos, y_pos, spec->icon);
     }
 }
 
-static void icon_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
+// Start callback: nothing special is needed here.
+static void icon_group_start(Entity *self, GameManager *manager, void *context)
 {
+    UNUSED(self);
     UNUSED(manager);
-    IconContext *ictx = (IconContext *)context;
-    furi_check(ictx, "Icon context is NULL");
-    Vector pos = entity_pos_get(self);
-    int x_pos = pos.x - camera_x - ictx->size.x / 2;
-    int y_pos = pos.y - camera_y - ictx->size.y / 2;
-    // Check if position is within the screen
-    if (x_pos + ictx->size.x < 0 || x_pos > SCREEN_WIDTH ||
-        y_pos + ictx->size.y < 0 || y_pos > SCREEN_HEIGHT)
-    {
-        return;
-    }
-    canvas_draw_icon(canvas, x_pos, y_pos, ictx->icon);
+    UNUSED(context);
+    // The context is assumed to be pre-filled by the world code.
 }
 
-static void icon_start(Entity *self, GameManager *manager, void *context)
+// Free callback: free the icon specs array.
+static void icon_group_free(Entity *self, GameManager *manager, void *context)
 {
+    UNUSED(self);
     UNUSED(manager);
-
-    // The entity's instance context
-    IconContext *ictx_instance = (IconContext *)context;
-    if (!ictx_instance)
+    IconGroupContext *igctx = (IconGroupContext *)context;
+    if (igctx && igctx->icons)
     {
-        FURI_LOG_E("Game", "Icon context instance is NULL");
-        return;
-    }
-
-    // Instead of allocating a temporary context and freeing it immediately,
-    // reuse the global generic context (similar to npc_context_generic in npc.c)
-    IconContext *generic_ctx = get_icon_context(g_name);
-    if (!generic_ctx)
-    {
-        FURI_LOG_E("Game", "Failed to get icon context for %s", g_name);
-        return;
+        free(igctx->icons);
+        igctx->icons = NULL;
     }
-
-    // Copy generic icon data into the instance context
-    ictx_instance->icon = generic_ctx->icon;
-    ictx_instance->size = generic_ctx->size;
-
-    // Adjust position to be centered on the icon
-    Vector pos = entity_pos_get(self);
-    pos.x += ictx_instance->size.x / 2;
-    pos.y += ictx_instance->size.y / 2;
-    entity_pos_set(self, pos);
-
-    // Add a circular collider based on the icon's dimensions
-    entity_collider_add_circle(self, (ictx_instance->size.x + ictx_instance->size.y) / 4);
 }
 
-static void icon_free(Entity *self, GameManager *manager, void *context)
+// Optional helper to free an icon group context allocated dynamically.
+void icon_group_context_free(IconGroupContext *ctx)
 {
-    UNUSED(self);
-    UNUSED(manager);
-    UNUSED(context);
-    if (icon_context_generic)
+    if (ctx)
     {
-        free(icon_context_generic);
-        icon_context_generic = NULL;
+        if (ctx->icons)
+        {
+            free(ctx->icons);
+            ctx->icons = NULL;
+        }
+        free(ctx);
     }
 }
 
-// -------------- Entity description --------------
+// The entity description for the icon group.
+// Note: we set context_size to sizeof(IconGroupContext).
 const EntityDescription icon_desc = {
-    .start = icon_start,
-    .stop = icon_free,
+    .start = icon_group_start,
+    .stop = icon_group_free,
     .update = NULL,
-    .render = icon_render,
-    .collision = icon_collision,
+    .render = icon_group_render,
+    // We leave collision as NULL because we now handle collisions in player_update.
+    .collision = NULL,
     .event = NULL,
-    .context_size = sizeof(IconContext),
+    .context_size = sizeof(IconGroupContext),
 };
-
-// Generic allocation using a global static pointer
-static IconContext *icon_generic_alloc(IconID id, const Icon *icon, uint8_t width, uint8_t height)
-{
-    if (!icon_context_generic)
-    {
-        icon_context_generic = malloc(sizeof(IconContext));
-        if (!icon_context_generic)
-        {
-            FURI_LOG_E("Game", "Failed to allocate IconContext");
-            return NULL;
-        }
-    }
-    icon_context_generic->id = id;
-    icon_context_generic->icon = icon;
-    icon_context_generic->size = (Vector){width, height};
-    return icon_context_generic;
-}
-
-IconContext *get_icon_context(const char *name)
-{
-    if (is_str(name, "house"))
-        return icon_generic_alloc(ICON_ID_HOUSE, &I_icon_house_48x32px, 48, 32);
-    else if (is_str(name, "man"))
-        return icon_generic_alloc(ICON_ID_MAN, &I_icon_man_7x16, 7, 16);
-    else if (is_str(name, "plant"))
-        return icon_generic_alloc(ICON_ID_PLANT, &I_icon_plant_16x16, 16, 16);
-    else if (is_str(name, "tree"))
-        return icon_generic_alloc(ICON_ID_TREE, &I_icon_tree_16x16, 16, 16);
-    else if (is_str(name, "woman"))
-        return icon_generic_alloc(ICON_ID_WOMAN, &I_icon_woman_9x16, 9, 16);
-    else if (is_str(name, "fence"))
-        return icon_generic_alloc(ICON_ID_FENCE, &I_icon_fence_16x8px, 16, 8);
-    else if (is_str(name, "fence_end"))
-        return icon_generic_alloc(ICON_ID_FENCE_END, &I_icon_fence_end_16x8px, 16, 8);
-    else if (is_str(name, "fence_vertical_end"))
-        return icon_generic_alloc(ICON_ID_FENCE_VERTICAL_END, &I_icon_fence_vertical_end_6x8px, 6, 8);
-    else if (is_str(name, "fence_vertical_start"))
-        return icon_generic_alloc(ICON_ID_FENCE_VERTICAL_START, &I_icon_fence_vertical_start_6x15px, 6, 15);
-    else if (is_str(name, "flower"))
-        return icon_generic_alloc(ICON_ID_FLOWER, &I_icon_flower_16x16, 16, 16);
-    else if (is_str(name, "lake_bottom"))
-        return icon_generic_alloc(ICON_ID_LAKE_BOTTOM, &I_icon_lake_bottom_31x12px, 31, 12);
-    else if (is_str(name, "lake_bottom_left"))
-        return icon_generic_alloc(ICON_ID_LAKE_BOTTOM_LEFT, &I_icon_lake_bottom_left_24x22px, 24, 22);
-    else if (is_str(name, "lake_bottom_right"))
-        return icon_generic_alloc(ICON_ID_LAKE_BOTTOM_RIGHT, &I_icon_lake_bottom_right_24x22px, 24, 22);
-    else if (is_str(name, "lake_left"))
-        return icon_generic_alloc(ICON_ID_LAKE_LEFT, &I_icon_lake_left_11x31px, 11, 31);
-    else if (is_str(name, "lake_right"))
-        return icon_generic_alloc(ICON_ID_LAKE_RIGHT, &I_icon_lake_right_11x31, 11, 31);
-    else if (is_str(name, "lake_top"))
-        return icon_generic_alloc(ICON_ID_LAKE_TOP, &I_icon_lake_top_31x12px, 31, 12);
-    else if (is_str(name, "lake_top_left"))
-        return icon_generic_alloc(ICON_ID_LAKE_TOP_LEFT, &I_icon_lake_top_left_24x22px, 24, 22);
-    else if (is_str(name, "lake_top_right"))
-        return icon_generic_alloc(ICON_ID_LAKE_TOP_RIGHT, &I_icon_lake_top_right_24x22px, 24, 22);
-    else if (is_str(name, "rock_large"))
-        return icon_generic_alloc(ICON_ID_ROCK_LARGE, &I_icon_rock_large_18x19px, 18, 19);
-    else if (is_str(name, "rock_medium"))
-        return icon_generic_alloc(ICON_ID_ROCK_MEDIUM, &I_icon_rock_medium_16x14px, 16, 14);
-    else if (is_str(name, "rock_small"))
-        return icon_generic_alloc(ICON_ID_ROCK_SMALL, &I_icon_rock_small_10x8px, 10, 8);
-
-    // If no match is found, log an error and return NULL
-    FURI_LOG_E("Game", "Icon not found: %s", name);
-    return NULL;
-}

+ 34 - 24
game/icon.h

@@ -4,35 +4,45 @@
 
 typedef enum
 {
-    ICON_ID_HOUSE,                // House
-    ICON_ID_MAN,                  // Man
-    ICON_ID_PLANT,                // Plant
-    ICON_ID_TREE,                 // Tree
-    ICON_ID_WOMAN,                // Woman
-    ICON_ID_FENCE,                // Fence
-    ICON_ID_FENCE_END,            // Fence end
-    ICON_ID_FENCE_VERTICAL_END,   // Vertical fence end
-    ICON_ID_FENCE_VERTICAL_START, // Vertical fence start
-    ICON_ID_FLOWER,               // Flower
-    ICON_ID_LAKE_BOTTOM,          // Lake bottom
-    ICON_ID_LAKE_BOTTOM_LEFT,     // Lake bottom left
-    ICON_ID_LAKE_BOTTOM_RIGHT,    // Lake bottom right
-    ICON_ID_LAKE_LEFT,            // Lake left
-    ICON_ID_LAKE_RIGHT,           // Lake right
-    ICON_ID_LAKE_TOP,             // Lake top
-    ICON_ID_LAKE_TOP_LEFT,        // Lake top left
-    ICON_ID_LAKE_TOP_RIGHT,       // Lake top right
-    ICON_ID_ROCK_LARGE,           // Large rock
-    ICON_ID_ROCK_MEDIUM,          // Medium rock
-    ICON_ID_ROCK_SMALL,           // Small rock
+    ICON_ID_HOUSE,
+    ICON_ID_MAN,
+    ICON_ID_PLANT,
+    ICON_ID_TREE,
+    ICON_ID_WOMAN,
+    ICON_ID_FENCE,
+    ICON_ID_FENCE_END,
+    ICON_ID_FENCE_VERTICAL_END,
+    ICON_ID_FENCE_VERTICAL_START,
+    ICON_ID_FLOWER,
+    ICON_ID_LAKE_BOTTOM,
+    ICON_ID_LAKE_BOTTOM_LEFT,
+    ICON_ID_LAKE_BOTTOM_RIGHT,
+    ICON_ID_LAKE_LEFT,
+    ICON_ID_LAKE_RIGHT,
+    ICON_ID_LAKE_TOP,
+    ICON_ID_LAKE_TOP_LEFT,
+    ICON_ID_LAKE_TOP_RIGHT,
+    ICON_ID_ROCK_LARGE,
+    ICON_ID_ROCK_MEDIUM,
+    ICON_ID_ROCK_SMALL,
 } IconID;
 
 typedef struct
 {
     IconID id;
     const Icon *icon;
-    Vector size;
-} IconContext;
+    Vector pos;  // position at which to draw the icon
+    Vector size; // dimensions for centering
+} IconSpec;
+
+typedef struct
+{
+    int count;       // number of icons in this group
+    IconSpec *icons; // pointer to an array of icon specs
+} IconGroupContext;
+
+extern IconGroupContext *g_current_icon_group;
 
 extern const EntityDescription icon_desc;
-IconContext *get_icon_context(const char *name);
+// For debugging, you might want to declare functions to allocate and free the pool.
+void icon_group_context_free(IconGroupContext *ctx);

+ 37 - 0
game/player.c

@@ -1,4 +1,5 @@
 #include <game/player.h>
+#include <game/icon.h>
 #include <game/storage.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -208,6 +209,39 @@ static void vgm_direction(Imu *imu, PlayerContext *player, Vector *pos)
         player->direction = ENTITY_UP;
     }
 }
+
+// This static function handles collisions with icons.
+// It receives the player entity pointer, the player's current position, and a pointer to PlayerContext.
+static void handle_collision(Entity *playerEntity, Vector playerPos, PlayerContext *player)
+{
+    // If there is no active icon group, do nothing.
+    if (!g_current_icon_group)
+        return;
+
+    // Loop over all icon specifications in the current icon group.
+    for (int i = 0; i < g_current_icon_group->count; i++)
+    {
+        IconSpec *spec = &g_current_icon_group->icons[i];
+
+        // Calculate the difference between player's position and the icon's center.
+        float dx = playerPos.x - spec->pos.x;
+        float dy = playerPos.y - spec->pos.y;
+
+        // Use an approximate collision radius:
+        float radius = (spec->size.x + spec->size.y) / 4.0f;
+
+        // Collision: if player's distance to the icon center is less than the collision radius.
+        if ((dx * dx + dy * dy) < (radius * radius))
+        {
+            // Revert the player's position and reset movement.
+            entity_pos_set(playerEntity, player->old_position);
+            player->dx = 0;
+            player->dy = 0;
+            break;
+        }
+    }
+}
+
 uint16_t elapsed_ws_timer = 0;
 static void player_update(Entity *self, GameManager *manager, void *context)
 {
@@ -479,6 +513,9 @@ static void player_update(Entity *self, GameManager *manager, void *context)
     }
     else
         player->state = ENTITY_MOVING;
+
+    // handle icon collision
+    handle_collision(self, pos, player);
 }
 
 static void draw_tutorial(Canvas *canvas, GameManager *manager)

+ 233 - 57
game/world.c

@@ -1,6 +1,7 @@
 #include <game/world.h>
 #include <game/storage.h>
 #include <flip_storage/storage.h>
+#include "game/icon.h"
 
 bool draw_json_world_furi(GameManager *manager, Level *level, const FuriString *json_data)
 {
@@ -9,82 +10,257 @@ bool draw_json_world_furi(GameManager *manager, Level *level, const FuriString *
         FURI_LOG_E("Game", "JSON data is NULL");
         return false;
     }
-    int levels_added = 0;
-    FURI_LOG_I("Game", "Looping through world data");
+
+    // Pass 1: Count the total number of icons.
+    int total_icons = 0;
     for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
     {
-        FURI_LOG_I("Game", "Looping through world data: %d", i);
         FuriString *data = get_json_array_value_furi("json_data", i, json_data);
         if (!data)
-        {
             break;
-        }
-
-        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);
-
-        if (!icon || !x || !y || !amount || !horizontal)
+        if (amount)
         {
-            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;
+            int count = atoi(furi_string_get_cstr(amount));
+            if (count < 1)
+                count = 1;
+            total_icons += count;
+            furi_string_free(amount);
         }
+        furi_string_free(data);
+    }
+    FURI_LOG_I("Game", "Total icons to spawn: %d", total_icons);
+
+    // Allocate the icon group context (local instance)
+    IconGroupContext igctx;
+    igctx.count = total_icons;
+    igctx.icons = malloc(total_icons * sizeof(IconSpec));
+    if (!igctx.icons)
+    {
+        FURI_LOG_E("Game", "Failed to allocate icon group array for %d icons", total_icons);
+        return false;
+    }
+    GameContext *game_context = game_manager_game_context_get(manager);
+    game_context->icon_count = total_icons;
+
+    // Pass 2: Parse the JSON to fill the icon specs.
+    int spec_index = 0;
+    for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
+    {
+        FuriString *data = get_json_array_value_furi("json_data", i, json_data);
+        if (!data)
+            break;
+
+        FuriString *icon_str = get_json_value_furi("icon", data);
+        FuriString *x_str = get_json_value_furi("x", data);
+        FuriString *y_str = get_json_value_furi("y", data);
+        FuriString *amount_str = get_json_value_furi("amount", data);
+        FuriString *horizontal_str = get_json_value_furi("horizontal", data);
 
-        int count = atoi(furi_string_get_cstr(amount));
-        if (count < 2)
+        if (!icon_str || !x_str || !y_str || !amount_str || !horizontal_str)
         {
-            // Just one icon
-            spawn_icon(
-                manager,
-                level,
-                furi_string_get_cstr(icon),
-                atoi(furi_string_get_cstr(x)),
-                atoi(furi_string_get_cstr(y)));
+            FURI_LOG_E("Game", "Incomplete icon data: %s", furi_string_get_cstr(data));
+            if (icon_str)
+                furi_string_free(icon_str);
+            if (x_str)
+                furi_string_free(x_str);
+            if (y_str)
+                furi_string_free(y_str);
+            if (amount_str)
+                furi_string_free(amount_str);
+            if (horizontal_str)
+                furi_string_free(horizontal_str);
+            furi_string_free(data);
+            continue;
         }
-        else
+
+        int count = atoi(furi_string_get_cstr(amount_str));
+        if (count < 1)
+            count = 1;
+        float base_x = atof_furi(x_str);
+        float base_y = atof_furi(y_str);
+        bool is_horizontal = (furi_string_cmp(horizontal_str, "true") == 0);
+        int spacing = 17;
+
+        for (int j = 0; j < count; j++)
         {
-            bool is_horizontal = (furi_string_cmp(horizontal, "true") == 0);
-            spawn_icon_line(
-                manager,
-                level,
-                furi_string_get_cstr(icon),
-                atoi(furi_string_get_cstr(x)),
-                atoi(furi_string_get_cstr(y)),
-                count,
-                is_horizontal,
-                17 // set as 17 for now
-            );
+            IconSpec *spec = &igctx.icons[spec_index];
+            if (is_horizontal)
+            {
+                spec->pos.x = base_x + (j * spacing);
+                spec->pos.y = base_y;
+            }
+            else
+            {
+                spec->pos.x = base_x;
+                spec->pos.y = base_y + (j * spacing);
+            }
+            const char *name = furi_string_get_cstr(icon_str);
+            if (is_str(name, "house"))
+            {
+                spec->id = ICON_ID_HOUSE;
+                spec->icon = &I_icon_house_48x32px;
+                spec->size = (Vector){48, 32};
+            }
+            else if (is_str(name, "man"))
+            {
+                spec->id = ICON_ID_MAN;
+                spec->icon = &I_icon_man_7x16;
+                spec->size = (Vector){7, 16};
+            }
+            else if (is_str(name, "plant"))
+            {
+                spec->id = ICON_ID_PLANT;
+                spec->icon = &I_icon_plant_16x16;
+                spec->size = (Vector){16, 16};
+            }
+            else if (is_str(name, "tree"))
+            {
+                spec->id = ICON_ID_TREE;
+                spec->icon = &I_icon_tree_16x16;
+                spec->size = (Vector){16, 16};
+            }
+            else if (is_str(name, "woman"))
+            {
+                spec->id = ICON_ID_WOMAN;
+                spec->icon = &I_icon_woman_9x16;
+                spec->size = (Vector){9, 16};
+            }
+            else if (is_str(name, "fence"))
+            {
+                spec->id = ICON_ID_FENCE;
+                spec->icon = &I_icon_fence_16x8px;
+                spec->size = (Vector){16, 8};
+            }
+            else if (is_str(name, "fence_end"))
+            {
+                spec->id = ICON_ID_FENCE_END;
+                spec->icon = &I_icon_fence_end_16x8px;
+                spec->size = (Vector){16, 8};
+            }
+            else if (is_str(name, "fence_vertical_end"))
+            {
+                spec->id = ICON_ID_FENCE_VERTICAL_END;
+                spec->icon = &I_icon_fence_vertical_end_6x8px;
+                spec->size = (Vector){6, 8};
+            }
+            else if (is_str(name, "fence_vertical_start"))
+            {
+                spec->id = ICON_ID_FENCE_VERTICAL_START;
+                spec->icon = &I_icon_fence_vertical_start_6x15px;
+                spec->size = (Vector){6, 15};
+            }
+            else if (is_str(name, "flower"))
+            {
+                spec->id = ICON_ID_FLOWER;
+                spec->icon = &I_icon_flower_16x16;
+                spec->size = (Vector){16, 16};
+            }
+            else if (is_str(name, "lake_bottom"))
+            {
+                spec->id = ICON_ID_LAKE_BOTTOM;
+                spec->icon = &I_icon_lake_bottom_31x12px;
+                spec->size = (Vector){31, 12};
+            }
+            else if (is_str(name, "lake_bottom_left"))
+            {
+                spec->id = ICON_ID_LAKE_BOTTOM_LEFT;
+                spec->icon = &I_icon_lake_bottom_left_24x22px;
+                spec->size = (Vector){24, 22};
+            }
+            else if (is_str(name, "lake_bottom_right"))
+            {
+                spec->id = ICON_ID_LAKE_BOTTOM_RIGHT;
+                spec->icon = &I_icon_lake_bottom_right_24x22px;
+                spec->size = (Vector){24, 22};
+            }
+            else if (is_str(name, "lake_left"))
+            {
+                spec->id = ICON_ID_LAKE_LEFT;
+                spec->icon = &I_icon_lake_left_11x31px;
+                spec->size = (Vector){11, 31};
+            }
+            else if (is_str(name, "lake_right"))
+            {
+                spec->id = ICON_ID_LAKE_RIGHT;
+                spec->icon = &I_icon_lake_right_11x31;
+                spec->size = (Vector){11, 31};
+            }
+            else if (is_str(name, "lake_top"))
+            {
+                spec->id = ICON_ID_LAKE_TOP;
+                spec->icon = &I_icon_lake_top_31x12px;
+                spec->size = (Vector){31, 12};
+            }
+            else if (is_str(name, "lake_top_left"))
+            {
+                spec->id = ICON_ID_LAKE_TOP_LEFT;
+                spec->icon = &I_icon_lake_top_left_24x22px;
+                spec->size = (Vector){24, 22};
+            }
+            else if (is_str(name, "lake_top_right"))
+            {
+                spec->id = ICON_ID_LAKE_TOP_RIGHT;
+                spec->icon = &I_icon_lake_top_right_24x22px;
+                spec->size = (Vector){24, 22};
+            }
+            else if (is_str(name, "rock_large"))
+            {
+                spec->id = ICON_ID_ROCK_LARGE;
+                spec->icon = &I_icon_rock_large_18x19px;
+                spec->size = (Vector){18, 19};
+            }
+            else if (is_str(name, "rock_medium"))
+            {
+                spec->id = ICON_ID_ROCK_MEDIUM;
+                spec->icon = &I_icon_rock_medium_16x14px;
+                spec->size = (Vector){16, 14};
+            }
+            else if (is_str(name, "rock_small"))
+            {
+                spec->id = ICON_ID_ROCK_SMALL;
+                spec->icon = &I_icon_rock_small_10x8px;
+                spec->size = (Vector){10, 8};
+            }
+            else
+            {
+                FURI_LOG_E("Game", "Icon name not recognized: %s", name);
+                continue;
+            }
+            spec_index++;
         }
 
+        furi_string_free(icon_str);
+        furi_string_free(x_str);
+        furi_string_free(y_str);
+        furi_string_free(amount_str);
+        furi_string_free(horizontal_str);
         furi_string_free(data);
-        furi_string_free(icon);
-        furi_string_free(x);
-        furi_string_free(y);
-        furi_string_free(amount);
-        furi_string_free(horizontal);
-        levels_added++;
     }
+
+    // Spawn one icon group entity.
+    Entity *groupEntity = level_add_entity(level, &icon_desc);
+    IconGroupContext *entityContext = (IconGroupContext *)entity_context_get(groupEntity);
+    if (entityContext)
+    {
+        memcpy(entityContext, &igctx, sizeof(IconGroupContext));
+    }
+    else
+    {
+        FURI_LOG_E("Game", "Failed to get entity context for icon group");
+        free(igctx.icons);
+        return false;
+    }
+
+    // Set the global pointer so that player collision logic can use it.
+    g_current_icon_group = entityContext;
+
     FURI_LOG_I("Game", "Finished loading world data");
-    return levels_added > 0;
+    return true;
 }
 
+// The remainder of the file (draw_town_world, draw_pvp_world, etc.) remains unchanged.
+
 static void draw_town_world(Level *level, GameManager *manager, void *context)
 {
     UNUSED(context);