Bienvenido a Open Games Community

Únete ahora para acceder a todas nuestras funciones. Una vez que te registres e inicies sesión, podrás crear temas, responder a hilos existentes, valorar la reputación de otros miembros y muchísimo más. Además, es rápido y totalmente gratuito; ¿a qué esperas?

Ayuda convertir este NPC para usarlo en canary, consta de comprar items, addons, mounts, exp boost x tournament coins, por favor, ygracias de antemano

LOLO

Mordragor Chronicles
Miembro
Registrado
9 Sep 2021
Mensajes
28
Puntuación de reacción
3
Puntos
133
Ubicación
Venezuela
Ayuda convertir este NPC para usarlo en canary, consta de comprar items, addons, mounts, exp boost x tournament coins, por favor, ygracias de antemano
Código Lua:
local OFFER_TYPE = {
    ITEM = 1,
    STACK_ITEM = 2,
    ADDON = 3,
    MOUNT = 4,
    EXP_BOOST = 5,
}

local function createReturnValue(status, internal, msg)
    internal = internal or ""
    msg = msg or "no error"
    local ret = {
        status = status,
        internal = internal,
        msg = msg,
    }
    return ret
end

local OFFER_TYPE_FUNC = {
    [OFFER_TYPE.ITEM] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        if player:getFreeCapacity() < ItemType(offer.itemId):getWeight(offer.count) then
            return createReturnValue(true, "", "Please make sure you have free capacity to hold this item.")
        end
        local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
        if inbox and inbox:getEmptySlots() > offer.count then
            if not inbox:addItem(offer.itemId, offer.count) then
                return createReturnValue(false, "Cannot create item", "Please, contact a admin.")
            end
        else
            return createReturnValue(true, "", "Please make sure you have free slots in your store inbox.")
        end
        return createReturnValue(true)
    end,
    [OFFER_TYPE.STACK_ITEM] = function(player, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        
        if player:getFreeCapacity() < ItemType(offer.itemId):getWeight(offer.count) then
            return createReturnValue(true, "", "Please make sure you have free capacity to hold this item.")
        end
        
        local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
        if inbox and inbox:getEmptySlots() > offer.count then
            if not inbox:addItem(offer.itemId, offer.count) then
                return createReturnValue(false, "Cannot create item", "Please, contact a admin.")
            end
        else
            return createReturnValue(true, "", "Please make sure you have free slots in your store inbox.")
        end
        
        return createReturnValue(true)
    end,
    [OFFER_TYPE.ADDON] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        
        local hasOutfit = false
        for _, sexId in pairs(offer.lookType) do
            if player:hasOutfit(sexId, 3) then
                return createReturnValue(true, "", "You already have this outfit and addon.")
            else
                player:addOutfitAddon(sexId, 3)
            end
        end

        return createReturnValue(true)
    end,
    [OFFER_TYPE.MOUNT] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        
        if player:hasMount(offer.mountId) then
            return createReturnValue(true, "", "You already have this outfit and addon.")
        end
        
        player:addMount(offer.mountId)
        
        return createReturnValue(true)
    end,
    [OFFER_TYPE.EXP_BOOST] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        local lastPurchase = player:getStorageValue(98336)
        if lastPurchase > os.time() then
            return createReturnValue(true, "", "You can only buy XP Boost once a day.")
        end
        player:setStorageValue(98336, os.time() + 24 * 60 * 60)
        
        player:setStoreXpBoost(50)
        local currentExpBoostTime = player:getExpBoostStamina()
        player:setExpBoostStamina(currentExpBoostTime + offer.count * 60 * 60)
        
        return createReturnValue(true)
    end,
}

local TournamentShop = {
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Items",
        categoryId = 1,
        offers = {
            {
                offerName = "Swan Feather Cloak",
                itemId = 29079,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Shining Sun Catcher",
                itemId = 29213,
                count = 1,
                price = 120,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Moon Mirror",
                itemId = 29211,
                count = 1,
                price = 140,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Butterfly Ring",
                itemId = 29003,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Blossom Bag",
                itemId = 29080,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Gill Coat",
                itemId = 18399,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Gill Gugel",
                itemId = 18398,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Gill Legs",
                itemId = 18400,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Glacial Rod",
                itemId = 18412,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Muck Rod",
                itemId = 18411,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Wand Of Everblazing",
                itemId = 18409,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Wand Of Defiance",
                itemId = 18390,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Spellbook Of Vigilance",
                itemId = 18401,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Shield",
                itemId = 18410,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Legs",
                itemId = 18405,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Helmet",
                itemId = 18403,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Boots",
                itemId = 18406,
                count = 1,
                price = 80,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Armor",
                itemId = 18404,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Mycological Mace",
                itemId = 18452,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Mycological Bow",
                itemId = 18454,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Shiny Blade",
                itemId = 18465,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Crystalline Axe",
                itemId = 18451,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Crystal Crossbow",
                itemId = 18453,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthheart cuirass",
                itemId = 25177,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthheart hauberk",
                itemId = 25178,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthheart platemail",
                itemId = 25179,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthmind raiment",
                itemId = 25191,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthsoul tabard",
                itemId = 25187,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "fireheart cuirass",
                itemId = 25174,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "fireheart hauberk",
                itemId = 25175,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "fireheart platemail",
                itemId = 25176,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "firemind raiment",
                itemId = 25190,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "firesoul tabard",
                itemId = 25186,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostheart cuirass",
                itemId = 25183,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostheart hauberk",
                itemId = 25184,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },           
            {
                offerName = "frostheart platemail",
                itemId = 25185,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostmind raiment",
                itemId = 25193,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostsoul tabard",
                itemId = 25189,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "magic shield potion",
                itemId = 40398,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thunderheart cuirass",
                itemId = 25180,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thunderheart hauberk",
                itemId = 25181,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thunderheart platemail",
                itemId = 25182,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thundermind raiment",
                itemId = 25192,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thundersoul tabard",
                itemId = 25188,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },           
            
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Addons",
        categoryId = 2,
        offers = {
            {
                offerName = "Puppeteer full",
                lookType = {[0] = 696, [1] = 697}, -- 0 = female, 1 = male.
                price = 1200,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Arena Champion full",
                lookType = {[0] = 885, [1] = 884}, -- 0 = female, 1 = male.
                price = 1200,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Grove Keeper full",
                lookType = {[0] = 909, [1] = 908}, -- 0 = female, 1 = male.
                price = 1000,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Pumpkin Mummy full",
                lookType = {[0] = 1128, [1] = 1127}, -- 0 = female, 1 = male.
                price = 1800,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Dream Warrior full",
                lookType = {[0] = 1147, [1] = 1146}, -- 0 = female, 1 = male.
                price = 1600,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Formal Dress full",
                lookType = {[0] = 1461, [1] = 1460}, -- 0 = female, 1 = male.
                price = 2500,
                type = OFFER_TYPE.ADDON,
            },           
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Mounts",
        categoryId = 3,
        offers = {
            {
                offerName = "Tin Lizzard",
                mountId = 8,
                price = 800,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Draptor",
                mountId = 6,
                price = 690,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Uniwheel",
                mountId = 15,
                price = 700,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Crystal Wolf",
                mountId = 16,
                price = 850,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Phant",
                mountId = 182,
                price = 1200,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Water Buffalo",
                mountId = 35,
                price = 600,
                type = OFFER_TYPE.MOUNT,
            },           
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Userful Things",
        categoryId = 4,
        offers = {
            {
                offerName = "Boost Exp 2h",
                count = 2,
                price = 1000,
                type = OFFER_TYPE.EXP_BOOST,
            },
            {
                offerName = "Boost Exp 4h",
                count = 4,
                price = 2000,
                type = OFFER_TYPE.EXP_BOOST,
            },           
        
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------   
    {
        categoryName = "Especial Offers",
        categoryId = 5,
        offers = {
            {
                offerName = "Blazing Unicorn",
                mountId = 113,
                price = 2600,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Golden addon full",
                lookType = {[0] = 1211, [1] = 1210}, -- 0 = female, 1 = male.
                price = 15000,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Mage Addon Full",
                lookType = {[0] = 138, [1] = 130}, -- 0 = female, 1 = male.
                price = 45000,
                type = OFFER_TYPE.ADDON,
            },           
            {
                offerName = "soul stone",
                itemId = 5809,
                count = 1,
                price = 1000,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "golden helmet",
                itemId = 2471,
                count = 1,
                price = 30000,
                type = OFFER_TYPE.ITEM,
            },           
        },
    },   
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
}

local function getShopCategory(param)
    if not param then
        return nil
    end
    
    for index, category in pairs(TournamentShop) do
        if type(param) == "string" then
            if param == category.categoryName then
                return category
            end
        elseif type(param) == "number" then
            if param == category.categoryId then
                return category
            end
        end
    end
    
    return nil
end

local function getShopOffer(categoryParam, param)
    if not categoryParam or not param then
        return nil
    end
    local category
    
    if type(categoryParam) == "table" and categoryParam.offers then
        category = categoryParam
    elseif type(categoryParam) == "string" or type(categoryParam) == "number" then
        category = getShopCategory(categoryParam)
    end
    
    if not category then
        return nil
    end
    
    for index=1, #category.offers do
        local offer = category.offers[index]
        if offer then
            if index == param then
                return offer
            end
        end
    end
    
    return nil
end

local function processShopPurchase(cid, offer)
    local player = Player(cid)
    if not player or not offer or type(offer) ~= "table" then
        return false
    end
    if player:getTournamentCoinsBalance() < offer.price then
        player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have enough tournament.")
        return false
    end
    
    local retValue = OFFER_TYPE_FUNC[offer.type](player, offer)
    
    if not retValue.status then
        Spdlog.warn(string.format("[TournamentShop] Error: %s, Player: %s, Message: %s", retValue.internal, player:getName(), retValue.msg))
    end
    
    if retValue.msg ~= "no error" then
        player:sendTextMessage(MESSAGE_EVENT_ADVANCE, retValue.msg)
        return false
    end
    
    player:removeTournamentCoinsBalance(offer.price)
    
    player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You bought %sx %s for %s Tournament Coins.", offer.count, offer.offerName, offer.price))
    return true
end

function sendShopCategoryModal(cid, id)
    local player = Player(cid)
    if not player or not id then
        return false
    end
    
    local category = getShopCategory(id)
    if not category then
        return false
    end
    
    local window = ModalWindow {
        title = 'Tournament Shop',
        message = string.format("You have %s Coins\nSelect offer.", player:getTournamentCoinsBalance())
    }
    
    for index=1, #category.offers do
        local offer = category.offers[index]
        if offer then
            local offerCountStr = offer.count or ""
            if offer.type == OFFER_TYPE.ITEM then
                offerCountStr = offer.count.."x"
            elseif offer.type == OFFER_TYPE.EXP_BOOST then
                offerCountStr = ""
            elseif offer.type == OFFER_TYPE.ADDON then
                offerCountStr = ""
            elseif offer.type == OFFER_TYPE.MOUNT then
                offerCountStr = ""
            end
            local choice = window:addChoice(string.format("%s %s | Price: %s", offerCountStr, offer.offerName, offer.price))
            choice.id = index
        end
    end
    
    window:addButton('Buy',
        function(button, choice)
            local offer = getShopOffer(category, choice.id)
            if not offer then
                Spdlog.warn(string.format("Failed to find offer: %s category: %s, player: %s", choice.id, category.categoryName, player:getName()));
                return false
            end
            processShopPurchase(cid, offer)
        end
    )
    
    window:addButton('Back',
        function(button, choice)
            sendTournamentShopModal(cid)
        end
    )
    
    window:addButton('Close')
    
    window:setDefaultEnterButton('Buy')
    window:setDefaultEscapeButton('Back')
    window:sendToPlayer(player)
    
    return true
end

function sendTournamentShopModal(cid)
    local player = Player(cid)
    if not player then
        return false
    end
    
    local window = ModalWindow {
        title = 'Tournament Shop',
        message = string.format("You have %s Tourn. Coins\nSelect category.", player:getTournamentCoinsBalance())
    }
    
    for _, category in pairs(TournamentShop) do
        local choice = window:addChoice(category.categoryName)
        choice.id = category.categoryId
    end
    
    window:addButton('Select',
        function(button, choice)
            sendShopCategoryModal(cid, choice.id)
        end
    )
    
    window:addButton('Close')
    
    window:setDefaultEnterButton('Select')
    window:setDefaultEscapeButton('Close')
    window:sendToPlayer(player)
    return true
end

local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)

function onCreatureAppear(cid)
    npcHandler:onCreatureAppear(cid)
end
function onCreatureDisappear(cid)
    npcHandler:onCreatureDisappear(cid)
end
function onCreatureSay(cid, type, msg)
    npcHandler:onCreatureSay(cid, type, msg)
end
function onThink()
    npcHandler:onThink()
end

local function greetCallback(cid)
    npcHandler:setMessage(MESSAGE_GREET, "Hello |PLAYERNAME|, looking for {tournament}, {shop} or {offers}?")
    local player = Player(cid)
    if not player then
        return true
    end
    npcHandler:setMessage(MESSAGE_GREET, "Hello |PLAYERNAME|, looking for {tournament}, {shop} or {offers}?. You have "..player:getTournamentCoinsBalance().." Tournament Coins.")
    return true
end

local function creatureSayCallback(cid, type, msg)
    local player = Player(cid)
    if not player or not npcHandler:isFocused(cid) then
        return false
    end
    if msgcontains(msg, "tournament") or msgcontains(msg, "shop") or msgcontains(msg, "offers") then
        if player:getTournamentCoinsBalance() <= 0 then
            npcHandler:say("You don't have Tournament Coins to access the Shop.", cid)
            return true
        end
        sendTournamentShopModal(player)
        return true
    end
    -- player:getTournamentCoinsBalance()
    return true
end

npcHandler:setCallback(CALLBACK_GREET, greetCallback)
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())


XML:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Tournament NPC" walkinterval="2000" floorchange="0" script="tournament_shop.lua">
    <health now="100" max="100"/>
    <look type="54"/>
    <parameters>
        <parameter key="message_greet" value="Hello |PLAYERNAME|, looking for {tournament} - {shop} - {offers}?"/>
        <parameter key="message_farewell" value="Bye."/>
    </parameters>
</npc>
 

Alex

Administrador
Dev
Registrado
1 Sep 2021
Mensajes
2.362
Puntuación de reacción
28.645
Puntos
707
Ayuda convertir este NPC para usarlo en canary, consta de comprar items, addons, mounts, exp boost x tournament coins, por favor, ygracias de antemano
Código Lua:
local OFFER_TYPE = {
    ITEM = 1,
    STACK_ITEM = 2,
    ADDON = 3,
    MOUNT = 4,
    EXP_BOOST = 5,
}

local function createReturnValue(status, internal, msg)
    internal = internal or ""
    msg = msg or "no error"
    local ret = {
        status = status,
        internal = internal,
        msg = msg,
    }
    return ret
end

local OFFER_TYPE_FUNC = {
    [OFFER_TYPE.ITEM] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        if player:getFreeCapacity() < ItemType(offer.itemId):getWeight(offer.count) then
            return createReturnValue(true, "", "Please make sure you have free capacity to hold this item.")
        end
        local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
        if inbox and inbox:getEmptySlots() > offer.count then
            if not inbox:addItem(offer.itemId, offer.count) then
                return createReturnValue(false, "Cannot create item", "Please, contact a admin.")
            end
        else
            return createReturnValue(true, "", "Please make sure you have free slots in your store inbox.")
        end
        return createReturnValue(true)
    end,
    [OFFER_TYPE.STACK_ITEM] = function(player, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
       
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
       
        if player:getFreeCapacity() < ItemType(offer.itemId):getWeight(offer.count) then
            return createReturnValue(true, "", "Please make sure you have free capacity to hold this item.")
        end
       
        local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
        if inbox and inbox:getEmptySlots() > offer.count then
            if not inbox:addItem(offer.itemId, offer.count) then
                return createReturnValue(false, "Cannot create item", "Please, contact a admin.")
            end
        else
            return createReturnValue(true, "", "Please make sure you have free slots in your store inbox.")
        end
       
        return createReturnValue(true)
    end,
    [OFFER_TYPE.ADDON] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
       
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
       
        local hasOutfit = false
        for _, sexId in pairs(offer.lookType) do
            if player:hasOutfit(sexId, 3) then
                return createReturnValue(true, "", "You already have this outfit and addon.")
            else
                player:addOutfitAddon(sexId, 3)
            end
        end

        return createReturnValue(true)
    end,
    [OFFER_TYPE.MOUNT] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
       
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
       
        if player:hasMount(offer.mountId) then
            return createReturnValue(true, "", "You already have this outfit and addon.")
        end
       
        player:addMount(offer.mountId)
       
        return createReturnValue(true)
    end,
    [OFFER_TYPE.EXP_BOOST] = function(cid, offer)
        local player = Player(cid)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
       
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        local lastPurchase = player:getStorageValue(98336)
        if lastPurchase > os.time() then
            return createReturnValue(true, "", "You can only buy XP Boost once a day.")
        end
        player:setStorageValue(98336, os.time() + 24 * 60 * 60)
       
        player:setStoreXpBoost(50)
        local currentExpBoostTime = player:getExpBoostStamina()
        player:setExpBoostStamina(currentExpBoostTime + offer.count * 60 * 60)
       
        return createReturnValue(true)
    end,
}

local TournamentShop = {
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Items",
        categoryId = 1,
        offers = {
            {
                offerName = "Swan Feather Cloak",
                itemId = 29079,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Shining Sun Catcher",
                itemId = 29213,
                count = 1,
                price = 120,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Moon Mirror",
                itemId = 29211,
                count = 1,
                price = 140,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Butterfly Ring",
                itemId = 29003,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Blossom Bag",
                itemId = 29080,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Gill Coat",
                itemId = 18399,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Gill Gugel",
                itemId = 18398,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Gill Legs",
                itemId = 18400,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Glacial Rod",
                itemId = 18412,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Muck Rod",
                itemId = 18411,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Wand Of Everblazing",
                itemId = 18409,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Wand Of Defiance",
                itemId = 18390,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Spellbook Of Vigilance",
                itemId = 18401,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Shield",
                itemId = 18410,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Legs",
                itemId = 18405,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Helmet",
                itemId = 18403,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Boots",
                itemId = 18406,
                count = 1,
                price = 80,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Prismatic Armor",
                itemId = 18404,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Mycological Mace",
                itemId = 18452,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Mycological Bow",
                itemId = 18454,
                count = 1,
                price = 150,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Shiny Blade",
                itemId = 18465,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Crystalline Axe",
                itemId = 18451,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "Crystal Crossbow",
                itemId = 18453,
                count = 1,
                price = 100,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthheart cuirass",
                itemId = 25177,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthheart hauberk",
                itemId = 25178,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthheart platemail",
                itemId = 25179,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthmind raiment",
                itemId = 25191,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "earthsoul tabard",
                itemId = 25187,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "fireheart cuirass",
                itemId = 25174,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "fireheart hauberk",
                itemId = 25175,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "fireheart platemail",
                itemId = 25176,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "firemind raiment",
                itemId = 25190,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "firesoul tabard",
                itemId = 25186,
                count = 1,
                price = 200,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostheart cuirass",
                itemId = 25183,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostheart hauberk",
                itemId = 25184,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },          
            {
                offerName = "frostheart platemail",
                itemId = 25185,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostmind raiment",
                itemId = 25193,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "frostsoul tabard",
                itemId = 25189,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "magic shield potion",
                itemId = 40398,
                count = 1,
                price = 50,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thunderheart cuirass",
                itemId = 25180,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thunderheart hauberk",
                itemId = 25181,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thunderheart platemail",
                itemId = 25182,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thundermind raiment",
                itemId = 25192,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "thundersoul tabard",
                itemId = 25188,
                count = 1,
                price = 250,
                type = OFFER_TYPE.ITEM,
            },          
           
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Addons",
        categoryId = 2,
        offers = {
            {
                offerName = "Puppeteer full",
                lookType = {[0] = 696, [1] = 697}, -- 0 = female, 1 = male.
                price = 1200,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Arena Champion full",
                lookType = {[0] = 885, [1] = 884}, -- 0 = female, 1 = male.
                price = 1200,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Grove Keeper full",
                lookType = {[0] = 909, [1] = 908}, -- 0 = female, 1 = male.
                price = 1000,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Pumpkin Mummy full",
                lookType = {[0] = 1128, [1] = 1127}, -- 0 = female, 1 = male.
                price = 1800,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Dream Warrior full",
                lookType = {[0] = 1147, [1] = 1146}, -- 0 = female, 1 = male.
                price = 1600,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Formal Dress full",
                lookType = {[0] = 1461, [1] = 1460}, -- 0 = female, 1 = male.
                price = 2500,
                type = OFFER_TYPE.ADDON,
            },          
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Mounts",
        categoryId = 3,
        offers = {
            {
                offerName = "Tin Lizzard",
                mountId = 8,
                price = 800,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Draptor",
                mountId = 6,
                price = 690,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Uniwheel",
                mountId = 15,
                price = 700,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Crystal Wolf",
                mountId = 16,
                price = 850,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Phant",
                mountId = 182,
                price = 1200,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Water Buffalo",
                mountId = 35,
                price = 600,
                type = OFFER_TYPE.MOUNT,
            },          
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
    {
        categoryName = "Userful Things",
        categoryId = 4,
        offers = {
            {
                offerName = "Boost Exp 2h",
                count = 2,
                price = 1000,
                type = OFFER_TYPE.EXP_BOOST,
            },
            {
                offerName = "Boost Exp 4h",
                count = 4,
                price = 2000,
                type = OFFER_TYPE.EXP_BOOST,
            },          
       
        },
    },
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------  
    {
        categoryName = "Especial Offers",
        categoryId = 5,
        offers = {
            {
                offerName = "Blazing Unicorn",
                mountId = 113,
                price = 2600,
                type = OFFER_TYPE.MOUNT,
            },
            {
                offerName = "Golden addon full",
                lookType = {[0] = 1211, [1] = 1210}, -- 0 = female, 1 = male.
                price = 15000,
                type = OFFER_TYPE.ADDON,
            },
            {
                offerName = "Mage Addon Full",
                lookType = {[0] = 138, [1] = 130}, -- 0 = female, 1 = male.
                price = 45000,
                type = OFFER_TYPE.ADDON,
            },          
            {
                offerName = "soul stone",
                itemId = 5809,
                count = 1,
                price = 1000,
                type = OFFER_TYPE.ITEM,
            },
            {
                offerName = "golden helmet",
                itemId = 2471,
                count = 1,
                price = 30000,
                type = OFFER_TYPE.ITEM,
            },          
        },
    },  
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
}

local function getShopCategory(param)
    if not param then
        return nil
    end
   
    for index, category in pairs(TournamentShop) do
        if type(param) == "string" then
            if param == category.categoryName then
                return category
            end
        elseif type(param) == "number" then
            if param == category.categoryId then
                return category
            end
        end
    end
   
    return nil
end

local function getShopOffer(categoryParam, param)
    if not categoryParam or not param then
        return nil
    end
    local category
   
    if type(categoryParam) == "table" and categoryParam.offers then
        category = categoryParam
    elseif type(categoryParam) == "string" or type(categoryParam) == "number" then
        category = getShopCategory(categoryParam)
    end
   
    if not category then
        return nil
    end
   
    for index=1, #category.offers do
        local offer = category.offers[index]
        if offer then
            if index == param then
                return offer
            end
        end
    end
   
    return nil
end

local function processShopPurchase(cid, offer)
    local player = Player(cid)
    if not player or not offer or type(offer) ~= "table" then
        return false
    end
    if player:getTournamentCoinsBalance() < offer.price then
        player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have enough tournament.")
        return false
    end
   
    local retValue = OFFER_TYPE_FUNC[offer.type](player, offer)
   
    if not retValue.status then
        Spdlog.warn(string.format("[TournamentShop] Error: %s, Player: %s, Message: %s", retValue.internal, player:getName(), retValue.msg))
    end
   
    if retValue.msg ~= "no error" then
        player:sendTextMessage(MESSAGE_EVENT_ADVANCE, retValue.msg)
        return false
    end
   
    player:removeTournamentCoinsBalance(offer.price)
   
    player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You bought %sx %s for %s Tournament Coins.", offer.count, offer.offerName, offer.price))
    return true
end

function sendShopCategoryModal(cid, id)
    local player = Player(cid)
    if not player or not id then
        return false
    end
   
    local category = getShopCategory(id)
    if not category then
        return false
    end
   
    local window = ModalWindow {
        title = 'Tournament Shop',
        message = string.format("You have %s Coins\nSelect offer.", player:getTournamentCoinsBalance())
    }
   
    for index=1, #category.offers do
        local offer = category.offers[index]
        if offer then
            local offerCountStr = offer.count or ""
            if offer.type == OFFER_TYPE.ITEM then
                offerCountStr = offer.count.."x"
            elseif offer.type == OFFER_TYPE.EXP_BOOST then
                offerCountStr = ""
            elseif offer.type == OFFER_TYPE.ADDON then
                offerCountStr = ""
            elseif offer.type == OFFER_TYPE.MOUNT then
                offerCountStr = ""
            end
            local choice = window:addChoice(string.format("%s %s | Price: %s", offerCountStr, offer.offerName, offer.price))
            choice.id = index
        end
    end
   
    window:addButton('Buy',
        function(button, choice)
            local offer = getShopOffer(category, choice.id)
            if not offer then
                Spdlog.warn(string.format("Failed to find offer: %s category: %s, player: %s", choice.id, category.categoryName, player:getName()));
                return false
            end
            processShopPurchase(cid, offer)
        end
    )
   
    window:addButton('Back',
        function(button, choice)
            sendTournamentShopModal(cid)
        end
    )
   
    window:addButton('Close')
   
    window:setDefaultEnterButton('Buy')
    window:setDefaultEscapeButton('Back')
    window:sendToPlayer(player)
   
    return true
end

function sendTournamentShopModal(cid)
    local player = Player(cid)
    if not player then
        return false
    end
   
    local window = ModalWindow {
        title = 'Tournament Shop',
        message = string.format("You have %s Tourn. Coins\nSelect category.", player:getTournamentCoinsBalance())
    }
   
    for _, category in pairs(TournamentShop) do
        local choice = window:addChoice(category.categoryName)
        choice.id = category.categoryId
    end
   
    window:addButton('Select',
        function(button, choice)
            sendShopCategoryModal(cid, choice.id)
        end
    )
   
    window:addButton('Close')
   
    window:setDefaultEnterButton('Select')
    window:setDefaultEscapeButton('Close')
    window:sendToPlayer(player)
    return true
end

local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)

function onCreatureAppear(cid)
    npcHandler:onCreatureAppear(cid)
end
function onCreatureDisappear(cid)
    npcHandler:onCreatureDisappear(cid)
end
function onCreatureSay(cid, type, msg)
    npcHandler:onCreatureSay(cid, type, msg)
end
function onThink()
    npcHandler:onThink()
end

local function greetCallback(cid)
    npcHandler:setMessage(MESSAGE_GREET, "Hello |PLAYERNAME|, looking for {tournament}, {shop} or {offers}?")
    local player = Player(cid)
    if not player then
        return true
    end
    npcHandler:setMessage(MESSAGE_GREET, "Hello |PLAYERNAME|, looking for {tournament}, {shop} or {offers}?. You have "..player:getTournamentCoinsBalance().." Tournament Coins.")
    return true
end

local function creatureSayCallback(cid, type, msg)
    local player = Player(cid)
    if not player or not npcHandler:isFocused(cid) then
        return false
    end
    if msgcontains(msg, "tournament") or msgcontains(msg, "shop") or msgcontains(msg, "offers") then
        if player:getTournamentCoinsBalance() <= 0 then
            npcHandler:say("You don't have Tournament Coins to access the Shop.", cid)
            return true
        end
        sendTournamentShopModal(player)
        return true
    end
    -- player:getTournamentCoinsBalance()
    return true
end

npcHandler:setCallback(CALLBACK_GREET, greetCallback)
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())


XML:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Tournament NPC" walkinterval="2000" floorchange="0" script="tournament_shop.lua">
    <health now="100" max="100"/>
    <look type="54"/>
    <parameters>
        <parameter key="message_greet" value="Hello |PLAYERNAME|, looking for {tournament} - {shop} - {offers}?"/>
        <parameter key="message_farewell" value="Bye."/>
    </parameters>
</npc>


Mucho script, no estoy seguro pero es un principio prueba si tienes errores manda el error copiado:


Código Lua:
local internalNpcName = "Admiral Wyrmslicer"
local npcType = Game.createNpcType(internalNpcName)
local npcConfig = {}

npcConfig.name = internalNpcName
npcConfig.description = internalNpcName

npcConfig.health = 100
npcConfig.maxHealth = npcConfig.health
npcConfig.walkInterval = 2000
npcConfig.walkRadius = 2

npcConfig.outfit = {
    lookType = 132,
    lookHead = 19,
    lookBody = 113,
    lookLegs = 112,
    lookFeet = 114,
    lookAddons = 0
}

npcConfig.flags = {
    floorchange = false
}

local OFFER_TYPE = {
    ITEM = 1,
    STACK_ITEM = 2,
    ADDON = 3,
    MOUNT = 4,
    EXP_BOOST = 5,
}

local function createReturnValue(status, internal, msg)
    internal = internal or ""
    msg = msg or "no error"
    local ret = {
        status = status,
        internal = internal,
        msg = msg,
    }
    return ret
end

local OFFER_TYPE_FUNC = {
    [OFFER_TYPE.ITEM] = function(creature, offer)
        local player = Player(creature)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        if player:getFreeCapacity() < ItemType(offer.itemId):getWeight(offer.count) then
            return createReturnValue(true, "", "Please make sure you have free capacity to hold this item.")
        end
        local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
        if inbox and inbox:getEmptySlots() > offer.count then
            if not inbox:addItem(offer.itemId, offer.count) then
                return createReturnValue(false, "Cannot create item", "Please, contact a admin.")
            end
        else
            return createReturnValue(true, "", "Please make sure you have free slots in your store inbox.")
        end
        return createReturnValue(true)
    end,
    [OFFER_TYPE.STACK_ITEM] = function(creature, offer)
        local player = Player(creature)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        
        if player:getFreeCapacity() < ItemType(offer.itemId):getWeight(offer.count) then
            return createReturnValue(true, "", "Please make sure you have free capacity to hold this item.")
        end
        
        local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
        if inbox and inbox:getEmptySlots() > offer.count then
            if not inbox:addItem(offer.itemId, offer.count) then
                return createReturnValue(false, "Cannot create item", "Please, contact a admin.")
            end
        else
            return createReturnValue(true, "", "Please make sure you have free slots in your store inbox.")
        end
        
        return createReturnValue(true)
    end,
    [OFFER_TYPE.ADDON] = function(creature, offer)
        local player = Player(creature)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        
        local hasOutfit = false
        for _, sexId in pairs(offer.lookType) do
            if player:hasOutfit(sexId, 3) then
                return createReturnValue(true, "", "You already have this outfit and addon.")
            else
                player:addOutfitAddon(sexId, 3)
            end
        end

        return createReturnValue(true)
    end,
    [OFFER_TYPE.MOUNT] = function(creature, offer)
        local player = Player(creature)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        
        if player:hasMount(offer.mountId) then
            return createReturnValue(true, "", "You already have this outfit and addon.")
        end
        
        player:addMount(offer.mountId)
        
        return createReturnValue(true)
    end,
    [OFFER_TYPE.EXP_BOOST] = function(creature, offer)
        local player = Player(creature)
        if not player then
            return createReturnValue(false, "player is null", "Please, contact a admin.")
        end
        
        if not offer then
            return createReturnValue(false, "offer is null", "Please, contact a admin.")
        end
        local lastPurchase = player:getStorageValue(98336)
        if lastPurchase > os.time() then
            return createReturnValue(true, "", "You can only buy XP Boost once a day.")
        end
        player:setStorageValue(98336, os.time() + 24 * 60 * 60)
        
        player:setStoreXpBoost(50)
        local currentExpBoostTime = player:getExpBoostStamina()
        player:setExpBoostStamina(currentExpBoostTime + offer.count * 60 * 60)
        
        return createReturnValue(true)
    end,
}

local TournamentShop = {
    ----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
{
    categoryName = "Items",
    categoryId = 1,
    offers = {
        {
            offerName = "Swan Feather Cloak",
            itemId = 29079,
            count = 1,
            price = 200,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Shining Sun Catcher",
            itemId = 29213,
            count = 1,
            price = 120,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Moon Mirror",
            itemId = 29211,
            count = 1,
            price = 140,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Butterfly Ring",
            itemId = 29003,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Blossom Bag",
            itemId = 29080,
            count = 1,
            price = 100,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Gill Coat",
            itemId = 18399,
            count = 1,
            price = 150,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Gill Gugel",
            itemId = 18398,
            count = 1,
            price = 150,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Gill Legs",
            itemId = 18400,
            count = 1,
            price = 150,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Glacial Rod",
            itemId = 18412,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Muck Rod",
            itemId = 18411,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Wand Of Everblazing",
            itemId = 18409,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Wand Of Defiance",
            itemId = 18390,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Spellbook Of Vigilance",
            itemId = 18401,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Prismatic Shield",
            itemId = 18410,
            count = 1,
            price = 100,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Prismatic Legs",
            itemId = 18405,
            count = 1,
            price = 100,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Prismatic Helmet",
            itemId = 18403,
            count = 1,
            price = 100,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Prismatic Boots",
            itemId = 18406,
            count = 1,
            price = 80,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Prismatic Armor",
            itemId = 18404,
            count = 1,
            price = 100,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Mycological Mace",
            itemId = 18452,
            count = 1,
            price = 150,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Mycological Bow",
            itemId = 18454,
            count = 1,
            price = 150,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Shiny Blade",
            itemId = 18465,
            count = 1,
            price = 200,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Crystalline Axe",
            itemId = 18451,
            count = 1,
            price = 200,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "Crystal Crossbow",
            itemId = 18453,
            count = 1,
            price = 100,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "earthheart cuirass",
            itemId = 25177,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "earthheart hauberk",
            itemId = 25178,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "earthheart platemail",
            itemId = 25179,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "earthmind raiment",
            itemId = 25191,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "earthsoul tabard",
            itemId = 25187,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "fireheart cuirass",
            itemId = 25174,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "fireheart hauberk",
            itemId = 25175,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "fireheart platemail",
            itemId = 25176,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "firemind raiment",
            itemId = 25190,
            count = 1,
            price = 200,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "firesoul tabard",
            itemId = 25186,
            count = 1,
            price = 200,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "frostheart cuirass",
            itemId = 25183,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "frostheart hauberk",
            itemId = 25184,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },           
        {
            offerName = "frostheart platemail",
            itemId = 25185,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "frostmind raiment",
            itemId = 25193,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "frostsoul tabard",
            itemId = 25189,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "magic shield potion",
            itemId = 40398,
            count = 1,
            price = 50,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "thunderheart cuirass",
            itemId = 25180,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "thunderheart hauberk",
            itemId = 25181,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "thunderheart platemail",
            itemId = 25182,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "thundermind raiment",
            itemId = 25192,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "thundersoul tabard",
            itemId = 25188,
            count = 1,
            price = 250,
            type = OFFER_TYPE.ITEM,
        },           
        
    },
},
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
{
    categoryName = "Addons",
    categoryId = 2,
    offers = {
        {
            offerName = "Puppeteer full",
            lookType = {[0] = 696, [1] = 697}, -- 0 = female, 1 = male.
            price = 1200,
            type = OFFER_TYPE.ADDON,
        },
        {
            offerName = "Arena Champion full",
            lookType = {[0] = 885, [1] = 884}, -- 0 = female, 1 = male.
            price = 1200,
            type = OFFER_TYPE.ADDON,
        },
        {
            offerName = "Grove Keeper full",
            lookType = {[0] = 909, [1] = 908}, -- 0 = female, 1 = male.
            price = 1000,
            type = OFFER_TYPE.ADDON,
        },
        {
            offerName = "Pumpkin Mummy full",
            lookType = {[0] = 1128, [1] = 1127}, -- 0 = female, 1 = male.
            price = 1800,
            type = OFFER_TYPE.ADDON,
        },
        {
            offerName = "Dream Warrior full",
            lookType = {[0] = 1147, [1] = 1146}, -- 0 = female, 1 = male.
            price = 1600,
            type = OFFER_TYPE.ADDON,
        },
        {
            offerName = "Formal Dress full",
            lookType = {[0] = 1461, [1] = 1460}, -- 0 = female, 1 = male.
            price = 2500,
            type = OFFER_TYPE.ADDON,
        },           
    },
},
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
{
    categoryName = "Mounts",
    categoryId = 3,
    offers = {
        {
            offerName = "Tin Lizzard",
            mountId = 8,
            price = 800,
            type = OFFER_TYPE.MOUNT,
        },
        {
            offerName = "Draptor",
            mountId = 6,
            price = 690,
            type = OFFER_TYPE.MOUNT,
        },
        {
            offerName = "Uniwheel",
            mountId = 15,
            price = 700,
            type = OFFER_TYPE.MOUNT,
        },
        {
            offerName = "Crystal Wolf",
            mountId = 16,
            price = 850,
            type = OFFER_TYPE.MOUNT,
        },
        {
            offerName = "Phant",
            mountId = 182,
            price = 1200,
            type = OFFER_TYPE.MOUNT,
        },
        {
            offerName = "Water Buffalo",
            mountId = 35,
            price = 600,
            type = OFFER_TYPE.MOUNT,
        },           
    },
},
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
{
    categoryName = "Userful Things",
    categoryId = 4,
    offers = {
        {
            offerName = "Boost Exp 2h",
            count = 2,
            price = 1000,
            type = OFFER_TYPE.EXP_BOOST,
        },
        {
            offerName = "Boost Exp 4h",
            count = 4,
            price = 2000,
            type = OFFER_TYPE.EXP_BOOST,
        },           
    
    },
},
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------   
{
    categoryName = "Especial Offers",
    categoryId = 5,
    offers = {
        {
            offerName = "Blazing Unicorn",
            mountId = 113,
            price = 2600,
            type = OFFER_TYPE.MOUNT,
        },
        {
            offerName = "Golden addon full",
            lookType = {[0] = 1211, [1] = 1210}, -- 0 = female, 1 = male.
            price = 15000,
            type = OFFER_TYPE.ADDON,
        },
        {
            offerName = "Mage Addon Full",
            lookType = {[0] = 138, [1] = 130}, -- 0 = female, 1 = male.
            price = 45000,
            type = OFFER_TYPE.ADDON,
        },           
        {
            offerName = "soul stone",
            itemId = 5809,
            count = 1,
            price = 1000,
            type = OFFER_TYPE.ITEM,
        },
        {
            offerName = "golden helmet",
            itemId = 2471,
            count = 1,
            price = 30000,
            type = OFFER_TYPE.ITEM,
        },           
    },
},   
----------------------------------------------------------------------------------------------
-------------==== category====----------------------------------------------------------------
}

local function getShopCategory(param)
    if not param then
        return nil
    end
    
    for index, category in pairs(TournamentShop) do
        if type(param) == "string" then
            if param == category.categoryName then
                return category
            end
        elseif type(param) == "number" then
            if param == category.categoryId then
                return category
            end
        end
    end
    
    return nil
end

local function getShopOffer(categoryParam, param)
    if not categoryParam or not param then
        return nil
    end
    local category
    
    if type(categoryParam) == "table" and categoryParam.offers then
        category = categoryParam
    elseif type(categoryParam) == "string" or type(categoryParam) == "number" then
        category = getShopCategory(categoryParam)
    end
    
    if not category then
        return nil
    end
    
    for index=1, #category.offers do
        local offer = category.offers[index]
        if offer then
            if index == param then
                return offer
            end
        end
    end
    
    return nil
end

local function processShopPurchase(creature, offer)
    local player = Player(creature)
    if not player or not offer or type(offer) ~= "table" then
        return false
    end
    if player:getTournamentCoinsBalance() < offer.price then
        player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have enough tournament.")
        return false
    end
    
    local retValue = OFFER_TYPE_FUNC[offer.type](player, offer)
    
    if not retValue.status then
        Spdlog.warn(string.format("[TournamentShop] Error: %s, Player: %s, Message: %s", retValue.internal, player:getName(), retValue.msg))
    end
    
    if retValue.msg ~= "no error" then
        player:sendTextMessage(MESSAGE_EVENT_ADVANCE, retValue.msg)
        return false
    end
    
    player:removeTournamentCoinsBalance(offer.price)
    
    player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You bought %sx %s for %s Tournament Coins.", offer.count, offer.offerName, offer.price))
    return true
end


local function sendShopCategoryModal(creature, id)
    local player = Player(creature)
    if not player or not id then
        return false
    end
    
    local category = getShopCategory(id)
    if not category then
        return false
    end
    
    local window = ModalWindow {
        title = 'Tournament Shop',
        message = string.format("You have %s Coins\nSelect offer.", player:getTournamentCoinsBalance())
    }
    
    for index=1, #category.offers do
        local offer = category.offers[index]
        if offer then
            local offerCountStr = tostring(offer.count or "")
            if offer.type == OFFER_TYPE.ITEM then
                offerCountStr = offer.count.."x"
            elseif offer.type == OFFER_TYPE.EXP_BOOST then
                offerCountStr = ""
            elseif offer.type == OFFER_TYPE.ADDON then
                offerCountStr = ""
            elseif offer.type == OFFER_TYPE.MOUNT then
                offerCountStr = ""
            end
            local choice = window:addChoice(string.format("%s %s | Price: %s", offerCountStr, offer.offerName, offer.price))
            choice.id = index
        end
    end
    
    window:addButton('Buy',
        function(button, choice)
            local offer = getShopOffer(category, choice.id)
            if not offer then
                Spdlog.warn(string.format("Failed to find offer: %s category: %s, player: %s", choice.id, category.categoryName, player:getName()));
                return false
            end
            processShopPurchase(creature, offer)
        end
    )
    
    window:addButton('Back',
        function(button, choice)
            sendTournamentShopModal(creature)
        end
    )
    
    window:addButton('Close')
    
    window:setDefaultEnterButton('Buy')
    window:setDefaultEscapeButton('Back')
    window:sendToPlayer(player)
    
    return true
end

function sendTournamentShopModal(creature)
    local player = Player(creature)
    if not player then
        return false
    end
    
    local window = ModalWindow {
        title = 'Tournament Shop',
        message = string.format("You have %s Tourn. Coins\nSelect category.", player:getTournamentCoinsBalance())
    }
    
    for _, category in pairs(TournamentShop) do
        local choice = window:addChoice(category.categoryName)
        choice.id = category.categoryId
    end
    
    window:addButton('Select',
        function(button, choice)
            sendShopCategoryModal(creature, choice.id)
        end
    )
    
    window:addButton('Close')
    
    window:setDefaultEnterButton('Select')
    window:setDefaultEscapeButton('Close')
    window:sendToPlayer(player)
    return true
end


local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)

npcType.onThink = function(npc, interval)
    npcHandler:onThink(npc, interval)
end

npcType.onAppear = function(npc, creature)
    npcHandler:onAppear(npc, creature)
end

npcType.onDisappear = function(npc, creature)
    npcHandler:onDisappear(npc, creature)
end

npcType.onMove = function(npc, creature, fromPosition, toPosition)
    npcHandler:onMove(npc, creature, fromPosition, toPosition)
end

npcType.onSay = function(npc, creature, type, message)
    npcHandler:onSay(npc, creature, type, message)
end

npcType.onCloseChannel = function(npc, creature)
    npcHandler:onCloseChannel(npc, creature)
end

npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true)

npcType:register(npcConfig)
 
shape1
shape2
shape3
shape4
shape5
shape6
Arriba