• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

OTC SHOP Aura and Wings PRoblem

jareczekjsp

Member
Joined
Jan 30, 2023
Messages
264
Solutions
1
Reaction score
22
GitHub
Jarek123
I have This Script Otc shop from Creaturescripts
LUA:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "https://revolut.me/jarosa7b6p" -- can be empty
local SHOP_AD = { -- can be nil
  image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
  url = "http://santera.hopto.org",
  text = ""
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE

CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

]]--

function init()
  -- Definiowanie kategorii zgodnie z bazą danych
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Premium Time"
  })
  local category2 = addCategory({
    type="item",
    item=ItemType(7409):getClientId(),
    count=1,
    name="MS/ED Items"
  })
  local category3 = addCategory({
    type="item",
    item=ItemType(7366):getClientId(),
    count=1,
    name="RP Items"
  })
  local category4 = addCategory({
    type="item",
    item=ItemType(7433):getClientId(),
    count=1,
    name="Knight Items"
  })
  local category5 = addCategory({
    type="item",
    item=ItemType(9969):getClientId(),
    count=1,
    name="VIP Items"
  })

  -- Dodawanie przedmiotów do kategorii
  category1.addItem(10, 2160, 50, "50 Crystal Coins", "50 crystal coins. They weigh 5.00 oz.")
  category1.addItem(10, 0, 30, "PACC 30", "30 Days of Premium Account")
 
  category2.addItem(20, 7409, 1, "Mega Staff", "[DAMAGE 950-1200] Only for sorcerers, druids.")
  category2.addItem(50, 2453, 1, "Assault Staff", "[DAMAGE 2750-3000] Only for sorcerers, druids.")
  category2.addItem(10, 2662, 1, "Santera Mage Hat", "(Arm:20, magic level +6, protection all +10%)")
  category2.addItem(10, 8866, 1, "Santera Mage Armor", "(Arm:20, magic level +8, protection all +10%)")
  category2.addItem(10, 7730, 1, "Santera Mage Legs", "(Arm:30, magic level +6, protection all +10%)")
  category2.addItem(15, 8907, 1, "Santera Spellbook", "(Def:100, magic level +10, protection all +8%)")

  category3.addItem(20, 7366, 1, "Mega Star", "(Atk:115, Def:0). Only for paladins.")
  category3.addItem(50, 2352, 1, "Assault Arrow", "Atk : 175 . Only for paladins.")
  category3.addItem(10, 7903, 1, "Santera Paladin Helmet", "(Arm:50, distance fighting +5, shielding +10)")
  category3.addItem(10, 8880, 1, "Santera Paladin Armor", "(Arm:20, distance fighting +5, shielding +10)")
  category3.addItem(10, 7885, 1, "Santera Paladin Legs", "(Arm:50, distance fighting +5, shielding +20)")
  category3.addItem(15, 8909, 1, "Santera Paladin Shield", "(Def:100, distance fighting +5, shielding +20)")

  category4.addItem(20, 7433, 1, "Mega Axe", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(20, 7407, 1, "Mega Sword", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(50, 7753, 1, "Assault Axe", "(Atk:150, Def:10). Only for knights.")
  category4.addItem(50, 7747, 1, "Assault Sword", "(Atk:150, Def:40). Only for knights.")
  category4.addItem(10, 7497, 1, "Santera Knight Helmet", "(Arm:30, protection all +8%)")
  category4.addItem(10, 8886, 1, "Santera Knight Armor", "(Arm:30, club fighting +6, protection all +8%)")
  category4.addItem(10, 7894, 1, "Santera Knight Legs", "(Arm:50, club fighting +6, shielding +10)")
  category4.addItem(15, 8906, 1, "Santera Knight Shield", "(Def:100, club fighting +5, shielding +15, protection all +10%)")

  category5.addItem(10, 9969, 1, "Frag remover", "Delete your frags")
  category5.addItem(10, 8300, 1, "Upgrade Rune", "You can upgrade your EQ +1")
  category5.addItem(20, 9932, 1, "Santera Boots", "SPEED +200 and Reg Mana/Hp 700/s")
  category5.addItem(20, 7697, 1, "Santera Ring", "Speed +100, HP 700/MANA 900 regeneration.")
  category5.addItem(10, 2196, 1, "Santera Amulet", "Protection all +10%")
  category5.addItem(5, 9693, 1, "Addon Doll", "SAY !addon hunter")
  category5.addItem(30, 2281, 1, "Donate VIP 30 Days", "Access to Donate Vip area")
  category5.addItem(5, 5957, 1, "EXP 10%", "Extra exp 10% more for 1 hour.")
  category5.addItem(13, 12505, 1, "Exp 50%", "Extra exp 50% more for 1 hour.")
end


function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback)     
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      })   
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status}) 
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return 
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init()   
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer")     
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)   
  end
  local status = callback(player, offer)
  if status == true then   
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_historyy` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
    local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

    if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then   
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
    end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultOutfitBuyAction(player, offer)
  return "default outfit buy action is not implemented"
end

function defaultImageBuyAction(player, offer)
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
and I would Like add my Auras and Wings But I try and I dont know how always is poroblem ;/
Code:
<?xml version="1.0" encoding="UTF-8"?>
<auras>
    <aura id="1" clientid="708" name="Aura 1" speed="20" />
    <aura id="2" clientid="709" name="Aura 2" speed="20" />
    <aura id="3" clientid="713" name="Aura 3" speed="20" />
    <aura id="4" clientid="715" name="Aura 4" speed="20" />
    <aura id="5" clientid="718" name="Aura 5" speed="20" />
    <aura id="6" clientid="719" name="Aura 6" speed="20" />
    <aura id="7" clientid="161" name="Aura 7" speed="20" />
    <aura id="8" clientid="162" name="Aura 8" speed="20" />
    <aura id="9" clientid="163" name="Aura 9" speed="20" />
    <aura id="10" clientid="164" name="Aura 10" speed="20" />
    <aura id="11" clientid="165" name="Aura 12" speed="20" />
    <aura id="12" clientid="165" name="Aura 13" speed="20" />
    <aura id="13" clientid="166" name="Aura 14" speed="20" />
    <aura id="14" clientid="167" name="Aura 15" speed="20" />
    <aura id="15" clientid="168" name="Aura 16" speed="20" />
    <aura id="16" clientid="169" name="Aura 17" speed="20" />
    <aura id="17" clientid="170" name="Aura 18" speed="20" />
    <aura id="18" clientid="172" name="Aura 19" speed="20" />
    <aura id="19" clientid="173" name="Aura 20" speed="20" />
    <aura id="20" clientid="174" name="Aura 21" speed="20" />
    <aura id="21" clientid="1307" name="Aura 22" speed="20" />

</auras>

Code:
<?xml version="1.0" encoding="UTF-8"?>
<wings>
    <wing id="1" clientid="1525" name="Wings 1" speed="20" />
    <wing id="2" clientid="705" name="Wings 2" speed="20" />
    <wing id="3" clientid="706" name="Wings 3" speed="20" />
    <wing id="4" clientid="707" name="Wings 4" speed="20" />
    <wing id="5" clientid="710" name="Wings 5" speed="20" />
    <wing id="6" clientid="711" name="Wings 6" speed="20" />
    <wing id="7" clientid="175" name="Wings 7" speed="20" />
    <wing id="8" clientid="176" name="Wings 8" speed="20" />
    <wing id="9" clientid="177" name="Wings 9" speed="20" />
    <wing id="10" clientid="178" name="Wings 10" speed="20" />
    <wing id="11" clientid="179" name="Wings 11" speed="20" />
    <wing id="12" clientid="180" name="Wings 12" speed="20" />
    <wing id="13" clientid="181" name="Wings 13" speed="20" />
    <wing id="14" clientid="182" name="Wings 14" speed="20" />
    <wing id="15" clientid="183" name="Wings 15" speed="20" />
    <wing id="16" clientid="184" name="Wings 16" speed="20" />
    <wing id="17" clientid="185" name="Wings 17" speed="20" />
    <wing id="18" clientid="1315" name="JWings 18" speed="20" />

</wings>
 
Solution
Edited post:

In this script, previously, type only added the wings and aura ID to the player. I reviewed it and understood that a change was needed. Now, only wingID, auraID, and shaderID with the correct names will be added to the player.

The type part is only for displaying the visual picture.

It's working fine now.


LUA:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player...
When I buy wing or aura I no buy and I see error in console
LUA:
Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/extendedopcode.lua:onExtendedOpcode
data/creaturescripts/scripts/extendedopcode.lua:409: attempt to concatenate field 'name' (a nil value)
stack traceback:
        [C]: in function '__concat'
        data/creaturescripts/scripts/extendedopcode.lua:409: in function 'callback'
        data/creaturescripts/scripts/extendedopcode.lua:328: in function 'processBuy'
        data/creaturescripts/scripts/extendedopcode.lua:308: in function <data/creaturescripts/scripts/extendedopcode.lua:286>

Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/extendedopcode.lua:onExtendedOpcode
data/creaturescripts/scripts/extendedopcode.lua:409: attempt to concatenate field 'name' (a nil value)
stack traceback:
        [C]: in function '__concat'
        data/creaturescripts/scripts/extendedopcode.lua:409: in function 'callback'
        data/creaturescripts/scripts/extendedopcode.lua:328: in function 'processBuy'
        data/creaturescripts/scripts/extendedopcode.lua:308: in function <data/creaturescripts/scripts/extendedopcode.lua:286>

My script is

Code:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "https://revolut.me/jarosa7b6p" -- can be empty
local SHOP_AD = { -- can be nil
  image = "",
  url = "http://santera.hopto.org",
  text = "Santera.Hopto.Org"
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE

CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

]]--

function init()
  -- Definiowanie kategorii zgodnie z baza danych
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Premium Time"
  })
  local category2 = addCategory({
    type="item",
    item=ItemType(7409):getClientId(),
    count=1,
    name="Sorcerer/Druid Items"
  })
  local category3 = addCategory({
    type="item",
    item=ItemType(7366):getClientId(),
    count=1,
    name="Paladin Items"
  })
  local category4 = addCategory({
    type="item",
    item=ItemType(7433):getClientId(),
    count=1,
    name="Knight Items"
  })
  local category5 = addCategory({
    type="item",
    item=ItemType(9969):getClientId(),
    count=1,
    name="VIP Items"
  })

---------- wings and auras, shader -------------
local category6 = addCategory({
    type = "outfit",
    name = "Wings",
    outfit = {
      type = 1884,

      rotating = true
    }
  })
  local category7 = addCategory({
    type = "outfit",
    name = "Aura",
    outfit = {
      type = 1884,

      rotating = true
    }
  })
  local category8 = addCategory({
    type = "outfit",
    name = "Shader",
    outfit = {
      type = 1884,

      rotating = true
    }
  })

  -- Dodawanie przedmiotów do kategorii
  category1.addItem(10, 2160, 50, "50 Crystal Coins", "50 crystal coins. They weigh 5.00 oz.")
  category1.addItem(10, 0, 30, "PACC 30", "30 Days of Premium Account")
 
  category2.addItem(20, 7409, 1, "Mega Staff", "[DAMAGE 950-1200] Only for sorcerers, druids.")
  category2.addItem(50, 2453, 1, "Assault Staff", "[DAMAGE 2750-3000] Only for sorcerers, druids.")
  category2.addItem(10, 2662, 1, "Santera Mage Hat", "(Arm:20, magic level +6, protection all +10%)")
  category2.addItem(10, 8866, 1, "Santera Mage Armor", "(Arm:20, magic level +8, protection all +10%)")
  category2.addItem(10, 7730, 1, "Santera Mage Legs", "(Arm:30, magic level +6, protection all +10%)")
  category2.addItem(15, 8907, 1, "Santera Spellbook", "(Def:100, magic level +10, protection all +8%)")

  category3.addItem(20, 7366, 1, "Mega Star", "(Atk:115, Def:0). Only for paladins.")
  category3.addItem(50, 2352, 1, "Assault Arrow", "Atk : 175 . Only for paladins.")
  category3.addItem(10, 7903, 1, "Santera Paladin Helmet", "(Arm:50, distance fighting +5, shielding +10)")
  category3.addItem(10, 8880, 1, "Santera Paladin Armor", "(Arm:20, distance fighting +5, shielding +10)")
  category3.addItem(10, 7885, 1, "Santera Paladin Legs", "(Arm:50, distance fighting +5, shielding +20)")
  category3.addItem(15, 8909, 1, "Santera Paladin Shield", "(Def:100, distance fighting +5, shielding +20)")

  category4.addItem(20, 7433, 1, "Mega Axe", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(20, 7407, 1, "Mega Sword", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(50, 7753, 1, "Assault Axe", "(Atk:150, Def:10). Only for knights.")
  category4.addItem(50, 7747, 1, "Assault Sword", "(Atk:150, Def:40). Only for knights.")
  category4.addItem(10, 7497, 1, "Santera Knight Helmet", "(Arm:30, protection all +8%)")
  category4.addItem(10, 8886, 1, "Santera Knight Armor", "(Arm:30, club fighting +6, protection all +8%)")
  category4.addItem(10, 7894, 1, "Santera Knight Legs", "(Arm:50, club fighting +6, shielding +10)")
  category4.addItem(15, 8906, 1, "Santera Knight Shield", "(Def:100, club fighting +5, shielding +15, protection all +10%)")

  category5.addItem(10, 9969, 1, "Frag remover", "Delete your frags")
  category5.addItem(10, 8300, 1, "Upgrade Rune", "You can upgrade your EQ +1")
  category5.addItem(20, 9932, 1, "Santera Boots", "SPEED +200 and Reg Mana/Hp 700/s")
  category5.addItem(20, 7697, 1, "Santera Ring", "Speed +100, HP 700/MANA 900 regeneration.")
  category5.addItem(10, 2196, 1, "Santera Amulet", "Protection all +10%")
  category5.addItem(5, 9693, 1, "Addon Doll", "SAY !addon hunter")
  category5.addItem(30, 2281, 1, "Donate VIP 30 Days", "Access to Donate Vip area")
  category5.addItem(5, 5957, 1, "EXP 10%", "Extra exp 10% more for 1 hour.")
  category5.addItem(13, 12505, 1, "Exp 50%", "Extra exp 50% more for 1 hour.")
-------- Wings and Auras Shader ------

category6.addOutfit(250, {
wing = 121,
type = 1884,
rotating = true,
name = "wing" -- Provide the name of the outfit or mount here
}, "wing", "this is your new cool wings. You can buy it here.")


category7.addOutfit(250, {
aura = 121,
type = 1884,
rotating = true,
name = "aura" -- Provide the name of the outfit or mount here
}, "aura", "this is your new cool wings. You can buy it here.")


category8.addOutfit(250, {
    shaderAdd = 1,
    type = 128,
    shader = 'Rainbow Outline',
    feet = 114,
    legs = 114,
    body = 116,
    head = 2,
    rotating = true,
    name = "Rainbow Outfit" -- Provide the name of the outfit or mount here
}, "aura", "this is your new cool wings. You can buy it here.")
end


function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback)     
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      })   
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status}) 
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return 
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init()   
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer")     
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)   
  end
  local status = callback(player, offer)
  if status == true then   
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_historyy` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
    local resultId = db.storeQuery("SELECT * FROM `shop_historyy` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

    if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then   
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
    end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultOutfitBuyAction(player, offer)
    local wingId = offer['hasWings']
    local auraId = offer['hasAuras']
    local shaderId = offer['shader']
 
    local points = getPoints(player)
    if offer['cost'] > points or points < 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You don't have enough points to buy this outfit")
        return false
    end
  
    -- Check if player already has the wings, auras, or shader
    if wingId and wingId > 0 and player:hasWing(wingId) then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You already have these wings!")
        return false
    end
  
    if auraId and auraId > 0 and player:hasAura(auraId) then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You already have this aura!")
        return false
    end
  
    if shaderId and tonumber(shaderId) and tonumber(shaderId) > 0 and player:hasShader(tonumber(shaderId)) then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You already have this shader!")
        return false
    end
 
    db.query("UPDATE `accounts` SET `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
  
    local message = "You have purchased the " .. offer['name'] .. "!"
    player:sendTextMessage(MESSAGE_INFO_DESCR, message)
  
    if wingId and wingId > 0 then
        player:addWings(wingId)
    end
  
    if auraId and auraId > 0 then
        player:addAura(auraId)
    end
      
    if shaderId and tonumber(shaderId) and tonumber(shaderId) > 0 then
        player:addShader(tonumber(shaderId))
    end
  
    db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
 
    return true
end

function defaultImageBuyAction(player, offer)
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
 
Edited post:

In this script, previously, type only added the wings and aura ID to the player. I reviewed it and understood that a change was needed. Now, only wingID, auraID, and shaderID with the correct names will be added to the player.

The type part is only for displaying the visual picture.

It's working fine now.


LUA:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "https://revolut.me/jarosa7b6p" -- can be empty
local SHOP_AD = { -- can be nil
  image = "",
  url = "http://santera.hopto.org",
  text = "Santera.Hopto.Org"
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE

CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

]]--

function init()
  -- Define categories according to the database
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Premium Time"
  })
  local category2 = addCategory({
    type="item",
    item=ItemType(7409):getClientId(),
    count=1,
    name="Sorcerer/Druid Items"
  })
  local category3 = addCategory({
    type="item",
    item=ItemType(7366):getClientId(),
    count=1,
    name="Paladin Items"
  })
  local category4 = addCategory({
    type="item",
    item=ItemType(7433):getClientId(),
    count=1,
    name="Knight Items"
  })
  local category5 = addCategory({
    type="item",
    item=ItemType(9969):getClientId(),
    count=1,
    name="VIP Items"
  })

