RA

[PokéTibia / TFS 0.3.6] Pokémon solto na house — limite, falas e persistência

Por raulcdj, há 14h em Mods, funções e outros

1
22
raulcdjR
01 de outubro de 2026 às 13:46

Atualização 1.2: nomes dos Pokémon corrigidos. Agora mostra o apelido da Ball ou a espécie. Reinicie o servidor para carregar o XML novo. Neste cliente, a barra de vida pode aparecer junto; o Pokémon continua protegido contra dano.

[PokéTibia / TFS 0.3.6] Pokémon solto na house — limite, falas e persistência

Apresentação

Salve, pessoal! Estou liberando o sistema de Pokémon solto na house que adaptei para minha base TFS 0.3.6 / PokexGeneration, com auxílio do Codex. A ideia veio daquele pedido antigo de deixar o Pokémon andando na house usando a Ball no chão.

Como funciona: o dono coloca uma Ball saudável no chão da house e dá use. O Pokémon aparece como decoração, anda dentro da house e fala as frases da espécie. Outro use recolhe. O limite inicial é 2 por house.

Recursos: trava da Ball enquanto exposta, proteção contra dano, sem combate, limite da própria house, atributos originais preservados (level, boost, shiny, nick, addon e helds) e restauração depois de restart a partir do save de houses. Alakazam também pode falar “Ala alak alakazam!”; Tropius usa “Trooo!”, “PIUSS!” e “TROPIUS!”.

IMPORTANTE: precisa alterar a source e recompilar o SERVIDOR. Não é uma versão universal para qualquer TFS, nem um sistema somente Lua. Não foi testado em TFS 1.x/OTX. Os códigos e o passo a passo estão abaixo, para copiar e adaptar.

Minha conta está sem espaço de anexos no fórum; por isso estou disponibilizando todo o código no próprio tópico. Não é necessário baixar um executável de outra pessoa.

Tutorial e compatibilidade

Pokémon solto na house — edição pública 1.2

Sistema para bases PokéTibia derivadas de TFS 0.3.6 / PokexGeneration. Testado na base em que foi desenvolvido. Não é um script universal para qualquer versão do TFS.

Funcionalidades

  • Ball com Pokémon recolhido e saudável, diretamente no chão da house.
  • Apenas o dono pode soltar/recolher; limite inicial de 2.
  • NPC decorativo com aparência da espécie ou addon da Ball, caminhada dentro da house, sem ataque e protegido contra dano.
  • Ball travada enquanto exposta, inclusive para movimentação por scripts.
  • Todos os atributos originais da Ball preservados: espécie/shiny, nick, level, boost, HP, exp, addon e helds.
  • Falas do XML da espécie: Alakazam também fala “Ala alak alakazam!”; Tropius usa “Trooo!”, “PIUSS!” e “TROPIUS!”. São textos de monstro, não áudio.
  • Recriação depois de restart com os marcadores salvos no armazenamento de houses.

Dependências e compatibilidade

É necessário ter a source e recompilar o SERVIDOR. O patch acrescenta getHouseDisplayBalls(); essa função não existe no TFS padrão. Não basta copiar os Lua.

A biblioteca usa os nomes desta família de bases: getPokeballType(itemid), tabela pokeballs[tipo].on, atributo poke (nome exato no registro de monstros), hp numérico positivo e addon como lookType ativo. O callback de ação precisa identificar a posição real da Ball. A pasta de dados deve ser data/, com data/monster/monsters.xml. O carregamento automático de data/lib/ deve estar ativo.

Outras bases podem ter nomes, IDs, saves, UIDs e atributos diferentes. Adapte esses pontos antes de instalar. Guild halls foram excluídas porque seu proprietário é um ID de guild, não o GUID de jogador. Não foi testado em TFS 1.x ou OTX.

Instalação

