diff --git a/source.zip b/source.zip new file mode 100644 index 0000000..372301c Binary files /dev/null and b/source.zip differ diff --git a/source/A4B1/CMakeLists.txt b/source/A4B1/CMakeLists.txt index b4a439f..837b1d1 100644 --- a/source/A4B1/CMakeLists.txt +++ b/source/A4B1/CMakeLists.txt @@ -1,36 +1,8 @@ -# Disable raylib examples/games and include FetchContent -set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(BUILD_GAMES OFF CACHE BOOL "" FORCE) -include(FetchContent) +find_package(PkgConfig REQUIRED) -# Declare and make raylib available -FetchContent_Declare( - raylib - GIT_REPOSITORY "https://github.com/raysan5/raylib.git" - GIT_TAG "master" - GIT_PROGRESS TRUE +pkg_check_modules(RAYLIB REQUIRED IMPORTED_TARGET raylib) + +add_exe_module( + A4B1 + DEPENDS PkgConfig::RAYLIB ) - -# 2. Descargar Pugixml (Lector de XML/SVG) -FetchContent_Declare( - pugixml - GIT_REPOSITORY "https://github.com/zeux/pugixml.git" - GIT_TAG "v1.14" # Versión estable - GIT_PROGRESS TRUE -) - -# 1. Save the local PLATFORM variable -set(ORIGINAL_PLATFORM "${PLATFORM}") - -# 2. Override the local variable to "Desktop" (This shadows the cache and hides "linux" from raylib) -set(PLATFORM "Desktop") - -# 3. Configure raylib -FetchContent_MakeAvailable(raylib pugixml) - -# 4. Restore your project's local PLATFORM variable back to "linux" -set(PLATFORM "${ORIGINAL_PLATFORM}") - - -add_exe_module(pacman OUTPUT_NAME "Pacman") -target_link_libraries(pacman PRIVATE raylib pugixml) diff --git a/source/A4B1/enemy.cpp b/source/A4B1/enemy.cpp new file mode 100644 index 0000000..4519dfa --- /dev/null +++ b/source/A4B1/enemy.cpp @@ -0,0 +1,415 @@ +#include "raylib.h" +#include + +extern Vector3 playerPosition; +extern bool IsSolidAt(Vector3 position, float radius); + +const int NPC_COUNT = 12; + +Vector3 enemyPosition[NPC_COUNT] = +{ + { -72.0f, 1.0f, -72.0f }, + { 72.0f, 1.0f, -72.0f }, + { -72.0f, 1.0f, 72.0f }, + { 72.0f, 1.0f, 72.0f }, + + { -48.0f, 1.0f, -36.0f }, + { 48.0f, 1.0f, -36.0f }, + { -48.0f, 1.0f, 36.0f }, + { 48.0f, 1.0f, 36.0f }, + + { -28.0f, 1.0f, -68.0f }, + { 28.0f, 1.0f, -68.0f }, + { -28.0f, 1.0f, 68.0f }, + { 28.0f, 1.0f, 68.0f } +}; + +float enemyWalkTime[NPC_COUNT] = {}; +float enemyStunTime[NPC_COUNT] = {}; +float enemySpin[NPC_COUNT] = {}; + +const float enemySpeed = 2.4f; +const float enemyStunDuration = 2.5f; + +bool enemyMoving[NPC_COUNT] = {}; + +void MoveEnemyWithCollision( + int index, + float moveX, + float moveZ +) +{ + Vector3 nextX = enemyPosition[index]; + + nextX.x += moveX; + + if (!IsSolidAt(nextX, 0.45f)) + enemyPosition[index].x = nextX.x; + + Vector3 nextZ = enemyPosition[index]; + + nextZ.z += moveZ; + + if (!IsSolidAt(nextZ, 0.45f)) + enemyPosition[index].z = nextZ.z; +} + +void UpdateEnemy(float deltaTime) +{ + for (int i = 0; i < NPC_COUNT; i++) + { + float dx = + playerPosition.x - + enemyPosition[i].x; + + float dz = + playerPosition.z - + enemyPosition[i].z; + + float distance = + std::sqrt( + dx * dx + + dz * dz + ); + + // ========================================= + // MAREADO + // ========================================= + + if (enemyStunTime[i] > 0.0f) + { + enemyStunTime[i] -= deltaTime; + enemySpin[i] += deltaTime * 18.0f; + + enemyMoving[i] = false; + + if (enemyStunTime[i] < 0.0f) + enemyStunTime[i] = 0.0f; + + continue; + } + + // ========================================= + // EMPUJAR JUGADOR + // ========================================= + + if (distance > 0.001f && + distance < 1.5f) + { + float pushX = + (dx / distance) * + 10.0f * + deltaTime; + + float pushZ = + (dz / distance) * + 10.0f * + deltaTime; + + Vector3 next = playerPosition; + + next.x += pushX; + next.z += pushZ; + + if (!IsSolidAt(next, 0.45f)) + playerPosition = next; + } + + // ========================================= + // SEGUIR JUGADOR + // ========================================= + + if (distance > 1.8f) + { + dx /= distance; + dz /= distance; + + MoveEnemyWithCollision( + i, + dx * enemySpeed * deltaTime, + dz * enemySpeed * deltaTime + ); + + enemyWalkTime[i] += + deltaTime * 9.0f; + + enemyMoving[i] = true; + } + else + { + enemyMoving[i] = false; + enemyWalkTime[i] = 0.0f; + } + } + + // ========================================= + // CLICK DERECHO + // ========================================= + + if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) + { + for (int i = 0; i < NPC_COUNT; i++) + { + float dx = + enemyPosition[i].x - + playerPosition.x; + + float dz = + enemyPosition[i].z - + playerPosition.z; + + float distance = + std::sqrt( + dx * dx + + dz * dz + ); + + if (distance < 4.0f && + distance > 0.001f) + { + dx /= distance; + dz /= distance; + + MoveEnemyWithCollision( + i, + dx * 3.5f, + dz * 3.5f + ); + + enemyStunTime[i] = + enemyStunDuration; + + enemySpin[i] = 0.0f; + enemyWalkTime[i] = 0.0f; + } + } + } +} + +void TeleportEnemyNearPlayer( + int index, + float yaw +) +{ + if (index < 0 || + index >= NPC_COUNT) + return; + + // ========================================= + // CONSERVAR LA DISTANCIA ACTUAL + // ========================================= + + float dx = + enemyPosition[index].x - + playerPosition.x; + + float dz = + enemyPosition[index].z - + playerPosition.z; + + float oldDistance = + std::sqrt( + dx * dx + + dz * dz + ); + + if (oldDistance < 3.0f) + oldDistance = 3.0f; + + // ========================================= + // BUSCAR OTRA DIRECCIÓN + // ========================================= + + float baseAngle = + std::atan2( + dz, + dx + ); + + for (int attempt = 0; attempt < 16; attempt++) + { + float angle = + baseAngle + + PI + + attempt * 0.4f; + + Vector3 newPosition = + { + playerPosition.x + + std::cos(angle) * oldDistance, + + 1.0f, + + playerPosition.z + + std::sin(angle) * oldDistance + }; + + if (!IsSolidAt(newPosition, 0.45f)) + { + enemyPosition[index] = + newPosition; + + enemyStunTime[index] = 0.0f; + enemySpin[index] = 0.0f; + enemyWalkTime[index] = 0.0f; + + return; + } + } +} + +int CheckEnemyHit(Vector3 bulletPosition) +{ + for (int i = 0; i < NPC_COUNT; i++) + { + float hitX = + bulletPosition.x - + enemyPosition[i].x; + + float hitY = + bulletPosition.y - + 1.2f; + + float hitZ = + bulletPosition.z - + enemyPosition[i].z; + + float distance = + std::sqrt( + hitX * hitX + + hitY * hitY + + hitZ * hitZ + ); + + if (distance < 0.8f) + return i; + } + + return -1; +} + +void DrawEnemy() +{ + for (int i = 0; i < NPC_COUNT; i++) + { + float legMove = 0.0f; + float armMove = 0.0f; + + if (enemyMoving[i]) + { + legMove = + std::sin(enemyWalkTime[i]) * + 0.18f; + + armMove = + std::sin(enemyWalkTime[i]) * + 0.25f; + } + + float spinX = 0.0f; + float spinZ = 0.0f; + + if (enemyStunTime[i] > 0.0f) + { + spinX = + std::sin(enemySpin[i]) * + 0.25f; + + spinZ = + std::cos(enemySpin[i]) * + 0.25f; + } + + DrawCube( + { + enemyPosition[i].x - + 0.22f + + spinX, + + 0.45f, + + enemyPosition[i].z + + legMove + + spinZ + }, + 0.25f, + 0.9f, + 0.25f, + DARKGRAY + ); + + DrawCube( + { + enemyPosition[i].x + + 0.22f - + spinX, + + 0.45f, + + enemyPosition[i].z - + legMove - + spinZ + }, + 0.25f, + 0.9f, + 0.25f, + DARKGRAY + ); + + DrawCube( + { + enemyPosition[i].x, + 1.25f, + enemyPosition[i].z + }, + 0.8f, + 1.0f, + 0.45f, + BLUE + ); + + DrawSphere( + { + enemyPosition[i].x + spinX, + 2.05f, + enemyPosition[i].z + spinZ + }, + 0.38f, + BEIGE + ); + + DrawCube( + { + enemyPosition[i].x - + 0.55f + + spinX, + + 1.25f + armMove, + + enemyPosition[i].z + + spinZ + }, + 0.18f, + 0.75f, + 0.18f, + BLUE + ); + + DrawCube( + { + enemyPosition[i].x + + 0.55f - + spinX, + + 1.25f - armMove, + + enemyPosition[i].z - + spinZ + }, + 0.18f, + 0.75f, + 0.18f, + BLUE + ); + } +} diff --git a/source/A4B1/game.cpp b/source/A4B1/game.cpp new file mode 100644 index 0000000..1bfbaf3 --- /dev/null +++ b/source/A4B1/game.cpp @@ -0,0 +1,457 @@ +#include "raylib.h" +#include +#include +#include + +struct Wall +{ + Vector3 position; + Vector3 size; +}; + +struct Furniture +{ + Vector3 position; + Vector3 size; + int type; +}; + +static const int MAZE_W = 41; +static const int MAZE_H = 41; +static const float CELL = 4.0f; + +static bool maze[MAZE_H][MAZE_W]; +static std::vector walls; +static std::vector furniture; + +extern Vector3 playerPosition; +extern Camera3D GetPlayerCamera(); + +extern void UpdatePlayer(float deltaTime); +extern void UpdateEnemy(float deltaTime); +extern void UpdateWeapons(float deltaTime); + +extern void DrawEnemy(); +extern void DrawBullets(); +extern void DrawPlayerWeapon(); + +bool IsSolidAt(Vector3 position, float radius) +{ + for (const Wall& wall : walls) + { + if (position.x >= wall.position.x - wall.size.x / 2.0f - radius && + position.x <= wall.position.x + wall.size.x / 2.0f + radius && + position.z >= wall.position.z - wall.size.z / 2.0f - radius && + position.z <= wall.position.z + wall.size.z / 2.0f + radius) + { + return true; + } + } + + for (const Furniture& object : furniture) + { + if (position.x >= object.position.x - object.size.x / 2.0f - radius && + position.x <= object.position.x + object.size.x / 2.0f + radius && + position.z >= object.position.z - object.size.z / 2.0f - radius && + position.z <= object.position.z + object.size.z / 2.0f + radius) + { + return true; + } + } + + return false; +} + +static Vector3 CellToWorld(int x, int z) +{ + return { + (x - MAZE_W / 2) * CELL, + 0.0f, + (z - MAZE_H / 2) * CELL + }; +} + +static void GenerateMaze() +{ + for (int z = 0; z < MAZE_H; z++) + { + for (int x = 0; x < MAZE_W; x++) + { + maze[z][x] = true; + } + } + + std::vector stack; + stack.push_back({1, 1}); + maze[1][1] = false; + + std::mt19937 rng(12345); + + const int dirs[4][2] = + { + { 2, 0 }, + {-2, 0 }, + { 0, 2 }, + { 0,-2 } + }; + + while (!stack.empty()) + { + Vector2 current = stack.back(); + + std::vector available; + + for (int i = 0; i < 4; i++) + { + int nx = (int)current.x + dirs[i][0]; + int nz = (int)current.y + dirs[i][1]; + + if (nx > 0 && nx < MAZE_W - 1 && + nz > 0 && nz < MAZE_H - 1 && + maze[nz][nx]) + { + available.push_back(i); + } + } + + if (available.empty()) + { + stack.pop_back(); + continue; + } + + int d = available[rng() % available.size()]; + + int nx = (int)current.x + dirs[d][0]; + int nz = (int)current.y + dirs[d][1]; + + maze[((int)current.y + nz) / 2] + [((int)current.x + nx) / 2] = false; + + maze[nz][nx] = false; + + stack.push_back({(float)nx, (float)nz}); + } + + for (int z = 3; z < MAZE_H - 3; z += 6) + { + for (int x = 3; x < MAZE_W - 3; x += 6) + { + maze[z][x] = false; + + if (x + 1 < MAZE_W - 1) + maze[z][x + 1] = false; + + if (x > 1) + maze[z][x - 1] = false; + + if (z + 1 < MAZE_H - 1) + maze[z + 1][x] = false; + + if (z > 1) + maze[z - 1][x] = false; + } + } +} + +static void BuildWalls() +{ + walls.clear(); + + for (int z = 0; z < MAZE_H; z++) + { + for (int x = 0; x < MAZE_W; x++) + { + if (!maze[z][x]) + continue; + + Vector3 p = CellToWorld(x, z); + + walls.push_back({ + {p.x, 1.5f, p.z}, + {CELL, 3.0f, CELL} + }); + } + } +} + +static void GenerateFurniture() +{ + furniture.clear(); + + for (int z = 2; z < MAZE_H - 2; z++) + { + for (int x = 2; x < MAZE_W - 2; x++) + { + if (maze[z][x]) + continue; + + if ((x * 13 + z * 7) % 17 != 0) + continue; + + if (x <= 4 && z <= 4) + continue; + + Vector3 p = CellToWorld(x, z); + + Furniture object; + object.type = (x * 3 + z * 5) % 5; + + switch (object.type) + { + case 0: + object.position = {p.x, 0.8f, p.z}; + object.size = {2.4f, 1.6f, 2.4f}; + break; + + case 1: + object.position = {p.x, 0.75f, p.z}; + object.size = {1.5f, 1.5f, 1.5f}; + break; + + case 2: + object.position = {p.x, 1.35f, p.z}; + object.size = {1.8f, 2.7f, 1.0f}; + break; + + case 3: + object.position = {p.x, 0.4f, p.z}; + object.size = {2.5f, 0.8f, 0.8f}; + break; + + default: + object.position = {p.x, 0.6f, p.z}; + object.size = {2.0f, 1.2f, 1.2f}; + break; + } + + if (!IsSolidAt(object.position, 0.2f)) + furniture.push_back(object); + } + } +} + +static void DrawFloor() +{ + for (int z = 0; z < MAZE_H; z++) + { + for (int x = 0; x < MAZE_W; x++) + { + if (maze[z][x]) + continue; + + Vector3 p = CellToWorld(x, z); + + int shade = 108 + ((x * 17 + z * 11) % 9); + + Color color = { + (unsigned char)shade, + (unsigned char)shade, + (unsigned char)(shade + 1), + 255 + }; + + DrawCube( + {p.x, -0.06f, p.z}, + CELL + 0.16f, + 0.12f, + CELL + 0.16f, + color + ); + } + } +} + +static void DrawWalls() +{ + for (const Wall& wall : walls) + { + DrawCube( + wall.position, + wall.size.x, + wall.size.y, + wall.size.z, + {52, 82, 116, 255} + ); + } +} + +static void DrawCeiling() +{ + for (int z = 0; z < MAZE_H; z++) + { + for (int x = 0; x < MAZE_W; x++) + { + if (maze[z][x]) + continue; + + Vector3 p = CellToWorld(x, z); + + bool lightZone = + ((x * 3 + z * 7) % 11 == 0); + + Color ceilingColor = + lightZone + ? Color{155, 155, 155, 255} + : Color{70, 70, 70, 255}; + + DrawCube( + {p.x, 3.12f, p.z}, + CELL + 0.16f, + 0.16f, + CELL + 0.16f, + ceilingColor + ); + + if (lightZone) + { + DrawCube( + {p.x, 3.01f, p.z}, + 1.3f, + 0.04f, + 0.7f, + RAYWHITE + ); + } + } + } +} + +static void DrawFurniture() +{ + for (const Furniture& object : furniture) + { + Color color; + + switch (object.type) + { + case 0: + color = {75, 75, 75, 255}; + break; + + case 1: + color = {100, 82, 65, 255}; + break; + + case 2: + color = {62, 62, 68, 255}; + break; + + case 3: + color = {84, 70, 55, 255}; + break; + + default: + color = {90, 90, 90, 255}; + break; + } + + DrawCube( + object.position, + object.size.x, + object.size.y, + object.size.z, + color + ); + } +} + +static void DrawHUD() +{ + DrawRectangle( + 0, + 0, + 300, + 70, + {0, 0, 0, 100} + ); + + DrawText( + "NIVEL: TALAN-TALAN", + 15, + 12, + 20, + RAYWHITE + ); + + DrawText( + "WASD MOVER", + 15, + 39, + 15, + LIGHTGRAY + ); + + int centerX = GetScreenWidth() / 2; + int centerY = GetScreenHeight() / 2; + + DrawLine( + centerX - 8, + centerY, + centerX + 8, + centerY, + RAYWHITE + ); + + DrawLine( + centerX, + centerY - 8, + centerX, + centerY + 8, + RAYWHITE + ); +} + +void RunGame() +{ + InitWindow( + 1366, + 768, + "A4B1 - Nivel Talan-Talan" + ); + + SetTargetFPS(60); + DisableCursor(); + + GenerateMaze(); + BuildWalls(); + GenerateFurniture(); + + while (!WindowShouldClose()) + { + float deltaTime = GetFrameTime(); + + if (IsKeyPressed(KEY_F11)) + { + ToggleFullscreen(); + } + + UpdatePlayer(deltaTime); + UpdateEnemy(deltaTime); + UpdateWeapons(deltaTime); + + BeginDrawing(); + + ClearBackground({25, 25, 28, 255}); + + BeginMode3D(GetPlayerCamera()); + + DrawFloor(); + DrawWalls(); + DrawCeiling(); + DrawFurniture(); + + DrawEnemy(); + DrawBullets(); + + EndMode3D(); + + DrawPlayerWeapon(); + DrawHUD(); + + EndDrawing(); + } + + EnableCursor(); + CloseWindow(); +} diff --git a/source/A4B1/main.cpp b/source/A4B1/main.cpp index 01b3886..30828d7 100644 --- a/source/A4B1/main.cpp +++ b/source/A4B1/main.cpp @@ -1,3175 +1,9 @@ #include "raylib.h" -#include -#include -#include -#include -#include - -// ============================================================ -// A4B1 0.0.2 -// ============================================================ - -const int PANTALLA_ANCHO = 1280; -const int PANTALLA_ALTO = 720; - -const float MUNDO_ANCHO = 2800.0f; -const float MUNDO_ALTO = 1900.0f; - -const float BORDE = 45.0f; - -const float RADIO_JUGADOR = 19.0f; -const float RADIO_IA = 20.0f; - -const float VELOCIDAD_JUGADOR = 310.0f; -const float VELOCIDAD_IA = 180.0f; - -const float RADIO_VISION = 470.0f; -const float TIEMPO_RECARGA = 1.15f; - -const int EQUIPO_AZUL = 0; -const int EQUIPO_ROJO = 1; - -// ============================================================ -// ARMAS -// ============================================================ - -enum TipoArma -{ - ARMA_RAPIDA = 0, - ARMA_EQUILIBRADA, - ARMA_PESADA, - ARMA_PRECISA -}; - -struct Arma -{ - TipoArma tipo; - int balas; - int cartuchos; - - bool enSuelo; - bool activa; - - Vector2 posicion; -}; - -struct Bala -{ - Vector2 posicion; - Vector2 direccion; - - float velocidad; - float dano; - - int equipo; - - int paredesAtraviesadas; - int rebotes; - - bool activa; -}; - -struct Botiquin -{ - Vector2 posicion; - bool activa; -}; - -struct Municion -{ - Vector2 posicion; - bool activa; -}; - -struct MarcaMuerte -{ - Vector2 posicion; - float tiempo; - bool activa; -}; - -struct Entidad -{ - Vector2 posicion; - Vector2 velocidad; - Vector2 direccion; - - float radio; - - float vida; - float vidaMaxima; - - int equipo; - - bool jugador; - bool viva; - - int arma; - - bool recargando; - float tiempoRecarga; - - float tiempoDisparo; - float tiempoAtascado; - - Vector2 ultimaPosicion; -}; - -struct Pared -{ - Rectangle rectangulo; -}; - -// ============================================================ -// FUNCIONES BÁSICAS -// ============================================================ - -float Distancia(Vector2 a, Vector2 b) -{ - float dx = a.x - b.x; - float dy = a.y - b.y; - - return std::sqrt(dx * dx + dy * dy); -} - -Vector2 Normalizar(Vector2 v) -{ - float longitud = - std::sqrt(v.x * v.x + v.y * v.y); - - if (longitud <= 0.0001f) - return {0, 0}; - - return { - v.x / longitud, - v.y / longitud - }; -} - -Vector2 Multiplicar(Vector2 v, float valor) -{ - return { - v.x * valor, - v.y * valor - }; -} - -// ============================================================ -// ARMAS -// ============================================================ - -const char* NombreArma(TipoArma tipo) -{ - switch (tipo) - { - case ARMA_RAPIDA: - return "RAPIDA"; - - case ARMA_EQUILIBRADA: - return "EQUILIBRADA"; - - case ARMA_PESADA: - return "PESADA"; - - case ARMA_PRECISA: - return "PRECISA"; - } - - return "???"; -} - -int CapacidadArma(TipoArma tipo) -{ - switch (tipo) - { - case ARMA_RAPIDA: - return 12; - - case ARMA_EQUILIBRADA: - return 8; - - case ARMA_PESADA: - return 4; - - case ARMA_PRECISA: - return 3; - } - - return 8; -} - -float CadenciaArma(TipoArma tipo) -{ - switch (tipo) - { - case ARMA_RAPIDA: - return 0.10f; - - case ARMA_EQUILIBRADA: - return 0.25f; - - case ARMA_PESADA: - return 0.70f; - - case ARMA_PRECISA: - return 1.05f; - } - - return 0.25f; -} - -float DanoArma(TipoArma tipo) -{ - switch (tipo) - { - case ARMA_RAPIDA: - return 10.0f; - - case ARMA_EQUILIBRADA: - return 18.0f; - - case ARMA_PESADA: - return 40.0f; - - case ARMA_PRECISA: - return 70.0f; - } - - return 10.0f; -} - -float VelocidadBala(TipoArma tipo) -{ - switch (tipo) - { - case ARMA_RAPIDA: - return 820.0f; - - case ARMA_EQUILIBRADA: - return 760.0f; - - case ARMA_PESADA: - return 690.0f; - - case ARMA_PRECISA: - return 1050.0f; - } - - return 760.0f; -} - -Color ColorArma(TipoArma tipo) -{ - switch (tipo) - { - case ARMA_RAPIDA: - return YELLOW; - - case ARMA_EQUILIBRADA: - return SKYBLUE; - - case ARMA_PESADA: - return ORANGE; - - case ARMA_PRECISA: - return PURPLE; - } - - return WHITE; -} - -Arma CrearArma( - TipoArma tipo, - Vector2 posicion, - bool enSuelo -) -{ - Arma arma{}; - - arma.tipo = tipo; - arma.balas = CapacidadArma(tipo); - - // Cartuchos infinitos. - arma.cartuchos = 999999; - - arma.enSuelo = enSuelo; - arma.activa = true; - arma.posicion = posicion; - - return arma; -} - -// ============================================================ -// COLISIONES -// ============================================================ - -bool CirculoChocaRectangulo( - Vector2 centro, - float radio, - Rectangle rect -) -{ - float cercanoX = - std::clamp( - centro.x, - rect.x, - rect.x + rect.width - ); - - float cercanoY = - std::clamp( - centro.y, - rect.y, - rect.y + rect.height - ); - - float dx = centro.x - cercanoX; - float dy = centro.y - cercanoY; - - return dx * dx + dy * dy <= radio * radio; -} - -bool EntidadChocaPared( - Vector2 posicion, - float radio, - const std::vector& paredes -) -{ - for (const auto& pared : paredes) - { - if ( - CirculoChocaRectangulo( - posicion, - radio, - pared.rectangulo - ) - ) - { - return true; - } - } - - return false; -} - -bool PuntoDentroRectangulo( - Vector2 punto, - Rectangle rect -) -{ - return CheckCollisionPointRec( - punto, - rect - ); -} - -bool LineaChocaRectangulo( - Vector2 inicio, - Vector2 fin, - Rectangle rect -) -{ - const int pasos = 50; - - for (int i = 0; i <= pasos; i++) - { - float t = - static_cast(i) / - static_cast(pasos); - - Vector2 punto = { - inicio.x + - (fin.x - inicio.x) * t, - - inicio.y + - (fin.y - inicio.y) * t - }; - - if ( - PuntoDentroRectangulo( - punto, - rect - ) - ) - { - return true; - } - } - - return false; -} - -bool HayParedEntre( - Vector2 a, - Vector2 b, - const std::vector& paredes -) -{ - for (const auto& pared : paredes) - { - if ( - LineaChocaRectangulo( - a, - b, - pared.rectangulo - ) - ) - { - return true; - } - } - - return false; -} - -// ============================================================ -// MAPA -// ============================================================ - -std::vector CrearMapa() -{ - std::vector paredes; - - // Bordes - paredes.push_back({ - {0, 0, MUNDO_ANCHO, BORDE} - }); - - paredes.push_back({ - { - 0, - MUNDO_ALTO - BORDE, - MUNDO_ANCHO, - BORDE - } - }); - - paredes.push_back({ - {0, 0, BORDE, MUNDO_ALTO} - }); - - paredes.push_back({ - { - MUNDO_ANCHO - BORDE, - 0, - BORDE, - MUNDO_ALTO - } - }); - - // Izquierda - paredes.push_back({ - {500, 300, 400, 50} - }); - - paredes.push_back({ - {500, 1550, 400, 50} - }); - - paredes.push_back({ - {900, 300, 55, 500} - }); - - paredes.push_back({ - {900, 1100, 55, 500} - }); - - // Derecha - paredes.push_back({ - {1900, 300, 400, 50} - }); - - paredes.push_back({ - {1900, 1550, 400, 50} - }); - - paredes.push_back({ - {1845, 300, 55, 500} - }); - - paredes.push_back({ - {1845, 1100, 55, 500} - }); - - // Centro - paredes.push_back({ - {1220, 650, 360, 55} - }); - - paredes.push_back({ - {1220, 1195, 360, 55} - }); - - // Coberturas - paredes.push_back({ - {650, 700, 180, 45} - }); - - paredes.push_back({ - {650, 1155, 180, 45} - }); - - paredes.push_back({ - {1970, 700, 180, 45} - }); - - paredes.push_back({ - {1970, 1155, 180, 45} - }); - - return paredes; -} - -// ============================================================ -// MOVIMIENTO -// ============================================================ - -void MoverEntidad( - Entidad& entidad, - Vector2 movimiento, - const std::vector& paredes -) -{ - Vector2 nuevaX = entidad.posicion; - nuevaX.x += movimiento.x; - - if ( - !EntidadChocaPared( - nuevaX, - entidad.radio, - paredes - ) - ) - { - entidad.posicion.x = nuevaX.x; - } - - Vector2 nuevaY = entidad.posicion; - nuevaY.y += movimiento.y; - - if ( - !EntidadChocaPared( - nuevaY, - entidad.radio, - paredes - ) - ) - { - entidad.posicion.y = nuevaY.y; - } -} - -// ============================================================ -// SEPARACIÓN DE COMPAÑEROS -// ============================================================ - -Vector2 FuerzaSeparacion( - int indice, - const std::vector& entidades -) -{ - Vector2 fuerza = {0, 0}; - - const Entidad& yo = - entidades[indice]; - - for ( - int i = 0; - i < static_cast(entidades.size()); - i++ - ) - { - if (i == indice) - continue; - - const Entidad& otra = - entidades[i]; - - if (!otra.viva) - continue; - - if (otra.equipo != yo.equipo) - continue; - - float distancia = - Distancia( - yo.posicion, - otra.posicion - ); - - if ( - distancia > 0 && - distancia < 110 - ) - { - Vector2 alejamiento = { - yo.posicion.x - - otra.posicion.x, - - yo.posicion.y - - otra.posicion.y - }; - - alejamiento = - Normalizar( - alejamiento - ); - - float intensidad = - (110.0f - distancia) / - 110.0f; - - fuerza.x += - alejamiento.x * - intensidad; - - fuerza.y += - alejamiento.y * - intensidad; - } - } - - return fuerza; -} - -// ============================================================ -// DIRECCIÓN LIBRE -// ============================================================ - -Vector2 BuscarDireccionLibre( - const Entidad& entidad, - const std::vector& paredes -) -{ - const Vector2 direcciones[] = { - {1, 0}, - {-1, 0}, - {0, 1}, - {0, -1}, - - {0.707f, 0.707f}, - {-0.707f, 0.707f}, - {0.707f, -0.707f}, - {-0.707f, -0.707f} - }; - - for (const Vector2& direccion : direcciones) - { - Vector2 prueba = { - entidad.posicion.x + - direccion.x * 100.0f, - - entidad.posicion.y + - direccion.y * 100.0f - }; - - if ( - !EntidadChocaPared( - prueba, - entidad.radio + 4, - paredes - ) - ) - { - return direccion; - } - } - - return {1, 0}; -} - -// ============================================================ -// ENEMIGO VISIBLE -// ============================================================ - -int BuscarEnemigo( - int indice, - const std::vector& entidades, - const std::vector& paredes -) -{ - const Entidad& yo = - entidades[indice]; - - int mejor = -1; - float mejorDistancia = 999999.0f; - - for ( - int i = 0; - i < static_cast(entidades.size()); - i++ - ) - { - if (i == indice) - continue; - - const Entidad& enemigo = - entidades[i]; - - if (!enemigo.viva) - continue; - - if (enemigo.equipo == yo.equipo) - continue; - - float distancia = - Distancia( - yo.posicion, - enemigo.posicion - ); - - if (distancia > 900) - continue; - - if ( - !HayParedEntre( - yo.posicion, - enemigo.posicion, - paredes - ) - ) - { - if (distancia < mejorDistancia) - { - mejorDistancia = distancia; - mejor = i; - } - } - } - - return mejor; -} - -// ============================================================ -// ARMAS EN EL SUELO -// ============================================================ - -int BuscarArmaCercana( - const Entidad& entidad, - const std::vector& armas -) -{ - int mejor = -1; - float mejorDistancia = 75.0f; - - for ( - int i = 0; - i < static_cast(armas.size()); - i++ - ) - { - const Arma& arma = - armas[i]; - - if (!arma.activa) - continue; - - if (!arma.enSuelo) - continue; - - float distancia = - Distancia( - entidad.posicion, - arma.posicion - ); - - if (distancia < mejorDistancia) - { - mejorDistancia = distancia; - mejor = i; - } - } - - return mejor; -} - -void RecogerArma( - Entidad& entidad, - int indiceArma, - std::vector& armas -) -{ - if ( - indiceArma < 0 || - indiceArma >= static_cast(armas.size()) - ) - { - return; - } - - Arma& nueva = - armas[indiceArma]; - - if ( - !nueva.activa || - !nueva.enSuelo - ) - { - return; - } - - // Si ya llevaba una, la tira. - if (entidad.arma >= 0) - { - Arma& vieja = - armas[entidad.arma]; - - vieja.enSuelo = true; - vieja.activa = true; - vieja.posicion = - entidad.posicion; - } - - nueva.enSuelo = false; - - entidad.arma = - indiceArma; - - entidad.recargando = false; - entidad.tiempoRecarga = 0; -} - -// ============================================================ -// DISPARO -// ============================================================ - -void Disparar( - Entidad& entidad, - std::vector& armas, - std::vector& balas -) -{ - if (!entidad.viva) - return; - - if (entidad.arma < 0) - return; - - if ( - entidad.arma >= - static_cast(armas.size()) - ) - { - entidad.arma = -1; - return; - } - - Arma& arma = - armas[entidad.arma]; - - if (arma.balas <= 0) - return; - - if (entidad.recargando) - return; - - if (entidad.tiempoDisparo > 0) - return; - - arma.balas--; - - Bala bala{}; - - bala.posicion = { - entidad.posicion.x + - entidad.direccion.x * - (entidad.radio + 9), - - entidad.posicion.y + - entidad.direccion.y * - (entidad.radio + 9) - }; - - bala.direccion = - Normalizar( - entidad.direccion - ); - - bala.velocidad = - VelocidadBala( - arma.tipo - ); - - bala.dano = - DanoArma( - arma.tipo - ); - - bala.equipo = - entidad.equipo; - - bala.paredesAtraviesadas = 0; - bala.rebotes = 0; - bala.activa = true; - - balas.push_back(bala); - - entidad.tiempoDisparo = - CadenciaArma( - arma.tipo - ); -} - -// ============================================================ -// RECARGA -// ============================================================ - -void IniciarRecarga( - Entidad& entidad, - std::vector& armas -) -{ - if (!entidad.viva) - return; - - if (entidad.arma < 0) - return; - - if ( - entidad.arma >= - static_cast(armas.size()) - ) - { - entidad.arma = -1; - return; - } - - Arma& arma = - armas[entidad.arma]; - - if (entidad.recargando) - return; - - if ( - arma.balas >= - CapacidadArma(arma.tipo) - ) - { - return; - } - - // Nunca se acaban. - arma.cartuchos = 999999; - - entidad.recargando = true; - entidad.tiempoRecarga = - TIEMPO_RECARGA; -} - -void ActualizarRecarga( - Entidad& entidad, - std::vector& armas, - float dt -) -{ - if (!entidad.recargando) - return; - - entidad.tiempoRecarga -= dt; - - if (entidad.tiempoRecarga <= 0) - { - entidad.recargando = false; - - if ( - entidad.arma >= 0 && - entidad.arma < - static_cast(armas.size()) - ) - { - Arma& arma = - armas[entidad.arma]; - - arma.balas = - CapacidadArma( - arma.tipo - ); - - arma.cartuchos = 999999; - } - } -} - -// ============================================================ -// MARCAS DE MUERTE -// ============================================================ - -void CrearMarcaMuerte( - std::vector& marcas, - Vector2 posicion -) -{ - marcas.push_back({ - posicion, - 0.0f, - true - }); -} - -// ============================================================ -// BALAS -// ============================================================ - -void MoverBalas( - std::vector& balas, - const std::vector& paredes, - std::vector& entidades, - std::vector& marcas, - std::vector& armas, - float dt -) -{ - for (Bala& bala : balas) - { - if (!bala.activa) - continue; - - Vector2 inicio = - bala.posicion; - - Vector2 destino = { - inicio.x + - bala.direccion.x * - bala.velocidad * - dt, - - inicio.y + - bala.direccion.y * - bala.velocidad * - dt - }; - - // ---------------------------------------------------- - // BORDES - // ---------------------------------------------------- - - bool bordeX = - destino.x < BORDE || - destino.x > MUNDO_ANCHO - BORDE; - - bool bordeY = - destino.y < BORDE || - destino.y > MUNDO_ALTO - BORDE; - - if (bordeX) - { - if (bala.rebotes < 1) - { - bala.direccion.x *= -1.0f; - bala.rebotes++; - - destino.x = - inicio.x + - bala.direccion.x * - bala.velocidad * - dt; - } - else - { - bala.activa = false; - continue; - } - } - - if (bordeY) - { - if (bala.rebotes < 1) - { - bala.direccion.y *= -1.0f; - bala.rebotes++; - - destino.y = - inicio.y + - bala.direccion.y * - bala.velocidad * - dt; - } - else - { - bala.activa = false; - continue; - } - } - - // ---------------------------------------------------- - // PAREDES - // ---------------------------------------------------- - - bool chocoPared = false; - Rectangle paredGolpeada{}; - - for (const auto& pared : paredes) - { - if ( - LineaChocaRectangulo( - inicio, - destino, - pared.rectangulo - ) - ) - { - chocoPared = true; - paredGolpeada = - pared.rectangulo; - break; - } - } - - if (chocoPared) - { - // PRIMERA PARED: - // atraviesa sin rebotar. - if (bala.paredesAtraviesadas == 0) - { - bala.paredesAtraviesadas++; - - bala.posicion = - destino; - - continue; - } - - // SEGUNDA PARED: - // rebota. - if ( - bala.paredesAtraviesadas == 1 && - bala.rebotes == 0 - ) - { - bala.paredesAtraviesadas++; - bala.rebotes++; - - float izquierda = - std::fabs( - inicio.x - - paredGolpeada.x - ); - - float derecha = - std::fabs( - inicio.x - - ( - paredGolpeada.x + - paredGolpeada.width - ) - ); - - float arriba = - std::fabs( - inicio.y - - paredGolpeada.y - ); - - float abajo = - std::fabs( - inicio.y - - ( - paredGolpeada.y + - paredGolpeada.height - ) - ); - - float minimo = - std::min( - std::min( - izquierda, - derecha - ), - std::min( - arriba, - abajo - ) - ); - - if ( - minimo == izquierda || - minimo == derecha - ) - { - bala.direccion.x *= -1.0f; - } - else - { - bala.direccion.y *= -1.0f; - } - - bala.posicion = { - inicio.x + - bala.direccion.x * 8.0f, - - inicio.y + - bala.direccion.y * 8.0f - }; - - continue; - } - - // TERCERA PARED: - // desaparece. - bala.activa = false; - continue; - } - - bala.posicion = - destino; - - // ---------------------------------------------------- - // GOLPEAR ENTIDADES - // ---------------------------------------------------- - - for ( - int i = 0; - i < static_cast(entidades.size()); - i++ - ) - { - Entidad& entidad = - entidades[i]; - - if (!entidad.viva) - continue; - - if (entidad.equipo == bala.equipo) - continue; - - if ( - Distancia( - bala.posicion, - entidad.posicion - ) < - entidad.radio + 5.0f - ) - { - entidad.vida -= bala.dano; - - bala.activa = false; - - if (entidad.vida <= 0) - { - entidad.vida = 0; - entidad.viva = false; - - // Marca naranja si muere - // un compañero del jugador. - if ( - entidad.equipo == EQUIPO_AZUL && - !entidad.jugador - ) - { - CrearMarcaMuerte( - marcas, - entidad.posicion - ); - } - - // Su arma cae al suelo - // conservando las balas. - if ( - entidad.arma >= 0 && - entidad.arma < - static_cast( - armas.size() - ) - ) - { - Arma& arma = - armas[entidad.arma]; - - arma.enSuelo = true; - arma.activa = true; - arma.posicion = - entidad.posicion; - - entidad.arma = -1; - } - } - - break; - } - } - } - - balas.erase( - std::remove_if( - balas.begin(), - balas.end(), - [](const Bala& bala) - { - return !bala.activa; - } - ), - balas.end() - ); -} - -// ============================================================ -// PICKUPS -// ============================================================ - -bool PosicionLibre( - Vector2 posicion, - const std::vector& paredes -) -{ - for (const auto& pared : paredes) - { - if ( - CirculoChocaRectangulo( - posicion, - 18.0f, - pared.rectangulo - ) - ) - { - return false; - } - } - - return true; -} - -void CrearBotiquin( - std::vector& botiquines, - const std::vector& paredes -) -{ - for (int intento = 0; intento < 100; intento++) - { - Vector2 posicion = { - static_cast( - 120 + rand() % 2560 - ), - static_cast( - 120 + rand() % 1660 - ) - }; - - if ( - PosicionLibre( - posicion, - paredes - ) - ) - { - botiquines.push_back({ - posicion, - true - }); - - return; - } - } -} - -void CrearMunicion( - std::vector& municiones, - const std::vector& paredes -) -{ - for (int intento = 0; intento < 100; intento++) - { - Vector2 posicion = { - static_cast( - 120 + rand() % 2560 - ), - static_cast( - 120 + rand() % 1660 - ) - }; - - if ( - PosicionLibre( - posicion, - paredes - ) - ) - { - municiones.push_back({ - posicion, - true - }); - - return; - } - } -} - -void ActualizarBotiquines( - std::vector& botiquines, - std::vector& entidades -) -{ - for (auto& botiquin : botiquines) - { - if (!botiquin.activa) - continue; - - for (Entidad& entidad : entidades) - { - if (!entidad.viva) - continue; - - if ( - Distancia( - botiquin.posicion, - entidad.posicion - ) < - entidad.radio + 18.0f - ) - { - if ( - entidad.vida < - entidad.vidaMaxima - ) - { - entidad.vida = - std::min( - entidad.vidaMaxima, - entidad.vida + 35.0f - ); - - botiquin.activa = false; - break; - } - } - } - } -} - -void ActualizarMunicion( - std::vector& municiones, - std::vector& entidades, - std::vector& armas -) -{ - for (auto& municion : municiones) - { - if (!municion.activa) - continue; - - for (Entidad& entidad : entidades) - { - if (!entidad.viva) - continue; - - if (entidad.arma < 0) - continue; - - if ( - entidad.arma >= - static_cast(armas.size()) - ) - { - entidad.arma = -1; - continue; - } - - if ( - Distancia( - municion.posicion, - entidad.posicion - ) < - entidad.radio + 18.0f - ) - { - Arma& arma = - armas[entidad.arma]; - - arma.balas = - CapacidadArma( - arma.tipo - ); - - arma.cartuchos = 999999; - - municion.activa = false; - - break; - } - } - } -} - -// ============================================================ -// IA -// ============================================================ - -void ActualizarIA( - int indice, - std::vector& entidades, - std::vector& armas, - std::vector& balas, - const std::vector& paredes, - float dt, - float tiempoPartida -) -{ - Entidad& ia = - entidades[indice]; - - if (!ia.viva) - return; - - ia.tiempoDisparo = - std::max( - 0.0f, - ia.tiempoDisparo - dt - ); - - ActualizarRecarga( - ia, - armas, - dt - ); - - // -------------------------------------------------------- - // BUSCAR ARMA - // -------------------------------------------------------- - - if (ia.arma < 0) - { - int armaCercana = - BuscarArmaCercana( - ia, - armas - ); - - if (armaCercana >= 0) - { - RecogerArma( - ia, - armaCercana, - armas - ); - } - } - - if (ia.arma < 0) - { - Vector2 direccion = { - ia.equipo == EQUIPO_AZUL - ? 1.0f - : -1.0f, - 0 - }; - - MoverEntidad( - ia, - Multiplicar( - direccion, - VELOCIDAD_IA * dt - ), - paredes - ); - - return; - } - - if ( - ia.arma >= - static_cast(armas.size()) - ) - { - ia.arma = -1; - return; - } - - Arma& arma = - armas[ia.arma]; - - if (arma.balas <= 0) - { - IniciarRecarga( - ia, - armas - ); - - return; - } - - int enemigo = - BuscarEnemigo( - indice, - entidades, - paredes - ); - - Vector2 direccionMovimiento = { - 0, - 0 - }; - - if (enemigo >= 0) - { - Entidad& objetivo = - entidades[enemigo]; - - Vector2 haciaObjetivo = { - objetivo.posicion.x - - ia.posicion.x, - - objetivo.posicion.y - - ia.posicion.y - }; - - float distancia = - Distancia( - ia.posicion, - objetivo.posicion - ); - - haciaObjetivo = - Normalizar( - haciaObjetivo - ); - - ia.direccion = - haciaObjetivo; - - if (distancia > 480.0f) - { - direccionMovimiento = - haciaObjetivo; - } - else if (distancia < 250.0f) - { - direccionMovimiento = - Multiplicar( - haciaObjetivo, - -1.0f - ); - } - else - { - direccionMovimiento = { - -haciaObjetivo.y, - haciaObjetivo.x - }; - - float signo = - std::sin( - GetTime() * - 1.5 + - indice * 3 - ); - - if (signo < 0) - { - direccionMovimiento.x *= -1; - direccionMovimiento.y *= -1; - } - } - - // Protección inicial: - // durante 3 segundos no disparan. - if ( - tiempoPartida > 3.0f && - distancia < 900.0f && - arma.balas > 0 - ) - { - Disparar( - ia, - armas, - balas - ); - } - } - else - { - float tiempo = - GetTime() + - indice * 5.0f; - - direccionMovimiento = { - std::cos( - tiempo * 0.45f - ), - std::sin( - tiempo * 0.37f - ) - }; - - direccionMovimiento = - Normalizar( - direccionMovimiento - ); - } - - // Separación entre compañeros. - Vector2 separacion = - FuerzaSeparacion( - indice, - entidades - ); - - direccionMovimiento.x += - separacion.x * 2.0f; - - direccionMovimiento.y += - separacion.y * 2.0f; - - direccionMovimiento = - Normalizar( - direccionMovimiento - ); - - // Evitar paredes. - Vector2 prueba = { - ia.posicion.x + - direccionMovimiento.x * 80.0f, - - ia.posicion.y + - direccionMovimiento.y * 80.0f - }; - - if ( - EntidadChocaPared( - prueba, - ia.radio, - paredes - ) - ) - { - direccionMovimiento = - BuscarDireccionLibre( - ia, - paredes - ); - } - - Vector2 antes = - ia.posicion; - - MoverEntidad( - ia, - Multiplicar( - direccionMovimiento, - VELOCIDAD_IA * dt - ), - paredes - ); - - float movimiento = - Distancia( - antes, - ia.posicion - ); - - if (movimiento < 1.5f) - ia.tiempoAtascado += dt; - else - ia.tiempoAtascado = 0; - - // Desatascar. - if (ia.tiempoAtascado > 0.45f) - { - Vector2 libre = - BuscarDireccionLibre( - ia, - paredes - ); - - MoverEntidad( - ia, - Multiplicar( - libre, - VELOCIDAD_IA * - dt * - 3.0f - ), - paredes - ); - - ia.tiempoAtascado = 0; - } - - ia.ultimaPosicion = - ia.posicion; -} - -// ============================================================ -// DIBUJAR MAPA -// ============================================================ - -void DibujarMapa( - const std::vector& paredes -) -{ - DrawRectangle( - 0, - 0, - static_cast(MUNDO_ANCHO), - static_cast(MUNDO_ALTO), - Color{ - 47, - 49, - 54, - 255 - } - ); - - for ( - int x = 0; - x < static_cast(MUNDO_ANCHO); - x += 100 - ) - { - DrawLine( - x, - 0, - x, - static_cast(MUNDO_ALTO), - Color{ - 55, - 57, - 62, - 255 - } - ); - } - - for ( - int y = 0; - y < static_cast(MUNDO_ALTO); - y += 100 - ) - { - DrawLine( - 0, - y, - static_cast(MUNDO_ANCHO), - y, - Color{ - 55, - 57, - 62, - 255 - } - ); - } - - for (const auto& pared : paredes) - { - DrawRectangleRec( - pared.rectangulo, - Color{ - 25, - 27, - 31, - 255 - } - ); - - DrawRectangleLinesEx( - pared.rectangulo, - 2, - Color{ - 90, - 94, - 100, - 255 - } - ); - } -} - -// ============================================================ -// DIBUJAR ARMAS -// ============================================================ - -void DibujarArmas( - const std::vector& armas -) -{ - for (const Arma& arma : armas) - { - if (!arma.activa) - continue; - - if (!arma.enSuelo) - continue; - - DrawCircleV( - arma.posicion, - 13, - ColorArma(arma.tipo) - ); - - DrawCircleLines( - static_cast(arma.posicion.x), - static_cast(arma.posicion.y), - 15, - BLACK - ); - - DrawText( - NombreArma(arma.tipo), - static_cast(arma.posicion.x - 35), - static_cast(arma.posicion.y + 18), - 10, - WHITE - ); - } -} - -// ============================================================ -// BOTIQUINES -// ============================================================ - -void DibujarBotiquines( - const std::vector& botiquines -) -{ - for (const auto& botiquin : botiquines) - { - if (!botiquin.activa) - continue; - - DrawCircleV( - botiquin.posicion, - 15, - GREEN - ); - - DrawRectangle( - static_cast(botiquin.posicion.x - 4), - static_cast(botiquin.posicion.y - 10), - 8, - 20, - WHITE - ); - - DrawRectangle( - static_cast(botiquin.posicion.x - 10), - static_cast(botiquin.posicion.y - 4), - 20, - 8, - WHITE - ); - } -} - -// ============================================================ -// MUNICIÓN -// ============================================================ - -void DibujarMunicion( - const std::vector& municiones -) -{ - for (const auto& municion : municiones) - { - if (!municion.activa) - continue; - - DrawRectangle( - static_cast(municion.posicion.x - 13), - static_cast(municion.posicion.y - 10), - 26, - 20, - GOLD - ); - - DrawRectangleLines( - static_cast(municion.posicion.x - 13), - static_cast(municion.posicion.y - 10), - 26, - 20, - BLACK - ); - - DrawText( - "AM", - static_cast(municion.posicion.x - 8), - static_cast(municion.posicion.y - 7), - 12, - BLACK - ); - } -} - -// ============================================================ -// ENTIDADES -// ============================================================ - -void DibujarEntidades( - const std::vector& entidades -) -{ - for (const auto& entidad : entidades) - { - if (!entidad.viva) - continue; - - Color color = - entidad.equipo == EQUIPO_AZUL - ? BLUE - : RED; - - DrawCircleV( - entidad.posicion, - entidad.radio, - color - ); - - DrawCircleLines( - static_cast(entidad.posicion.x), - static_cast(entidad.posicion.y), - entidad.radio, - WHITE - ); - - DrawLineEx( - entidad.posicion, - { - entidad.posicion.x + - entidad.direccion.x * - (entidad.radio + 10), - - entidad.posicion.y + - entidad.direccion.y * - (entidad.radio + 10) - }, - 4, - WHITE - ); - - float porcentaje = - entidad.vida / - entidad.vidaMaxima; - - DrawRectangle( - static_cast(entidad.posicion.x - 25), - static_cast(entidad.posicion.y - 34), - 50, - 6, - DARKGRAY - ); - - DrawRectangle( - static_cast(entidad.posicion.x - 25), - static_cast(entidad.posicion.y - 34), - static_cast(50 * porcentaje), - 6, - GREEN - ); - - if (entidad.jugador) - { - DrawCircleLines( - static_cast(entidad.posicion.x), - static_cast(entidad.posicion.y), - entidad.radio + 5, - WHITE - ); - } - } -} - -// ============================================================ -// MINIMAPA -// ============================================================ - -void DibujarMinimapa( - const std::vector& paredes, - const std::vector& entidades, - const std::vector& marcas -) -{ - const int mapaX = 820; - const int mapaY = 8; - const int mapaAncho = 440; - const int mapaAlto = 58; - - DrawRectangle( - mapaX, - mapaY, - mapaAncho, - mapaAlto, - Color{ - 10, - 12, - 16, - 245 - } - ); - - DrawRectangleLines( - mapaX, - mapaY, - mapaAncho, - mapaAlto, - LIGHTGRAY - ); - - float escalaX = - static_cast(mapaAncho) / - MUNDO_ANCHO; - - float escalaY = - static_cast(mapaAlto) / - MUNDO_ALTO; - - // Paredes - for (const auto& pared : paredes) - { - int x = - mapaX + - static_cast( - pared.rectangulo.x * escalaX - ); - - int y = - mapaY + - static_cast( - pared.rectangulo.y * escalaY - ); - - int w = - std::max( - 1, - static_cast( - pared.rectangulo.width * - escalaX - ) - ); - - int h = - std::max( - 1, - static_cast( - pared.rectangulo.height * - escalaY - ) - ); - - DrawRectangle( - x, - y, - w, - h, - Color{ - 80, - 83, - 88, - 255 - } - ); - } - - // Muertes de compañeros. - for (const auto& marca : marcas) - { - if (!marca.activa) - continue; - - int x = - mapaX + - static_cast( - marca.posicion.x * escalaX - ); - - int y = - mapaY + - static_cast( - marca.posicion.y * escalaY - ); - - DrawCircle( - x, - y, - 5, - ORANGE - ); - - DrawCircleLines( - x, - y, - 8, - GOLD - ); - } - - // Solo equipo azul. - for (const auto& entidad : entidades) - { - if (!entidad.viva) - continue; - - if (entidad.equipo != EQUIPO_AZUL) - continue; - - int x = - mapaX + - static_cast( - entidad.posicion.x * escalaX - ); - - int y = - mapaY + - static_cast( - entidad.posicion.y * escalaY - ); - - if (entidad.jugador) - { - DrawCircle( - x, - y, - 5, - WHITE - ); - - DrawCircleLines( - x, - y, - 8, - BLUE - ); - } - else - { - DrawCircle( - x, - y, - 4, - SKYBLUE - ); - } - } - - DrawText( - "MAPA", - mapaX + 6, - mapaY + 4, - 9, - LIGHTGRAY - ); -} - -// ============================================================ -// HUD -// ============================================================ - -void DibujarHUD( - const Entidad& jugador, - const std::vector& armas -) -{ - DrawRectangle( - 0, - 0, - PANTALLA_ANCHO, - 74, - Color{ - 10, - 12, - 16, - 245 - } - ); - - DrawText( - TextFormat( - "VIDA: %.0f / %.0f", - jugador.vida, - jugador.vidaMaxima - ), - 15, - 10, - 18, - WHITE - ); - - if ( - jugador.arma >= 0 && - jugador.arma < - static_cast(armas.size()) - ) - { - const Arma& arma = - armas[jugador.arma]; - - DrawText( - TextFormat( - "ARMA: %s", - NombreArma(arma.tipo) - ), - 15, - 38, - 15, - ColorArma(arma.tipo) - ); - - DrawText( - TextFormat( - "BALAS: %d / %d", - arma.balas, - CapacidadArma(arma.tipo) - ), - 220, - 14, - 18, - WHITE - ); - - DrawText( - "CARTUCHOS: INFINITOS", - 220, - 40, - 15, - GREEN - ); - } - else - { - DrawText( - "SIN ARMA", - 220, - 20, - 18, - RED - ); - } - - DrawText( - TextFormat( - "FPS: %d", - GetFPS() - ), - 700, - 12, - 18, - GREEN - ); -} - -// ============================================================ -// MAIN -// ============================================================ +void RunGame(); int main() { - srand( - static_cast( - time(nullptr) - ) - ); - - InitWindow( - PANTALLA_ANCHO, - PANTALLA_ALTO, - "A4B1 0.0.2" - ); - - SetTargetFPS(60); - - // ======================================================== - // MAPA - // ======================================================== - - std::vector paredes = - CrearMapa(); - - // ======================================================== - // ARMAS DEL SUELO - // ======================================================== - - std::vector armas; - - armas.push_back( - CrearArma( - ARMA_RAPIDA, - {650, 450}, - true - ) - ); - - armas.push_back( - CrearArma( - ARMA_EQUILIBRADA, - {2150, 450}, - true - ) - ); - - armas.push_back( - CrearArma( - ARMA_PESADA, - {650, 1400}, - true - ) - ); - - armas.push_back( - CrearArma( - ARMA_PRECISA, - {2150, 1400}, - true - ) - ); - - // ======================================================== - // 3 VS 3 - // - // AZUL: lado izquierdo - // ROJO: lado derecho - // - // MUY SEPARADOS. - // ======================================================== - - std::vector entidades; - - // Jugador azul - entidades.push_back({ - {250, 500}, - {0, 0}, - {1, 0}, - - RADIO_JUGADOR, - - 100, - 100, - - EQUIPO_AZUL, - - true, - true, - - -1, - - false, - 0, - - 0, - 0, - - {250, 500} - }); - - // Compañero azul - entidades.push_back({ - {250, 850}, - {0, 0}, - {1, 0}, - - RADIO_IA, - - 100, - 100, - - EQUIPO_AZUL, - - false, - true, - - -1, - - false, - 0, - - 0, - 0, - - {250, 850} - }); - - // Compañero azul - entidades.push_back({ - {250, 1200}, - {0, 0}, - {1, 0}, - - RADIO_IA, - - 100, - 100, - - EQUIPO_AZUL, - - false, - true, - - -1, - - false, - 0, - - 0, - 0, - - {250, 1200} - }); - - // Enemigo rojo - entidades.push_back({ - {2550, 500}, - {0, 0}, - {-1, 0}, - - RADIO_IA, - - 100, - 100, - - EQUIPO_ROJO, - - false, - true, - - -1, - - false, - 0, - - 0, - 0, - - {2550, 500} - }); - - // Enemigo rojo - entidades.push_back({ - {2550, 850}, - {0, 0}, - {-1, 0}, - - RADIO_IA, - - 100, - 100, - - EQUIPO_ROJO, - - false, - true, - - -1, - - false, - 0, - - 0, - 0, - - {2550, 850} - }); - - // Enemigo rojo - entidades.push_back({ - {2550, 1200}, - {0, 0}, - {-1, 0}, - - RADIO_IA, - - 100, - 100, - - EQUIPO_ROJO, - - false, - true, - - -1, - - false, - 0, - - 0, - 0, - - {2550, 1200} - }); - - // ======================================================== - // UN ARMA EXCLUSIVA PARA CADA JUGADOR - // ======================================================== - - armas.push_back( - CrearArma( - ARMA_EQUILIBRADA, - {0, 0}, - false - ) - ); - entidades[0].arma = - static_cast(armas.size()) - 1; - - armas.push_back( - CrearArma( - ARMA_RAPIDA, - {0, 0}, - false - ) - ); - entidades[1].arma = - static_cast(armas.size()) - 1; - - armas.push_back( - CrearArma( - ARMA_PESADA, - {0, 0}, - false - ) - ); - entidades[2].arma = - static_cast(armas.size()) - 1; - - armas.push_back( - CrearArma( - ARMA_PRECISA, - {0, 0}, - false - ) - ); - entidades[3].arma = - static_cast(armas.size()) - 1; - - armas.push_back( - CrearArma( - ARMA_EQUILIBRADA, - {0, 0}, - false - ) - ); - entidades[4].arma = - static_cast(armas.size()) - 1; - - armas.push_back( - CrearArma( - ARMA_PESADA, - {0, 0}, - false - ) - ); - entidades[5].arma = - static_cast(armas.size()) - 1; - - // ======================================================== - // BALAS - // ======================================================== - - std::vector balas; - - // ======================================================== - // BOTIQUINES - // ======================================================== - - std::vector botiquines; - - for (int i = 0; i < 5; i++) - { - CrearBotiquin( - botiquines, - paredes - ); - } - - // ======================================================== - // MUNICIÓN - // ======================================================== - - std::vector municiones; - - for (int i = 0; i < 5; i++) - { - CrearMunicion( - municiones, - paredes - ); - } - - // ======================================================== - // MARCAS DE MUERTE - // ======================================================== - - std::vector marcas; - - // ======================================================== - // CÁMARA - // ======================================================== - - Camera2D camara{}; - - camara.offset = { - PANTALLA_ANCHO / 2.0f, - PANTALLA_ALTO / 2.0f - }; - - camara.target = - entidades[0].posicion; - - camara.rotation = 0; - camara.zoom = 1.0f; - - // ======================================================== - // TIEMPO - // ======================================================== - - float tiempoPartida = 0.0f; - - // ======================================================== - // LOOP PRINCIPAL - // ======================================================== - - while (!WindowShouldClose()) - { - float dt = - GetFrameTime(); - - tiempoPartida += dt; - - // ==================================================== - // JUGADOR - // ==================================================== - - Entidad& jugador = - entidades[0]; - - if (jugador.viva) - { - jugador.tiempoDisparo = - std::max( - 0.0f, - jugador.tiempoDisparo - dt - ); - - ActualizarRecarga( - jugador, - armas, - dt - ); - - Vector2 movimiento = { - 0, - 0 - }; - - if (IsKeyDown(KEY_W)) - movimiento.y -= 1; - - if (IsKeyDown(KEY_S)) - movimiento.y += 1; - - if (IsKeyDown(KEY_A)) - movimiento.x -= 1; - - if (IsKeyDown(KEY_D)) - movimiento.x += 1; - - movimiento = - Normalizar( - movimiento - ); - - MoverEntidad( - jugador, - Multiplicar( - movimiento, - VELOCIDAD_JUGADOR * dt - ), - paredes - ); - - if ( - movimiento.x != 0 || - movimiento.y != 0 - ) - { - jugador.direccion = - movimiento; - } - - if ( - IsKeyDown(KEY_SPACE) && - tiempoPartida > 3.0f - ) - { - Disparar( - jugador, - armas, - balas - ); - } - - if (IsKeyPressed(KEY_R)) - { - IniciarRecarga( - jugador, - armas - ); - } - - if (IsKeyPressed(KEY_E)) - { - int armaCercana = - BuscarArmaCercana( - jugador, - armas - ); - - if (armaCercana >= 0) - { - RecogerArma( - jugador, - armaCercana, - armas - ); - } - } - } - - // ==================================================== - // IA - // ==================================================== - - for ( - int i = 1; - i < static_cast(entidades.size()); - i++ - ) - { - ActualizarIA( - i, - entidades, - armas, - balas, - paredes, - dt, - tiempoPartida - ); - } - - // ==================================================== - // BALAS - // ==================================================== - - MoverBalas( - balas, - paredes, - entidades, - marcas, - armas, - dt - ); - - // ==================================================== - // BOTIQUINES - // ==================================================== - - ActualizarBotiquines( - botiquines, - entidades - ); - - int botiquinesActivos = 0; - - for (const auto& botiquin : botiquines) - { - if (botiquin.activa) - botiquinesActivos++; - } - - while (botiquinesActivos < 5) - { - CrearBotiquin( - botiquines, - paredes - ); - - botiquinesActivos = 0; - - for (const auto& botiquin : botiquines) - { - if (botiquin.activa) - botiquinesActivos++; - } - - if (botiquines.size() > 30) - break; - } - - // ==================================================== - // MUNICIÓN - // ==================================================== - - ActualizarMunicion( - municiones, - entidades, - armas - ); - - int municionActiva = 0; - - for (const auto& municion : municiones) - { - if (municion.activa) - municionActiva++; - } - - while (municionActiva < 5) - { - CrearMunicion( - municiones, - paredes - ); - - municionActiva = 0; - - for (const auto& municion : municiones) - { - if (municion.activa) - municionActiva++; - } - - if (municiones.size() > 30) - break; - } - - // ==================================================== - // CÁMARA - // ==================================================== - - camara.target = - jugador.posicion; - - // ==================================================== - // ZOOM - // ==================================================== - - if (IsKeyDown(KEY_MINUS)) - { - camara.zoom -= - 0.5f * dt; - } - - if (IsKeyDown(KEY_EQUAL)) - { - camara.zoom += - 0.5f * dt; - } - - camara.zoom = - std::clamp( - camara.zoom, - 0.65f, - 1.4f - ); - - // ==================================================== - // FULLSCREEN - // ==================================================== - - if (IsKeyPressed(KEY_F11)) - { - ToggleFullscreen(); - } - - // ==================================================== - // DIBUJAR - // ==================================================== - - BeginDrawing(); - - ClearBackground(BLACK); - - BeginMode2D(camara); - - DibujarMapa( - paredes - ); - - DibujarBotiquines( - botiquines - ); - - DibujarMunicion( - municiones - ); - - DibujarArmas( - armas - ); - - // Balas - for (const Bala& bala : balas) - { - if (!bala.activa) - continue; - - Color color = - bala.equipo == EQUIPO_AZUL - ? SKYBLUE - : ORANGE; - - DrawCircleV( - bala.posicion, - 5, - color - ); - } - - DibujarEntidades( - entidades - ); - - EndMode2D(); - - // ==================================================== - // OSCURIDAD - // ==================================================== - - DrawRectangle( - 0, - 74, - PANTALLA_ANCHO, - PANTALLA_ALTO - 74, - Color{ - 0, - 0, - 0, - 115 - } - ); - - // ==================================================== - // HUD - // ==================================================== - - DibujarHUD( - jugador, - armas - ); - - // ==================================================== - // MINIMAPA - // ==================================================== - - DibujarMinimapa( - paredes, - entidades, - marcas - ); - - // ==================================================== - // CONTROLES - // ==================================================== - - DrawText( - "WASD: mover", - 15, - 685, - 15, - LIGHTGRAY - ); - - DrawText( - "SPACE: disparar", - 130, - 685, - 15, - LIGHTGRAY - ); - - DrawText( - "R: recargar", - 275, - 685, - 15, - LIGHTGRAY - ); - - DrawText( - "E: recoger arma", - 375, - 685, - 15, - LIGHTGRAY - ); - - // ==================================================== - // RECARGANDO - // ==================================================== - - if ( - jugador.viva && - jugador.recargando - ) - { - DrawText( - "RECARGANDO...", - PANTALLA_ANCHO / 2 - 90, - PANTALLA_ALTO - 55, - 22, - YELLOW - ); - } - - // ==================================================== - // MUERTO - // ==================================================== - - if (!jugador.viva) - { - DrawRectangle( - 0, - 0, - PANTALLA_ANCHO, - PANTALLA_ALTO, - Color{ - 0, - 0, - 0, - 180 - } - ); - - DrawText( - "HAS MUERTO", - PANTALLA_ANCHO / 2 - 145, - PANTALLA_ALTO / 2 - 30, - 45, - RED - ); - } - - // ==================================================== - // MARCADOR - // ==================================================== - - int azulesVivos = 0; - int rojosVivos = 0; - - for (const auto& entidad : entidades) - { - if (!entidad.viva) - continue; - - if (entidad.equipo == EQUIPO_AZUL) - azulesVivos++; - else - rojosVivos++; - } - - DrawText( - TextFormat( - "AZUL %d - ROJO %d", - azulesVivos, - rojosVivos - ), - 1000, - 685, - 16, - WHITE - ); - - EndDrawing(); - } - - CloseWindow(); - - return 0; -} \ No newline at end of file + RunGame(); + return 0; +} diff --git a/source/A4B1/player.cpp b/source/A4B1/player.cpp new file mode 100644 index 0000000..8d21ccd --- /dev/null +++ b/source/A4B1/player.cpp @@ -0,0 +1,242 @@ +#include "raylib.h" +#include + +extern bool IsSolidAt(Vector3 position, float radius); + +Vector3 playerPosition = {-76.0f, 1.0f, -76.0f}; + +float verticalVelocity = 0.0f; +float playerYaw = 0.0f; +float playerPitch = 0.0f; + +float weaponBobTime = 0.0f; +float weaponShootTime = 0.0f; + +const float moveSpeed = 7.0f; +const float jumpForce = 9.0f; +const float gravity = 20.0f; + +static const float PLAYER_RADIUS = 0.45f; +static const float PLAYER_GROUND_Y = 1.0f; +static const float PLAYER_HEAD_Y = 2.0f; +static const float CEILING_Y = 3.0f; + +Camera3D GetPlayerCamera() +{ + Camera3D camera = {}; + + camera.position = playerPosition; + + Vector3 forward = + { + cosf(playerPitch) * cosf(playerYaw), + sinf(playerPitch), + cosf(playerPitch) * sinf(playerYaw) + }; + + camera.target = + { + camera.position.x + forward.x, + camera.position.y + forward.y, + camera.position.z + forward.z + }; + + camera.up = {0.0f, 1.0f, 0.0f}; + camera.fovy = 75.0f; + camera.projection = CAMERA_PERSPECTIVE; + + return camera; +} + +static void MovePlayer(Vector3 movement) +{ + // X separado de Z para poder deslizarse por las paredes. + Vector3 nextX = playerPosition; + nextX.x += movement.x; + + if (!IsSolidAt(nextX, PLAYER_RADIUS)) + { + playerPosition.x = nextX.x; + } + + Vector3 nextZ = playerPosition; + nextZ.z += movement.z; + + if (!IsSolidAt(nextZ, PLAYER_RADIUS)) + { + playerPosition.z = nextZ.z; + } +} + +void UpdatePlayer(float deltaTime) +{ + Vector2 mouse = GetMouseDelta(); + + playerYaw += mouse.x * 0.003f; + playerPitch -= mouse.y * 0.003f; + + if (playerPitch > 1.45f) + playerPitch = 1.45f; + + if (playerPitch < -1.45f) + playerPitch = -1.45f; + + Vector3 movement = {0.0f, 0.0f, 0.0f}; + + Vector3 forward = + { + cosf(playerYaw), + 0.0f, + sinf(playerYaw) + }; + + Vector3 right = + { + -sinf(playerYaw), + 0.0f, + cosf(playerYaw) + }; + + if (IsKeyDown(KEY_W)) + { + movement.x += forward.x; + movement.z += forward.z; + } + + if (IsKeyDown(KEY_S)) + { + movement.x -= forward.x; + movement.z -= forward.z; + } + + if (IsKeyDown(KEY_D)) + { + movement.x += right.x; + movement.z += right.z; + } + + if (IsKeyDown(KEY_A)) + { + movement.x -= right.x; + movement.z -= right.z; + } + + float movementLength = + sqrtf( + movement.x * movement.x + + movement.z * movement.z + ); + + if (movementLength > 0.0f) + { + movement.x /= movementLength; + movement.z /= movementLength; + + MovePlayer({ + movement.x * moveSpeed * deltaTime, + 0.0f, + movement.z * moveSpeed * deltaTime + }); + + weaponBobTime += deltaTime * 10.0f; + } + + bool grounded = + playerPosition.y <= PLAYER_GROUND_Y + 0.001f; + + if (grounded) + { + playerPosition.y = PLAYER_GROUND_Y; + verticalVelocity = 0.0f; + + if (IsKeyPressed(KEY_SPACE)) + { + verticalVelocity = jumpForce; + } + } + + verticalVelocity -= gravity * deltaTime; + + playerPosition.y += + verticalVelocity * deltaTime; + + if (playerPosition.y < PLAYER_GROUND_Y) + { + playerPosition.y = PLAYER_GROUND_Y; + verticalVelocity = 0.0f; + } + + // Colisión con el techo. + if (playerPosition.y + PLAYER_HEAD_Y > CEILING_Y) + { + playerPosition.y = + CEILING_Y - PLAYER_HEAD_Y; + + if (verticalVelocity > 0.0f) + verticalVelocity = 0.0f; + } + + if (weaponShootTime > 0.0f) + weaponShootTime -= deltaTime; +} + +void DrawPlayerWeapon() +{ + float bobX = 0.0f; + float bobY = 0.0f; + + bobX = sinf(weaponBobTime) * 5.0f; + bobY = fabsf(cosf(weaponBobTime)) * 4.0f; + + if (weaponShootTime > 0.0f) + { + bobX += GetRandomValue(-4, 4); + bobY += GetRandomValue(-5, 5); + } + + int w = GetScreenWidth(); + int h = GetScreenHeight(); + + float x = w * 0.76f + bobX; + float y = h * 0.68f + bobY; + + DrawRectangle( + (int)x, + (int)y, + 190, + 95, + DARKGRAY + ); + + DrawRectangle( + (int)x + 55, + (int)y - 35, + 80, + 55, + GRAY + ); + + DrawRectangle( + (int)x + 72, + (int)y - 62, + 45, + 40, + BLACK + ); + + DrawRectangle( + (int)x + 20, + (int)y + 30, + 45, + 65, + BLACK + ); + + DrawRectangle( + (int)x + 120, + (int)y + 15, + 65, + 35, + BLACK + ); +} diff --git a/source/A4B1/weapons.cpp b/source/A4B1/weapons.cpp new file mode 100644 index 0000000..e40562a --- /dev/null +++ b/source/A4B1/weapons.cpp @@ -0,0 +1,225 @@ +#include "raylib.h" +#include +#include + +extern Camera3D GetPlayerCamera(); +extern Vector3 playerPosition; +extern float playerYaw; +extern float weaponShootTime; + +extern int CheckEnemyHit(Vector3 bulletPosition); +extern void TeleportEnemyNearPlayer(int index, float yaw); +extern bool IsSolidAt(Vector3 position, float radius); + +struct Bullet +{ + Vector3 position; + Vector3 direction; + float speed; + bool active; +}; + +static const int MAX_BULLETS = 100; + +static Bullet bullets[MAX_BULLETS]; + +static Vector3 GetForward() +{ + Vector3 forward = + { + cosf(playerYaw), + 0.0f, + sinf(playerYaw) + }; + + return forward; +} + +static void SpawnBullet( + Vector3 start, + Vector3 direction +) +{ + for (int i = 0; i < MAX_BULLETS; i++) + { + if (!bullets[i].active) + { + bullets[i].position = start; + bullets[i].direction = direction; + bullets[i].speed = 35.0f; + bullets[i].active = true; + return; + } + } +} + +void UpdateWeapons(float deltaTime) +{ + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + Camera3D camera = GetPlayerCamera(); + + Vector3 forward = + { + camera.target.x - camera.position.x, + camera.target.y - camera.position.y, + camera.target.z - camera.position.z + }; + + float length = + sqrtf( + forward.x * forward.x + + forward.y * forward.y + + forward.z * forward.z + ); + + if (length > 0.001f) + { + forward.x /= length; + forward.y /= length; + forward.z /= length; + } + + Vector3 right = + { + -sinf(playerYaw), + 0.0f, + cosf(playerYaw) + }; + + Vector3 start = + { + camera.position.x + forward.x * 0.7f, + camera.position.y + forward.y * 0.7f, + camera.position.z + forward.z * 0.7f + }; + + const float spread = 0.07f; + + SpawnBullet( + start, + { + forward.x, + forward.y, + forward.z + } + ); + + SpawnBullet( + start, + { + forward.x + right.x * spread, + forward.y, + forward.z + right.z * spread + } + ); + + SpawnBullet( + start, + { + forward.x - right.x * spread, + forward.y, + forward.z - right.z * spread + } + ); + + SpawnBullet( + start, + { + forward.x + right.x * spread * 2.0f, + forward.y, + forward.z + right.z * spread * 2.0f + } + ); + + SpawnBullet( + start, + { + forward.x - right.x * spread * 2.0f, + forward.y, + forward.z - right.z * spread * 2.0f + } + ); + + weaponShootTime = 0.12f; + } + + for (int i = 0; i < MAX_BULLETS; i++) + { + if (!bullets[i].active) + continue; + + Vector3 movement = + { + bullets[i].direction.x * + bullets[i].speed * deltaTime, + + bullets[i].direction.y * + bullets[i].speed * deltaTime, + + bullets[i].direction.z * + bullets[i].speed * deltaTime + }; + + Vector3 nextPosition = + { + bullets[i].position.x + movement.x, + bullets[i].position.y + movement.y, + bullets[i].position.z + movement.z + }; + + // Las balas chocan con paredes y muebles. + if (IsSolidAt(nextPosition, 0.08f)) + { + bullets[i].active = false; + continue; + } + + bullets[i].position = nextPosition; + + int hitNPC = + CheckEnemyHit(bullets[i].position); + + if (hitNPC >= 0) + { + TeleportEnemyNearPlayer( + hitNPC, + playerYaw + ); + + bullets[i].active = false; + continue; + } + + float dx = + bullets[i].position.x - + playerPosition.x; + + float dz = + bullets[i].position.z - + playerPosition.z; + + float distance = + sqrtf(dx * dx + dz * dz); + + if (distance > 200.0f) + { + bullets[i].active = false; + } + } +} + +void DrawBullets() +{ + for (int i = 0; i < MAX_BULLETS; i++) + { + if (!bullets[i].active) + continue; + + DrawSphere( + bullets[i].position, + 0.10f, + YELLOW + ); + } +}