---------- wings and auras, shader -------------
local category6 = addCategory({
    type = "outfit",
    name = "Wings",
    outfit = {
      type = 1884,
      rotating = true
    }
  })
  local category7 = addCategory({
    type = "outfit",
    name = "Aura",
    outfit = {
      type = 1884,
      rotating = true
    }
  })
  local category8 = addCategory({
    type = "outfit",
    name = "Shader",
    outfit = {
      type = 1884,
      rotating = true
    }
  })

  -- Adding items to categories
  category1.addItem(10, 2160, 50, "50 Crystal Coins", "50 crystal coins. They weigh 5.00 oz.")
  category1.addItem(10, 0, 30, "PACC 30", "30 Days of Premium Account")
 
  category2.addItem(20, 7409, 1, "Mega Staff", "[DAMAGE 950-1200] Only for sorcerers, druids.")
  category2.addItem(50, 2453, 1, "Assault Staff", "[DAMAGE 2750-3000] Only for sorcerers, druids.")
  category2.addItem(10, 2662, 1, "Santera Mage Hat", "(Arm:20, magic level +6, protection all +10%)")
  category2.addItem(10, 8866, 1, "Santera Mage Armor", "(Arm:20, magic level +8, protection all +10%)")
  category2.addItem(10, 7730, 1, "Santera Mage Legs", "(Arm:30, magic level +6, protection all +10%)")
  category2.addItem(15, 8907, 1, "Santera Spellbook", "(Def:100, magic level +10, protection all +8%)")

  category3.addItem(20, 7366, 1, "Mega Star", "(Atk:115, Def:0). Only for paladins.")
  category3.addItem(50, 2352, 1, "Assault Arrow", "Atk : 175 . Only for paladins.")
  category3.addItem(10, 7903, 1, "Santera Paladin Helmet", "(Arm:50, distance fighting +5, shielding +10)")
  category3.addItem(10, 8880, 1, "Santera Paladin Armor", "(Arm:20, distance fighting +5, shielding +10)")
  category3.addItem(10, 7885, 1, "Santera Paladin Legs", "(Arm:50, distance fighting +5, shielding +20)")
  category3.addItem(15, 8909, 1, "Santera Paladin Shield", "(Def:100, distance fighting +5, shielding +20)")

  category4.addItem(20, 7433, 1, "Mega Axe", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(20, 7407, 1, "Mega Sword", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(50, 7753, 1, "Assault Axe", "(Atk:150, Def:10). Only for knights.")
  category4.addItem(50, 7747, 1, "Assault Sword", "(Atk:150, Def:40). Only for knights.")
  category4.addItem(10, 7497, 1, "Santera Knight Helmet", "(Arm:30, protection all +8%)")
  category4.addItem(10, 8886, 1, "Santera Knight Armor", "(Arm:30, club fighting +6, protection all +8%)")
  category4.addItem(10, 7894, 1, "Santera Knight Legs", "(Arm:50, club fighting +6, shielding +10)")
  category4.addItem(15, 8906, 1, "Santera Knight Shield", "(Def:100, club fighting +5, shielding +15, protection all +10%)")

  category5.addItem(10, 9969, 1, "Frag remover", "Delete your frags")
  category5.addItem(10, 8300, 1, "Upgrade Rune", "You can upgrade your EQ +1")
  category5.addItem(20, 9932, 1, "Santera Boots", "SPEED +200 and Reg Mana/Hp 700/s")
  category5.addItem(20, 7697, 1, "Santera Ring", "Speed +100, HP 700/MANA 900 regeneration.")
  category5.addItem(10, 2196, 1, "Santera Amulet", "Protection all +10%")
  category5.addItem(5, 9693, 1, "Addon Doll", "SAY !addon hunter")
  category5.addItem(30, 2281, 1, "Donate VIP 30 Days", "Access to Donate Vip area")
  category5.addItem(5, 5957, 1, "EXP 10%", "Extra exp 10% more for 1 hour.")
  category5.addItem(13, 12505, 1, "Exp 50%", "Extra exp 50% more for 1 hour.")
 
 
-------- Wings and Auras, Shader ------

-------- Wings -------
category6.addOutfit(250, {
    wing = 121,
    type = 1888,
    rotating = true,
    name = "Dragon Wings"
}, "Dragon Wings", "These majestic dragon wings will make you stand out in any crowd.")

category6.addOutfit(300, {
    wing = 122,
    type = 1887,
    rotating = true,
    name = "Angel Wings"
}, "Angel Wings", "Pure white angel wings that radiate with holy light.")

category6.addOutfit(350, {
    wing = 119,
    type = 1886,
    rotating = true,
    name = "Demon Wings"
}, "Demon Wings", "Dark demonic wings with ember effects.")

-------- Auras -------
category7.addOutfit(250, {
    auras = 1,
    type = 1884,
    rotating = true,
    name = "Fire Aura"
}, "Fire Aura", "A burning aura that surrounds your character with mystical flames.")

category7.addOutfit(300, {
    auras = 2,
    type = 1884,
    rotating = true,
    name = "Ice Aura"
}, "Ice Aura", "A freezing aura that creates swirling ice crystals around you.")

category7.addOutfit(350, {
    auras = 3,
    type = 1884,
    rotating = true,
    name = "Lightning Aura"
}, "Lightning Aura", "An electric aura that crackles with lightning around your character.")

-------- Shader -------
category8.addOutfit(250, {
    shaderAdd = 1,
    type = 128,
    shader = 'Rainbow Outline',
    feet = 114,
    legs = 114,
    body = 116,
    head = 2,
    rotating = true,
    name = "Rainbow Shader"
}, "Rainbow Shader", "This colorful shader adds a rainbow effect to your character's outline.")

category8.addOutfit(300, {
    shaderAdd = 2,
    type = 128,
    shader = 'Neon Glow',
    feet = 114,
    legs = 114,
    body = 116,
    head = 2,
    rotating = true,
    name = "Neon Shader"
}, "Neon Shader", "A bright neon shader that makes your character glow in the dark.")

category8.addOutfit(350, {
    shaderAdd = 3,
    type = 128,
    shader = 'Gold Effect',
    feet = 114,
    legs = 114,
    body = 116,
    head = 2,
    rotating = true,
    name = "Gold Shader"
}, "Gold Shader", "A golden shader that makes your character shine like pure gold.")

end


function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback)     
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      -- Make sure outfit has a name property, using title if needed
      outfit.name = outfit.name or title
      
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      })   
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addPremiumDays = function(cost, image, title, description, count, callback)
      if not callback then
        callback = defaultPremiumBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description,
        count=count,
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status})
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init()   
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer")     
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)   
  end
  local status = callback(player, offer)
  if status == true then   
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
    local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

    if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then   
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
    end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultPremiumBuyAction(player, offer)
  if not offer["count"] then
    return "Invalid premium days offer"
  end
 
  if player:getPremiumDays() + offer["count"] > 360 then
    return "You cannot purchase more than 1 year of premium account."
  else
    player:addPremiumDays(offer["count"])
    return true
  end