1. Faça backup da base. Em uma cópia de testes, coloque os três arquivos da pasta data/ nas mesmas pastas do servidor.
2. Se estiver copiando do tópico, salve o último bloco de código como house_pokemon_source.patch. Aplique o patch à source, a partir da pasta que contém source. Com Git disponível: git apply --check --ignore-space-change house_pokemon_source.patch e depois git apply --ignore-space-change house_pokemon_source.patch. A opção trata diferenças de espaços e finais de linha comuns em bases Windows. Se não aplicar, adapte manualmente os trechos (+ são adições, - são remoções). Não substitua arquivos completos da sua source por arquivos de outra base.
3. Recompile o servidor. As mudanças incluem registro da nova função Lua, exceção para o NPC dedicado em HouseTile, bloqueio de mover Balls, restrição de movimento/teleporte, proteção de dano e liberação de marcadores quando muda o dono.
4. No início de onUse do seu goback de actions, antes de alterar a Ball, adicione:

if HousePokemon.use(cid, item, frompos) then return true end

Se o parâmetro de posição chama fromPosition, use esse nome no lugar de frompos. Não troque o seu goback completo. Se a biblioteca não carregar automaticamente, carregue data/lib/house_pokemon.lua uma vez no bootstrap Lua de actions e de globalevents.

5. Dentro de <globalevents> no data/globalevents/globalevents.xml, acrescente:

<globalevent name="HousePokemon" interval="0" event="script" value="house_pokemon.lua"/>

Nesta revisão o intervalo 0 executa aproximadamente uma vez por segundo. Se sua revisão usa outra unidade/agendamento, configure o equivalente a um segundo e confirme no teste.

6. Inicie usando o executável recompilado. O dono coloca uma Ball saudável no chão da house e dá use para soltar. Outro use recolhe. O NPC é criado/removido no próximo ciclo (cerca de um segundo).

Não é preciso alterar catch, client, mapa, IDs de Ball ou esquema do banco. É preciso que o save de houses já preserve atributos personalizados dos itens. O pacote não contém mapa, banco, configurações particulares, executável, sprites ou source completa.

Configuração

Na primeira linha de configuração de data/lib/house_pokemon.lua, altere limit = 2 para ajustar o máximo por house. A aparência usa addon quando positivo; caso contrário, lê o look da espécie no XML.

As falas são lidas do bloco <voices> do XML, respeitando intervalo/chance com mínimo de 5 segundos. Elas ficam em cache; reinicie após alterar o XML. Espécies sem frases usam o próprio nome; shiny sem frases usa as da espécie normal. Há uma fala ao aparecer. Para retirar a frase adicional de Alakazam, remova a condição species:lower() == "alakazam" dentro de voices.

Os atributos adicionados à Ball são apenas house_display_owner e house_display_key. Não são persistidos CIDs de NPC nem UIDs de item. O NPC não recebe poderes de batalha de boost, level ou helds: esses valores permanecem guardados na Ball.

Testes e limites conhecidos

Compilação na source alvo, Lua 5.1 e XML aprovados. Testados: dono, limite 2, recolhimento, atributos intactos, falha de spawn, repetição sem duplicar e troca de dono em testes Lua. Em uma cópia isolada do servidor foram verificados criação, bloqueio da Ball, dano negado, teleporte negado, save e restauração real após restart, além de caminhada dentro da house e recolhimento. As falas passaram por testes Lua 5.1; não foi feita conferência visual das falas no cliente.

Durante ensaios anteriores, duas inicializações falharam intermitentemente durante o carregamento de items, antes dos scripts. A causa não foi identificada; os testes finais de inicialização/restart passaram. Não há garantia de estabilidade prolongada ou compatibilidade com outras sources. Por isso o pacote público contém código e patch, sem binário pronto.

Persistência acompanha o save existente. Queda antes de salvar pode perder mudanças desde o último save. Se não houver espaço para criar o NPC, a Ball mantém o marcador e a tentativa é repetida; use a Ball para cancelar. Na desinstalação, recolha as decorações antes de remover o sistema.

Créditos

Compartilhado por DarkRed; desenvolvido para sua base com auxílio do Codex. Ideia inspirada no pedido de eriveltonpacheco: https://tibiaking.com/topic/93254-pokemon-solto-na-house/. O tópico original descreve a ideia, não fornece este código. As alterações C++ são destinadas ao TFS, que mantém os créditos e a licença do projeto original. Sprites e conteúdo de Pokémon não são distribuídos neste pacote.


