You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1120 lines
32 KiB

--[[
Finalizing Market:
Note: Feel free to work on any area and submit
it as a pull request from your git fork.
BeniS's Skype: benjiz69
List:
* Add constraints for creating/buying offers:
- Add max market offers or a new method for updating depot items
- Add timer before you can create another offer (1 minute?)
- Add a check for buying offers (if you do not have enough balance)
* Add offer management:
- Current Offers
- Offer History
* Optimize Offer Updates:
- Cache and avoid dead loop runs
* Clean up the interface building
- Add a new market interface file to handle building?
* Optimize loading market items:
- Cache items to their categories
* Add offer table column ordering.
- Player Name, Amount, Total Price, Peice Price and Ends At
* Add simple close button.
]]
Market = {}
local protocol = runinsandbox('marketprotocol.lua')
marketWindow = nil
mainTabBar = nil
displaysTabBar = nil
offersTabBar = nil
selectionTabBar = nil
marketOffersPanel = nil
browsePanel = nil
overviewPanel = nil
itemOffersPanel = nil
itemDetailsPanel = nil
itemStatsPanel = nil
myOffersPanel = nil
currentOffersPanel = nil
offerHistoryPanel = nil
itemsPanel = nil
selectedOffer = {}
nameLabel = nil
feeLabel = nil
balanceLabel = nil
totalPriceEdit = nil
piecePriceEdit = nil
amountEdit = nil
searchEdit = nil
radioItemSet = nil
selectedItem = nil
offerTypeList = nil
categoryList = nil
subCategoryList = nil
slotFilterList = nil
createOfferButton = nil
buyButton = nil
sellButton = nil
anonymous = nil
filterButtons = {}
buyOfferTable = nil
sellOfferTable = nil
detailsTable = nil
buyStatsTable = nil
sellStatsTable = nil
marketOffers = {}
marketItems = {}
information = {}
currentItems = {}
fee = 0
local offerTableHeader = {
{['text'] = 'Player Name', ['width'] = 100},
{['text'] = 'Amount', ['width'] = 60},
{['text'] = 'Total Price', ['width'] = 90},
{['text'] = 'Piece Price', ['width'] = 80},
{['text'] = 'Ends at', ['width'] = 120}
}
local function isItemValid(item, category)
local searchFilter = searchEdit:getText():lower()
local useSearchFilter = false
if searchFilter and searchFilter:len() > 2 then
useSearchFilter = true
end
local filterSearchAll = filterButtons[MarketFilters.SearchAll]:isChecked()
if filterSearchAll and useSearchFilter then
category = MarketCategory.All
end
if item.marketData.category ~= category and category ~= MarketCategory.All then
return false
end
-- filter item
local slotFilter = false
if slotFilterList:isEnabled() then
slotFilter = getMarketSlotFilterId(slotFilterList:getCurrentOption().text)
end
local marketData = item.marketData
local filterVocation = filterButtons[MarketFilters.Vocation]:isChecked()
local filterLevel = filterButtons[MarketFilters.Level]:isChecked()
local filterDepot = filterButtons[MarketFilters.Depot]:isChecked()
if slotFilter then
if slotFilter ~= 255 and item.ptr:getClothSlot() ~= slotFilter then
return false
end
end
local player = g_game.getLocalPlayer()
if filterLevel and marketData.requiredLevel and player:getLevel() < marketData.requiredLevel then
return false
end
if filterVocation and marketData.restrictVocation > 0 then
local voc = Bit.bit(information.vocation)
if not Bit.hasBit(marketData.restrictVocation, voc) then
return false
end
end
if filterDepot and Market.depotContains(item.ptr:getId()) <= 0 then
return false
end
if useSearchFilter then
local checkString = marketData.name:lower()
if not checkString:find(searchFilter) then
return false
end
end
return true
end
local function clearItems()
currentItems = {}
Market.refreshItemsWidget()
end
local function clearOffers()
marketOffers[MarketAction.Buy] = {}
marketOffers[MarketAction.Sell] = {}
buyOfferTable:clearData()
sellOfferTable:clearData()
end
local function clearFilters()
for _, filter in pairs(filterButtons) do
if filter and filter:isChecked() then
filter:setChecked(false)
end
end
end
local function refreshTypeList()
offerTypeList:clearOptions()
offerTypeList:addOption('Buy')
if Market.isItemSelected() then
if Market.depotContains(selectedItem.item.ptr:getId()) > 0 then
offerTypeList:addOption('Sell')
end
end
end
local function refreshFee()
feeLabel:setText('')
fee = 0
end
local function addOffer(offer, type)
if not offer then
return false
end
local id = offer:getId()
local player = offer:getPlayer()
local amount = offer:getAmount()
local price = offer:getPrice()
local timestamp = offer:getTimeStamp()
if amount < 1 then return false end
if type == MarketAction.Buy then
local data = {
{['text'] = player, ['width'] = 100},
{['text'] = amount, ['width'] = 60},
{['text'] = price*amount, ['width'] = 90},
{['text'] = price, ['width'] = 80},
{['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120}
}
buyOfferTable:addRow(data, id)
else
local data = {
{['text'] = player, ['width'] = 100},
{['text'] = amount, ['width'] = 60},
{['text'] = price*amount, ['width'] = 90},
{['text'] = price, ['width'] = 80},
{['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120}
}
sellOfferTable:addRow(data, id)
end
return true
end
local function mergeOffer(offer)
if not offer then
return false
end
local id = offer:getId()
local type = offer:getType()
local amount = offer:getAmount()
local replaced = false
if type == MarketAction.Buy then
for i = 1, #marketOffers[MarketAction.Buy] do
local o = marketOffers[MarketAction.Buy][i]
-- replace existing offer
if o:isEqual(id) then
marketOffers[MarketAction.Buy][i] = offer
replaced = true
end
end
if not replaced then
table.insert(marketOffers[MarketAction.Buy], offer)
end
else
for i = 1, #marketOffers[MarketAction.Sell] do
local o = marketOffers[MarketAction.Sell][i]
-- replace existing offer
if o:isEqual(id) then
marketOffers[MarketAction.Sell][i] = offer
replaced = true
end
end
if not replaced then
table.insert(marketOffers[MarketAction.Sell], offer)
end
end
return true
end
local function updateOffers(offers)
-- TODO: optimize offer updates
selectedOffer[MarketAction.Buy] = nil
selectedOffer[MarketAction.Sell] = nil
if not buyOfferTable or not sellOfferTable then
return
end
balanceLabel:setColor('#bbbbbb')
buyOfferTable:clearData()
sellOfferTable:clearData()
sellButton:setEnabled(false)
buyButton:setEnabled(false)
for k, offer in pairs(offers) do
mergeOffer(offer)
end
for type, offers in pairs(marketOffers) do
for i = 1, #offers do
addOffer(offers[i], type)
end
end
end
local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
if not selectedItem then
return
end
-- update item details
detailsTable:clearData()
for k, desc in pairs(descriptions) do
local columns = {
{['text'] = getMarketDescriptionName(desc[1])..':'},
{['text'] = desc[2], ['width'] = 330}
}
detailsTable:addRow(columns)
end
-- update sale item statistics
sellStatsTable:clearData()
if table.empty(saleStats) then
sellStatsTable:addRow({{['text'] = 'No information'}})
else
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
for _, stat in pairs(saleStats) do
if not stat:isNull() then
transactions = transactions + stat:getTransactions()
totalPrice = totalPrice + stat:getTotalPrice()
local newHigh = stat:getHighestPrice()
if newHigh > highestPrice then
highestPrice = newHigh
end
local newLow = stat:getLowestPrice()
-- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft
if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then
lowestPrice = newLow
end
end
end
sellStatsTable:addRow({{['text'] = 'Total Transations:'},
{['text'] = transactions, ['width'] = 270}})
sellStatsTable:addRow({{['text'] = 'Highest Price:'},
{['text'] = highestPrice, ['width'] = 270}})
if totalPrice > 0 and transactions > 0 then
sellStatsTable:addRow({{['text'] = 'Average Price:'},
{['text'] = math.floor(totalPrice/transactions), ['width'] = 270}})
else
sellStatsTable:addRow({{['text'] = 'Average Price:'},
{['text'] = 0, ['width'] = 270}})
end
sellStatsTable:addRow({{['text'] = 'Lowest Price:'},
{['text'] = lowestPrice, ['width'] = 270}})
end
-- update buy item statistics
buyStatsTable:clearData()
if table.empty(purchaseStats) then
buyStatsTable:addRow({{['text'] = 'No information'}})
else
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
for _, stat in pairs(purchaseStats) do
if not stat:isNull() then
transactions = transactions + stat:getTransactions()
totalPrice = totalPrice + stat:getTotalPrice()
local newHigh = stat:getHighestPrice()
if newHigh > highestPrice then
highestPrice = newHigh
end
local newLow = stat:getLowestPrice()
-- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft
if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then
lowestPrice = newLow
end
end
end
buyStatsTable:addRow({{['text'] = 'Total Transations:'},
{['text'] = transactions, ['width'] = 270}})
buyStatsTable:addRow({{['text'] = 'Highest Price:'},
{['text'] = highestPrice, ['width'] = 270}})
if totalPrice > 0 and transactions > 0 then
buyStatsTable:addRow({{['text'] = 'Average Price:'},
{['text'] = math.floor(totalPrice/transactions), ['width'] = 270}})
else
buyStatsTable:addRow({{['text'] = 'Average Price:'},
{['text'] = 0, ['width'] = 270}})
end
buyStatsTable:addRow({{['text'] = 'Lowest Price:'},
{['text'] = lowestPrice, ['width'] = 270}})
end
end
local function updateSelectedItem(widget)
selectedItem.item = widget.item
selectedItem.ref = widget
Market.resetCreateOffer()
if Market.isItemSelected() then
selectedItem:setItem(selectedItem.item.ptr)
nameLabel:setText(selectedItem.item.marketData.name)
clearOffers()
Market.enableCreateOffer(true)-- update offer types
MarketProtocol.sendMarketBrowse(selectedItem.item.ptr:getId()) -- send browsed msg
else
Market.clearSelectedItem()
end
end
local function updateBalance(balance)
local balance = tonumber(balance)
if not balance then
return
end
if balance < 0 then balance = 0 end
information.balance = balance
balanceLabel:setText('Balance: '..balance..' gold')
balanceLabel:resizeToText()
end
local function updateDepotItemCount(itemId, amount)
if Market.depotContains(itemId) < amount then
return false
end
for i = 1, #information.depotItems do
local depotItem = information.depotItems[i]
if depotItem and itemId == depotItem.ptr:getId() then
local depotItemCount = depotItem.ptr:getCount()
if depotItem.ptr:isStackable() then
if depotItemCount <= 100 and depotItemCount >= amount then
if (depotItemCount - amount) <= 0 then
table.remove(information.depotItems, i) -- warning: this re-indexes
else
depotItem.ptr:setCount(depotItemCount - amount)
information.depotItems[i] = depotItem
end
return true
else
local removeCount = math.floor(amount/100)
local remainder = amount % depotItemCount
if remainder > 0 then
removeCount = removeCount + 1
end
for j = 1, removeCount do
if j == removeCount and remainder > 0 then
updateDepotItemCount(itemId, remainder)
else
updateDepotItemCount(itemId, 100)
end
end
return true
end
else
if amount > 0 then
information.depotItems[i] = nil
amount = amount - 1
end
end
end
end
return true
end
local function updateFee(price, amount)
fee = math.ceil(price / 100 * amount)
if fee < 20 then
fee = 20
elseif fee > 1000 then
fee = 1000
end
feeLabel:setText('Fee: '..fee)
feeLabel:resizeToText()
end
local function openAmountWindow(callback, type, actionText)
local actionText = actionText or ''
if not Market.isOfferSelected(type) then
return
end
amountWindow = g_ui.createWidget('AmountWindow', rootWidget)
amountWindow:lock()
local max = selectedOffer[type]:getAmount()
if type == MarketAction.Sell then
local depot = Market.depotContains(selectedOffer[type]:getItem():getId())
if max > depot then
max = depot
end
end
local spinbox = amountWindow:getChildById('amountSpinBox')
spinbox:setMaximum(max)
spinbox:setMinimum(1)
spinbox:setValue(1)
local scrollbar = amountWindow:getChildById('amountScrollBar')
scrollbar:setMaximum(max)
scrollbar:setMinimum(1)
scrollbar:setValue(1)
scrollbar.onValueChange = function(self, value) spinbox:setValue(value) end
spinbox.onValueChange = function(self, value) scrollbar:setValue(value) end
local okButton = amountWindow:getChildById('buttonOk')
if actionText ~= '' then okButton:setText(actionText) end
local okFunc = function()
local counter = selectedOffer[type]:getCounter()
local timestamp = selectedOffer[type]:getTimeStamp()
callback(spinbox:getValue(), timestamp, counter)
okButton:getParent():destroy()
amountWindow = nil
end
local cancelButton = amountWindow:getChildById('buttonCancel')
local cancelFunc = function()
cancelButton:getParent():destroy()
amountWindow = nil
end
amountWindow.onEnter = okFunc
amountWindow.onEscape = cancelFunc
okButton.onClick = okFunc
cancelButton.onClick = cancelFunc
end
local function onSelectSellOffer(table, selectedRow, previousSelectedRow)
updateBalance()
for _, offer in pairs(marketOffers[MarketAction.Sell]) do
if offer:isEqual(selectedRow.ref) then
selectedOffer[MarketAction.Buy] = offer
buyButton:setEnabled(true)
end
end
local offer = selectedOffer[MarketAction.Buy]
if offer then
local price = offer:getPrice()
if price > information.balance then
balanceLabel:setColor('#b22222')
else
local slice = (information.balance / 2)
if (price/slice) * 100 <= 40 then
color = '#008b00' -- green
elseif (price/slice) * 100 <= 70 then
color = '#eec900' -- yellow
else
color = '#ee9a00' -- orange
end
balanceLabel:setColor(color)
end
end
end
local function onSelectBuyOffer(table, selectedRow, previousSelectedRow)
updateBalance()
for _, offer in pairs(marketOffers[MarketAction.Buy]) do
if offer:isEqual(selectedRow.ref) then
selectedOffer[MarketAction.Sell] = offer
if Market.depotContains(offer:getItem():getId()) > 0 then
sellButton:setEnabled(true)
else
sellButton:setEnabled(false)
end
end
end
end
local function onChangeCategory(combobox, option)
local id = getMarketCategoryId(option)
if id == MarketCategory.MetaWeapons then
-- enable and load weapons filter/items
subCategoryList:setEnabled(true)
slotFilterList:setEnabled(true)
local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text)
Market.loadMarketItems(subId)
else
subCategoryList:setEnabled(false)
slotFilterList:setEnabled(false)
Market.loadMarketItems(id) -- load standard filter
end
end
local function onChangeSubCategory(combobox, option)
Market.loadMarketItems(getMarketCategoryId(option))
slotFilterList:clearOptions()
local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text)
local slots = MarketCategoryWeapons[subId].slots
for _, slot in pairs(slots) do
if table.hasKey(MarketSlotFilters, slot) then
slotFilterList:addOption(MarketSlotFilters[slot])
end
end
slotFilterList:setEnabled(true)
end
local function onChangeSlotFilter(combobox, option)
Market.updateCurrentItems()
end
local function onChangeOfferType(combobox, option)
local id = selectedItem.item.ptr:getId()
if option == 'Sell' then
local max = Market.depotContains(id)
amountEdit:setMaximum(max)
else
amountEdit:setMaximum(999999)
end
end
local function onTotalPriceChange()
local totalPrice = totalPriceEdit:getValue()
local piecePrice = piecePriceEdit:getValue()
local amount = amountEdit:getValue()
piecePriceEdit:setValue(math.floor(totalPrice/amount))
if Market.isItemSelected() then
updateFee(totalPrice, amount)
end
end
local function onPiecePriceChange()
local totalPrice = totalPriceEdit:getValue()
local piecePrice = piecePriceEdit:getValue()
local amount = amountEdit:getValue()
totalPriceEdit:setValue(piecePrice*amount)
if Market.isItemSelected() then
updateFee(totalPrice, amount)
end
end
local function onAmountChange()
local totalPrice = totalPriceEdit:getValue()
local piecePrice = piecePriceEdit:getValue()
local amount = amountEdit:getValue()
piecePriceEdit:setValue(math.floor(totalPrice/amount))
totalPriceEdit:setValue(piecePrice*amount)
if Market.isItemSelected() then
updateFee(totalPrice, amount)
end
end
local function initMarketItems()
-- populate all market items
marketItems = {}
local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0)
for i = 1, #types do
local t = types[i]
local newItem = Item.create(t:getId())
if newItem then
local marketData = t:getMarketData()
if not table.empty(marketData) then
local item = {
ptr = newItem,
marketData = marketData
}
marketItems[#marketItems+1] = item
end
end
end
end
local function initInterface()
-- TODO: clean this up
-- setup main tabs
mainTabBar = marketWindow:getChildById('mainTabBar')
mainTabBar:setContentWidget(marketWindow:getChildById('mainTabContent'))
-- setup 'Market Offer' section tabs
marketOffersPanel = g_ui.loadUI('ui/marketoffers.otui')
mainTabBar:addTab(tr('Market Offers'), marketOffersPanel)
selectionTabBar = marketOffersPanel:getChildById('leftTabBar')
selectionTabBar:setContentWidget(marketOffersPanel:getChildById('leftTabContent'))
browsePanel = g_ui.loadUI('ui/marketoffers/browse.otui')
selectionTabBar:addTab(tr('Browse'), browsePanel)
overviewPanel = g_ui.loadUI('ui/marketoffers/overview.otui')
selectionTabBar:addTab(tr('Overview'), overviewPanel)
displaysTabBar = marketOffersPanel:getChildById('rightTabBar')
displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent'))
itemOffersPanel = g_ui.loadUI('ui/marketoffers/itemoffers.otui')
displaysTabBar:addTab(tr('Offers'), itemOffersPanel)
itemDetailsPanel = g_ui.loadUI('ui/marketoffers/itemdetails.otui')
displaysTabBar:addTab(tr('Details'), itemDetailsPanel)
itemStatsPanel = g_ui.loadUI('ui/marketoffers/itemstats.otui')
displaysTabBar:addTab(tr('Statistics'), itemStatsPanel)
-- setup 'My Offer' section tabs
myOffersPanel = g_ui.loadUI('ui/myoffers.otui')
mainTabBar:addTab(tr('My Offers'), myOffersPanel)
offersTabBar = myOffersPanel:getChildById('offersTabBar')
offersTabBar:setContentWidget(myOffersPanel:getChildById('offersTabContent'))
currentOffersPanel = g_ui.loadUI('ui/myoffers/currentoffers.otui')
offersTabBar:addTab(tr('Current Offers'), currentOffersPanel)
offerHistoryPanel = g_ui.loadUI('ui/myoffers/offerhistory.otui')
offersTabBar:addTab(tr('Offer History'), offerHistoryPanel)
balanceLabel = marketWindow:getChildById('balanceLabel')
-- setup offers
buyButton = itemOffersPanel:getChildById('buyButton')
buyButton.onClick = function() openAmountWindow(Market.buyMarketOffer, MarketAction.Buy, 'Buy') end
sellButton = itemOffersPanel:getChildById('sellButton')
sellButton.onClick = function() openAmountWindow(Market.sellMarketOffer, MarketAction.Sell, 'Sell') end
-- setup selected item
nameLabel = marketOffersPanel:getChildById('nameLabel')
selectedItem = marketOffersPanel:getChildById('selectedItem')
selectedItem.item = {}
-- setup create new offer
totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit')
piecePriceEdit = marketOffersPanel:getChildById('piecePriceEdit')
amountEdit = marketOffersPanel:getChildById('amountEdit')
feeLabel = marketOffersPanel:getChildById('feeLabel')
totalPriceEdit.onValueChange = onTotalPriceChange
piecePriceEdit.onValueChange = onPiecePriceChange
amountEdit.onValueChange = onAmountChange
offerTypeList = marketOffersPanel:getChildById('offerTypeComboBox')
offerTypeList.onOptionChange = onChangeOfferType
anonymous = marketOffersPanel:getChildById('anonymousCheckBox')
createOfferButton = marketOffersPanel:getChildById('createOfferButton')
createOfferButton.onClick = Market.createNewOffer
Market.enableCreateOffer(false)
-- setup filters
filterButtons[MarketFilters.Vocation] = browsePanel:getChildById('filterVocation')
filterButtons[MarketFilters.Level] = browsePanel:getChildById('filterLevel')
filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot')
filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll')
searchEdit = browsePanel:getChildById('searchEdit')
categoryList = browsePanel:getChildById('categoryComboBox')
subCategoryList = browsePanel:getChildById('subCategoryComboBox')
slotFilterList = browsePanel:getChildById('slotComboBox')
slotFilterList:addOption(MarketSlotFilters[255])
slotFilterList:setEnabled(false)
for i = MarketCategory.First, MarketCategory.Last do
if i >= MarketCategory.Ammunition and i <= MarketCategory.WandsRods then
subCategoryList:addOption(getMarketCategoryName(i))
else
categoryList:addOption(getMarketCategoryName(i))
end
end
categoryList:addOption(getMarketCategoryName(255)) -- meta weapons
categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First))
subCategoryList:setEnabled(false)
-- hook item filters
categoryList.onOptionChange = onChangeCategory
subCategoryList.onOptionChange = onChangeSubCategory
slotFilterList.onOptionChange = onChangeSlotFilter
-- setup tables
buyOfferTable = itemOffersPanel:recursiveGetChildById('buyingTable')
sellOfferTable = itemOffersPanel:recursiveGetChildById('sellingTable')
detailsTable = itemDetailsPanel:recursiveGetChildById('detailsTable')
buyStatsTable = itemStatsPanel:recursiveGetChildById('buyStatsTable')
sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable')
buyOfferTable.onSelectionChange = onSelectBuyOffer
sellOfferTable.onSelectionChange = onSelectSellOffer
end
function init()
g_ui.importStyle('market.otui')
g_ui.importStyle('ui/general/markettabs.otui')
g_ui.importStyle('ui/general/marketbuttons.otui')
g_ui.importStyle('ui/general/marketcombobox.otui')
g_ui.importStyle('ui/general/amountwindow.otui')
protocol.initProtocol()
connect(g_game, { onGameEnd = Market.reset })
marketWindow = g_ui.createWidget('MarketWindow', rootWidget)
marketWindow:hide()
initInterface() -- build interface
initMarketItems()
end
function terminate()
protocol.terminateProtocol()
disconnect(g_game, { onGameEnd = Market.reset })
if marketWindow then
marketWindow:destroy()
end
Market = nil
end
function Market.reset()
Market.close()
balanceLabel:setColor('#bbbbbb')
categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First))
clearFilters()
clearItems()
end
function Market.clearSelectedItem()
if Market.isItemSelected() then
Market.resetCreateOffer()
offerTypeList:clearOptions()
offerTypeList:setText('Please Select')
offerTypeList:setEnabled(false)
clearOffers()
radioItemSet:selectWidget(nil)
nameLabel:setText('No item selected.')
selectedItem:setItem(nil)
selectedItem.item = nil
selectedItem.ref:setChecked(false)
selectedItem.ref = nil
detailsTable:clearData()
buyStatsTable:clearData()
sellStatsTable:clearData()
Market.enableCreateOffer(false)
end
end
function Market.isItemSelected()
return selectedItem and not table.empty(selectedItem.item) and selectedItem.item.ptr
end
function Market.isOfferSelected(type)
return selectedOffer[type] and not selectedOffer[type]:isNull()
end
function Market.depotContains(itemId)
local count = 0
for i = 1, #information.depotItems do
local item = information.depotItems[i]
if item and item.ptr:getId() == itemId then
count = count + item.ptr:getCount()
end
end
return count
end
function Market.enableCreateOffer(enable)
offerTypeList:setEnabled(enable)
totalPriceEdit:setEnabled(enable)
piecePriceEdit:setEnabled(enable)
amountEdit:setEnabled(enable)
anonymous:setEnabled(enable)
createOfferButton:setEnabled(enable)
local prevAmountButton = marketOffersPanel:recursiveGetChildById('prevAmountButton')
local nextAmountButton = marketOffersPanel:recursiveGetChildById('nextAmountButton')
prevAmountButton:setEnabled(enable)
nextAmountButton:setEnabled(enable)
end
function Market.close(notify)
local notify = notify or true
marketWindow:hide()
marketWindow:unlock()
Market.clearSelectedItem()
if notify then
MarketProtocol.sendMarketLeave()
end
end
function Market.incrementAmount()
amountEdit:setValue(amountEdit:getValue() + 1)
end
function Market.decrementAmount()
amountEdit:setValue(amountEdit:getValue() - 1)
end
function Market.updateCurrentItems()
local id = getMarketCategoryId(categoryList:getCurrentOption().text)
if id == MarketCategory.MetaWeapons then
id = getMarketCategoryId(subCategoryList:getCurrentOption().text)
end
Market.loadMarketItems(id)
end
function Market.resetCreateOffer()
piecePriceEdit:setValue(1)
totalPriceEdit:setValue(1)
amountEdit:setValue(1)
refreshTypeList()
refreshFee()
end
function Market.refreshItemsWidget(selectItem)
local selectItem = selectItem or 0
itemsPanel = browsePanel:recursiveGetChildById('itemsPanel')
local layout = itemsPanel:getLayout()
layout:disableUpdates()
Market.clearSelectedItem()
itemsPanel:destroyChildren()
if radioItemSet then
radioItemSet:destroy()
end
radioItemSet = UIRadioGroup.create()
local select = nil
for i = 1, #currentItems do
local item = currentItems[i]
local itemBox = g_ui.createWidget('MarketItemBox', itemsPanel)
itemBox.onCheckChange = Market.onItemBoxChecked
itemBox.item = item
if selectItem > 0 and item.ptr:getId() == selectItem then
select = itemBox
end
local itemWidget = itemBox:getChildById('item')
item.ptr:setCount(1) -- reset item count for image
itemWidget:setItem(item.ptr)
local amount = Market.depotContains(item.ptr:getId())
if amount > 0 then
itemWidget:setText(amount)
itemBox:setTooltip('You have '.. amount ..' in your depot.')
end
radioItemSet:addWidget(itemBox)
end
if select then
selectedItem.item = select.item
selectedItem.ref = select
select:setChecked(true)
end
layout:enableUpdates()
layout:update()
end
--[[
TODO: Optimize loading market items
* Preload items to their categories
]]
function Market.loadMarketItems(category)
if table.empty(marketItems) then
initMarketItems()
end
clearItems()
for i = 1, #marketItems do
local item = marketItems[i]
if isItemValid(item, category) then
table.insert(currentItems, item)
end
end
Market.refreshItemsWidget()
end
function Market.loadDepotItems(depotItems)
information.depotItems = {}
local items = {}
for i = 1, #depotItems do
local data = depotItems[i]
local id, count = data[1], data[2]
local tmpItem = Item.create(id)
if tmpItem:isStackable() then
if count > 100 then
local createCount = math.floor(count/100)
local remainder = count % 100
if remainder > 0 then
createCount = createCount + 1
end
for i = 1, createCount do
local newItem = Item.create(id)
if i == createCount and remainder > 0 then
newItem:setCount(remainder)
else
newItem:setCount(100)
end
table.insert(items, newItem)
end
else
local newItem = Item.create(id)
newItem:setCount(count)
table.insert(items, newItem)
end
else
for i = 1, count do
table.insert(items, Item.create(id))
end
end
end
for _, newItem in pairs(items) do
local marketData = newItem:getMarketData()
if not table.empty(marketData) then
local item = {
ptr = newItem,
marketData = marketData
}
table.insert(information.depotItems, item)
end
end
end
function Market.createNewOffer()
local type = offerTypeList:getCurrentOption().text
if type == 'Sell' then
type = MarketAction.Sell
else
type = MarketAction.Buy
end
if not Market.isItemSelected() then
return
end
local item = selectedItem.item
local spriteId = item.ptr:getId()
local piecePrice = piecePriceEdit:getValue()
local totalPrice = totalPriceEdit:getValue()
local amount = amountEdit:getValue()
local anonymous = anonymous:isChecked() and 1 or 0
local errorMsg = ''
if piecePrice > piecePriceEdit.maximum then
errorMsg = errorMsg..'Price is too high.\n'
elseif piecePrice < piecePriceEdit.minimum then
errorMsg = errorMsg..'Price is too low.\n'
end
if amount > amountEdit.maximum then
errorMsg = errorMsg..'Amount is too high.\n'
elseif amount < amountEdit.minimum then
errorMsg = errorMsg..'Amount is too low.\n'
end
if errorMsg ~= '' then
UIMessageBox.display('Error', errorMsg, MessageBoxOk)
return
end
MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, piecePrice, anonymous)
if type == MarketAction.Sell then
--[[
This is required due to bot protected protocol (cannot update browse item without user input)
normal way is to use the new retrieved depot items in onMarketEnter and refresh the items widget.
]]
updateDepotItemCount(spriteId, amount)
Market.refreshItemsWidget(spriteId)
end
Market.resetCreateOffer()
end
function Market.buyMarketOffer(amount, timestamp, counter)
if timestamp > 0 and amount > 0 then
MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount)
end
end
function Market.sellMarketOffer(amount, timestamp, counter)
if timestamp > 0 and amount > 0 then
MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount)
local spriteId = selectedItem.item.ptr:getId()
updateDepotItemCount(spriteId, amount)
Market.refreshItemsWidget(spriteId)
end
end
function Market.onItemBoxChecked(widget)
if widget:isChecked() then
updateSelectedItem(widget)
end
end
-- protocol callback functions
function Market.onMarketEnter(depotItems, offers, balance, vocation)
updateBalance(balance)
information.totalOffers = offers
local player = g_game.getLocalPlayer()
if player then
information.player = player
end
if vocation < 0 then
if player then
information.vocation = player:getVocation()
end
else
-- vocation must be compatible with < 950
information.vocation = vocation
end
Market.loadDepotItems(depotItems)
if table.empty(currentItems) then
Market.loadMarketItems(MarketCategory.First)
end
-- build offer table header
if buyOfferTable and not buyOfferTable:hasHeader() then
buyOfferTable:addHeaderRow(offerTableHeader)
end
if sellOfferTable and not sellOfferTable:hasHeader() then
sellOfferTable:addHeaderRow(offerTableHeader)
end
if g_game.isOnline() then
marketWindow:lock()
marketWindow:show()
end
end
function Market.onMarketLeave()
Market.close(false)
end
function Market.onMarketDetail(itemId, descriptions, purchaseStats, saleStats)
updateDetails(itemId, descriptions, purchaseStats, saleStats)
end
function Market.onMarketBrowse(offers)
updateOffers(offers)
end