Fala pessoal do XT, hoje vim trazer pra vocês um sistema que eu desenvolvi nessa última semana e inclusive fiz um tópico na seção de show off (confira o tópico aqui) numa tentativa de agitar um pouco e prover uma certa especulação em relação ao sistema. Bom, eu to aqui hoje pra informar que deu mais que certo e que meu tópico humilde (que eu realmente acreditava que seria ignorado, ainda mais numa seção morta como aquela) teve uma repercursão enorme (inclusive indo parar no portal do XT )
Bom, toda pessoa quando faz um código espera que ele agrade aos demais e tenha um certo valor, e bem, vocês superaram minhas expectativas.. com isso nada mais justo do que compartilhar com essa comunidade que me recebeu tão bem .
Chega de falatório e vamos direto aos negócios:
- Introdução:
O sistema em si é uma cópia dos jogos de ritmo (GuitarFreaks, Band Hero, Rocksmith e o próprio Guitar Hero). Neste jogo pequenas notas irão cair pela tela até atingir um local especial, onde você poderá usar as teclas (Shift + 7, Shift + 8, Shift + 9, ou apenas 7, 9 e 9 [com Num Lock desativado]) para escolher as direções que as notas estão afim de "ativá-las". Uma vez ativada a nota ela será "queimada" e será considerado 1 acerto. Caso você não consiga ativar a nota e ela passar direto, um efeito de fumaça indicará o "Miss", o mesmo vale para se você tentar ativar uma região sem notas.
- Sistema de pontuação:
Haverão 3 classificadores estatísticos para cada música tocada, sendo eles:
Notas acertadas: O principal fator de ponto, ele que definirá seu recorde e o valor máximo q ele pode assumir será o número de notas da música.
Movimentos errados: Quantidade de movimentos que você fez para alguma região que não possuia notas, conta como pontuação punitiva.
Notas passadas/erradas: Quantidade de notas que você deixou escapar, note que as notas acertadas + notas passadas compõe o número total de notas da música.
Por enquanto o sistema não possui nenhum preço para se jogar mas também não possui nenhuma recompensa, fiz com o intuito de ser apenas um mini game divertido dentro do Tibia. Porém o código é livre e está disponibilizado aqui embaixo pra qualquer um modificar como desejar.
-
Video explicativo:
- Dificuldades:
O mini game possui 3 dificuldades, porém essa quantidade é tão configurável quanto as pré definições de cada dificuldade. Além da quantidade de notas ser diferente, a velocidade com que elas descem e consequentemente o tempo que você tem pra acertá-las fica diminuido.
- Diferenciais:
Um forte diferencial desse sistema é que ele foi feito seguindo todas as regras possíveis pra melhorar seu desempenho, desde funções recursivas leves até separação em funções encapsuladas que carregam apenas as informações necessárias. O sistema contém também algumas seguranças que serão explicadas mais abaixo juntamente com o código para prevenir bugs e garantir diversão sem dores de cabeça!
- Instalação (Map Editor):
Primeiramente faça uma área parecida com essa daqui:
Não precisa ser igualzinha, apenas seguir a mesma proporção
Agora use a ferramenta de No-Logout para setar os quadradinhos que contém UID e UID2 escritos como No-Logout, isso vai impedir que players desloguem e fiquem presos dentro da sala (se eles derem exit, quando eles relogarem estarão do lado de fora da sala)
Ainda olhando a foto acima, coloque 42320 nos pisos que estão escritos UID, e 42319 no piso que contém UID2.
Nas alavancas, coloque uid 2819 em todas elas e coloque actionid 101 para a alavanca do fácil, 102 na alavanca do médio e 103 na do dificil;
A regra é sempre 100 + dificuldade, como nós estamos utilizando apenas 3 será do 101 ao 103.
Note que se você esquecer ou colocar uma dificuldade não existente o sistema adotará a dificuldade 1 como padrão para aquela alavanca.
Se chegou até aqui, meus parabéns. Muito provável que você tenha feito tudo certo. Agora vamos à parte do código.
- Instalação (scripts):
Em actions.xml insira essa linha
<action uniqueid="2819" event="script" value="GHtest.lua"/>
Agora em data/actions/scripts crie um arquivo com extensão.lua e chameo de GHtest. Insira o seguinte bloco de instruções dentro dele:
local function isRoomEmpty() -- funcao que verifica se a sala nao esta ocupada return getGlobalStorageValue(19281) < 1 and true or false end local npos = {x = 141, y = 51, z = 7} -- posicao mais da esquerda que as notas nascem, as outras duas vao ser baseadas nessa daqui x = 142 e x = 143 local startPos = {x = 142, y = 58, z= 7} -- posicao que vc vai ao clicar na alavanca local finalPos = {x = 145, y = 57, z= 7} -- poscao q vc vai ao terminar o tempo local storage_point = 12721 -- storage que salva os pontos local storage_erradas = 12722 -- storage que salva as notas erradas local storage_passadas = 12723 -- storage que salva as notas que passaram function onUse(cid, item, itemEx, fromPos, toPos) if isRoomEmpty() then doTeleportThing(cid, startPos) setGlobalStorageValue(19281, 1) setGlobalStorageValue(19282, 0) local d = dif[item.actionid - 100] and item.actionid - 100 or 1 addEvent(function() setGlobalStorageValue(19281, 0) if isCreature(cid) then if getPlayerStorageValue(cid, storage_point) > getPlayerStorageValue(cid, recorde[d]) then setPlayerStorageValue(cid, recorde[d], getPlayerStorageValue(cid, storage_point)) end setPlayerStorageValue(cid, storage_passadas, getGlobalStorageValue(19282)) local msg = "Estatísticas da partida:\n\n| Notas acertadas: ".. getPlayerStorageValue(cid, storage_point) .."\n| Movimentos errados: ".. getPlayerStorageValue(cid, storage_erradas) .."\n| Notas passadas: ".. getPlayerStorageValue(cid, storage_passadas) .."\n\n| Seu recorde pessoal para esta dificulade é: ".. getPlayerStorageValue(cid, recorde[d]) .. "" doShowTextDialog(cid, 2349, msg) doTeleportThing(cid, finalPos) end end, (#dif[d] + 9) * delay[d]) for j = 1, #dif[d] do addEvent(function() if dif[d][j] ~= nil then callWave({x= npos.x + dif[d][j], y = npos.y, z = npos.z}, 0, 1679 + dif[d][j], delay[d], 19282) end end, (delay[d] + 30) * j) end else doPlayerSendCancel(cid, "The room is busy right now, try again later.") end return true end
Aproveite e já configure as posições npos, startPos e finalPos de acordo com as posições do seu map editor (olhe na segunda imagem para ver onde deve ser cada uma das posições com base na sala)
Feito isso crie um arquivo em data/lib com nome qualquer, aqui em casa eu utilizei o nome 120 - Guitar Hero Lib e a extensão.lua.
Coloque isto dentro desse arquivo e salve:
--[[ Lib for Guitar Hero: by Night Wolf (aka xWhiteWolf) and dalvorsn ]] function callWave(pos, i, id, delay, gstr) -- funcao que carrega cada nota pra baixo if i > 0 then prevPos = {x = pos.x, y = pos.y - 1 + i , z = pos.z} local prevItem = getTileItemById(prevPos, id) if prevItem.uid > 0 then doRemoveItem(prevItem.uid, 1) if i == 7 then doSendMagicEffect(prevPos, 2) setGlobalStorageValue(gstr, getGlobalStorageValue(gstr) + 1) end end end if i < 7 then local cPos = {x = pos.x, y = pos.y + i, z = pos.z} local item = doCreateItem(id, 1, cPos) if i == 6 then doItemSetAttribute(item, "aid", 1010) end addEvent(callWave, delay, pos, i+1, id, delay, gstr) end end function cleanRoom(npos) for x = 0, 2 do for y = 0, 6 do local pos = {x = npos.x + x, y = npos.y + y, z = npos.z } local tile_info = getTileInfo(pos) if tile_info.trashed then doCleanTile(pos) end end end end dif = { -- [dificuldade] = notas que serao tocadas (nil = vazio, 0 = primeira posicao, 1 = segunda e 2 = terceira) [1] = {nil,nil,0,1,2,nil,nil,2,1,1,0,0,2,0,1}, [2] = {nil,nil,0,1,1,2,1,1,0,0,2,0,1,nil,1,2,1,1,0,0,2,0,1}, [3] = {nil,nil,0,nil,1,nil,1,1,0,0,2,0,1,1,1,nil,0,2,1,1,0,0,2,1,1,0,nil,2,2,1,1,1,1,2} } delay = { -- delay entre cada nota de acordo com a dificuldade [dificuldade] = delay [1] = 500, [2] = 400, [3] = 300 } recorde = { -- salva os recordes de cada dificuldade separadamente [1] = 12724, [2] = 12725, [3] = 12726 }
Agora adicione a seguinte tag em data/movements/movements.xml:
<movevent type="StepIn" uniqueid="42320;42319" event="script" value="GHTestmov.lua"/>
Por fim crie um arquivo chamado GHTestmov.lua em data/movements/scripts e preencha seu conteúdo com as linhas abaixo:
local itemid = 1680 --- coloque o id da nota do meio local npos = {x = 141, y = 51, z = 7} -- posicao que ira nascer a primeira nota local storage_point = 12721 -- storage que salva os pontos local storage_erradas = 12722 -- storage que salva as notas erradas local storage_passadas = 12723 -- storage que salva as notas que passaram function onStepIn(cid, item, position, lastPosition, fromPosition, toPosition, actor) if item.uid == 42319 then if math.abs(fromPosition.x - toPosition.x) > 2 then cleanRoom(npos) setPlayerStorageValue(cid, storage_erradas, 0) setPlayerStorageValue(cid, storage_point, 0) return true end else local corrector = toPosition.x - fromPosition.x local checkItem = getTileItemById(toPosition, itemid + corrector) if checkItem.uid > 0 and checkItem.actionid == 1010 then doRemoveItem(checkItem.uid, 1) doSendMagicEffect(toPosition, 15) setPlayerStorageValue(cid, storage_point, getPlayerStorageValue(cid, storage_point) + 1) else doSendMagicEffect(toPosition, 2) setPlayerStorageValue(cid, storage_erradas, getPlayerStorageValue(cid, storage_erradas) + 1) end doTeleportThing(cid, fromPosition) end return true end
Aproveite para deixar a npos idêntica à npos que você configurou lá no actions se baseando na posição da sua sala no map editor.
------------------------ FIM ------------------
O sistema já deve funcionar como foi planejado, se você não manja de código ou quer o sistema exatamente como foi mostrado no vídeo você pode ignorar a parte abaixo.
-
Configurando seu sistema (básico):
Para as pessoas que querem configurar apenas as coisas básicas, você pode fazê-lo nas primeiras linhas de cada script (actions/movements) ou alterando diretamente na lib, irei ensinar passo a passo aqui oque significa cada coisa.
- Alterando a lib
As coisas básicas que podem ser alteradas na lib são as seguintes:
dif = { -- [dificuldade] = notas que serao tocadas (nil = vazio, 0 = primeira posicao, 1 = segunda e 2 = terceira) [1] = {nil,nil,0,1,2,nil,nil,2,1,1,0,0,2,0,1}, [2] = {nil,nil,0,1,1,2,1,1,0,0,2,0,1,nil,1,2,1,1,0,0,2,0,1}, [3] = {nil,nil,0,nil,1,nil,1,1,0,0,2,0,1,1,1,nil,0,2,1,1,0,0,2,1,1,0,nil,2,2,1,1,1,1,2} } delay = { -- delay entre cada nota de acordo com a dificuldade [dificuldade] = delay [1] = 500, [2] = 400, [3] = 300 } recorde = { -- salva os recordes de cada dificuldade separadamente [1] = 12724, [2] = 12725, [3] = 12726 }
dif separa como será cada música de cada dificuldade, as notas sempre serão nesse exato formato. nil significa que não estarei usando posição nenhuma naquela linha, 0 significa a primeira posição (nota verde), 1 significa segunda opção (nota vermelha) e 2 significa terceira opção (nota azul). Eu aconselho a começar todas as músicas com 2 ou mais 'nil' para dar tempo de o player entender que vai começar. A distribuição que eu usei foi meio que aleatória então sinta-se a vontade pra mudar tanto o tamanho das musicas quanto a variedade de notas.
delay é a tabela que oganiza os delays de cada dificuldade, isso significa que para a dificuldade 1(fácil) nós temos 500 milisegundos para cada mudança de nota (1000 milisegundos = 1 segundo), ou seja, a cada 2 passadas de nota temos 1 segundo dentro da sala. No modo hard a cada 300 milisegundos mudamos a nota, deixando o jogo muito mais rápido e dificil.
recorde é a tabela que possui os storages que irão salvar os recordes nas respectivas dificuldades.
- Alterando actions
local storage_point = 12721 -- storage que salva os pontos local storage_erradas = 12722 -- storage que salva as notas erradas local storage_passadas = 12723 -- storage que salva as notas que passaram
Apenas mude os storages
- Alterando movements
local itemid = 1680 --- coloque o id da nota do meio local npos = {x = 141, y = 51, z = 7} -- posicao que ira nascer a primeira nota local storage_point = 12721 -- storage que salva os pontos local storage_erradas = 12722 -- storage que salva as notas erradas local storage_passadas = 12723 -- storage que salva as notas que passaram
aconselho mexer somente nos storages, mantendo igual à action.
Só abra o spoiler abaixo se você estiver descontente com a forma que seu código roda e tiver certeza absoluta de que você tem o conhecimento necessário para alterar as coisas que vou explicar.
- Configurações (intermédiarias e avançadas)
Na lib ainda podemos definir comprimento da area que vamos utilizar, o padrão é 7 sqms mas podemos aumentar alterando a função callWave que está na lib.
function callWave(pos, i, id, delay, gstr) -- funcao que carrega cada nota pra baixo
if i > 0 thenprevPos = {x = pos.x, y = pos.y - 1 + i , z = pos.z}local prevItem = getTileItemById(prevPos, id)if prevItem.uid > 0 thendoRemoveItem(prevItem.uid, 1)if i == 7 thendoSendMagicEffect(prevPos, 2)setGlobalStorageValue(gstr, getGlobalStorageValue(gstr) + 1)endendendif i < 7 thenlocal cPos = {x = pos.x, y = pos.y + i, z = pos.z}local item = doCreateItem(id, 1, cPos)if i == 6 thendoItemSetAttribute(item, "aid", 1010)endendif i < 7 thenaddEvent(callWave, delay, pos, i+1, id, delay, gstr)endend
Nos locais que estão demarcados em vermelho você deve aumentar sempre seguindo a proporção, se coloca 9 no lugar do 7 o 6 deverá virar um 8. Arrume também no actions nessa parte aqui:
end, (#dif[d] + 9) * delay[d])
e coloque 2 números acima do numero de sqms que você irá percorrer. Se colocar 9 sqms, use 11 ali.
Note que o nosso sistema possui um sistema de clean antes de cada partida, esse sistema foi desenvolvido pelo nosso colega @dalvorsn e também se baseia no numero de sqms que o sistema possui. Altere nessa linha da lib
for y = 0, 6 do
Altere o 6 para o numero de sqms - 1. Se tiver 9 sqms vc coloca o 8 ali.
Ao chegar na posição que podemos tocar a nota, ela recebe um action id para garantir que pessoas não joguem pillows naquela posição e façam pontos de forma roubada, se quiser alterar o actionid utilizado mude na linha em verde, MAS LEMBRE-SE DE MUDAR TAMBÉM NESSA LINHA DO MOVEMENTS
if checkItem.uid > 0 and checkItem.actionid == 1010 then
Alterando as mensagens:
local msg = "Estatísticas da partida:\n\n| Notas acertadas: ".. getPlayerStorageValue(cid, storage_point) .."\n| Movimentos errados: ".. getPlayerStorageValue(cid, storage_erradas) .."\n| Notas passadas: ".. getPlayerStorageValue(cid, storage_passadas) .."\n\n| Seu recorde pessoal para esta dificulade é: ".. getPlayerStorageValue(cid, recorde[d]) .. ""doShowTextDialog(cid, 2349, msg)
a primeira linha são os textos (o \n serve para pular linha)
a segunda linha em azul indica o id do item que será mostrado na janela ao te dar as estatísticas, utilizei o item 2349 que é a blue note.
Se você achar que o sistema está com problema para reconhecer notas iguais seguidas, procure no actions essa linha aqui
end, (delay[d] + 30) * j)
e ali no 30 coloque um numero maior (tente fazer algo menor que 100 para evitar muita diferença entre as chamas de nota e as transições de nota). No meu servidor eu uso 20, coloquei 30 pra vocês aqui mas alguns servers mais lagados podem precisar de numeros em torno de 50.
Para alterar os ids utilizados você deve modificar a função callWave, o parametro dela recebe a posição que a nota deverá ir (0, 1 ou 2) e soma no id do pillow.
1679 é o id que será usado caso a nota seja 0
1680 é o id que será usado caso a nota seja 1
1681 é o id que será usado caso a nota seja 2.
1679 + dif[d][j]
Altere tanto essa linha do actions
callWave({x= npos.x + dif[d][j], y = npos.y, z = npos.z}, 0, 1679 + dif[d][j], delay[d], 19282)
quanto o itemid no movements
local itemid = 1680 --- coloque o id da nota do meio
Por fim, se vc for alterar os uniqueids utilizados na tag do movements.xml, não esqueça de alterar também no script do movements.
if item.uid == 42319 then
<movevent type="StepIn" uniqueid="42320;42319" event="script" value="GHTestmov.lua"/>
Espero que vocês tenham curtido, façam um bom uso e qualquer dúvida postem nos comentários abaixo.
Agradecimento ao @dalvorsn pela função que limpa a sala antes de começar o jogo e ao pessoal do servidor Refugia que fez um script de guitar hero lá e me deu a idéia. Abraços do Lobo.