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,375 @@
|
||||
import {
|
||||
alvoNaMira,
|
||||
atirar,
|
||||
raioNoCirculo,
|
||||
recolherCaixas,
|
||||
resfriar,
|
||||
trocarArma,
|
||||
} from "./armas";
|
||||
import { lerMapa } from "./mapa";
|
||||
import type { ArmaId, Partida, TipoZumbi, Zumbi } from "./tipos";
|
||||
import { ARMAS, JOGADOR, ZUMBIS } from "./valores";
|
||||
|
||||
const LARGURA = 7;
|
||||
|
||||
function mapaAberto() {
|
||||
const linhas = ["#######", "#N...J#"];
|
||||
for (let i = 0; i < 13; i++) linhas.push("#.....#");
|
||||
linhas.push("#######");
|
||||
return lerMapa(linhas);
|
||||
}
|
||||
|
||||
function zumbi(
|
||||
id: number,
|
||||
x: number,
|
||||
z: number,
|
||||
tipo: TipoZumbi = "comum",
|
||||
): Zumbi {
|
||||
return {
|
||||
id,
|
||||
tipo,
|
||||
problemaId: "site-lento",
|
||||
x,
|
||||
z,
|
||||
rumo: 0,
|
||||
vida: ZUMBIS[tipo].vida,
|
||||
estado: "andando",
|
||||
tempo: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** Jogador em (3.5, 2.5) olhando para +z, com a pistola. */
|
||||
function partida(arma: ArmaId = 0): Partida {
|
||||
return {
|
||||
passo: 0,
|
||||
rng: 1,
|
||||
dificuldade: "normal",
|
||||
problemaId: "site-lento",
|
||||
hordas: [],
|
||||
horda: 0,
|
||||
fase: "horda",
|
||||
tempoFase: 0,
|
||||
tetoAVista: 16,
|
||||
mapa: mapaAberto(),
|
||||
jogador: {
|
||||
x: 3.5,
|
||||
z: 2.5,
|
||||
rumo: 0,
|
||||
inclinacao: 0,
|
||||
vida: JOGADOR.vida,
|
||||
arma,
|
||||
municao: 10,
|
||||
recarga: 0,
|
||||
},
|
||||
zumbis: [],
|
||||
fila: [],
|
||||
drones: [],
|
||||
caixas: [],
|
||||
feed: [],
|
||||
resolvidos: [],
|
||||
placar: { jogador: 0, drones: 0 },
|
||||
eventos: [],
|
||||
proximoId: 100,
|
||||
};
|
||||
}
|
||||
|
||||
describe("alvoNaMira", () => {
|
||||
it("é nulo com a faca", () => {
|
||||
const p = partida(2);
|
||||
p.zumbis = [zumbi(1, 3.5, 3.5)];
|
||||
expect(alvoNaMira(p)).toBeNull();
|
||||
});
|
||||
|
||||
it("é nulo sem zumbi na linha", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 1.5, 5.5)];
|
||||
expect(alvoNaMira(p)).toBeNull();
|
||||
});
|
||||
|
||||
it("devolve o corpo, com a distância e a altura", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
p.jogador.inclinacao = Math.atan(-0.2);
|
||||
const alvo = alvoNaMira(p);
|
||||
expect(alvo?.zumbi.id).toBe(1);
|
||||
expect(alvo?.cabeca).toBe(false);
|
||||
expect(alvo?.distancia).toBeCloseTo(2.65);
|
||||
expect(alvo?.y).toBeCloseTo(1.6 - 0.2 * 2.65);
|
||||
});
|
||||
|
||||
it("devolve a cabeça e não fere ninguém", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
expect(alvoNaMira(p)?.cabeca).toBe(true);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.eventos).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("raioNoCirculo", () => {
|
||||
it("devolve a distância até a primeira interseção", () => {
|
||||
expect(raioNoCirculo(0, 0, 0, 1, 0, 5, 1)).toBeCloseTo(4);
|
||||
});
|
||||
|
||||
it("devolve null quando o raio passa ao lado", () => {
|
||||
expect(raioNoCirculo(0, 0, 0, 1, 3, 5, 1)).toBeNull();
|
||||
});
|
||||
|
||||
it("devolve null quando o círculo está atrás", () => {
|
||||
expect(raioNoCirculo(0, 0, 0, 1, 0, -5, 1)).toBeNull();
|
||||
});
|
||||
|
||||
it("devolve 0 com a origem dentro do círculo", () => {
|
||||
expect(raioNoCirculo(0, 0.2, 0, 1, 0, 0, 1)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("trocarArma", () => {
|
||||
it("troca e põe a recarga em 10", () => {
|
||||
const p = partida(0);
|
||||
trocarArma(p, 1);
|
||||
expect(p.jogador.arma).toBe(1);
|
||||
expect(p.jogador.recarga).toBe(10);
|
||||
});
|
||||
|
||||
it("não faz nada para a mesma arma", () => {
|
||||
const p = partida(0);
|
||||
trocarArma(p, 0);
|
||||
expect(p.jogador.recarga).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resfriar", () => {
|
||||
it("desconta 1 da recarga", () => {
|
||||
const p = partida();
|
||||
p.jogador.recarga = 3;
|
||||
resfriar(p);
|
||||
expect(p.jogador.recarga).toBe(2);
|
||||
});
|
||||
|
||||
it("para em 0", () => {
|
||||
const p = partida();
|
||||
resfriar(p);
|
||||
expect(p.jogador.recarga).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("recolherCaixas", () => {
|
||||
it("recolhe a caixa perto e deixa a longe", () => {
|
||||
const p = partida();
|
||||
p.caixas = [
|
||||
{ id: 1, x: 3.5, z: 3.0 },
|
||||
{ id: 2, x: 3.5, z: 8.5 },
|
||||
];
|
||||
recolherCaixas(p);
|
||||
expect(p.caixas.map((c) => c.id)).toEqual([2]);
|
||||
expect(p.jogador.municao).toBe(40);
|
||||
expect(p.eventos).toEqual([{ tipo: "caixa", balas: 30 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("atirar: regras gerais", () => {
|
||||
it("não faz nada com recarga pendente", () => {
|
||||
const p = partida();
|
||||
p.jogador.recarga = 1;
|
||||
atirar(p);
|
||||
expect(p.eventos).toEqual([]);
|
||||
expect(p.jogador.recarga).toBe(1);
|
||||
});
|
||||
|
||||
it("automática sem bala não atira e não gasta recarga", () => {
|
||||
const p = partida(1);
|
||||
p.jogador.municao = 0;
|
||||
atirar(p);
|
||||
expect(p.eventos).toEqual([]);
|
||||
expect(p.jogador.recarga).toBe(0);
|
||||
});
|
||||
|
||||
it("automática gasta uma bala e arma a recarga", () => {
|
||||
const p = partida(1);
|
||||
atirar(p);
|
||||
expect(p.jogador.municao).toBe(9);
|
||||
expect(p.jogador.recarga).toBe(ARMAS[1].intervalo);
|
||||
});
|
||||
|
||||
it("pistola nunca gasta bala, nem com a munição zerada", () => {
|
||||
const p = partida(0);
|
||||
p.jogador.municao = 0;
|
||||
atirar(p);
|
||||
expect(p.jogador.municao).toBe(0);
|
||||
expect(p.jogador.recarga).toBe(ARMAS[0].intervalo);
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("atirar: tiro por raio", () => {
|
||||
it("sem zumbi, o tiro vai até a parede", () => {
|
||||
const p = partida();
|
||||
atirar(p);
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
const e = p.eventos[0];
|
||||
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(12.5);
|
||||
});
|
||||
|
||||
it("acerta o corpo com o dano da arma", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
p.jogador.inclinacao = Math.atan(-0.2);
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100 - ARMAS[0].dano);
|
||||
const [tiro, acerto] = p.eventos;
|
||||
expect(tiro).toMatchObject({ tipo: "tiro", autor: "jogador", arma: 0 });
|
||||
expect(tiro.tipo === "tiro" && tiro.distancia).toBeCloseTo(2.65);
|
||||
expect(acerto).toMatchObject({
|
||||
tipo: "acerto",
|
||||
zumbiId: 1,
|
||||
cabeca: false,
|
||||
});
|
||||
expect(acerto.tipo === "acerto" && acerto.y).toBeCloseTo(1.6 - 0.2 * 2.65);
|
||||
expect(acerto.tipo === "acerto" && acerto.x).toBeCloseTo(3.5);
|
||||
expect(acerto.tipo === "acerto" && acerto.z).toBeCloseTo(5.15);
|
||||
});
|
||||
|
||||
it("acerta a cabeça com o dano dobrado", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100 - ARMAS[0].dano * 2);
|
||||
expect(p.eventos[1]).toMatchObject({ tipo: "acerto", cabeca: true });
|
||||
});
|
||||
|
||||
it("erra por cima com a inclinação alta", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
p.jogador.inclinacao = 1;
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
const e = p.eventos[0];
|
||||
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(12.5);
|
||||
});
|
||||
|
||||
it("erra por baixo, no chão", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
p.jogador.inclinacao = Math.atan(-1);
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("o raio que passa ao lado não acerta", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 5.5, 5.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
});
|
||||
|
||||
it("parede na frente do zumbi o protege", () => {
|
||||
const p = partida();
|
||||
p.mapa.opaco[8 * LARGURA + 3] = 1;
|
||||
p.zumbis = [zumbi(1, 3.5, 10.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
const e = p.eventos[0];
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(5.5);
|
||||
});
|
||||
|
||||
it("o zumbi da frente protege o de trás", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 9.5), zumbi(2, 3.5, 5.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.zumbis[1].vida).toBe(32);
|
||||
});
|
||||
|
||||
it("zumbi morrendo é atravessado", () => {
|
||||
const p = partida();
|
||||
const morto = zumbi(1, 3.5, 5.5);
|
||||
morto.estado = "morrendo";
|
||||
p.zumbis = [morto, zumbi(2, 3.5, 9.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.zumbis[1].vida).toBe(32);
|
||||
});
|
||||
|
||||
it("o tiro que passa por cima do da frente pode acertar o de trás", () => {
|
||||
const p = partida();
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5, "rapido"), zumbi(2, 3.5, 9.5, "chefao")];
|
||||
p.jogador.inclinacao = Math.atan(0.05);
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(50);
|
||||
expect(p.zumbis[1].vida).toBeLessThan(1500);
|
||||
});
|
||||
|
||||
it("matar registra a morte pelo ferir", () => {
|
||||
const p = partida();
|
||||
const fraco = zumbi(1, 3.5, 5.5);
|
||||
fraco.vida = 10;
|
||||
p.zumbis = [fraco];
|
||||
atirar(p);
|
||||
expect(fraco.estado).toBe("morrendo");
|
||||
expect(p.eventos.some((e) => e.tipo === "morte")).toBe(true);
|
||||
});
|
||||
|
||||
it("a automática usa o dano da automática", () => {
|
||||
const p = partida(1);
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
p.jogador.inclinacao = Math.atan(-0.2);
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100 - ARMAS[1].dano);
|
||||
});
|
||||
});
|
||||
|
||||
describe("atirar: faca", () => {
|
||||
it("fere o mais próximo dentro do alcance e do arco", () => {
|
||||
const p = partida(2);
|
||||
p.zumbis = [zumbi(1, 3.5, 3.9), zumbi(2, 3.5, 3.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.zumbis[1].vida).toBe(100 - ARMAS[2].dano);
|
||||
expect(p.eventos[0]).toMatchObject({ tipo: "tiro", arma: 2 });
|
||||
const acerto = p.eventos.find((e) => e.tipo === "acerto");
|
||||
expect(acerto).toMatchObject({
|
||||
zumbiId: 2,
|
||||
cabeca: false,
|
||||
y: ZUMBIS.comum.altura / 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("não acerta fora do arco", () => {
|
||||
const p = partida(2);
|
||||
p.zumbis = [zumbi(1, 3.5, 1.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.eventos).toEqual([
|
||||
{ tipo: "tiro", autor: "jogador", arma: 2, distancia: ARMAS[2].alcance },
|
||||
]);
|
||||
});
|
||||
|
||||
it("não acerta fora do alcance", () => {
|
||||
const p = partida(2);
|
||||
p.zumbis = [zumbi(1, 3.5, 5.5)];
|
||||
atirar(p);
|
||||
expect(p.zumbis[0].vida).toBe(100);
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("ignora o zumbi morrendo", () => {
|
||||
const p = partida(2);
|
||||
const morto = zumbi(1, 3.5, 3.5);
|
||||
morto.estado = "morrendo";
|
||||
p.zumbis = [morto];
|
||||
atirar(p);
|
||||
expect(p.eventos).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("a distância do tiro é a do zumbi ferido", () => {
|
||||
const p = partida(2);
|
||||
p.zumbis = [zumbi(1, 3.5, 3.5)];
|
||||
atirar(p);
|
||||
const e = p.eventos[0];
|
||||
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(0.65);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user