Ir para conteúdo
Propaganda

Posts Recomendados

  • Administrador

Existem muitos tutoriais sobre como escrever scripts para o TFS 1.x, então decidi escrever um breve tutorial sobre como NÃO escrever scripts para o TFS 1.x.

Neste tutorial irei descrever dois erros comuns que resultam em travamento do servidor!

 

A primeira coisa que todos notam quando mudam o mecanismo do TFS 0.x para 1.x são os objetos no LUA. Existem muitos deles, mas os mais populares são: Jogador, Monstro, Npc, Criatura, Bloco, Item, Recipiente, Posição.

O que a maioria dos criadores de scripts não sabe é que esses objetos são vinculados diretamente ao endereço na memória RAM (a objetos C++). Se você tentar usar o objeto C++ (por exemplo, obter o nome do jogador) que foi excluído, isso resultará em crash do servidor. O ruim disso é: você não pode verificar se o objeto foi excluído em C++!

Os programadores C++ estão acostumados a tomar cuidado para não usar objetos excluídos. Os criadores de scripts LUA em todos os mecanismos antigos sempre recebem uma boa mensagem de erro quando usam uma variável que não existe (por exemplo, Criatura não encontrada). Existem 2 erros comuns que resultam em travamento. A pior coisa sobre eles é que nem sempre travam o servidor. Você pode escrever um código como esse, testá-lo, colocá-lo em seu servidor, muitos jogadores irão usá-lo... e depois de uma semana você terá um travamento aleatório.

 

1° Problema

Spoiler

Passando objeto para addEvent - addEvent é uma função que executa determinada função com algum atraso. Exemplo - imprima o texto após 5 segundos:

function executeMeLater()
    print('printed after 5 seconds!')
end

addEvent(executeMeLater, 5000)

Você pode passar argumentos para funções executadas com atraso:

function executeMeLater(param1, param2)
    print(param1)
    print(param2)
end

addEvent(executeMeLater, 5000, 'test string', 12345)

Irá imprimir:

test string
12345

Depois de 5 segundos.

 

Qual é o problema em passar objeto para ele? Objeto - na memória RAM - já pode ser excluído!

Exemplo de talkaction que trava o servidor:

function buggedEvent1(player)
   print('Execute bugged event 1')
   print(player)
end

function buggedEvent2(hiddenPlayerObject)
   print('Execute bugged event 2')
   print(hiddenPlayerObject.myPlayerVariable:getName())
end

function onSay(player, words, param)
   print('onSay, player name: ' .. player:getName())
-- TRY 1
   addEvent(buggedEvent1, 4000, player)

-- TRY 2
   local hiddenPlayerObject = { myPlayerVariable = player }
   addEvent(buggedEvent2, 5000, hiddenPlayerObject)
   return true
end

O que acontece se o jogador permanecer online por 5 segundos após executar esta talkaction:

onSay, player name: Gesior

Lua Script Error: [TalkAction Interface]
data/talkactions/scripts/crashAddEvent.lua:onSay
luaAddEvent(). Argument #3 is unsafe
stack traceback:
    [C]: in function 'addEvent'
    data/talkactions/scripts/crashAddEvent.lua:14: in function <data/talkactions/scripts/crashAddEvent.lua:11>
Execute bugged event 1
268435457
Execute bugged event 2
Gesior

Como você pode ver, existe uma proteção simples contra a passagem de objetos como argumentos para addEvent. Passá-lo diretamente como argumento resulta na mudança do objeto Player para a versão antiga 'cid' (número de ID temporário do jogador). No segundo exemplo de evento coloquei o objeto Player dentro da tabela para ignorar o código de proteção. Dessa forma eu poderia usar ':getName()' no objeto, pois não foi convertido para o número 'cid'.

O que acontece se o jogador fizer login novamente dentro de 5 segundos após executar esta ação de conversação:

onSay, player name: Gesior

Lua Script Error: [TalkAction Interface]
data/talkactions/scripts/crashAddEvent.lua:onSay
luaAddEvent(). Argument #3 is unsafe
stack traceback:
    [C]: in function 'addEvent'
    data/talkactions/scripts/crashAddEvent.lua:14: in function <data/talkactions/scripts/crashAddEvent.lua:11>
