world.c 16 KB

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