فهرست منبع

edit json, sync v0.1 -> 0.2, load/save player info

jblanked 1 سال پیش
والد
کامیت
de1ba585ba
18فایلهای تغییر یافته به همراه510 افزوده شده و 279 حذف شده
  1. 119 0
      app.c
  2. 3 3
      flip_storage/storage.c
  3. 1 0
      flip_world.h
  4. 1 0
      game/draw.h
  5. 1 1
      game/enemy.h
  6. 7 200
      game/game.c
  7. 1 60
      game/game.h
  8. 1 1
      game/level.c
  9. 215 0
      game/player.c
  10. 61 0
      game/player.h
  11. 75 0
      game/storage.c
  12. 8 0
      game/storage.h
  13. 1 1
      game/world.c
  14. 1 1
      jsmn/jsmn.c
  15. 1 7
      jsmn/jsmn.h
  16. 1 4
      jsmn/jsmn_furi.c
  17. 1 1
      jsmn/jsmn_furi.h
  18. 12 0
      jsmn/jsmn_h.h

+ 119 - 0
app.c

@@ -42,6 +42,125 @@ int32_t flip_world_main(void *p)
         easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
         easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
     }
     }
 
 
+    // this will be removed in version 0.3. we'll keep all our data in the data folder from now on
+    // load app version
+    char saved_app_version[16];
+    if (load_char("app_version", saved_app_version, sizeof(saved_app_version)))
+    {
+        float saved_version = strtod(saved_app_version, NULL);
+        if (saved_version == 0.1)
+        {
+            // transfer files over into the data folder (to bs used to load the player context)
+            char directory_path[256];
+            snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
+
+            // Create the directory
+            Storage *storage = furi_record_open(RECORD_STORAGE);
+            storage_common_mkdir(storage, directory_path);
+
+            // copy the whole folder
+            char source_path[256];
+            snprintf(source_path, sizeof(source_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
+
+            if (storage_common_migrate(storage, source_path, directory_path) != FSE_OK)
+            {
+                FURI_LOG_E(TAG, "Failed to migrate files");
+            }
+            else
+            {
+
+                void clean_up(char *file_path)
+                {
+                    char updated_file_path[128];
+                    snprintf(updated_file_path, sizeof(updated_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s", file_path);
+
+                    // check if the file exists
+                    if (storage_file_exists(storage, updated_file_path) &&
+                        storage_common_remove(storage, updated_file_path) != FSE_OK)
+                    {
+                        FURI_LOG_E(TAG, "Failed to delete %s", updated_file_path);
+                    }
+
+                    // check if the directory exists
+                    if (storage_dir_exists(storage, updated_file_path) &&
+                        storage_common_remove(storage, updated_file_path) != FSE_OK)
+                    {
+                        FURI_LOG_E(TAG, "Failed to delete %s", updated_file_path);
+                    }
+                }
+
+                // clean up
+                clean_up("WiFi-SSID.txt");
+                clean_up("WiFi-Password.txt");
+                clean_up("Flip-Social-Username.txt");
+                clean_up("Flip-Social-Password.txt");
+                clean_up("Game-FPS.txt");
+                clean_up("Game-Screen-Always-On.txt");
+                clean_up("is_logged_in.txt");
+                clean_up("data/worlds");
+                clean_up("data/settings.bin");
+            }
+        }
+    }
+    else
+    {
+        // transfer files over into the data folder (to bs used to load the player context)
+        char directory_path[256];
+        snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
+
+        // Create the directory
+        Storage *storage = furi_record_open(RECORD_STORAGE);
+        storage_common_mkdir(storage, directory_path);
+
+        // copy the whole folder
+        char source_path[256];
+        snprintf(source_path, sizeof(source_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
+
+        if (storage_common_migrate(storage, source_path, directory_path) != FSE_OK)
+        {
+            FURI_LOG_E(TAG, "Failed to migrate files");
+        }
+        else
+        {
+
+            void clean_up(char *file_path)
+            {
+                char updated_file_path[128];
+                snprintf(updated_file_path, sizeof(updated_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s", file_path);
+
+                // check if the file exists
+                if (storage_file_exists(storage, updated_file_path) &&
+                    storage_common_remove(storage, updated_file_path) != FSE_OK)
+                {
+                    FURI_LOG_E(TAG, "Failed to delete %s", updated_file_path);
+                }
+
+                // check if the directory exists
+                if (storage_dir_exists(storage, updated_file_path) &&
+                    storage_common_remove(storage, updated_file_path) != FSE_OK)
+                {
+                    FURI_LOG_E(TAG, "Failed to delete %s", updated_file_path);
+                }
+            }
+
+            // clean up
+            clean_up("WiFi-SSID.txt");
+            clean_up("WiFi-Password.txt");
+            clean_up("Flip-Social-Username.txt");
+            clean_up("Flip-Social-Password.txt");
+            clean_up("Game-FPS.txt");
+            clean_up("Game-Screen-Always-On.txt");
+            clean_up("is_logged_in.txt");
+            clean_up("data/worlds");
+            clean_up("data/settings.bin");
+        }
+    }
+
+    // svae app version
+    char app_version[16];
+    snprintf(app_version, sizeof(app_version), "%f", (double)VERSION);
+    save_char("app_version", app_version);
+
     // Run the view dispatcher
     // Run the view dispatcher
     view_dispatcher_run(app->view_dispatcher);
     view_dispatcher_run(app->view_dispatcher);
 
 

+ 3 - 3
flip_storage/storage.c

@@ -170,7 +170,7 @@ bool save_char(
     }
     }
     // Create the directory for saving settings
     // Create the directory for saving settings
     char directory_path[256];
     char directory_path[256];
-    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
 
 
     // Create the directory
     // Create the directory
     Storage *storage = furi_record_open(RECORD_STORAGE);
     Storage *storage = furi_record_open(RECORD_STORAGE);
@@ -179,7 +179,7 @@ bool save_char(
     // Open the settings file
     // Open the settings file
     File *file = storage_file_alloc(storage);
     File *file = storage_file_alloc(storage);
     char file_path[256];
     char file_path[256];
-    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s.txt", path_name);
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/%s.txt", path_name);
 
 
     // Open the file in write mode
     // Open the file in write mode
     if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
     if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
@@ -221,7 +221,7 @@ bool load_char(
     File *file = storage_file_alloc(storage);
     File *file = storage_file_alloc(storage);
 
 
     char file_path[256];
     char file_path[256];
-    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/%s.txt", path_name);
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/%s.txt", path_name);
 
 
     // Open the file for reading
     // Open the file for reading
     if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
     if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))

+ 1 - 0
flip_world.h

@@ -12,6 +12,7 @@
 #include <dialogs/dialogs.h>
 #include <dialogs/dialogs.h>
 
 
 #define TAG "FlipWorld"
 #define TAG "FlipWorld"
+#define VERSION 0.2
 #define VERSION_TAG "FlipWorld v0.2"
 #define VERSION_TAG "FlipWorld v0.2"
 
 
 // Define the submenu items for our FlipWorld application
 // Define the submenu items for our FlipWorld application

+ 1 - 0
game/draw.h

@@ -1,5 +1,6 @@
 #pragma once
 #pragma once
 #include "game/icon.h"
 #include "game/icon.h"
+#include <game/player.h>
 
 
 // Global variables to store camera position
 // Global variables to store camera position
 extern int camera_x;
 extern int camera_x;

+ 1 - 1
game/enemy.h

@@ -1,5 +1,5 @@
 #pragma once
 #pragma once
-#include "game.h"
+#include <game/game.h>
 #include "flip_world.h"
 #include "flip_world.h"
 
 
 typedef enum
 typedef enum

+ 7 - 200
game/game.c

@@ -1,199 +1,5 @@
-#include "game.h"
-
-/****** Entities: Player ******/
-
-static Level *get_next_level(GameManager *manager)
-{
-    Level *current_level = game_manager_current_level_get(manager);
-    GameContext *game_context = game_manager_game_context_get(manager);
-    for (int i = 0; i < game_context->level_count; i++)
-    {
-        if (game_context->levels[i] == current_level)
-        {
-            // check if i+1 is out of bounds, if so, return the first level
-            game_context->current_level = (i + 1) % game_context->level_count;
-            return game_context->levels[(i + 1) % game_context->level_count] ? game_context->levels[(i + 1) % game_context->level_count] : game_context->levels[0];
-        }
-    }
-    game_context->current_level = 0;
-    return game_context->levels[0] ? game_context->levels[0] : game_manager_add_level(manager, generic_level("town_world", 0));
-}
-
-void player_spawn(Level *level, GameManager *manager)
-{
-    GameContext *game_context = game_manager_game_context_get(manager);
-    game_context->players[0] = level_add_entity(level, &player_desc);
-
-    // Set player position.
-    entity_pos_set(game_context->players[0], (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
-
-    // Add collision box to player entity
-    // Box is centered in player x and y, and it's size is 10x10
-    entity_collider_add_rect(game_context->players[0], 10 + PLAYER_COLLISION_HORIZONTAL, 10 + PLAYER_COLLISION_VERTICAL);
-
-    // Get player context
-    PlayerContext *player_context = entity_context_get(game_context->players[0]);
-
-    // Load player sprite
-    player_context->sprite_right = game_manager_sprite_load(manager, "player_right.fxbm");
-    player_context->sprite_left = game_manager_sprite_load(manager, "player_left.fxbm");
-    player_context->direction = PLAYER_RIGHT; // default direction
-    player_context->health = 100;
-    player_context->strength = 10;
-    player_context->level = 1;
-    player_context->xp = 0;
-    player_context->start_position = entity_pos_get(game_context->players[0]);
-    player_context->attack_timer = 0.5f;
-    player_context->elapsed_attack_timer = player_context->attack_timer;
-    player_context->health_regen = 1; // 1 health per second
-    player_context->elapsed_health_regen = 0;
-    player_context->max_health = 100 + ((player_context->level - 1) * 10); // 10 health per level
-
-    // Set player username
-    if (!load_char("Flip-Social-Username", player_context->username, 32))
-    {
-        snprintf(player_context->username, 32, "Player");
-    }
-
-    game_context->player_context = player_context;
-}
-
-// Modify player_update to track direction
-static void player_update(Entity *self, GameManager *manager, void *context)
-{
-    PlayerContext *player = (PlayerContext *)context;
-    InputState input = game_manager_input_get(manager);
-    Vector pos = entity_pos_get(self);
-    GameContext *game_context = game_manager_game_context_get(manager);
-
-    // apply health regeneration
-    player->elapsed_health_regen += 1.0f / game_context->fps;
-    if (player->elapsed_health_regen >= 1.0f && player->health < player->max_health)
-    {
-        player->health += player->health_regen;
-        player->elapsed_health_regen = 0;
-    }
-
-    // Increment the elapsed_attack_timer for the player
-    player->elapsed_attack_timer += 1.0f / game_context->fps;
-
-    // Store previous direction
-    int prev_dx = player->dx;
-    int prev_dy = player->dy;
-
-    // Reset movement deltas each frame
-    player->dx = 0;
-    player->dy = 0;
-
-    // Handle movement input
-    if (input.held & GameKeyUp)
-    {
-        pos.y -= 2;
-        player->dy = -1;
-        player->direction = PLAYER_UP;
-        game_context->user_input = GameKeyUp;
-    }
-    if (input.held & GameKeyDown)
-    {
-        pos.y += 2;
-        player->dy = 1;
-        player->direction = PLAYER_DOWN;
-        game_context->user_input = GameKeyDown;
-    }
-    if (input.held & GameKeyLeft)
-    {
-        pos.x -= 2;
-        player->dx = -1;
-        player->direction = PLAYER_LEFT;
-        game_context->user_input = GameKeyLeft;
-    }
-    if (input.held & GameKeyRight)
-    {
-        pos.x += 2;
-        player->dx = 1;
-        player->direction = PLAYER_RIGHT;
-        game_context->user_input = GameKeyRight;
-    }
-
-    // Clamp the player's position to stay within world bounds
-    pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
-    pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
-
-    // Update player position
-    entity_pos_set(self, pos);
-
-    // switch levels if holding OK
-    if (input.held & GameKeyOk)
-    {
-        // if all enemies are dead, allow the "OK" button to switch levels
-        // otherwise the "OK" button will be used to attack
-        if (game_context->enemy_count == 0)
-        {
-            game_manager_next_level_set(manager, get_next_level(manager));
-            furi_delay_ms(500);
-        }
-        else
-        {
-            game_context->user_input = GameKeyOk;
-            furi_delay_ms(100);
-        }
-        return;
-    }
-
-    // If the player is not moving, retain the last movement direction
-    if (player->dx == 0 && player->dy == 0)
-    {
-        player->dx = prev_dx;
-        player->dy = prev_dy;
-        player->state = PLAYER_IDLE;
-        game_context->user_input = -1; // reset user input
-    }
-    else
-    {
-        player->state = PLAYER_MOVING;
-    }
-
-    // Handle back button to stop the game
-    if (input.pressed & GameKeyBack)
-    {
-        game_manager_game_stop(manager);
-    }
-}
-
-static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
-{
-    // Get player context
-    UNUSED(manager);
-    PlayerContext *player = context;
-
-    // Get player position
-    Vector pos = entity_pos_get(self);
-
-    // Draw background (updates camera_x and camera_y)
-    draw_background(canvas, pos);
-
-    // Draw player sprite relative to camera, centered on the player's position
-    canvas_draw_sprite(
-        canvas,
-        player->direction == PLAYER_RIGHT ? player->sprite_right : player->sprite_left,
-        pos.x - camera_x - 5, // Center the sprite horizontally
-        pos.y - camera_y - 5  // Center the sprite vertically
-    );
-
-    // draw username over player's head
-    canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
-    canvas_draw_str(canvas, pos.x - camera_x - (strlen(player->username) * 2), pos.y - camera_y - 7, player->username);
-}
-
-const EntityDescription player_desc = {
-    .start = NULL,                         // called when entity is added to the level
-    .stop = NULL,                          // called when entity is removed from the level
-    .update = player_update,               // called every frame
-    .render = player_render,               // called every frame, after update
-    .collision = NULL,                     // called when entity collides with another entity
-    .event = NULL,                         // called when entity receives an event
-    .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
-};
+#include <game/game.h>
+#include <game/storage.h>
 
 
 /****** Game ******/
 /****** Game ******/
 /*
 /*
@@ -270,10 +76,11 @@ static void game_start(GameManager *game_manager, void *ctx)
 static void game_stop(void *ctx)
 static void game_stop(void *ctx)
 {
 {
     GameContext *game_context = ctx;
     GameContext *game_context = ctx;
-    // 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)
+    save_player_context(game_context->player_context);
+    if (game_context->player_context)
+    {
+        free(game_context->player_context);
+    }
     for (int i = 0; i < game_context->level_count; i++)
     for (int i = 0; i < game_context->level_count; i++)
     {
     {
         game_context->levels[i] = NULL;
         game_context->levels[i] = NULL;

+ 1 - 60
game/game.h

@@ -4,63 +4,4 @@
 #include <game/level.h>
 #include <game/level.h>
 #include <game/enemy.h>
 #include <game/enemy.h>
 #include "flip_world.h"
 #include "flip_world.h"
-#include "flip_storage/storage.h"
-
-#define PLAYER_COLLISION_VERTICAL 5
-#define PLAYER_COLLISION_HORIZONTAL 5
-
-typedef enum
-{
-    PLAYER_IDLE,
-    PLAYER_MOVING,
-    PLAYER_ATTACKING,
-    PLAYER_ATTACKED,
-    PLAYER_DEAD,
-} PlayerState;
-
-typedef enum
-{
-    PLAYER_UP,
-    PLAYER_DOWN,
-    PLAYER_LEFT,
-    PLAYER_RIGHT
-} PlayerDirection;
-
-typedef struct
-{
-    PlayerDirection direction;  // direction the player is facing
-    PlayerState state;          // current state of the player
-    Vector start_position;      // starting position of the player
-    Sprite *sprite_right;       // player sprite looking right
-    Sprite *sprite_left;        // player sprite looking left
-    int8_t dx;                  // x direction
-    int8_t dy;                  // y direction
-    uint32_t xp;                // experience points
-    uint32_t level;             // player level
-    uint32_t strength;          // player strength
-    uint32_t health;            // player health
-    uint32_t max_health;        // player maximum health
-    uint32_t health_regen;      // player health regeneration rate per second/frame
-    float elapsed_health_regen; // time elapsed since last health regeneration
-    float attack_timer;         // Cooldown duration between attacks
-    float elapsed_attack_timer; // Time elapsed since the last attack
-    char username[32];          // player username
-} PlayerContext;
-
-typedef struct
-{
-    PlayerContext *player_context;
-    Level *levels[10];
-    Entity *enemies[2];
-    Entity *players[1];
-    GameKey user_input;
-    float fps;
-    int level_count;
-    int enemy_count;
-    int current_level;
-} GameContext;
-
-extern const EntityDescription player_desc;
-void player_spawn(Level *level, GameManager *manager);
-
-// Get game context: GameContext* game_context = game_manager_game_context_get(manager);
+#include <game/player.h>

+ 1 - 1
game/level.c

@@ -1,5 +1,5 @@
 #include <game/level.h>
 #include <game/level.h>
-
+#include <flip_storage/storage.h>
 static void level_start(Level *level, GameManager *manager, void *context)
 static void level_start(Level *level, GameManager *manager, void *context)
 {
 {
     UNUSED(manager);
     UNUSED(manager);

+ 215 - 0
game/player.c

@@ -0,0 +1,215 @@
+#include <game/player.h>
+#include <game/storage.h>
+/****** Entities: Player ******/
+
+static Level *get_next_level(GameManager *manager)
+{
+    Level *current_level = game_manager_current_level_get(manager);
+    GameContext *game_context = game_manager_game_context_get(manager);
+    for (int i = 0; i < game_context->level_count; i++)
+    {
+        if (game_context->levels[i] == current_level)
+        {
+            // check if i+1 is out of bounds, if so, return the first level
+            game_context->current_level = (i + 1) % game_context->level_count;
+            return game_context->levels[(i + 1) % game_context->level_count] ? game_context->levels[(i + 1) % game_context->level_count] : game_context->levels[0];
+        }
+    }
+    game_context->current_level = 0;
+    return game_context->levels[0] ? game_context->levels[0] : game_manager_add_level(manager, generic_level("town_world", 0));
+}
+
+void player_spawn(Level *level, GameManager *manager)
+{
+    GameContext *game_context = game_manager_game_context_get(manager);
+    game_context->players[0] = level_add_entity(level, &player_desc);
+
+    // Set player position.
+    entity_pos_set(game_context->players[0], (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
+
+    // Add collision box to player entity
+    // Box is centered in player x and y, and it's size is 10x10
+    entity_collider_add_rect(game_context->players[0], 10 + PLAYER_COLLISION_HORIZONTAL, 10 + PLAYER_COLLISION_VERTICAL);
+
+    // Get player context
+    PlayerContext *player_context = entity_context_get(game_context->players[0]);
+    PlayerContext *loaded_player_context = load_player_context();
+
+    if (!loaded_player_context)
+    {
+        // Load player sprite
+        player_context->sprite_right = game_manager_sprite_load(manager, "player_right.fxbm");
+        player_context->sprite_left = game_manager_sprite_load(manager, "player_left.fxbm");
+        player_context->direction = PLAYER_RIGHT; // default direction
+        player_context->health = 100;
+        player_context->strength = 10;
+        player_context->level = 1;
+        player_context->xp = 0;
+        player_context->start_position = entity_pos_get(game_context->players[0]);
+        player_context->attack_timer = 0.5f;
+        player_context->elapsed_attack_timer = player_context->attack_timer;
+        player_context->health_regen = 1; // 1 health per second
+        player_context->elapsed_health_regen = 0;
+        player_context->max_health = 100 + ((player_context->level - 1) * 10); // 10 health per level
+
+        // Set player username
+        if (!load_char("Flip-Social-Username", player_context->username, 32))
+        {
+            snprintf(player_context->username, 32, "Player");
+        }
+
+        game_context->player_context = player_context;
+        return;
+    }
+
+    // Copy loaded player context to player context
+    memcpy(player_context, loaded_player_context, sizeof(PlayerContext));
+    game_context->player_context = player_context;
+
+    // Free loaded player context
+    free(loaded_player_context);
+
+    // Load player sprite (we'll add this to the JSON later when players can choose their sprite)
+    player_context->sprite_right = game_manager_sprite_load(manager, "player_right.fxbm");
+    player_context->sprite_left = game_manager_sprite_load(manager, "player_left.fxbm");
+
+    // save the player context to storage
+    save_player_context(player_context);
+}
+
+// Modify player_update to track direction
+static void player_update(Entity *self, GameManager *manager, void *context)
+{
+    PlayerContext *player = (PlayerContext *)context;
+    InputState input = game_manager_input_get(manager);
+    Vector pos = entity_pos_get(self);
+    GameContext *game_context = game_manager_game_context_get(manager);
+
+    // apply health regeneration
+    player->elapsed_health_regen += 1.0f / game_context->fps;
+    if (player->elapsed_health_regen >= 1.0f && player->health < player->max_health)
+    {
+        player->health += (player->health_regen + player->health > player->max_health) ? player->max_health - player->health : player->health_regen;
+        player->elapsed_health_regen = 0;
+    }
+
+    // Increment the elapsed_attack_timer for the player
+    player->elapsed_attack_timer += 1.0f / game_context->fps;
+
+    // Store previous direction
+    int prev_dx = player->dx;
+    int prev_dy = player->dy;
+
+    // Reset movement deltas each frame
+    player->dx = 0;
+    player->dy = 0;
+
+    // Handle movement input
+    if (input.held & GameKeyUp)
+    {
+        pos.y -= 2;
+        player->dy = -1;
+        player->direction = PLAYER_UP;
+        game_context->user_input = GameKeyUp;
+    }
+    if (input.held & GameKeyDown)
+    {
+        pos.y += 2;
+        player->dy = 1;
+        player->direction = PLAYER_DOWN;
+        game_context->user_input = GameKeyDown;
+    }
+    if (input.held & GameKeyLeft)
+    {
+        pos.x -= 2;
+        player->dx = -1;
+        player->direction = PLAYER_LEFT;
+        game_context->user_input = GameKeyLeft;
+    }
+    if (input.held & GameKeyRight)
+    {
+        pos.x += 2;
+        player->dx = 1;
+        player->direction = PLAYER_RIGHT;
+        game_context->user_input = GameKeyRight;
+    }
+
+    // Clamp the player's position to stay within world bounds
+    pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
+    pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
+
+    // Update player position
+    entity_pos_set(self, pos);
+
+    // switch levels if holding OK
+    if (input.held & GameKeyOk)
+    {
+        // if all enemies are dead, allow the "OK" button to switch levels
+        // otherwise the "OK" button will be used to attack
+        if (game_context->enemy_count == 0)
+        {
+            game_manager_next_level_set(manager, get_next_level(manager));
+            furi_delay_ms(500);
+        }
+        else
+        {
+            game_context->user_input = GameKeyOk;
+            furi_delay_ms(100);
+        }
+        return;
+    }
+
+    // If the player is not moving, retain the last movement direction
+    if (player->dx == 0 && player->dy == 0)
+    {
+        player->dx = prev_dx;
+        player->dy = prev_dy;
+        player->state = PLAYER_IDLE;
+        game_context->user_input = -1; // reset user input
+    }
+    else
+    {
+        player->state = PLAYER_MOVING;
+    }
+
+    // Handle back button to stop the game
+    if (input.pressed & GameKeyBack)
+    {
+        game_manager_game_stop(manager);
+    }
+}
+
+static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
+{
+    // Get player context
+    UNUSED(manager);
+    PlayerContext *player = context;
+
+    // Get player position
+    Vector pos = entity_pos_get(self);
+
+    // Draw background (updates camera_x and camera_y)
+    draw_background(canvas, pos);
+
+    // Draw player sprite relative to camera, centered on the player's position
+    canvas_draw_sprite(
+        canvas,
+        player->direction == PLAYER_RIGHT ? player->sprite_right : player->sprite_left,
+        pos.x - camera_x - 5, // Center the sprite horizontally
+        pos.y - camera_y - 5  // Center the sprite vertically
+    );
+
+    // draw username over player's head
+    canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+    canvas_draw_str(canvas, pos.x - camera_x - (strlen(player->username) * 2), pos.y - camera_y - 7, player->username);
+}
+
+const EntityDescription player_desc = {
+    .start = NULL,                         // called when entity is added to the level
+    .stop = NULL,                          // called when entity is removed from the level
+    .update = player_update,               // called every frame
+    .render = player_render,               // called every frame, after update
+    .collision = NULL,                     // called when entity collides with another entity
+    .event = NULL,                         // called when entity receives an event
+    .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
+};

+ 61 - 0
game/player.h

@@ -0,0 +1,61 @@
+#pragma once
+#include "engine/engine.h"
+#include <flip_world.h>
+#include <game/game.h>
+
+#define PLAYER_COLLISION_VERTICAL 5
+#define PLAYER_COLLISION_HORIZONTAL 5
+
+typedef enum
+{
+    PLAYER_IDLE,
+    PLAYER_MOVING,
+    PLAYER_ATTACKING,
+    PLAYER_ATTACKED,
+    PLAYER_DEAD,
+} PlayerState;
+
+typedef enum
+{
+    PLAYER_UP,
+    PLAYER_DOWN,
+    PLAYER_LEFT,
+    PLAYER_RIGHT
+} PlayerDirection;
+
+typedef struct
+{
+    PlayerDirection direction;  // direction the player is facing
+    PlayerState state;          // current state of the player
+    Vector start_position;      // starting position of the player
+    Sprite *sprite_right;       // player sprite looking right
+    Sprite *sprite_left;        // player sprite looking left
+    int8_t dx;                  // x direction
+    int8_t dy;                  // y direction
+    uint32_t xp;                // experience points
+    uint32_t level;             // player level
+    uint32_t strength;          // player strength
+    uint32_t health;            // player health
+    uint32_t max_health;        // player maximum health
+    uint32_t health_regen;      // player health regeneration rate per second/frame
+    float elapsed_health_regen; // time elapsed since last health regeneration
+    float attack_timer;         // Cooldown duration between attacks
+    float elapsed_attack_timer; // Time elapsed since the last attack
+    char username[32];          // player username
+} PlayerContext;
+
+typedef struct
+{
+    PlayerContext *player_context;
+    Level *levels[10];
+    Entity *enemies[2];
+    Entity *players[1];
+    GameKey user_input;
+    float fps;
+    int level_count;
+    int enemy_count;
+    int current_level;
+} GameContext;
+
+extern const EntityDescription player_desc;
+void player_spawn(Level *level, GameManager *manager);

+ 75 - 0
game/storage.c

@@ -0,0 +1,75 @@
+#include <game/storage.h>
+bool save_player_context(PlayerContext *player_context)
+{
+    if (!player_context)
+    {
+        FURI_LOG_E(TAG, "Invalid player context");
+        return false;
+    }
+    char player_context_json[512];
+    snprintf(player_context_json, sizeof(player_context_json), "{\"username\":\"%s\",\"level\":%lu,\"xp\":%lu,\"health\":%lu,\"strength\":%lu,\"max_health\":%lu,\"health_regen\":%ld,\"elapsed_health_regen\":%f,\"attack_timer\":%f,\"elapsed_attack_timer\":%f,\"direction\":%u,\"state\":%u,\"start_position\":{\"x\":%f,\"y\":%f},\"dx\":%u,\"dy\":%u}",
+             player_context->username, player_context->level, player_context->xp, player_context->health, player_context->strength, player_context->max_health, player_context->health_regen, (double)player_context->elapsed_health_regen, (double)player_context->attack_timer, (double)player_context->elapsed_attack_timer, player_context->direction, player_context->state, (double)player_context->start_position.x, (double)player_context->start_position.y, player_context->dx, player_context->dy);
+
+    return save_char("player_context", player_context_json);
+}
+
+PlayerContext *load_player_context()
+{
+    char player_context_json[512];
+    if (!load_char("player_context", player_context_json, sizeof(player_context_json)))
+    {
+        FURI_LOG_E(TAG, "Failed to load player context");
+        return NULL;
+    }
+    PlayerContext *player_context = (PlayerContext *)malloc(sizeof(PlayerContext));
+    if (!player_context)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate player context");
+        return NULL;
+    }
+    // Parse the JSON data
+    char *username = get_json_value("username", player_context_json);
+    char *level = get_json_value("level", player_context_json);
+    char *xp = get_json_value("xp", player_context_json);
+    char *health = get_json_value("health", player_context_json);
+    char *strength = get_json_value("strength", player_context_json);
+    char *max_health = get_json_value("max_health", player_context_json);
+    char *health_regen = get_json_value("health_regen", player_context_json);
+    char *elapsed_health_regen = get_json_value("elapsed_health_regen", player_context_json);
+    char *attack_timer = get_json_value("attack_timer", player_context_json);
+    char *elapsed_attack_timer = get_json_value("elapsed_attack_timer", player_context_json);
+    char *direction = get_json_value("direction", player_context_json);
+    char *state = get_json_value("state", player_context_json);
+    char *dx = get_json_value("dx", player_context_json);
+    char *dy = get_json_value("dy", player_context_json);
+    char *start_position = get_json_value("start_position", player_context_json);
+    char *start_position_x = get_json_value("x", start_position);
+    char *start_position_y = get_json_value("y", start_position);
+
+    if (!username || !level || !xp || !health || !strength || !max_health || !health_regen || !elapsed_health_regen || !attack_timer || !elapsed_attack_timer || !direction || !state || !dx || !dy || !start_position || !start_position_x || !start_position_y)
+    {
+        FURI_LOG_E(TAG, "Failed to parse player context");
+        free(player_context);
+        return NULL;
+    }
+
+    // Copy the parsed values to the player context
+    strncpy(player_context->username, username, sizeof(player_context->username));
+    player_context->level = atoi(level);
+    player_context->xp = atoi(xp);
+    player_context->health = atoi(health);
+    player_context->strength = atoi(strength);
+    player_context->max_health = atoi(max_health);
+    player_context->health_regen = atoi(health_regen);
+    player_context->elapsed_health_regen = strtod(elapsed_health_regen, NULL);
+    player_context->attack_timer = strtod(attack_timer, NULL);
+    player_context->elapsed_attack_timer = strtod(elapsed_attack_timer, NULL);
+    player_context->direction = atoi(direction);
+    player_context->state = atoi(state);
+    player_context->dx = atoi(dx);
+    player_context->dy = atoi(dy);
+    player_context->start_position.x = atoi(start_position_x);
+    player_context->start_position.y = atoi(start_position_y);
+
+    return player_context;
+}

+ 8 - 0
game/storage.h

@@ -0,0 +1,8 @@
+#pragma once
+#include <game/player.h>
+#include <game/game.h>
+#include <flip_world.h>
+#include <flip_storage/storage.h>
+
+bool save_player_context(PlayerContext *player_context);
+PlayerContext *load_player_context();

+ 1 - 1
game/world.c

@@ -1,5 +1,5 @@
 #include <game/world.h>
 #include <game/world.h>
-
+#include <flip_storage/storage.h>
 void draw_bounds(Canvas *canvas)
 void draw_bounds(Canvas *canvas)
 {
 {
     // Draw the outer bounds adjusted by camera offset
     // Draw the outer bounds adjusted by camera offset

+ 1 - 1
jsmn/jsmn.c

@@ -422,7 +422,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
 }
 }
 
 
 // Helper function to create a JSON object
 // Helper function to create a JSON object
-char *jsmn(const char *key, const char *value)
+char *get_json(const char *key, const char *value)
 {
 {
     int length = strlen(key) + strlen(value) + 8;         // Calculate required length
     int length = strlen(key) + strlen(value) + 8;         // Calculate required length
     char *result = (char *)malloc(length * sizeof(char)); // Allocate memory
     char *result = (char *)malloc(length * sizeof(char)); // Allocate memory

+ 1 - 7
jsmn/jsmn.h

@@ -56,14 +56,8 @@ extern "C"
 #define JB_JSMN_EDIT
 #define JB_JSMN_EDIT
 /* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/
 /* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/
 
 
-#include <string.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <furi.h>
-
 // Helper function to create a JSON object
 // Helper function to create a JSON object
-char *jsmn(const char *key, const char *value);
+char *get_json(const char *key, const char *value);
 // Helper function to compare JSON keys
 // Helper function to compare JSON keys
 int jsoneq(const char *json, jsmntok_t *tok, const char *s);
 int jsoneq(const char *json, jsmntok_t *tok, const char *s);
 
 

+ 1 - 4
jsmn/jsmn_furi.c

@@ -400,11 +400,8 @@ int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
     return count;
     return count;
 }
 }
 
 
-// The rest of your code (e.g., get_json_value_furi, get_json_array_value_furi, etc.)
-// remains unchanged and can still rely on these updated parsing functions.
-
 // Helper function to create a JSON object: {"key":"value"}
 // Helper function to create a JSON object: {"key":"value"}
-FuriString *jsmn_create_object(const FuriString *key, const FuriString *value)
+FuriString *get_json_furi(const FuriString *key, const FuriString *value)
 {
 {
     FuriString *result = furi_string_alloc();
     FuriString *result = furi_string_alloc();
     furi_string_printf(result, "{\"%s\":\"%s\"}",
     furi_string_printf(result, "{\"%s\":\"%s\"}",

+ 1 - 1
jsmn/jsmn_furi.h

@@ -47,7 +47,7 @@ extern "C"
 #define JB_JSMN_FURI_EDIT
 #define JB_JSMN_FURI_EDIT
 
 
 // Helper function to create a JSON object
 // Helper function to create a JSON object
-FuriString *jsmn_create_object(const FuriString *key, const FuriString *value);
+FuriString *get_json_furi(const FuriString *key, const FuriString *value);
 
 
 // Updated signatures to accept const char* key
 // Updated signatures to accept const char* key
 FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
 FuriString *get_json_value_furi(const char *key, const FuriString *json_data);

+ 12 - 0
jsmn/jsmn_h.h

@@ -38,4 +38,16 @@ typedef struct
     int toksuper;         /* superior token node, e.g. parent object or array */
     int toksuper;         /* superior token node, e.g. parent object or array */
 } jsmn_parser;
 } jsmn_parser;
 
 
+typedef struct
+{
+    char *key;
+    char *value;
+} JSON;
+
+typedef struct
+{
+    FuriString *key;
+    FuriString *value;
+} FuriJSON;
+
 FuriString *char_to_furi_string(const char *str);
 FuriString *char_to_furi_string(const char *str);