end

function defaultOutfitBuyAction(player, offer)
    local outfit = offer['outfit'] or {}
    local displayName = offer['title'] or "outfit"
    local typeId = outfit['type']
    
    local points = getPoints(player)
    if offer['cost'] > points or points < 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You don't have enough points to buy this " .. displayName)
        return false
    end
 
    if player:getStorageValue(typeId) == 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You already have this " .. displayName .. "!")
        return false
    end
    
    if outfit['wing'] then
        player:addWings(outfit['wing'])
    elseif outfit['auras'] then
        player:addAura(outfit['auras'])
    elseif outfit['shaderAdd'] then
        player:addShader(outfit['shaderAdd'])
    else
    
        local categoryName = string.lower(displayName)
        
        if string.find(categoryName, "wing") then
            player:addWings(typeId)
        elseif string.find(categoryName, "aura") then
            player:addAura(typeId)
        elseif string.find(categoryName, "shader") then
            player:addShader(typeId)
        else
            pcall(function() player:addWings(typeId) end)
            pcall(function() player:addAura(typeId) end)
            pcall(function() player:addShader(typeId) end)
        end
    end
    
    player:setStorageValue(typeId, 1)
 
    local message = "You have purchased " .. displayName .. "!"
    player:sendTextMessage(MESSAGE_INFO_DESCR, message)
    
    return true