# Correção dos nomes visíveis dos Pokémon na house

HousePokemon.xml agora usa hidename="0" e hidehealth="0". A biblioteca mantém o apelido nick da Ball ou, se vazio, a espécie poke. A identidade interna continua HousePokemon; a Ball continua com seus atributos intactos e os bloqueios de dano/combate/movimento continuam na source.

Causa confirmada no código: hidehealth="1" fazia ProtocolGame enviar HP percentual zero. No Client_Source, Creature::drawInformation retorna antes de desenhar qualquer informação quando HP < 1. Portanto ativar só hidename não bastava. Ao enviar HP normal, o cliente pode desenhar o nome e também uma barra de vida. Essa barra não torna o Pokémon atacável; a proteção permanece.

Reabra o servidor para carregar o XML corrigido. As decorações persistidas serão recriadas pelo sistema; se necessário, recolha e solte a Ball novamente. Se a opção geral de nomes do cliente estiver desligada, ative-a. Não precisa recompilar para esta correção.

Verificação: testes Lua 5.1 de apelido, espécie/Shiny, apelido vazio, restauração e atributos da Ball; XML e caminho servidor/cliente que antes suprimia os nomes conferidos. A exibição final ainda depende de conferir no cliente após reabrir o servidor.


A função doCreatureSetNick precisa existir na sua source. Ela altera somente o rótulo enviado, preservando a identidade interna HousePokemon. Se o seu fork não a possui, adapte esse recurso antes de instalar a chamada da biblioteca.

data/lib/house_pokemon.lua

-- TFS 0.3.6, Kanto-e-Johto. Only metadata owned by this feature is changed.
HousePokemon = HousePokemon or {limit = 2, instances = {}, serial = 0}
local H = HousePokemon
H.voiceCache = H.voiceCache or {}
H.voiceTimers = H.voiceTimers or {}

local function clear(uid)
doItemEraseAttribute(uid, "house_display_owner")
doItemEraseAttribute(uid, "house_display_key")
end

local function looks()
if H.looks then return H.looks end
H.looks = {}
local f = io.open("data/monster/monsters.xml", "r")
if not f then return H.looks end
local registry = f:read("*all"):gsub("<!%-%-.-%-%->", "")
f:close()
for attrs in registry:gmatch("<monster%s+([^>]+)>") do
local name = attrs:match('name%s*=%s*"([^"]+)"')
local path = attrs:match('file%s*=%s*"([^"]+)"')
if name and path then H.looks[name:lower()] = path end
end
return H.looks
end

local function appearance(uid)
local addon = tonumber(getItemAttribute(uid, "addon")) or 0
if addon > 0 then return addon end
local name = getItemAttribute(uid, "poke")
local path = name and looks()[name:lower()]
if not path then return nil end
local f = io.open("data/monster/" .. path, "r")
if not f then return nil end
local content = f:read("*all"):gsub("<!%-%-.-%-%->", "")
f:close()
local attrs = content:match("<look%s+([^>]+)>") or ""
return tonumber(attrs:match('type%s*=%s*"(%d+)"'))
end

local function decodeVoice(text)
return text:gsub("&quot;", '"'):gsub("&apos;", "'"):gsub("&lt;", "<")
:gsub("&gt;", ">"):gsub("&amp;", "&")
end

