Explorar o código

update VGM support, start dynamic level loading

jblanked hai 1 ano
pai
achega
e2dbf49879
Modificáronse 8 ficheiros con 225 adicións e 97 borrados
  1. 4 0
      app.c
  2. 52 50
      engine/level.h
  3. 10 26
      game/game.c
  4. 1 0
      game/game.h
  5. 57 4
      game/level.c
  6. 2 0
      game/level.h
  7. 96 17
      game/player.c
  8. 3 0
      game/player.h

+ 4 - 0
app.c

@@ -14,6 +14,10 @@ int32_t flip_world_main(void *p)
         return -1;
     }
 
+    // initialize the VGM
+    furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull);
+    furi_hal_gpio_write(&gpio_ext_pc1, false); // pull pin 15 low
+
     // check if board is connected (Derek Jamison)
     // initialize the http
     if (flipper_http_init(flipper_http_rx_callback, app))

+ 52 - 50
engine/level.h

@@ -3,58 +3,60 @@
 #include "entity.h"
 
 #ifdef __cplusplus
-extern "C" {
+extern "C"
+{
 #endif
 
-typedef struct GameManager GameManager;
-
-typedef struct {
-    void (*alloc)(Level* level, GameManager* manager, void* context);
-    void (*free)(Level* level, GameManager* manager, void* context);
-    void (*start)(Level* level, GameManager* manager, void* context);
-    void (*stop)(Level* level, GameManager* manager, void* context);
-    size_t context_size;
-} LevelBehaviour;
-
-/**
- * @brief Remove all entities from the level
- * 
- * @param level level instance
- */
-void level_clear(Level* level);
-
-/**
- * @brief Add an entity to the level
- * 
- * @param level level instance
- * @param behaviour entity behaviour
- * @return Entity* 
- */
-Entity* level_add_entity(Level* level, const EntityDescription* behaviour);
-
-/**
- * @brief Remove an entity from the level
- * 
- * @param level level instance
- * @param entity entity to remove
- */
-void level_remove_entity(Level* level, Entity* entity);
-
-/**
- * @brief Send an event to all entities of a certain type in the level
- * 
- * @param level level instance
- * @param sender entity that sends the event
- * @param receiver_desc entity description that will receive the event, NULL for all entities
- * @param type event type
- * @param value event value
- */
-void level_send_event(
-    Level* level,
-    Entity* sender,
-    const EntityDescription* receiver_desc,
-    uint32_t type,
-    EntityEventValue value);
+    typedef struct GameManager GameManager;
+
+    typedef struct
+    {
+        void (*alloc)(Level *level, GameManager *manager, void *context);
+        void (*free)(Level *level, GameManager *manager, void *context);
+        void (*start)(Level *level, GameManager *manager, void *context);
+        void (*stop)(Level *level, GameManager *manager, void *context);
+        size_t context_size;
+    } LevelBehaviour;
+
+    /**
+     * @brief Remove all entities from the level
+     *
+     * @param level level instance
+     */
+    void level_clear(Level *level);
+
+    /**
+     * @brief Add an entity to the level
+     *
+     * @param level level instance
+     * @param behaviour entity behaviour
+     * @return Entity*
+     */
+    Entity *level_add_entity(Level *level, const EntityDescription *behaviour);
+
+    /**
+     * @brief Remove an entity from the level
+     *
+     * @param level level instance
+     * @param entity entity to remove
+     */
+    void level_remove_entity(Level *level, Entity *entity);
+
+    /**
+     * @brief Send an event to all entities of a certain type in the level
+     *
+     * @param level level instance
+     * @param sender entity that sends the event
+     * @param receiver_desc entity description that will receive the event, NULL for all entities
+     * @param type event type
+     * @param value event value
+     */
+    void level_send_event(
+        Level *level,
+        Entity *sender,
+        const EntityDescription *receiver_desc,
+        uint32_t type,
+        EntityEventValue value);
 
 #ifdef __cplusplus
 }

+ 10 - 26
game/game.c

@@ -13,36 +13,15 @@ static void game_start(GameManager *game_manager, void *ctx)
     GameContext *game_context = ctx;
     game_context->fps = game_fps_choices_2[game_fps_index];
     game_context->player_context = NULL;
-
-    // open the world list from storage, then create a level for each world
-    char file_path[128];
-    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
-    FuriString *world_list = flipper_http_load_from_file(file_path);
-    if (!world_list)
-    {
-        FURI_LOG_E("Game", "Failed to load world list");
-        game_context->levels[0] = game_manager_add_level(game_manager, generic_level("town_world_v2", 0));
-        game_context->level_count = 1;
-        return;
-    }
-    for (int i = 0; i < 10; i++)
-    {
-        FuriString *world_name = get_json_array_value_furi("worlds", i, world_list);
-        if (!world_name)
-        {
-            break;
-        }
-        game_context->levels[i] = game_manager_add_level(game_manager, generic_level(furi_string_get_cstr(world_name), i));
-        furi_string_free(world_name);
-        game_context->level_count++;
-    }
-    furi_string_free(world_list);
-
     game_context->current_level = 0;
-    FURI_LOG_I("Game", "Level count: %d", game_context->level_count);
+    allocate_level(game_manager, 0);
 
     // Notifications - for LED light access
     game_context->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // imu
+    game_context->imu = imu_alloc();
+    game_context->imu_present = imu_present(game_context->imu);
 }
 
 /*
@@ -64,6 +43,11 @@ static void game_stop(void *ctx)
         FURI_LOG_E("Game", "Game context is NULL");
         return;
     }
+    if (game_context->imu)
+    {
+        imu_free(game_context->imu);
+        game_context->imu = NULL;
+    }
     // close the notifications
     furi_record_close(RECORD_NOTIFICATION);
     if (game_context->player_context)

+ 1 - 0
game/game.h

@@ -1,5 +1,6 @@
 #pragma once
 #include "engine/engine.h"
+#include <engine/level_i.h>
 #include <game/world.h>
 #include <game/level.h>
 #include <game/enemy.h>

+ 57 - 4
game/level.c

@@ -1,7 +1,61 @@
 #include <game/level.h>
 #include <flip_storage/storage.h>
 #include <game/storage.h>
-void set_world(Level *level, GameManager *manager, char *id)
+bool allocate_level(GameManager *manager, int index)
+{
+    GameContext *game_context = game_manager_game_context_get(manager);
+    if (!game_context)
+    {
+        FURI_LOG_E("Game", "Game context is NULL");
+        return false;
+    }
+    // open the world list from storage, then create a level for each world
+    char file_path[128];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
+    FuriString *world_list = flipper_http_load_from_file(file_path);
+    if (!world_list)
+    {
+        FURI_LOG_E("Game", "Failed to load world list");
+        game_context->levels[0] = game_manager_add_level(manager, generic_level("town_world_v2", 0));
+        game_context->level_count = 1;
+        return false;
+    }
+    FuriString *world_name = get_json_array_value_furi("worlds", index, world_list);
+    if (!world_name)
+    {
+        FURI_LOG_E("Game", "Failed to get world name");
+        furi_string_free(world_list);
+        return false;
+    }
+    game_context->levels[game_context->current_level] = game_manager_add_level(manager, generic_level(furi_string_get_cstr(world_name), index));
+    furi_string_free(world_name);
+    furi_string_free(world_list);
+    return true;
+}
+void free_last_level(GameManager *manager)
+{
+    GameContext *game_context = game_manager_game_context_get(manager);
+    if (!game_context)
+    {
+        FURI_LOG_E("Game", "Game context is NULL");
+        return;
+    }
+    if (game_context->current_level == 1)
+    {
+        if (game_context->levels[0])
+        {
+            level_free(game_context->levels[0]);
+        }
+    }
+    else
+    {
+        if (game_context->levels[1])
+        {
+            level_free(game_context->levels[1]);
+        }
+    }
+}
+static void set_world(Level *level, GameManager *manager, char *id)
 {
     char file_path[256];
     snprintf(file_path, sizeof(file_path),
@@ -68,7 +122,6 @@ void set_world(Level *level, GameManager *manager, char *id)
         FURI_LOG_I("Game", "Finished loading world data");
     }
 }
-
 static void level_start(Level *level, GameManager *manager, void *context)
 {
     if (!level || !context || !manager)
@@ -140,7 +193,7 @@ static void level_generic_free()
     }
 }
 
-static void level_free(Level *level, GameManager *manager, void *context)
+static void free_level(Level *level, GameManager *manager, void *context)
 {
     UNUSED(level);
     UNUSED(manager);
@@ -169,7 +222,7 @@ static void level_alloc_generic_world(Level *level, GameManager *manager, void *
 
 const LevelBehaviour _generic_level = {
     .alloc = level_alloc_generic_world,
-    .free = level_free,
+    .free = free_level,
     .start = level_start,
     .stop = NULL,
     .context_size = sizeof(LevelContext),

+ 2 - 0
game/level.h

@@ -8,3 +8,5 @@ typedef struct
 } LevelContext;
 
 const LevelBehaviour *generic_level(const char *id, int index);
+bool allocate_level(GameManager *manager, int index);
+void free_last_level(GameManager *manager);

+ 96 - 17
game/player.c

@@ -4,19 +4,22 @@
 
 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)
+    {
+        FURI_LOG_E(TAG, "Failed to get game context");
+        return NULL;
+    }
+    game_context->current_level = game_context->current_level == 0 ? 1 : 0;
+    if (!game_context->levels[game_context->current_level])
     {
-        if (game_context->levels[i] == current_level)
+        if (!allocate_level(manager, game_context->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];
+            FURI_LOG_E(TAG, "Failed to allocate level %d", game_context->current_level);
+            return NULL;
         }
     }
-    game_context->current_level = 0;
-    return game_context->levels[0] ? game_context->levels[0] : game_manager_add_level(manager, generic_level("town_world", 0));
+    return game_context->levels[game_context->current_level];
 }
 
 void player_spawn(Level *level, GameManager *manager)
@@ -100,6 +103,37 @@ void player_spawn(Level *level, GameManager *manager)
     game_context->player_context = player_context;
 }
 
+// code from Derek Jamison
+// eventually we'll add dynamic positioning based on how much pitch/roll is detected
+// instead of assigning a fixed value
+static int player_x_from_pitch(float pitch)
+{
+    if (pitch > 5.0) // was 9.0
+    {
+        return 1;
+    }
+    else if (pitch < -5.0) // was -9.0
+    {
+        return -1;
+    }
+    return 0;
+}
+
+static int player_y_from_roll(float roll)
+{
+    if (roll > 5.0)
+    {
+        return 1;
+    }
+    else if (roll < -10.0) // was -20
+    {
+        return -1;
+    }
+    return 0;
+}
+
+static bool is_new_level = false;
+
 // Modify player_update to track direction
 static void player_update(Entity *self, GameManager *manager, void *context)
 {
@@ -108,6 +142,58 @@ static void player_update(Entity *self, GameManager *manager, void *context)
     Vector pos = entity_pos_get(self);
     GameContext *game_context = game_manager_game_context_get(manager);
 
+    // Store previous direction
+    int prev_dx = player->dx;
+    int prev_dy = player->dy;
+
+    // Reset movement deltas each frame
+    player->dx = 0;
+    player->dy = 0;
+
+    if (game_context->imu_present)
+    {
+        player->dx = player_x_from_pitch(-imu_pitch_get(game_context->imu));
+        player->dy = player_y_from_roll(-imu_roll_get(game_context->imu));
+
+        switch (player_x_from_pitch(-imu_pitch_get(game_context->imu)))
+        {
+        case -1:
+            player->direction = PLAYER_LEFT;
+            player->dx = -1;
+            pos.x -= 1;
+            break;
+        case 1:
+            player->direction = PLAYER_RIGHT;
+            player->dx = 1;
+            pos.x += 1;
+            break;
+        default:
+            break;
+        };
+        switch (player_y_from_roll(-imu_roll_get(game_context->imu)))
+        {
+        case -1:
+            player->direction = PLAYER_UP;
+            player->dy = -1;
+            pos.y -= 1;
+            break;
+        case 1:
+            player->direction = PLAYER_DOWN;
+            player->dy = 1;
+            pos.y += 1;
+            break;
+        default:
+            break;
+        };
+    }
+
+    if (is_new_level)
+    {
+        // remove previous level if it exists
+        free_last_level(manager);
+        is_new_level = false;
+    }
+
     // apply health regeneration
     player->elapsed_health_regen += 1.0f / game_context->fps;
     if (player->elapsed_health_regen >= 1.0f && player->health < player->max_health)
@@ -119,14 +205,6 @@ static void player_update(Entity *self, GameManager *manager, void *context)
     // 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)
     {
@@ -175,11 +253,12 @@ static void player_update(Entity *self, GameManager *manager, void *context)
             save_player_context(player);
             game_manager_next_level_set(manager, get_next_level(manager));
             furi_delay_ms(500);
+            is_new_level = true;
         }
         else
         {
             game_context->user_input = GameKeyOk;
-            furi_delay_ms(100);
+            // furi_delay_ms(100);
         }
         return;
     }

+ 3 - 0
game/player.h

@@ -3,6 +3,7 @@
 #include <flip_world.h>
 #include <game/game.h>
 #include <notification/notification_messages.h>
+#include "engine/sensors/imu.h"
 
 // Maximum enemies
 #define MAX_ENEMIES 2
@@ -59,6 +60,8 @@ typedef struct
     int current_level;
     bool ended_early;
     NotificationApp *notifications;
+    Imu *imu;
+    bool imu_present;
 } GameContext;
 
 typedef struct