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
Member
Messages
28
Points
133
Ayuda convertir este NPC para usarlo en canary, consta de comprar items, addons, mounts, exp boost x tournament coins, por favor, ygracias de antemano
JavaScript:
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>
 
Ayuda convertir este NPC para usarlo en canary, consta de comprar items, addons, mounts, exp boost x tournament coins, por favor, ygracias de antemano
JavaScript:
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:


JavaScript:
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)
 

Tibia Server Lists

#1 FEATURED US Ultron OT 1,515 / 5,000 EXPStages PROTO8.6 ENGINEUltron OT RANK PTS6.6 -9% VOTES0 #2 FEATURED US Nefera 192 / 0 EXPx1 PROTO15.20 ENGINECrystal Server RANK PTS5.9 +25% VOTES0 #3 FEATURED CL ZHYRAVEX 13 / 500 EXPx5 PROTO10.98 ENGINETFS 1.4.2 RANK PTS4.7 +100% VOTES0 #4 FEATURED ES Deixis 0 / 0 EXPStages PROTO1098 ENGINETFS RANK PTS4.0 -61% VOTES1 #5 DE DBU Online 207 / 400 EXP11 PROTO8.6 ENGINETFS 1.0 RANK PTS6.1 +1,000% VOTES0 #6 BR Exordion Legacy 82 / 2,000 EXPx2 PROTO7.72 ENGINEExordion RANK PTS5.7 +2% VOTES0 #7 MX Marlboro War 33 / 300 EXP999 PROTO8.6 ENGINETFS 0.3.7 RANK PTS5.5 -28% VOTES0 #8 BR Tibinha YurOts 28 / 150 EXPStages PROTO7.4 ENGINEYurOTS 1.5 RANK PTS5.5 +3% VOTES0 #9 ES JustRL FreeTC 1530 21 / 250 EXPx2 PROTO15.30 ENGINECrystal Server RANK PTS5.0 +19% VOTES0 #10 US Wiki Ots 55 / 1,000 EXPx9999 PROTOcustom client ENGINETFS 1.4.2 RANK PTS5.0 +100% VOTES0 #11 PL Lubelski 11 / 1,000 EXP200 PROTO7.6 ENGINELubelskiOTS 2.56.0 RANK PTS4.9 -17% VOTES0 #12 BR Turion Server 0 / 2,000 EXPx1 PROTO7.4 ENGINETurion 7.7 Project RANK PTS4.6 +253% VOTES0 #13 PL ReWar 0 / 2,000 EXPcustom PROTO8.6 ENGINEOther RANK PTS4.6 +100% VOTES0 #14 US Halera 0 / 0 EXPStages PROTO10.98 ENGINETFS 1.4.2 RANK PTS4.4 -80% VOTES0 #15 PL Velesta 0 / 1,000 EXP20 PROTO8.0 ENGINETFS 1.4.2 RANK PTS4.4 -100% VOTES0 #16 US BerethaOnline 0 / 2,000 EXPStages PROTO15.0 ENGINETFS RANK PTS4.3 -76% VOTES0 #17 ES Farlia Global 2026 0 / 333 EXPx5 PROTO15.25 ENGINEFarliaServ 1.525 RANK PTS4.0 -100% VOTES0 #18 SE MortalisOT Custom 0 / 500 EXP100x -1.2x PROTO10.98 ENGINEMortalisOT V1 RANK PTS4.0 -100% VOTES0 #19 RU Oldavia | 8.60 0 / 0 EXPx9999 PROTO8.6 ENGINETFS 1.3 RANK PTS3.9 +100% VOTES0 #20 US Rommania 0 / 3,000 EXPstages, x10 PROTO7.4 ENGINETFS 0.4 RANK PTS3.6 -89% VOTES0 #21 MX Maeli 2 / 150 EXPx10 PROTO10.98 ENGINETFS 1.4.2 RANK PTS3.5 -33% VOTES0 #22 ES DarkDragon EXPx10 PROTO8.6 ENGINECanary RANK PTS0.0 0% VOTES0
Top