player.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. #include <ArduinoJson.h>
  2. #include "player.h"
  3. #include "sprites.h"
  4. typedef struct
  5. {
  6. const char *name;
  7. uint8_t *data;
  8. Vector size;
  9. } PlayerContext;
  10. static PlayerContext player_context_get(const char *name, bool is_left)
  11. {
  12. // players
  13. if (strcmp(name, "naked") == 0)
  14. return {name, is_left ? player_left_naked_10x10px : player_right_naked_10x10px, Vector(10, 10)};
  15. if (strcmp(name, "sword") == 0)
  16. return {name, is_left ? player_left_sword_15x11px : player_right_sword_15x11px, Vector(15, 11)};
  17. if (strcmp(name, "axe") == 0)
  18. return {name, is_left ? player_left_axe_15x11px : player_right_axe_15x11px, Vector(15, 11)};
  19. if (strcmp(name, "bow") == 0)
  20. return {name, is_left ? player_left_bow_13x11px : player_right_bow_13x11px, Vector(13, 11)};
  21. // enemies
  22. if (strcmp(name, "cyclops") == 0)
  23. return {name, is_left ? enemy_left_cyclops_10x11px : enemy_right_cyclops_10x11px, Vector(10, 11)};
  24. if (strcmp(name, "ghost") == 0)
  25. return {name, is_left ? enemy_left_ghost_15x15px : enemy_right_ghost_15x15px, Vector(15, 15)};
  26. if (strcmp(name, "ogre") == 0)
  27. return {name, is_left ? enemy_left_ogre_10x13px : enemy_right_ogre_10x13px, Vector(10, 13)};
  28. return {NULL, NULL, Vector(0, 0)};
  29. }
  30. static void enemy_update(Entity *self, Game *game)
  31. {
  32. // check if enemy is dead
  33. if (self->state == ENTITY_DEAD)
  34. {
  35. return;
  36. }
  37. // float delta_time = 1.0 / game->fps;
  38. float delta_time = 1.0 / 30; // 30 frames per second
  39. // Increment the elapsed_attack_timer for the enemy
  40. self->elapsed_attack_timer += delta_time;
  41. switch (self->state)
  42. {
  43. case ENTITY_IDLE:
  44. // Increment the elapsed_move_timer
  45. self->elapsed_move_timer += delta_time;
  46. self->position_set(self->position);
  47. // Check if it's time to move again
  48. if (self->elapsed_move_timer >= self->move_timer)
  49. {
  50. // Determine the next state based on the current position
  51. if (fabs(self->position.x - self->start_position.x) < 1 && fabs(self->position.y - self->start_position.y) < 1)
  52. {
  53. self->state = ENTITY_MOVING_TO_END;
  54. }
  55. else if (fabs(self->position.x - self->end_position.x) < 1 && fabs(self->position.y - self->end_position.y) < 1)
  56. {
  57. self->state = ENTITY_MOVING_TO_START;
  58. }
  59. // Reset the elapsed_move_timer
  60. self->elapsed_move_timer = 0;
  61. }
  62. break;
  63. case ENTITY_MOVING_TO_END:
  64. case ENTITY_MOVING_TO_START:
  65. case ENTITY_ATTACKED:
  66. // determine the direction vector
  67. Vector direction_vector = {0, 0};
  68. // if attacked, change state to moving based on the direction
  69. if (self->state == ENTITY_ATTACKED)
  70. {
  71. self->state = self->position.x < self->old_position.x ? ENTITY_MOVING_TO_END : ENTITY_MOVING_TO_START;
  72. }
  73. // Determine the target position based on the current state
  74. Vector target_position = self->state == ENTITY_MOVING_TO_END ? self->end_position : self->start_position;
  75. // Calculate direction towards the target
  76. if (self->position.x < target_position.x)
  77. {
  78. direction_vector.x = 1;
  79. self->direction = ENTITY_RIGHT;
  80. }
  81. else if (self->position.x > target_position.x)
  82. {
  83. direction_vector.x = -1;
  84. self->direction = ENTITY_LEFT;
  85. }
  86. else if (self->position.y < target_position.y)
  87. {
  88. direction_vector.y = 1;
  89. self->direction = ENTITY_DOWN;
  90. }
  91. else if (self->position.y > target_position.y)
  92. {
  93. direction_vector.y = -1;
  94. self->direction = ENTITY_UP;
  95. }
  96. // Normalize direction vector
  97. float length = sqrt(direction_vector.x * direction_vector.x + direction_vector.y * direction_vector.y);
  98. if (length != 0)
  99. {
  100. direction_vector.x /= length;
  101. direction_vector.y /= length;
  102. }
  103. // Update position based on direction and speed
  104. Vector new_pos = self->position;
  105. new_pos.x += direction_vector.x * self->speed * delta_time;
  106. new_pos.y += direction_vector.y * self->speed * delta_time;
  107. // Clamp the position to the target to prevent overshooting
  108. if ((direction_vector.x > 0 && new_pos.x > target_position.x) || (direction_vector.x < 0 && new_pos.x < target_position.x))
  109. {
  110. new_pos.x = target_position.x;
  111. }
  112. if ((direction_vector.y > 0 && new_pos.y > target_position.y) || (direction_vector.y < 0 && new_pos.y < target_position.y))
  113. {
  114. new_pos.y = target_position.y;
  115. }
  116. // Set the new position
  117. self->position_set(new_pos);
  118. // Check if the enemy has reached or surpassed the target_position
  119. bool reached_x = fabs(new_pos.x - target_position.x) < 1;
  120. bool reached_y = fabs(new_pos.y - target_position.y) < 1;
  121. if (reached_x && reached_y)
  122. {
  123. // Set the state to idle
  124. self->state = ENTITY_IDLE;
  125. self->elapsed_move_timer = 0;
  126. self->position_changed = true;
  127. }
  128. break;
  129. }
  130. }
  131. static void draw_username(Game *game, Vector pos, const char *username)
  132. {
  133. // skip if drawing the username is out of the screen
  134. if (pos.x - game->pos.x - (strlen(username) * 2 + 8) < 0 || pos.x - game->pos.x + (strlen(username) * 2 + 8) > game->size.x ||
  135. pos.y - game->pos.y - 10 < 0 || pos.y - game->pos.y > game->size.y)
  136. {
  137. return;
  138. }
  139. // draw box around the username
  140. game->draw->display->fillRect(pos.x - game->pos.x - (strlen(username) * 2), pos.y - game->pos.y - 10, strlen(username) * 5 + 4, 10, TFT_WHITE);
  141. // draw username over player's head
  142. game->draw->text(Vector(pos.x - game->pos.x - (strlen(username) * 2), pos.y - game->pos.y - 10), username, TFT_RED);
  143. }
  144. static void enemy_render(Entity *self, Draw *draw, Game *game)
  145. {
  146. if (self->state == ENTITY_DEAD)
  147. {
  148. return;
  149. }
  150. char health_str[32];
  151. snprintf(health_str, sizeof(health_str), "%.0f", (double)self->health);
  152. // skip if enemy is out of the screen
  153. if (self->position.x + self->size.x < game->pos.x || self->position.x > game->pos.x + game->size.x ||
  154. self->position.y + self->size.y < game->pos.y || self->position.y > game->pos.y + game->size.y)
  155. {
  156. return;
  157. }
  158. // Choose sprite based on direction
  159. if (self->direction == ENTITY_LEFT)
  160. {
  161. self->sprite = self->sprite_left;
  162. self->size = self->sprite_left->size;
  163. }
  164. else if (self->direction == ENTITY_RIGHT)
  165. {
  166. self->sprite = self->sprite_right;
  167. self->size = self->sprite_right->size;
  168. }
  169. // draw health of enemy
  170. draw_username(game, self->position, health_str);
  171. }
  172. int last_button = -1;
  173. // Enemy collision function: when this is called, the enemy has collided with another entity
  174. static void enemy_collision(Entity *self, Entity *other, Game *game)
  175. {
  176. if (strcmp(other->name, "Player") == 0)
  177. {
  178. // Get positions of the enemy and the player
  179. Vector enemy_pos = self->position;
  180. Vector player_pos = other->position;
  181. // Determine if the enemy is facing the player or player is facing the enemy
  182. bool enemy_is_facing_player = false;
  183. bool player_is_facing_enemy = false;
  184. if (self->direction == ENTITY_LEFT && player_pos.x < enemy_pos.x ||
  185. self->direction == ENTITY_RIGHT && player_pos.x > enemy_pos.x ||
  186. self->direction == ENTITY_UP && player_pos.y < enemy_pos.y ||
  187. self->direction == ENTITY_DOWN && player_pos.y > enemy_pos.y)
  188. {
  189. enemy_is_facing_player = true;
  190. }
  191. if (other->direction == ENTITY_LEFT && enemy_pos.x < player_pos.x ||
  192. other->direction == ENTITY_RIGHT && enemy_pos.x > player_pos.x ||
  193. other->direction == ENTITY_UP && enemy_pos.y < player_pos.y ||
  194. other->direction == ENTITY_DOWN && enemy_pos.y > player_pos.y)
  195. {
  196. player_is_facing_enemy = true;
  197. }
  198. // Handle Player Attacking Enemy (Press OK, facing enemy, and enemy not facing player)
  199. // we need to store the last button pressed to prevent multiple attacks
  200. if (player_is_facing_enemy && last_button == BUTTON_CENTER && !enemy_is_facing_player)
  201. {
  202. // Reset last button
  203. last_button = -1;
  204. // check if enough time has passed since the last attack
  205. if (other->elapsed_attack_timer >= other->attack_timer)
  206. {
  207. // Reset player's elapsed attack timer
  208. other->elapsed_attack_timer = 0;
  209. self->elapsed_attack_timer = 0; // Reset enemy's attack timer to block enemy attack
  210. // Increase XP by the enemy's strength
  211. other->xp += self->strength;
  212. // Increase health by 10% of the enemy's strength
  213. other->health += self->strength * 0.1;
  214. // check max health
  215. if (other->health > 100)
  216. {
  217. other->health = 100;
  218. }
  219. // Decrease enemy health by player strength
  220. self->health -= other->strength;
  221. // check if enemy is dead
  222. if (self->health > 0)
  223. {
  224. self->state = ENTITY_ATTACKED;
  225. self->elapsed_move_timer = 0;
  226. self->position_changed = true;
  227. self->position_set(self->old_position);
  228. }
  229. }
  230. }
  231. // Handle Enemy Attacking Player (enemy facing player)
  232. else if (enemy_is_facing_player)
  233. {
  234. // check if enough time has passed since the last attack
  235. if (self->elapsed_attack_timer >= self->attack_timer)
  236. {
  237. // Reset enemy's elapsed attack timer
  238. self->elapsed_attack_timer = 0;
  239. // Decrease player health by enemy strength
  240. other->health -= self->strength;
  241. // check if player is dead
  242. if (other->health > 0)
  243. {
  244. other->state = ENTITY_ATTACKED;
  245. other->position_set(other->old_position);
  246. }
  247. }
  248. }
  249. // check if player is dead
  250. if (other->health <= 0)
  251. {
  252. other->state = ENTITY_DEAD;
  253. other->position = other->start_position;
  254. other->health = other->max_health;
  255. other->position_set(other->start_position);
  256. }
  257. // check if enemy is dead
  258. if (self->health <= 0)
  259. {
  260. self->state = ENTITY_DEAD;
  261. self->position = Vector(-100, -100);
  262. self->health = 0;
  263. self->elapsed_move_timer = 0;
  264. self->position_set(self->position);
  265. }
  266. }
  267. }
  268. static void enemy_spawn(
  269. Level *level,
  270. const char *name,
  271. EntityDirection direction,
  272. Vector start_position,
  273. Vector end_position,
  274. float move_timer,
  275. float elapsed_move_timer,
  276. float speed,
  277. float attack_timer,
  278. float elapsed_attack_timer,
  279. float strength,
  280. float health)
  281. {
  282. // Get the enemy context
  283. PlayerContext enemy_left = player_context_get(name, true);
  284. PlayerContext enemy_right = player_context_get(name, false);
  285. // check if enemy context is valid
  286. if (enemy_left.data != NULL && enemy_right.data != NULL)
  287. {
  288. // Create the enemy entity
  289. Entity *entity = new Entity(name, ENTITY_ENEMY, start_position, enemy_left.size, enemy_left.data, enemy_left.data, enemy_right.data, NULL, NULL, enemy_update, enemy_render, enemy_collision, true);
  290. entity->direction = direction;
  291. entity->start_position = start_position;
  292. entity->end_position = end_position;
  293. entity->move_timer = move_timer;
  294. entity->elapsed_move_timer = elapsed_move_timer;
  295. entity->speed = speed;
  296. entity->attack_timer = attack_timer;
  297. entity->elapsed_attack_timer = elapsed_attack_timer;
  298. entity->strength = strength;
  299. entity->health = health;
  300. entity->max_health = health;
  301. // Add the enemy entity to the level
  302. level->entity_add(entity);
  303. }
  304. }
  305. void enemy_spawn_json(Level *level, const char *json)
  306. {
  307. // Parse the json
  308. JsonDocument doc;
  309. DeserializationError error = deserializeJson(doc, json);
  310. // Check for errors
  311. if (error)
  312. {
  313. return;
  314. }
  315. // Loop through the json data
  316. int index = 0;
  317. while (doc["enemy_data"][index])
  318. {
  319. // Get the enemy data
  320. const char *id = doc["enemy_data"][index]["id"];
  321. Vector start_position = Vector(doc["enemy_data"][index]["start_position"]["x"], doc["enemy_data"][index]["start_position"]["y"]);
  322. Vector end_position = Vector(doc["enemy_data"][index]["end_position"]["x"], doc["enemy_data"][index]["end_position"]["y"]);
  323. float move_timer = doc["enemy_data"][index]["move_timer"];
  324. float speed = doc["enemy_data"][index]["speed"];
  325. float attack_timer = doc["enemy_data"][index]["attack_timer"];
  326. float strength = doc["enemy_data"][index]["strength"];
  327. float health = doc["enemy_data"][index]["health"];
  328. // Spawn the enemy entity
  329. enemy_spawn(level, id, ENTITY_LEFT, start_position, end_position, move_timer, 0, speed, attack_timer, 0, strength, health);
  330. // Increment the index
  331. index++;
  332. }
  333. }
  334. // Update player stats based on XP using iterative method
  335. static int get_player_level_iterative(uint32_t xp)
  336. {
  337. int level = 1;
  338. uint32_t xp_required = 100; // Base XP for level 2
  339. while (level < 100 && xp >= xp_required) // Maximum level supported
  340. {
  341. level++;
  342. xp_required = (uint32_t)(xp_required * 1.5); // 1.5 growth factor per level
  343. }
  344. return level;
  345. }
  346. static void update_stats(Entity *player)
  347. {
  348. // Determine the player's level based on XP
  349. player->level = get_player_level_iterative(player->xp);
  350. // Update strength and max health based on the new level
  351. player->strength = 10 + (player->level * 1); // 1 strength per level
  352. player->max_health = 100 + ((player->level - 1) * 10); // 10 health per level
  353. }
  354. static void player_update(Entity *self, Game *game)
  355. {
  356. // Apply health regeneration
  357. self->elapsed_health_regen += 1.0 / 30; // 30 frames per second
  358. if (self->elapsed_health_regen >= 1 && self->health < self->max_health)
  359. {
  360. self->health += self->health_regen;
  361. self->elapsed_health_regen = 0;
  362. if (self->health > self->max_health)
  363. {
  364. self->health = self->max_health;
  365. }
  366. }
  367. // Increment the elapsed_attack_timer for the player
  368. self->elapsed_attack_timer += 1.0 / 30; // 30 frames per second
  369. // update plyer traits
  370. update_stats(self);
  371. Vector oldPos = self->position;
  372. Vector newPos = oldPos;
  373. // Move according to input
  374. if (game->input == BUTTON_UP)
  375. {
  376. newPos.y -= 5;
  377. self->direction = ENTITY_UP;
  378. last_button = BUTTON_UP;
  379. }
  380. else if (game->input == BUTTON_DOWN)
  381. {
  382. newPos.y += 5;
  383. self->direction = ENTITY_DOWN;
  384. last_button = BUTTON_DOWN;
  385. }
  386. else if (game->input == BUTTON_LEFT)
  387. {
  388. newPos.x -= 5;
  389. self->direction = ENTITY_LEFT;
  390. last_button = BUTTON_LEFT;
  391. }
  392. else if (game->input == BUTTON_RIGHT)
  393. {
  394. newPos.x += 5;
  395. self->direction = ENTITY_RIGHT;
  396. last_button = BUTTON_RIGHT;
  397. }
  398. else if (game->input == BUTTON_CENTER)
  399. {
  400. last_button = BUTTON_CENTER;
  401. }
  402. // reset input
  403. game->input = -1;
  404. // Tentatively set new position
  405. self->position_set(newPos);
  406. // check if new position is within the level boundaries
  407. if (newPos.x < 0 || newPos.x + self->size.x > game->current_level->size.x ||
  408. newPos.y < 0 || newPos.y + self->size.y > game->current_level->size.y)
  409. {
  410. // restore old position
  411. self->position_set(oldPos);
  412. }
  413. // Store the current camera position before updating
  414. game->old_pos = game->pos;
  415. // Update camera position to center the player
  416. float camera_x = self->position.x - (game->size.x / 2);
  417. float camera_y = self->position.y - (game->size.y / 2);
  418. // Clamp camera position to the world boundaries
  419. camera_x = constrain(camera_x, 0, game->current_level->size.x - game->size.x);
  420. camera_y = constrain(camera_y, 0, game->current_level->size.y - game->size.y);
  421. // Set the new camera position
  422. game->pos = Vector(camera_x, camera_y);
  423. // update player sprite based on direction
  424. if (self->direction == ENTITY_LEFT)
  425. {
  426. self->sprite = self->sprite_left;
  427. }
  428. else if (self->direction == ENTITY_RIGHT)
  429. {
  430. self->sprite = self->sprite_right;
  431. }
  432. }
  433. // Draw the user stats (health, xp, and level)
  434. static void draw_user_stats(Entity *self, Vector pos, Game *game)
  435. {
  436. // first draw a white rectangle to make the text more readable
  437. game->draw->display->fillRect(pos.x - 2, pos.y - 5, 48, 32, TFT_WHITE);
  438. char health[32];
  439. char xp[32];
  440. char level[32];
  441. snprintf(health, sizeof(health), "HP : %.0f", (double)self->health);
  442. snprintf(level, sizeof(level), "LVL: %.0f", (double)self->level);
  443. if (self->xp < 10000)
  444. snprintf(xp, sizeof(xp), "XP : %.0f", (double)self->xp);
  445. else
  446. snprintf(xp, sizeof(xp), "XP : %.0fK", (double)self->xp / 1000);
  447. // draw items
  448. game->draw->text(Vector(pos.x, pos.y), health, TFT_RED);
  449. game->draw->text(Vector(pos.x, pos.y + 9), xp, TFT_RED);
  450. game->draw->text(Vector(pos.x, pos.y + 18), level, TFT_RED);
  451. }
  452. static void player_render(Entity *self, Draw *draw, Game *game)
  453. {
  454. draw_username(game, self->position, "Player"); // draw the username at the new position
  455. draw_user_stats(self, Vector(5, 210), game); // draw the user stats at the new position
  456. }
  457. void player_spawn(Level *level, const char *name, Vector position)
  458. {
  459. // Get the player context
  460. PlayerContext player_left = player_context_get(name, true);
  461. PlayerContext player_right = player_context_get(name, false);
  462. // check if player context is valid
  463. if (player_left.data != NULL && player_right.data != NULL)
  464. {
  465. // Create the player entity
  466. Entity *player = new Entity("Player", ENTITY_PLAYER, position, player_left.size, player_left.data, player_left.data, player_right.data, NULL, NULL, player_update, player_render, NULL, true);
  467. player->level = 1;
  468. player->health = 100;
  469. player->max_health = 100;
  470. player->strength = 10;
  471. player->attack_timer = 1;
  472. player->health_regen = 1;
  473. level->entity_add(player);
  474. }
  475. }