| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956 |
- // enemy.c
- #include <game/enemy.h>
- #include <notification/notification_messages.h>
- #include <flip_storage/storage.h>
- #include <game/storage.h>
- static EntityContext *enemy_context_generic;
- // Allocation function
- static EntityContext *enemy_generic_alloc(
- SpriteID id,
- int index,
- Vector size,
- Vector start_position,
- Vector end_position,
- float move_timer, // Wait duration before moving again
- float speed,
- float attack_timer,
- float strength,
- float health,
- bool is_user,
- FuriString *username)
- {
- if (!enemy_context_generic)
- {
- enemy_context_generic = malloc(sizeof(EntityContext));
- }
- if (!enemy_context_generic)
- {
- FURI_LOG_E("Game", "Failed to allocate EntityContext");
- return NULL;
- }
- enemy_context_generic->id = id;
- enemy_context_generic->index = index;
- enemy_context_generic->size = size;
- enemy_context_generic->start_position = start_position;
- enemy_context_generic->end_position = end_position;
- enemy_context_generic->move_timer = move_timer; // Set wait duration
- enemy_context_generic->elapsed_move_timer = 0.0f; // Initialize elapsed timer
- enemy_context_generic->speed = speed;
- enemy_context_generic->attack_timer = attack_timer;
- enemy_context_generic->strength = strength;
- enemy_context_generic->health = health;
- // Initialize other fields as needed
- enemy_context_generic->sprite_right = NULL; // sprite is assigned later
- enemy_context_generic->sprite_left = NULL; // sprite is assigned later
- enemy_context_generic->direction = ENTITY_RIGHT; // Default direction
- enemy_context_generic->state = ENTITY_MOVING_TO_END; // Start in IDLE state
- // Set radius based on size, for example, average of size.x and size.y divided by 2
- enemy_context_generic->radius = (size.x + size.y) / 4.0f;
- //
- enemy_context_generic->is_user = is_user;
- //
- if (username != NULL)
- {
- snprintf(enemy_context_generic->username, sizeof(enemy_context_generic->username), "%s", furi_string_get_cstr(username));
- }
- else
- {
- snprintf(enemy_context_generic->username, sizeof(enemy_context_generic->username), "SYSTEM_ENEMY");
- }
- return enemy_context_generic;
- }
- // Enemy start function
- static void enemy_start(Entity *self, GameManager *manager, void *context)
- {
- UNUSED(manager);
- if (!self || !context)
- {
- FURI_LOG_E("Game", "Enemy start: Invalid parameters");
- return;
- }
- if (!enemy_context_generic)
- {
- FURI_LOG_E("Game", "Enemy start: Enemy context not set");
- return;
- }
- EntityContext *enemy_context = (EntityContext *)context;
- // Copy fields from generic context
- enemy_context->id = enemy_context_generic->id;
- enemy_context->index = enemy_context_generic->index;
- enemy_context->size = enemy_context_generic->size;
- enemy_context->start_position = enemy_context_generic->start_position;
- enemy_context->end_position = enemy_context_generic->end_position;
- enemy_context->move_timer = enemy_context_generic->move_timer;
- enemy_context->elapsed_move_timer = enemy_context_generic->elapsed_move_timer;
- enemy_context->speed = enemy_context_generic->speed;
- enemy_context->attack_timer = enemy_context_generic->attack_timer;
- enemy_context->strength = enemy_context_generic->strength;
- enemy_context->health = enemy_context_generic->health;
- enemy_context->sprite_right = enemy_context_generic->sprite_right;
- enemy_context->sprite_left = enemy_context_generic->sprite_left;
- enemy_context->direction = enemy_context_generic->direction;
- enemy_context->state = enemy_context_generic->state;
- enemy_context->radius = enemy_context_generic->radius;
- enemy_context->is_user = enemy_context_generic->is_user;
- snprintf(enemy_context->username, sizeof(enemy_context->username), "%s", enemy_context_generic->username);
- // Set enemy's initial position based on start_position
- entity_pos_set(self, enemy_context->start_position);
- // Add collision circle based on the enemy's radius
- entity_collider_add_circle(self, enemy_context->radius);
- }
- // Enemy render function
- static void enemy_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
- {
- if (!self || !context || !canvas || !manager)
- return;
- EntityContext *enemy_context = (EntityContext *)context;
- GameContext *game_context = game_manager_game_context_get(manager);
- // Get the position of the enemy
- Vector pos = entity_pos_get(self);
- // Get the camera position
- int x_pos = pos.x - draw_camera_x - enemy_context->size.x / 2;
- int y_pos = pos.y - draw_camera_y - enemy_context->size.y / 2;
- // check if position is within the screen
- if (x_pos + enemy_context->size.x < 0 || x_pos > SCREEN_WIDTH || y_pos + enemy_context->size.y < 0 || y_pos > SCREEN_HEIGHT)
- return;
- // Choose sprite based on direction
- Sprite *current_sprite = NULL;
- if (enemy_context->direction == ENTITY_LEFT)
- {
- current_sprite = enemy_context->sprite_left;
- }
- else
- {
- current_sprite = enemy_context->sprite_right;
- }
- // no enemies in story mode for now
- if (game_context->game_mode != GAME_MODE_STORY || (game_context->story_step == 4 || game_context->story_step >= STORY_TUTORIAL_STEPS))
- {
- // Draw enemy sprite relative to camera, centered on the enemy's position
- canvas_draw_sprite(
- canvas,
- current_sprite,
- pos.x - draw_camera_x - (enemy_context->size.x / 2),
- pos.y - draw_camera_y - (enemy_context->size.y / 2));
- // draw health of enemy
- char health_str[32];
- snprintf(health_str, sizeof(health_str), "%.0f", (double)enemy_context->health);
- draw_username(canvas, pos, health_str);
- }
- }
- static void enemy_atk_notify(GameContext *game_context, EntityContext *enemy_context, bool player_attacked)
- {
- if (!game_context || !enemy_context)
- {
- FURI_LOG_E("Game", "Send attack notification: Invalid parameters");
- return;
- }
- NotificationApp *notifications = furi_record_open(RECORD_NOTIFICATION);
- const bool vibration_allowed = strstr(yes_or_no_choices[vibration_on_index], "Yes") != NULL;
- const bool sound_allowed = strstr(yes_or_no_choices[sound_on_index], "Yes") != NULL;
- if (player_attacked)
- {
- if (vibration_allowed && sound_allowed)
- {
- notification_message(notifications, &sequence_success);
- }
- else if (vibration_allowed && !sound_allowed)
- {
- notification_message(notifications, &sequence_single_vibro);
- }
- else if (!vibration_allowed && sound_allowed)
- {
- // change this to sound later
- notification_message(notifications, &sequence_blink_blue_100);
- }
- else
- {
- notification_message(notifications, &sequence_blink_blue_100);
- }
- FURI_LOG_I("Game", "Player attacked enemy '%d'!", enemy_context->id);
- }
- else
- {
- if (vibration_allowed && sound_allowed)
- {
- notification_message(notifications, &sequence_error);
- }
- else if (vibration_allowed && !sound_allowed)
- {
- notification_message(notifications, &sequence_single_vibro);
- }
- else if (!vibration_allowed && sound_allowed)
- {
- // change this to sound later
- notification_message(notifications, &sequence_blink_red_100);
- }
- else
- {
- notification_message(notifications, &sequence_blink_red_100);
- }
- FURI_LOG_I("Game", "Enemy '%d' attacked the player!", enemy_context->id);
- }
- // close the notifications
- furi_record_close(RECORD_NOTIFICATION);
- }
- // Enemy collision function
- static void enemy_collision(Entity *self, Entity *other, GameManager *manager, void *context)
- {
- if (!self || !other || !context || !manager)
- {
- FURI_LOG_E("Game", "Enemy collision: Invalid parameters");
- return;
- }
- EntityContext *enemy_context = (EntityContext *)context;
- furi_check(enemy_context, "Enemy collision: EntityContext is NULL");
- GameContext *game_context = game_manager_game_context_get(manager);
- furi_check(game_context, "Enemy collision: GameContext is NULL");
- PlayerContext *player_context = entity_context_get(game_context->player);
- if (game_context->game_mode == GAME_MODE_STORY && game_context->story_step != 4 && game_context->story_step < STORY_TUTORIAL_STEPS)
- {
- // FURI_LOG_I("Game", "Enemy collision: No enemies in story mode");
- return;
- }
- // Check if the enemy collided with the player
- if (entity_description_get(other) == &player_desc)
- {
- // Get positions of the enemy and the player
- Vector enemy_pos = entity_pos_get(self);
- Vector player_pos = entity_pos_get(other);
- // 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;
- // Determine if the enemy is facing the player
- if ((enemy_context->direction == ENTITY_LEFT && player_pos.x < enemy_pos.x) ||
- (enemy_context->direction == ENTITY_RIGHT && player_pos.x > enemy_pos.x) ||
- (enemy_context->direction == ENTITY_UP && player_pos.y < enemy_pos.y) ||
- (enemy_context->direction == ENTITY_DOWN && player_pos.y > enemy_pos.y))
- {
- enemy_is_facing_player = true;
- }
- // Determine if the player is facing the enemy
- if ((player_context->direction == ENTITY_LEFT && enemy_pos.x < player_pos.x) ||
- (player_context->direction == ENTITY_RIGHT && enemy_pos.x > player_pos.x) ||
- (player_context->direction == ENTITY_UP && enemy_pos.y < player_pos.y) ||
- (player_context->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)
- if (player_is_facing_enemy && game_context->last_button == GameKeyOk && !enemy_is_facing_player)
- {
- if (game_context->game_mode == GAME_MODE_STORY && game_context->story_step == 4)
- {
- // FURI_LOG_I("Game", "Player attacked enemy '%d'!", enemy_context->id);
- game_context->story_step++;
- }
- // Reset last button
- game_context->last_button = -1;
- if (player_context->elapsed_attack_timer >= player_context->attack_timer)
- {
- enemy_atk_notify(game_context, enemy_context, true);
- // Reset player's elapsed attack timer
- player_context->elapsed_attack_timer = 0.0f;
- enemy_context->elapsed_attack_timer = 0.0f; // Reset enemy's attack timer to block enemy attack
- // Increase XP by the enemy's strength
- player_context->xp += enemy_context->strength;
- // Increase healthy by 10% of the enemy's strength
- player_context->health += enemy_context->strength * 0.1f;
- if (player_context->health > player_context->max_health)
- {
- player_context->health = player_context->max_health;
- }
- // Decrease enemy health by player strength
- enemy_context->health -= player_context->strength;
- if (enemy_context->health <= 0)
- {
- enemy_context->state = ENTITY_DEAD;
- // if pvp, end the game
- if (game_context->game_mode == GAME_MODE_PVP)
- {
- player_context->health = player_context->max_health;
- save_player_context(player_context);
- furi_delay_ms(100);
- game_context->end_reason = GAME_END_PVP_ENEMY_DEAD;
- game_manager_game_stop(manager);
- return;
- }
- // Reset enemy position and health
- enemy_context->health = 100; // this needs to be set to the enemy's max health
- // in PVE mode, enemies can respawn
- if (game_context->game_mode != GAME_MODE_PVE)
- {
- // remove from game context and set in safe zone
- game_context->enemies[enemy_context->index] = NULL;
- game_context->enemy_count--;
- entity_collider_remove(self);
- entity_pos_set(self, (Vector){-100, -100});
- return;
- }
- }
- else
- {
- enemy_context->state = ENTITY_ATTACKED;
- // Vector old_pos = entity_pos_get(self);
- // Bounce the enemy back by X units opposite their last movement direction
- enemy_pos.x -= player_context->dx * enemy_context->radius + game_context->icon_offset;
- // enemy_pos.y -= player_context->dy * enemy_context->radius + game_context->icon_offset;
- entity_pos_set(self, enemy_pos);
- // Reset enemy's movement direction to prevent immediate re-collision
- player_context->dx = 0;
- player_context->dy = 0;
- }
- }
- else
- {
- FURI_LOG_I("Game", "Player attack on enemy '%d' is on cooldown: %f seconds remaining", enemy_context->id, (double)(player_context->attack_timer - player_context->elapsed_attack_timer));
- }
- }
- // Handle Enemy Attacking Player (enemy facing player)
- else if (enemy_is_facing_player)
- {
- if (enemy_context->elapsed_attack_timer >= enemy_context->attack_timer)
- {
- enemy_atk_notify(game_context, enemy_context, false);
- // Reset enemy's elapsed attack timer
- enemy_context->elapsed_attack_timer = 0.0f;
- // Decrease player health by enemy strength
- player_context->health -= enemy_context->strength;
- if (player_context->health <= 0)
- {
- FURI_LOG_I("Game", "Player is dead.. resetting player position and health");
- player_context->state = ENTITY_DEAD;
- // if pvp, end the game
- if (game_context->game_mode == GAME_MODE_PVP)
- {
- save_player_context(player_context);
- furi_delay_ms(100);
- game_context->end_reason = GAME_END_PVP_PLAYER_DEAD;
- game_manager_game_stop(manager);
- return;
- }
- // Reset player position and health
- entity_pos_set(other, player_context->start_position);
- player_context->health = player_context->max_health;
- // subtract player's XP by the enemy's strength
- player_context->xp -= enemy_context->strength;
- if ((int)player_context->xp < 0)
- {
- player_context->xp = 0;
- }
- }
- else
- {
- FURI_LOG_I("Game", "Player took %f damage from enemy '%d'", (double)enemy_context->strength, enemy_context->id);
- player_context->state = ENTITY_ATTACKED;
- // Bounce the player back by X units opposite their last movement direction
- player_pos.x -= player_context->dx * enemy_context->radius + game_context->icon_offset;
- // player_pos.y -= player_context->dy * enemy_context->radius + game_context->icon_offset;
- entity_pos_set(other, player_pos);
- // Reset player's movement direction to prevent immediate re-collision
- player_context->dx = 0;
- player_context->dy = 0;
- }
- }
- }
- else // handle other collisions
- {
- // Set the player's old position to prevent collision
- entity_pos_set(other, player_context->old_position);
- // Reset player's movement direction to prevent immediate re-collision
- player_context->dx = 0;
- player_context->dy = 0;
- }
- if (player_context->state == ENTITY_DEAD)
- {
- // Reset player's position and health
- entity_pos_set(other, player_context->start_position);
- player_context->health = player_context->max_health;
- }
- }
- // if not player than must be an icon or npc; so push back
- else
- {
- // push enemy back
- Vector enemy_pos = entity_pos_get(self);
- switch (enemy_context->direction)
- {
- case ENTITY_LEFT:
- enemy_pos.x += (enemy_context->size.x + game_context->icon_offset);
- break;
- case ENTITY_RIGHT:
- enemy_pos.x -= (enemy_context->size.x + game_context->icon_offset);
- break;
- case ENTITY_UP:
- enemy_pos.y += (enemy_context->size.y + game_context->icon_offset);
- break;
- case ENTITY_DOWN:
- enemy_pos.y -= (enemy_context->size.y + game_context->icon_offset);
- break;
- default:
- break;
- }
- entity_pos_set(self, enemy_pos);
- }
- }
- static bool enemy_is_game_enemy(GameManager *manager, const char *username)
- {
- GameContext *game_context = game_manager_game_context_get(manager);
- if (game_context)
- {
- for (int i = 0; i < MAX_ENEMIES; i++)
- {
- if (!game_context->enemies[i])
- break;
- EntityContext *enemy_context = entity_context_get(game_context->enemies[i]);
- if (enemy_context && is_str(enemy_context->username, username))
- {
- return true;
- }
- }
- }
- return false;
- }
- static void enemy_pvp_position(GameManager *manager, EntityContext *enemy, Entity *self)
- {
- if (!manager || !enemy || !self)
- {
- FURI_LOG_E("Game", "enemy_pvp_position: Invalid parameters");
- return;
- }
- GameContext *game_context = game_manager_game_context_get(manager);
- if (!game_context->fhttp->last_response || strlen(game_context->fhttp->last_response) == 0)
- {
- return;
- }
- // parse the response and set the enemy position
- /* expected response:
- {
- "u": "JBlanked",
- "xp": 37743,
- "h": 207,
- "ehr": 0.7,
- "eat": 127.5,
- "d": 2,
- "s": 1,
- "sp": {
- "x": 381.0,
- "y": 192.0
- }
- }
- */
- // match username
- char *u = get_json_value("u", game_context->fhttp->last_response);
- if (!u)
- {
- FURI_LOG_E("Game", "enemy_pvp_position: Failed to get username");
- return;
- }
- // check if the username matches
- if (!is_str(u, enemy->username))
- {
- if (strlen(u) == 0 || enemy_is_game_enemy(manager, u))
- {
- free(u);
- return;
- }
- PlayerContext *player_context = entity_context_get(game_context->player);
- if (is_str(player_context->username, u))
- {
- free(u);
- return;
- }
- char *h = get_json_value("h", game_context->fhttp->last_response);
- if (!h)
- {
- free(u);
- return;
- }
- FuriString *enemy_data = furi_string_alloc();
- furi_string_printf(
- enemy_data,
- "{\"enemy_data\":[{\"id\":\"sword\",\"is_user\":\"true\",\"username\":\"%s\","
- "\"index\":0,\"start_position\":{\"x\":350,\"y\":210},\"end_position\":{\"x\":350,\"y\":210},"
- "\"move_timer\":1,\"speed\":1,\"attack_timer\":\"0.1\",\"strength\":\"100\",\"health\":%f}]}",
- u,
- (double)atoi(h) // h is an int
- );
- free(h); // free health
- enemy_spawn(game_context->levels[game_context->current_level], manager, enemy_data); // add the enemy to the game context
- FURI_LOG_I("Game", "enemy_pvp_position: Added enemy '%s' to the game context", u);
- free(u);
- furi_string_free(enemy_data); // free enemy data
- return;
- }
- free(u); // free username
- // we need the health, elapsed attack timer, direction, xp, and position
- char *h = get_json_value("h", game_context->fhttp->last_response);
- char *eat = get_json_value("eat", game_context->fhttp->last_response);
- char *d = get_json_value("d", game_context->fhttp->last_response);
- char *xp = get_json_value("xp", game_context->fhttp->last_response);
- char *sp = get_json_value("sp", game_context->fhttp->last_response);
- char *x = get_json_value("x", sp);
- char *y = get_json_value("y", sp);
- if (!h || !eat || !d || !sp || !x || !y || !xp)
- {
- if (h)
- free(h);
- if (eat)
- free(eat);
- if (d)
- free(d);
- if (sp)
- free(sp);
- if (x)
- free(x);
- if (y)
- free(y);
- if (xp)
- free(xp);
- return;
- }
- // set enemy info
- enemy->health = (float)atoi(h); // h is an int
- if (enemy->health <= 0)
- {
- enemy->health = 0;
- enemy->state = ENTITY_DEAD;
- entity_pos_set(self, (Vector){-100, -100});
- free(h);
- free(eat);
- free(d);
- free(sp);
- free(x);
- free(y);
- free(xp);
- PlayerContext *player_context = entity_context_get(game_context->player);
- if (player_context)
- {
- save_player_context(player_context);
- furi_delay_ms(100);
- game_manager_game_stop(manager);
- }
- return;
- }
- enemy->elapsed_attack_timer = atof_(eat);
- switch (atoi(d))
- {
- case 0:
- enemy->direction = ENTITY_LEFT;
- break;
- case 1:
- enemy->direction = ENTITY_RIGHT;
- break;
- case 2:
- enemy->direction = ENTITY_UP;
- break;
- case 3:
- enemy->direction = ENTITY_DOWN;
- break;
- default:
- enemy->direction = ENTITY_RIGHT;
- break;
- }
- enemy->xp = (atoi)(xp); // xp is an int
- enemy->level = player_level_iterative_get(enemy->xp);
- Vector new_pos = (Vector){
- .x = atof_(x),
- .y = atof_(y),
- };
- // set enemy position
- entity_pos_set(self, new_pos);
- // free the strings
- free(h);
- free(eat);
- free(d);
- free(sp);
- free(x);
- free(y);
- free(xp);
- }
- // Enemy update function
- static void enemy_update(Entity *self, GameManager *manager, void *context)
- {
- if (!self || !context || !manager)
- return;
- EntityContext *enemy_context = (EntityContext *)context;
- if (!enemy_context || enemy_context->state == ENTITY_DEAD)
- {
- return;
- }
- GameContext *game_context = game_manager_game_context_get(manager);
- if (!game_context)
- {
- FURI_LOG_E("Game", "Enemy update: Failed to get GameContext");
- return;
- }
- const float delta_time = 1.0f / game_context->fps;
- if (game_context->game_mode == GAME_MODE_PVP)
- {
- // update enemy position
- enemy_pvp_position(manager, enemy_context, self);
- }
- else
- {
- // update enemy position for pve mode
- if (game_context->game_mode == GAME_MODE_PVE)
- {
- enemy_pvp_position(manager, enemy_context, self);
- }
- // Increment the elapsed_attack_timer for the enemy
- enemy_context->elapsed_attack_timer += delta_time;
- switch (enemy_context->state)
- {
- case ENTITY_IDLE:
- // Increment the elapsed_move_timer
- enemy_context->elapsed_move_timer += delta_time;
- // Check if it's time to move again
- if (enemy_context->elapsed_move_timer >= enemy_context->move_timer)
- {
- // Determine the next state based on the current position
- Vector current_pos = entity_pos_get(self);
- if (fabs(current_pos.x - enemy_context->start_position.x) < (double)1.0 &&
- fabs(current_pos.y - enemy_context->start_position.y) < (double)1.0)
- {
- enemy_context->state = ENTITY_MOVING_TO_END;
- }
- else
- {
- enemy_context->state = ENTITY_MOVING_TO_START;
- }
- enemy_context->elapsed_move_timer = 0.0f;
- }
- break;
- case ENTITY_MOVING_TO_END:
- case ENTITY_MOVING_TO_START:
- case ENTITY_ATTACKED:
- {
- // Get current position
- Vector current_pos = entity_pos_get(self);
- if (enemy_context->state == ENTITY_ATTACKED)
- {
- // set direction again
- enemy_context->state = enemy_context->direction == ENTITY_LEFT ? ENTITY_MOVING_TO_START : ENTITY_MOVING_TO_END;
- }
- // Determine the target position based on the current state
- Vector target_position = (enemy_context->state == ENTITY_MOVING_TO_END) ? enemy_context->end_position : enemy_context->start_position;
- Vector direction_vector = {0, 0};
- // Calculate direction towards the target
- if (current_pos.x < target_position.x)
- {
- direction_vector.x = 1.0f;
- enemy_context->direction = ENTITY_RIGHT;
- }
- else if (current_pos.x > target_position.x)
- {
- direction_vector.x = -1.0f;
- enemy_context->direction = ENTITY_LEFT;
- }
- if (current_pos.y < target_position.y)
- {
- direction_vector.y = 1.0f;
- enemy_context->direction = ENTITY_DOWN;
- }
- else if (current_pos.y > target_position.y)
- {
- direction_vector.y = -1.0f;
- enemy_context->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 = current_pos;
- new_pos.x += direction_vector.x * enemy_context->speed * delta_time;
- new_pos.y += direction_vector.y * enemy_context->speed * delta_time;
- // Clamp the position to the target to prevent overshooting
- if ((direction_vector.x > 0.0f && new_pos.x > target_position.x) ||
- (direction_vector.x < 0.0f && new_pos.x < target_position.x))
- {
- new_pos.x = target_position.x;
- }
- if ((direction_vector.y > 0.0f && new_pos.y > target_position.y) ||
- (direction_vector.y < 0.0f && new_pos.y < target_position.y))
- {
- new_pos.y = target_position.y;
- }
- // Set the new position
- entity_pos_set(self, new_pos);
- // Check if the enemy has reached or surpassed the target_position
- bool reached_x = fabs(new_pos.x - target_position.x) < (double)1.0;
- bool reached_y = fabs(new_pos.y - target_position.y) < (double)1.0;
- // If reached the target position on both axes, transition to IDLE
- if (reached_x && reached_y)
- {
- enemy_context->state = ENTITY_IDLE;
- enemy_context->elapsed_move_timer = 0.0f;
- }
- }
- break;
- default:
- break;
- }
- }
- }
- // Free function for the entity
- static void enemy_free(Entity *self, GameManager *manager, void *context)
- {
- UNUSED(self);
- UNUSED(manager);
- UNUSED(context);
- if (enemy_context_generic)
- {
- free(enemy_context_generic);
- enemy_context_generic = NULL;
- }
- }
- // Enemy behavior structure
- static const EntityDescription _generic_enemy = {
- .start = enemy_start,
- .stop = enemy_free,
- .update = enemy_update,
- .render = enemy_render,
- .collision = enemy_collision,
- .event = NULL,
- .context_size = sizeof(EntityContext),
- };
- // Enemy function to return the entity description
- static const EntityDescription *enemy(
- GameManager *manager,
- const char *id,
- int index,
- Vector start_position,
- Vector end_position,
- float move_timer, // Wait duration before moving again
- float speed,
- float attack_timer,
- float strength,
- float health,
- bool is_user,
- FuriString *username)
- {
- SpriteContext *sprite_context = sprite_context_get(id);
- if (!sprite_context)
- {
- FURI_LOG_E("Game", "Failed to get SpriteContext");
- return NULL;
- }
- // Allocate a new EntityContext with provided parameters
- enemy_context_generic = enemy_generic_alloc(
- sprite_context->id,
- index,
- (Vector){sprite_context->width, sprite_context->height},
- start_position,
- end_position,
- move_timer, // Set wait duration
- speed,
- attack_timer,
- strength,
- health,
- is_user, username);
- if (!enemy_context_generic)
- {
- FURI_LOG_E("Game", "Failed to allocate EntityContext");
- return NULL;
- }
- // assign sprites to the context
- enemy_context_generic->sprite_right = game_manager_sprite_load(manager, sprite_context->right_file_name);
- enemy_context_generic->sprite_left = game_manager_sprite_load(manager, sprite_context->left_file_name);
- // Set initial direction based on start and end positions
- if (start_position.x < end_position.x)
- {
- enemy_context_generic->direction = ENTITY_RIGHT;
- }
- else
- {
- enemy_context_generic->direction = ENTITY_LEFT;
- }
- // Set initial state based on movement
- if (start_position.x != end_position.x || start_position.y != end_position.y)
- {
- enemy_context_generic->state = ENTITY_MOVING_TO_END;
- }
- else
- {
- enemy_context_generic->state = ENTITY_IDLE;
- }
- free(sprite_context);
- return &_generic_enemy;
- }
- void enemy_spawn(Level *level, GameManager *manager, FuriString *json)
- {
- if (!level || !manager || !json)
- {
- FURI_LOG_E("Game", "Level, GameManager, or JSON is NULL");
- return;
- }
- FuriString *id = get_json_value_furi("id", json);
- FuriString *_index = get_json_value_furi("index", json);
- //
- FuriString *start_position = get_json_value_furi("start_position", json);
- FuriString *start_position_x = get_json_value_furi("x", start_position);
- FuriString *start_position_y = get_json_value_furi("y", start_position);
- //
- FuriString *end_position = get_json_value_furi("end_position", json);
- FuriString *end_position_x = get_json_value_furi("x", end_position);
- FuriString *end_position_y = get_json_value_furi("y", end_position);
- //
- FuriString *move_timer = get_json_value_furi("move_timer", json);
- FuriString *speed = get_json_value_furi("speed", json);
- FuriString *attack_timer = get_json_value_furi("attack_timer", json);
- FuriString *strength = get_json_value_furi("strength", json);
- FuriString *health = get_json_value_furi("health", json);
- //
- if (!id || !_index || !start_position || !start_position_x || !start_position_y || !end_position || !end_position_x || !end_position_y || !move_timer || !speed || !attack_timer || !strength || !health)
- {
- FURI_LOG_E("Game", "Failed to parse JSON values");
- return;
- }
- FuriString *is_user = get_json_value_furi("is_user", json);
- bool is_user_value = false;
- if (is_user)
- {
- is_user_value = strstr(furi_string_get_cstr(is_user), "true") != NULL;
- }
- FuriString *username = get_json_value_furi("username", json);
- // no need to check for username, it is optional
- GameContext *game_context = game_manager_game_context_get(manager);
- if (game_context && game_context->enemy_count < MAX_ENEMIES && !game_context->enemies[game_context->enemy_count])
- {
- game_context->enemies[game_context->enemy_count] = level_add_entity(level, enemy(
- manager,
- furi_string_get_cstr(id),
- atoi(furi_string_get_cstr(_index)),
- (Vector){atof_furi(start_position_x), atof_furi(start_position_y)},
- (Vector){atof_furi(end_position_x), atof_furi(end_position_y)},
- atof_furi(move_timer),
- atof_furi(speed),
- atof_furi(attack_timer),
- atof_furi(strength),
- atof_furi(health),
- is_user_value, username));
- game_context->enemy_count++;
- }
- furi_string_free(id);
- furi_string_free(_index);
- furi_string_free(start_position);
- furi_string_free(start_position_x);
- furi_string_free(start_position_y);
- furi_string_free(end_position);
- furi_string_free(end_position_x);
- furi_string_free(end_position_y);
- furi_string_free(move_timer);
- furi_string_free(speed);
- furi_string_free(attack_timer);
- furi_string_free(strength);
- furi_string_free(health);
- if (is_user)
- {
- furi_string_free(is_user);
- }
- if (username)
- {
- furi_string_free(username);
- }
- }
|