| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 |
- #include <ArduinoJson.h>
- #include "player.h"
- #include "sprites.h"
- typedef struct
- {
- const char *name;
- uint8_t *data;
- Vector size;
- } PlayerContext;
- static PlayerContext player_context_get(const char *name, bool is_left)
- {
- // players
- if (strcmp(name, "naked") == 0)
- return {name, is_left ? player_left_naked_10x10px : player_right_naked_10x10px, Vector(10, 10)};
- if (strcmp(name, "sword") == 0)
- return {name, is_left ? player_left_sword_15x11px : player_right_sword_15x11px, Vector(15, 11)};
- if (strcmp(name, "axe") == 0)
- return {name, is_left ? player_left_axe_15x11px : player_right_axe_15x11px, Vector(15, 11)};
- if (strcmp(name, "bow") == 0)
- return {name, is_left ? player_left_bow_13x11px : player_right_bow_13x11px, Vector(13, 11)};
- // enemies
- if (strcmp(name, "cyclops") == 0)
- return {name, is_left ? enemy_left_cyclops_10x11px : enemy_right_cyclops_10x11px, Vector(10, 11)};
- if (strcmp(name, "ghost") == 0)
- return {name, is_left ? enemy_left_ghost_15x15px : enemy_right_ghost_15x15px, Vector(15, 15)};
- if (strcmp(name, "ogre") == 0)
- return {name, is_left ? enemy_left_ogre_10x13px : enemy_right_ogre_10x13px, Vector(10, 13)};
- return {NULL, NULL, Vector(0, 0)};
- }
- static void enemy_update(Entity *self, Game *game)
- {
- // check if enemy is dead
- if (self->state == ENTITY_DEAD)
- {
- return;
- }
- // float delta_time = 1.0 / game->fps;
- float delta_time = 1.0 / 30; // 30 frames per second
- // Increment the elapsed_attack_timer for the enemy
- self->elapsed_attack_timer += delta_time;
- switch (self->state)
- {
- case ENTITY_IDLE:
- // Increment the elapsed_move_timer
- self->elapsed_move_timer += delta_time;
- self->position_set(self->position);
- // Check if it's time to move again
- if (self->elapsed_move_timer >= self->move_timer)
- {
- // Determine the next state based on the current position
- if (fabs(self->position.x - self->start_position.x) < 1 && fabs(self->position.y - self->start_position.y) < 1)
- {
- self->state = ENTITY_MOVING_TO_END;
- }
- else if (fabs(self->position.x - self->end_position.x) < 1 && fabs(self->position.y - self->end_position.y) < 1)
- {
- self->state = ENTITY_MOVING_TO_START;
- }
- // Reset the elapsed_move_timer
- self->elapsed_move_timer = 0;
- }
- break;
- case ENTITY_MOVING_TO_END:
- case ENTITY_MOVING_TO_START:
- case ENTITY_ATTACKED:
- // determine the direction vector
- Vector direction_vector = {0, 0};
- // if attacked, change state to moving based on the direction
- if (self->state == ENTITY_ATTACKED)
- {
- self->state = self->position.x < self->old_position.x ? ENTITY_MOVING_TO_END : ENTITY_MOVING_TO_START;
- }
- // Determine the target position based on the current state
- Vector target_position = self->state == ENTITY_MOVING_TO_END ? self->end_position : self->start_position;
- // Calculate direction towards the target
- if (self->position.x < target_position.x)
- {
- direction_vector.x = 1;
- self->direction = ENTITY_RIGHT;
- }
- else if (self->position.x > target_position.x)
- {
- direction_vector.x = -1;
- self->direction = ENTITY_LEFT;
- }
- else if (self->position.y < target_position.y)
- {
- direction_vector.y = 1;
- self->direction = ENTITY_DOWN;
- }
- else if (self->position.y > target_position.y)
- {
- direction_vector.y = -1;
- self->direction = ENTITY_UP;
- }
- // Normalize direction vector
- float length = sqrt(direction_vector.x * direction_vector.x + direction_vector.y * direction_vector.y);
- if (length != 0)
- {
- direction_vector.x /= length;
- direction_vector.y /= length;
- }
- // Update position based on direction and speed
- Vector new_pos = self->position;
- new_pos.x += direction_vector.x * self->speed * delta_time;
- new_pos.y += direction_vector.y * self->speed * delta_time;
- // Clamp the position to the target to prevent overshooting
- if ((direction_vector.x > 0 && new_pos.x > target_position.x) || (direction_vector.x < 0 && new_pos.x < target_position.x))
- {
- new_pos.x = target_position.x;
- }
- if ((direction_vector.y > 0 && new_pos.y > target_position.y) || (direction_vector.y < 0 && new_pos.y < target_position.y))
- {
- new_pos.y = target_position.y;
- }
- // Set the new position
- self->position_set(new_pos);
- // Check if the enemy has reached or surpassed the target_position
- bool reached_x = fabs(new_pos.x - target_position.x) < 1;
- bool reached_y = fabs(new_pos.y - target_position.y) < 1;
- if (reached_x && reached_y)
- {
- // Set the state to idle
- self->state = ENTITY_IDLE;
- self->elapsed_move_timer = 0;
- self->position_changed = true;
- }
- break;
- }
- }
- static void draw_username(Game *game, Vector pos, const char *username)
- {
- // skip if drawing the username is out of the screen
- if (pos.x - game->pos.x - (strlen(username) * 2 + 8) < 0 || pos.x - game->pos.x + (strlen(username) * 2 + 8) > game->size.x ||
- pos.y - game->pos.y - 10 < 0 || pos.y - game->pos.y > game->size.y)
- {
- return;
- }
- // draw box around the username
- 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);
- // draw username over player's head
- game->draw->text(Vector(pos.x - game->pos.x - (strlen(username) * 2), pos.y - game->pos.y - 10), username, TFT_RED);
- }
- static void enemy_render(Entity *self, Draw *draw, Game *game)
- {
- if (self->state == ENTITY_DEAD)
- {
- return;
- }
- char health_str[32];
- snprintf(health_str, sizeof(health_str), "%.0f", (double)self->health);
- // skip if enemy is out of the screen
- if (self->position.x + self->size.x < game->pos.x || self->position.x > game->pos.x + game->size.x ||
- self->position.y + self->size.y < game->pos.y || self->position.y > game->pos.y + game->size.y)
- {
- return;
- }
- // Choose sprite based on direction
- if (self->direction == ENTITY_LEFT)
- {
- self->sprite = self->sprite_left;
- self->size = self->sprite_left->size;
- }
- else if (self->direction == ENTITY_RIGHT)
- {
- self->sprite = self->sprite_right;
- self->size = self->sprite_right->size;
- }
- // draw health of enemy
- draw_username(game, self->position, health_str);
- }
- int last_button = -1;
- // Enemy collision function: when this is called, the enemy has collided with another entity
- static void enemy_collision(Entity *self, Entity *other, Game *game)
- {
- if (strcmp(other->name, "Player") == 0)
- {
- // Get positions of the enemy and the player
- Vector enemy_pos = self->position;
- Vector player_pos = other->position;
- // Determine if the enemy is facing the player or player is facing the enemy
- bool enemy_is_facing_player = false;
- bool player_is_facing_enemy = false;
- if (self->direction == ENTITY_LEFT && player_pos.x < enemy_pos.x ||
- self->direction == ENTITY_RIGHT && player_pos.x > enemy_pos.x ||
- self->direction == ENTITY_UP && player_pos.y < enemy_pos.y ||
- self->direction == ENTITY_DOWN && player_pos.y > enemy_pos.y)
- {
- enemy_is_facing_player = true;
- }
- if (other->direction == ENTITY_LEFT && enemy_pos.x < player_pos.x ||
- other->direction == ENTITY_RIGHT && enemy_pos.x > player_pos.x ||
- other->direction == ENTITY_UP && enemy_pos.y < player_pos.y ||
- other->direction == ENTITY_DOWN && enemy_pos.y > player_pos.y)
- {
- player_is_facing_enemy = true;
- }
- // Handle Player Attacking Enemy (Press OK, facing enemy, and enemy not facing player)
- // we need to store the last button pressed to prevent multiple attacks
- if (player_is_facing_enemy && last_button == BUTTON_CENTER && !enemy_is_facing_player)
- {
- // Reset last button
- last_button = -1;
- // check if enough time has passed since the last attack
- if (other->elapsed_attack_timer >= other->attack_timer)
- {
- // Reset player's elapsed attack timer
- other->elapsed_attack_timer = 0;
- self->elapsed_attack_timer = 0; // Reset enemy's attack timer to block enemy attack
- // Increase XP by the enemy's strength
- other->xp += self->strength;
- // Increase health by 10% of the enemy's strength
- other->health += self->strength * 0.1;
- // check max health
- if (other->health > 100)
- {
- other->health = 100;
- }
- // Decrease enemy health by player strength
- self->health -= other->strength;
- // check if enemy is dead
- if (self->health > 0)
- {
- self->state = ENTITY_ATTACKED;
- self->elapsed_move_timer = 0;
- self->position_changed = true;
- self->position_set(self->old_position);
- }
- }
- }
- // Handle Enemy Attacking Player (enemy facing player)
- else if (enemy_is_facing_player)
- {
- // check if enough time has passed since the last attack
- if (self->elapsed_attack_timer >= self->attack_timer)
- {
- // Reset enemy's elapsed attack timer
- self->elapsed_attack_timer = 0;
- // Decrease player health by enemy strength
- other->health -= self->strength;
- // check if player is dead
- if (other->health > 0)
- {
- other->state = ENTITY_ATTACKED;
- other->position_set(other->old_position);
- }
- }
- }
- // check if player is dead
- if (other->health <= 0)
- {
- other->state = ENTITY_DEAD;
- other->position = other->start_position;
- other->health = other->max_health;
- other->position_set(other->start_position);
- }
- // check if enemy is dead
- if (self->health <= 0)
- {
- self->state = ENTITY_DEAD;
- self->position = Vector(-100, -100);
- self->health = 0;
- self->elapsed_move_timer = 0;
- self->position_set(self->position);
- }
- }
- }
- static void enemy_spawn(
- Level *level,
- const char *name,
- EntityDirection direction,
- Vector start_position,
- Vector end_position,
- float move_timer,
- float elapsed_move_timer,
- float speed,
- float attack_timer,
- float elapsed_attack_timer,
- float strength,
- float health)
- {
- // Get the enemy context
- PlayerContext enemy_left = player_context_get(name, true);
- PlayerContext enemy_right = player_context_get(name, false);
- // check if enemy context is valid
- if (enemy_left.data != NULL && enemy_right.data != NULL)
- {
- // Create the enemy entity
- 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);
- entity->direction = direction;
- entity->start_position = start_position;
- entity->end_position = end_position;
- entity->move_timer = move_timer;
- entity->elapsed_move_timer = elapsed_move_timer;
- entity->speed = speed;
- entity->attack_timer = attack_timer;
- entity->elapsed_attack_timer = elapsed_attack_timer;
- entity->strength = strength;
- entity->health = health;
- entity->max_health = health;
- // Add the enemy entity to the level
- level->entity_add(entity);
- }
- }
- void enemy_spawn_json(Level *level, const char *json)
- {
- // Parse the json
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, json);
- // Check for errors
- if (error)
- {
- return;
- }
- // Loop through the json data
- int index = 0;
- while (doc["enemy_data"][index])
- {
- // Get the enemy data
- const char *id = doc["enemy_data"][index]["id"];
- Vector start_position = Vector(doc["enemy_data"][index]["start_position"]["x"], doc["enemy_data"][index]["start_position"]["y"]);
- Vector end_position = Vector(doc["enemy_data"][index]["end_position"]["x"], doc["enemy_data"][index]["end_position"]["y"]);
- float move_timer = doc["enemy_data"][index]["move_timer"];
- float speed = doc["enemy_data"][index]["speed"];
- float attack_timer = doc["enemy_data"][index]["attack_timer"];
- float strength = doc["enemy_data"][index]["strength"];
- float health = doc["enemy_data"][index]["health"];
- // Spawn the enemy entity
- enemy_spawn(level, id, ENTITY_LEFT, start_position, end_position, move_timer, 0, speed, attack_timer, 0, strength, health);
- // Increment the index
- index++;
- }
- }
- // Update player stats based on XP using iterative method
- static int get_player_level_iterative(uint32_t xp)
- {
- int level = 1;
- uint32_t xp_required = 100; // Base XP for level 2
- while (level < 100 && xp >= xp_required) // Maximum level supported
- {
- level++;
- xp_required = (uint32_t)(xp_required * 1.5); // 1.5 growth factor per level
- }
- return level;
- }
- static void update_stats(Entity *player)
- {
- // Determine the player's level based on XP
- player->level = get_player_level_iterative(player->xp);
- // Update strength and max health based on the new level
- player->strength = 10 + (player->level * 1); // 1 strength per level
- player->max_health = 100 + ((player->level - 1) * 10); // 10 health per level
- }
- static void player_update(Entity *self, Game *game)
- {
- // Apply health regeneration
- self->elapsed_health_regen += 1.0 / 30; // 30 frames per second
- if (self->elapsed_health_regen >= 1 && self->health < self->max_health)
- {
- self->health += self->health_regen;
- self->elapsed_health_regen = 0;
- if (self->health > self->max_health)
- {
- self->health = self->max_health;
- }
- }
- // Increment the elapsed_attack_timer for the player
- self->elapsed_attack_timer += 1.0 / 30; // 30 frames per second
- // update plyer traits
- update_stats(self);
- Vector oldPos = self->position;
- Vector newPos = oldPos;
- // Move according to input
- if (game->input == BUTTON_UP)
- {
- newPos.y -= 5;
- self->direction = ENTITY_UP;
- last_button = BUTTON_UP;
- }
- else if (game->input == BUTTON_DOWN)
- {
- newPos.y += 5;
- self->direction = ENTITY_DOWN;
- last_button = BUTTON_DOWN;
- }
- else if (game->input == BUTTON_LEFT)
- {
- newPos.x -= 5;
- self->direction = ENTITY_LEFT;
- last_button = BUTTON_LEFT;
- }
- else if (game->input == BUTTON_RIGHT)
- {
- newPos.x += 5;
- self->direction = ENTITY_RIGHT;
- last_button = BUTTON_RIGHT;
- }
- else if (game->input == BUTTON_CENTER)
- {
- last_button = BUTTON_CENTER;
- }
- // reset input
- game->input = -1;
- // Tentatively set new position
- self->position_set(newPos);
- // check if new position is within the level boundaries
- if (newPos.x < 0 || newPos.x + self->size.x > game->current_level->size.x ||
- newPos.y < 0 || newPos.y + self->size.y > game->current_level->size.y)
- {
- // restore old position
- self->position_set(oldPos);
- }
- // Store the current camera position before updating
- game->old_pos = game->pos;
- // Update camera position to center the player
- float camera_x = self->position.x - (game->size.x / 2);
- float camera_y = self->position.y - (game->size.y / 2);
- // Clamp camera position to the world boundaries
- camera_x = constrain(camera_x, 0, game->current_level->size.x - game->size.x);
- camera_y = constrain(camera_y, 0, game->current_level->size.y - game->size.y);
- // Set the new camera position
- game->pos = Vector(camera_x, camera_y);
- // update player sprite based on direction
- if (self->direction == ENTITY_LEFT)
- {
- self->sprite = self->sprite_left;
- }
- else if (self->direction == ENTITY_RIGHT)
- {
- self->sprite = self->sprite_right;
- }
- }
- // Draw the user stats (health, xp, and level)
- static void draw_user_stats(Entity *self, Vector pos, Game *game)
- {
- // first draw a white rectangle to make the text more readable
- game->draw->display->fillRect(pos.x - 2, pos.y - 5, 48, 32, TFT_WHITE);
- char health[32];
- char xp[32];
- char level[32];
- snprintf(health, sizeof(health), "HP : %.0f", (double)self->health);
- snprintf(level, sizeof(level), "LVL: %.0f", (double)self->level);
- if (self->xp < 10000)
- snprintf(xp, sizeof(xp), "XP : %.0f", (double)self->xp);
- else
- snprintf(xp, sizeof(xp), "XP : %.0fK", (double)self->xp / 1000);
- // draw items
- game->draw->text(Vector(pos.x, pos.y), health, TFT_RED);
- game->draw->text(Vector(pos.x, pos.y + 9), xp, TFT_RED);
- game->draw->text(Vector(pos.x, pos.y + 18), level, TFT_RED);
- }
- static void player_render(Entity *self, Draw *draw, Game *game)
- {
- draw_username(game, self->position, "Player"); // draw the username at the new position
- draw_user_stats(self, Vector(5, 210), game); // draw the user stats at the new position
- }
- void player_spawn(Level *level, const char *name, Vector position)
- {
- // Get the player context
- PlayerContext player_left = player_context_get(name, true);
- PlayerContext player_right = player_context_get(name, false);
- // check if player context is valid
- if (player_left.data != NULL && player_right.data != NULL)
- {
- // Create the player entity
- 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);
- player->level = 1;
- player->health = 100;
- player->max_health = 100;
- player->strength = 10;
- player->attack_timer = 1;
- player->health_regen = 1;
- level->entity_add(player);
- }
- }
|