Níveis e Mapas do EternalTD
Um "nível" no EternalTD é um config YAML em plugins/EternalTD/levels/ emparelhado com uma pasta de mundo template em plugins/EternalTD/worlds/. Quando um jogador entra, o EternalTD clona o mundo template para o contêiner de mundos do servidor e executa a sessão nessa cópia clonada.
Campos do Config do Nível
| Campo | Tipo | Padrão | Notas |
|---|---|---|---|
isEnabled | bool | true | Níveis desativados são ignorados durante o carregamento |
levelName | string | null | Nome de exibição mostrado em mensagens, scoreboards e menus de NPC |
levelDescription | lista de strings | [] | Linhas mostradas em menus de NPC. Suporta os placeholders $highscoreWave e $highscorePlayer |
worldName | string | null | O nome da pasta template em plugins/EternalTD/worlds/ |
startLocation | lista de strings | null | Lista de localizações serializadas onde os mobs spawnam |
endLocation | lista de strings | null | Lista de localizações serializadas para as quais os mobs caminham (as casas "vermelhas") |
levelLocations | lista de strings | null | Toda casa de grade caminhável no nível; no build atual elas precisam vir do pacote ou ser escritas manualmente no YAML |
wavesConfigFile | string | obrigatório | Nome do arquivo de config de onda vinculado (waves/<name>.yml) |
highscoreWave | int | 0 | Melhor onda alcançada neste nível |
highscorePlayerName | string | "no one" | Nome de exibição do jogador que estabeleceu o recorde |
environment | enum | NORMAL | Ambiente de mundo usado quando o mundo clonado é carregado |
O tamanho de grade usado em todo lugar é 3 blocos por casa lógica (constante GRID_SIZE no código).
waveCount não existe mais. Arquivos de nível antigos que ainda carregam a chave são simplesmente ignorados — o plugin nunca a lê e nunca a escreve de volta.
Formato de String de Localização
As localizações no EternalTD são serializadas como strings separadas por vírgulas no formato:
worldName,x,y,z,yaw,pitch
Pacotes baixados normalmente fornecem esses valores. Para um mapa personalizado, os comandos atuais de seleção e registro não salvam levelLocations, então você precisa escrever à mão as localizações de grade geradas no YAML do nível.
Ciclo de Vida do Mundo
Quando um jogador entra em um nível:
- O EternalTD verifica se este jogador já não tem uma cópia em andamento. Um segundo
/etd joinenquanto a primeira ainda está copiando é recusado com "Your level is already being prepared." - Ele procura a pasta template por
worldNameemplugins/EternalTD/worlds/e rejeita a entrada se a pasta do mundo estiver ausente, se o arquivo de ondas vinculado falhou ao carregar, ou se o nome do mundo não for um nome de pasta seguro. - Ele reserva o próximo sufixo numérico livre (
<worldName>_0,<worldName>_1, ...). A reserva é sincronizada e lembra nomes que estão reservados mas ainda não estão em disco, então dois jogadores entrando ao mesmo tempo não podem colidir no mesmo nome de instância. - O template é copiado para o contêiner de mundos do servidor fora da thread principal, então o servidor não trava com um mapa grande.
- O mundo clonado é carregado como um mundo temporário do tipo void através do
TemporaryWorldManagerdo MagmaCore, então a migração do Paper 26.1+ fica em quarentena e chunks ausentes voltam como void em vez de terreno recém-gerado. - O jogador é teletransportado para o novo mundo; um
InstanceProtectorinterno aplica as regras de proteção do EternalTD.
Requisitos do Modelo
A cópia é validada antes de qualquer coisa ser escrita, e um modelo é rejeitado se:
- não for um diretório, ou for um link simbólico
- não tiver
level.datna sua raiz - contiver um link simbólico em qualquer lugar dentro dele
- contiver um arquivo chamado
.eternaltd-instance(esse nome é reservado, veja abaixo)
Uma cópia que falha é revertida e o jogador é informado de que o nível não foi iniciado.
Marcadores de Propriedade da Instância
Todo clone recebe um arquivo .eternaltd-instance escrito na sua raiz, registrando o nome do modelo e o nome da instância. O EternalTD só deleta uma pasta de mundo cujo marcador corresponda à instância que ele acha que está deletando. É essa a proteção que impede que uma colisão de nome obsoleta, uma pasta feita à mão ou uma sessão descasada apaguem um mundo que o EternalTD não criou — se o marcador estiver ausente ou não corresponder, a pasta é deixada no lugar e um aviso é registrado.
Instâncias obsoletas que sobraram de um crash são limpas quando os níveis carregam, e mesmo assim apenas para pastas que carreguem um marcador válido e não estejam carregadas no momento.
Validação de Início de Sessão
Uma sessão só se torna jogável se todas as condições a seguir valerem. Qualquer falha aborta a run, restaura o jogador (inventário, modo de jogo, voo, scoreboard) e deleta o mundo temporário:
levelLocations,startLocationeendLocationestão todos presentes e não vazios — caso contrário, "This level doesn't have a complete play area, start, and end configuration!"- a própria área de jogo é analisada como uma grade válida — caso contrário, "This level's play area is invalid!"
- toda casa inicial tem tanto um caminho terrestre quanto um caminho aéreo até alguma casa final — caso contrário, "This level does not have complete land and air paths from every start to an end!"
Quando a sessão termina:
- Quaisquer jogadores restantes no mundo clonado são teletransportados de volta para a localização de spawn do
config.yml, ou expulsos se nenhum spawn estiver configurado. - As regras de proteção do mundo são removidas e todas as torres restantes são desmontadas (uma torre que lança erro ao ser removida é registrada e pulada, em vez de deixar o restante travado).
- Os chunk tickets mantidos sobre a área de jogo são liberados (veja Validação de Caminho).
- O mundo clonado é descarregado e deletado do disco (
TemporaryWorldManager.permanentlyDeleteWorld), tanto no layout legado quanto no layout migrado do Paper 26.1+. - O encerramento é idempotente — um segundo
end()(por exemplo,/etd quitimediatamente seguido de/etd hub) não faz nada.
Regras de Proteção da Instância
Enquanto um nível está ativo, o mundo clonado tem estas regras aplicadas:
- Explosões desabilitadas
- Fluxo de líquidos desabilitado
- Elytra desabilitado
- Alternância de voo bloqueada
- Fogo amigo bloqueado
- Spawn de mobs do vanilla bloqueado
Fluxo de Autoria de Mapa
O fluxo atual de autoria de mapas usa as ferramentas dentro do jogo:
- Coloque uma pasta de mundo template em
plugins/EternalTD/worlds/<worldName>/. - Crie ou baixe um YAML de nível correspondente em
plugins/EternalTD/levels/. - Execute
/etd reloade entre no mundo do nível manualmente (ou abra-o em single-player para configurar). - Use
/etd selectfloore clique com os botões direito/esquerdo em dois cantos para marcar a área jogável, ou use/etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2>para fornecê-los diretamente. - Execute
/etd showselection <level>para confirmar que a seleção parece correta. - Execute
/etd register <level>para limpar a seleção. Note que, no build atual, nemregisternemshowselectionde fato persistem a região de piso — o auxiliar que salvarialevelLocations(LevelsConfigFields#addLevelLocations) está definido mas nunca é invocado por um comando. Atualmente você precisa escreverlevelLocationsno YAML do nível à mão se ele ainda não estiver populado por um pacote baixado. - Fique em pé em uma casa de spawn inicial e execute
/etd register <level> start. Repita para cada casa inicial (este comando de fato persiste emstartLocation). - Fique em pé em uma casa final e execute
/etd register <level> end. Repita para cada casa final (este comando de fato persiste emendLocation). - Recarregue novamente e teste o nível entrando nele pelo menu do NPC ou por
/etd join <level>.
Os comandos de seleção geram casas de grade usando:
size = abs(corner1 - corner2 + 1) / 3
Uma casa é pulada quando o bloco de piso dela é passável, ou quando o bloco diretamente acima do piso não é passável. Em outras palavras, o piso precisa ser sólido e o espaço acima dele precisa estar livre para a casa se registrar como jogável — selecione o piso, não o ar acima dele.
Validação de Caminho
O EternalTD armazena em cache, para cada casa inicial, o caminho A* mais barato até alguma casa final — um caminho terrestre e um caminho aéreo. Com várias casas finais, ele compara os caminhos concluídos pelo custo e mantém o mais curto, então um mapa com várias saídas encaminha os inimigos para a mais próxima que seja alcançável, e não para a primeira encontrada.
O cache terrestre é reconstruído quando a sessão inicia, sempre que uma torre é colocada e sempre que uma torre é vendida. O cache aéreo é computado apenas uma vez, na primeira reconstrução: caminhos aéreos ignoram torres por completo, então nada que o jogador construa pode invalidá-los.
Inimigos aéreos usam esse caminho aéreo separado, que ignora completamente o bloqueio por torres e em vez disso segue o offset aéreo (4 blocos acima do caminho configurado).
Carregar a área de jogo também força o carregamento de todo chunk que contenha uma casa de grade, para que uma run longa não trave em um chunk descarregado. Esses chunk tickets são liberados quando a sessão termina, logo antes de o mundo da instância ser deletado.
O cache também é o critério que define se um nível é jogável: se qualquer casa inicial estiver sem o caminho terrestre ou sem o caminho aéreo, a sessão é recusada no momento da entrada, em vez de iniciar uma run que os inimigos não conseguem terminar. A mesma verificação roda quando uma torre é colocada, então uma torre que isolaria uma casa inicial é rejeitada e o ouro não é gasto.
Uma localização inicial ou final que não fique no centro da sua casa 3x3 da grade é descartada com um aviso no console, o que é a causa habitual de um nível reportar um conjunto de caminhos incompleto.
Toda casa inicial e final resolvida também é marcada como não construível, para que os jogadores não possam bloquear um spawn ou uma saída construindo em cima dela. Essas casas aparecem em azul-claro sob o destaque de posicionamento.
NPCs e Menus de Nível
Configs de NPC em plugins/EternalTD/npcs/ vinculam NPCs aldeões a um ou mais níveis. Clicar com o botão direito no NPC abre um inventário de 9 slots listando cada nível como um vidro tingido verde rotulado com o nome e a descrição do nível.
| Campo | Tipo | Padrão | Notas |
|---|---|---|---|
isEnabled | bool | true | NPCs desativados são ignorados |
levelIDs | lista de strings | obrigatório | Nomes de arquivo dos níveis que este NPC oferece |
location | string | null | Localização de spawn no formato padrão worldName,x,y,z,yaw,pitch |
name | string | "Default Name" | Nome de exibição do NPC |
difficulty | string | "Difficulty: Not Set" | Rótulo de dificuldade mostrado acima do NPC |
disguise | string | null | Descritor do LibsDisguises (ex.: custom:etd_tutorial_npc) |
customDisguiseData | string | null | Dados extras de comando do LibsDisguises — geralmente a string longa de skin de jogador |
O aldeão é spawnado invulnerável, com IA desabilitada, persistente e marcado com a chave de namespace de NPC do EternalTD. Se o LibsDisguises estiver instalado e tanto disguise quanto customDisguiseData estiverem definidos, o aldeão é disfarçado no spawn.
Um armor stand flutuante com o rótulo difficulty é spawnado 2.3 blocos acima do NPC.
Comportamento de Spawn
O DefaultConfig controla como os jogadores são gerenciados no mundo do hub:
setupDone— flag que rastreia se a orientação de configuração de primeira vez foi concluída (padrãofalse).spawnLocations— padrão paraetd_spawn,0,65,0,0,0e é sempre escrito na configuração. Só é resolvido e usado quando o mundoetd_spawnexiste.manageSpawn— padrãotrue. Quando habilitado, jogadores que entram são teletransportados para a localização de spawn 1 tick após o login.playerGuide— o texto do livro guia dentro do jogo.nightbreak.autoDownloadPluginUpdates— configuração compartilhada do MagmaCore (padrãofalse). Quando habilitada, as atualizações de plugin e conteúdo são baixadas automaticamente na inicialização.
Quando manageSpawn é true e o mundo de spawn está carregado, todo jogador que entra no servidor é teletransportado para spawnLocations. Jogadores que já estavam conectados enquanto o EternalTD ainda inicializava são levados para o mesmo spawn assim que a inicialização termina, a menos que já estejam no mundo do hub.