Files
Attacks-for-Bombs-1/source/A4B1/main.cpp
T
2026-09-26 18:19:51 -03:00

3175 lines
60 KiB
C++

#include "raylib.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <vector>
// ============================================================
// 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<Pared>& 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<float>(i) /
static_cast<float>(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<Pared>& paredes
)
{
for (const auto& pared : paredes)
{
if (
LineaChocaRectangulo(
a,
b,
pared.rectangulo
)
)
{
return true;
}
}
return false;
}
// ============================================================
// MAPA
// ============================================================
std::vector<Pared> CrearMapa()
{
std::vector<Pared> 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<Pared>& 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<Entidad>& entidades
)
{
Vector2 fuerza = {0, 0};
const Entidad& yo =
entidades[indice];
for (
int i = 0;
i < static_cast<int>(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<Pared>& 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<Entidad>& entidades,
const std::vector<Pared>& paredes
)
{
const Entidad& yo =
entidades[indice];
int mejor = -1;
float mejorDistancia = 999999.0f;
for (
int i = 0;
i < static_cast<int>(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<Arma>& armas
)
{
int mejor = -1;
float mejorDistancia = 75.0f;
for (
int i = 0;
i < static_cast<int>(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<Arma>& armas
)
{
if (
indiceArma < 0 ||
indiceArma >= static_cast<int>(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<Arma>& armas,
std::vector<Bala>& balas
)
{
if (!entidad.viva)
return;
if (entidad.arma < 0)
return;
if (
entidad.arma >=
static_cast<int>(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<Arma>& armas
)
{
if (!entidad.viva)
return;
if (entidad.arma < 0)
return;
if (
entidad.arma >=
static_cast<int>(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<Arma>& 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<int>(armas.size())
)
{
Arma& arma =
armas[entidad.arma];
arma.balas =
CapacidadArma(
arma.tipo
);
arma.cartuchos = 999999;
}
}
}
// ============================================================
// MARCAS DE MUERTE
// ============================================================
void CrearMarcaMuerte(
std::vector<MarcaMuerte>& marcas,
Vector2 posicion
)
{
marcas.push_back({
posicion,
0.0f,
true
});
}
// ============================================================
// BALAS
// ============================================================
void MoverBalas(
std::vector<Bala>& balas,
const std::vector<Pared>& paredes,
std::vector<Entidad>& entidades,
std::vector<MarcaMuerte>& marcas,
std::vector<Arma>& 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<int>(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<int>(
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<Pared>& paredes
)
{
for (const auto& pared : paredes)
{
if (
CirculoChocaRectangulo(
posicion,
18.0f,
pared.rectangulo
)
)
{
return false;
}
}
return true;
}
void CrearBotiquin(
std::vector<Botiquin>& botiquines,
const std::vector<Pared>& paredes
)
{
for (int intento = 0; intento < 100; intento++)
{
Vector2 posicion = {
static_cast<float>(
120 + rand() % 2560
),
static_cast<float>(
120 + rand() % 1660
)
};
if (
PosicionLibre(
posicion,
paredes
)
)
{
botiquines.push_back({
posicion,
true
});
return;
}
}
}
void CrearMunicion(
std::vector<Municion>& municiones,
const std::vector<Pared>& paredes
)
{
for (int intento = 0; intento < 100; intento++)
{
Vector2 posicion = {
static_cast<float>(
120 + rand() % 2560
),
static_cast<float>(
120 + rand() % 1660
)
};
if (
PosicionLibre(
posicion,
paredes
)
)
{
municiones.push_back({
posicion,
true
});
return;
}
}
}
void ActualizarBotiquines(
std::vector<Botiquin>& botiquines,
std::vector<Entidad>& 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<Municion>& municiones,
std::vector<Entidad>& entidades,
std::vector<Arma>& 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<int>(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<Entidad>& entidades,
std::vector<Arma>& armas,
std::vector<Bala>& balas,
const std::vector<Pared>& 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<int>(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<Pared>& paredes
)
{
DrawRectangle(
0,
0,
static_cast<int>(MUNDO_ANCHO),
static_cast<int>(MUNDO_ALTO),
Color{
47,
49,
54,
255
}
);
for (
int x = 0;
x < static_cast<int>(MUNDO_ANCHO);
x += 100
)
{
DrawLine(
x,
0,
x,
static_cast<int>(MUNDO_ALTO),
Color{
55,
57,
62,
255
}
);
}
for (
int y = 0;
y < static_cast<int>(MUNDO_ALTO);
y += 100
)
{
DrawLine(
0,
y,
static_cast<int>(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<Arma>& armas
)
{
for (const Arma& arma : armas)
{
if (!arma.activa)
continue;
if (!arma.enSuelo)
continue;
DrawCircleV(
arma.posicion,
13,
ColorArma(arma.tipo)
);
DrawCircleLines(
static_cast<int>(arma.posicion.x),
static_cast<int>(arma.posicion.y),
15,
BLACK
);
DrawText(
NombreArma(arma.tipo),
static_cast<int>(arma.posicion.x - 35),
static_cast<int>(arma.posicion.y + 18),
10,
WHITE
);
}
}
// ============================================================
// BOTIQUINES
// ============================================================
void DibujarBotiquines(
const std::vector<Botiquin>& botiquines
)
{
for (const auto& botiquin : botiquines)
{
if (!botiquin.activa)
continue;
DrawCircleV(
botiquin.posicion,
15,
GREEN
);
DrawRectangle(
static_cast<int>(botiquin.posicion.x - 4),
static_cast<int>(botiquin.posicion.y - 10),
8,
20,
WHITE
);
DrawRectangle(
static_cast<int>(botiquin.posicion.x - 10),
static_cast<int>(botiquin.posicion.y - 4),
20,
8,
WHITE
);
}
}
// ============================================================
// MUNICIÓN
// ============================================================
void DibujarMunicion(
const std::vector<Municion>& municiones
)
{
for (const auto& municion : municiones)
{
if (!municion.activa)
continue;
DrawRectangle(
static_cast<int>(municion.posicion.x - 13),
static_cast<int>(municion.posicion.y - 10),
26,
20,
GOLD
);
DrawRectangleLines(
static_cast<int>(municion.posicion.x - 13),
static_cast<int>(municion.posicion.y - 10),
26,
20,
BLACK
);
DrawText(
"AM",
static_cast<int>(municion.posicion.x - 8),
static_cast<int>(municion.posicion.y - 7),
12,
BLACK
);
}
}
// ============================================================
// ENTIDADES
// ============================================================
void DibujarEntidades(
const std::vector<Entidad>& 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<int>(entidad.posicion.x),
static_cast<int>(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<int>(entidad.posicion.x - 25),
static_cast<int>(entidad.posicion.y - 34),
50,
6,
DARKGRAY
);
DrawRectangle(
static_cast<int>(entidad.posicion.x - 25),
static_cast<int>(entidad.posicion.y - 34),
static_cast<int>(50 * porcentaje),
6,
GREEN
);
if (entidad.jugador)
{
DrawCircleLines(
static_cast<int>(entidad.posicion.x),
static_cast<int>(entidad.posicion.y),
entidad.radio + 5,
WHITE
);
}
}
}
// ============================================================
// MINIMAPA
// ============================================================
void DibujarMinimapa(
const std::vector<Pared>& paredes,
const std::vector<Entidad>& entidades,
const std::vector<MarcaMuerte>& 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<float>(mapaAncho) /
MUNDO_ANCHO;
float escalaY =
static_cast<float>(mapaAlto) /
MUNDO_ALTO;
// Paredes
for (const auto& pared : paredes)
{
int x =
mapaX +
static_cast<int>(
pared.rectangulo.x * escalaX
);
int y =
mapaY +
static_cast<int>(
pared.rectangulo.y * escalaY
);
int w =
std::max(
1,
static_cast<int>(
pared.rectangulo.width *
escalaX
)
);
int h =
std::max(
1,
static_cast<int>(
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<int>(
marca.posicion.x * escalaX
);
int y =
mapaY +
static_cast<int>(
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<int>(
entidad.posicion.x * escalaX
);
int y =
mapaY +
static_cast<int>(
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<Arma>& 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<int>(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
// ============================================================
int main()
{
srand(
static_cast<unsigned int>(
time(nullptr)
)
);
InitWindow(
PANTALLA_ANCHO,
PANTALLA_ALTO,
"A4B1 0.0.2"
);
SetTargetFPS(60);
// ========================================================
// MAPA
// ========================================================
std::vector<Pared> paredes =
CrearMapa();
// ========================================================
// ARMAS DEL SUELO
// ========================================================
std::vector<Arma> 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<Entidad> 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<int>(armas.size()) - 1;
armas.push_back(
CrearArma(
ARMA_RAPIDA,
{0, 0},
false
)
);
entidades[1].arma =
static_cast<int>(armas.size()) - 1;
armas.push_back(
CrearArma(
ARMA_PESADA,
{0, 0},
false
)
);
entidades[2].arma =
static_cast<int>(armas.size()) - 1;
armas.push_back(
CrearArma(
ARMA_PRECISA,
{0, 0},
false
)
);
entidades[3].arma =
static_cast<int>(armas.size()) - 1;
armas.push_back(
CrearArma(
ARMA_EQUILIBRADA,
{0, 0},
false
)
);
entidades[4].arma =
static_cast<int>(armas.size()) - 1;
armas.push_back(
CrearArma(
ARMA_PESADA,
{0, 0},
false
)
);
entidades[5].arma =
static_cast<int>(armas.size()) - 1;
// ========================================================
// BALAS
// ========================================================
std::vector<Bala> balas;
// ========================================================
// BOTIQUINES
// ========================================================
std::vector<Botiquin> botiquines;
for (int i = 0; i < 5; i++)
{
CrearBotiquin(
botiquines,
paredes
);
}
// ========================================================
// MUNICIÓN
// ========================================================
std::vector<Municion> municiones;
for (int i = 0; i < 5; i++)
{
CrearMunicion(
municiones,
paredes
);
}
// ========================================================
// MARCAS DE MUERTE
// ========================================================
std::vector<MarcaMuerte> 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<int>(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;
}