end

function defaultImageBuyAction(player, offer)
  if offer['count'] and tonumber(offer['count']) > 0 then
    return defaultPremiumBuyAction(player, offer)
  end
 
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
 
Last edited:
Solution
So I dont know why not working for me
LUA:
-- BETA VERSION, net tested yet
-- Instruction:
-- creaturescripts.xml      <event type="extendedopcode" name="Shop" script="shop.lua" />
-- and in login.lua         player:registerEvent("Shop")
-- create sql table shop_history
-- set variables
-- set up function init(), add there items and categories, follow examples
-- set up callbacks at the bottom to add player item/outfit/whatever you want

local SHOP_EXTENDED_OPCODE = 201
local SHOP_OFFERS = {}
local SHOP_CALLBACKS = {}
local SHOP_CATEGORIES = nil
local SHOP_BUY_URL = "https://revolut.me/jarosa7b6p" -- can be empty
local SHOP_AD = { -- can be nil
  image = "",
  url = "http://santera.hopto.org",
  text = "Santera.Hopto.Org"
}
local MAX_PACKET_SIZE = 50000

--[[ SQL TABLE

CREATE TABLE `shop_history` (
  `id` int(11) NOT NULL,
  `account` int(11) NOT NULL,
  `player` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `cost` int(11) NOT NULL,
  `details` varchar(500) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `shop_history`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `shop_history`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

]]--

function init()
  -- Define categories according to the database
  SHOP_CATEGORIES = {}

  local category1 = addCategory({
    type="item",
    item=ItemType(2160):getClientId(),
    count=100,
    name="Premium Time"
  })
  local category2 = addCategory({
    type="item",
    item=ItemType(7409):getClientId(),
    count=1,
    name="Sorcerer/Druid Items"
  })
  local category3 = addCategory({
    type="item",
    item=ItemType(7366):getClientId(),
    count=1,
    name="Paladin Items"
  })
  local category4 = addCategory({
    type="item",
    item=ItemType(7433):getClientId(),
    count=1,
    name="Knight Items"
  })
  local category5 = addCategory({
    type="item",
    item=ItemType(9969):getClientId(),
    count=1,
    name="VIP Items"
  })

---------- wings and auras, shader -------------
local category6 = addCategory({
    type = "outfit",
    name = "Wings",
    outfit = {
      type = 1884,
      rotating = true
    }
  })
  local category7 = addCategory({
    type = "outfit",
    name = "Aura",
    outfit = {
      type = 1884,
      rotating = true
    }
  })
  local category8 = addCategory({
    type = "outfit",
    name = "Shader",
    outfit = {
      type = 1884,
      rotating = true
    }
  })

  -- Adding items to categories
  category1.addItem(10, 2160, 50, "50 Crystal Coins", "50 crystal coins. They weigh 5.00 oz.")
  category1.addItem(10, 0, 30, "PACC 30", "30 Days of Premium Account")
 
  category2.addItem(20, 7409, 1, "Mega Staff", "[DAMAGE 950-1200] Only for sorcerers, druids.")
  category2.addItem(50, 2453, 1, "Assault Staff", "[DAMAGE 2750-3000] Only for sorcerers, druids.")
  category2.addItem(10, 2662, 1, "Santera Mage Hat", "(Arm:20, magic level +6, protection all +10%)")
  category2.addItem(10, 8866, 1, "Santera Mage Armor", "(Arm:20, magic level +8, protection all +10%)")
  category2.addItem(10, 7730, 1, "Santera Mage Legs", "(Arm:30, magic level +6, protection all +10%)")
  category2.addItem(15, 8907, 1, "Santera Spellbook", "(Def:100, magic level +10, protection all +8%)")

  category3.addItem(20, 7366, 1, "Mega Star", "(Atk:115, Def:0). Only for paladins.")
  category3.addItem(50, 2352, 1, "Assault Arrow", "Atk : 175 . Only for paladins.")
  category3.addItem(10, 7903, 1, "Santera Paladin Helmet", "(Arm:50, distance fighting +5, shielding +10)")
  category3.addItem(10, 8880, 1, "Santera Paladin Armor", "(Arm:20, distance fighting +5, shielding +10)")
  category3.addItem(10, 7885, 1, "Santera Paladin Legs", "(Arm:50, distance fighting +5, shielding +20)")
  category3.addItem(15, 8909, 1, "Santera Paladin Shield", "(Def:100, distance fighting +5, shielding +20)")

  category4.addItem(20, 7433, 1, "Mega Axe", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(20, 7407, 1, "Mega Sword", "(Atk:115, Def:25). Only for knights.")
  category4.addItem(50, 7753, 1, "Assault Axe", "(Atk:150, Def:10). Only for knights.")
  category4.addItem(50, 7747, 1, "Assault Sword", "(Atk:150, Def:40). Only for knights.")
  category4.addItem(10, 7497, 1, "Santera Knight Helmet", "(Arm:30, protection all +8%)")
  category4.addItem(10, 8886, 1, "Santera Knight Armor", "(Arm:30, club fighting +6, protection all +8%)")
  category4.addItem(10, 7894, 1, "Santera Knight Legs", "(Arm:50, club fighting +6, shielding +10)")
  category4.addItem(15, 8906, 1, "Santera Knight Shield", "(Def:100, club fighting +5, shielding +15, protection all +10%)")

  category5.addItem(10, 9969, 1, "Frag remover", "Delete your frags")
  category5.addItem(10, 8300, 1, "Upgrade Rune", "You can upgrade your EQ +1")
  category5.addItem(20, 9932, 1, "Santera Boots", "SPEED +200 and Reg Mana/Hp 700/s")
  category5.addItem(20, 7697, 1, "Santera Ring", "Speed +100, HP 700/MANA 900 regeneration.")
  category5.addItem(10, 2196, 1, "Santera Amulet", "Protection all +10%")
  category5.addItem(5, 9693, 1, "Addon Doll", "SAY !addon hunter")
  category5.addItem(30, 2281, 1, "Donate VIP 30 Days", "Access to Donate Vip area")
  category5.addItem(5, 5957, 1, "EXP 10%", "Extra exp 10% more for 1 hour.")
  category5.addItem(13, 12505, 1, "Exp 50%", "Extra exp 50% more for 1 hour.")
 
 
-------- Wings and Auras, Shader ------

-------- Wings -------
category6.addOutfit(250, {
    type = 121,
    rotating = true,
    name = "Dragon Wings"
}, "Dragon Wings", "These majestic dragon wings will make you stand out in any crowd.")

category6.addOutfit(300, {
    type = 122,
    rotating = true,
    name = "Angel Wings"
}, "Angel Wings", "Pure white angel wings that radiate with holy light.")

category6.addOutfit(350, {
    type = 123,
    rotating = true,
    name = "Demon Wings"
}, "Demon Wings", "Dark demonic wings with ember effects.")

-------- Auras -------
category7.addOutfit(20, {
    type = 708,
    rotating = true,
    name = "Aura 1"
}, "Aura 1", "Custom Aura around your character.")

category7.addOutfit(20, {
    type = 173,
    rotating = true,
    name = "Aura 2"
}, "Aura 2", "Custom Aura around your character.")

category7.addOutfit(20, {
    type = 165,
    rotating = true,
    name = "Aura 3"
}, "Aura 3", "Custom Aura around your character.")

category7.addOutfit(30, {
    type = 163,
    rotating = true,
    name = "Aura 4"
}, "Aura 4", "Custom Aura around your character.")

category7.addOutfit(30, {
    type = 1307,
    rotating = true,
    name = "Aura 5"
}, "Aura 5", "Custom Aura around your character.")

category7.addOutfit(30, {
    type = 719,
    rotating = true,
    name = "Aura 6"
}, "Aura 6", "Custom Aura around your character.")

category7.addOutfit(30, {
    type = 713,
    rotating = true,
    name = "Aura 7"
}, "Aura 7", "Custom Aura around your character.")

-------- Shader -------
category8.addOutfit(250, {
    type = 1,
    shader = 'Rainbow Outline',
    rotating = true,
    name = "Rainbow Shader"
}, "Rainbow Shader", "This colorful shader adds a rainbow effect to your character's outline.")

category8.addOutfit(300, {
    type = 2,
    shader = 'Neon Glow',
    rotating = true,
    name = "Neon Shader"
}, "Neon Shader", "A bright neon shader that makes your character glow in the dark.")

category8.addOutfit(350, {
    type = 3,
    shader = 'Gold Effect',
    rotating = true,
    name = "Gold Shader"
}, "Gold Shader", "A golden shader that makes your character shine like pure gold.")

end


function addCategory(data)
  data['offers'] = {}
  table.insert(SHOP_CATEGORIES, data)
  table.insert(SHOP_CALLBACKS, {})
  local index = #SHOP_CATEGORIES
  return {
    addItem = function(cost, itemId, count, title, description, callback)     
      if not callback then
        callback = defaultItemBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="item",
        item=ItemType(itemId):getClientId(), -- displayed
        itemId=itemId,
        count=count,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addOutfit = function(cost, outfit, title, description, callback)
      if not callback then
        callback = defaultOutfitBuyAction
      end
      -- Make sure outfit has a name property, using title if needed
      outfit.name = outfit.name or title
      
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="outfit",
        outfit=outfit,
        title=title,
        description=description
      })   
      table.insert(SHOP_CALLBACKS[index], callback)
    end,
    addImage = function(cost, image, title, description, callback)
      if not callback then
        callback = defaultImageBuyAction
      end
      table.insert(SHOP_CATEGORIES[index]['offers'], {
        cost=cost,
        type="image",
        image=image,
        title=title,
        description=description
      })
      table.insert(SHOP_CALLBACKS[index], callback)
    end
  }
