MX 1 год назад
Родитель
Сommit
35a4fbc232
11 измененных файлов с 304 добавлено и 37 удалено
  1. 35 0
      engine/canvas.c
  2. 42 2
      engine/canvas.h
  3. 3 4
      engine/entity.c
  4. 30 8
      engine/game_engine.c
  5. 12 0
      engine/game_manager.c
  6. 19 0
      engine/game_manager.h
  7. 78 18
      engine/level.c
  8. 27 2
      engine/level.h
  9. 5 3
      engine/sensors/imu.c
  10. 29 0
      engine/vector.c
  11. 24 0
      engine/vector.h

+ 35 - 0
engine/canvas.c

@@ -25,4 +25,39 @@ size_t canvas_printf_width(Canvas* canvas, const char* format, ...) {
     furi_string_free(string);
 
     return size;
+}
+
+void canvas_printf_aligned(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    Align h,
+    Align v,
+    const char* format,
+    ...) {
+    FuriString* string = furi_string_alloc();
+    va_list args;
+    va_start(args, format);
+    furi_string_vprintf(string, format, args);
+    va_end(args);
+
+    canvas_draw_str_aligned(canvas, x, y, h, v, furi_string_get_cstr(string));
+
+    furi_string_free(string);
+}
+
+void canvas_draw_str_aligned_outline(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    Align h,
+    Align v,
+    const char* cstr) {
+    canvas_invert_color(canvas);
+    canvas_draw_str_aligned(canvas, x + 1, y + 0, h, v, cstr);
+    canvas_draw_str_aligned(canvas, x - 1, y - 0, h, v, cstr);
+    canvas_draw_str_aligned(canvas, x + 0, y + 1, h, v, cstr);
+    canvas_draw_str_aligned(canvas, x - 0, y - 1, h, v, cstr);
+    canvas_invert_color(canvas);
+    canvas_draw_str_aligned(canvas, x, y, h, v, cstr);
 }

+ 42 - 2
engine/canvas.h

@@ -15,7 +15,8 @@ extern "C" {
  * @param format format string
  * @param ...  arguments
  */
-void canvas_printf(Canvas* canvas, uint8_t x, uint8_t y, const char* format, ...);
+void canvas_printf(Canvas* canvas, uint8_t x, uint8_t y, const char* format, ...)
+    __attribute__((__format__(__printf__, 4, 5)));
 
 /**
  * @brief Get width of formatted string
@@ -25,7 +26,46 @@ void canvas_printf(Canvas* canvas, uint8_t x, uint8_t y, const char* format, ...
  * @param ... arguments
  * @return size_t width of formatted string
  */
-size_t canvas_printf_width(Canvas* canvas, const char* format, ...);
+size_t canvas_printf_width(Canvas* canvas, const char* format, ...)
+    __attribute__((__format__(__printf__, 2, 3)));
+
+/**
+ * @brief Print formatted string to canvas with alignment
+ * 
+ * @param canvas canvas instance
+ * @param x x position
+ * @param y y position
+ * @param h horizontal alignment
+ * @param v vertical alignment
+ * @param format format string
+ * @param ... arguments
+ */
+void canvas_printf_aligned(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    Align h,
+    Align v,
+    const char* format,
+    ...) __attribute__((__format__(__printf__, 6, 7)));
+
+/**
+ * @brief Draw aligned string with outline
+ * 
+ * @param canvas 
+ * @param x 
+ * @param y 
+ * @param h 
+ * @param v 
+ * @param cstr 
+ */
+void canvas_draw_str_aligned_outline(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    Align h,
+    Align v,
+    const char* cstr);
 
 #ifdef __cplusplus
 }

+ 3 - 4
engine/entity.c

@@ -2,6 +2,7 @@
 #include "entity_i.h"
 #include <stdlib.h>
 #include <furi.h>
+#include <math.h>
 
 #ifdef ENTITY_DEBUG
 #define ENTITY_D(...) FURI_LOG_D("Entity", __VA_ARGS__)