Gesior has logged out.
Execute bugged event 1
268435456
Execute bugged event 2
Segmentation fault (core dumped)

SERVIDOR CRASHADO!

Ele tentou carregar o objeto Player passado (use ':getName()' nele). O Player relogou, então determinada instância do Player foi removida em C++. relatório gdb após falha:

#0  0x0000000000000000 in ?? ()
No symbol table info available.
#1  0x000055d4fed50615 in LuaScriptInterface::luaCreatureGetName(lua_State*) ()
No symbol table info available.
#2  0x00007f8865d55e37 in ?? () from /usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2
No symbol table info available.
#3  0x00007f8865da327c in lua_pcall () from /usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2
No symbol table info available.
#4  0x000055d4fedc9274 in LuaScriptInterface::protectedCall(lua_State*, int, int) ()
No symbol table info available.
#5  0x000055d4fedca368 in LuaScriptInterface::callFunction(int) ()
No symbol table info available.
#6  0x000055d4fed39af2 in LuaEnvironment::executeTimerEvent(unsigned int) ()
No symbol table info available.
#7  0x000055d4fed1eb6e in void std::__invoke_impl<void, void (LuaEnvironment::*&)(unsigned int), LuaEnvironment*&, unsigned int&>(std::__invoke_memfun_deref, void (LuaEnvironment::*&)(unsigned int), LuaEnvironment*&, unsigned int&) ()
No symbol table info available.
#8  0x000055d4fed1bcac in std::__invoke_result<void (LuaEnvironment::*&)(unsigned int), LuaEnvironment*&, unsigned int&>::type std::__invoke<void (LuaEnvironment::*&)(unsigned int), LuaEnvironment*&, unsigned int&>(void (LuaEnvironment::*&)(unsigned int), LuaEnvironment*&, unsigned int&) ()
No symbol table info available.
#9  0x000055d4fed16be2 in void std::_Bind<void (LuaEnvironment::*(LuaEnvironment*, unsigned int))(unsigned int)>::__call<void, , 0ul, 1ul>(std::tuple<>&&, std::_Index_tuple<0ul, 1ul>) ()
No symbol table info available.
#10 0x000055d4fed1048d in void std::_Bind<void (LuaEnvironment::*(LuaEnvironment*, unsigned int))(unsigned int)>::operator()<, void>() ()
No symbol table info available.
#11 0x000055d4fed09916 in std::_Function_handler<void (), std::_Bind<void (LuaEnvironment::*(LuaEnvironment*, unsigned int))(unsigned int)> >::_M_invoke(std::_Any_data const&) ()
No symbol table info available.
#12 0x000055d4febfaec8 in std::function<void ()>::operator()() const ()
No symbol table info available.
#13 0x000055d4febfa9fe in Task::operator()() ()
No symbol table info available.
#14 0x000055d4febfac2c in Dispatcher::threadMain() ()

 

2° Problema

Spoiler

Manter o objeto em variável para uso futuro no script.

Há um exemplo de uso comum em script - manter o último jogador que executou o script (que entrou em alguma sala, que usou alguma alavanca):

local lastPlayer = nil

function onSay(player, words, param)
   print('onSay, player name: ' .. player:getName())
   if lastPlayer ~= nil then
      print('onSay, last player name: ' .. lastPlayer:getName())
   end
   lastPlayer = player
   return true
end

Você pode executar este código quantas vezes quiser e ele funcionará bem.. até que você relogue ou o jogador que o usou por último, saia e outra pessoa o execute.

relatório gdb após falha:

#0  0x00007ff55a471d20 in ?? ()
No symbol table info available.
#1  0x000055dffa717615 in LuaScriptInterface::luaCreatureGetName(lua_State*) ()
No symbol table info available.
#2  0x00007ff560fd0e37 in ?? () from /usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2
No symbol table info available.
#3  0x00007ff56101e27c in lua_pcall () from /usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2
No symbol table info available.
#4  0x000055dffa790274 in LuaScriptInterface::protectedCall(lua_State*, int, int) ()
No symbol table info available.
#5  0x000055dffa791368 in LuaScriptInterface::callFunction(int) ()
No symbol table info available.
#6  0x000055dffa5f8554 in TalkAction::executeSay(Player*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, SpeakClasses) const ()
No symbol table info available.
#7  0x000055dffa5f81c1 in TalkActions::playerSaySpell(Player*, SpeakClasses, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const ()
No symbol table info available.
#8  0x000055dffa827c4f in Game::playerSaySpell(Player*, SpeakClasses, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) ()
No symbol table info available.
#9  0x000055dffa827910 in Game::playerSay(unsigned int, unsigned short, SpeakClasses, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) ()
No symbol table info available.

 

