Ir para conteúdo

Maze Minigame


meubk

Posts Recomendados

Especificações

 

Nome: Maze Minigame

Eventos: Movement

Testado em: The Forgotten Server 0.3.6 PL1 8.54 (linux-build)

Autor: Skyen Hasus

 

Copyright © 2011 Skyen Hasus

Licensa: GNU General Public License v3 <http://www.gnu.org/licenses/gpl.html>

 

Observações:

  • Optei por fazer um minigame de labirinto por ser um clássico mundialmente conhecido, e pelo algoritmo de geração ser complexo, mas não difícil, além de poder ser totalmente aplicado em um jogo como o Tibia.
  • O script não foi feito sob Programação Funcional, que é o paradigma mais comum usado em scripts para Open Tibia, e sim sob Orientação à Objetos. A classe do minigame é a Maze, contida em data/movements/lib/libmaze.lua
  • Os labirintos gerados são únicos, uma vez que usam de pseudo-randomismo (math.random) para serem gerados pelo computador. Todos os labirintos gerados tem solução, pois nenhum bloco do labirinto fica isolado.
  • Qualquer tamanho para o labirinto é aceito, porém, quanto maior o labirinto, mais o algoritmo vai demorar para gerar o labirinto. Um labirinto 50x50 foi gerado sem travamentos em minha máquina, com estas especificações:

 

Sistema Operacional: GNU/Linux Ubuntu 10.10 - Maverick Meerkat

Processador: Intel® Pentium® Dual CPU E2180 @ 2.0 GHz

Memória RAM: 2.0 GB

 

  • O código está todo comentado e foi previamente testado para evitar problemas.

 

Instalação e configuração

 

 

O script é instalado apenas copiando os conteúdos do arquivo zipado para suas respectivas pastas, e modificando o arquivo movements.xml para incluir o conteúdo do arquivo zipado.

 

O script requer uma leve modificação no mapa, como mostra a imagem abaixo:

 

screenuz.png

 

O script já vem pré-configurado para o mapa acima.

 

A área maior é a área onde será gerado o labirinto. Na imagem acima o tamanho dela é de 15x15. Este valor já vem pré-configurado no script, na linha 46 do maze.lua.

maze:set_size(15, 15)

 

Esta área deve estar preenchida com o No-Logout Zone.

 

Os tiles destacados em verde na parte de cima devem ter a ActionID 1001 (Este valor pode ser alterado desde que o script maze.lua e o movements.xml também sejam alterados). O ActionID 1001 é o chaveamento para iniciar o labirinto.

 

O tile destacados em vermelho na parte de baixo deve ter a ActionID 1002 (Este valor pode ser alterado desde que o script maze.lua e o movements.xml também sejam alterados). O ActionID 1002 é o chaveamento para encerrar o labirinto.

 

Caso você deseje alterar algum dado, estas são as configurações necessárias:

 

Relocation Position: Posição para onde serão teleportados os itens e players não relacionados ao minigame quando for executada uma limpeza do labirinto.

maze:set_relocation_pos(x, y, z)

 

Top-Left Position: Posição superior-esquerda do primeiro tile da área do labirinto.

maze:set_topleft_pos(x, y, z)

 

Exit: Posição da célula do labirinto que será usada como saída. Vale lembrar que esta célula deve estar na parte inferior do labirinto (Isto pode ser alterado mudando a linha 290 do libmaze.lua) e não é contada como uma posição em SQM do Tibia, e sim como número da célula.

maze:set_exit(x, y)

 

Wall ID: ItemID da parede do labirinto que será gerada.

maze:set_wall(itemid)

 

Para adicionar uma nova entrada no labirinto, usar:

maze:add_entrance({
       tile = {x=?, y=?, z=?},
       init = {x=?, y=?, z=?},
       exit = {x=?, y=?, z=?},
})

 

tile: Posição do tile com o ActionID 1001, usado para ativar o labirinto.

init: Posição que o player daquele tile será teleportado assim que o minigame começar.

