game.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #include "game.h"
  2. /****** Entities: Player ******/
  3. static Level *get_next_level(GameManager *manager)
  4. {
  5. Level *current_level = game_manager_current_level_get(manager);
  6. GameContext *game_context = game_manager_game_context_get(manager);
  7. for (int i = 0; i < game_context->level_count; i++)
  8. {
  9. if (game_context->levels[i] == current_level)
  10. {
  11. // check if i+1 is out of bounds, if so, return the first level
  12. game_context->current_level = (i + 1) % game_context->level_count;
  13. return game_context->levels[(i + 1) % game_context->level_count] ? game_context->levels[(i + 1) % game_context->level_count] : game_context->levels[0];
  14. }
  15. }
  16. game_context->current_level = 0;
  17. return game_context->levels[0] ? game_context->levels[0] : game_manager_add_level(manager, generic_level("town_world", 0));
  18. }
  19. void player_spawn(Level *level, GameManager *manager)
  20. {
  21. GameContext *game_context = game_manager_game_context_get(manager);
  22. game_context->players[0] = level_add_entity(level, &player_desc);
  23. // Set player position.
  24. entity_pos_set(game_context->players[0], (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
  25. // Add collision box to player entity
  26. // Box is centered in player x and y, and it's size is 10x10
  27. entity_collider_add_rect(game_context->players[0], 10 + PLAYER_COLLISION_HORIZONTAL, 10 + PLAYER_COLLISION_VERTICAL);
  28. // Get player context
  29. PlayerContext *player_context = entity_context_get(game_context->players[0]);
  30. // Load player sprite
  31. player_context->sprite_right = game_manager_sprite_load(manager, "player_right.fxbm");
  32. player_context->sprite_left = game_manager_sprite_load(manager, "player_left.fxbm");
  33. player_context->direction = PLAYER_RIGHT; // default direction
  34. player_context->health = 100;
  35. player_context->strength = 10;
  36. player_context->level = 1;
  37. player_context->xp = 0;
  38. player_context->start_position = entity_pos_get(game_context->players[0]);
  39. player_context->attack_timer = 0.5f;
  40. player_context->elapsed_attack_timer = player_context->attack_timer;
  41. player_context->health_regen = 1; // 1 health per second
  42. player_context->elapsed_health_regen = 0;
  43. player_context->max_health = 100 + (player_context->level * 10); // 10 health per level
  44. // Set player username
  45. if (!load_char("Flip-Social-Username", player_context->username, 32))
  46. {
  47. snprintf(player_context->username, 32, "Player");
  48. }
  49. game_context->player_context = player_context;
  50. }
  51. // Modify player_update to track direction
  52. static void player_update(Entity *self, GameManager *manager, void *context)
  53. {
  54. PlayerContext *player = (PlayerContext *)context;
  55. InputState input = game_manager_input_get(manager);
  56. Vector pos = entity_pos_get(self);
  57. GameContext *game_context = game_manager_game_context_get(manager);
  58. // apply health regeneration
  59. player->elapsed_health_regen += 1.0f / game_context->fps;
  60. if (player->elapsed_health_regen >= 1.0f)
  61. {
  62. player->health += player->health_regen;
  63. player->elapsed_health_regen = 0;
  64. }
  65. // Increment the elapsed_attack_timer for the player
  66. player->elapsed_attack_timer += 1.0f / game_context->fps;
  67. // Store previous direction
  68. int prev_dx = player->dx;
  69. int prev_dy = player->dy;
  70. // Reset movement deltas each frame
  71. player->dx = 0;
  72. player->dy = 0;
  73. // Handle movement input
  74. if (input.held & GameKeyUp)
  75. {
  76. pos.y -= 2;
  77. player->dy = -1;
  78. player->direction = PLAYER_UP;
  79. game_context->user_input = GameKeyUp;
  80. }
  81. if (input.held & GameKeyDown)
  82. {
  83. pos.y += 2;
  84. player->dy = 1;
  85. player->direction = PLAYER_DOWN;
  86. game_context->user_input = GameKeyDown;
  87. }
  88. if (input.held & GameKeyLeft)
  89. {
  90. pos.x -= 2;
  91. player->dx = -1;
  92. player->direction = PLAYER_LEFT;
  93. game_context->user_input = GameKeyLeft;
  94. }
  95. if (input.held & GameKeyRight)
  96. {
  97. pos.x += 2;
  98. player->dx = 1;
  99. player->direction = PLAYER_RIGHT;
  100. game_context->user_input = GameKeyRight;
  101. }
  102. // Clamp the player's position to stay within world bounds
  103. pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
  104. pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
  105. // Update player position
  106. entity_pos_set(self, pos);
  107. // switch levels if holding OK
  108. if (input.held & GameKeyOk)
  109. {
  110. // if all enemies are dead, allow the "OK" button to switch levels
  111. // otherwise the "OK" button will be used to attack
  112. if (game_context->enemy_count == 0)
  113. {
  114. game_manager_next_level_set(manager, get_next_level(manager));
  115. furi_delay_ms(500);
  116. }
  117. else
  118. {
  119. game_context->user_input = GameKeyOk;
  120. furi_delay_ms(100);
  121. }
  122. return;
  123. }
  124. // If the player is not moving, retain the last movement direction
  125. if (player->dx == 0 && player->dy == 0)
  126. {
  127. player->dx = prev_dx;
  128. player->dy = prev_dy;
  129. player->state = PLAYER_IDLE;
  130. game_context->user_input = -1; // reset user input
  131. }
  132. else
  133. {
  134. player->state = PLAYER_MOVING;
  135. }
  136. // Handle back button to stop the game
  137. if (input.pressed & GameKeyBack)
  138. {
  139. game_manager_game_stop(manager);
  140. }
  141. }
  142. static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  143. {
  144. // Get player context
  145. UNUSED(manager);
  146. PlayerContext *player = context;
  147. // Get player position
  148. Vector pos = entity_pos_get(self);
  149. // Draw background (updates camera_x and camera_y)
  150. draw_background(canvas, pos);
  151. // Draw player sprite relative to camera, centered on the player's position
  152. canvas_draw_sprite(
  153. canvas,
  154. player->direction == PLAYER_RIGHT ? player->sprite_right : player->sprite_left,
  155. pos.x - camera_x - 5, // Center the sprite horizontally
  156. pos.y - camera_y - 5 // Center the sprite vertically
  157. );
  158. // draw username over player's head
  159. canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
  160. canvas_draw_str(canvas, pos.x - camera_x - (strlen(player->username) * 2), pos.y - camera_y - 7, player->username);
  161. }
  162. const EntityDescription player_desc = {
  163. .start = NULL, // called when entity is added to the level
  164. .stop = NULL, // called when entity is removed from the level
  165. .update = player_update, // called every frame
  166. .render = player_render, // called every frame, after update
  167. .collision = NULL, // called when entity collides with another entity
  168. .event = NULL, // called when entity receives an event
  169. .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
  170. };
  171. /****** Game ******/
  172. /*
  173. Write here the start code for your game, for example: creating a level and so on.
  174. Game context is allocated (game.context_size) and passed to this function, you can use it to store your game data.
  175. */
  176. static void game_start(GameManager *game_manager, void *ctx)
  177. {
  178. // Do some initialization here, for example you can load score from storage.
  179. // For simplicity, we will just set it to 0.
  180. GameContext *game_context = ctx;
  181. game_context->fps = game_fps_choices_2[game_fps_index];
  182. game_context->player_context = NULL;
  183. // open the world list from storage, then create a level for each world
  184. char file_path[128];
  185. snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  186. FuriString *world_list = flipper_http_load_from_file(file_path);
  187. if (!world_list)
  188. {
  189. FURI_LOG_E("Game", "Failed to load world list");
  190. game_context->levels[0] = game_manager_add_level(game_manager, generic_level("town_world", 0));
  191. game_context->level_count = 1;
  192. return;
  193. }
  194. for (int i = 0; i < 10; i++)
  195. {
  196. FuriString *world_name = get_json_array_value_furi("worlds", i, world_list);
  197. if (!world_name)
  198. {
  199. break;
  200. }
  201. game_context->levels[i] = game_manager_add_level(game_manager, generic_level(furi_string_get_cstr(world_name), i));
  202. furi_string_free(world_name);
  203. game_context->level_count++;
  204. }
  205. furi_string_free(world_list);
  206. // add one enemy
  207. game_context->enemies[0] = level_add_entity(game_context->levels[0], enemy(game_manager,
  208. "player",
  209. 0,
  210. (Vector){10, 10},
  211. (Vector){WORLD_WIDTH / 2 + 11, WORLD_HEIGHT / 2 + 16},
  212. (Vector){WORLD_WIDTH / 2 - 11, WORLD_HEIGHT / 2 + 16},
  213. 1,
  214. 32,
  215. 0.5f,
  216. 10,
  217. 100));
  218. // add another enemy
  219. game_context->enemies[1] = level_add_entity(game_context->levels[0], enemy(game_manager,
  220. "player",
  221. 1,
  222. (Vector){10, 10},
  223. (Vector){WORLD_WIDTH / 2 + 11, WORLD_HEIGHT / 2 + 32},
  224. (Vector){WORLD_WIDTH / 2 - 11, WORLD_HEIGHT / 2 + 32},
  225. 1,
  226. 32,
  227. 0.5f,
  228. 10,
  229. 100));
  230. game_context->enemy_count = 2;
  231. game_context->current_level = 0;
  232. }
  233. /*
  234. Write here the stop code for your game, for example, freeing memory, if it was allocated.
  235. You don't need to free level, sprites or entities, it will be done automatically.
  236. Also, you don't need to free game_context, it will be done automatically, after this function.
  237. */
  238. static void game_stop(void *ctx)
  239. {
  240. GameContext *game_context = ctx;
  241. // If you want to do other final logic (like saving scores), do it here.
  242. // But do NOT free levels[] if the engine manages them.
  243. // Just clear out your pointer array if you like (not strictly necessary)
  244. for (int i = 0; i < game_context->level_count; i++)
  245. {
  246. game_context->levels[i] = NULL;
  247. }
  248. game_context->level_count = 0;
  249. }
  250. /*
  251. Your game configuration, do not rename this variable, but you can change its content here.
  252. */
  253. const Game game = {
  254. .target_fps = 0, // set to 0 because we set this in game_app (callback.c line 22)
  255. .show_fps = false, // show fps counter on the screen
  256. .always_backlight = true, // keep display backlight always on
  257. .start = game_start, // will be called once, when game starts
  258. .stop = game_stop, // will be called once, when game stops
  259. .context_size = sizeof(GameContext), // size of game context
  260. };