Como tornar os scripts seguros? Passe cid/guid/itemid/position, não objeto.

Script do Problema 1 - versão segura:

Spoiler
function buggedEvent1(player)
   print('Execute bugged event 1')
   print(player)
end

function buggedEvent2(hiddenPlayerObject)
   print('Execute bugged event 2')
   -- FIX: TENTE CRIAR OBJETO 'PLAYER' A PARTIR DE SEU CID
   local myPlayer = Player(hiddenPlayerObject.myPlayerVariable)
   -- FIX: IRÁ CRIAR O OBJETO 'PLAYER' OU RETORNAR 'nil'
   if myPlayer then
      -- FIX: 'if' VERIFICOU QUE 'myPlayer' NÃO É 'nil',
      -- ASSIM CRIAR O OBJETO 'PLAYER' FUNCIONOU
      -- PODEMOS USAR 'getName'
      print(myPlayer:getName())
   end
end

function onSay(player, words, param)
   print('onSay, player name: ' .. player:getName())
   -- TRY 1
   addEvent(buggedEvent1, 4000, player)

   -- TRY 2
   -- FIX: PASS ID (cid) DO JOGADOR, NÃO DO OBJETO
   local hiddenPlayerObject = { myPlayerVariable = player:getId() }
   addEvent(buggedEvent2, 5000, hiddenPlayerObject)
   return true
end

 

Script do Problema 2 - versão segura:

Versão com ID do jogador (em scripts antigos chamados 'cid'). É um ID de jogador temporário. Ele muda quando o jogador relog/morre:

Spoiler
local lastPlayerCid = nil

function onSay(player, words, param)
   print('onSay, player name: ' .. player:getName())
   -- FIX: TENTE CRIAR OBJETO 'PLAYER' A PARTIR DE SEU CID
   local lastPlayer = Player(lastPlayerCid)
   -- FIX: IRÁ CRIAR O OBJETO 'PLAYER' OU RETORNAR 'nil'
   if lastPlayer then
      print('onSay, last player name: ' .. lastPlayer:getName())
   end
   -- FIX: ARMAZENAR ID DO JOGADOR (cid), NÃO OBJETO
   lastPlayerCid = player:getId()
   return true
end

Versão com GUID do player. É o ID do jogador do banco de dados. Isso nunca muda.

local lastPlayerGuid = nil

function onSay(player, words, param)
   print('onSay, player name: ' .. player:getName())
   -- FIX: TENTE CRIAR OBJETO 'PLAYER' A PARTIR DO GUID
   local lastPlayer = Player(lastPlayerGuid)
   -- FIX: IRÁ CRIAR O OBJETO 'PLAYER' OU RETORNAR 'nil'
   if lastPlayer then
      print('onSay, last player name: ' .. lastPlayer:getName())
   end
   -- FIX: ARMAZENAR ID DO JOGADOR (cid), NÃO OBJETO
   lastPlayerGuid = player:getGuid()
   return true
end

 

Existem 2 razões principais para usar a versão do ID:

  • Às vezes é útil saber que o jogador relogou/morreu (alterou ID). Se ele foi para a zona sem logout e seu ID mudou, significa que ele morreu.

  • Também funciona para objetos Monstro, Npc e Criatura. Você pode armazenar o ID do monstro/npc/player e carregá-lo da mesma maneira - alguns scripts LUA funcionam para monstros (ex. movements).

Há um motivo para usar o GUID:

- Alguns scripts precisam saber se o jogador está online, mesmo que ele tenha relogado.

 

Créditos: Gesior.pl

  • Thanks 2
Link para o comentário
https://tibiadevs.com/forums/topic/160-tfs-1x-como-n%C3%A3o-escrever-scripts-lua-ou-como-travar-o-servidor-por-script-lua/
Compartilhar em outros sites

  • Administrador
  • Moderador

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora
×
  • Criar Novo...