local function voices(name)
if H.voiceCache[name] then return H.voiceCache[name] end
local result = {interval=5, chance=10, lines={}}
local path = looks()[name:lower()]
local f = path and io.open("data/monster/" .. path, "r")
if f then
local content = f:read("*all"):gsub("<!%-%-.-%-%->", "")
f:close()
local settings, body = content:match("<voices%s*([^>]*)>(.-)</voices>")
if body then
result.interval = math.max(5, math.ceil((tonumber(settings:match('interval%s*=%s*"(%d+)"')) or 5000)/1000))
result.chance = math.min(100, math.max(0, tonumber(settings:match('chance%s*=%s*"(%d+)"')) or 10))
for attrs in body:gmatch("<voice%s+([^>]+)>") do
local sentence = attrs:match('sentence%s*=%s*"([^"]+)"') or attrs:match("sentence%s*=%s*'([^']+)'")
if sentence and sentence ~= "" then
table.insert(result.lines, {text=decodeVoice(sentence), yell=attrs:match('yell%s*=%s*"([^"]+)"')})
end
end
end
end
local species = name:gsub("^[Ss]hiny%s+", "")
if species:lower() == "alakazam" then
table.insert(result.lines, {text="Ala alak alakazam!"})
end
if #result.lines == 0 then
if species ~= name then
result = voices(species)
else
table.insert(result.lines, {text=species:upper() .. "!"})
end
end
H.voiceCache[name] = result
return result
end

