Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/js/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -4449,6 +4449,7 @@ export class Game {

_ctfWin(team) {
this.roundsWon[team] = (this.roundsWon[team] || 0) + 1;
this.world.onGoal?.();
this.state = 'roundEnd'; this.stateUntil = this.time + 4;
this.player.scoped = false; this.el.scope.classList.remove('on');
this.radioOpen = null; this._radioUi();
Expand Down
46 changes: 44 additions & 2 deletions public/js/map_campomorro.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { detailFor } from './textures.js';
import { applyLook } from './map_sky.js';
import { aplicaVento, updateVento } from './wind.js';
import { GPUParticles } from './gpuparticles.js';
import { createFavelaAmbience } from './ambientlife.js';
import { createFavelaAmbience, FAVELA_AMBIENCE_ASSETS } from './ambientlife.js';
import { AMB_LOOPS } from './soundscape.js';

const QP = new URLSearchParams(typeof location !== 'undefined' ? location.search : '');
Expand Down Expand Up @@ -47,6 +47,7 @@ export const CAMPOMORRO_PROPS = [
// RC4: grama da frente E (e-models) — o piloto do vento mora nela
'grama_corrego_01', 'grama_corrego_02', 'planta_corrego_taboa', 'planta_corrego_taioba',
];
export const CAMPOMORRO_AMBIENCE = FAVELA_AMBIENCE_ASSETS;

function laneHeight(x, z) {
for (const l of LANES) {
Expand Down Expand Up @@ -330,6 +331,46 @@ export function buildCampoMorro(scene, T = {}) {
addBox(11.8, 0.55, 3.3, MAT.concrete, -7, gArq, 20, { collide: false, bala: true });
for (let i = 0; i < 3; i++)
addBox(11.4 - i * 0.5, 0.34 + i * 0.28, 0.18, MAT.concrete, -7, groundHeightAt(-7, 18.36 + i * 0.08), 18.36 + i * 0.08, { collide: false, bala: true });
// Cobertura e bancos fazem a arquibancada ler como estrutura de campo, não um bloco solto.
for (const x of [-12, -7, -2]) {
addBox(.18, 3.7, .18, MAT.steelRust, x, gArq + .2, 21.4, { collide: false, cast: false });
addBox(.18, 3.7, .18, MAT.steelRust, x, gArq + .2, 18.3, { collide: false, cast: false });
}
const cobertura = addBox(12.8, .18, 3.8, MAT.galpaoRoof, -7, gArq + 3.8, 19.85, { collide: false, cast: false });
cobertura.userData.fieldStandRoof = true;
for (let fila = 0; fila < 3; fila++) {
const banco = addBox(11.1 - fila * .45, .16, .42, MAT.steelRust, -7, gArq + .68 + fila * .33, 19.0 + fila * .64,
{ collide: false, cast: false });
banco.userData.fieldStandBench = fila;
}
/* Torcida sem indivíduo identificável: uma silhueta compartilhada, 24 matrizes e uma chamada.
`onGoal` vem do ponto CTF; fora de gol a torcida senta, no gol ergue corpo e braços. */
const corpoTorcida = mergeParts([
new THREE.BoxGeometry(.34, .66, .22).translate(0, .38, 0),
new THREE.SphereGeometry(.16, 7, 6).translate(0, .86, 0),
new THREE.BoxGeometry(.62, .075, .075).translate(0, .58, 0),
]);
const torcida = new THREE.InstancedMesh(corpoTorcida, new THREE.MeshStandardMaterial({ color: 0x303438, roughness: 1 }), 24);
torcida.userData.crowdInstanced = true; torcida.userData.crowdCount = 24; torcida.castShadow = true; torcida.receiveShadow = true;
root.add(torcida);
const torcidaSlots = [];
for (let fila = 0; fila < 3; fila++) for (let coluna = 0; coluna < 8; coluna++)
torcidaSlots.push({ x: -11.35 + coluna * 1.24 + (fila % 2) * .18, y: gArq + .6 + fila * .34, z: 19.0 + fila * .64, phase: fila * 8 + coluna });
const dummyTorcida = new THREE.Object3D();
let tempoTorcida = 0, golAte = 0;
const atualizarTorcida = (time) => {
const gol = time < golAte;
for (let i = 0; i < torcidaSlots.length; i++) {
const slot = torcidaSlots[i], balanco = Math.sin(time * 2.4 + slot.phase) * .035;
dummyTorcida.position.set(slot.x, slot.y + balanco + (gol ? .46 + (slot.phase % 3) * .06 : 0), slot.z);
dummyTorcida.rotation.set(gol ? Math.sin(time * 9 + slot.phase) * .18 : 0, Math.PI, 0);
dummyTorcida.scale.set(1, gol ? 1.18 : .72, 1);
dummyTorcida.updateMatrix(); torcida.setMatrixAt(i, dummyTorcida.matrix);
}
torcida.instanceMatrix.needsUpdate = true;
torcida.userData.crowdState = gol ? 'goal-standing' : 'seated';
};
atualizarTorcida(0);

// Fachadas do beco oeste quebram a visada antes da entrada do campo.
const CASAS = [[-32, 2], [-32, 18], [-24.5, 23], [-13, 24], [7, 24], [29, 21], [31, -5], [13, -24], [-8, -24], [-27, -21], [-33, -10]];
Expand Down Expand Up @@ -705,7 +746,8 @@ export function buildCampoMorro(scene, T = {}) {
return {
ambience,sound:{loops:[{src:AMB_LOOPS.funk,pos:[28,2,-21],radius:24,vol:.5},{src:AMB_LOOPS.grilos,pos:[0,3,0],radius:80,vol:.26}],bioma:'campo'},
root, colliders, occluders, decalSolids: [root], groundHeightAt, spawns, sun, hemi, pickups, ctfPoints,
update(dt) { updateVento(dt); updatePoeira(dt); },
onGoal() { golAte = tempoTorcida + 3.2; },
update(dt, time = 0) { tempoTorcida = time; updateVento(dt); updatePoeira(dt); atualizarTorcida(time); },
waypoints: { nodes, adj }, nearestWaypoint, findPath,
levels: [{ nome: 'galpao', x0: GALPAO.x0, x1: GALPAO.x1, z0: GALPAO.z0, z1: GALPAO.z1, dePartida: 'B' }],
bounds: { minX: -HALF_X + 0.5, maxX: HALF_X - 0.5, minZ: -HALF_Z + 0.5, maxZ: HALF_Z - 0.5 },
Expand Down
4 changes: 2 additions & 2 deletions public/js/maps.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { buildHavan, havanPropsForMatch } from './map_havan.js';
import { buildFerroVelho, FERRO_PROPS } from './map_ferrovelho.js';
import { buildQuebrada, QUEBRADA_PROPS } from './map_quebrada.js';
import { buildEscadao, ESCADAO_PROPS, ESCADAO_AMBIENCE } from './map_escadao.js';
import { buildCampoMorro, CAMPOMORRO_PROPS } from './map_campomorro.js';
import { buildCampoMorro, CAMPOMORRO_PROPS, CAMPOMORRO_AMBIENCE } from './map_campomorro.js';
import { buildLajes, LAJES_PROPS, LAJES_AMBIENCE } from './map_lajes_authored.js';
import { buildCorrego, CORREGO_PROPS, CORREGO_AMBIENCE } from './map_corrego.js';
import { buildMansao, MANSAO_PROPS } from './map_mansao.js';
Expand Down Expand Up @@ -67,7 +67,7 @@ export const MAPS = {
/* DÍVIDA: renomear o prefixo `fy_` só aqui quebra CALADO — `LOOKS` (bloom.js) cai no
DEFAULT_LOOK de Brasília. Renome exige mudança própria com régua (PR #200). */
escadao: { name: 'Escadão (Morro)', build: buildEscadao, props: ESCADAO_PROPS, ambience: ESCADAO_AMBIENCE, ctfMode: true },
campomorro: { name: 'Campo do Morro', build: buildCampoMorro, props: CAMPOMORRO_PROPS, ctfMode: true },
campomorro: { name: 'Campo do Morro', build: buildCampoMorro, props: CAMPOMORRO_PROPS, ambience: CAMPOMORRO_AMBIENCE, ctfMode: true },
lajes: { name: 'Lajes (Comunidade)', build: buildLajes, props: LAJES_PROPS, ambience: LAJES_AMBIENCE, ctfMode: true },
corrego: { name: 'Córrego (Favela de SP)', build: buildCorrego, props: CORREGO_PROPS, ambience: CORREGO_AMBIENCE, ctfMode: true },
mansao: { name: 'Mansão do Joá', build: buildMansao, props: MANSAO_PROPS, ctfMode: true },
Expand Down
138 changes: 69 additions & 69 deletions tools/eval/ARCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

| Arquivo | Linhas | Símbolos |
|---|---:|---:|
| `public/js/game.js` | 7261 | 273 |
| `public/js/game.js` | 7262 | 273 |
| `public/js/main.js` | 2813 | 259 |
| `public/js/glbchars.js` | 969 | 68 |
| `public/js/characters.js` | 1169 | 41 |
Expand All @@ -23,21 +23,21 @@ Os 15 maiores somam **3152 linhas (43% do arquivo)**. Método grande = PR irrevi

| Linhas | Início | Método | |
|---:|---:|---|---|
| 814 | 5903 | `_updateBot()` | ⚠️ candidato a extração |
| 814 | 5904 | `_updateBot()` | ⚠️ candidato a extração |
| 547 | 612 | `constructor()` | 🔴 append-only |
| 351 | 5092 | `_updatePlayer()` | ⚠️ candidato a extração |
| 351 | 5093 | `_updatePlayer()` | ⚠️ candidato a extração |
| 249 | 2316 | `_resetPositions()` | |
| 241 | 1256 | `_buildViewModels()` | |
| 148 | 5443 | `_updatePickups()` | |
| 133 | 4576 | `_botCtf()` | |
| 148 | 5444 | `_updatePickups()` | |
| 133 | 4577 | `_botCtf()` | |
| 115 | 1934 | `_touchControls()` | |
| 85 | 3108 | `_tryShoot()` | |
| 84 | 4307 | `_initCTF()` | |
| 80 | 7063 | `_updateHud()` | |
| 80 | 7064 | `_updateHud()` | |
| 79 | 3499 | `_dmgArc()` | |
| 79 | 7143 | `update()` | 🔴 append-only |
| 76 | 4715 | `_updateCtfHud()` | |
| 71 | 6730 | `_updateBotNN()` | |
| 79 | 7144 | `update()` | 🔴 append-only |
| 76 | 4716 | `_updateCtfHud()` | |
| 71 | 6731 | `_updateBotNN()` | |

## Tabela de CONFLITO — resolvida para as linhas de hoje

Expand All @@ -48,19 +48,19 @@ faixas disjuntas simultaneamente com zero conflito de conteúdo.
| Frente | Faixas em `game.js` | Arquivos exclusivos |
|---|---|---|
| **ARMAS / VIEWMODEL** | `324–327` `361–455` `482–503` `1256–1654` `2873–2878` `2960–3043` `3062–3258` `3720–3744` `3792–3856` `3924–3940` | `public/js/vmattach.js` `public/js/springs.js` `public/js/weapons.js` `public/js/fparms.js` `public/js/handik.js` `public/js/recoil.js` `public/js/vmlab.js` |
| **BOTS / JOGABILIDADE** | `164–167` `218–218` `244–255` `545–556` `3360–3468` `4251–4306` `4472–4708` `4791–4813` `5092–5442` `5775–5792` `5874–6716` | — |
| **MAPAS / MUNDO** | `1202–1255` `2316–2564` `4307–4449` `5443–5590` | `public/js/maps.js` `public/js/mapprops.js` `public/js/map_brasilia.js` `public/js/map_havan.js` `public/js/map_piscina.js` `public/js/map_piscinao_ramos.js` `public/js/map_ferrovelho.js` |
| **BOTS / JOGABILIDADE** | `164–167` `218–218` `244–255` `545–556` `3360–3468` `4251–4306` `4473–4709` `4792–4814` `5093–5443` `5776–5793` `5875–6717` | — |
| **MAPAS / MUNDO** | `1202–1255` `2316–2564` `4307–4449` `5444–5591` | `public/js/maps.js` `public/js/mapprops.js` `public/js/map_brasilia.js` `public/js/map_havan.js` `public/js/map_piscina.js` `public/js/map_piscinao_ramos.js` `public/js/map_ferrovelho.js` |
| **GRÁFICOS / FX** | `1655–1664` `1779–1810` `2803–2815` `3745–3783` `3867–3923` | `public/js/bloom.js` `public/js/textures.js` `public/js/vao.js` `public/js/stylize.js` `public/js/gpuparticles.js` |
| **UI / HUD / MENU** | `1159–1201` `2757–2781` `2797–2802` `2816–2822` `3499–3640` `3656–3719` `6885–6948` `6979–7027` `7063–7142` | `public/js/main.js` `public/style.css` `src/pages/index.astro` |
| **UI / HUD / MENU** | `1159–1201` `2757–2781` `2797–2802` `2816–2822` `3499–3640` `3656–3719` `6886–6949` `6980–7028` `7064–7143` | `public/js/main.js` `public/style.css` `src/pages/index.astro` |
| **ÁUDIO** | — | `public/js/audio.js` |
| **PERSONAGENS** | — | `public/js/characters.js` `public/js/glbchars.js` |
| **SITE / BACKEND** | — | `src/` `supabase/` |

**🔴 Zonas vermelhas (append-only, qualquer frente pode precisar):** `update()` 7143–7221 · `_dom()` 1159–1201 · `constructor()` 612–1158
**🔴 Zonas vermelhas (append-only, qualquer frente pode precisar):** `update()` 7144–7222 · `_dom()` 1159–1201 · `constructor()` 612–1158

Nenhuma sobreposição entre frentes — todas as faixas são disjuntas. ✓

Cobertura: **3779 de 7261 linhas (52%)** do `game.js` têm dono declarado. O resto é território neutro — declare a frente mesmo assim.
Cobertura: **3779 de 7262 linhas (52%)** do `game.js` têm dono declarado. O resto é território neutro — declare a frente mesmo assim.

<details><summary><strong>Índice completo de <code>game.js</code> (todos os símbolos)</strong></summary>

Expand Down Expand Up @@ -241,61 +241,61 @@ Cobertura: **3779 de 7261 linhas (52%)** do `game.js` têm dono declarado. O res
| 4251 | `_botSeparation()` | 56 |
| 4307 | `_initCTF()` | 84 |
| 4391 | `_updateCTF()` | 59 |
| 4450 | `_ctfWin()` | 22 |
| 4472 | `_freeYaw()` | 25 |
| 4497 | `_pullString()` | 23 |
| 4520 | `_walkReach()` | 18 |
| 4538 | `_wpComp()` | 16 |
| 4554 | `_findPathLocal()` | 22 |
| 4576 | `_botCtf()` | 133 |
| 4709 | `_hideCtfHud()` | 6 |
| 4715 | `_updateCtfHud()` | 76 |
| 4791 | `_collide()` | 23 |
| 4814 | `_collideRot()` | 22 |
| 4836 | `_mantleAlcance()` | 50 |
| 4886 | `_mantleAlcancavel()` | 12 |
| 4898 | `_mantleTarget()` | 35 |
| 4933 | `_freeSpot()` | 30 |
| 4963 | `_retaAndavel()` | 20 |
| 4983 | `_walkDepth()` | 16 |
| 4999 | `_noteHit()` | 17 |
| 5016 | `_deathFeedback()` | 43 |
| 5059 | `_updateReplayCam()` | 33 |
| 5092 | `_updatePlayer()` | 351 |
| 5443 | `_updatePickups()` | 148 |
| 5591 | `_wpnMode()` | 5 |
| 5596 | `_botWeapon()` | 10 |
| 5606 | `_municaoInfinita()` | 1 |
| 5607 | `_pickupAllowed()` | 7 |
| 5614 | `_grabPickup()` | 35 |
| 5649 | `_assentarNoChao()` | 11 |
| 5660 | `_dropWeapon()` | 18 |
| 5678 | `_sumirDrop()` | 36 |
| 5714 | `_spawnY()` | 3 |
| 5717 | `_spawnYaw()` | 5 |
| 5722 | `_pickSpawn()` | 23 |
| 5745 | `_respawnPlayer()` | 30 |
| 5775 | `_losClear()` | 18 |
| 5793 | `_botCall()` | 37 |
| 5830 | `_teamMarkTex()` | 23 |
| 5853 | `_makeTeamMark()` | 14 |
| 5867 | `_updateTeamMark()` | 7 |
| 5874 | `_botEye()` | 1 |
| 5875 | `_enemyOf()` | 8 |
| 5883 | `_duelToken()` | 20 |
| 5903 | `_updateBot()` | 814 |
| 6717 | `_flushTraining()` | 13 |
| 6730 | `_updateBotNN()` | 71 |
| 6801 | `_botShootNN()` | 46 |
| 6847 | `_radarFoot()` | 38 |
| 6885 | `_updateRadar()` | 64 |
| 6949 | `_banner()` | 26 |
| 6975 | `_resultadoDaRodada()` | 4 |
| 6979 | `_showScoreboard()` | 49 |
| 7028 | `_updateWeaponHud()` | 35 |
| 7063 | `_updateHud()` | 80 |
| 7143 | `update()` | 79 |
| 7222 | `dispose()` | 39 |
| 4450 | `_ctfWin()` | 23 |
| 4473 | `_freeYaw()` | 25 |
| 4498 | `_pullString()` | 23 |
| 4521 | `_walkReach()` | 18 |
| 4539 | `_wpComp()` | 16 |
| 4555 | `_findPathLocal()` | 22 |
| 4577 | `_botCtf()` | 133 |
| 4710 | `_hideCtfHud()` | 6 |
| 4716 | `_updateCtfHud()` | 76 |
| 4792 | `_collide()` | 23 |
| 4815 | `_collideRot()` | 22 |
| 4837 | `_mantleAlcance()` | 50 |
| 4887 | `_mantleAlcancavel()` | 12 |
| 4899 | `_mantleTarget()` | 35 |
| 4934 | `_freeSpot()` | 30 |
| 4964 | `_retaAndavel()` | 20 |
| 4984 | `_walkDepth()` | 16 |
| 5000 | `_noteHit()` | 17 |
| 5017 | `_deathFeedback()` | 43 |
| 5060 | `_updateReplayCam()` | 33 |
| 5093 | `_updatePlayer()` | 351 |
| 5444 | `_updatePickups()` | 148 |
| 5592 | `_wpnMode()` | 5 |
| 5597 | `_botWeapon()` | 10 |
| 5607 | `_municaoInfinita()` | 1 |
| 5608 | `_pickupAllowed()` | 7 |
| 5615 | `_grabPickup()` | 35 |
| 5650 | `_assentarNoChao()` | 11 |
| 5661 | `_dropWeapon()` | 18 |
| 5679 | `_sumirDrop()` | 36 |
| 5715 | `_spawnY()` | 3 |
| 5718 | `_spawnYaw()` | 5 |
| 5723 | `_pickSpawn()` | 23 |
| 5746 | `_respawnPlayer()` | 30 |
| 5776 | `_losClear()` | 18 |
| 5794 | `_botCall()` | 37 |
| 5831 | `_teamMarkTex()` | 23 |
| 5854 | `_makeTeamMark()` | 14 |
| 5868 | `_updateTeamMark()` | 7 |
| 5875 | `_botEye()` | 1 |
| 5876 | `_enemyOf()` | 8 |
| 5884 | `_duelToken()` | 20 |
| 5904 | `_updateBot()` | 814 |
| 6718 | `_flushTraining()` | 13 |
| 6731 | `_updateBotNN()` | 71 |
| 6802 | `_botShootNN()` | 46 |
| 6848 | `_radarFoot()` | 38 |
| 6886 | `_updateRadar()` | 64 |
| 6950 | `_banner()` | 26 |
| 6976 | `_resultadoDaRodada()` | 4 |
| 6980 | `_showScoreboard()` | 49 |
| 7029 | `_updateWeaponHud()` | 35 |
| 7064 | `_updateHud()` | 80 |
| 7144 | `update()` | 79 |
| 7223 | `dispose()` | 39 |

</details>

Expand Down
18 changes: 17 additions & 1 deletion tools/eval/campo-contract-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
luzes e prova que a régua não confunde geometria presente com interior legível.
*/
import { THREE, bootGame, initTextures } from './harness.mjs';
import { readFileSync } from 'node:fs';
import { CAMPO_FIELD_MOUTH } from './map-evidence-views.mjs';

const MUT = process.argv.includes('--mutante=spawn-fora');
Expand All @@ -26,9 +27,10 @@ const MUT_BLOCKOUT = process.argv.includes('--mutante=galpao-blockout');
const MUT_REDES = process.argv.includes('--mutante=traves-sem-rede');
const MUT_BANCO = process.argv.includes('--mutante=bancada-central');
const MUT_MARCOS = process.argv.includes('--mutante=marcos-clonados');
const MUT_TORCIDA = process.argv.includes('--mutante=torcida-sentada');
const game = bootGame('campomorro', { textures: initTextures(), bots: 0 });
const luzesGalpao = [], faixasSaida = [], superficiesGalpao = [], framesGalpao = [];
const redes = [], bancos = [], marcos = [];
const redes = [], bancos = [], marcos = [], coberturas = [], bancosArq = [], torcidas = [];
game.scene.traverse((object) => {
if (object.userData?.mapLight === 'galpao') luzesGalpao.push(object);
if (object.userData?.galpaoExitBand) faixasSaida.push(object);
Expand All @@ -37,6 +39,9 @@ game.scene.traverse((object) => {
if (object.userData?.goalNet) redes.push(object);
if (object.userData?.fieldBench) bancos.push(object);
if (object.userData?.fieldLandmark) marcos.push(object);
if (object.userData?.fieldStandRoof) coberturas.push(object);
if (object.userData?.fieldStandBench !== undefined) bancosArq.push(object);
if (object.userData?.crowdInstanced) torcidas.push(object);
});
if (MUT_GALPAO) {
for (const luz of luzesGalpao) luz.intensity = 0;
Expand All @@ -46,6 +51,7 @@ if (MUT_BLOCKOUT) for (const superficie of superficiesGalpao) superficie.materia
if (MUT_REDES) for (const rede of redes) rede.visible = false;
if (MUT_BANCO && bancos[0]) bancos[0].position.set(0, bancos[0].position.y, 0);
if (MUT_MARCOS) for (const marco of marcos) marco.userData.fieldLandmark = 'clonado';
if (MUT_TORCIDA) game.world.onGoal = () => {};
const spawns = JSON.parse(JSON.stringify(game.world.spawns));
if (MUT) spawns.E[0] = { ...spawns.E[0], x: -31, z: 8 };

Expand All @@ -67,6 +73,16 @@ if (bancos.some((banco) => Math.abs(banco.position.x) < 3 && Math.abs(banco.posi
falhas.push('bancada invade a faixa central de 6 × 6 m');
if (new Set(marcos.map((marco) => marco.userData.fieldLandmark)).size < 3)
falhas.push(`${new Set(marcos.map((m) => m.userData.fieldLandmark)).size}/3 marcos distintos ao redor do campo`);
game.world.update?.(1 / 60, 0);
const torcidaSentada = torcidas.length === 1 && torcidas[0].userData.crowdState === 'seated';
game.world.onGoal?.(); game.world.update?.(1 / 60, .5);
const torcidaNoGol = torcidas.length === 1 && torcidas[0].userData.crowdState === 'goal-standing';
if (coberturas.filter((o) => o.visible !== false).length < 1 || bancosArq.filter((o) => o.visible !== false).length < 3)
falhas.push(`${coberturas.filter((o) => o.visible !== false).length}/1 cobertura e ${bancosArq.filter((o) => o.visible !== false).length}/3 bancos na arquibancada`);
if (!torcidaSentada || !torcidaNoGol || torcidas[0]?.count !== 24)
falhas.push(`torcida instanciada reage ao gol: sentada=${torcidaSentada} gol=${torcidaNoGol} instâncias=${torcidas[0]?.count || 0}`);
if (!readFileSync('public/js/game.js', 'utf8').includes('this.world.onGoal?.();'))
falhas.push('Game._ctfWin não chama world.onGoal()');
const noCampo = ({ x, z }) => Math.abs(x) <= 19.4 && Math.abs(z) <= 11.9;
const noGalpao = ({ x, z }) => x >= 22.4 && x <= 33.6 && z >= -25.6 && z <= -16.4;
for (const [i, spawn] of (spawns.E || []).entries()) if (!noCampo(spawn))
Expand Down
Loading