end

function getPoints(player)
  local points = 0
  local resultId = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. player:getAccountId())
  if resultId ~= false then
    points = result.getDataInt(resultId, "premium_points")
    result.free(resultId)
  end
  return points
end

function getStatus(player)
  local status = {
    ad = SHOP_AD,
    points = getPoints(player),
    buyUrl = SHOP_BUY_URL
  }
  return status
end

function sendJSON(player, action, data, forceStatus)
  local status = nil
  if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
      status = getStatus(player)
  end
  player:setStorageValue(1150001, os.time())
 

  local buffer = json.encode({action = action, data = data, status = status})
  local s = {}
  for i=1, #buffer, MAX_PACKET_SIZE do
     s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
  end
  local msg = NetworkMessage()
  if #s == 1 then
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString(s[1])
    msg:sendToPlayer(player)
    return
  end
  -- split message if too big
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("S" .. s[1])
  msg:sendToPlayer(player)
  for i=2,#s - 1 do
    msg = NetworkMessage()
    msg:addByte(50)
    msg:addByte(SHOP_EXTENDED_OPCODE)
    msg:addString("P" .. s[i])
    msg:sendToPlayer(player)
  end
  msg = NetworkMessage()
  msg:addByte(50)
  msg:addByte(SHOP_EXTENDED_OPCODE)
  msg:addString("E" .. s[#s])
  msg:sendToPlayer(player)
end

function sendMessage(player, title, msg, forceStatus)
  sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
end

function onExtendedOpcode(player, opcode, buffer)
  if opcode ~= SHOP_EXTENDED_OPCODE then
    return false
  end
  local status, json_data = pcall(function() return json.decode(buffer) end)
  if not status then
    return false
  end

  local action = json_data['action']
  local data = json_data['data']
  if not action or not data then
    return false
  end

  if SHOP_CATEGORIES == nil then
    init()   
  end

  if action == 'init' then
    sendJSON(player, "categories", SHOP_CATEGORIES)
  elseif action == 'buy' then
    processBuy(player, data)
  elseif action == "history" then
    sendHistory(player)
  end
  return true
end

function processBuy(player, data)
  local categoryId = tonumber(data["category"])
  local offerId = tonumber(data["offer"])
  local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
  local callback = SHOP_CALLBACKS[categoryId][offerId]
  if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
    sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
    return sendMessage(player, "Error!", "Invalid offer")     
  end
  local points = getPoints(player)
  if not offer['cost'] or offer['cost'] > points or points < 1 then
    return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)   
  end
  local status = callback(player, offer)
  if status == true then   
    db.query("UPDATE `accounts` set `premium_points` = `premium_points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
    db.asyncQuery("INSERT INTO `shop_historyy` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
    return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
  end
  if status == nil or status == false then
    status = "Unknown error while buying " .. offer['title']
  end
  sendMessage(player, "Error!", status)
end

function sendHistory(player)
  if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
    return -- min 10s delay
  end
  player:setStorageValue(1150002, os.time())
 
  local history = {}
    local resultId = db.storeQuery("SELECT * FROM `shop_historyy` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")

    if resultId ~= false then
    repeat
      local details = result.getDataString(resultId, "details")
      local status, json_data = pcall(function() return json.decode(details) end)
      if not status then   
        json_data = {
          type = "image",
          title = result.getDataString(resultId, "title"),
          cost = result.getDataInt(resultId, "cost")
        }
      end
      table.insert(history, json_data)
      history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
    until not result.next(resultId)
    result.free(resultId)
    end
 
  sendJSON(player, "history", history)
end

-- BUY CALLBACKS
-- May be useful: print(json.encode(offer))

function defaultItemBuyAction(player, offer)
  -- todo: check if has capacity
  if player:addItem(offer["itemId"], offer["count"], false) then
    return true
  end
  return "Can't add item! Do you have enough space?"
end

function defaultOutfitBuyAction(player, offer)
    local outfit = offer['outfit'] or {}
    local displayName = offer['title'] or "outfit"  -- Fallback to "outfit" if title not found
    local typeId = outfit['type']
    
    local points = getPoints(player)
    if offer['cost'] > points or points < 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You don't have enough points to buy this " .. displayName)
        return false
    end
 
    if player:getStorageValue(typeId) == 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You already have this " .. displayName .. "!")
        return false
    end
    
    local categoryName = string.lower(displayName)
    
    if string.find(categoryName, "wing") then
        player:addWings(typeId)
    elseif string.find(categoryName, "aura") then
        player:addAura(typeId)
    elseif string.find(categoryName, "shader") then
        player:addShader(typeId)
    else
        pcall(function() player:addWings(typeId) end)
        pcall(function() player:addAura(typeId) end)
        pcall(function() player:addShader(typeId) end)
    end
    
    player:setStorageValue(typeId, 1)
 
    local message = "You have purchased " .. displayName .. "!"
    player:sendTextMessage(MESSAGE_INFO_DESCR, message)
    
    return true
end

function defaultImageBuyAction(player, offer)
  return "default image buy action is not implemented"
end

function customImageBuyAction(player, offer)
  return "custom image buy action is not implemented. Offer: " .. offer['title']
end
 
ok working normal buy but when wa type 709, was picture aura but when i change for type 1 is now Zrzut ekranu 2025-03-14 235603.webphow \i can change or normal aura picture?
Post automatically merged:

Mateus Can you help me ?please
Post automatically merged:

Can be Like that?
LUA:
<?xml version="1.0" encoding="UTF-8"?>
<auras>
    <aura id="708" clientid="708" name="Aura 1" speed="20" />
    <aura id="709" clientid="709" name="Aura 2" speed="20" />
    <aura id="713" clientid="713" name="Aura 3" speed="20" />
    <aura id="715" clientid="715" name="Aura 4" speed="20" />
    <aura id="718" clientid="718" name="Aura 5" speed="20" />
    <aura id="719" clientid="719" name="Aura 6" speed="20" />
    <aura id="161" clientid="161" name="Aura 7" speed="20" />
    <aura id="162" clientid="162" name="Aura 8" speed="20" />
    <aura id="163" clientid="163" name="Aura 9" speed="20" />
    <aura id="164" clientid="164" name="Aura 10" speed="20" />
    <aura id="165" clientid="165" name="Aura 12" speed="20" />
    <aura id="165" clientid="165" name="Aura 13" speed="20" />
    <aura id="166" clientid="166" name="Aura 14" speed="20" />
    <aura id="167" clientid="167" name="Aura 15" speed="20" />
    <aura id="168" clientid="168" name="Aura 16" speed="20" />
    <aura id="169" clientid="169" name="Aura 17" speed="20" />
    <aura id="170" clientid="170" name="Aura 18" speed="20" />
    <aura id="172" clientid="172" name="Aura 19" speed="20" />
    <aura id="173" clientid="173" name="Aura 20" speed="20" />
    <aura id="174" clientid="174" name="Aura 21" speed="20" />
    <aura id="1307" clientid="1307" name="Aura 22" speed="20" />

</auras>
like taht working all but dont will be problems?
 
Last edited:
When you post, just let me know if something is wrong and wait until I return to check your comment. No need to send multiple messages like that.

I was away from the PC, went to sleep, and saw your messages when I woke up. I understand the situation.

I’ve already edited the post above.

What did I change?
wing = 121 → This is the ID that will be added to the player.
type = 1888 → This is the one that will display the visual picture.
 
Back
Top