Nuevo funcionamiento TALANES

This commit is contained in:
Clinv committed 2026-09-27 00:29:48 -03:00
1 parent 95a1231c63
commit 92daf86379
2 files changed
+602 -50

No files matched your search

+601 -41
View File
@@ -1,10 +1,14 @@
#include "raylib.h" #include "raylib.h"
#include <cmath> #include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <limits>
extern Vector3 playerPosition; extern Vector3 playerPosition;
extern bool IsSolidAt(Vector3 position, float radius); extern bool IsSolidAt(Vector3 position, float radius);
const int NPC_COUNT = 12; const int NPC_COUNT = 20;
Vector3 enemyPosition[NPC_COUNT] = 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 }, { -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 enemyWalkTime[NPC_COUNT] = {};
float enemyStunTime[NPC_COUNT] = {}; float enemyStunTime[NPC_COUNT] = {};
float enemySpin[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; 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<GridNode> 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<GridNode> 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<Node> open;
std::vector<bool> closed(
GRID_SIZE * GRID_SIZE,
false
);
std::vector<GridNode> cameFrom(
GRID_SIZE * GRID_SIZE,
{-1, -1}
);
std::vector<float> gScore(
GRID_SIZE * GRID_SIZE,
std::numeric_limits<float>::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<GridNode> 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( void MoveEnemyWithCollision(
int index, int index,
@@ -39,23 +375,206 @@ void MoveEnemyWithCollision(
float moveZ float moveZ
) )
{ {
Vector3 nextX = enemyPosition[index]; Vector3 nextX =
enemyPosition[index];
nextX.x += moveX; nextX.x += moveX;
if (!IsSolidAt(nextX, 0.45f)) if (PositionIsFreeForEnemy(nextX, index))
enemyPosition[index].x = nextX.x; enemyPosition[index].x = nextX.x;
Vector3 nextZ = enemyPosition[index]; Vector3 nextZ =
enemyPosition[index];
nextZ.z += moveZ; nextZ.z += moveZ;
if (!IsSolidAt(nextZ, 0.45f)) if (PositionIsFreeForEnemy(nextZ, index))
enemyPosition[index].z = nextZ.z; 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<float>::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<GridNode> 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) 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++) for (int i = 0; i < NPC_COUNT; i++)
{ {
float dx = float dx =
@@ -67,10 +586,7 @@ void UpdateEnemy(float deltaTime)
enemyPosition[i].z; enemyPosition[i].z;
float distance = float distance =
std::sqrt( std::sqrt(dx * dx + dz * dz);
dx * dx +
dz * dz
);
// ========================================= // =========================================
// MAREADO // 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) if (distance > 1.8f)
{ {
dx /= distance; MoveEnemyAlongPath(
dz /= distance;
MoveEnemyWithCollision(
i, i,
dx * enemySpeed * deltaTime, deltaTime
dz * enemySpeed * deltaTime
); );
enemyWalkTime[i] +=
deltaTime * 9.0f;
enemyMoving[i] = true;
} }
else else
{ {
@@ -195,10 +710,6 @@ void TeleportEnemyNearPlayer(
index >= NPC_COUNT) index >= NPC_COUNT)
return; return;
// =========================================
// CONSERVAR LA DISTANCIA ACTUAL
// =========================================
float dx = float dx =
enemyPosition[index].x - enemyPosition[index].x -
playerPosition.x; playerPosition.x;
@@ -216,10 +727,6 @@ void TeleportEnemyNearPlayer(
if (oldDistance < 3.0f) if (oldDistance < 3.0f)
oldDistance = 3.0f; oldDistance = 3.0f;
// =========================================
// BUSCAR OTRA DIRECCIÓN
// =========================================
float baseAngle = float baseAngle =
std::atan2( std::atan2(
dz, dz,
@@ -244,17 +751,23 @@ void TeleportEnemyNearPlayer(
std::sin(angle) * oldDistance std::sin(angle) * oldDistance
}; };
if (!IsSolidAt(newPosition, 0.45f)) if (!PositionIsFreeForEnemy(
newPosition,
index))
{ {
enemyPosition[index] = continue;
newPosition;
enemyStunTime[index] = 0.0f;
enemySpin[index] = 0.0f;
enemyWalkTime[index] = 0.0f;
return;
} }
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; 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() void DrawEnemy()
{ {
for (int i = 0; i < NPC_COUNT; i++) for (int i = 0; i < NPC_COUNT; i++)
+1 -9
View File
@@ -12,7 +12,7 @@ float playerPitch = 0.0f;
float weaponBobTime = 0.0f; float weaponBobTime = 0.0f;
float weaponShootTime = 0.0f; float weaponShootTime = 0.0f;
const float moveSpeed = 7.0f; const float moveSpeed = 11.0f;
const float jumpForce = 9.0f; const float jumpForce = 9.0f;
const float gravity = 20.0f; const float gravity = 20.0f;
@@ -50,22 +50,17 @@ Camera3D GetPlayerCamera()
static void MovePlayer(Vector3 movement) static void MovePlayer(Vector3 movement)
{ {
// X separado de Z para poder deslizarse por las paredes.
Vector3 nextX = playerPosition; Vector3 nextX = playerPosition;
nextX.x += movement.x; nextX.x += movement.x;
if (!IsSolidAt(nextX, PLAYER_RADIUS)) if (!IsSolidAt(nextX, PLAYER_RADIUS))
{
playerPosition.x = nextX.x; playerPosition.x = nextX.x;
}
Vector3 nextZ = playerPosition; Vector3 nextZ = playerPosition;
nextZ.z += movement.z; nextZ.z += movement.z;
if (!IsSolidAt(nextZ, PLAYER_RADIUS)) if (!IsSolidAt(nextZ, PLAYER_RADIUS))
{
playerPosition.z = nextZ.z; playerPosition.z = nextZ.z;
}
} }
void UpdatePlayer(float deltaTime) void UpdatePlayer(float deltaTime)
@@ -150,9 +145,7 @@ void UpdatePlayer(float deltaTime)
verticalVelocity = 0.0f; verticalVelocity = 0.0f;
if (IsKeyPressed(KEY_SPACE)) if (IsKeyPressed(KEY_SPACE))
{
verticalVelocity = jumpForce; verticalVelocity = jumpForce;
}
} }
verticalVelocity -= gravity * deltaTime; verticalVelocity -= gravity * deltaTime;
@@ -166,7 +159,6 @@ void UpdatePlayer(float deltaTime)
verticalVelocity = 0.0f; verticalVelocity = 0.0f;
} }
// Colisión con el techo.
if (playerPosition.y + PLAYER_HEAD_Y > CEILING_Y) if (playerPosition.y + PLAYER_HEAD_Y > CEILING_Y)
{ {
playerPosition.y = playerPosition.y =