game.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #include <gui/view_holder.h>
  2. #include <game/game.h>
  3. #include <game/storage.h>
  4. #include <alloc/alloc.h>
  5. #include <callback/game.h>
  6. // very simple tutorial check
  7. static bool game_tutorial_done(GameContext *game_context)
  8. {
  9. furi_check(game_context);
  10. char tutorial_done[32];
  11. if (!load_char("tutorial_done", tutorial_done, sizeof(tutorial_done)))
  12. {
  13. FURI_LOG_E("Game", "Failed to load tutorial_done");
  14. game_context->ended_early = true;
  15. game_context->end_reason = GAME_END_TUTORIAL_INCOMPLETE;
  16. return false;
  17. }
  18. if (!is_str(tutorial_done, "J You BLANKED on this one"))
  19. {
  20. FURI_LOG_E("Game", "Tutorial not done");
  21. game_context->ended_early = true;
  22. game_context->end_reason = GAME_END_TUTORIAL_INCOMPLETE;
  23. }
  24. return true;
  25. }
  26. /****** Game ******/
  27. /*
  28. Write here the start code for your game, for example: creating a level and so on.
  29. Game context is allocated (game.context_size) and passed to this function, you can use it to store your game data.
  30. */
  31. static void game_start(GameManager *game_manager, void *ctx)
  32. {
  33. // Do some initialization here, for example you can load score from storage.
  34. // check if enough memory
  35. if (!is_enough_heap(sizeof(GameContext), true))
  36. {
  37. FURI_LOG_E("Game", "Not enough heap memory.. ending game early.");
  38. GameContext *game_context = ctx;
  39. game_context->ended_early = true;
  40. game_context->end_reason = GAME_END_MEMORY;
  41. game_manager_game_stop(game_manager); // end game early
  42. return;
  43. }
  44. // For simplicity, we will just set it to 0.
  45. GameContext *game_context = ctx;
  46. game_context->fps = atof_(fps_choices_str[fps_index]);
  47. game_context->player = NULL;
  48. game_context->ended_early = false;
  49. game_context->current_level = 0;
  50. game_context->level_count = 0;
  51. game_context->enemy_count = 0;
  52. game_context->npc_count = 0;
  53. game_context->end_reason = GAME_END_MEMORY; // default value
  54. game_context->game_mode = game_mode_index;
  55. // set all levels to NULL
  56. for (int i = 0; i < MAX_LEVELS; i++)
  57. game_context->levels[i] = NULL;
  58. // set all enemies to NULL
  59. for (int i = 0; i < MAX_ENEMIES; i++)
  60. game_context->enemies[i] = NULL;
  61. // set all npcs to NULL
  62. for (int i = 0; i < MAX_NPCS; i++)
  63. game_context->npcs[i] = NULL;
  64. if (game_context->game_mode == GAME_MODE_PVE)
  65. {
  66. game_tutorial_done(game_context); // the game will end if tutorial is not done
  67. // attempt to allocate all levels
  68. for (int i = 0; i < MAX_LEVELS; i++)
  69. {
  70. if (!allocate_level(game_manager, i))
  71. {
  72. FURI_LOG_E("Game", "Failed to allocate level %d", i);
  73. if (i == 0)
  74. {
  75. game_context->levels[0] = game_manager_add_level(game_manager, story_world());
  76. game_context->level_count = 1;
  77. }
  78. break;
  79. }
  80. else
  81. game_context->level_count++;
  82. }
  83. }
  84. else if (game_context->game_mode == GAME_MODE_STORY)
  85. {
  86. if (load_uint32("story_step", &game_context->story_step) == false)
  87. {
  88. game_context->story_step = 0;
  89. }
  90. game_context->levels[0] = game_manager_add_level(game_manager, story_world());
  91. game_context->level_count = 1;
  92. for (int i = 1; i < MAX_LEVELS; i++)
  93. {
  94. if (!allocate_level(game_manager, i))
  95. {
  96. FURI_LOG_E("Game", "Failed to allocate level %d", i);
  97. break;
  98. }
  99. else
  100. game_context->level_count++;
  101. }
  102. }
  103. else if (game_context->game_mode == GAME_MODE_PVP)
  104. {
  105. game_tutorial_done(game_context); // the game will end if tutorial is not done
  106. // show pvp
  107. game_context->levels[0] = game_manager_add_level(game_manager, world_pvp());
  108. game_context->level_count = 1;
  109. }
  110. // imu
  111. game_context->imu = imu_alloc();
  112. game_context->imu_present = imu_present(game_context->imu);
  113. // FlipperHTTP
  114. if (game_context->game_mode != GAME_MODE_STORY)
  115. {
  116. // check if enough memory
  117. if (!is_enough_heap(sizeof(FlipperHTTP), true))
  118. {
  119. FURI_LOG_E("Game", "Not enough heap memory.. ending game early.");
  120. game_context->end_reason = GAME_END_MEMORY;
  121. game_context->ended_early = true;
  122. game_manager_game_stop(game_manager); // end game early
  123. return;
  124. }
  125. game_context->fhttp = flipper_http_alloc();
  126. if (!game_context->fhttp)
  127. {
  128. FURI_LOG_E("Game", "Failed to allocate FlipperHTTP");
  129. game_context->end_reason = GAME_END_MEMORY;
  130. game_context->ended_early = true;
  131. game_manager_game_stop(game_manager); // end game early
  132. return;
  133. }
  134. }
  135. }
  136. static void thanks(Canvas *canvas, void *context)
  137. {
  138. UNUSED(context);
  139. canvas_set_font(canvas, FontPrimary);
  140. canvas_draw_str(canvas, 35, 8, "Saving game");
  141. canvas_set_font(canvas, FontSecondary);
  142. canvas_draw_str(canvas, 0, 50, "Please wait while your");
  143. canvas_draw_str(canvas, 0, 60, "game is saved.");
  144. }
  145. /*
  146. Write here the stop code for your game, for example, freeing memory, if it was allocated.
  147. You don't need to free level, sprites or entities, it will be done automatically.
  148. Also, you don't need to free game_context, it will be done automatically, after this function.
  149. */
  150. static void game_stop(void *ctx)
  151. {
  152. furi_check(ctx);
  153. GameContext *game_context = ctx;
  154. const size_t heap_size = memmgr_heap_get_max_free_block();
  155. imu_free(game_context->imu);
  156. game_context->imu = NULL;
  157. // clear current level early
  158. if (game_context->levels[game_context->current_level])
  159. {
  160. level_clear(game_context->levels[game_context->current_level]);
  161. }
  162. if (game_context->game_mode != GAME_MODE_STORY)
  163. {
  164. if (game_context->fhttp)
  165. {
  166. flipper_http_websocket_stop(game_context->fhttp); // close websocket
  167. game_remove_from_lobby(game_context->fhttp); // remove player from lobby
  168. flipper_http_free(game_context->fhttp);
  169. }
  170. }
  171. else
  172. {
  173. save_uint32("story_step", game_context->story_step);
  174. }
  175. if (!game_context->ended_early)
  176. {
  177. easy_flipper_dialog("Game Over", "Thanks for playing FlipWorld!\nHit BACK then wait for\nthe game to save.");
  178. }
  179. else
  180. {
  181. char message[128];
  182. switch (game_context->end_reason)
  183. {
  184. case GAME_END_MEMORY:
  185. snprintf(message, sizeof(message), "Ran out of memory so the\ngame ended early. There were\n%zu bytes free.\n\nHit BACK to exit.", heap_size);
  186. break;
  187. case GAME_END_TUTORIAL_INCOMPLETE:
  188. snprintf(message, sizeof(message), "The tutorial is not complete.\nPlease finish the tutorial to\nsave your game.\n\nHit BACK to exit.");
  189. break;
  190. case GAME_END_PVP_REQUIREMENT:
  191. snprintf(message, sizeof(message), "You need to be level 10 to\nplay PvP.\n\nHit BACK to exit.");
  192. break;
  193. case GAME_END_PVP_ENEMY_DEAD:
  194. snprintf(message, sizeof(message), "You have defeated the enemy!\n\nHit BACK to exit.");
  195. break;
  196. case GAME_END_PVP_PLAYER_DEAD:
  197. snprintf(message, sizeof(message), "You have been defeated!\n\nHit BACK to exit.");
  198. break;
  199. case GAME_END_NETWORK:
  200. snprintf(message, sizeof(message), "Network error. Please check\nyour connection and try again.\n\nHit BACK to exit.");
  201. break;
  202. case GAME_END_APP:
  203. snprintf(message, sizeof(message), "App error.\n\nHit BACK to exit.");
  204. break;
  205. };
  206. easy_flipper_dialog("Game Over", message);
  207. }
  208. PlayerContext *player_context = malloc(sizeof(PlayerContext));
  209. if (!player_context)
  210. {
  211. FURI_LOG_E("Game", "Failed to allocate PlayerContext");
  212. return;
  213. }
  214. // save the player context
  215. if (load_player_context(player_context))
  216. {
  217. ViewPort *view_port = view_port_alloc();
  218. view_port_draw_callback_set(view_port, thanks, NULL);
  219. Gui *gui = furi_record_open(RECORD_GUI);
  220. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  221. uint32_t tick_count = furi_get_tick();
  222. furi_delay_ms(800);
  223. // save the player context to the API
  224. game_context->fhttp = flipper_http_alloc();
  225. if (game_context->fhttp)
  226. {
  227. save_player_context_api(player_context, game_context->fhttp);
  228. flipper_http_free(game_context->fhttp);
  229. }
  230. const uint32_t delay = 3500;
  231. tick_count = (tick_count + delay) - furi_get_tick();
  232. if (tick_count <= delay)
  233. {
  234. furi_delay_ms(tick_count);
  235. }
  236. easy_flipper_dialog("Game Saved", "Hit BACK to exit.");
  237. flip_world_show_submenu();
  238. gui_remove_view_port(gui, view_port);
  239. furi_record_close(RECORD_GUI);
  240. }
  241. // free the player context
  242. if (player_context)
  243. {
  244. free(player_context);
  245. player_context = NULL;
  246. }
  247. }
  248. /*
  249. Your game configuration, do not rename this variable, but you can change its content here.
  250. */
  251. const Game game = {
  252. .target_fps = 0, // set to 0 because we set this in game_app (callback.c line 22)
  253. .show_fps = false, // show fps counter on the screen
  254. .always_backlight = true, // keep display backlight always on
  255. .start = game_start, // will be called once, when game starts
  256. .stop = game_stop, // will be called once, when game stops
  257. .context_size = sizeof(GameContext), // size of game context
  258. };