Etapa 1 inteira (logica com 100% de cobertura, cena 3D, toque, sons, bancada de desempenho, e2e) e a base da etapa 2 (mapa novo no Tiled, texturas, imagens dos itens, sons conferidos, golpe recortado e medido). Pagina /jogo fora do menu e do sitemap ate a medicao no celular.
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
/** Jogador automático para testes e medição: função pura do estado, sem memória própria. */
|
|
|
|
import { temLinhaDeVisao } from "./mapa";
|
|
import type { ArmaId, Entrada, Partida, Zumbi } from "./tipos";
|
|
import { JOGADOR, ZUMBIS } from "./valores";
|
|
|
|
const GIRO_MAXIMO = 0.12;
|
|
const INCLINACAO_MAXIMA = 0.05;
|
|
const ALTURA_DA_MIRA = 0.65;
|
|
const ALCANCE_DA_FACA = 1.3;
|
|
const ERRO_PARA_ATIRAR = 0.05;
|
|
const DISTANCIA_DE_RECUO = 4;
|
|
const RAIO_DA_MULTIDAO = 12;
|
|
const TAMANHO_DA_MULTIDAO = 3;
|
|
|
|
const PISTOLA: ArmaId = 0;
|
|
const AUTOMATICA: ArmaId = 1;
|
|
const FACA: ArmaId = 2;
|
|
|
|
type Alvo = { zumbi: Zumbi; vivos: Zumbi[]; visao: boolean };
|
|
|
|
function distanciaAte(p: Partida, zumbi: Zumbi): number {
|
|
return Math.hypot(zumbi.x - p.jogador.x, zumbi.z - p.jogador.z);
|
|
}
|
|
|
|
function escolherAlvo(p: Partida): Alvo | undefined {
|
|
const { x, z } = p.jogador;
|
|
const vivos = p.zumbis.filter((zumbi) => zumbi.estado !== "morrendo");
|
|
if (vivos.length === 0) return undefined;
|
|
const vistos = vivos.filter((zumbi) =>
|
|
temLinhaDeVisao(p.mapa, x, z, zumbi.x, zumbi.z),
|
|
);
|
|
const lista = vistos.length > 0 ? vistos : vivos;
|
|
const zumbi = lista.reduce((a, b) =>
|
|
distanciaAte(p, b) < distanciaAte(p, a) ? b : a,
|
|
);
|
|
return { zumbi, vivos, visao: vistos.length > 0 };
|
|
}
|
|
|
|
function limitar(valor: number, maximo: number): number {
|
|
return Math.max(-maximo, Math.min(maximo, valor));
|
|
}
|
|
|
|
function escolherArma(p: Partida, alvo: Alvo): ArmaId {
|
|
const { tipo } = alvo.zumbi;
|
|
if (distanciaAte(p, alvo.zumbi) - ZUMBIS[tipo].raio <= ALCANCE_DA_FACA) {
|
|
return FACA;
|
|
}
|
|
const perto = alvo.vivos.filter(
|
|
(zumbi) => distanciaAte(p, zumbi) <= RAIO_DA_MULTIDAO,
|
|
);
|
|
const exige =
|
|
perto.length >= TAMANHO_DA_MULTIDAO ||
|
|
tipo === "blindado" ||
|
|
tipo === "chefao";
|
|
return p.jogador.municao > 0 && exige ? AUTOMATICA : PISTOLA;
|
|
}
|
|
|
|
export function pilotar(p: Partida): Entrada {
|
|
const alvo = escolherAlvo(p);
|
|
if (!alvo) return { mover: [0, 0], olhar: [0, 0], atirar: false, arma: 0 };
|
|
const { zumbi } = alvo;
|
|
const { x, z, rumo, inclinacao } = p.jogador;
|
|
const distancia = distanciaAte(p, zumbi);
|
|
const desvio = Math.atan2(zumbi.x - x, zumbi.z - z) - rumo;
|
|
const erro = Math.atan2(Math.sin(desvio), Math.cos(desvio));
|
|
const altura = ZUMBIS[zumbi.tipo].altura * ALTURA_DA_MIRA - JOGADOR.olhos;
|
|
const subida = Math.atan2(altura, distancia) - inclinacao;
|
|
return {
|
|
mover: [0, distancia < DISTANCIA_DE_RECUO ? -1 : 0],
|
|
olhar: [limitar(erro, GIRO_MAXIMO), limitar(subida, INCLINACAO_MAXIMA)],
|
|
atirar: Math.abs(erro) < ERRO_PARA_ATIRAR && alvo.visao,
|
|
arma: escolherArma(p, alvo),
|
|
};
|
|
}
|