canary/data/modules/scripts/npc/npc_dialog.lua
Eduardo Dantas 5ee38ce90a
feat: 15.13 npc module protocol and update NPC Lua scripts (#3982)
This commit standardizes NPC configuration across multiple Lua files by adding explicit profession and speechBubble properties to each affected NPC config.

The goal is to make each NPC role clear in the script itself and keep speech bubble behavior consistent between the server NPC configuration and the client presentation layer. NPCs now explicitly declare whether they are normal NPCs or trader NPCs, instead of relying on implicit or inconsistent defaults.

The profession value was added under npcConfig flags for all affected NPCs. Each NPC now uses either normal or trader depending on its expected role. This makes the NPC intent easier to read and helps avoid ambiguity when future scripts or client side behavior depend on the configured profession.

The speechBubble value was also added to each NPC config. Normal NPCs use SPEECHBUBBLE_NORMAL and trader NPCs use SPEECHBUBBLE_TRADE. This keeps the in game dialogue bubble aligned with the NPC role and improves consistency in how NPCs are displayed to players.

Main changes included in this commit

• Adds explicit profession configuration to affected NPC Lua files

• Uses normal for regular NPCs

• Uses trader for shop and trade related NPCs

• Adds explicit speechBubble configuration to affected NPC Lua files

• Uses SPEECHBUBBLE_NORMAL for normal NPC dialogue presentation

• Uses SPEECHBUBBLE_TRADE for trader NPC dialogue presentation

• Keeps NPC role information visible directly in each NPC config

• Reduces reliance on implicit default behavior

• Improves consistency between NPC server configuration and client side speech bubble rendering

• Aligns server NPC configs with the related client support added in tibia client PR 18

• Preserves existing NPC behavior while making the configuration more explicit

This is a configuration standardization change. It does not rework NPC logic or change the data access flow. The intended behavior remains the same, but the role and speech bubble metadata are now declared consistently so both server and client can interpret NPC presentation more reliably.
2026-07-01 10:12:58 -03:00

186 lines
3.6 KiB
Lua

NpcDialog = NpcDialog or {}
NpcDialog._stateByPlayerId = NpcDialog._stateByPlayerId or {}
NpcDialog.ValidProfessions = NpcDialog.ValidProfessions or {
normal = true,
sailor = true,
banker = true,
king = true,
queen = true,
trader = true,
hireling = true,
}
local BUTTON = {
Trade = 0,
TradePotion = 1,
TradeEquipment = 2,
Sail = 3,
DepositAll = 4,
Withdraw = 5,
Balance = 6,
Yes = 7,
No = 8,
Bye = 9,
}
local function addAction(actions, buttonId, keyword)
actions[#actions + 1] = {
buttonId = buttonId,
keyword = keyword,
}
end
local function hasNpcId(list, npcId)
for _, id in ipairs(list) do
if id == npcId then
return true
end
end
return false
end
function NpcDialog._getList(player)
local playerId = player and player:getId()
if not playerId then
return nil
end
local list = NpcDialog._stateByPlayerId[playerId]
if not list then
list = {}
NpcDialog._stateByPlayerId[playerId] = list
end
return list
end
function NpcDialog.add(player, npcId)
if not player or not npcId or npcId == 0 then
return
end
local list = NpcDialog._getList(player)
if not list or hasNpcId(list, npcId) then
return
end
list[#list + 1] = npcId
end
function NpcDialog.remove(player, npcId)
if not player or not npcId or npcId == 0 then
return
end
local list = NpcDialog._getList(player)
if not list or #list == 0 then
return
end
for i = #list, 1, -1 do
if list[i] == npcId then
table.remove(list, i)
end
end
end
function NpcDialog.clear(player)
if not player then
return
end
local playerId = player:getId()
if not playerId then
return
end
NpcDialog._stateByPlayerId[playerId] = {}
end
function NpcDialog.buildActions(player, npc)
local actions = {}
if not player or not npc then
return actions
end
addAction(actions, BUTTON.Yes, "yes")
addAction(actions, BUTTON.No, "no")
addAction(actions, BUTTON.Bye, "bye")
if npc:isMerchant() then
addAction(actions, BUTTON.Trade, "trade")
end
local profession = "normal"
if NpcDialogProfessionByName then
profession = NpcDialogProfessionByName[npc:getName():lower()] or profession
end
if profession == "sailor" then
addAction(actions, BUTTON.Sail, "sail")
end
if profession == "banker" then
addAction(actions, BUTTON.DepositAll, "deposit all")
addAction(actions, BUTTON.Withdraw, "withdraw")
addAction(actions, BUTTON.Balance, "balance")
end
if profession == "hireling" or npc:getSpeechBubble() == SPEECHBUBBLE_HIRELING then
local skills = player:kv():scoped("hireling-skills")
if skills:get("banker") then
addAction(actions, BUTTON.DepositAll, "deposit all")
addAction(actions, BUTTON.Withdraw, "withdraw")
addAction(actions, BUTTON.Balance, "balance")
end
if skills:get("trader") then
addAction(actions, BUTTON.TradePotion, "potions")
addAction(actions, BUTTON.TradeEquipment, "equipment")
end
end
return actions
end
function NpcDialog.sendWindow(player)
if not player then
return
end
local npcIds = NpcDialog._getList(player) or {}
local msg = NetworkMessage()
msg:addByte(0x1C)
msg:addByte(#npcIds > 0 and 0x00 or 0x01)
if #npcIds <= 0 then
msg:sendToPlayer(player)
return
end
msg:addByte(#npcIds)
for _, npcId in ipairs(npcIds) do
msg:addU32(npcId)
end
local actions = {}
local lastNpc = Npc(npcIds[#npcIds])
if not lastNpc then
NpcDialog.clear(player)
msg = NetworkMessage()
msg:addByte(0x1C)
msg:addByte(0x01)
msg:sendToPlayer(player)
return
end
actions = NpcDialog.buildActions(player, lastNpc)
msg:addByte(#actions)
for _, action in ipairs(actions) do
msg:addByte(action.buttonId)
msg:addString(action.keyword)
end
msg:sendToPlayer(player)
end