local function speak(key, npc, name)
if not npc or not isCreature(npc) or not name then return end
local now = os.time()
local voice = voices(name)
local timer = H.voiceTimers[key]
if timer and timer.cid == npc and now < timer.next then return end
H.voiceTimers[key] = {cid=npc, next=now+voice.interval}
-- Greet on appearance, then use the species' normal interval and chance.
if not timer or timer.cid ~= npc or math.random(100) <= voice.chance then
local line = voice.lines[math.random(#voice.lines)]
local yell = line.yell == "1" or line.yell == "true" or line.yell == "yes"
doCreatureSay(npc, line.text, yell and TALKTYPE_MONSTER_YELL or TALKTYPE_MONSTER)
end
end

function H.use(cid, item, pos)
local marked = tonumber(getItemAttribute(item.uid, "house_display_owner")) or 0
if pos.x == 65535 then
if marked > 0 then
doPlayerSendCancel(cid, "Esta Ball esta vinculada a uma decoracao na house.")
return true
end
return false
end
local house = getHouseFromPos(pos)
if not house or house == 0 then return false end
if type(getHouseDisplayBalls) ~= "function" then
doPlayerSendCancel(cid, "Reinicie usando o servidor com suporte a Pokemon na house.")
return true
end
local info = getHouseInfo(house)
if not info or info.guildHall or info.owner ~= getPlayerGUID(cid) then
doPlayerSendCancel(cid, "Somente o dono da house pode soltar ou recolher este Pokemon.")
return true
end
if marked > 0 then
clear(item.uid)
doPlayerSendCancel(cid, "Pokemon recolhido. A Ball sera liberada imediatamente.")
return true
end
local name = getItemAttribute(item.uid, "poke")
local kind = getPokeballType(item.itemid)
local ball = kind and pokeballs[kind]
if not name or not ball or item.itemid ~= ball.on then
doPlayerSendCancel(cid, "Use uma Ball com Pokemon recolhido e saudavel.")
return true
end
if (tonumber(getItemAttribute(item.uid, "hp")) or 0) <= 0 then
doPlayerSendCancel(cid, "Este Pokemon esta desmaiado.")
return true
end
if not appearance(item.uid) then
doPlayerSendCancel(cid, "Nao foi encontrada a aparencia deste Pokemon.")
return true
end
local count = 0
for _, entry in ipairs(getHouseDisplayBalls()) do
if entry.house == house then count = count + 1 end
end
if count >= H.limit then
doPlayerSendCancel(cid, "Esta house ja tem " .. H.limit .. " Pokemons soltos.")
return true
end
H.serial = H.serial + 1
local key = tostring(os.time()) .. ":" .. item.uid .. ":" .. H.serial
doItemSetAttribute(item.uid, "house_display_key", key)
doItemSetAttribute(item.uid, "house_display_owner", info.owner)
doPlayerSendCancel(cid, "Pokemon solto na house. Reutilize a Ball para recolher.")
return true
end

-- The global-event interface owns all NPCs; actions only change persisted Ball markers.
-- No item UID or creature CID is persisted. Scan results are used within this callback.
function H.sync()
if type(getHouseDisplayBalls) ~= "function" then return true end
local seen, counts = {}, {}
for _, entry in ipairs(getHouseDisplayBalls()) do
local uid = entry.uid
local owner = tonumber(getItemAttribute(uid, "house_display_owner"))
local key = getItemAttribute(uid, "house_display_key")
local info = getHouseInfo(entry.house)
local look = appearance(uid)
counts[entry.house] = counts[entry.house] or 0
if not info or info.guildHall or info.owner ~= owner or not key or not look
or seen[key] or counts[entry.house] >= H.limit then
clear(uid)
else
counts[entry.house] = counts[entry.house] + 1
seen[key] = true
local npc = H.instances[key]
if npc and isCreature(npc) and getHouseFromPos(getCreaturePosition(npc)) ~= entry.house then
doRemoveCreature(npc)
H.instances[key] = nil
npc = nil
end
if not npc or not isCreature(npc) then
-- Try the Ball tile and adjacent tiles; never create outside this house.
for _, offset in ipairs({{0,0},{1,0},{0,1},{-1,0},{0,-1}}) do
local p = {x=entry.pos.x+offset[1], y=entry.pos.y+offset[2], z=entry.pos.z}
if getHouseFromPos(p) == entry.house then
local created = doCreateNpc("HousePokemon", p, false)
if type(created) == "number" and created > 0 and isCreature(created) then
-- placeCreature may choose a nearby free tile in this engine.
if getHouseFromPos(getCreaturePosition(created)) == entry.house then
npc = created
H.instances[key] = npc
doSetCreatureOutfit(npc, {lookType=look}, -1)
-- Nick changes the displayed label, not the protected NPC identity.
local label = getItemAttribute(uid, "nick")
if type(label) ~= "string" or not label:match("%S") then
label = getItemAttribute(uid, "poke")
end
doCreatureSetNick(npc, label)
break
else
doRemoveCreature(created)
end
end
end
end
-- A blocked house keeps the Ball marker and retries next tick;
-- its owner can always use the Ball to cancel the display.
end
speak(key, npc, getItemAttribute(uid, "poke"))
end
end
for key, npc in pairs(H.instances) do
if not seen[key] then
if isCreature(npc) then doRemoveCreature(npc) end
H.instances[key] = nil
H.voiceTimers[key] = nil
end
end
return true
end

data/globalevents/scripts/house_pokemon.lua

function onThink(interval, lastExecution)
return HousePokemon.sync()
end

data/npc/HousePokemon.xml

<?xml version="1.0" encoding="UTF-8"?>
<npc name="HousePokemon" hidename="0" hidehealth="0" attackable="0" walkable="1" walkinterval="2000" floorchange="0" speed="110">
<health now="100" max="100"/>
<look type="2"/>
</npc>

Patch da source — aplicar e recompilar

--- a/source/game.cpp
+++ b/source/game.cpp
@@ -39,6 +39,8 @@
#include "monsters.h"

#include "house.h"
+#include "housetile.h"
+#include "npc.h"
#include "quests.h"

#include "actions.h"
@@ -300,7 +302,7 @@
tit = tile->getItemList()->begin();
while(tile->getItemList() && tit != tile->getItemList()->end())
{
- if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap()
+ if((*tit)->isMoveable() && !(*tit)->getIntegerAttribute("house_display_owner") && !(*tit)->isLoadedFromMap()
&& !(*tit)->isScriptProtected())
{
internalRemoveItem(NULL, *tit);
@@ -331,7 +333,7 @@
tit = tile->getItemList()->begin();
while(tile->getItemList() && tit != tile->getItemList()->end())
{
- if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap()
+ if((*tit)->isMoveable() && !(*tit)->getIntegerAttribute("house_display_owner") && !(*tit)->isLoadedFromMap()
&& !(*tit)->isScriptProtected())
{
internalRemoveItem(NULL, *tit);
@@ -363,7 +365,7 @@
tit = tile->getItemList()->begin();
while(tile->getItemList() && tit != tile->getItemList()->end())
{
- if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap()
+ if((*tit)->isMoveable() && !(*tit)->getIntegerAttribute("house_display_owner") && !(*tit)->isLoadedFromMap()
&& !(*tit)->isScriptProtected())
{
internalRemoveItem(NULL, *tit);
@@ -394,7 +396,7 @@
tit = tile->getItemList()->begin();
while(tile->getItemList() && tit != tile->getItemList()->end())
{
- if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap()
+ if((*tit)->isMoveable() && !(*tit)->getIntegerAttribute("house_display_owner") && !(*tit)->isLoadedFromMap()
&& !(*tit)->isScriptProtected())
{
internalRemoveItem(NULL, *tit);
@@ -1193,6 +1195,13 @@

ReturnValue Game::internalMoveCreature(Creature* actor, Creature* creature, Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t flags/* = 0*/)
{
+ if(creature->getNpc() && creature->getName() == "HousePokemon")
+ {
+ HouseTile* a = creature->getTile()->getHouseTile();
+ HouseTile* b = toCylinder->getTile()->getHouseTile();
+ if(!a || !b || a->getHouse() != b->getHouse() || toCylinder->getTile()->floorChange() || toCylinder->getTile()->positionChange())
+ return RET_NOTPOSSIBLE;
+ }
//check if we can move the creature to the destination
ReturnValue ret = toCylinder->__queryAdd(0, creature, 1, flags);
if(ret != RET_NOERROR)
@@ -1533,6 +1542,8 @@
ReturnValue Game::internalMoveItem(Creature* actor, Cylinder* fromCylinder, Cylinder* toCylinder,
int32_t index, Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/)
{
+ if(item && item->getIntegerAttribute("house_display_owner") && *item->getIntegerAttribute("house_display_owner") > 0)
+ return RET_NOTPOSSIBLE;
if(!toCylinder)
return RET_NOTPOSSIBLE;

@@ -2194,6 +2205,13 @@
{
if(Creature* creature = thing->getCreature())
{
+ if(creature->getNpc() && creature->getName() == "HousePokemon")
+ {
+ HouseTile* a = creature->getTile()->getHouseTile();
+ HouseTile* b = toTile->getHouseTile();
+ if(!a || !b || a->getHouse() != b->getHouse() || toTile->floorChange() || toTile->positionChange())
+ return RET_NOTPOSSIBLE;
+ }
if(Position::areInRange<1,1,0>(creature->getPosition(), newPos) && pushMove)
creature->getTile()->moveCreature(NULL, creature, toTile, false);
else
@@ -4369,6 +4387,7 @@
bool Game::combatChangeHealth(CombatType_t combatType, Creature* attacker, Creature* target, int32_t healthChange,
MagicEffect_t hitEffect/* = MAGIC_EFFECT_UNKNOWN*/, TextColor_t hitColor/* = TEXTCOLOR_UNKNOWN*/, bool force/* = false*/)
{
+ if(target->getNpc() && target->getName() == "HousePokemon" && healthChange < 0) return false;
const Position& targetPos = target->getPosition();
if(healthChange > 0)
{
--- a/source/house.cpp
+++ b/source/house.cpp
@@ -136,6 +136,17 @@
guid = player->getGuildId();
}

+ // Release decoration locks before the existing house cleanup/depot transfer.
+ for(HouseTileList::iterator t = houseTiles.begin(); t != houseTiles.end(); ++t)
+ {
+ TileItemVector* items = (*t)->getItemList();
+ if(!items) continue;
+ for(ItemVector::iterator i = items->begin(); i != items->end(); ++i)
+ {
+ (*i)->eraseAttribute("house_display_owner");
+ (*i)->eraseAttribute("house_display_key");
+ }
+ }
if(owner)
{
rentWarnings = paidUntil = 0;
--- a/source/housetile.cpp
+++ b/source/housetile.cpp
@@ -72,6 +72,12 @@

ReturnValue HouseTile::__queryAdd(int32_t index, const Thing* thing, uint32_t count, uint32_t flags) const
{
+ // This base rejects every non-summoned creature in houses by default.
+ // Only the dedicated decoration NPC uses the normal tile collision checks.
+ if(const Creature* display = thing->getCreature())
+ if(display->getNpc() && display->getName() == "HousePokemon")
+ return Tile::__queryAdd(index, thing, count, flags);
+
if(const Creature* creature = thing->getCreature())
{
if(const Player* player = creature->getPlayer())
--- a/source/luascript.cpp
+++ b/source/luascript.cpp
@@ -1896,6 +1896,7 @@

//getHouseInfo(houseId)
lua_register(m_luaState, "getHouseInfo", LuaScriptInterface::luaGetHouseInfo);
+ lua_register(m_luaState, "getHouseDisplayBalls", LuaScriptInterface::luaGetHouseDisplayBalls);

//getHouseAccessList(houseid, listId)
lua_register(m_luaState, "getHouseAccessList", LuaScriptInterface::luaGetHouseAccessList);
@@ -4972,6 +4973,33 @@
return 1;
}

+int32_t LuaScriptInterface::luaGetHouseDisplayBalls(lua_State* L)
+{
+ lua_newtable(L);
+ int index = 1;
+ for(HouseMap::iterator h = Houses::getInstance()->getHouseBegin(); h != Houses::getInstance()->getHouseEnd(); ++h)
+ {
+ House* house = h->second;
+ for(HouseTileList::iterator t = house->getHouseTileBegin(); t != house->getHouseTileEnd(); ++t)
+ {
+ TileItemVector* items = (*t)->getItemList();
+ if(!items) continue;
+ for(ItemVector::iterator i = items->begin(); i != items->end(); ++i)
+ {
+ const int32_t* owner = (*i)->getIntegerAttribute("house_display_owner");
+ if(!owner || !*owner) continue;
+ lua_newtable(L);
+ setField(L, "uid", getEnv()->addThing(*i));
+ setField(L, "itemid", (*i)->getID());
+ setField(L, "house", house->getId());
+ lua_pushstring(L, "pos"); pushPosition(L, (*t)->getPosition(), 0); pushTable(L);
+ lua_rawseti(L, -2, index++);
+ }
+ }
+ }
+ return 1;
+}
+
int32_t LuaScriptInterface::luaGetHouseInfo(lua_State* L)
{
//getHouseInfo(houseId)
--- a/source/luascript.h
+++ b/source/luascript.h
@@ -445,6 +445,7 @@
static int32_t luaGetTopCreature(lua_State* L);
static int32_t luaGetTileInfo(lua_State* L);
static int32_t luaDoTileQueryAdd(lua_State* L);
+ static int32_t luaGetHouseDisplayBalls(lua_State* L);
static int32_t luaGetHouseInfo(lua_State* L);
static int32_t luaGetHouseAccessList(lua_State* L);
static int32_t luaGetHouseByPlayerGUID(lua_State* L);
--- a/source/npc.cpp
+++ b/source/npc.cpp
@@ -23,6 +23,7 @@
#include <fstream>

#include "npc.h"
+#include "housetile.h"
#include "tools.h"

#include "luascript.h"
@@ -1917,6 +1918,12 @@
if(!tile)
return true;

+ if(getName() == "HousePokemon")
+ {
+ HouseTile* a = getTile()->getHouseTile();
+ HouseTile* b = tile->getHouseTile();
+ if(!a || !b || a->getHouse() != b->getHouse() || tile->floorChange() || tile->positionChange()) return false;
+ }
if(getTile()->isSwimmingPool() != tile->isSwimmingPool()) // prevent npc entering/exiting to swimming pool
return false;

Atualização dos nomes para quem já instalou o sistema

Este ZIP pequeno contém a biblioteca e o XML corrigidos, além das instruções. Exige o sistema completo e a função doCreatureSetNick existentes; não substitui o patch da source acima. O bloco cria somente o arquivo ZIP na pasta escolhida.

[IO.File]::WriteAllBytes((Join-Path (Get-Location) 'Pokemon-Solto-House-Nomes.zip'), [Convert]::FromBase64String('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'))

SHA256 da atualização: 668ce86a49dbe624965ac588879cf5c67fdcffe85b3ebf469c6142a8465cf3e9

Editado Outubro 1 por raulcdj

Nomes dos Pokémon corrigidos; XML e pacote de atualização 1.2.