exit: Posição que o player daquele tile será teleportado assim que o minigame acabar.

 

Bugs conhecidos e suas soluções:

Estes bugs não podem ser corrigidos via script. Abaixo seguem suas descrições e soluções.

  • Se o player fizer logout dentro da área do labirinto, ao fazer login o mesmo não conseguirá sair de dentro. Para isso, marque toda a área do labirinto como No-Logout Zone.
  • Se o mundo for No-PVP, os players podem passar por cima de outros (update). Caso um player já esteja sobre um dos tiles com ActionID, se outro player entrar em cima dele, o tile receberá um ID não-correspondente. Para solucionar o problema existe um patch de correção do The Forgotten Server que impossibilita a passagem de um player sobre outro em mundos No-PVP onde os tiles possuem ActionID.

 

Arquivos

 

/data/movements/movements.xml

<!-- Maze minigame -->
<movevent type="StepIn" actionid="1001;1002" event="script" value="maze.lua"/>
<movevent type="StepOut" actionid="1001;1002" event="script" value="maze.lua"/>

 

/data/movements/lib/libmaze.lua

-- libmaze.lua
-- This file is part of Maze minigame
--
-- Copyright (C) 2011 Skyen Hasus
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

-- Por questões de compatibilidade com diversas distribuições, mantive esta
-- biblioteca na mesma pasta do script.

-- Criação da classe Maze (Orientação à Objetos)
Maze = {}

-- Método útil para comparar posições
function Maze:compare_pos(pos1, pos2)
       return pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z
end

-- Inicializa uma nova instância de Maze (Orientação à Objetos)
function Maze:new(obj)
       local obj = obj or {}
       if not obj.entrances then
               obj.entrances = {}
       end
       if not obj.players then
               obj.players = {}
       end
       return setmetatable(obj, {__index=self})
end

-- Métodos de configuração
function Maze:add_entrance(template)
       table.insert(self.entrances, template)
end

function Maze:add_player(entrance, cid)
       self.players[cid] = entrance
end

function Maze:rem_players()
       for cid, _ in pairs(self.players) do
               self.players[cid] = nil
       end
end

function Maze:set_relocation_pos(x, y, z)
       self.relpos = {x=x, y=y, z=z}
end

function Maze:set_topleft_pos(x, y, z)
       self.topleft = {x=x, y=y, z=z}
end

function Maze:set_exit(x, y)
       self.exit = {x=x, y=y}
end

function Maze:set_wall(id)
       self.wall_id = id
end

function Maze:set_size(width, height)
       self.size = {width=width, height=height}
end

function Maze:set_area(template)
       self.area = template
end

-- Métodos para ler a configuração
function Maze:get_entrances()
       return self.entrances
end

function Maze:get_entrance(pos)
       for _, entrance in ipairs(maze:get_entrances()) do
               if self:compare_pos(pos, entrance.tile) then
                       return entrance
               end
       end
       return false
end

function Maze:get_players()
       return self.players
end

function Maze:get_relocation_pos()
       return self.relpos
end

function Maze:get_topleft_pos()
       return self.topleft
end

function Maze:get_exit()
       return self.exit
end

function Maze:get_wall()
       return self.wall_id
end

function Maze:get_size()
       return self.size
end

function Maze:get_area()
       return self.area
end

