feat(jogo): expediente dos mortos, jogo de zumbis em primeira pessoa
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.
This commit is contained in:
1 parent
c7d09d63d4
commit
67b32fcab3
118 files changed
+16730
No files matched your search
@@ -0,0 +1,164 @@
|
||||
/** Integra a lógica do jogo: cria a partida e avança um passo por vez. Sem three, React nem DOM. */
|
||||
|
||||
import { atirar, recolherCaixas, resfriar, trocarArma } from "./armas";
|
||||
import { avancarDrones, criarDrone } from "./drones";
|
||||
import { registrarResolvido } from "./feed";
|
||||
import { escolherHordas, montarFila } from "./hordas";
|
||||
import { campoDeFluxo, lerMapa, moverComColisao } from "./mapa";
|
||||
import { semear } from "./rng";
|
||||
import type { Dificuldade, Entrada, Fase, Partida } from "./tipos";
|
||||
import {
|
||||
ESCRITORIO,
|
||||
HORDAS,
|
||||
JOGADOR,
|
||||
MUNICAO,
|
||||
PASSO,
|
||||
TETO_A_VISTA,
|
||||
} from "./valores";
|
||||
import { avancarZumbis, nascer } from "./zumbis";
|
||||
|
||||
const HORDA_DO_CHEFAO = 3;
|
||||
|
||||
export function criarPartida(opcoes: {
|
||||
semente: number;
|
||||
dificuldade: Dificuldade;
|
||||
problemaId: string;
|
||||
tetoAVista?: number;
|
||||
mapa?: readonly string[];
|
||||
}): Partida {
|
||||
const sorteio = { rng: semear(opcoes.semente) };
|
||||
const hordas = escolherHordas(sorteio, opcoes.problemaId);
|
||||
const mapa = lerMapa(opcoes.mapa ?? ESCRITORIO);
|
||||
return {
|
||||
passo: 0,
|
||||
rng: sorteio.rng,
|
||||
dificuldade: opcoes.dificuldade,
|
||||
problemaId: opcoes.problemaId,
|
||||
hordas,
|
||||
horda: 0,
|
||||
fase: "inicio",
|
||||
tempoFase: 0,
|
||||
tetoAVista: opcoes.tetoAVista ?? TETO_A_VISTA,
|
||||
mapa,
|
||||
jogador: {
|
||||
x: mapa.inicio[0],
|
||||
z: mapa.inicio[1],
|
||||
rumo: 0,
|
||||
inclinacao: 0,
|
||||
vida: JOGADOR.vida,
|
||||
arma: 0,
|
||||
municao: MUNICAO.inicial,
|
||||
recarga: 0,
|
||||
},
|
||||
zumbis: [],
|
||||
fila: [],
|
||||
drones: [],
|
||||
caixas: [],
|
||||
feed: [],
|
||||
resolvidos: [],
|
||||
placar: { jogador: 0, drones: 0 },
|
||||
eventos: [],
|
||||
proximoId: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const campos = new WeakMap<Partida, { celula: number; campo: Int16Array }>();
|
||||
|
||||
/** Só recalcula o campo de fluxo quando o jogador muda de célula. */
|
||||
function campoAtual(p: Partida): Int16Array {
|
||||
const { x, z } = p.jogador;
|
||||
const celula = Math.floor(z) * p.mapa.largura + Math.floor(x);
|
||||
const guardado = campos.get(p);
|
||||
if (guardado?.celula === celula) return guardado.campo;
|
||||
const campo = campoDeFluxo(p.mapa, x, z);
|
||||
campos.set(p, { celula, campo });
|
||||
return campo;
|
||||
}
|
||||
|
||||
function moverJogador(p: Partida, entrada: Entrada): void {
|
||||
const j = p.jogador;
|
||||
j.rumo += entrada.olhar[0];
|
||||
j.inclinacao = Math.min(
|
||||
JOGADOR.inclinacaoMax,
|
||||
Math.max(-JOGADOR.inclinacaoMax, j.inclinacao + entrada.olhar[1]),
|
||||
);
|
||||
const [lado, frente] = entrada.mover;
|
||||
const escala = Math.min(1, 1 / Math.hypot(lado, frente));
|
||||
const passo = escala * JOGADOR.velocidade * PASSO;
|
||||
const dx = (Math.sin(j.rumo) * frente - Math.cos(j.rumo) * lado) * passo;
|
||||
const dz = (Math.cos(j.rumo) * frente + Math.sin(j.rumo) * lado) * passo;
|
||||
[j.x, j.z] = moverComColisao(p.mapa, j.x, j.z, dx, dz, JOGADOR.raio);
|
||||
}
|
||||
|
||||
function usarArma(p: Partida, entrada: Entrada): void {
|
||||
trocarArma(p, entrada.arma);
|
||||
resfriar(p);
|
||||
if (entrada.atirar) atirar(p);
|
||||
recolherCaixas(p);
|
||||
}
|
||||
|
||||
function mudarFase(p: Partida, fase: Fase): void {
|
||||
p.fase = fase;
|
||||
p.eventos.push({ tipo: "fase", fase });
|
||||
}
|
||||
|
||||
/** Começa a horda `p.horda`: a última é a do chefão. */
|
||||
function comecarHorda(p: Partida): void {
|
||||
p.tempoFase = 0;
|
||||
p.fila = montarFila(p.horda, p.hordas[p.horda], p.dificuldade);
|
||||
mudarFase(p, p.horda === HORDA_DO_CHEFAO ? "chefao" : "horda");
|
||||
}
|
||||
|
||||
function fecharHorda(p: Partida): void {
|
||||
const problemaId = p.hordas[p.horda];
|
||||
p.resolvidos.push(problemaId);
|
||||
registrarResolvido(p, problemaId);
|
||||
if (p.fase === "chefao") {
|
||||
p.eventos.push({ tipo: "resolvido", problemaId, droneId: -1 });
|
||||
mudarFase(p, "vitoria");
|
||||
return;
|
||||
}
|
||||
const drone = criarDrone(p, problemaId);
|
||||
p.eventos.push({ tipo: "resolvido", problemaId, droneId: drone.id });
|
||||
p.jogador.vida = Math.min(
|
||||
JOGADOR.vida,
|
||||
p.jogador.vida + JOGADOR.curaPorHorda,
|
||||
);
|
||||
p.tempoFase = 0;
|
||||
mudarFase(p, "pausa");
|
||||
}
|
||||
|
||||
function combater(p: Partida): void {
|
||||
nascer(p);
|
||||
avancarZumbis(p, campoAtual(p));
|
||||
avancarDrones(p);
|
||||
if (p.jogador.vida <= 0) {
|
||||
mudarFase(p, "derrota");
|
||||
} else if (p.fila.length === 0 && p.zumbis.length === 0) {
|
||||
fecharHorda(p);
|
||||
}
|
||||
}
|
||||
|
||||
function avancarFase(p: Partida): void {
|
||||
if (p.fase === "inicio") {
|
||||
if (p.tempoFase >= HORDAS.preparo) comecarHorda(p);
|
||||
} else if (p.fase === "pausa") {
|
||||
avancarDrones(p);
|
||||
if (p.tempoFase >= HORDAS.pausa) {
|
||||
p.horda++;
|
||||
comecarHorda(p);
|
||||
}
|
||||
} else {
|
||||
combater(p);
|
||||
}
|
||||
}
|
||||
|
||||
export function avancar(p: Partida, entrada: Entrada): void {
|
||||
p.eventos.length = 0;
|
||||
if (p.fase === "vitoria" || p.fase === "derrota") return;
|
||||
p.passo++;
|
||||
p.tempoFase++;
|
||||
moverJogador(p, entrada);
|
||||
usarArma(p, entrada);
|
||||
avancarFase(p);
|
||||
}
|
||||
Reference in new issue
Block a user