Maze Minigame

Ver o tópico anterior Ver o tópico seguinte Ir em baixo

Maze Minigame

Mensagem por Matheus em Sex Abr 29, 2011 2:47 pm

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:



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.
Código:
maze:set_relocation_pos(x, y, z)

Top-Left Position: Posição superior-esquerda do primeiro tile da área do labirinto.
Código:
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.
Código:
maze:set_exit(x, y)

Wall ID: ItemID da parede do labirinto que será gerada.
Código:
maze:set_wall(itemid)

Para adicionar uma nova entrada no labirinto, usar:
Código:
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
Código:
<!-- 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
Código:
-- 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

------
avatar
Matheus
Administrador
Administrador

Mensagens : 61
Placar : 207
Data de inscrição : 25/04/2011
Idade : 21
Localização : Campinas - SP

Ver perfil do usuário http://pokescript.forumeiros.com

Voltar ao Topo Ir em baixo

Ver o tópico anterior Ver o tópico seguinte Voltar ao Topo

- Tópicos similares

 
Permissão deste fórum:
Você não pode responder aos tópicos neste fórum