@@ -134,10 +135,8 @@ bool entity_collider_circle_circle(Entity* entity, Entity* other) {
     Vector pos1 = entity_collider_position_get(entity);
     Vector pos2 = entity_collider_position_get(other);
 
-    float dx = pos1.x - pos2.x;
-    float dy = pos1.y - pos2.y;
-    float distance = sqrtf(dx * dx + dy * dy);
-    return distance < entity->collider->circle.radius + other->collider->circle.radius;
+    Vector delta = vector_sub(pos1, pos2);
+    return vector_length(delta) < entity->collider->circle.radius + other->collider->circle.radius;
 }
 
 bool entity_collider_rect_rect(Entity* entity, Entity* other) {

+ 30 - 8
engine/game_engine.c

@@ -1,5 +1,6 @@
 #include "game_engine.h"
 #include <furi.h>
+#include <furi_hal_rtc.h>
 #include <gui/gui.h>
 #include <input/input.h>
 #include <notification/notification_messages.h>
@@ -7,6 +8,11 @@
 
 typedef _Atomic uint32_t AtomicUint32;
 
+typedef struct {
+    bool lefty;
+    AtomicUint32 state;
+} InputHolder;
+
 GameEngineSettings game_engine_settings_init() {
     GameEngineSettings settings;
     settings.target_fps = 30.0f;
@@ -61,7 +67,7 @@ static void clock_timer_callback(void* context) {
     furi_thread_flags_set(engine->thread_id, GameThreadFlagUpdate);
 }
 
-static const GameKey keys[] = {
+static const GameKey keys_right_hand[] = {
     [InputKeyUp] = GameKeyUp,
     [InputKeyDown] = GameKeyDown,
     [InputKeyRight] = GameKeyRight,
@@ -70,19 +76,32 @@ static const GameKey keys[] = {
     [InputKeyBack] = GameKeyBack,
 };
 
-static const size_t keys_count = sizeof(keys) / sizeof(keys[0]);
+static const GameKey keys_left_hand[] = {
+    [InputKeyUp] = GameKeyDown,
+    [InputKeyDown] = GameKeyUp,
+    [InputKeyRight] = GameKeyLeft,
+    [InputKeyLeft] = GameKeyRight,
+    [InputKeyOk] = GameKeyOk,
+    [InputKeyBack] = GameKeyBack,
+};
+static_assert(
+    sizeof(keys_right_hand) == sizeof(keys_left_hand),
+    "keys_right_hand and keys_left_hand do not match!");
+
+static const size_t keys_count = sizeof(keys_right_hand) / sizeof(keys_right_hand[0]);
 
 static void input_events_callback(const void* value, void* context) {
-    AtomicUint32* input_state = context;
+    InputHolder* holder = context;
     const InputEvent* event = value;
+    const GameKey* keys = holder->lefty ? keys_left_hand : keys_right_hand;
 
     if(event->key < keys_count) {
         switch(event->type) {
         case InputTypePress:
-            *input_state |= (keys[event->key]);
+            holder->state |= (keys[event->key]);
             break;
         case InputTypeRelease:
-            *input_state &= ~(keys[event->key]);
+            holder->state &= ~(keys[event->key]);
             break;
         default:
             break;
@@ -92,7 +111,10 @@ static void input_events_callback(const void* value, void* context) {
 
 void game_engine_run(GameEngine* engine) {
     // input state
-    AtomicUint32 input_state = 0;
+    InputHolder input_state = {
+        .lefty = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient),
+        .state = 0,
+    };
     uint32_t input_prev_state = 0;
 
     // set backlight if needed
@@ -130,7 +152,7 @@ void game_engine_run(GameEngine* engine) {
             time_start = time_end;
 
             // update input state
-            uint32_t input_current_state = input_state;
+            uint32_t input_current_state = input_state.state;
             InputState input = {
                 .held = input_current_state,
                 .pressed = input_current_state & ~input_prev_state,
@@ -150,7 +172,7 @@ void game_engine_run(GameEngine* engine) {
             // show fps if needed
             if(engine->settings.show_fps) {
                 canvas_set_color(canvas, ColorXOR);
-                canvas_printf(canvas, 0, 7, "%u", (uint32_t)roundf(engine->fps));
+                canvas_printf(canvas, 0, 7, "%lu", (uint32_t)roundf(engine->fps));
             }
 
             // and output screen buffer

+ 12 - 0
engine/game_manager.c

@@ -159,4 +159,16 @@ Sprite* game_manager_sprite_load(GameManager* manager, const char* path) {
     furi_string_free(path_full);
 
     return sprite;
+}
+
+Level* game_manager_entity_level_get(GameManager* manager, Entity* entity) {
+    LevelList_it_t it;
+    LevelList_it(it, manager->levels);
+    while(!LevelList_end_p(it)) {
+        if(level_contains_entity(*LevelList_cref(it), entity)) {
+            return *LevelList_cref(it);
+        }
+        LevelList_next(it);
+    }
+    return NULL;
 }

+ 19 - 0
engine/game_manager.h

@@ -13,8 +13,27 @@ Level* game_manager_add_level(GameManager* manager, const LevelBehaviour* behavi
 
 void game_manager_next_level_set(GameManager* manager, Level* level);
 
+/**
+ * @brief Get the current level
+ * @warning This function returns current level, but in entity start or stop callbacks, the level may be different from entity's level.
+ * For example, if an entity is added to a level_game and you currently are in level_pause, this function will return level_pause.
+ * Use game_manager_entity_level_get to get the entity's level, or save the level pointer somewhere.
+ * 
+ * @param manager game manager instance
+ * @return Level* level instance
+ */
 Level* game_manager_current_level_get(GameManager* manager);
 
+/**
+ * @brief Get the level of an entity
+ * @warning This function is kinda slow, use it only when other methods are not possible
+ * 
+ * @param manager 
+ * @param entity 
+ * @return Level* 
+ */
+Level* game_manager_entity_level_get(GameManager* manager, Entity* entity);
+
 GameEngine* game_manager_engine_get(GameManager* manager);
 
 InputState game_manager_input_get(GameManager* manager);

+ 78 - 18
engine/level.c

@@ -10,6 +10,7 @@ LIST_DEF(EntityList, Entity*, M_POD_OPLIST);
     M_EACH(name, list, EntityList_t)
 
 #define LEVEL_DEBUG(...) FURI_LOG_D("Level", __VA_ARGS__)
+#define LEVEL_INFO(...) FURI_LOG_I("Level", __VA_ARGS__)
 #define LEVEL_ERROR(...) FURI_LOG_E("Level", __VA_ARGS__)
 
 struct Level {
@@ -19,11 +20,16 @@ struct Level {
     const LevelBehaviour* behaviour;
     void* context;
     GameManager* manager;
+
+    bool clear;
+    LevelClearCallback clear_callback;
+    void* clear_context;
 };
 
 Level* level_alloc(const LevelBehaviour* behaviour, GameManager* manager) {
     Level* level = malloc(sizeof(Level));
     level->manager = manager;
+    level->clear = false;
     EntityList_init(level->entities);
     EntityList_init(level->to_add);
     EntityList_init(level->to_remove);
@@ -62,7 +68,7 @@ static void level_process_remove(Level* level) {
     EntityList_clear(level->to_remove);
 }
 
-static bool level_entity_in_list_p(EntityList_t list, Entity* entity) {
+static bool level_entity_in_list_p(const EntityList_t list, Entity* entity) {
     FOREACH(item, list) {
         if(*item == entity) {
             return true;
@@ -71,22 +77,7 @@ static bool level_entity_in_list_p(EntityList_t list, Entity* entity) {
     return false;
 }
 
-void level_free(Level* level) {
-    level_clear(level);
-
-    EntityList_clear(level->entities);
-    EntityList_clear(level->to_add);
-    EntityList_clear(level->to_remove);
-
-    if(level->behaviour->context_size > 0) {
-        free(level->context);
-    }
-
-    LEVEL_DEBUG("Freeing level at %p", level);
-    free(level);
-}
-
-void level_clear(Level* level) {
+static void level_clear_entities(Level* level) {
     size_t iterations = 0;
 
     do {
@@ -109,7 +100,23 @@ void level_clear(Level* level) {
 
         // entity_call_stop can call level_remove_entity or level_add_entity
         // so we need to process to_add and to_remove again
-    } while(!EntityList_empty_p(level->to_add) || !EntityList_empty_p(level->to_remove));
+    } while(!EntityList_empty_p(level->to_add) || !EntityList_empty_p(level->to_remove) ||
+            !EntityList_empty_p(level->entities));
+}
+
+void level_free(Level* level) {
+    level_clear_entities(level);
+
+    EntityList_clear(level->entities);
+    EntityList_clear(level->to_add);
+    EntityList_clear(level->to_remove);
+
+    if(level->behaviour->context_size > 0) {
+        free(level->context);
+    }
+
+    LEVEL_DEBUG("Freeing level at %p", level);
+    free(level);
 }
 
 Entity* level_add_entity(Level* level, const EntityDescription* description) {
@@ -182,7 +189,19 @@ static void level_process_collision(Level* level, GameManager* manager) {
     }
 }
 
+void level_clear(Level* level, LevelClearCallback callback, void* context) {
+    level->clear_callback = callback;
+    level->clear_context = context;
+    level->clear = true;
+}
+
 void level_update(Level* level, GameManager* manager) {
+    if(level->clear) {
+        level_clear_entities(level);
+        level->clear_callback(level, level->clear_context);
+        level->clear = false;
+    }
+
     level_process_add(level);
     level_process_remove(level);
     level_process_update(level, manager);
@@ -244,6 +263,47 @@ size_t level_entity_count(const Level* level, const EntityDescription* descripti
     return count;
 }
 
+Entity* level_entity_get(const Level* level, const EntityDescription* description, size_t index) {
+    size_t count = 0;
+    FOREACH(item, level->entities) {
+        if(description == NULL || description == entity_description_get(*item)) {
+            if(!level_entity_in_list_p(level->to_remove, *item)) {
+                if(count == index) {
+                    return *item;
+                }
+                count++;
+            }
+        }
+    }
+
+    FOREACH(item, level->to_add) {
+        if(description == NULL || description == entity_description_get(*item)) {
+            if(!level_entity_in_list_p(level->to_remove, *item)) {
+                if(count == index) {
+                    return *item;
+                }
+                count++;
+            }
+        }
+    }
+
+    return NULL;
+}
+
 void* level_context_get(Level* level) {
     return level->context;
+}
+
+bool level_contains_entity(const Level* level, const Entity* entity) {
+    FOREACH(item, level->entities) {
+        if(*item == entity) {
+            return true;
+        }
+    }
+    FOREACH(item, level->to_add) {
+        if(*item == entity) {
+            return true;
+        }
+    }
+    return false;
 }

+ 27 - 2
engine/level.h

@@ -16,12 +16,17 @@ typedef struct {
     size_t context_size;
 } LevelBehaviour;
 
+/** Level reset callback */
+typedef void (*LevelClearCallback)(Level* level, void* context);
+
 /**
- * @brief Remove all entities from the level
+ * @brief Clear entities and call callback at the start of the next update
  * 
  * @param level level instance
+ * @param callback callback
+ * @param context context
  */
-void level_clear(Level* level);
+void level_clear(Level* level, LevelClearCallback callback, void* context);
 
 /**
  * @brief Add an entity to the level
@@ -65,6 +70,17 @@ void level_send_event(
  */
 size_t level_entity_count(const Level* level, const EntityDescription* description);
 
+/**
+ * @brief Get an entity of a certain type in the level
+ * Returns NULL if the index is out of bounds
+ * 
+ * @param level level instance
+ * @param description entity description, NULL for all entities
+ * @param index entity index
+ * @return Entity* entity
+ */
+Entity* level_entity_get(const Level* level, const EntityDescription* description, size_t index);
+
 /**
  * @brief Get the context of the level
  * 
@@ -73,6 +89,15 @@ size_t level_entity_count(const Level* level, const EntityDescription* descripti
  */
 void* level_context_get(Level* level);
 
+/**
+ * @brief Check if the level contains an entity
+ * 
+ * @param level level instance
+ * @param entity entity
+ * @return true if the level contains the entity
+ */
+bool level_contains_entity(const Level* level, const Entity* entity);
+
 #ifdef __cplusplus
 }
 #endif

+ 5 - 3
engine/sensors/imu.c

@@ -37,6 +37,7 @@ typedef struct {
     FuriThread* thread;
     ICM42688P* icm42688p;
     ImuProcessedData processed_data;
+    bool lefty;
 } ImuThread;
 
 static void imu_madgwick_filter(
@@ -168,7 +169,7 @@ ImuThread* imu_start(ICM42688P* icm42688p) {
     ImuThread* imu = malloc(sizeof(ImuThread));
     imu->icm42688p = icm42688p;
     imu->thread = furi_thread_alloc_ex("ImuThread", 4096, imu_thread, imu);
-
+    imu->lefty = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient);
     furi_thread_start(imu->thread);
 
     return imu;
@@ -312,7 +313,8 @@ bool imu_present(Imu* imu) {
 
 float imu_pitch_get(Imu* imu) {
     // we pretend that reading a float is an atomic operation
-    return imu->thread->processed_data.pitch;
+    return imu->thread->lefty ? -imu->thread->processed_data.pitch :
+                                imu->thread->processed_data.pitch;
 }
 
 float imu_roll_get(Imu* imu) {
@@ -322,5 +324,5 @@ float imu_roll_get(Imu* imu) {
 
 float imu_yaw_get(Imu* imu) {
     // we pretend that reading a float is an atomic operation
-    return imu->thread->processed_data.yaw;
+    return imu->thread->lefty ? -imu->thread->processed_data.yaw : imu->thread->processed_data.yaw;
 }

+ 29 - 0
engine/vector.c

@@ -1,4 +1,11 @@
 #include "vector.h"
+#include <math.h>
+#include <float.h>
+
+#undef vector_add
+#undef vector_sub
+#undef vector_mul
+#undef vector_div
 
 Vector vector_add(Vector a, Vector b) {
     return (Vector){.x = a.x + b.x, .y = a.y + b.y};
@@ -30,4 +37,26 @@ Vector vector_mulf(Vector a, float b) {
 
 Vector vector_divf(Vector a, float b) {
     return (Vector){.x = a.x / b, .y = a.y / b};
+}
+
+float vector_length(Vector v) {
+    return sqrtf(v.x * v.x + v.y * v.y);
+}
+
+Vector vector_normalize(Vector v) {
+    float length = vector_length(v);
+    if(length < FLT_EPSILON) {
+        return (Vector){0, 0};
+    }
+    return (Vector){v.x / length, v.y / length};
+}
+
+float vector_dot(Vector a, Vector b) {
+    return a.x * b.x + a.y * b.y;
+}
+
+Vector vector_rand() {
+    float x = (rand() % __INT_MAX__) / (float)__INT_MAX__;
+    float y = (rand() % __INT_MAX__) / (float)__INT_MAX__;
+    return (Vector){x, y};
 }

+ 24 - 0
engine/vector.h

@@ -1,4 +1,5 @@
 #pragma once
+#include <m-core.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -27,6 +28,29 @@ Vector vector_mulf(Vector a, float b);
 
 Vector vector_divf(Vector a, float b);
 
+float vector_length(Vector v);
+
+Vector vector_normalize(Vector v);
+
+float vector_dot(Vector a, Vector b);
+
+Vector vector_rand();
+
+#define VECTOR_SELECT(func1, func2, a, b) \
+    _Generic(                             \
+        (b),                              \
+        float: func2,                     \
+        const float: func2,               \
+        int: func2,                       \
+        const int: func2,                 \
+        Vector: func1,                    \
+        const Vector: func1)(a, b)
+
+#define vector_add(a, b) VECTOR_SELECT(vector_add, vector_addf, a, b)
+#define vector_sub(a, b) VECTOR_SELECT(vector_sub, vector_subf, a, b)
+#define vector_mul(a, b) VECTOR_SELECT(vector_mul, vector_mulf, a, b)
+#define vector_div(a, b) VECTOR_SELECT(vector_div, vector_divf, a, b)
+
 #ifdef __cplusplus
 }
 #endif