world.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #include <game/world.h>
  2. #include <game/storage.h>
  3. #include <flip_storage/storage.h>
  4. #include "game/icon.h"
  5. static IconSpec world_get_icon_spec(const char *name)
  6. {
  7. if (is_str(name, "house"))
  8. return (IconSpec){.id = ICON_ID_HOUSE, .icon = &I_icon_house_48x32px, .size = (Vector){48, 32}};
  9. else if (is_str(name, "man"))
  10. return (IconSpec){.id = ICON_ID_MAN, .icon = &I_icon_man_7x16, .size = (Vector){7, 16}};
  11. else if (is_str(name, "plant"))
  12. return (IconSpec){.id = ICON_ID_PLANT, .icon = &I_icon_plant_16x16, .size = (Vector){16, 16}};
  13. else if (is_str(name, "tree"))
  14. return (IconSpec){.id = ICON_ID_TREE, .icon = &I_icon_tree_16x16, .size = (Vector){16, 16}};
  15. else if (is_str(name, "woman"))
  16. return (IconSpec){.id = ICON_ID_WOMAN, .icon = &I_icon_woman_9x16, .size = (Vector){9, 16}};
  17. else if (is_str(name, "fence"))
  18. return (IconSpec){.id = ICON_ID_FENCE, .icon = &I_icon_fence_16x8px, .size = (Vector){16, 8}};
  19. else if (is_str(name, "fence_end"))
  20. return (IconSpec){.id = ICON_ID_FENCE_END, .icon = &I_icon_fence_end_16x8px, .size = (Vector){16, 8}};
  21. else if (is_str(name, "fence_vertical_end"))
  22. return (IconSpec){.id = ICON_ID_FENCE_VERTICAL_END, .icon = &I_icon_fence_vertical_end_6x8px, .size = (Vector){6, 8}};
  23. else if (is_str(name, "fence_vertical_start"))
  24. return (IconSpec){.id = ICON_ID_FENCE_VERTICAL_START, .icon = &I_icon_fence_vertical_start_6x15px, .size = (Vector){6, 15}};
  25. else if (is_str(name, "flower"))
  26. return (IconSpec){.id = ICON_ID_FLOWER, .icon = &I_icon_flower_16x16, .size = (Vector){16, 16}};
  27. else if (is_str(name, "lake_bottom"))
  28. return (IconSpec){.id = ICON_ID_LAKE_BOTTOM, .icon = &I_icon_lake_bottom_31x12px, .size = (Vector){31, 12}};
  29. else if (is_str(name, "lake_bottom_left"))
  30. return (IconSpec){.id = ICON_ID_LAKE_BOTTOM_LEFT, .icon = &I_icon_lake_bottom_left_24x22px, .size = (Vector){24, 22}};
  31. else if (is_str(name, "lake_bottom_right"))
  32. return (IconSpec){.id = ICON_ID_LAKE_BOTTOM_RIGHT, .icon = &I_icon_lake_bottom_right_24x22px, .size = (Vector){24, 22}};
  33. else if (is_str(name, "lake_left"))
  34. return (IconSpec){.id = ICON_ID_LAKE_LEFT, .icon = &I_icon_lake_left_11x31px, .size = (Vector){11, 31}};
  35. else if (is_str(name, "lake_right"))
  36. return (IconSpec){.id = ICON_ID_LAKE_RIGHT, .icon = &I_icon_lake_right_11x31, .size = (Vector){11, 31}};
  37. else if (is_str(name, "lake_top"))
  38. return (IconSpec){.id = ICON_ID_LAKE_TOP, .icon = &I_icon_lake_top_31x12px, .size = (Vector){31, 12}};
  39. else if (is_str(name, "lake_top_left"))
  40. return (IconSpec){.id = ICON_ID_LAKE_TOP_LEFT, .icon = &I_icon_lake_top_left_24x22px, .size = (Vector){24, 22}};
  41. else if (is_str(name, "lake_top_right"))
  42. return (IconSpec){.id = ICON_ID_LAKE_TOP_RIGHT, .icon = &I_icon_lake_top_right_24x22px, .size = (Vector){24, 22}};
  43. else if (is_str(name, "rock_large"))
  44. return (IconSpec){.id = ICON_ID_ROCK_LARGE, .icon = &I_icon_rock_large_18x19px, .size = (Vector){18, 19}};
  45. else if (is_str(name, "rock_medium"))
  46. return (IconSpec){.id = ICON_ID_ROCK_MEDIUM, .icon = &I_icon_rock_medium_16x14px, .size = (Vector){16, 14}};
  47. else if (is_str(name, "rock_small"))
  48. return (IconSpec){.id = ICON_ID_ROCK_SMALL, .icon = &I_icon_rock_small_10x8px, .size = (Vector){10, 8}};
  49. return (IconSpec){.id = -1, .icon = NULL, .size = (Vector){0, 0}};
  50. }
  51. bool world_json_draw(GameManager *manager, Level *level, const FuriString *json_data)
  52. {
  53. if (!json_data)
  54. {
  55. FURI_LOG_E("Game", "JSON data is NULL");
  56. return false;
  57. }
  58. // Pass 1: Count the total number of icons.
  59. int total_icons = 0;
  60. for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
  61. {
  62. FuriString *data = get_json_array_value_furi("json_data", i, json_data);
  63. if (!data)
  64. break;
  65. FuriString *amount = get_json_value_furi("amount", data);
  66. if (amount)
  67. {
  68. int count = atoi(furi_string_get_cstr(amount));
  69. if (count < 1)
  70. count = 1;
  71. total_icons += count;
  72. furi_string_free(amount);
  73. }
  74. furi_string_free(data);
  75. }
  76. FURI_LOG_I("Game", "Total icons to spawn: %d", total_icons);
  77. // Allocate the icon group context (local instance)
  78. IconGroupContext igctx;
  79. igctx.count = total_icons;
  80. igctx.icons = malloc(total_icons * sizeof(IconSpec));
  81. if (!igctx.icons)
  82. {
  83. FURI_LOG_E("Game", "Failed to allocate icon group array for %d icons", total_icons);
  84. return false;
  85. }
  86. GameContext *game_context = game_manager_game_context_get(manager);
  87. game_context->icon_count = total_icons;
  88. // Pass 2: Parse the JSON to fill the icon specs.
  89. int spec_index = 0;
  90. for (int i = 0; i < MAX_WORLD_OBJECTS; i++)
  91. {
  92. FuriString *data = get_json_array_value_furi("json_data", i, json_data);
  93. if (!data)
  94. break;
  95. FuriString *icon_str = get_json_value_furi("icon", data);
  96. FuriString *x_str = get_json_value_furi("x", data);
  97. FuriString *y_str = get_json_value_furi("y", data);
  98. FuriString *amount_str = get_json_value_furi("amount", data);
  99. FuriString *horizontal_str = get_json_value_furi("horizontal", data);
  100. if (!icon_str || !x_str || !y_str || !amount_str || !horizontal_str)
  101. {
  102. FURI_LOG_E("Game", "Incomplete icon data: %s", furi_string_get_cstr(data));
  103. if (icon_str)
  104. furi_string_free(icon_str);
  105. if (x_str)
  106. furi_string_free(x_str);
  107. if (y_str)
  108. furi_string_free(y_str);
  109. if (amount_str)
  110. furi_string_free(amount_str);
  111. if (horizontal_str)
  112. furi_string_free(horizontal_str);
  113. furi_string_free(data);
  114. continue;
  115. }
  116. int count = atoi(furi_string_get_cstr(amount_str));
  117. if (count < 1)
  118. count = 1;
  119. float base_x = atof_furi(x_str);
  120. float base_y = atof_furi(y_str);
  121. bool is_horizontal = (furi_string_cmp(horizontal_str, "true") == 0);
  122. int spacing = 17;
  123. for (int j = 0; j < count; j++)
  124. {
  125. IconSpec spec = world_get_icon_spec(furi_string_get_cstr(icon_str));
  126. if (!spec.icon)
  127. {
  128. FURI_LOG_E("Game", "Icon name not recognized: %s", furi_string_get_cstr(icon_str));
  129. continue;
  130. }
  131. if (is_horizontal)
  132. {
  133. spec.pos.x = base_x + (j * spacing);
  134. spec.pos.y = base_y;
  135. }
  136. else
  137. {
  138. spec.pos.x = base_x;
  139. spec.pos.y = base_y + (j * spacing);
  140. }
  141. igctx.icons[spec_index++] = spec;
  142. }
  143. furi_string_free(icon_str);
  144. furi_string_free(x_str);
  145. furi_string_free(y_str);
  146. furi_string_free(amount_str);
  147. furi_string_free(horizontal_str);
  148. furi_string_free(data);
  149. }
  150. // Spawn one icon group entity.
  151. Entity *groupEntity = level_add_entity(level, &icon_desc);
  152. IconGroupContext *entityContext = (IconGroupContext *)entity_context_get(groupEntity);
  153. if (entityContext)
  154. {
  155. memcpy(entityContext, &igctx, sizeof(IconGroupContext));
  156. }
  157. else
  158. {
  159. FURI_LOG_E("Game", "Failed to get entity context for icon group");
  160. free(igctx.icons);
  161. return false;
  162. }
  163. // Set the global pointer so that player collision logic can use it.
  164. g_current_icon_group = entityContext;
  165. FURI_LOG_I("Game", "Finished loading world data");
  166. return true;
  167. }
  168. static void world_draw_town(Level *level, GameManager *manager, void *context)
  169. {
  170. UNUSED(context);
  171. if (!manager || !level)
  172. {
  173. FURI_LOG_E("Game", "Manager or level is NULL");
  174. return;
  175. }
  176. GameContext *game_context = game_manager_game_context_get(manager);
  177. level_clear(level);
  178. FuriString *json_data_str = furi_string_alloc();
  179. furi_string_cat_str(json_data_str, "{\"name\":\"shadow_woods_v5\",\"author\":\"ChatGPT\",\"json_data\":[{\"icon\":\"rock_medium\",\"x\":100,\"y\":100,\"amount\":10,\"horizontal\":true},{\"icon\":\"rock_medium\",\"x\":400,\"y\":300,\"amount\":6,\"horizontal\":true},{\"icon\":\"rock_small\",\"x\":600,\"y\":200,\"amount\":8,\"horizontal\":true},{\"icon\":\"fence\",\"x\":50,\"y\":50,\"amount\":10,\"horizontal\":true},{\"icon\":\"fence\",\"x\":250,\"y\":150,\"amount\":12,\"horizontal\":true},{\"icon\":\"fence\",\"x\":550,\"y\":350,\"amount\":12,\"horizontal\":true},{\"icon\":\"rock_large\",\"x\":400,\"y\":70,\"amount\":12,\"horizontal\":true},{\"icon\":\"rock_large\",\"x\":200,\"y\":200,\"amount\":6,\"horizontal\":false},{\"icon\":\"tree\",\"x\":5,\"y\":5,\"amount\":45,\"horizontal\":true},{\"icon\":\"tree\",\"x\":5,\"y\":5,\"amount\":20,\"horizontal\":false},{\"icon\":\"tree\",\"x\":22,\"y\":22,\"amount\":44,\"horizontal\":true},{\"icon\":\"tree\",\"x\":22,\"y\":22,\"amount\":20,\"horizontal\":false},{\"icon\":\"tree\",\"x\":5,\"y\":347,\"amount\":45,\"horizontal\":true},{\"icon\":\"tree\",\"x\":5,\"y\":364,\"amount\":45,\"horizontal\":true},{\"icon\":\"tree\",\"x\":735,\"y\":37,\"amount\":18,\"horizontal\":false},{\"icon\":\"tree\",\"x\":752,\"y\":37,\"amount\":18,\"horizontal\":false}],\"enemy_data\":[{\"id\":\"cyclops\",\"index\":0,\"start_position\":{\"x\":350,\"y\":210},\"end_position\":{\"x\":390,\"y\":210},\"move_timer\":2,\"speed\":30,\"attack_timer\":0.4,\"strength\":10,\"health\":100},{\"id\":\"ogre\",\"index\":1,\"start_position\":{\"x\":200,\"y\":320},\"end_position\":{\"x\":220,\"y\":320},\"move_timer\":0.5,\"speed\":45,\"attack_timer\":0.6,\"strength\":20,\"health\":200},{\"id\":\"ghost\",\"index\":2,\"start_position\":{\"x\":100,\"y\":80},\"end_position\":{\"x\":180,\"y\":85},\"move_timer\":2.2,\"speed\":55,\"attack_timer\":0.5,\"strength\":30,\"health\":300},{\"id\":\"ogre\",\"index\":3,\"start_position\":{\"x\":400,\"y\":50},\"end_position\":{\"x\":490,\"y\":50},\"move_timer\":1.7,\"speed\":35,\"attack_timer\":1.0,\"strength\":20,\"health\":200}],\"npc_data\":[{\"id\":\"funny\",\"index\":0,\"start_position\":{\"x\":350,\"y\":180},\"end_position\":{\"x\":350,\"y\":180},\"move_timer\":0,\"speed\":0,\"message\":\"Hello there!\"}]}");
  180. if (!separate_world_data("shadow_woods_v5", json_data_str))
  181. {
  182. FURI_LOG_E("Game", "Failed to separate world data");
  183. }
  184. furi_string_free(json_data_str);
  185. level_set_world(level, manager, "shadow_woods_v5");
  186. game_context->icon_offset = 0;
  187. if (!game_context->imu_present)
  188. {
  189. game_context->icon_offset += ((game_context->icon_count / 10) / 15);
  190. }
  191. player_spawn(level, manager);
  192. }
  193. static const LevelBehaviour _world_training = {
  194. .alloc = NULL,
  195. .free = NULL,
  196. .start = world_draw_town,
  197. .stop = NULL,
  198. .context_size = 0,
  199. };
  200. const LevelBehaviour *world_training()
  201. {
  202. return &_world_training;
  203. }
  204. static void world_draw_pvp(Level *level, GameManager *manager, void *context)
  205. {
  206. UNUSED(context);
  207. if (!manager || !level)
  208. {
  209. FURI_LOG_E("Game", "Manager or level is NULL");
  210. return;
  211. }
  212. GameContext *game_context = game_manager_game_context_get(manager);
  213. level_clear(level);
  214. FuriString *json_data_str = furi_string_alloc();
  215. furi_string_cat_str(json_data_str, "{\"name\":\"pvp_world\",\"author\":\"ChatGPT\",\"json_data\":[{\"icon\":\"rock_medium\",\"x\":100,\"y\":100,\"amount\":10,\"horizontal\":true},{\"icon\":\"rock_medium\",\"x\":400,\"y\":300,\"amount\":6,\"horizontal\":true},{\"icon\":\"rock_small\",\"x\":600,\"y\":200,\"amount\":8,\"horizontal\":true},{\"icon\":\"fence\",\"x\":50,\"y\":50,\"amount\":10,\"horizontal\":true},{\"icon\":\"fence\",\"x\":250,\"y\":150,\"amount\":12,\"horizontal\":true},{\"icon\":\"fence\",\"x\":550,\"y\":350,\"amount\":12,\"horizontal\":true},{\"icon\":\"rock_large\",\"x\":400,\"y\":70,\"amount\":12,\"horizontal\":true},{\"icon\":\"rock_large\",\"x\":200,\"y\":200,\"amount\":6,\"horizontal\":false},{\"icon\":\"tree\",\"x\":5,\"y\":5,\"amount\":45,\"horizontal\":true},{\"icon\":\"tree\",\"x\":5,\"y\":5,\"amount\":20,\"horizontal\":false},{\"icon\":\"tree\",\"x\":22,\"y\":22,\"amount\":44,\"horizontal\":true},{\"icon\":\"tree\",\"x\":22,\"y\":22,\"amount\":20,\"horizontal\":false},{\"icon\":\"tree\",\"x\":5,\"y\":347,\"amount\":45,\"horizontal\":true},{\"icon\":\"tree\",\"x\":5,\"y\":364,\"amount\":45,\"horizontal\":true},{\"icon\":\"tree\",\"x\":735,\"y\":37,\"amount\":18,\"horizontal\":false},{\"icon\":\"tree\",\"x\":752,\"y\":37,\"amount\":18,\"horizontal\":false}]}");
  216. if (!separate_world_data("pvp_world", json_data_str))
  217. {
  218. FURI_LOG_E("Game", "Failed to separate world data");
  219. }
  220. furi_string_free(json_data_str);
  221. level_set_world(level, manager, "pvp_world");
  222. game_context->is_switching_level = false;
  223. game_context->icon_offset = 0;
  224. if (!game_context->imu_present)
  225. {
  226. game_context->icon_offset += ((game_context->icon_count / 10) / 15);
  227. }
  228. player_spawn(level, manager);
  229. }
  230. static const LevelBehaviour _world_pvp = {
  231. .alloc = NULL,
  232. .free = NULL,
  233. .start = world_draw_pvp,
  234. .stop = NULL,
  235. .context_size = 0,
  236. };
  237. const LevelBehaviour *world_pvp()
  238. {
  239. return &_world_pvp;
  240. }
  241. FuriString *world_fetch(const char *name)
  242. {
  243. if (!name)
  244. {
  245. FURI_LOG_E("Game", "World name is NULL");
  246. return NULL;
  247. }
  248. FlipperHTTP *fhttp = flipper_http_alloc();
  249. if (!fhttp)
  250. {
  251. FURI_LOG_E("Game", "Failed to allocate HTTP");
  252. return NULL;
  253. }
  254. char url[256];
  255. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/v5/get/world/%s/", name);
  256. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", name);
  257. fhttp->save_received_data = true;
  258. if (!flipper_http_request(fhttp, GET, url, "{\"Content-Type\": \"application/json\"}", NULL))
  259. {
  260. FURI_LOG_E("Game", "Failed to send HTTP request");
  261. flipper_http_free(fhttp);
  262. return NULL;
  263. }
  264. fhttp->state = RECEIVING;
  265. furi_timer_start(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  266. while (fhttp->state == RECEIVING && furi_timer_is_running(fhttp->get_timeout_timer) > 0)
  267. {
  268. // Wait for the request to be received
  269. furi_delay_ms(100);
  270. }
  271. furi_timer_stop(fhttp->get_timeout_timer);
  272. if (fhttp->state != IDLE)
  273. {
  274. FURI_LOG_E("Game", "Failed to receive world data");
  275. flipper_http_free(fhttp);
  276. return NULL;
  277. }
  278. flipper_http_free(fhttp);
  279. FuriString *returned_data = load_furi_world(name);
  280. if (!returned_data)
  281. {
  282. FURI_LOG_E("Game", "Failed to load world data from file");
  283. return NULL;
  284. }
  285. if (!separate_world_data((char *)name, returned_data))
  286. {
  287. FURI_LOG_E("Game", "Failed to separate world data");
  288. furi_string_free(returned_data);
  289. return NULL;
  290. }
  291. return returned_data;
  292. }