From 92daf86379102508eb25a567150d0cc2cb1f990a Mon Sep 17 00:00:00 2001 From: TEC-GO Date: Sun, 27 Sep 2026 00:29:48 -0300 Subject: [PATCH] Nuevo funcionamiento TALANES --- source/A4B1/enemy.cpp | 642 ++++++++++++++++++++++++++++++++++++++--- source/A4B1/player.cpp | 10 +- 2 files changed, 602 insertions(+), 50 deletions(-) diff --git a/source/A4B1/enemy.cpp b/source/A4B1/enemy.cpp index 4519dfa..2277914 100644 --- a/source/A4B1/enemy.cpp +++ b/source/A4B1/enemy.cpp @@ -1,10 +1,14 @@ #include "raylib.h" #include +#include +#include +#include +#include extern Vector3 playerPosition; extern bool IsSolidAt(Vector3 position, float radius); -const int NPC_COUNT = 12; +const int NPC_COUNT = 20; Vector3 enemyPosition[NPC_COUNT] = { @@ -21,17 +25,349 @@ Vector3 enemyPosition[NPC_COUNT] = { -28.0f, 1.0f, -68.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 }, + + { -60.0f, 1.0f, -52.0f }, + { 60.0f, 1.0f, -52.0f }, + { -60.0f, 1.0f, 52.0f }, + { 60.0f, 1.0f, 52.0f }, + + { -16.0f, 1.0f, -48.0f }, + { 16.0f, 1.0f, -48.0f }, + { -16.0f, 1.0f, 48.0f }, + { 16.0f, 1.0f, 48.0f } }; float enemyWalkTime[NPC_COUNT] = {}; float enemyStunTime[NPC_COUNT] = {}; float enemySpin[NPC_COUNT] = {}; +bool enemyMoving[NPC_COUNT] = {}; -const float enemySpeed = 2.4f; +const float enemySpeed = 5.5f; const float enemyStunDuration = 2.5f; -bool enemyMoving[NPC_COUNT] = {}; +static bool showEnemyPaths = false; + +static const int GRID_SIZE = 41; +static const float CELL_SIZE = 4.0f; +static const float GRID_OFFSET = 20.0f; + +struct GridNode +{ + int x; + int z; + + bool operator==(const GridNode& other) const + { + return x == other.x && z == other.z; + } +}; + +static std::vector enemyPath[NPC_COUNT]; + +static int WorldToGridX(float x) +{ + return (int)std::round(x / CELL_SIZE + GRID_OFFSET); +} + +static int WorldToGridZ(float z) +{ + return (int)std::round(z / CELL_SIZE + GRID_OFFSET); +} + +static Vector3 GridToWorld(int x, int z) +{ + return + { + (x - GRID_OFFSET) * CELL_SIZE, + 1.0f, + (z - GRID_OFFSET) * CELL_SIZE + }; +} + +static bool IsInsideGrid(int x, int z) +{ + return + x >= 0 && + x < GRID_SIZE && + z >= 0 && + z < GRID_SIZE; +} + +static bool IsWalkableCell(int x, int z) +{ + if (!IsInsideGrid(x, z)) + return false; + + Vector3 position = GridToWorld(x, z); + + return !IsSolidAt(position, 0.42f); +} + +static float GridDistance( + int x1, + int z1, + int x2, + int z2 +) +{ + float dx = (float)(x1 - x2); + float dz = (float)(z1 - z2); + + return std::sqrt(dx * dx + dz * dz); +} + +static std::vector FindPath( + Vector3 startWorld, + Vector3 targetWorld +) +{ + GridNode start = + { + WorldToGridX(startWorld.x), + WorldToGridZ(startWorld.z) + }; + + GridNode target = + { + WorldToGridX(targetWorld.x), + WorldToGridZ(targetWorld.z) + }; + + if (!IsInsideGrid(start.x, start.z)) + return {}; + + if (!IsInsideGrid(target.x, target.z)) + return {}; + + // Si el destino está ocupado por un mueble, + // buscamos la celda caminable más cercana. + if (!IsWalkableCell(target.x, target.z)) + { + bool found = false; + + for (int radius = 1; radius <= 5 && !found; radius++) + { + for (int z = target.z - radius; + z <= target.z + radius && !found; + z++) + { + for (int x = target.x - radius; + x <= target.x + radius; + x++) + { + if (IsWalkableCell(x, z)) + { + target = {x, z}; + found = true; + break; + } + } + } + } + + if (!found) + return {}; + } + + if (!IsWalkableCell(start.x, start.z)) + return {}; + + struct Node + { + int x; + int z; + float g; + float f; + }; + + std::vector open; + std::vector closed( + GRID_SIZE * GRID_SIZE, + false + ); + + std::vector cameFrom( + GRID_SIZE * GRID_SIZE, + {-1, -1} + ); + + std::vector gScore( + GRID_SIZE * GRID_SIZE, + std::numeric_limits::infinity() + ); + + auto indexOf = + [](int x, int z) + { + return z * GRID_SIZE + x; + }; + + gScore[indexOf(start.x, start.z)] = 0.0f; + + open.push_back( + { + start.x, + start.z, + 0.0f, + GridDistance( + start.x, + start.z, + target.x, + target.z + ) + } + ); + + const int directions[4][2] = + { + { 1, 0 }, + {-1, 0 }, + { 0, 1 }, + { 0,-1 } + }; + + while (!open.empty()) + { + int best = 0; + + for (int i = 1; i < (int)open.size(); i++) + { + if (open[i].f < open[best].f) + best = i; + } + + Node current = open[best]; + open.erase(open.begin() + best); + + if (current.x == target.x && + current.z == target.z) + { + std::vector path; + + GridNode currentNode = + { + target.x, + target.z + }; + + while (!(currentNode == start)) + { + path.push_back(currentNode); + + GridNode previous = + cameFrom[ + indexOf( + currentNode.x, + currentNode.z + ) + ]; + + if (previous.x < 0) + { + path.clear(); + return path; + } + + currentNode = previous; + } + + path.push_back(start); + + std::reverse( + path.begin(), + path.end() + ); + + return path; + } + + int currentIndex = + indexOf(current.x, current.z); + + if (closed[currentIndex]) + continue; + + closed[currentIndex] = true; + + for (const auto& direction : directions) + { + int nx = current.x + direction[0]; + int nz = current.z + direction[1]; + + if (!IsInsideGrid(nx, nz)) + continue; + + if (!IsWalkableCell(nx, nz)) + continue; + + int nextIndex = indexOf(nx, nz); + + if (closed[nextIndex]) + continue; + + float newG = current.g + 1.0f; + + if (newG < gScore[nextIndex]) + { + gScore[nextIndex] = newG; + + cameFrom[nextIndex] = + { + current.x, + current.z + }; + + float h = + GridDistance( + nx, + nz, + target.x, + target.z + ); + + open.push_back( + { + nx, + nz, + newG, + newG + h + } + ); + } + } + } + + return {}; +} + +static bool PositionIsFreeForEnemy( + Vector3 position, + int ignoreIndex +) +{ + if (IsSolidAt(position, 0.45f)) + return false; + + for (int i = 0; i < NPC_COUNT; i++) + { + if (i == ignoreIndex) + continue; + + float dx = + enemyPosition[i].x - + position.x; + + float dz = + enemyPosition[i].z - + position.z; + + if (dx * dx + dz * dz < 0.75f * 0.75f) + return false; + } + + return true; +} void MoveEnemyWithCollision( int index, @@ -39,23 +375,206 @@ void MoveEnemyWithCollision( float moveZ ) { - Vector3 nextX = enemyPosition[index]; + Vector3 nextX = + enemyPosition[index]; nextX.x += moveX; - if (!IsSolidAt(nextX, 0.45f)) + if (PositionIsFreeForEnemy(nextX, index)) enemyPosition[index].x = nextX.x; - Vector3 nextZ = enemyPosition[index]; + Vector3 nextZ = + enemyPosition[index]; nextZ.z += moveZ; - if (!IsSolidAt(nextZ, 0.45f)) + if (PositionIsFreeForEnemy(nextZ, index)) enemyPosition[index].z = nextZ.z; } +static void RecalculatePath(int index) +{ + enemyPath[index] = + FindPath( + enemyPosition[index], + playerPosition + ); +} + +static void MoveEnemyAlongPath( + int index, + float deltaTime +) +{ + if (enemyPath[index].size() < 2) + { + enemyMoving[index] = false; + return; + } + + Vector3 target = + GridToWorld( + enemyPath[index][1].x, + enemyPath[index][1].z + ); + + float dx = + target.x - + enemyPosition[index].x; + + float dz = + target.z - + enemyPosition[index].z; + + float distance = + std::sqrt(dx * dx + dz * dz); + + if (distance < 0.18f) + { + enemyPath[index].erase( + enemyPath[index].begin() + ); + + return; + } + + dx /= distance; + dz /= distance; + + MoveEnemyWithCollision( + index, + dx * enemySpeed * deltaTime, + dz * enemySpeed * deltaTime + ); + + enemyMoving[index] = true; + enemyWalkTime[index] += deltaTime * 9.0f; +} + +static int FindClosestEnemy() +{ + int best = 0; + float bestDistance = + std::numeric_limits::infinity(); + + for (int i = 0; i < NPC_COUNT; i++) + { + float dx = + enemyPosition[i].x - + playerPosition.x; + + float dz = + enemyPosition[i].z - + playerPosition.z; + + float distance = + dx * dx + dz * dz; + + if (distance < bestDistance) + { + bestDistance = distance; + best = i; + } + } + + return best; +} + +static bool HasNearbyTalan() +{ + 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 < 28.0f) + return true; + } + + return false; +} + +static void TeleportOneTalanToPlayerArea() +{ + int index = FindClosestEnemy(); + + float bestDistance = 18.0f; + + for (int attempt = 0; attempt < 40; attempt++) + { + float angle = + (float)attempt * + (PI * 2.0f / 40.0f); + + float distance = + bestDistance + + (attempt % 5) * 1.5f; + + Vector3 position = + { + playerPosition.x + + std::cos(angle) * distance, + + 1.0f, + + playerPosition.z + + std::sin(angle) * distance + }; + + if (!PositionIsFreeForEnemy( + position, + index)) + { + continue; + } + + std::vector testPath = + FindPath( + position, + playerPosition + ); + + if (!testPath.empty()) + { + enemyPosition[index] = + position; + + enemyWalkTime[index] = 0.0f; + enemyStunTime[index] = 0.0f; + enemySpin[index] = 0.0f; + + enemyPath[index] = + testPath; + + return; + } + } +} + void UpdateEnemy(float deltaTime) { + // P muestra/oculta las rutas. + if (IsKeyPressed(KEY_P)) + { + showEnemyPaths = + !showEnemyPaths; + } + + // Siempre debe haber un talán relativamente + // cerca del jugador. + if (!HasNearbyTalan()) + { + TeleportOneTalanToPlayerArea(); + } + for (int i = 0; i < NPC_COUNT; i++) { float dx = @@ -67,10 +586,7 @@ void UpdateEnemy(float deltaTime) enemyPosition[i].z; float distance = - std::sqrt( - dx * dx + - dz * dz - ); + std::sqrt(dx * dx + dz * dz); // ========================================= // MAREADO @@ -116,24 +632,23 @@ void UpdateEnemy(float deltaTime) } // ========================================= - // SEGUIR JUGADOR + // RUTA INTELIGENTE // ========================================= + // Recalcular frecuentemente para que los talanes + // reaccionen si el jugador cambia de dirección. + if (enemyPath[i].empty() || + ((int)(GetTime() * 4.0) + i) % 5 == 0) + { + RecalculatePath(i); + } + if (distance > 1.8f) { - dx /= distance; - dz /= distance; - - MoveEnemyWithCollision( + MoveEnemyAlongPath( i, - dx * enemySpeed * deltaTime, - dz * enemySpeed * deltaTime + deltaTime ); - - enemyWalkTime[i] += - deltaTime * 9.0f; - - enemyMoving[i] = true; } else { @@ -195,10 +710,6 @@ void TeleportEnemyNearPlayer( index >= NPC_COUNT) return; - // ========================================= - // CONSERVAR LA DISTANCIA ACTUAL - // ========================================= - float dx = enemyPosition[index].x - playerPosition.x; @@ -216,10 +727,6 @@ void TeleportEnemyNearPlayer( if (oldDistance < 3.0f) oldDistance = 3.0f; - // ========================================= - // BUSCAR OTRA DIRECCIÓN - // ========================================= - float baseAngle = std::atan2( dz, @@ -244,17 +751,23 @@ void TeleportEnemyNearPlayer( std::sin(angle) * oldDistance }; - if (!IsSolidAt(newPosition, 0.45f)) + if (!PositionIsFreeForEnemy( + newPosition, + index)) { - enemyPosition[index] = - newPosition; - - enemyStunTime[index] = 0.0f; - enemySpin[index] = 0.0f; - enemyWalkTime[index] = 0.0f; - - return; + continue; } + + enemyPosition[index] = + newPosition; + + enemyStunTime[index] = 0.0f; + enemySpin[index] = 0.0f; + enemyWalkTime[index] = 0.0f; + + RecalculatePath(index); + + return; } } @@ -288,6 +801,53 @@ int CheckEnemyHit(Vector3 bulletPosition) return -1; } +// ============================================= +// RUTAS VISIBLES CON P +// ============================================= + +void DrawEnemyPaths() +{ + if (!showEnemyPaths) + return; + + for (int i = 0; i < NPC_COUNT; i++) + { + if (enemyPath[i].size() < 2) + continue; + + Vector3 previous = + enemyPosition[i]; + + previous.y = 0.12f; + + for (size_t p = 1; + p < enemyPath[i].size(); + p++) + { + Vector3 current = + GridToWorld( + enemyPath[i][p].x, + enemyPath[i][p].z + ); + + current.y = 0.12f; + + DrawLine3D( + previous, + current, + LIME + ); + + previous = current; + } + } +} + +// ============================================= +// TALÁN ORIGINAL +// NO TOCAR +// ============================================= + void DrawEnemy() { for (int i = 0; i < NPC_COUNT; i++) diff --git a/source/A4B1/player.cpp b/source/A4B1/player.cpp index 8d21ccd..8f1f14a 100644 --- a/source/A4B1/player.cpp +++ b/source/A4B1/player.cpp @@ -12,7 +12,7 @@ float playerPitch = 0.0f; float weaponBobTime = 0.0f; float weaponShootTime = 0.0f; -const float moveSpeed = 7.0f; +const float moveSpeed = 11.0f; const float jumpForce = 9.0f; const float gravity = 20.0f; @@ -50,22 +50,17 @@ Camera3D GetPlayerCamera() 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) @@ -150,9 +145,7 @@ void UpdatePlayer(float deltaTime) verticalVelocity = 0.0f; if (IsKeyPressed(KEY_SPACE)) - { verticalVelocity = jumpForce; - } } verticalVelocity -= gravity * deltaTime; @@ -166,7 +159,6 @@ void UpdatePlayer(float deltaTime) verticalVelocity = 0.0f; } - // Colisión con el techo. if (playerPosition.y + PLAYER_HEAD_Y > CEILING_Y) { playerPosition.y =