-- Métodos úteis para o desenvolvimento do script
function Maze:get_top_left(pos)
       return {x=pos.x-1, y=pos.y-1, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_top_right(pos)
       return {x=pos.x+1, y=pos.y-1, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_bottom_left(pos)
       return {x=pos.x-1, y=pos.y+1, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_bottom_right(pos)
       return {x=pos.x+1, y=pos.y+1, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_top(pos)
       return {x=pos.x, y=pos.y-1, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_bottom(pos)
       return {x=pos.x, y=pos.y+1, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_left(pos)
       return {x=pos.x-1, y=pos.y, z=pos.z, stackpos=pos.stackpos}
end

function Maze:get_right(pos)
       return {x=pos.x+1, y=pos.y, z=pos.z, stackpos=pos.stackpos}
end

-- Método para transformar uma posição do Tibia em células do labirinto
function Maze:to_maze(value)
       return (value-1)/2
end

-- Método que verifica se todos os players estão em suas posição e se não
-- há nenhum player dentro do labirinto
function Maze:is_available()
       local start = self:get_topleft_pos()
       local size = self:get_size()
       for _, entrance in ipairs(self:get_entrances()) do
               local player = getTopCreature(entrance.tile)
               if player.uid == 0 or not isPlayer(player.uid) then
                       return false
               end
       end
       for x = start.x, start.x+size.width do
               for y = start.y, start.y+size.height do
                       local player = getTopCreature({x=x, y=y, z=start.z})
                       if isCreature(player.uid) then
                               return false
                       end
               end
       end
       return true
end

-- Método para pegar uma lista de células vizinhas
function Maze:get_neighbors(x, y)
       local neighbors = {
               {x=x, y=y-1},
               {x=x, y=y+1},
               {x=x-1, y=y},
               {x=x+1, y=y},
       }
       return neighbors
end

-- Método para determinar se uma posição está dentro de uma área
function Maze:is_valid(x, y)
       local size = self:get_size()
       local width = self:to_maze(size.width)
       local height = self:to_maze(size.height)
       return x >= 1 and x <= width and y >= 1 and y <= height
end

-- Método para geração de uma área limpa para o labirinto
function Maze:generate_area()
       local size = self:get_size()
       -- Verifica se a área do labirinto é valida
       if not ((size.width-1)%2 == 0 and (size.height-1)%2 == 0) then
               print("Warning: Invalid size for maze area generation!")
               return false
       end

       -- Gera a área e suas respectivas células limpas
       local area = {}
       for x = 1, self:to_maze(size.width) do
               area[x] = {}
               for y = 1, self:to_maze(size.height) do
                       -- Gera uma nova célula limpa
                       area[x][y] = {
                               visited = false,
                               top = true,
                               bottom = true,
                               left = true,
                               right = true,
                       }
               end
       end
       self:set_area(area)
       return true
end

-- Método recursivo para caminhar pela área do labirinto, gerando os caminhos
function Maze:handle_cell(x, y)
       -- Pega a área e tamanho do labirinto e copia em váriaveis locais
       -- para otimização do código
       local area = self:get_area()
       local size = self:get_size()

       -- Antes de mais nada, marca a célula atual como visitada
       area[x][y].visited = true;

       -- Pega uma lista de células vizinhas
       local nb = self:get_neighbors(x, y)
       local used = {false, false, false, false}

       -- Converte o tamanho do labirinto de número de tiles
       -- para número de células
       local width = self:to_maze(size.width)
       local height = self:to_maze(size.height)

       -- Enquanto a célula atual tiver vizinhas não visitadas, inicie um novo
       -- caminho pelo labirinto, partindo de uma célula aleatória
       while not (used[1] and used[2] and used[3] and used[4]) do
               local c = math.random(1, 4)
               used[c] = true
               -- Verifica se a célula vizinha escolhida é válida e ainda não
               -- foi visitada
               if self:is_valid(nb[c].x, nb[c].y) and
               not area[nb[c].x][nb[c].y].visited then
                       -- Abre as paredes entre as duas células
                       if c == 1 then
                               area[x][y].top = false
                               area[nb[c].x][nb[c].y].bottom = false
                       elseif c == 2 then
                               area[x][y].bottom = false
                               area[nb[c].x][nb[c].y].top = false
                       elseif c == 3 then
                               area[x][y].left = false
                               area[nb[c].x][nb[c].y].right = false
                       elseif c == 4 then
                               area[x][y].right = false
                               area[nb[c].x][nb[c].y].left = false
                       end
                       -- Salva as modificações na área e faz a recursão
                       self:set_area(area)
                       self:handle_cell(nb[c].x, nb[c].y)
               end
       end
       -- No fim de tudo, salva a área do labirinto gerado
       self:set_area(area)
end

-- Gera um novo labirinto
function Maze:generate_maze()
       local size = self:get_size()
       local centerx = math.floor(math.ceil(size.width/2)/2)
       local centery = math.floor(math.ceil(size.height/2)/2)
       self:handle_cell(centerx, centery);

       local area = self:get_area()
       local exit = self:get_exit()
       area[exit.x][exit.y].bottom = false
       self:set_area(area)
end

-- Método útil para limpar a área do labirinto dentro do jogo
function Maze:clean(callback, wall, winner)
       winner = winner or false
       local start = self:get_topleft_pos()
       local size = self:get_size()
       -- Faz uma varredura pela área
       for x = start.x, start.x + size.width-1 do
               for y = start.y, start.y + size.height-1 do
                       local pos = {x=x, y=y, z=start.z, stackpos=1}
                       -- Enquanto existirem itens na posição, continue
                       -- a enviá-los para a função callback
                       while getThingFromPos(pos, false).uid ~= 0 do
                               local thing = getThingFromPos(pos, false)
                               if wall and thing.itemid == self:get_wall() then
                                       doRemoveThing(thing.uid)
                               else
                                       callback(self, thing, winner)
                               end
                               pos.stackpos = pos.stackpos + 1
                       end
               end
       end
end

-- Método para aplicar uma área de labirinto gerada em uma área do Tibia
-- Mesmo que uma área de labirinto seja criada e gerada, nada aparecerá no
-- Tibia caso se esta função não for chamada
function Maze:apply_maze()
       local pos = self:get_topleft_pos()
       local wall = self:get_wall()
       local area = self:get_area()
       local size = self:get_size()
       -- Faz uma varredura pela área
       for x = 1, self:to_maze(size.width) do
               for y = 1, self:to_maze(size.height) do
                       -- Pega a célula da posição atual
                       local cell = area[x][y]
                       local rawpos = {x=pos.x+x*2-1, y=pos.y+y*2-1, z=pos.z}
                       rawpos.stackpos = 1

                       -- Cria as paredes fixas (que não precisam ser geradas)
                       -- em seus respectivos lugares
                       local cpos = self:get_top_left(rawpos)
                       if getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
                       local cpos = self:get_top_right(rawpos)
                       if getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
                       local cpos = self:get_bottom_left(rawpos)
                       if getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
                       local cpos = self:get_bottom_right(rawpos)
                       if getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end

                       -- Cria as paredes geradas em seus respectivos lugares
                       local cpos = self:get_top(rawpos)
                       if cell.top and getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
                       local cpos = self:get_bottom(rawpos)
                       if cell.bottom and getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
                       local cpos = self:get_left(rawpos)
                       if cell.left and getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
                       local cpos = self:get_right(rawpos)
                       if cell.right and getThingFromPos(cpos, false).uid == 0 then
                               doCreateItem(wall, cpos)
                       end
               end
       end
end

 

/data/movements/scripts/maze.lua

-- maze.lua
-- This file is part of Maze minigame
--
-- Copyright (C) 2011 Skyen Hasus
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

-- Ao carregar o script, carregar também a biblioteca
dofile(getDataDir() .. "movements/lib/libmaze.lua")

-- -----------------------------------------------------------------------------
-- Criação e configuração de um novo labirinto:
-- -----------------------------------------------------------------------------

local maze = Maze:new()

maze:add_entrance({
       tile = {x=1000, y=1016, z=7},
       init = {x=1000, y=1018, z=7},
       exit = {x=1000, y=1014, z=7},
})

maze:add_entrance({
       tile = {x=1012, y=1016, z=7},
       init = {x=1012, y=1018, z=7},
       exit = {x=1012, y=1014, z=7},
})

maze:set_relocation_pos(1006, 1012, 7)
maze:set_topleft_pos(999, 1017, 7)
maze:set_exit(4, 7)
maze:set_wall(1483)
maze:set_size(15, 15)

-- -----------------------------------------------------------------------------

-- Gera uma nova semente aleatória, garantindo que cada labirinto seja único
math.randomseed(os.time())

-- Função callback para limpeza do labirinto
local function clear_thing(maze, thing, winner)
       local entrances = maze:get_entrances()
       local players = maze:get_players()
       if isPlayer(thing.uid) and players[thing.uid] then
               if winner then
                       doPlayerSendTextMessage(thing.uid, 25,
                       getPlayerName(winner) .. " completed the maze!")
               end
               doTeleportThing(thing.uid, entrances[players[thing.uid]].exit)
       else
               doTeleportThing(thing.uid, maze:get_relocation_pos())
       end
end

-- Função callback principal do evento StepIn
function onStepIn(cid, item, position)
       if not isPlayer(cid) then
               return true
       end
       doTransformItem(item.uid, item.itemid+1)

       if item.actionid == 1001 and maze:is_available() then
               if not maze:generate_area() then
                       return false
               end
               maze:generate_maze()
               maze:clean(clear_thing, true)
               maze:apply_maze()
               for id, entrance in ipairs(maze:get_entrances()) do
                       local player = getTopCreature(entrance.tile)
                       doTeleportThing(player.uid, entrance.init)
                       maze:add_player(id, player.uid)
               end
       elseif item.actionid == 1002 then
               local entrances = maze:get_entrances()
               local players = maze:get_players()
               doTeleportThing(cid, entrances[players[cid]].exit)
               doPlayerSendTextMessage(cid, 25, "You completed the maze!")
               maze:clean(clear_thing, false, cid)
               maze:rem_players()
       end
       return true
end

-- Função callback principal do evento StepOut
function onStepOut(cid, item)
       if not isPlayer(cid) then
               return true
       end
       doTransformItem(item.uid, item.itemid-1)
       return true
end

 

------

 

Isso aew, obrigado a todos que participou do concurso e este foi o grande vencedor.

Link para o comentário
Compartilhar em outros sites

  • 1 month later...

Mano teria como da uma explicada melhor como funciona o script? ele da um erro!

[Error - MoveEvents Interface]
data/movements/scripts/maze.lua
Description:
(luaDoCreateItem) Tile Not Found

 

me explica melhor essa parte:

Relocation Position: Posição para onde serão teleportados os itens e players não relacionados ao minigame quando for executada uma limpeza do labirinto.

 

maze:set_relocation_pos(x, y, z)

 

 

 

Top-Left Position: Posição superior-esquerda do primeiro tile da área do labirinto.

 

maze:set_topleft_pos(x, y, z)

 

 

 

Exit: Posição da célula do labirinto que será usada como saída. Vale lembrar que esta célula deve estar na parte inferior do labirinto (Isto pode ser alterado mudando a linha 290 do libmaze.lua) e não é contada como uma posição em SQM do Tibia, e sim como número da célula.

 

maze:set_exit(x, y)

 

 

 

Wall ID: ItemID da parede do labirinto que será gerada.

 

maze:set_wall(itemid)

 

 

 

Para adicionar uma nova entrada no labirinto, usar:

 

maze:add_entrance({

tile = {x=?, y=?, z=?},

init = {x=?, y=?, z=?},

exit = {x=?, y=?, z=?},

})

_____________________________________________________________________________________________________________________________________

Ajudei? verdinha :button_ok:

61ec48409f664c3bb1ef6ef151dcbd11.0.gif

Editado por infernity
Link para o comentário
Compartilhar em outros sites

  • 1 month later...
  • 3 weeks later...

Kra funcionou aqui mais eu queria sabe se ele num deveria limpa todos os wall q aparecerem quando eles pisarem na saida ?

pq aqui não ta limpando eles pisam no tile de exit e o labirinto só muda não limpa tudo!

 

vlw pelo sistema :thumbsupsmiley:

Link para o comentário
Compartilhar em outros sites

  • 3 years later...
×
×
  • Criar Novo...