1267 lines
35 KiB
Lua
1267 lines
35 KiB
Lua
--[[
|
|
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 offer management:
|
|
- Current Offers
|
|
- Offer History
|
|
|
|
* Clean up the interface building
|
|
- Add a new market interface file to handle building?
|
|
|
|
* Extend information features
|
|
- Hover over offers for purchase information (balance after transaction, etc)
|
|
]]
|
|
|
|
Market = {}
|
|
|
|
local protocol = runinsandbox('marketprotocol')
|
|
|
|
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 = {}
|
|
selectedMyOffer = {}
|
|
|
|
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
|
|
|
|
buyCancelButton = nil
|
|
sellCancelButton = nil
|
|
buyMyOfferTable = nil
|
|
sellMyOfferTable = nil
|
|
|
|
offerExhaust = {}
|
|
marketOffers = {}
|
|
marketItems = {}
|
|
information = {}
|
|
currentItems = {}
|
|
lastCreatedOffer = 0
|
|
fee = 0
|
|
averagePrice = 0
|
|
|
|
loaded = false
|
|
|
|
local function isItemValid(item, category, searchFilter)
|
|
if not item or not item.marketData then
|
|
return false
|
|
end
|
|
|
|
if not category 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.thingType: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.getDepotCount(item.marketData.tradeAs) <= 0 then
|
|
return false
|
|
end
|
|
if searchFilter then
|
|
return marketData.name:lower():find(searchFilter)
|
|
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 clearMyOffers()
|
|
marketOffers[MarketAction.Buy] = {}
|
|
marketOffers[MarketAction.Sell] = {}
|
|
buyMyOfferTable:clearData()
|
|
sellMyOfferTable:clearData()
|
|
end
|
|
|
|
local function clearFilters()
|
|
for _, filter in pairs(filterButtons) do
|
|
if filter and filter:isChecked() ~= filter.default then
|
|
filter:setChecked(filter.default)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function clearFee()
|
|
feeLabel:setText('')
|
|
fee = 20
|
|
end
|
|
|
|
local function refreshTypeList()
|
|
offerTypeList:clearOptions()
|
|
offerTypeList:addOption('Buy')
|
|
|
|
if Market.isItemSelected() then
|
|
if Market.getDepotCount(selectedItem.item.marketData.tradeAs) > 0 then
|
|
offerTypeList:addOption('Sell')
|
|
end
|
|
end
|
|
end
|
|
|
|
local function addOffer(offer, offerType)
|
|
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()
|
|
local itemName = offer:getItem():getMarketData().name
|
|
|
|
buyOfferTable:toggleSorting(false)
|
|
sellOfferTable:toggleSorting(false)
|
|
|
|
buyMyOfferTable:toggleSorting(false)
|
|
sellMyOfferTable:toggleSorting(false)
|
|
|
|
if amount < 1 then return false end
|
|
if offerType == MarketAction.Buy then
|
|
if offer.warn then
|
|
buyOfferTable:setColumnStyle('OfferTableWarningColumn', true)
|
|
end
|
|
|
|
local row = nil
|
|
if offer.var == MarketRequest.MyOffers then
|
|
row = buyMyOfferTable:addRow({
|
|
{text = itemName},
|
|
{text = price*amount},
|
|
{text = price},
|
|
{text = amount},
|
|
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
|
|
})
|
|
else
|
|
row = buyOfferTable:addRow({
|
|
{text = player},
|
|
{text = amount},
|
|
{text = price*amount},
|
|
{text = price},
|
|
{text = string.gsub(os.date('%c', timestamp), " ", " ")}
|
|
})
|
|
end
|
|
row.ref = id
|
|
|
|
if offer.warn then
|
|
row:setTooltip(tr('This offer is 25%% below the average market price'))
|
|
buyOfferTable:setColumnStyle('OfferTableColumn', true)
|
|
end
|
|
else
|
|
if offer.warn then
|
|
sellOfferTable:setColumnStyle('OfferTableWarningColumn', true)
|
|
end
|
|
|
|
local row = nil
|
|
if offer.var == MarketRequest.MyOffers then
|
|
row = sellMyOfferTable:addRow({
|
|
{text = itemName},
|
|
{text = price*amount},
|
|
{text = price},
|
|
{text = amount},
|
|
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
|
|
})
|
|
else
|
|
row = sellOfferTable:addRow({
|
|
{text = player},
|
|
{text = amount},
|
|
{text = price*amount},
|
|
{text = price},
|
|
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
|
|
})
|
|
end
|
|
row.ref = id
|
|
|
|
if offer.warn then
|
|
row:setTooltip(tr('This offer is 25%% above the average market price'))
|
|
sellOfferTable:setColumnStyle('OfferTableColumn', true)
|
|
end
|
|
end
|
|
|
|
buyOfferTable:toggleSorting(false)
|
|
sellOfferTable:toggleSorting(false)
|
|
buyOfferTable:sort()
|
|
sellOfferTable:sort()
|
|
|
|
buyMyOfferTable:toggleSorting(false)
|
|
sellMyOfferTable:toggleSorting(false)
|
|
buyMyOfferTable:sort()
|
|
sellMyOfferTable:sort()
|
|
|
|
return true
|
|
end
|
|
|
|
local function mergeOffer(offer)
|
|
if not offer then
|
|
return false
|
|
end
|
|
|
|
local id = offer:getId()
|
|
local offerType = offer:getType()
|
|
local amount = offer:getAmount()
|
|
local replaced = false
|
|
|
|
if offerType == MarketAction.Buy then
|
|
if averagePrice > 0 then
|
|
offer.warn = offer:getPrice() <= averagePrice - math.floor(averagePrice / 4)
|
|
end
|
|
|
|
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
|
|
if averagePrice > 0 then
|
|
offer.warn = offer:getPrice() >= averagePrice + math.floor(averagePrice / 4)
|
|
end
|
|
|
|
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)
|
|
if not buyOfferTable or not sellOfferTable then
|
|
return
|
|
end
|
|
|
|
balanceLabel:setColor('#bbbbbb')
|
|
selectedOffer[MarketAction.Buy] = nil
|
|
selectedOffer[MarketAction.Sell] = nil
|
|
|
|
selectedMyOffer[MarketAction.Buy] = nil
|
|
selectedMyOffer[MarketAction.Sell] = nil
|
|
|
|
-- clear existing offer data
|
|
buyOfferTable:clearData()
|
|
buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
|
|
sellOfferTable:clearData()
|
|
sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
|
|
|
|
sellButton:setEnabled(false)
|
|
buyButton:setEnabled(false)
|
|
|
|
buyCancelButton:setEnabled(false)
|
|
sellCancelButton:setEnabled(false)
|
|
|
|
for _, 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]}
|
|
}
|
|
detailsTable:addRow(columns)
|
|
end
|
|
|
|
-- update sale item statistics
|
|
sellStatsTable:clearData()
|
|
if table.empty(saleStats) then
|
|
sellStatsTable:addRow({{text = 'No information'}})
|
|
else
|
|
local offerAmount = 0
|
|
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
|
|
for _, stat in pairs(saleStats) do
|
|
if not stat:isNull() then
|
|
offerAmount = offerAmount + 1
|
|
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
|
|
|
|
if offerAmount >= 5 and transactions >= 10 then
|
|
averagePrice = math.round(totalPrice / transactions)
|
|
else
|
|
averagePrice = 0
|
|
end
|
|
|
|
sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}})
|
|
sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
|
|
|
|
if totalPrice > 0 and transactions > 0 then
|
|
sellStatsTable:addRow({{text = 'Average Price:'},
|
|
{text = math.floor(totalPrice/transactions)}})
|
|
else
|
|
sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
|
|
end
|
|
|
|
sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
|
|
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}})
|
|
buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
|
|
|
|
if totalPrice > 0 and transactions > 0 then
|
|
buyStatsTable:addRow({{text = 'Average Price:'},
|
|
{text = math.floor(totalPrice/transactions)}})
|
|
else
|
|
buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
|
|
end
|
|
|
|
buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
|
|
end
|
|
end
|
|
|
|
local function updateSelectedItem(widget)
|
|
selectedItem.item = widget.item
|
|
selectedItem.ref = widget
|
|
|
|
Market.resetCreateOffer()
|
|
if Market.isItemSelected() then
|
|
selectedItem:setItem(selectedItem.item.displayItem)
|
|
nameLabel:setText(selectedItem.item.marketData.name)
|
|
clearOffers()
|
|
|
|
Market.enableCreateOffer(true) -- update offer types
|
|
MarketProtocol.sendMarketBrowse(selectedItem.item.marketData.tradeAs) -- 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 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 destroyAmountWindow()
|
|
if amountWindow then
|
|
amountWindow:destroy()
|
|
amountWindow = nil
|
|
end
|
|
end
|
|
|
|
local function cancelMyOffer(actionType)
|
|
local offer = selectedMyOffer[actionType]
|
|
MarketProtocol.sendMarketCancelOffer(offer:getTimeStamp(), offer:getCounter())
|
|
Market.refreshMyOffers()
|
|
end
|
|
|
|
local function openAmountWindow(callback, actionType, actionText)
|
|
if not Market.isOfferSelected(actionType) then
|
|
return
|
|
end
|
|
|
|
amountWindow = g_ui.createWidget('AmountWindow', rootWidget)
|
|
amountWindow:lock()
|
|
|
|
local offer = selectedOffer[actionType]
|
|
local item = offer:getItem()
|
|
|
|
local maximum = offer:getAmount()
|
|
if actionType == MarketAction.Sell then
|
|
local depot = Market.getDepotCount(item:getId())
|
|
if maximum > depot then
|
|
maximum = depot
|
|
end
|
|
else
|
|
maximum = math.min(maximum, math.floor(information.balance / offer:getPrice()))
|
|
end
|
|
|
|
if item:isStackable() then
|
|
maximum = math.min(maximum, MarketMaxAmountStackable)
|
|
else
|
|
maximum = math.min(maximum, MarketMaxAmount)
|
|
end
|
|
|
|
local itembox = amountWindow:getChildById('item')
|
|
itembox:setItemId(item:getId())
|
|
|
|
local scrollbar = amountWindow:getChildById('amountScrollBar')
|
|
scrollbar:setText(offer:getPrice()..'gp')
|
|
|
|
scrollbar.onValueChange = function(widget, value)
|
|
widget:setText((value*offer:getPrice())..'gp')
|
|
itembox:setText(value)
|
|
end
|
|
|
|
scrollbar:setRange(1, maximum)
|
|
scrollbar:setValue(1)
|
|
|
|
local okButton = amountWindow:getChildById('buttonOk')
|
|
if actionText then
|
|
okButton:setText(actionText)
|
|
end
|
|
|
|
local okFunc = function()
|
|
local counter = offer:getCounter()
|
|
local timestamp = offer:getTimeStamp()
|
|
callback(scrollbar:getValue(), timestamp, counter)
|
|
destroyAmountWindow()
|
|
end
|
|
|
|
local cancelButton = amountWindow:getChildById('buttonCancel')
|
|
local cancelFunc = function()
|
|
destroyAmountWindow()
|
|
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
|
|
end
|
|
end
|
|
|
|
local offer = selectedOffer[MarketAction.Buy]
|
|
if offer then
|
|
local price = offer:getPrice()
|
|
if price > information.balance then
|
|
balanceLabel:setColor('#b22222') -- red
|
|
buyButton:setEnabled(false)
|
|
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)
|
|
buyButton:setEnabled(true)
|
|
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.getDepotCount(offer:getItem():getId()) > 0 then
|
|
sellButton:setEnabled(true)
|
|
else
|
|
sellButton:setEnabled(false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onSelectMyBuyOffer(table, selectedRow, previousSelectedRow)
|
|
for _, offer in pairs(marketOffers[MarketAction.Buy]) do
|
|
if offer:isEqual(selectedRow.ref) then
|
|
selectedMyOffer[MarketAction.Buy] = offer
|
|
buyCancelButton:setEnabled(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onSelectMySellOffer(table, selectedRow, previousSelectedRow)
|
|
for _, offer in pairs(marketOffers[MarketAction.Sell]) do
|
|
if offer:isEqual(selectedRow.ref) then
|
|
selectedMyOffer[MarketAction.Sell] = offer
|
|
sellCancelButton:setEnabled(true)
|
|
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 item = selectedItem.item
|
|
local maximum = item.thingType:isStackable() and MarketMaxAmountStackable or MarketMaxAmount
|
|
|
|
if option == 'Sell' then
|
|
maximum = math.min(maximum, Market.getDepotCount(item.marketData.tradeAs))
|
|
amountEdit:setMaximum(maximum)
|
|
else
|
|
amountEdit:setMaximum(maximum)
|
|
end
|
|
end
|
|
|
|
local function onTotalPriceChange()
|
|
local amount = amountEdit:getValue()
|
|
local totalPrice = totalPriceEdit:getValue()
|
|
local piecePrice = math.floor(totalPrice/amount)
|
|
|
|
piecePriceEdit:setValue(piecePrice, true)
|
|
if Market.isItemSelected() then
|
|
updateFee(piecePrice, amount)
|
|
end
|
|
end
|
|
|
|
local function onPiecePriceChange()
|
|
local amount = amountEdit:getValue()
|
|
local totalPrice = totalPriceEdit:getValue()
|
|
local piecePrice = piecePriceEdit:getValue()
|
|
|
|
totalPriceEdit:setValue(piecePrice*amount, true)
|
|
if Market.isItemSelected() then
|
|
updateFee(piecePrice, amount)
|
|
end
|
|
end
|
|
|
|
local function onAmountChange()
|
|
local amount = amountEdit:getValue()
|
|
local piecePrice = piecePriceEdit:getValue()
|
|
local totalPrice = piecePrice * amount
|
|
|
|
totalPriceEdit:setValue(piecePrice*amount, true)
|
|
if Market.isItemSelected() then
|
|
updateFee(piecePrice, amount)
|
|
end
|
|
end
|
|
|
|
local function onMarketMessage(messageMode, message)
|
|
Market.displayMessage(message)
|
|
end
|
|
|
|
local function initMarketItems()
|
|
for c = MarketCategory.First, MarketCategory.Last do
|
|
marketItems[c] = {}
|
|
end
|
|
|
|
-- save a list of items which are already added
|
|
local itemSet = {}
|
|
|
|
-- populate all market items
|
|
local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0)
|
|
for i = 1, #types do
|
|
local itemType = types[i]
|
|
|
|
local item = Item.create(itemType:getId())
|
|
if item then
|
|
local marketData = itemType:getMarketData()
|
|
if not table.empty(marketData) and not itemSet[marketData.tradeAs] then
|
|
-- Some items use a different sprite in Market
|
|
item:setId(marketData.showAs)
|
|
|
|
-- create new marketItem block
|
|
local marketItem = {
|
|
displayItem = item,
|
|
thingType = itemType,
|
|
marketData = marketData
|
|
}
|
|
|
|
-- add new market item
|
|
table.insert(marketItems[marketData.category], marketItem)
|
|
itemSet[marketData.tradeAs] = true
|
|
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')
|
|
mainTabBar:addTab(tr('Market Offers'), marketOffersPanel)
|
|
|
|
selectionTabBar = marketOffersPanel:getChildById('leftTabBar')
|
|
selectionTabBar:setContentWidget(marketOffersPanel:getChildById('leftTabContent'))
|
|
|
|
browsePanel = g_ui.loadUI('ui/marketoffers/browse')
|
|
selectionTabBar:addTab(tr('Browse'), browsePanel)
|
|
|
|
-- Currently not used
|
|
-- "Reserved for more functionality later"
|
|
--overviewPanel = g_ui.loadUI('ui/marketoffers/overview')
|
|
--selectionTabBar:addTab(tr('Overview'), overviewPanel)
|
|
|
|
displaysTabBar = marketOffersPanel:getChildById('rightTabBar')
|
|
displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent'))
|
|
|
|
itemStatsPanel = g_ui.loadUI('ui/marketoffers/itemstats')
|
|
displaysTabBar:addTab(tr('Statistics'), itemStatsPanel)
|
|
|
|
itemDetailsPanel = g_ui.loadUI('ui/marketoffers/itemdetails')
|
|
displaysTabBar:addTab(tr('Details'), itemDetailsPanel)
|
|
|
|
itemOffersPanel = g_ui.loadUI('ui/marketoffers/itemoffers')
|
|
displaysTabBar:addTab(tr('Offers'), itemOffersPanel)
|
|
displaysTabBar:selectTab(displaysTabBar:getTab(tr('Offers')))
|
|
|
|
-- setup 'My Offer' section tabs
|
|
myOffersPanel = g_ui.loadUI('ui/myoffers')
|
|
mainTabBar:addTab(tr('My Offers'), myOffersPanel)
|
|
|
|
offersTabBar = myOffersPanel:getChildById('offersTabBar')
|
|
offersTabBar:setContentWidget(myOffersPanel:getChildById('offersTabContent'))
|
|
|
|
currentOffersPanel = g_ui.loadUI('ui/myoffers/currentoffers')
|
|
offersTabBar:addTab(tr('Current Offers'), currentOffersPanel)
|
|
|
|
offerHistoryPanel = g_ui.loadUI('ui/myoffers/offerhistory')
|
|
offersTabBar:addTab(tr('Offer History'), offerHistoryPanel)
|
|
|
|
balanceLabel = marketWindow:getChildById('balanceLabel')
|
|
|
|
-- setup offers
|
|
buyButton = itemOffersPanel:getChildById('buyButton')
|
|
buyButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Buy, 'Buy') end
|
|
|
|
sellButton = itemOffersPanel:getChildById('sellButton')
|
|
sellButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Sell, 'Sell') end
|
|
|
|
-- setup selected item
|
|
nameLabel = marketOffersPanel:getChildById('nameLabel')
|
|
selectedItem = marketOffersPanel:getChildById('selectedItem')
|
|
|
|
-- 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')
|
|
|
|
-- set filter default values
|
|
clearFilters()
|
|
|
|
-- hook filters
|
|
for _, filter in pairs(filterButtons) do
|
|
filter.onCheckChange = Market.updateCurrentItems
|
|
end
|
|
|
|
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
|
|
|
|
-- setup my offers
|
|
buyMyOfferTable = currentOffersPanel:recursiveGetChildById('myBuyingTable')
|
|
sellMyOfferTable = currentOffersPanel:recursiveGetChildById('mySellingTable')
|
|
buyMyOfferTable.onSelectionChange = onSelectMyBuyOffer
|
|
sellMyOfferTable.onSelectionChange = onSelectMySellOffer
|
|
|
|
buyCancelButton = currentOffersPanel:getChildById('buyCancelButton')
|
|
buyCancelButton.onClick = function() cancelMyOffer(MarketAction.Buy) end
|
|
|
|
sellCancelButton = currentOffersPanel:getChildById('sellCancelButton')
|
|
sellCancelButton.onClick = function() cancelMyOffer(MarketAction.Sell) end
|
|
|
|
|
|
buyStatsTable:setColumnWidth({120, 270})
|
|
sellStatsTable:setColumnWidth({120, 270})
|
|
detailsTable:setColumnWidth({80, 330})
|
|
|
|
buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
|
|
sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
|
|
|
|
buyMyOfferTable:setSorting(3, TABLE_SORTING_DESC)
|
|
sellMyOfferTable:setSorting(3, TABLE_SORTING_DESC)
|
|
end
|
|
|
|
function init()
|
|
g_ui.importStyle('market')
|
|
g_ui.importStyle('ui/general/markettabs')
|
|
g_ui.importStyle('ui/general/marketbuttons')
|
|
g_ui.importStyle('ui/general/marketcombobox')
|
|
g_ui.importStyle('ui/general/amountwindow')
|
|
|
|
offerExhaust[MarketAction.Sell] = 10
|
|
offerExhaust[MarketAction.Buy] = 20
|
|
|
|
registerMessageMode(MessageModes.Market, onMarketMessage)
|
|
|
|
protocol.initProtocol()
|
|
connect(g_game, { onGameEnd = Market.reset })
|
|
connect(g_game, { onGameEnd = Market.close })
|
|
marketWindow = g_ui.createWidget('MarketWindow', rootWidget)
|
|
marketWindow:hide()
|
|
|
|
initInterface() -- build interface
|
|
end
|
|
|
|
function terminate()
|
|
Market.close()
|
|
|
|
unregisterMessageMode(MessageModes.Market, onMarketMessage)
|
|
|
|
protocol.terminateProtocol()
|
|
disconnect(g_game, { onGameEnd = Market.reset })
|
|
disconnect(g_game, { onGameEnd = Market.close })
|
|
|
|
destroyAmountWindow()
|
|
marketWindow:destroy()
|
|
|
|
Market = nil
|
|
end
|
|
|
|
function Market.reset()
|
|
balanceLabel:setColor('#bbbbbb')
|
|
categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First))
|
|
searchEdit:setText('')
|
|
clearFilters()
|
|
clearMyOffers()
|
|
if not table.empty(information) then
|
|
Market.updateCurrentItems()
|
|
end
|
|
end
|
|
|
|
function Market.displayMessage(message)
|
|
if marketWindow:isHidden() then return end
|
|
|
|
local infoBox = displayInfoBox(tr('Market Error'), message)
|
|
infoBox:lock()
|
|
end
|
|
|
|
function Market.clearSelectedItem()
|
|
if Market.isItemSelected() then
|
|
Market.resetCreateOffer(true)
|
|
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 selectedItem.item
|
|
end
|
|
|
|
function Market.isOfferSelected(type)
|
|
return selectedOffer[type] and not selectedOffer[type]:isNull()
|
|
end
|
|
|
|
function Market.getDepotCount(itemId)
|
|
return information.depotItems[itemId] or 0
|
|
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)
|
|
if notify == nil then notify = true end
|
|
if not marketWindow:isHidden() then
|
|
marketWindow:hide()
|
|
marketWindow:unlock()
|
|
Market.clearSelectedItem()
|
|
Market.reset()
|
|
if notify then
|
|
MarketProtocol.sendMarketLeave()
|
|
end
|
|
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(resetFee)
|
|
piecePriceEdit:setValue(1)
|
|
totalPriceEdit:setValue(1)
|
|
amountEdit:setValue(1)
|
|
refreshTypeList()
|
|
|
|
if resetFee then
|
|
clearFee()
|
|
else
|
|
updateFee(0, 0)
|
|
end
|
|
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.marketData.tradeAs == selectItem then
|
|
select = itemBox
|
|
selectItem = 0
|
|
end
|
|
|
|
local itemWidget = itemBox:getChildById('item')
|
|
itemWidget:setItem(item.displayItem)
|
|
|
|
local amount = Market.getDepotCount(item.marketData.tradeAs)
|
|
if amount > 0 then
|
|
itemWidget:setText(amount)
|
|
itemBox:setTooltip('You have '.. amount ..' in your depot.')
|
|
end
|
|
|
|
radioItemSet:addWidget(itemBox)
|
|
end
|
|
|
|
if select then
|
|
radioItemSet:selectWidget(select, false)
|
|
end
|
|
|
|
layout:enableUpdates()
|
|
layout:update()
|
|
end
|
|
|
|
function Market.refreshOffers()
|
|
if Market.isItemSelected() then
|
|
Market.onItemBoxChecked(selectedItem.ref)
|
|
else
|
|
Market.refreshMyOffers()
|
|
end
|
|
end
|
|
|
|
function Market.refreshMyOffers()
|
|
clearMyOffers()
|
|
MarketProtocol.sendMarketBrowseMyOffers()
|
|
end
|
|
|
|
|
|
function Market.loadMarketItems(category)
|
|
clearItems()
|
|
|
|
-- check search filter
|
|
local searchFilter = searchEdit:getText()
|
|
if searchFilter and searchFilter:len() > 2 then
|
|
if filterButtons[MarketFilters.SearchAll]:isChecked() then
|
|
category = MarketCategory.All
|
|
end
|
|
end
|
|
|
|
if category == MarketCategory.All then
|
|
-- loop all categories
|
|
for category = MarketCategory.First, MarketCategory.Last do
|
|
for i = 1, #marketItems[category] do
|
|
local item = marketItems[category][i]
|
|
if isItemValid(item, category, searchFilter) then
|
|
|
|
table.insert(currentItems, item)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
-- loop specific category
|
|
for i = 1, #marketItems[category] do
|
|
local item = marketItems[category][i]
|
|
if isItemValid(item, category, searchFilter) then
|
|
table.insert(currentItems, item)
|
|
end
|
|
end
|
|
end
|
|
|
|
Market.refreshItemsWidget()
|
|
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 spriteId = selectedItem.item.marketData.tradeAs
|
|
|
|
local piecePrice = piecePriceEdit:getValue()
|
|
local amount = amountEdit:getValue()
|
|
local anonymous = anonymous:isChecked() and 1 or 0
|
|
|
|
-- error checking
|
|
local errorMsg = ''
|
|
if type == MarketAction.Buy then
|
|
if information.balance < ((piecePrice * amount) + fee) then
|
|
errorMsg = errorMsg..'Not enough balance to create this offer.\n'
|
|
end
|
|
elseif type == MarketAction.Sell then
|
|
if information.balance < fee then
|
|
errorMsg = errorMsg..'Not enough balance to create this offer.\n'
|
|
end
|
|
if Market.getDepotCount(spriteId) < amount then
|
|
errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n'
|
|
end
|
|
end
|
|
|
|
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 amount * piecePrice > MarketMaxPrice then
|
|
errorMsg = errorMsg..'Total price is too high.\n'
|
|
end
|
|
|
|
if information.totalOffers >= MarketMaxOffers then
|
|
errorMsg = errorMsg..'You cannot create more offers.\n'
|
|
end
|
|
|
|
local timeCheck = os.time() - lastCreatedOffer
|
|
if timeCheck < offerExhaust[type] then
|
|
local waitTime = math.ceil(offerExhaust[type] - timeCheck)
|
|
errorMsg = errorMsg..'You must wait '.. waitTime ..' seconds before creating a new offer.\n'
|
|
end
|
|
|
|
if errorMsg ~= '' then
|
|
Market.displayMessage(errorMsg)
|
|
return
|
|
end
|
|
|
|
MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, piecePrice, anonymous)
|
|
lastCreatedOffer = os.time()
|
|
Market.resetCreateOffer()
|
|
end
|
|
|
|
function Market.acceptMarketOffer(amount, timestamp, counter)
|
|
if timestamp > 0 and amount > 0 then
|
|
MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount)
|
|
Market.refreshOffers()
|
|
end
|
|
end
|
|
|
|
function Market.onItemBoxChecked(widget)
|
|
if widget:isChecked() then
|
|
updateSelectedItem(widget)
|
|
end
|
|
end
|
|
|
|
-- protocol callback functions
|
|
|
|
function Market.onMarketEnter(depotItems, offers, balance, vocation)
|
|
if not loaded then
|
|
initMarketItems()
|
|
loaded = true
|
|
end
|
|
|
|
updateBalance(balance)
|
|
averagePrice = 0
|
|
|
|
information.totalOffers = offers
|
|
local player = g_game.getLocalPlayer()
|
|
if player then
|
|
information.player = player
|
|
end
|
|
if vocation == -1 then
|
|
if player then
|
|
information.vocation = player:getVocation()
|
|
end
|
|
else
|
|
-- vocation must be compatible with < 950
|
|
information.vocation = vocation
|
|
end
|
|
|
|
-- set list of depot items
|
|
information.depotItems = depotItems
|
|
|
|
-- update the items widget to match depot items
|
|
if Market.isItemSelected() then
|
|
local spriteId = selectedItem.item.marketData.tradeAs
|
|
MarketProtocol.silent(true) -- disable protocol messages
|
|
Market.refreshItemsWidget(spriteId)
|
|
MarketProtocol.silent(false) -- enable protocol messages
|
|
else
|
|
Market.refreshItemsWidget()
|
|
end
|
|
|
|
if table.empty(currentItems) then
|
|
Market.loadMarketItems(MarketCategory.First)
|
|
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
|