From f9f1c5c3ec30a3dcbd10edc4dac3ddab0f3d988f Mon Sep 17 00:00:00 2001 From: TEC-GO Date: Sat, 26 Sep 2026 18:19:51 -0300 Subject: [PATCH] A4B1 Relase 0.2 --- source/A4B1/main.cpp | 4056 ++++++++++++++++++++++++++++++------------ 1 file changed, 2960 insertions(+), 1096 deletions(-) diff --git a/source/A4B1/main.cpp b/source/A4B1/main.cpp index 1ff80e3..01b3886 100644 --- a/source/A4B1/main.cpp +++ b/source/A4B1/main.cpp @@ -1,15 +1,78 @@ #include "raylib.h" -#include -#include + #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; }; @@ -19,19 +82,38 @@ struct Municion 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 municion; + + int arma; + + bool recargando; + float tiempoRecarga; + float tiempoDisparo; + float tiempoAtascado; + + Vector2 ultimaPosicion; }; struct Pared @@ -39,200 +121,230 @@ struct Pared Rectangle rectangulo; }; -const float MUNDO_ANCHO = 1000.0f; -const float MUNDO_ALTO = 650.0f; -const float BORDE = 35.0f; +// ============================================================ +// FUNCIONES BÁSICAS +// ============================================================ -const float RADIO_JUGADOR = 18.0f; -const float RADIO_ENEMIGO = 20.0f; - -const float VELOCIDAD_JUGADOR = 300.0f; -const float VELOCIDAD_IA = 175.0f; - -const float VELOCIDAD_BALA = 720.0f; -const float DAÑO_BALA = 25.0f; - -const float RADIO_VISION = 235.0f; - -float Limitar(float valor, float minimo, float maximo) +float Distancia(Vector2 a, Vector2 b) { - if (valor < minimo) - return minimo; + float dx = a.x - b.x; + float dy = a.y - b.y; - if (valor > maximo) - return maximo; - - return valor; + return std::sqrt(dx * dx + dy * dy); } Vector2 Normalizar(Vector2 v) { - float largo = sqrtf(v.x * v.x + v.y * v.y); + float longitud = + std::sqrt(v.x * v.x + v.y * v.y); - if (largo <= 0.0001f) - return {0.0f, 0.0f}; + if (longitud <= 0.0001f) + return {0, 0}; return { - v.x / largo, - v.y / largo + v.x / longitud, + v.y / longitud }; } -float Distancia(Vector2 a, Vector2 b) +Vector2 Multiplicar(Vector2 v, float valor) { - float x = a.x - b.x; - float y = a.y - b.y; - - return sqrtf(x * x + y * y); + return { + v.x * valor, + v.y * valor + }; } -bool CirculoCirculo( - Vector2 a, - float radioA, - Vector2 b, - float radioB) +// ============================================================ +// ARMAS +// ============================================================ + +const char* NombreArma(TipoArma tipo) { - return Distancia(a, b) < radioA + radioB; + switch (tipo) + { + case ARMA_RAPIDA: + return "RAPIDA"; + + case ARMA_EQUILIBRADA: + return "EQUILIBRADA"; + + case ARMA_PESADA: + return "PESADA"; + + case ARMA_PRECISA: + return "PRECISA"; + } + + return "???"; } -bool CirculoRectangulo( +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) + Rectangle rect +) { - float puntoX = Limitar( - centro.x, - rect.x, - rect.x + rect.width - ); - - float puntoY = Limitar( - centro.y, - rect.y, - rect.y + rect.height - ); - - float dx = centro.x - puntoX; - float dy = centro.y - puntoY; - - return dx * dx + dy * dy < radio * radio; -} - -void RebotarBorde(Entidad& entidad) -{ - float izquierda = BORDE + entidad.radio; - float derecha = MUNDO_ANCHO - BORDE - entidad.radio; - float arriba = BORDE + entidad.radio; - float abajo = MUNDO_ALTO - BORDE - entidad.radio; - - if (entidad.posicion.x < izquierda) - { - entidad.posicion.x = izquierda; - entidad.velocidad.x = fabsf(entidad.velocidad.x); - } - - if (entidad.posicion.x > derecha) - { - entidad.posicion.x = derecha; - entidad.velocidad.x = -fabsf(entidad.velocidad.x); - } - - if (entidad.posicion.y < arriba) - { - entidad.posicion.y = arriba; - entidad.velocidad.y = fabsf(entidad.velocidad.y); - } - - if (entidad.posicion.y > abajo) - { - entidad.posicion.y = abajo; - entidad.velocidad.y = -fabsf(entidad.velocidad.y); - } -} - -void RebotarPared( - Entidad& entidad, - const std::vector& paredes) -{ - for (const Pared& pared : paredes) - { - if (!CirculoRectangulo( - entidad.posicion, - entidad.radio, - pared.rectangulo)) - { - continue; - } - - float izquierda = pared.rectangulo.x; - float derecha = pared.rectangulo.x + - pared.rectangulo.width; - - float arriba = pared.rectangulo.y; - float abajo = pared.rectangulo.y + - pared.rectangulo.height; - - float dIzquierda = - fabsf(entidad.posicion.x - izquierda); - - float dDerecha = - fabsf(entidad.posicion.x - derecha); - - float dArriba = - fabsf(entidad.posicion.y - arriba); - - float dAbajo = - fabsf(entidad.posicion.y - abajo); - - float menor = std::min( - std::min(dIzquierda, dDerecha), - std::min(dArriba, dAbajo) + float cercanoX = + std::clamp( + centro.x, + rect.x, + rect.x + rect.width ); - if (menor == dIzquierda) - { - entidad.posicion.x = - izquierda - entidad.radio; + float cercanoY = + std::clamp( + centro.y, + rect.y, + rect.y + rect.height + ); - entidad.velocidad.x = - -fabsf(entidad.velocidad.x); - } - else if (menor == dDerecha) - { - entidad.posicion.x = - derecha + entidad.radio; + float dx = centro.x - cercanoX; + float dy = centro.y - cercanoY; - entidad.velocidad.x = - fabsf(entidad.velocidad.x); - } - else if (menor == dArriba) - { - entidad.posicion.y = - arriba - entidad.radio; - - entidad.velocidad.y = - -fabsf(entidad.velocidad.y); - } - else - { - entidad.posicion.y = - abajo + entidad.radio; - - entidad.velocidad.y = - fabsf(entidad.velocidad.y); - } - } + return dx * dx + dy * dy <= radio * radio; } -bool TocarPared( +bool EntidadChocaPared( Vector2 posicion, - const std::vector& paredes) + float radio, + const std::vector& paredes +) { - for (const Pared& pared : paredes) + for (const auto& pared : paredes) { - if (CheckCollisionPointRec( + if ( + CirculoChocaRectangulo( posicion, - pared.rectangulo)) + radio, + pared.rectangulo + ) + ) { return true; } @@ -241,595 +353,876 @@ bool TocarPared( return false; } -void MoverBala( - Bala& bala, - float dt, - const std::vector& paredes) +bool PuntoDentroRectangulo( + Vector2 punto, + Rectangle rect +) { - if (!bala.activa) - return; - - Vector2 siguiente = { - bala.posicion.x + - bala.direccion.x * - bala.velocidad * - dt, - - bala.posicion.y + - bala.direccion.y * - bala.velocidad * - dt - }; - - bool golpeo = false; - - if (siguiente.x < BORDE) - { - siguiente.x = BORDE; - - if (bala.rebotes == 0) - { - bala.direccion.x *= -1.0f; - bala.rebotes++; - } - else - { - bala.activa = false; - } - - golpeo = true; - } - - if (siguiente.x > MUNDO_ANCHO - BORDE) - { - siguiente.x = MUNDO_ANCHO - BORDE; - - if (bala.rebotes == 0) - { - bala.direccion.x *= -1.0f; - bala.rebotes++; - } - else - { - bala.activa = false; - } - - golpeo = true; - } - - if (siguiente.y < BORDE) - { - siguiente.y = BORDE; - - if (bala.rebotes == 0) - { - bala.direccion.y *= -1.0f; - bala.rebotes++; - } - else - { - bala.activa = false; - } - - golpeo = true; - } - - if (siguiente.y > MUNDO_ALTO - BORDE) - { - siguiente.y = MUNDO_ALTO - BORDE; - - if (bala.rebotes == 0) - { - bala.direccion.y *= -1.0f; - bala.rebotes++; - } - else - { - bala.activa = false; - } - - golpeo = true; - } - - if (!bala.activa) - return; - - if (!golpeo) - { - for (const Pared& pared : paredes) - { - if (!CheckCollisionPointRec( - siguiente, - pared.rectangulo)) - { - continue; - } - - if (bala.rebotes == 0) - { - float izquierda = pared.rectangulo.x; - float derecha = - pared.rectangulo.x + - pared.rectangulo.width; - - float arriba = pared.rectangulo.y; - float abajo = - pared.rectangulo.y + - pared.rectangulo.height; - - float dxIzquierda = - fabsf(siguiente.x - izquierda); - - float dxDerecha = - fabsf(siguiente.x - derecha); - - float dyArriba = - fabsf(siguiente.y - arriba); - - float dyAbajo = - fabsf(siguiente.y - abajo); - - float menorX = - std::min(dxIzquierda, dxDerecha); - - float menorY = - std::min(dyArriba, dyAbajo); - - if (menorX < menorY) - bala.direccion.x *= -1.0f; - else - bala.direccion.y *= -1.0f; - - bala.rebotes++; - - siguiente.x += - bala.direccion.x * 3.0f; - - siguiente.y += - bala.direccion.y * 3.0f; - } - else - { - bala.activa = false; - } - - break; - } - } - - if (bala.activa) - bala.posicion = siguiente; + return CheckCollisionPointRec( + punto, + rect + ); } -int BuscarObjetivo( - int indice, - const std::vector& entidades) +bool LineaChocaRectangulo( + Vector2 inicio, + Vector2 fin, + Rectangle rect +) { - int objetivo = -1; - float menorDistancia = 999999.0f; + const int pasos = 50; - for (int i = 0; - i < (int)entidades.size(); - i++) + 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; - if (!entidades[i].viva) + const Entidad& otra = + entidades[i]; + + if (!otra.viva) continue; - if (entidades[i].equipo == - entidades[indice].equipo) - { + if (otra.equipo != yo.equipo) continue; - } float distancia = Distancia( - entidades[indice].posicion, - entidades[i].posicion + yo.posicion, + otra.posicion ); - if (distancia < menorDistancia) + if ( + distancia > 0 && + distancia < 110 + ) { - menorDistancia = distancia; - objetivo = i; + 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 objetivo; + 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& balas) + std::vector& armas, + std::vector& balas +) { if (!entidad.viva) return; - if (entidad.municion <= 0) + if (entidad.arma < 0) return; - Bala bala; + 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 * 27.0f, + entidad.direccion.x * + (entidad.radio + 9), entidad.posicion.y + - entidad.direccion.y * 27.0f + entidad.direccion.y * + (entidad.radio + 9) }; bala.direccion = - Normalizar(entidad.direccion); + Normalizar( + entidad.direccion + ); bala.velocidad = - VELOCIDAD_BALA; + 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.municion--; + entidad.tiempoDisparo = + CadenciaArma( + arma.tipo + ); } -bool HayMunicion( - const std::vector& municiones) -{ - for (const Municion& municion : municiones) - { - if (municion.activa) - return true; - } +// ============================================================ +// RECARGA +// ============================================================ - return false; -} - -void CrearMunicion( - std::vector& municiones, - const std::vector& paredes, - const std::vector& entidades) -{ - if ((int)municiones.size() >= 2) - return; - - Vector2 posicion; - - bool valida = false; - - for (int intento = 0; - intento < 100 && !valida; - intento++) - { - posicion = { - (float)GetRandomValue( - (int)BORDE + 25, - (int)MUNDO_ANCHO - - (int)BORDE - 25 - ), - - (float)GetRandomValue( - (int)BORDE + 25, - (int)MUNDO_ALTO - - (int)BORDE - 25 - ) - }; - - valida = - !TocarPared( - posicion, - paredes - ); - - if (!valida) - continue; - - valida = false; - } - - if (!valida) - return; - - for (const Entidad& entidad : entidades) - { - if (!entidad.viva) - continue; - - if (Distancia( - posicion, - entidad.posicion) < 100.0f) - { - return; - } - } - - municiones.push_back({ - posicion, - true - }); -} - -void DibujarEntidad( - const Entidad& entidad, - float escalaX, - float escalaY, - float escala) +void IniciarRecarga( + Entidad& entidad, + std::vector& armas +) { if (!entidad.viva) return; - Vector2 posicion = { - entidad.posicion.x * escalaX, - entidad.posicion.y * escalaY - }; + if (entidad.arma < 0) + return; - Color color = - entidad.equipo == 0 - ? BLUE - : RED; + if ( + entidad.arma >= + static_cast(armas.size()) + ) + { + entidad.arma = -1; + return; + } - DrawCircleV( - posicion, - entidad.radio * escala, - color - ); + Arma& arma = + armas[entidad.arma]; - Vector2 direccion = { - posicion.x + - entidad.direccion.x * - 25.0f * - escala, + if (entidad.recargando) + return; - posicion.y + - entidad.direccion.y * - 25.0f * - escala - }; + if ( + arma.balas >= + CapacidadArma(arma.tipo) + ) + { + return; + } - DrawLineEx( - posicion, - direccion, - 3.0f * escala, - WHITE - ); + // Nunca se acaban. + arma.cartuchos = 999999; - float vidaAncho = - 42.0f * escala; - - float vidaAlto = - 5.0f * escala; - - float vidaX = - posicion.x - - vidaAncho * 0.5f; - - float vidaY = - posicion.y - - entidad.radio * escala - - 13.0f * escala; - - DrawRectangle( - (int)vidaX, - (int)vidaY, - (int)vidaAncho, - (int)vidaAlto, - DARKGRAY - ); - - DrawRectangle( - (int)vidaX, - (int)vidaY, - (int)( - vidaAncho * - (entidad.vida / - entidad.vidaMaxima) - ), - (int)vidaAlto, - GREEN - ); + entidad.recargando = true; + entidad.tiempoRecarga = + TIEMPO_RECARGA; } -int main() +void ActualizarRecarga( + Entidad& entidad, + std::vector& armas, + float dt +) { - SetConfigFlags( - FLAG_WINDOW_RESIZABLE | - FLAG_VSYNC_HINT - ); + if (!entidad.recargando) + return; - InitWindow( - 1100, - 750, - "A4B1 0.1" - ); + entidad.tiempoRecarga -= dt; - SetTargetFPS(120); - - SetRandomSeed( - (unsigned int)GetTime() - ); - - std::vector paredes = { - {{280.0f, 150.0f, 180.0f, 14.0f}}, - {{540.0f, 150.0f, 180.0f, 14.0f}}, - {{280.0f, 486.0f, 180.0f, 14.0f}}, - {{540.0f, 486.0f, 180.0f, 14.0f}}, - {{240.0f, 230.0f, 14.0f, 150.0f}}, - {{746.0f, 230.0f, 14.0f, 150.0f}}, - {{450.0f, 245.0f, 100.0f, 14.0f}}, - {{450.0f, 391.0f, 100.0f, 14.0f}} - }; - - std::vector entidades = { - { - {220.0f, 325.0f}, - {0.0f, 0.0f}, - {1.0f, 0.0f}, - RADIO_JUGADOR, - 100.0f, - 100.0f, - 0, - true, - true, - 5, - 0.0f - }, - - { - {180.0f, 500.0f}, - {0.0f, 0.0f}, - {1.0f, 0.0f}, - RADIO_ENEMIGO, - 100.0f, - 100.0f, - 0, - false, - true, - 5, - 0.0f - }, - - { - {780.0f, 180.0f}, - {0.0f, 0.0f}, - {-1.0f, 0.0f}, - RADIO_ENEMIGO, - 100.0f, - 100.0f, - 1, - false, - true, - 5, - 0.0f - }, - - { - {780.0f, 470.0f}, - {0.0f, 0.0f}, - {-1.0f, 0.0f}, - RADIO_ENEMIGO, - 100.0f, - 100.0f, - 1, - false, - true, - 5, - 0.0f - } - }; - - std::vector balas; - std::vector municiones; - - float tiempoAparicionMunicion = 0.0f; - int impactos = 0; - - while (!WindowShouldClose()) + if (entidad.tiempoRecarga <= 0) { - float dt = GetFrameTime(); + entidad.recargando = false; - if (IsKeyPressed(KEY_F11)) - ToggleFullscreen(); - - int anchoPantalla = - GetScreenWidth(); - - int altoPantalla = - GetScreenHeight(); - - float escalaX = - anchoPantalla / - MUNDO_ANCHO; - - float escalaY = - altoPantalla / - MUNDO_ALTO; - - float escala = - std::min( - escalaX, - escalaY - ); - - Entidad& jugador = - entidades[0]; - - Vector2 movimiento = { - 0.0f, - 0.0f - }; - - if (IsKeyDown(KEY_W)) - movimiento.y -= 1.0f; - - if (IsKeyDown(KEY_S)) - movimiento.y += 1.0f; - - if (IsKeyDown(KEY_A)) - movimiento.x -= 1.0f; - - if (IsKeyDown(KEY_D)) - movimiento.x += 1.0f; - - movimiento = - Normalizar(movimiento); - - if (movimiento.x != 0.0f || - movimiento.y != 0.0f) + if ( + entidad.arma >= 0 && + entidad.arma < + static_cast(armas.size()) + ) { - jugador.direccion = - movimiento; - } + Arma& arma = + armas[entidad.arma]; - jugador.velocidad = { - movimiento.x * - VELOCIDAD_JUGADOR, - - movimiento.y * - VELOCIDAD_JUGADOR - }; - - if (jugador.viva) - { - jugador.posicion.x += - jugador.velocidad.x * - dt; - - jugador.posicion.y += - jugador.velocidad.y * - dt; - - RebotarBorde(jugador); - RebotarPared( - jugador, - paredes - ); - } - - jugador.tiempoDisparo -= dt; - - if (jugador.viva && - IsKeyPressed(KEY_SPACE) && - jugador.tiempoDisparo <= 0.0f) - { - if (jugador.municion > 0) - { - Disparar( - jugador, - balas + arma.balas = + CapacidadArma( + arma.tipo ); - jugador.tiempoDisparo = - 0.18f; + 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; } } - for (int i = 1; - i < (int)entidades.size(); - i++) + 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]; @@ -837,471 +1230,1942 @@ int main() if (!entidad.viva) continue; - int objetivo = - BuscarObjetivo( - i, - entidades - ); - - if (objetivo >= 0) - { - Vector2 direccion = - Normalizar({ - entidades[objetivo].posicion.x - - entidad.posicion.x, - - entidades[objetivo].posicion.y - - entidad.posicion.y - }); - - entidad.direccion = - direccion; - - float distancia = - Distancia( - entidad.posicion, - entidades[objetivo].posicion - ); - - Vector2 movimientoIA = - direccion; - - if (distancia < 250.0f) - { - movimientoIA = { - -direccion.y, - direccion.x - }; - } - - entidad.velocidad = { - movimientoIA.x * - VELOCIDAD_IA, - - movimientoIA.y * - VELOCIDAD_IA - }; - - entidad.tiempoDisparo -= dt; - - if (entidad.tiempoDisparo <= 0.0f && - entidad.municion > 0) - { - Disparar( - entidad, - balas - ); - - entidad.tiempoDisparo = - 0.75f; - } - } - - entidad.posicion.x += - entidad.velocidad.x * - dt; - - entidad.posicion.y += - entidad.velocidad.y * - dt; - - RebotarBorde( - entidad - ); - - RebotarPared( - entidad, - paredes - ); - } - - for (Bala& bala : balas) - { - if (!bala.activa) + if (entidad.equipo == bala.equipo) continue; - MoverBala( - bala, - dt, - paredes - ); - - if (!bala.activa) - continue; - - for (int i = 0; - i < (int)entidades.size(); - i++) + if ( + Distancia( + bala.posicion, + entidad.posicion + ) < + entidad.radio + 5.0f + ) { - Entidad& entidad = - entidades[i]; - - if (!entidad.viva) - continue; - - if (entidad.equipo == - bala.equipo) - continue; - - if (!CirculoCirculo( - bala.posicion, - 6.0f, - entidad.posicion, - entidad.radio)) - { - continue; - } - - entidad.vida -= - DAÑO_BALA; - - impactos++; - - Vector2 empuje = - bala.direccion; - - entidad.posicion.x += - empuje.x * 10.0f; - - entidad.posicion.y += - empuje.y * 10.0f; - - if (entidad.vida <= 0.0f) - { - entidad.vida = 0.0f; - entidad.viva = false; - entidad.velocidad = { - 0.0f, - 0.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() - ); - - tiempoAparicionMunicion -= - dt; - - if (tiempoAparicionMunicion <= 0.0f) - { - CrearMunicion( - municiones, - paredes, - entidades - ); - - tiempoAparicionMunicion = - 4.0f; - } - - for (Municion& municion : - municiones) - { - if (!municion.activa) - continue; - - if (!jugador.viva) - continue; - - if (Distancia( - jugador.posicion, - municion.posicion - ) < 28.0f) + balas.erase( + std::remove_if( + balas.begin(), + balas.end(), + [](const Bala& bala) { - jugador.municion += 5; - municion.activa = false; + return !bala.activa; } - } + ), + balas.end() + ); +} - municiones.erase( - std::remove_if( - municiones.begin(), - municiones.end(), - [](const Municion& municion) - { - return !municion.activa; - } - ), - municiones.end() - ); +// ============================================================ +// PICKUPS +// ============================================================ - BeginDrawing(); - - ClearBackground({ - 0, - 0, - 0, - 255 - }); - - Vector2 jugadorPantalla = { - jugador.posicion.x * - escalaX, - - jugador.posicion.y * - escalaY - }; - - for (const Pared& pared : - paredes) - { - Vector2 centro = { - pared.rectangulo.x + - pared.rectangulo.width * - 0.5f, - - pared.rectangulo.y + - pared.rectangulo.height * - 0.5f - }; - - if (Distancia( - centro, - jugador.posicion - ) > RADIO_VISION + 180.0f) - { - continue; - } - - Rectangle r = { - pared.rectangulo.x * - escalaX, - - pared.rectangulo.y * - escalaY, - - pared.rectangulo.width * - escalaX, - - pared.rectangulo.height * - escalaY - }; - - DrawRectangleRounded( - r, - 0.3f, - 8, - { - 45, - 45, - 52, - 255 - } - ); - - DrawRectangleRoundedLines( - r, - 0.3f, - 8, - { - 120, - 120, - 130, - 255 - } - ); - } - - Rectangle borde = { - BORDE * escalaX, - BORDE * escalaY, - - (MUNDO_ANCHO - - BORDE * 2.0f) * - escalaX, - - (MUNDO_ALTO - - BORDE * 2.0f) * - escalaY - }; - - if (Distancia( - jugador.posicion, - { - MUNDO_ANCHO * 0.5f, - MUNDO_ALTO * 0.5f - } - ) < RADIO_VISION + 500.0f) - { - DrawRectangleRoundedLines( - borde, - 0.08f, - 16, - WHITE - ); - } - - for (const Municion& municion : - municiones) - { - if (!municion.activa) - continue; - - if (Distancia( - jugador.posicion, - municion.posicion - ) > RADIO_VISION) - continue; - - Vector2 posicion = { - municion.posicion.x * - escalaX, - - municion.posicion.y * - escalaY - }; - - DrawRectangle( - (int)posicion.x - 9, - (int)posicion.y - 9, - 18, - 18, - YELLOW - ); - - DrawText( - "+5", - (int)posicion.x - 8, - (int)posicion.y - 7, - 14, - BLACK - ); - } - - for (const Bala& bala : - balas) - { - if (!bala.activa) - continue; - - if (Distancia( - jugador.posicion, - bala.posicion - ) > RADIO_VISION) - continue; - - Vector2 posicion = { - bala.posicion.x * - escalaX, - - bala.posicion.y * - escalaY - }; - - Color color = - bala.equipo == 0 - ? BLUE - : RED; - - DrawCircleV( +bool PosicionLibre( + Vector2 posicion, + const std::vector& paredes +) +{ + for (const auto& pared : paredes) + { + if ( + CirculoChocaRectangulo( posicion, - 5.0f * escala, - color - ); + 18.0f, + pared.rectangulo + ) + ) + { + return false; } + } - for (const Entidad& entidad : - entidades) + 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( - jugador.posicion, + if ( + Distancia( + botiquin.posicion, entidad.posicion - ) > RADIO_VISION) + ) < + 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; - DibujarEntidad( - entidad, - escalaX, - escalaY, - escala + 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 ); } + } - DrawCircle( - (int)jugadorPantalla.x, - (int)jugadorPantalla.y, - RADIO_VISION * escala, - Fade(BLACK, 0.0f) + if (ia.arma < 0) + { + Vector2 direccion = { + ia.equipo == EQUIPO_AZUL + ? 1.0f + : -1.0f, + 0 + }; + + MoverEntidad( + ia, + Multiplicar( + direccion, + VELOCIDAD_IA * dt + ), + paredes ); - DrawRectangle( + 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, - 15, - 270, - 145, - Fade(BLACK, 0.75f) + BLACK ); DrawText( - TextFormat( - "VIDA: %d", - (int)jugador.vida - ), - 28, - 25, - 22, + NombreArma(arma.tipo), + static_cast(arma.posicion.x - 35), + static_cast(arma.posicion.y + 18), + 10, WHITE ); + } +} - DrawText( - TextFormat( - "BALAS: %d", - jugador.municion - ), - 28, - 53, - 22, - YELLOW - ); +// ============================================================ +// BOTIQUINES +// ============================================================ - DrawText( - TextFormat( - "IMPACTOS: %d", - impactos - ), - 28, - 81, - 19, - WHITE - ); +void DibujarBotiquines( + const std::vector& botiquines +) +{ + for (const auto& botiquin : botiquines) + { + if (!botiquin.activa) + continue; - DrawText( - TextFormat( - "FPS: %d", - GetFPS() - ), - 28, - 107, - 18, + 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( - "AZUL 2 VS ROJO 2", - 28, - 132, + "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 +// ============================================================ + +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(); }