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

3zequi3l

Hellgrave Premium
Miembro
LV
12
 
Awards
10
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

Miembro del equipo
Webdesigner
LV
58
 
Awards
38
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)
 
Arriba