mapcreator git · master
SDL3 2.5D game and engine using assets, with map editor
C++ 99.3%git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gzsrc/character/enemy.cpp raw
#include "character/enemy.hpp"
#include <stdlib.h>
#include <set>
#include "map/map.hpp"
#include "map/path_finding.hpp"
std::vector<Enemy*> Enemy::enemies;
std::vector<SpawnParams> Enemy::enemies_to_spawn;
SDL_FPoint Enemy::colider_default = {7, 7};
EnemyAnimation Enemy::animation_all;
size_t Enemy::tile_size = 64;
// Persistent thread pool
std::vector<SDL_Thread*> Enemy::workers;
std::vector<WorkerData> Enemy::job_queue;
std::mutex Enemy::queue_mutex;
std::condition_variable Enemy::queue_cv;
bool Enemy::stop_threads = false;
int Enemy::jobs_remaining = 0;
std::condition_variable Enemy::jobs_done_cv;
int Enemy::id_next = 1;
Enemy::Enemy(WeaponType weapon_type, WeaponMaterial weapon_material, SDL_FPoint position, EnemyStats status)
: weapon(weapon_type, weapon_material) {
x = position.x;
y = position.y;
stats = status;
id = id_next++;
detection_radius = 150 + rand() % 51;
speed = 40.0f;
run = 1.4f;
direction = WalkDirection::DOWN;
moving = false;
flip = false;
collider.w = colider_default.x;
collider.h = colider_default.y;
collider.x = (tile_size/2 - collider.w/2);
collider.y = tile_size - collider.h - 16;
collider_world.x = x + collider.x + collider.w * 0.5f;
collider_world.y = y + collider.y + collider.h * 0.5f;
arm_reach_radius = 6;
resources = {};
point_in_reach = false;
dist_min = weapon.dimension.x-8;
dist_max = weapon.dimension.x;
max_death_time = 10.0;
stats = status;
death_duration = 0;
death_duration_max = 0.8;
direction_blocked[WalkDirection::DOWN] = false;
direction_blocked[WalkDirection::UP] = false;
direction_blocked[WalkDirection::LEFT] = false;
direction_blocked[WalkDirection::RIGHT] = false;
animation = animation_all;
}
void
Enemy::init_All(SDL_Renderer* renderer) {
animation_all.run[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Run/Run_up.png", tile_size, tile_size, "None", false, false);
animation_all.run[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Run/Run.png", tile_size, tile_size, "None", false, false);
animation_all.run[WalkDirection::UP] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Run/Run_up.png", tile_size, tile_size, "None", false, false);
animation_all.idle[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "None", false, false);
animation_all.idle[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "None", false, false);
animation_all.idle[WalkDirection::UP] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "None", false, false);
animation_all.attack[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Attack/Attack_Down.png", tile_size, tile_size, "enemy.attack", false, true);
animation_all.attack[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Attack/Attack.png", tile_size, tile_size, "enemy.attack", false, true);
animation_all.attack[WalkDirection::UP] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Attack/Attack_Up.png", tile_size, tile_size, "enemy.attack", false, true);
animation_all.hit[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "character.hit", false, false);
animation_all.hit[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "character.hit", false, false);
animation_all.hit[WalkDirection::UP] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "character.hit", false, false);
animation_all.death[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Death/Death-Sheet_processed.png", tile_size, tile_size, "None", false, false);
animation_all.death[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Death/Death-Sheet_processed.png", tile_size, tile_size, "None", false, false);
animation_all.death[WalkDirection::UP] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Death/Death-Sheet_processed.png", tile_size, tile_size, "None", false, false);
init_Workers(10);
}
void
Enemy::Init() {
handle_Animation();
set_Status(EnemyStatus::IDLE);
// REINIT POSITION
SDL_FPoint new_position = Map::get_ClosestPointWithoutCollision(
{static_cast<float>(collider_world.x), static_cast<float>(collider_world.y),
collider.w, collider.h});
if (new_position.x == 0 && new_position.y == 0) {
std::cout << "No position found!\n";
return;
}
x = new_position.x - collider.x - collider.w * 0.5;
y = new_position.y - collider.y - collider.h * 0.5;
}
void Enemy::set_Status(const EnemyStatus& s) {
status = s;
active_anim->timer = 0;
active_anim->current_frame = 0;
Animation::animation_frame_time = active_anim->frame_time;
}
void
Enemy::handle_Animation() {
AnimationData* active_anim_ref = nullptr;
WalkDirection dir = direction;
if ( direction == WalkDirection::LEFT ||
direction == WalkDirection::RIGHT)
dir = WalkDirection::SIDE;
switch (status) {
case EnemyStatus::RUN:
active_anim_ref = &animation.run[dir];
break;
case EnemyStatus::ATTACK:
active_anim_ref = &animation.attack[dir];
break;
case EnemyStatus::DYING:
active_anim_ref = &animation.death[dir];
break;
case EnemyStatus::DEAD:
active_anim_ref = &animation.death[dir];
break;
case EnemyStatus::HIT:
active_anim_ref = &animation.hit[dir];
break;
default:
active_anim_ref = &animation.idle[dir];
break;
}
if (status == EnemyStatus::DEAD) {
if (active_anim_ref) {
AnimationData& ad = *active_anim_ref;
// lock animation to last frame
if (!ad.frames.empty()) {
ad.current_frame = ad.frames.size() - 1;
}
// also stop the timer so update() never progresses
ad.timer = 0.0f;
}
active_anim = active_anim_ref;
return; // absolutely do NOT call update() when dead
}
if (active_anim_ref) {
Animation::update(*active_anim_ref, static_cast<float>(Default::delta_time));
active_anim = active_anim_ref;
}
}
void
Enemy::Draw(SDL_Renderer* renderer) {
#ifdef DEBUG_BUILD
// DEBUG COLLIDER
SDL_FRect debug_rect {
Scale((x + collider.x) + Map::offset.x),
Scale((y + collider.y) + Map::offset.y),
Scale(collider.w),
Scale(collider.h)
};
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderRect(renderer, &debug_rect);
// DEBUG CHARACTER RECTANGLE
SDL_SetRenderDrawColor(renderer, 255, 255,255, 255);
SDL_FRect debug_rect_1 {
Scale(x+Map::offset.x),
Scale(y+Map::offset.y),
Scale(tile_size),
Scale(tile_size)
};
SDL_RenderRect(renderer, &debug_rect_1);
// DEBUG Arm Range
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 100);
for (float angle = 0; angle < 2 * M_PI; angle += 0.1f) {
float px = collider_world.x + cosf(angle) * arm_reach_radius + Map::offset.x;
float py = collider_world.y + sinf(angle) * arm_reach_radius + Map::offset.y;
SDL_RenderPoint(renderer, (int)Scale(px), (int)Scale(py));
}
// DEBUG Tiles in reach
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 80);
for (const auto& t : tiles_in_reach) {
SDL_FRect tile_rect = {
Scale(t.x * Tile::tile_size) + Map::offset.x,
Scale(t.y * Tile::tile_size) + Map::offset.y,
Scale(Tile::tile_size),
Scale(Tile::tile_size)
};
SDL_RenderFillRect(renderer, &tile_rect);
}
#endif
// Render Enemy
const SDL_FRect& src = active_anim->frames[active_anim->current_frame];
SDL_FRect dst {
Scale(x + Map::offset.x),
Scale(y + Map::offset.y),
Scale(src.w),
Scale(src.h)
};
/*
if (stats.health > 0)
if (flip) weapon.Draw(renderer, x + Map::offset.x, y + Map::offset.y);
*/
SDL_RenderTextureRotated(renderer, active_anim->texture, &src, &dst,
0.0, nullptr,
flip ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
/*
if (stats.health > 0)
if (!flip) weapon.Draw(renderer, x + Map::offset.x, y + Map::offset.y);
*/
// HEALTH BAR
if (stats.health <= 0) return;
float bar_width = Scale(40.0f);
float bar_height = Scale(4.0f);
// position above the enemy sprite
float bar_x = Scale(x + Map::offset.x + tile_size/2) - bar_width/2;
float bar_y = Scale(y + Map::offset.y) + bar_height*2 - Scale(4.0f);
// background (empty bar)
SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
SDL_FRect bar_bg {
bar_x-1,
bar_y-1,
bar_width+2,
bar_height+2
};
SDL_RenderFillRect(renderer, &bar_bg);
// current health percentage
float ratio = 0.0f;
if (stats.health_max > 0.0f) {
ratio = stats.health / stats.health_max;
if (ratio < 0.0f) ratio = 0.0f;
if (ratio > 1.0f) ratio = 1.0f;
}
// foreground (current health)
SDL_SetRenderDrawColor(renderer, 200, 0, 0, 255);
SDL_FRect bar_fg {
bar_x,
bar_y,
bar_width * ratio,
bar_height
};
SDL_RenderFillRect(renderer, &bar_fg);
}
Enemy*
Enemy::is_InEnemyReach(const SDL_FRect& rect) {
for (Enemy* enemy : Enemy::enemies) {
if (enemy->stats.health <= 0) continue;
SDL_FRect other = {
enemy->x + enemy->collider.x,
enemy->y + enemy->collider.y,
enemy->collider.w,
enemy->collider.h
};
bool overlap =
rect.x < other.x + other.w &&
rect.x + rect.w > other.x &&
rect.y < other.y + other.h &&
rect.y + rect.h > other.y;
if (overlap)
return enemy;
}
return nullptr;
}
/*
static inline double ms_since(Uint64 start) {
static const double freq = static_cast<double>(SDL_GetPerformanceFrequency());
return (SDL_GetPerformanceCounter() - start) * 1000.0 / freq;
}
*/
int EnemyWorkerThread(void*) {
while (true) {
std::unique_lock<std::mutex> lock(Enemy::queue_mutex);
Enemy::queue_cv.wait(lock, [] {
return Enemy::stop_threads || !Enemy::job_queue.empty();
});
if (Enemy::stop_threads && Enemy::job_queue.empty())
return 0;
WorkerData job = Enemy::job_queue.back();
Enemy::job_queue.pop_back();
lock.unlock();
job.enemy->set_NextMoves(*job.grid);
std::lock_guard<std::mutex> finish_lock(Enemy::queue_mutex);
if (--Enemy::jobs_remaining == 0)
Enemy::jobs_done_cv.notify_one();
}
}
void Enemy::init_Workers(int num_threads) {
stop_threads = false;
jobs_remaining = 0;
for (int i = 0; i < num_threads; ++i) {
SDL_Thread* th = SDL_CreateThread(EnemyWorkerThread, "EnemyWorker", nullptr);
workers.push_back(th);
}
}
void Enemy::shutdown_Workers() {
{
std::lock_guard<std::mutex> lock(queue_mutex);
stop_threads = true;
}
queue_cv.notify_all();
for (SDL_Thread* th : workers) {
SDL_WaitThread(th, nullptr);
}
workers.clear();
}
void Enemy::run_GetMovesThreads()
{
std::vector<bool> grid = Map::tile_collission_map;
std::set<int> checks = {0,1,2,3,4,5,6,7,8,12,16,32,64,128,256};
// ---- First pass: mark grid exactly like old version ----
for (Enemy* e : Enemy::enemies) {
if (e->status != EnemyStatus::RUN ||
!checks.count(e->next_moves.size()))
continue;
int tx = static_cast<int>(e->collider_world.x / Tile::tile_collider_size);
int ty = static_cast<int>(e->collider_world.y / Tile::tile_collider_size);
if (tx < 0 || ty < 0 ||
tx >= static_cast<int>(Map::size.x * Tile::tile_collider_size) ||
ty >= static_cast<int>(Map::size.y * Tile::tile_collider_size))
continue;
size_t id = static_cast<size_t>(
ty * (Map::size.x * Tile::tile_collider_size) + tx);
grid[id] = true;
}
auto grid_ptr = std::make_shared<std::vector<bool>>(std::move(grid));
// ---- Wait for previous frame to finish ----
{
std::unique_lock<std::mutex> lock(queue_mutex);
jobs_done_cv.wait(lock, [] { return jobs_remaining == 0; });
}
// ---- Push jobs ----
{
std::lock_guard<std::mutex> lock(queue_mutex);
for (Enemy* e : Enemy::enemies) {
if (e->status != EnemyStatus::RUN)
continue;
if (e->point_in_reach)
continue;
if (e->next_position.x == 0 || e->next_position.y == 0 )
continue;
job_queue.push_back({ e, grid_ptr });
++jobs_remaining;
}
}
queue_cv.notify_all();
// ---- Wait like old SDL_WaitThread loop ----
{
std::unique_lock<std::mutex> lock(queue_mutex);
jobs_done_cv.wait(lock, [] { return jobs_remaining == 0; });
}
}
void Enemy::Decide(Character& player) {
//float d_x = player.collider_world.x - collider_world.x;
//float d_y = player.collider_world.y - collider_world.y;
//size_t dc_x = static_cast<size_t>(abs(d_x));
//size_t dc_y = static_cast<size_t>(abs(d_y));
EnemyMovingType r;
if (is_character_in_range) {
r = EnemyMovingType::CHARACTER;
} else if (move_focus != EnemyMovingType::NONE) {
r = move_focus;
} else {
// pick random type excluding CHARACTER
do {
r = static_cast<EnemyMovingType>(1 + rand() % (static_cast<int>(EnemyMovingType::LAST) - 1));
} while (r == EnemyMovingType::CHARACTER);
}
set_MoveLogic(r, player);
}
inline bool same_corner(const SDL_FPoint& a, const SDL_FPoint& b, float epsilon = 1.0f) {
return std::fabs(a.x - b.x) <= epsilon && std::fabs(a.y - b.y) <= epsilon;
}
inline bool Enemy::character_InRange(SDL_FPoint char_pos) const {
float dx = char_pos.x - collider_world.x;
float dy = char_pos.y - collider_world.y;
float distance_squared = dx * dx + dy * dy;
return distance_squared <= detection_radius * detection_radius;
}
bool
Enemy::set_CollisinnWithEnemy(const SDL_FRect& rect) {
if (avoiding_object)
return false;
for (Enemy* enemy : Enemy::enemies) {
if (enemy == this || enemy->stats.health <= 0) continue;
SDL_FRect other = {
enemy->x + enemy->collider.x,
enemy->y + enemy->collider.y,
enemy->collider.w,
enemy->collider.h
};
bool overlap =
rect.x < other.x + other.w &&
rect.x + rect.w > other.x &&
rect.y < other.y + other.h &&
rect.y + rect.h > other.y;
if (overlap) {
last_object_collision.min_x = rect.x;
last_object_collision.max_x = rect.x+rect.w;
last_object_collision.min_y = rect.y;
last_object_collision.max_y = rect.y+rect.h;
last_object_collision.valid = true;
collidiing_with_enemy = enemy;
return true;
}
}
return false;
}
bool
Enemy::is_CollidingWithEnemy(const SDL_FRect& rect) {
for (Enemy* enemy : Enemy::enemies) {
if (enemy->stats.health <= 0)
continue;
SDL_FRect other = {
enemy->x + enemy->collider.x,
enemy->y + enemy->collider.y,
enemy->collider.w,
enemy->collider.h
};
bool overlap =
rect.x < other.x + other.w &&
rect.x + rect.w > other.x &&
rect.y < other.y + other.h &&
rect.y + rect.h > other.y;
if (overlap) {
return true;
}
}
return false;
}
/*
bool
Enemy::is_CollidingWithObjects(const SDL_FRect& rect) {
const size_t scale = Tile::tile_size / Tile::tile_collider_size;
// convert rectangle to collider coordinates
int start_cx = static_cast<int>(std::floor(rect.x / float(Tile::tile_collider_size)));
int end_cx = static_cast<int>(std::ceil((rect.x + rect.w) / float(Tile::tile_collider_size)));
int start_cy = static_cast<int>(std::floor(rect.y / float(Tile::tile_collider_size)));
int end_cy = static_cast<int>(std::ceil((rect.y + rect.h) / float(Tile::tile_collider_size)));
// loop over tiles overlapped by rectangle in tile units
size_t tile_start_x = static_cast<size_t>(rect.x / Tile::tile_size);
size_t tile_end_x = static_cast<size_t>((rect.x + rect.w) / Tile::tile_size);
size_t tile_start_y = static_cast<size_t>(rect.y / Tile::tile_size);
size_t tile_end_y = static_cast<size_t>((rect.y + rect.h) / Tile::tile_size);
bool collision_found = false;
float min_x = std::numeric_limits<float>::max();
float min_y = std::numeric_limits<float>::max();
float max_x = std::numeric_limits<float>::lowest();
float max_y = std::numeric_limits<float>::lowest();
for (size_t ty = tile_start_y; ty <= tile_end_y && ty < Map::size.y; ++ty) {
for (size_t tx = tile_start_x; tx <= tile_end_x && tx < Map::size.x; ++tx) {
size_t map_index = ty * Map::size.x + tx;
TileMap& tilemap = Map::tile_map[map_index];
for (const TileObject& obj : tilemap.objects) {
Object* object = Object::get_ObjectById(obj.object_id);
std::vector<bool> collision_map = object->get_CollisionMap();
int obj_abs_x = static_cast<int>(tx) - obj.position.x;
int obj_abs_y = static_cast<int>(ty) - obj.position.y;
int obj_cx = obj_abs_x * static_cast<int>(scale);
int obj_cy = obj_abs_y * static_cast<int>(scale);
size_t obj_cw = static_cast<size_t>(object->dimension.x) * scale;
size_t obj_ch = static_cast<size_t>(object->dimension.y) * scale;
for (int cy = obj_cy; cy < obj_cy+static_cast<int>(obj_ch); ++cy) {
for (int cx = obj_cx; cx < obj_cx+static_cast<int>(obj_cw); ++cx) {
int rel_x = cx - obj_cx;
int rel_y = cy - obj_cy;
if (rel_x < 0 || rel_y < 0 || static_cast<size_t>(rel_x) >= obj_cw || static_cast<size_t>(rel_y) >= obj_ch)
continue;
size_t obj_index = static_cast<size_t>(rel_y) * obj_cw + static_cast<size_t>(rel_x);
if (collision_map[obj_index]) {
float cell_x = static_cast<float>(cx) * Tile::tile_collider_size;
float cell_y = static_cast<float>(cy) * Tile::tile_collider_size;
min_x = std::min(min_x, cell_x);
min_y = std::min(min_y, cell_y);
max_x = std::max(max_x, cell_x + static_cast<float>(Tile::tile_collider_size));
max_y = std::max(max_y, cell_y + static_cast<float>(Tile::tile_collider_size));
if (cx >= start_cx && cx < end_cx && cy >= start_cy && cy < end_cy)
collision_found = true;
}
if (cx >= end_cx && cy >= end_cy && !collision_found)
return false;
}
}
}
}
}
if (collision_found) {
last_object_collision.min_x = std::min(min_x, max_x);
last_object_collision.max_x = std::max(min_x, max_x);
last_object_collision.min_y = std::min(min_y, max_y);
last_object_collision.max_y = std::max(min_y, max_y);
last_object_collision.valid = true;
}
return collision_found;
}
*/
void Enemy::compute_MapCollisionInterval(size_t cx, size_t cy, size_t max_x, size_t max_y) {
last_map_collision.valid = false;
const size_t start_index = cy * max_x + cx;
if (!Map::tile_collission_map[start_index])
return;
size_t min_x = cx;
while (min_x > 0 &&
Map::tile_collission_map[cy * max_x + (min_x - 1)])
min_x--;
size_t max_xi = cx;
while (max_xi < max_x - 1 &&
Map::tile_collission_map[cy * max_x + (max_xi + 1)])
max_xi++;
size_t min_y = cy;
while (min_y > 0 &&
Map::tile_collission_map[(min_y - 1) * max_x + cx])
min_y--;
size_t max_yi = cy;
while (max_yi < max_y - 1 &&
Map::tile_collission_map[(max_yi + 1) * max_x + cx])
max_yi++;
last_map_collision.min_x = min_x * Tile::tile_collider_size;
last_map_collision.max_x = (max_xi + 1) * Tile::tile_collider_size;
last_map_collision.min_y = min_y * Tile::tile_collider_size;
last_map_collision.max_y = (max_yi + 1) * Tile::tile_collider_size;
last_map_collision.valid = true;
}
bool
Enemy::is_CollidingWithWorld(const SDL_FRect& rect) {
const size_t max_cells_x = static_cast<size_t>(Map::size.x * (Tile::tile_size / Tile::tile_collider_size));
const size_t max_cells_y = static_cast<size_t>(Map::size.y * (Tile::tile_size / Tile::tile_collider_size));
const float corners[4][2] = {
{ rect.x, rect.y },
{ rect.x + rect.w, rect.y },
{ rect.x, rect.y + rect.h },
{ rect.x + rect.w, rect.y + rect.h }
};
for (int i = 0; i < 4; ++i) {
const size_t cell_x = corners[i][0] / Tile::tile_collider_size;
const size_t cell_y = corners[i][1] / Tile::tile_collider_size;
if (cell_x >= max_cells_x || cell_y >= max_cells_y)
return true;
const size_t index = static_cast<size_t>(cell_y * max_cells_x + cell_x);
if (Map::tile_collission_map[index]) {
const size_t index = cell_y * max_cells_x
+ cell_x;
if (Map::tile_collission_map[index]) {
compute_MapCollisionInterval(cell_x, cell_y, max_cells_x, max_cells_y);
//std::cout << "COL: " << last_map_collision.min_x << " " << last_map_collision.max_x << " " << last_map_collision.min_y << " " << last_map_collision.max_y << std::endl;
return true;
}
}
}
return false;
}
bool
Enemy::is_Colliding(const SDL_FRect& rect) {
/*
const size_t max_cells_x = Map::size.x * (Tile::tile_size / Tile::tile_collider_size);
const size_t max_cells_y = Map::size.y * (Tile::tile_size / Tile::tile_collider_size);
const float corners[4][2] = {
{ rect.x, rect.y },
{ rect.x + rect.w, rect.y },
{ rect.x, rect.y + rect.h },
{ rect.x + rect.w, rect.y + rect.h }
};
for (int i = 0; i < 4; ++i) {
const size_t cell_x = corners[i][0] / Tile::tile_collider_size;
const size_t cell_y = corners[i][1] / Tile::tile_collider_size;
if (cell_x < 0 || cell_y < 0 || cell_x >= max_cells_x || cell_y >= max_cells_y)
return true;
const size_t index = static_cast<size_t>(cell_y) * static_cast<size_t>(max_cells_x)
+ static_cast<size_t>(cell_x);
if (Map::tile_collission_map[index]) {
const size_t index = static_cast<size_t>(cell_y) * static_cast<size_t>(max_cells_x)
+ static_cast<size_t>(cell_x);
if (Map::tile_collission_map[index]) {
compute_MapCollisionInterval(cell_x, cell_y, max_cells_x, max_cells_y);
//std::cout << "COL: " << last_map_collision.min_x << " " << last_map_collision.max_x << " " << last_map_collision.min_y << " " << last_map_collision.max_y << std::endl;
return true;
}
}
}
*/
// if (is_CollidingWithObjects(rect)) return true;
return set_CollisinnWithEnemy(rect);
}
void Enemy::move_Path() {
if (next_moves.empty()) return;
SDL_FPoint move_to_position = next_moves.back();
float cx = x + collider.x + collider.w * 0.5f;
float cy = y + collider.y + collider.h * 0.5f;
float dx = move_to_position.x - cx;
float dy = move_to_position.y - cy;
float dist = std::sqrt(dx*dx + dy*dy);
const float max_dist = 2;
if (dist <= max_dist) {
next_moves.pop_back();
if (next_moves.empty()) {
// rotate to target
float fx = next_position.x - cx;
float fy = next_position.y - cy;
flip = (fx < 0);
if (std::abs(fx) > std::abs(fy)) {
direction = (fx < 0 ? WalkDirection::LEFT : WalkDirection::RIGHT);
weapon.flip = flip;
} else {
direction = (fy < 0 ? WalkDirection::UP : WalkDirection::DOWN);
}
return;
}
return;
}
// normalize step
float step_len = speed * Default::delta_time;
if (dist < step_len) step_len = dist;
dx = dx / dist * step_len;
dy = dy / dist * step_len;
//std::cout << "MOVE_TO_POS: " << move_to_position.x << " " << move_to_position.y << "DX AND DY: " << dx << " " << dy << std::endl;
SDL_FRect test = collider;
bool is_actually_colliding = is_Colliding({collider.x + x, collider.y + y, collider.w, collider.h});
test.x = x + collider.x + dx;
test.y = y + collider.y + dy;
if (!is_Colliding(test) || is_actually_colliding) {
x += dx;
moving_logic.moving_distance.x += std::abs(dx);
y += dy;
moving_logic.moving_distance.y += std::abs(dy);
moving_logic.path_blocked = 0.0;
} else {
float dx = test.x - collider_world.x;
float dy = test.y - collider_world.y;
float len = std::sqrt(dx * dx + dy * dy);
dx /= len;
dy /= len;
bool left = (std::rand() % 2) == 0;
float px = left ? -dy : dy;
float py = left ? dx : -dx;
float distance = 40.0f + static_cast<float>(std::rand()) /
(static_cast<float>(RAND_MAX) / 40.0f);
SDL_FPoint result = {
collider_world.x + px * distance,
collider_world.y + py * distance
};
std::cout << "GETING OUTTTTT\n";
moving_logic.position = Map::get_ClosestPointWithoutCollision({result.x, result.y, collider.w, collider.h});
next_position = moving_logic.position;
moving_logic.moving_distance.x = 0.0f;
moving_logic.moving_distance.y = 0.0f;
point_in_reach = false;
moving_logic.path_blocked = 0.0;
set_Status(EnemyStatus::RUN);
moving_logic.avoid_blockage = true;
moving_logic.move_type = EnemyMovingType::WALK;
handle_Animation();
return;
}
// SET WALK DIRECTION
flip = (dx < 0);
if (fabs(dx) > 0.0f && fabs(dx) > fabs(dy)) {
direction = (dx < 0 ? WalkDirection::LEFT : WalkDirection::RIGHT);
} else if (dy > 0.0f) {
direction = WalkDirection::DOWN;
} else if (dy < .0f) {
direction = WalkDirection::UP;
}
if (status != EnemyStatus::RUN)
set_Status(EnemyStatus::RUN);
// update world positions
collider_world.x = x + collider.x + collider.w * 0.5f;
collider_world.y = y + collider.y + collider.h * 0.5f;
tile_world.x = collider_world.x / Tile::tile_size;
tile_world.y = collider_world.y / Tile::tile_size;
//set_TilesInReach();
}
void
Enemy::remove_Dead() {
if (Enemy::enemies.size() == 0)
return;
for (auto it = Enemy::enemies.begin(); it != Enemy::enemies.end(); ) {
Enemy* e = *it;
if (e->status == EnemyStatus::DEAD && e->death_duration > e->max_death_time) {
delete e;
it = Enemy::enemies.erase(it);
} else {
++it;
}
}
}
void
Enemy::init_SpawnEnemy(double delay_to_spawn_in_sec, WeaponType weapon_type = WeaponType::SWORD,
WeaponMaterial weapon_material = WeaponMaterial::WOOD,
SDL_FPoint position = {100,100},
EnemyStats status = {16,16,2,false,false,0,1.2}) {
double spawn_date = delay_to_spawn_in_sec + Default::get_TimeSinceStartInSec();
Enemy::enemies_to_spawn.push_back({spawn_date,
weapon_type,
weapon_material,
position,
status});
}
bool
Enemy::spawn_Enemy(SpawnParams& params) {
if (params.spawn_date > Default::get_TimeSinceStartInSec())
return false;
Enemy* enemy = new Enemy(params.type, params.material, params.pos, params.stats);
enemy->Init();
Enemy::enemies.push_back(enemy);
return true;
}
void
Enemy::set_NextMoves(const std::vector<bool>& grid) {
// std::cout << "PL: " << player_collider_world.x << " " << player_collider_world.y << std::endl;
// std::cout << "TG: " << target_pos.x << " " << target_pos.y << std::endl;
// std::cout << "DS: " << dist_max-dist_min << std::endl;
next_moves = find_Path({collider_world.x, collider_world.y, collider.w, collider.h}, next_position, dist_max-dist_min, 0, grid);
// std::cout << "NM " << next_moves.size() << " " << collider_world.x << " " << collider_world.y << " " << next_position.x << " " << next_position.y << std::endl;
}
void
Enemy::set_AlignPosition(const SDL_FPoint& pos) {
float dist = dist_min + (dist_max-dist_min)/2;
//std::cout << "DS: " << dist.x << " " << dist.y <<std::endl;
const float px = pos.x;
const float py = pos.y;
float nx = collider_world.x;
float ny = collider_world.y;
float cand[4][2];
cand[0][0] = px + dist; cand[0][1] = py;
cand[1][0] = px - dist; cand[1][1] = py;
cand[2][0] = px; cand[2][1] = py + dist;
cand[3][0] = px; cand[3][1] = py - dist;
// Compute distances from enemy to each candidate
float dist2[4];
for (int i = 0; i < 4; i++) {
const float dx2 = cand[i][0] - collider_world.x;
const float dy2 = cand[i][1] - collider_world.y;
dist2[i] = dx2*dx2 + dy2*dy2;
}
// Find nearest candidate first
int order[4] = {0,1,2,3};
for (int i = 0; i < 4; i++) {
int best = i;
for (int j = i+1; j < 4; j++) {
if (dist2[order[j]] < dist2[order[best]])
best = j;
}
int tmp = order[i];
order[i] = order[best];
order[best] = tmp;
}
for (int k = 0; k < 4; k++) {
const int idx = order[k];
const float tx = cand[idx][0];
const float ty = cand[idx][1];
if (Map::is_FreePosition({tx-2, ty+collider.h-2, collider.w+4, collider.h+4})) {
nx = tx;
ny = ty;
break;
}
}
next_position.x = nx;
next_position.y = ny;
}
void
Enemy::set_MoveLogic(EnemyMovingType& type, Character& player) {
moving_logic = EnemyMovingLogic();
moving_logic.move_type = type;
EnemyStatus sta = EnemyStatus::IDLE;
point_in_reach = true;
moving_logic.avoid_blockage = false;
stuck_moves = 0;
// std::cout << "FUCK " << (int)type << std::endl;
switch (type) {
case EnemyMovingType::NONE:
break;
case EnemyMovingType::OBJECT:
moving_logic.move_type = EnemyMovingType::NONE;
break;
case EnemyMovingType::IDLE:
moving_logic.duration = (180 + (rand() % 200))/100.0f;
sta = EnemyStatus::IDLE;
break;
case EnemyMovingType::WALK: {
SDL_FRect test_pos = collider;
int chance_move_far = rand() % 10;
if (chance_move_far < 3) {
moving_logic.min_distance = 100;
moving_logic.max_distance = 140;
} else {
moving_logic.min_distance = 50;
moving_logic.max_distance = 101;
}
// Retry generating a test position until it's valid
SDL_FPoint test_pos_c = {0,0};
// START CLAMP NEW POSITION TO BOUNDARIES WHICH SUUUUUUCKS!!!
int i = 0;
const int map_max_x = Map::boundaries.x * Tile::tile_size;
const int map_max_y = Map::boundaries.y * Tile::tile_size;
do {
if (i++ > 3) {
moving_logic.move_type = EnemyMovingType::NONE;
return;
}
int dist_x = moving_logic.min_distance +
rand() % (moving_logic.max_distance - moving_logic.min_distance + 1);
int dist_y = moving_logic.min_distance +
rand() % (moving_logic.max_distance - moving_logic.min_distance + 1);
if (rand() % 2) dist_x = -dist_x;
if (rand() % 2) dist_y = -dist_y;
int new_x = static_cast<int>(collider_world.x) + dist_x;
int new_y = static_cast<int>(collider_world.y) + dist_y;
if (new_x < 0 || new_y < 0 ||
new_x >= map_max_x || new_y >= map_max_y)
continue;
test_pos.x = new_x;
test_pos.y = new_y;
test_pos_c = Map::get_ClosestPointWithoutCollision(test_pos);
if (player.is_attackable && test_pos_c.x <= 530 && test_pos_c.y <= 334)
continue;
break;
} while (true);
// END CLAMP NEW POSITION TO BOUNDARIES WHICH SUUUUUUCkS!!!
moving_logic.moving_distance = {0, 0};
// moving_logic.position = Map::get_ClosestPointWithoutCollision(test_pos);
next_position = test_pos_c;
//set_AlignPosition(moving_logic.position);
// std::cout << " MEEE1 " << player.is_attackable << " " << collider_world.x << " " << collider_world.y << std::endl;
// std::cout << " MEEE2 " << player.is_attackable << " " << next_position.x << " " << next_position.y << std::endl;
sta = EnemyStatus::RUN;
point_in_reach = false;
break;
}
case EnemyMovingType::CHARACTER: {
bool is_player_alive = player.health_Get() > 0;
if (!is_player_alive) {
moving_logic.move_type = EnemyMovingType::NONE;
break;
}
moving_logic.object = static_cast<void*>(&player);
moving_logic.position = player.collider_world;
moving_logic.duration = 0.8 + ((double)rand() / RAND_MAX) * (1.8 - 0.8);
set_AlignPosition(moving_logic.position);
sta = EnemyStatus::RUN;
point_in_reach = false;
move_focus = EnemyMovingType::CHARACTER;
break;
}
case EnemyMovingType::LAST:
moving_logic.move_type = EnemyMovingType::NONE;
break;
}
if (status != sta) {
set_Status(sta);
handle_Animation();
}
}
void
Enemy::Update(Character& player) {
point_in_reach = false;
if (player.is_attackable) {
is_character_in_range = character_InRange(player.collider_world);
if (!is_character_in_range && moving_logic.move_type == EnemyMovingType::CHARACTER) {
next_moves.clear();
move_focus = EnemyMovingType::NONE;
moving_logic.move_type = EnemyMovingType::NONE;
attack_Stop();
}
}
// IS DEAD
if (stats.health <= 0) {
if (status == EnemyStatus::DYING ||
status == EnemyStatus::DEAD) {
death();
}
handle_Animation();
return;
}
// std::cout << "MVTY " << (int)moving_logic.move_type << std::endl;
// DECIDE // REDECIDE
//std::cout << "WLK " << moving_logic.moving_distance.x << " >= " << moving_logic.max_distance << " || " << moving_logic.moving_distance.y << " >= " << moving_logic.max_distance << std::endl;
if ( moving_logic.move_type == EnemyMovingType::NONE || (
moving_logic.move_type == EnemyMovingType::IDLE &&
moving_logic.duration <= 0)) {
Decide(player);
handle_Animation();
return;
} else
if (moving_logic.move_type == EnemyMovingType::IDLE) {
moving_logic.duration -= Default::delta_time;
handle_Animation();
return;
} else
if (moving_logic.move_type == EnemyMovingType::WALK) {
if ( moving_logic.moving_distance.x >= moving_logic.max_distance ||
moving_logic.moving_distance.y >= moving_logic.max_distance ) {
moving_logic.move_type = EnemyMovingType::NONE;
next_moves.clear();
handle_Animation();
return;
}
}
float d_x = moving_logic.position.x - collider_world.x;
float d_y = moving_logic.position.y - collider_world.y;
/*
size_t dc_x = static_cast<size_t>(abs(d_x));
size_t dc_y = static_cast<size_t>(abs(d_y));
bool dist_ok_x = (dc_x >= dist_min && dc_x <= weapon.dimension.x) && (dc_y <= weapon.dimension.y/2);
bool dist_ok_y = (dc_y >= dist_min && dc_y <= weapon.dimension.x) && (dc_x <= weapon.dimension.y/2);
*/
float dist_sq = d_x * d_x + d_y * d_y;
float min_sq = dist_min * dist_min;
float max_sq = dist_max * dist_max;
//std::cout << "DSTQ " << (int)moving_logic.move_type << " " << next_moves.size()<< " " << dist_sq << " " << min_sq << " " << max_sq << std::endl;
bool point_moved = false;
if ( player.collider_world.x != moving_logic.position.x ||
player.collider_world.y != moving_logic.position.y ) {
point_moved = true;
}
if (dist_sq >= min_sq && dist_sq <= max_sq) {
point_in_reach = true;
moving_logic.avoid_blockage = false;
next_moves.clear();
}
if (next_moves.empty()) {
if (moving_logic.move_type == EnemyMovingType::WALK || (move_focus == EnemyMovingType::CHARACTER && point_moved && moving_logic.duration >= 0)) {
float mld = moving_logic.duration;
set_Status(EnemyStatus::IDLE);
moving_logic.move_type = EnemyMovingType::IDLE;
if (moving_logic.move_type == EnemyMovingType::WALK)
moving_logic.duration = (moving_logic.moving_distance.x + moving_logic.moving_distance.y) / 100 + 0.5;
else
moving_logic.duration = mld;
std::cout << "Walk DONE !!!!\n";
return;
}
} else if (next_moves.size() < 2) {
++stuck_moves;
if (stuck_moves > 20) {
// std::cout << "IM STUCK!!\n";
set_Status(EnemyStatus::IDLE);
moving_logic.move_type = EnemyMovingType::IDLE;
next_position = collider_world;
next_moves.clear();
return;
}
}
if (move_focus == EnemyMovingType::CHARACTER) {
moving_logic.duration -= Default::delta_time;
// std::cout << moving_logic.duration << std::endl;
//std::cout << "NEXT MOVES: " << next_moves.size() << std::endl;
if (!moving_logic.avoid_blockage && ((point_moved && moving_logic.duration <= 0) || moving_logic.move_type != EnemyMovingType::CHARACTER)) {
set_MoveLogic(moving_logic.move_type, player);
return;
}
}
if (moving_logic.move_type == EnemyMovingType::CHARACTER) {
if (stats.is_attacking) {
Attack(point_in_reach);
handle_Animation();
return;
}
}
if ( !point_in_reach ) {
move_Path();
handle_Animation();
return;
}
next_moves.clear();
// SET WALK DIRECTION
flip = (d_x < 0);
if (fabs(d_x) > 0.0f && fabs(d_x) > fabs(d_y)) {
direction = (d_x < 0 ? WalkDirection::LEFT : WalkDirection::RIGHT);
} else if (d_y > 0.0f) {
direction = WalkDirection::DOWN;
} else if (d_y < .0f) {
direction = WalkDirection::UP;
}
switch (moving_logic.move_type) {
case EnemyMovingType::CHARACTER:
attack_Start();
break;
case EnemyMovingType::WALK:
moving_logic.move_type = EnemyMovingType::IDLE;
set_MoveLogic(moving_logic.move_type, player);
break;
default: break;
}
handle_Animation();
}
void
Enemy::attack_Start() {
set_Status(EnemyStatus::ATTACK);
handle_Animation();
Animation::animation_frame_time = stats.attack_duration_max / active_anim->frames.size();
stats.attack_duration = 0;
stats.is_attacking = true;
stats.has_attacked = false;
}
void
Enemy::Attack( bool in_reach) {
// i should fic the frame_time to attack_duration
// so the animation finishes with the attack end.
stats.attack_duration += Default::delta_time;
if (stats.attack_duration < stats.attack_duration_max/2)
return;
if (in_reach && !stats.has_attacked) {
#ifdef DEBUG_BUILD
std::cout << "Enemy: Attack character!\n";
#endif
stats.has_attacked = true;
if (moving_logic.move_type == EnemyMovingType::CHARACTER)
static_cast<Character*>(moving_logic.object)->getting_Hit(stats.demage);
//player.getting_Hit(stats.demage);
}
attack_Stop();
}
void
Enemy::attack_Stop() {
if (stats.attack_duration < stats.attack_duration_max)
return;
set_Status(EnemyStatus::IDLE);
stats.attack_duration = 0;
stats.is_attacking = false;
stats.has_attacked = false;
if (moving_logic.move_type == EnemyMovingType::CHARACTER) {
if (static_cast<Character*>(moving_logic.object)->health_Get() <= 0)
moving_logic.move_type = EnemyMovingType::NONE;
}
}
void
Enemy::getting_Hit(float demage) {
// if (status != EnemyStatus::HIT) set_Status(EnemyStatus::HIT);
std::cout << "ENEM " << stats.health << " " << demage << std::endl;
stats.health -= demage;
#ifdef DEBUG_BUILD
std::cout << "Enemy: Getting hit! " << stats.health << std::endl;
#endif
if (stats.health <= 0) {
attack_Stop();
stats.health = 0;
death();
}
}
void
Enemy::death() {
death_duration += Default::delta_time;
if (death_duration > death_duration_max) {
if (status != EnemyStatus::DEAD)
set_Status(EnemyStatus::DEAD);
} else {
if (status != EnemyStatus::DYING)
set_Status(EnemyStatus::DYING);
}
}
void
Enemy::set_TilesInReach() {
tiles_in_reach.clear();
float reach = arm_reach_radius;
float reach_sq = reach * reach;
// bounding box of tiles to check
int min_tx = static_cast<int>((collider_world.x - reach) / Tile::tile_size);
int max_tx = static_cast<int>((collider_world.x + reach) / Tile::tile_size);
int min_ty = static_cast<int>((collider_world.y - reach) / Tile::tile_size);
int max_ty = static_cast<int>((collider_world.y + reach) / Tile::tile_size);
// clamp to map bounds
if (min_tx < 0) min_tx = 0;
if (min_ty < 0) min_ty = 0;
if (max_tx >= static_cast<int>(Map::size.x)) max_tx = Map::size.x - 1;
if (max_ty >= static_cast<int>(Map::size.y)) max_ty = Map::size.y - 1;
for (int ty = min_ty; ty <= max_ty; ++ty) {
for (int tx = min_tx; tx <= max_tx; ++tx) {
// tile rectangle
float tile_left = tx * static_cast<int>(Tile::tile_size);
float tile_top = ty * static_cast<int>(Tile::tile_size);
float tile_right = tile_left + Tile::tile_size;
float tile_bottom = tile_top + Tile::tile_size;
// closest point on tile to character center
float closest_x = std::max(tile_left, std::min(collider_world.x, tile_right));
float closest_y = std::max(tile_top, std::min(collider_world.y, tile_bottom));
float dx = closest_x - collider_world.x;
float dy = closest_y - collider_world.y;
if (dx*dx + dy*dy <= reach_sq)
tiles_in_reach.push_back({tx, ty});
}
}
std::sort(tiles_in_reach.begin(), tiles_in_reach.end(),
[this](const SDL_Point& a, const SDL_Point& b) {
float ax = a.x * Tile::tile_size + Tile::tile_size * 0.5f;
float ay = a.y * Tile::tile_size + Tile::tile_size * 0.5f;
float bx = b.x * Tile::tile_size + Tile::tile_size * 0.5f;
float by = b.y * Tile::tile_size + Tile::tile_size * 0.5f;
float da = (ax - collider_world.x) * (ax - collider_world.x)
+ (ay - collider_world.y) * (ay - collider_world.y);
float db = (bx - collider_world.x) * (bx - collider_world.x)
+ (by - collider_world.y) * (by - collider_world.y);
return da < db;
});
}
static void WriteStats(std::ofstream &file, const EnemyStats &s) {
file.write(reinterpret_cast<const char*>(&s.health), sizeof(s.health));
file.write(reinterpret_cast<const char*>(&s.health_max), sizeof(s.health_max));
file.write(reinterpret_cast<const char*>(&s.demage), sizeof(s.demage));
file.write(reinterpret_cast<const char*>(&s.is_attacking), sizeof(s.is_attacking));
file.write(reinterpret_cast<const char*>(&s.has_attacked), sizeof(s.has_attacked));
file.write(reinterpret_cast<const char*>(&s.attack_duration), sizeof(s.attack_duration));
file.write(reinterpret_cast<const char*>(&s.attack_duration_max), sizeof(s.attack_duration_max));
}
static void ReadStats(std::ifstream &file, EnemyStats &s) {
file.read(reinterpret_cast<char*>(&s.health), sizeof(s.health));
file.read(reinterpret_cast<char*>(&s.health_max), sizeof(s.health_max));
file.read(reinterpret_cast<char*>(&s.demage), sizeof(s.demage));
file.read(reinterpret_cast<char*>(&s.is_attacking), sizeof(s.is_attacking));
file.read(reinterpret_cast<char*>(&s.has_attacked), sizeof(s.has_attacked));
file.read(reinterpret_cast<char*>(&s.attack_duration), sizeof(s.attack_duration));
file.read(reinterpret_cast<char*>(&s.attack_duration_max), sizeof(s.attack_duration_max));
}
bool
Enemy::save_All(const std::string &path) {
std::ofstream file(path, std::ios::binary | std::ios::trunc);
if (!file)
return false;
uint32_t count_total = static_cast<uint32_t>(enemies.size() + enemies_to_spawn.size());
file.write(reinterpret_cast<const char*>(&count_total), sizeof(count_total));
for (auto *e : enemies) {
SpawnParams sp;
sp.spawn_date = 0.0;
sp.type = e->weapon.type;
sp.material = e->weapon.material;
sp.pos = { e->x, e->y };
sp.stats = e->stats;
file.write(reinterpret_cast<const char*>(&sp.spawn_date), sizeof(sp.spawn_date));
file.write(reinterpret_cast<const char*>(&sp.type), sizeof(sp.type));
file.write(reinterpret_cast<const char*>(&sp.material), sizeof(sp.material));
file.write(reinterpret_cast<const char*>(&sp.pos.x), sizeof(sp.pos.x));
file.write(reinterpret_cast<const char*>(&sp.pos.y), sizeof(sp.pos.y));
WriteStats(file, sp.stats);
}
// save pending
double current_time = Default::get_TimeSinceStartInSec();
for (const auto &sp : enemies_to_spawn) {
SpawnParams tmp = sp;
tmp.spawn_date -= current_time;
file.write(reinterpret_cast<const char*>(&tmp.spawn_date), sizeof(tmp.spawn_date));
file.write(reinterpret_cast<const char*>(&tmp.type), sizeof(tmp.type));
file.write(reinterpret_cast<const char*>(&tmp.material), sizeof(tmp.material));
file.write(reinterpret_cast<const char*>(&tmp.pos.x), sizeof(tmp.pos.x));
file.write(reinterpret_cast<const char*>(&tmp.pos.y), sizeof(tmp.pos.y));
WriteStats(file, tmp.stats);
}
return file.good();
}
bool Enemy::load_All(const std::string &path) {
std::ifstream file(path, std::ios::binary);
if (!file)
return false;
for (auto *e : enemies) delete e;
enemies.clear();
enemies_to_spawn.clear();
uint32_t count_total = 0;
file.read(reinterpret_cast<char*>(&count_total), sizeof(count_total));
for (uint32_t i = 0; i < count_total; ++i) {
SpawnParams sp;
file.read(reinterpret_cast<char*>(&sp.spawn_date), sizeof(sp.spawn_date));
file.read(reinterpret_cast<char*>(&sp.type), sizeof(sp.type));
file.read(reinterpret_cast<char*>(&sp.material), sizeof(sp.material));
file.read(reinterpret_cast<char*>(&sp.pos.x), sizeof(sp.pos.x));
file.read(reinterpret_cast<char*>(&sp.pos.y), sizeof(sp.pos.y));
ReadStats(file, sp.stats);
double delay = sp.spawn_date;
init_SpawnEnemy(delay, sp.type, sp.material, sp.pos, sp.stats);
}
return file.good();
}