diff --git a/modules/game_market/market.lua b/modules/game_market/market.lua index 5c34f730..7253a274 100644 --- a/modules/game_market/market.lua +++ b/modules/game_market/market.lua @@ -1,96 +1,61 @@ Market = {} -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') - -local marketWindow -local mainTabBar - -local marketOffersPanel -local selectionTabBar -local displaysTabBar -local offersTabBar - -local itemsPanel -local browsePanel -local searchPanel -local itemOffersPanel -local itemDetailsPanel -local itemStatsPanel -local myOffersPanel -local currentOffersPanel -local offerHistoryPanel - -local selectedItem -local nameLabel -local radioItemSet -local categoryList -local subCategoryList -local slotFilterList -local filterButtons = {} - -local buyOfferTable -local sellOfferTable -local detailsTable -local buyStatsTable -local sellStatsTable - -local marketOffers = {} -local marketItems = {} -local depot = {} -local information = {} -local currentItems = {} +local protocol = runinsandbox('marketprotocol.lua') + +marketWindow = nil +mainTabBar = nil +displaysTabBar = nil +offersTabBar = nil +selectionTabBar = nil + +marketOffersPanel = nil +browsePanel = nil +searchPanel = nil +itemOffersPanel = nil +itemDetailsPanel = nil +itemStatsPanel = nil +myOffersPanel = nil +currentOffersPanel = nil +offerHistoryPanel = nil +itemsPanel = nil +selectedOffer = {} + +nameLabel = nil +feeLabel = nil +totalPriceEdit = nil +piecePriceEdit = nil +amountEdit = nil +radioItemSet = nil +selectedItem = nil +offerTypeList = nil +categoryList = nil +subCategoryList = nil +slotFilterList = nil +createOfferButton = 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'] = 'Peice Price', ['width'] = 80}, - {['text'] = 'Ends at', ['width'] = 120} - } - -local function getMarketCategoryName(id) - if table.hasKey(MarketCategoryStrings, id) then - return MarketCategoryStrings[id] - end -end - -local function getMarketCategoryId(name) - local id = table.find(MarketCategoryStrings, name) - if id then - return id - end -end - -local function getMarketDescriptionName(id) - if table.hasKey(MarketItemDescriptionStrings, id) then - return MarketItemDescriptionStrings[id] - end -end - -local function getMarketDescriptionId(name) - local id = table.find(MarketItemDescriptionStrings, name) - if id then - return id - end -end - -local function getMarketSlotFilterId(name) - local id = table.find(MarketSlotFilters, name) - if id then - return id - end -end - -local function getMarketSlotFilterName(id) - if table.hasKey(MarketSlotFilters, id) then - return MarketSlotFilters[id] - end -end - -local function isValidItem(item, category) + {['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) if item.marketData.category ~= category and category ~= MarketCategory[0] then return false end @@ -121,76 +86,218 @@ local function isValidItem(item, category) return false end end - if filterDepot and not Market.depotContains(item.ptr:getId()) then + if filterDepot and Market.depotContains(item.ptr:getId()) <= 0 then return false end return true end -local function clearSelectedItem() - if selectedItem and selectedItem.item.ptr then - Market.updateOffers({}) - radioItemSet:selectWidget(nil) - nameLabel:setText('No item selected.') +local function clearItems() + currentItems = {} + Market.refreshItemsWidget() +end - selectedItem:setItem(nil) - selectedItem.item = {} +local function clearFilters() + for _, filter in pairs(filterButtons) do + if filter and filter:isChecked() then + filter:setChecked(false) + end + end +end - detailsTable:clearData() - buyStatsTable:clearData() - sellStatsTable:clearData() +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 initMarketItems() - -- populate all market items - marketItems = {} - local types = g_things.findThingTypeByAttr(ThingAttrMarket) - 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 +local function refreshFee() + feeLabel:setText('') + fee = 0 +end + +local function updateOffers(offers) + marketOffers[MarketAction.Buy] = {} + marketOffers[MarketAction.Sell] = {} + if not buyOfferTable or not sellOfferTable then + return + end + buyOfferTable:clearData() + sellOfferTable:clearData() + balanceLabel:setColor('#bbbbbb') + + for k, offer in pairs(offers) do + if offer and offer:getAction() == MarketAction.Buy then + local data = { + {['text'] = offer:getPlayer(), ['width'] = 100}, + {['text'] = offer:getAmount(), ['width'] = 60}, + {['text'] = offer:getPrice()*offer:getAmount(), ['width'] = 90}, + {['text'] = offer:getPrice(), ['width'] = 80}, + {['text'] = string.gsub(os.date('%c', offer:getTimeStamp()), " ", " "), ['width'] = 120} + } + buyOfferTable:addRow(data, offer:getId()) + table.insert(marketOffers[MarketAction.Buy], offer) + else + local data = { + {['text'] = offer:getPlayer(), ['width'] = 100}, + {['text'] = offer:getAmount(), ['width'] = 60}, + {['text'] = offer:getPrice()*offer:getAmount(), ['width'] = 90}, + {['text'] = offer:getPrice(), ['width'] = 80}, + {['text'] = string.gsub(os.date('%c', offer:getTimeStamp()), " ", " "), ['width'] = 120} + } + sellOfferTable:addRow(data, offer:getId()) + table.insert(marketOffers[MarketAction.Sell], offer) + end + end +end + +local function updateDetails(itemId, descriptions, purchaseStats, saleStats) + if not selectedItem then + return + end + selectedItem.item.details = { + descriptions = descriptions, + purchaseStats = purchaseStats, + saleStats = saleStats + } + + -- 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 + for k, stat in pairs(saleStats) do + if not table.empty(stat) then + sellStatsTable:addRow({{['text'] = 'Total Transations:'}, + {['text'] = stat[1], ['width'] = 270}}) + + sellStatsTable:addRow({{['text'] = 'Highest Price:'}, + {['text'] = stat[3], ['width'] = 270}}) + + sellStatsTable:addRow({{['text'] = 'Average Price:'}, + {['text'] = math.floor(stat[2]/stat[1])}}) + + sellStatsTable:addRow({{['text'] = 'Lowest Price:'}, + {['text'] = stat[4], ['width'] = 270}}) + end + end + end + + -- update buy item statistics + buyStatsTable:clearData() + if table.empty(purchaseStats) then + buyStatsTable:addRow({{['text'] = 'No information'}}) + else + for k, stat in pairs(purchaseStats) do + if not table.empty(stat) then + buyStatsTable:addRow({{['text'] = 'Total Transations:'}, + {['text'] = stat[1], ['width'] = 270}}) + + buyStatsTable:addRow({{['text'] = 'Highest Price:'}, + {['text'] = stat[3], ['width'] = 270}}) + + buyStatsTable:addRow({{['text'] = 'Average Price:'}, + {['text'] = math.floor(stat[2]/stat[1]), ['width'] = 270}}) + + buyStatsTable:addRow({{['text'] = 'Lowest Price:'}, + {['text'] = stat[4], ['width'] = 270}}) end end end end -local function updateItemsWidget() - itemsPanel = browsePanel:recursiveGetChildById('itemsPanel') - local layout = itemsPanel:getLayout() - layout:disableUpdates() +local function updateSelectedItem(newItem) + selectedItem.item = newItem - clearSelectedItem() - itemsPanel:destroyChildren() + Market.resetCreateOffer() + if Market.isItemSelected() then + selectedItem:setItem(selectedItem.item.ptr) + nameLabel:setText(selectedItem.item.marketData.name) + -- update offer types + Market.enableCreateOffer(true) + + MarketProtocol.sendMarketBrowse(selectedItem.item.ptr:getId()) -- send browsed msg + else + Market.Market.clearSelectedItem() + end +end - if radioItemSet then - radioItemSet:destroy() +local function updateBalance(balance) + local balance = tonumber(balance) + if not balance then + return end - radioItemSet = UIRadioGroup.create() - for i = 1, #currentItems do - local item = currentItems[i] - local itemBox = g_ui.createWidget('MarketItemBox', itemsPanel) - local itemWidget = itemBox:getChildById('item') + if balance < 0 then balance = 0 end + information.balance = balance - itemBox.item = item - itemWidget:setItem(item.ptr) + balanceLabel = marketWindow:recursiveGetChildById('balanceLabel') + balanceLabel:setText('Balance: '..balance..'gp') + balanceLabel:resizeToText() +end - radioItemSet:addWidget(itemBox) +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 - layout:enableUpdates() - layout:update() +local function onSelectSellOffer(table, selectedRow, previousSelectedRow) + updateBalance() + for _, offer in pairs(marketOffers[MarketAction.Sell]) do + if offer:isEqual(selectedRow.ref) then + selectedOffer[MarketAction.Sell] = offer + end + end + + local offer = selectedOffer[MarketAction.Sell] + if offer then + if offer:getPrice() > information.balance then + balanceLabel:setColor('#b22222') + else + local slice = (information.balance / 2) + if (offer:getPrice()/slice) * 100 <= 40 then + color = '#008b00' -- green + elseif (offer:getPrice()/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.Buy] = offer + end + end end -local function onUpdateCategory(combobox, option) +local function onChangeCategory(combobox, option) local id = getMarketCategoryId(option) if id == MarketCategory.MetaWeapons then -- enable and load weapons filter/items @@ -205,7 +312,7 @@ local function onUpdateCategory(combobox, option) end end -local function onUpdateSubCategory(combobox, option) +local function onChangeSubCategory(combobox, option) local id = getMarketCategoryId(option) Market.loadMarketItems(id) -- setup slot filter @@ -220,10 +327,74 @@ local function onUpdateSubCategory(combobox, option) slotFilterList:setEnabled(true) end -local function onUpdateSlotFilter(combobox, option) +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) + 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 @@ -269,10 +440,27 @@ local function initInterface() offersTabBar:addTab(tr('Offer History'), offerHistoryPanel) -- setup selected item - nameLabel = marketOffersPanel:recursiveGetChildById('nameLabel') - selectedItem = marketOffersPanel:recursiveGetChildById('selectedItem') + 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') @@ -297,9 +485,9 @@ local function initInterface() subCategoryList:setEnabled(false) -- hook item filters - categoryList.onOptionChange = onUpdateCategory - subCategoryList.onOptionChange = onUpdateSubCategory - slotFilterList.onOptionChange = onUpdateSlotFilter + categoryList.onOptionChange = onChangeCategory + subCategoryList.onOptionChange = onChangeSubCategory + slotFilterList.onOptionChange = onChangeSlotFilter -- get tables buyOfferTable = itemOffersPanel:recursiveGetChildById('buyingTable') @@ -307,9 +495,18 @@ local function initInterface() detailsTable = itemDetailsPanel:recursiveGetChildById('detailsTable') buyStatsTable = itemStatsPanel:recursiveGetChildById('buyStatsTable') sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable') + buyOfferTable.onSelectionChange = onSelectBuyOffer + sellOfferTable.onSelectionChange = onSelectSellOffer end -function Market.init() +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') + protocol.initProtocol() + + connect(g_game, { onGameEnd = Market.reset }) marketWindow = g_ui.createWidget('MarketWindow', rootWidget) marketWindow:hide() @@ -317,58 +514,140 @@ function Market.init() initMarketItems() end -function Market.terminate() +function terminate() + protocol.terminateProtocol() + disconnect(g_game, { onGameEnd = Market.reset }) + if marketWindow then marketWindow:destroy() - marketWindow = nil - end - - mainTabBar = nil - displaysTabBar = nil - offersTabBar = nil - selectionTabBar = nil - - marketOffersPanel = nil - browsePanel = nil - searchPanel = nil - itemOffersPanel = nil - itemDetailsPanel = nil - itemStatsPanel = nil - myOffersPanel = nil - currentOffersPanel = nil - offerHistoryPanel = nil - itemsPanel = nil - - nameLabel = nil - radioItemSet = nil - selectedItem = nil - categoryList = nil - subCategoryList = nil - slotFilterList = nil - filterButtons = {} - - buyOfferTable = nil - sellOfferTable = nil - detailsTable = nil - buyStatsTable = nil - sellStatsTable = nil - - marketOffers = {} - marketItems = {} - information = {} - currentItems = {} + end Market = nil end +function Market.reset() + categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + Market.clearSelectedItem() + clearFilters() + clearItems() +end + +function Market.clearSelectedItem() + if selectedItem and selectedItem.item.ptr then + Market.resetCreateOffer() + offerTypeList:clearOptions() + offerTypeList:setText('Please Select') + offerTypeList:setEnabled(false) + + updateOffers({}) + radioItemSet:selectWidget(nil) + nameLabel:setText('No item selected.') + + selectedItem:setItem(nil) + selectedItem.item = {} + + 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.depotContains(itemId) + local count = 0 for i = 1, #information.depotItems do local item = information.depotItems[i] if item.ptr:getId() == itemId then - return true + 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.incrementAmount() + amountEdit:setValue(amountEdit:getValue() + 1) + -- change total price/piece price according +end + +function Market.decrementAmount() + amountEdit:setValue(amountEdit:getValue() - 1) + -- change total price/piece price according +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') + 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 + select:setChecked(true) end - return false + + layout:enableUpdates() + layout:update() end function Market.loadMarketItems(category) @@ -376,26 +655,46 @@ function Market.loadMarketItems(category) initMarketItems() end - currentItems = {} + clearItems() for i = 1, #marketItems do local item = marketItems[i] - if isValidItem(item, category) then + if isItemValid(item, category) then table.insert(currentItems, item) end end - updateItemsWidget() + Market.refreshItemsWidget() end function Market.loadDepotItems(depotItems) information.depotItems = {} + + local items = {} for i = 1, #depotItems do local data = depotItems[i] - local newItem = Item.create(data[1]) - if not newItem then - break + local id, count = data[1], data[2] + + local newItem = nil + if count > 100 then + local createCount = math.floor(count/100) + local remainder = count % 100 + 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 - newItem:setCount(data[2]) + end + + for _, newItem in pairs(items) do local marketData = newItem:getMarketData() if not table.empty(marketData) then @@ -408,141 +707,56 @@ function Market.loadDepotItems(depotItems) end end -function Market.updateCurrentItems() - -- get market category - local id = getMarketCategoryId(categoryList:getCurrentOption().text) - if id == MarketCategory.MetaWeapons then - id = getMarketCategoryId(subCategoryList:getCurrentOption().text) +function Market.createNewOffer() + local type = offerTypeList:getCurrentOption().text + if type == 'Sell' then + type = MarketAction.Sell + else + type = MarketAction.Buy end - Market.loadMarketItems(id) -end -function Market.updateOffers(offers) - marketOffers[MarketAction.Buy] = {} - marketOffers[MarketAction.Sell] = {} - if not buyOfferTable or not sellOfferTable then + if not Market.isItemSelected() then return end - buyOfferTable:clearData() - sellOfferTable:clearData() + local spriteId = selectedItem.item.ptr:getId() + local piecePrice = piecePriceEdit:getValue() + local totalPrice = totalPriceEdit:getValue() + local amount = amountEdit:getValue() + local anonymous = anonymous:isChecked() and 1 or 0 - for k, offer in pairs(offers) do - if offer and offer:getAction() == MarketAction.Buy then - local data = { - {['text'] = offer:getPlayer(), ['width'] = 100}, - {['text'] = offer:getAmount(), ['width'] = 60}, - {['text'] = offer:getPrice()*offer:getAmount(), ['width'] = 90}, - {['text'] = offer:getPrice(), ['width'] = 80}, - {['text'] = offer:getTimeStamp(), ['width'] = 120} - } - buyOfferTable:addRow(data) - table.insert(marketOffers[MarketAction.Buy], offer) - else - local data = { - {['text'] = offer:getPlayer(), ['width'] = 100}, - {['text'] = offer:getAmount(), ['width'] = 60}, - {['text'] = offer:getPrice()*offer:getAmount(), ['width'] = 90}, - {['text'] = offer:getPrice(), ['width'] = 80}, - {['text'] = offer:getTimeStamp(), ['width'] = 120} - } - sellOfferTable:addRow(data) - table.insert(marketOffers[MarketAction.Sell], offer) - end - end -end - -function Market.updateDetails(itemId, descriptions, purchaseStats, saleStats) - if not selectedItem then - return + 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 - selectedItem.item.details = { - descriptions = descriptions, - purchaseStats = purchaseStats, - saleStats = saleStats - } - - -- 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) + 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 - -- update sale item statistics - sellStatsTable:clearData() - if table.empty(saleStats) then - sellStatsTable:addRow({{['text'] = 'No information'}}) - else - for k, stat in pairs(saleStats) do - if not table.empty(stat) then - sellStatsTable:addRow({{['text'] = 'Total Transations:'}, - {['text'] = stat[1], ['width'] = 270}}) - - sellStatsTable:addRow({{['text'] = 'Highest Price:'}, - {['text'] = stat[3], ['width'] = 270}}) - - sellStatsTable:addRow({{['text'] = 'Average Price:'}, - {['text'] = math.floor(stat[2]/stat[1])}}) - - sellStatsTable:addRow({{['text'] = 'Lowest Price:'}, - {['text'] = stat[4], ['width'] = 270}}) - end - end + if errorMsg ~= '' then + UIMessageBox.display('Error', errorMsg, MessageBoxOk) + return end - -- update buy item statistics - buyStatsTable:clearData() - if table.empty(purchaseStats) then - buyStatsTable:addRow({{['text'] = 'No information'}}) - else - for k, stat in pairs(purchaseStats) do - if not table.empty(stat) then - buyStatsTable:addRow({{['text'] = 'Total Transations:'}, - {['text'] = stat[1], ['width'] = 270}}) - - buyStatsTable:addRow({{['text'] = 'Highest Price:'}, - {['text'] = stat[3], ['width'] = 270}}) - - buyStatsTable:addRow({{['text'] = 'Average Price:'}, - {['text'] = math.floor(stat[2]/stat[1]), ['width'] = 270}}) - - buyStatsTable:addRow({{['text'] = 'Lowest Price:'}, - {['text'] = stat[4], ['width'] = 270}}) - end - end - end + MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, piecePrice, anonymous) + Market.refreshItemsWidget(spriteId) + Market.resetCreateOffer() end -function Market.updateSelectedItem(newItem) - selectedItem.item = newItem - if selectedItem and not table.empty(selectedItem.item) then - if selectedItem.item.ptr then - selectedItem:setItem(selectedItem.item.ptr) - nameLabel:setText(selectedItem.item.marketData.name) - MarketProtocol.sendMarketBrowse(selectedItem.item.ptr:getId()) -- send browsed msg - end - else - selectedItem:setItem(nil) - nameLabel:setText(tr('No item selected.')) +function Market.onItemBoxChecked(widget) + if widget:isChecked() then + updateSelectedItem(widget.item) end end function Market.onMarketEnter(depotItems, offers, balance, vocation) - if marketWindow:isVisible() then - return - end marketOffers[MarketAction.Buy] = {} marketOffers[MarketAction.Sell] = {} - --[[ - TODO: - * clear filters on enter - * add market reset function - ]] - information.balance = balance + updateBalance(balance) information.totalOffers = offers if vocation < 0 then local player = g_game.getLocalPlayer() @@ -552,10 +766,10 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation) information.vocation = vocation end + Market.loadDepotItems(depotItems) if table.empty(currentItems) then Market.loadMarketItems(MarketCategory.First) end - Market.loadDepotItems(depotItems) -- build offer table header if buyOfferTable and not buyOfferTable:hasHeader() then @@ -565,14 +779,10 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation) sellOfferTable:addHeaderRow(offerTableHeader) end - local currentItem = radioItemSet:getSelectedWidget() - if currentItem then - -- Uncheck selected item, cannot make protocol calls to resend marketBrowsing - clearSelectedItem() + if g_game.isOnline() then + marketWindow:lock() + marketWindow:show() end - - marketWindow:lock() - marketWindow:show() end function Market.onMarketLeave() @@ -580,15 +790,9 @@ function Market.onMarketLeave() end function Market.onMarketDetail(itemId, descriptions, purchaseStats, saleStats) - Market.updateDetails(itemId, descriptions, purchaseStats, saleStats) + updateDetails(itemId, descriptions, purchaseStats, saleStats) end function Market.onMarketBrowse(offers) - Market.updateOffers(offers) -end - -function Market.onItemBoxChecked(widget) - if widget:isChecked() then - Market.updateSelectedItem(widget.item) - end + updateOffers(offers) end diff --git a/modules/game_market/market.otmod b/modules/game_market/market.otmod index 329f193f..a2fc890c 100644 --- a/modules/game_market/market.otmod +++ b/modules/game_market/market.otmod @@ -1,16 +1,9 @@ Module name: game_market - description: Manage the Players Market system + description: Global item market system author: BeniS website: www.otclient.info - - @onLoad: | - dofile 'marketoffer' - dofile 'marketprotocol' - dofile 'market' - MarketProtocol.init() - Market.init() - - @onUnload: | - MarketProtocol.terminate() - Market.terminate() + sandboxed: true + scripts: [marketoffer.lua, marketprotocol.lua, market.lua] + @onLoad: init() + @onUnload: terminate() diff --git a/modules/game_market/market.otui b/modules/game_market/market.otui index 2250fc30..df66011e 100644 --- a/modules/game_market/market.otui +++ b/modules/game_market/market.otui @@ -3,8 +3,8 @@ MarketWindow < MainWindow !text: tr('Market') size: 700 510 - @onEnter: self:hide() self:unlock() - @onEscape: self:hide() self:unlock() + @onEnter: self:hide() self:unlock() Market.clearSelectedItem() + @onEscape: self:hide() self:unlock() Market.clearSelectedItem() // Main Panel Window @@ -24,3 +24,11 @@ MarketWindow < MainWindow padding: 3 border-width: 1 border-color: #000000 + + Label + id: balanceLabel + !text: tr('Balance: 10000') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.right: parent.right \ No newline at end of file diff --git a/modules/game_market/marketoffer.lua b/modules/game_market/marketoffer.lua index 34f55868..df6590c2 100644 --- a/modules/game_market/marketoffer.lua +++ b/modules/game_market/marketoffer.lua @@ -46,15 +46,15 @@ MarketOffer.new = function(offerId, action, item, amount, price, playerName, sta return offer end -function MarketOffer:isEqual(offer) - return self.offer[OFFER_TIMESTAMP] == offer[OFFER_TIMESTAMP] and self.offer[OFFER_COUNTER] == offer[OFFER_COUNTER] +function MarketOffer:isEqual(id) + return self.id[OFFER_TIMESTAMP] == id[OFFER_TIMESTAMP] and self.id[OFFER_COUNTER] == id[OFFER_COUNTER] end -function MarketOffer:isLessThan(offer) - return self.offer[OFFER_TIMESTAMP] <= offer[OFFER_TIMESTAMP] and self.offer[OFFER_COUNTER] < offer[OFFER_COUNTER] +function MarketOffer:isLessThan(id) + return self.id[OFFER_TIMESTAMP] <= id[OFFER_TIMESTAMP] and self.id[OFFER_COUNTER] < id[OFFER_COUNTER] end -function MarketOffer:isNull(offer) +function MarketOffer:isNull() return table.empty(self.id) end diff --git a/modules/game_market/marketprotocol.lua b/modules/game_market/marketprotocol.lua index 1479a35e..5e43a7d9 100644 --- a/modules/game_market/marketprotocol.lua +++ b/modules/game_market/marketprotocol.lua @@ -44,8 +44,8 @@ local function parseMarketEnter(msg) local offers = msg:getU8() local depotItems = {} - local depotCount = (msg:getU16() - 1) - for i = 0, depotCount do + local depotCount = msg:getU16() + for i = 1, depotCount do local itemId = msg:getU16() -- item id local itemCount = msg:getU16() -- item count @@ -103,13 +103,13 @@ local function parseMarketBrowse(msg) local var = msg:getU16() local offers = {} - local buyOfferCount = (msg:getU32() - 1) - for i = 0, buyOfferCount do + local buyOfferCount = msg:getU32() + for i = 1, buyOfferCount do table.insert(offers, readMarketOffer(msg, MarketAction.Buy, var)) end - local sellOfferCount = (msg:getU32() - 1) - for i = 0, sellOfferCount do + local sellOfferCount = msg:getU32() + for i = 1, sellOfferCount do table.insert(offers, readMarketOffer(msg, MarketAction.Sell, var)) end @@ -118,7 +118,7 @@ local function parseMarketBrowse(msg) end -- public functions -function MarketProtocol.init() +function initProtocol() connect(g_game, { onGameStart = MarketProtocol.registerProtocol, onGameEnd = MarketProtocol.unregisterProtocol }) @@ -128,7 +128,7 @@ function MarketProtocol.init() end end -function MarketProtocol.terminate() +function terminateProtocol() disconnect(g_game, { onGameStart = MarketProtocol.registerProtocol, onGameEnd = MarketProtocol.unregisterProtocol }) diff --git a/modules/game_market/ui/general/marketbuttons.otui b/modules/game_market/ui/general/marketbuttons.otui index a73124a9..8b04dbc9 100644 --- a/modules/game_market/ui/general/marketbuttons.otui +++ b/modules/game_market/ui/general/marketbuttons.otui @@ -1,8 +1,8 @@ MarketButtonBox < UICheckBox - font: verdana-11px-antialised + font: verdana-11px-rounded color: #f55e5ebb size: 106 22 - text-offset: 0 0 + text-offset: 0 2 text-align: center image-source: /images/tabbutton_rounded.png image-clip: 0 0 20 20 diff --git a/modules/game_market/ui/general/marketcombobox.otui b/modules/game_market/ui/general/marketcombobox.otui index db1c3b14..6dd84173 100644 --- a/modules/game_market/ui/general/marketcombobox.otui +++ b/modules/game_market/ui/general/marketcombobox.otui @@ -1,8 +1,8 @@ MarketComboBoxPopupMenuButton < UIButton height: 18 - font: verdana-11px-antialised + font: verdana-11px-rounded text-align: left - text-offset: 2 0 + text-offset: 2 2 color: #aaaaaa background-color: alpha @@ -28,10 +28,10 @@ MarketComboBoxPopupMenu < UIPopupMenu padding: 1 MarketComboBox < UIComboBox - font: verdana-11px-antialised + font: verdana-11px-rounded color: #aaaaaa size: 86 20 - text-offset: 3 0 + text-offset: 3 2 text-align: left image-source: /images/combobox_rounded.png image-border: 1 diff --git a/modules/game_market/ui/general/markettabs.otui b/modules/game_market/ui/general/markettabs.otui index dce725ed..56f112a7 100644 --- a/modules/game_market/ui/general/markettabs.otui +++ b/modules/game_market/ui/general/markettabs.otui @@ -4,6 +4,8 @@ MarketTabBarPanel < Panel MarketTabBarButton < UIButton size: 20 25 image-source: /images/tabbutton_square.png + font: verdana-11px-rounded + text-offset: 0 2 image-clip: 0 0 20 20 image-border: 2 icon-color: white diff --git a/modules/game_market/ui/marketoffers.otui b/modules/game_market/ui/marketoffers.otui index 205b7bb1..237b9f0b 100644 --- a/modules/game_market/ui/marketoffers.otui +++ b/modules/game_market/ui/marketoffers.otui @@ -18,7 +18,7 @@ Panel MarketTabBar id: rightTabBar - width: 157 + width: 166 height:25 anchors.top: parent.top anchors.right: parent.right @@ -48,7 +48,142 @@ Panel Label id: nameLabel !text: tr('No item selected.') + font: verdana-11px-rounded + text-offset: 0 2 anchors.top: prev.top anchors.left: prev.right anchors.right: parent.right margin-left: 5 + + Label + id: createLabel + !text: tr('Create New Offer') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: rightTabBar.top + anchors.left: rightTabContent.left + margin-top: 355 + margin-left: 6 + + Label + id: offerTypeLabel + !text: tr('Offer Type:') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 7 + + MarketComboBox + id: offerTypeComboBox + !text: tr('Please Select') + anchors.top: prev.bottom + anchors.left: createLabel.left + margin-top: 3 + width: 105 + + $disabled: + color: #aaaaaa44 + + Label + id: totalPriceLabel + !text: tr('Total Price:') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 7 + + SpinBox + id: totalPriceEdit + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 3 + width: 75 + minimum: 1 + maximum: 99999999 + + $disabled: + color: #aaaaaa44 + + Label + id: piecePriceLabel + !text: tr('Piece Price:') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 7 + + SpinBox + id: piecePriceEdit + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 3 + width: 75 + minimum: 1 + maximum: 99999999 + + $disabled: + color: #aaaaaa44 + + Label + id: amountLabel + !text: tr('Amount:') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 32 + + PreviousButton + id: prevAmountButton + anchors.verticalCenter: piecePriceEdit.verticalCenter + anchors.left: piecePriceEdit.right + margin-left: 7 + @onClick: Market.decrementAmount() + + SpinBox + id: amountEdit + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 55 + minimum: 1 + maximum: 999999 + + NextButton + id: nextAmountButton + anchors.verticalCenter: piecePriceEdit.verticalCenter + anchors.left: prev.right + margin-left: 3 + @onClick: Market.incrementAmount() + + Button + id: createOfferButton + !text: tr('Create Offer') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 7 + width: 90 + //@onClick: g_game.closeNpcTrade() + + CheckBox + id: anonymousCheckBox + !text: tr('Anonymous') + anchors.left: prev.left + anchors.bottom: prev.top + margin-bottom: 6 + @onSetup: self:setChecked(false) + height: 16 + width: 70 + + Label + id: feeLabel + !text: tr('') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: createOfferButton.bottom + anchors.right: parent.right + margin-right: 8 + margin-top: 3 \ No newline at end of file diff --git a/modules/game_market/ui/marketoffers/browse.otui b/modules/game_market/ui/marketoffers/browse.otui index 143f50d7..f5a99da5 100644 --- a/modules/game_market/ui/marketoffers/browse.otui +++ b/modules/game_market/ui/marketoffers/browse.otui @@ -1,14 +1,15 @@ MarketItemBox < UICheckBox + id: itemBox border-width: 1 border-color: #000000 color: #aaaaaa text-align: center - text-offset: 0 20 - @onCheckChange: Market.onItemBoxChecked(self) Item id: item phantom: true + text-offset: 0 13 + text-align: right anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter margin: 1 diff --git a/modules/game_market/ui/marketoffers/itemdetails.otui b/modules/game_market/ui/marketoffers/itemdetails.otui index 33de5a22..4b2fc55f 100644 --- a/modules/game_market/ui/marketoffers/itemdetails.otui +++ b/modules/game_market/ui/marketoffers/itemdetails.otui @@ -25,9 +25,9 @@ Panel anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right - margin-top: 55 + margin-top: 63 margin-left: 6 - margin-bottom: 75 + margin-bottom: 85 margin-right: 6 padding: 1 focusable: false diff --git a/modules/game_market/ui/marketoffers/itemoffers.otui b/modules/game_market/ui/marketoffers/itemoffers.otui index cbcc6cd8..bb257a44 100644 --- a/modules/game_market/ui/marketoffers/itemoffers.otui +++ b/modules/game_market/ui/marketoffers/itemoffers.otui @@ -49,6 +49,8 @@ Panel Label !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 anchors.top: parent.top anchors.left: parent.left margin-top: 44 @@ -59,7 +61,7 @@ Panel anchors.top: prev.bottom anchors.left: prev.left anchors.right: parent.right - height: 120 + height: 115 margin-top: 5 margin-bottom: 5 margin-right: 6 @@ -101,6 +103,8 @@ Panel Label !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 anchors.top: prev.top anchors.left: parent.left margin-top: 9 @@ -114,7 +118,7 @@ Panel margin-top: 5 margin-bottom: 5 margin-right: 6 - height: 120 + height: 115 padding: 1 focusable: false background-color: #222833 diff --git a/modules/game_market/ui/marketoffers/itemstats.otui b/modules/game_market/ui/marketoffers/itemstats.otui index 5f2f3d9e..709831b4 100644 --- a/modules/game_market/ui/marketoffers/itemstats.otui +++ b/modules/game_market/ui/marketoffers/itemstats.otui @@ -20,6 +20,8 @@ Panel Label !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -62,6 +64,8 @@ Panel Label !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 anchors.top: buyStatsTable.bottom anchors.left: parent.left margin-top: 9 @@ -73,7 +77,7 @@ Panel anchors.left: buyStatsTable.left anchors.right: buyStatsTable.right margin-top: 6 - height: 122 + height: 112 padding: 1 focusable: false background-color: #222833 diff --git a/modules/gamelib/market.lua b/modules/gamelib/market.lua index b90ea060..0e23d508 100644 --- a/modules/gamelib/market.lua +++ b/modules/gamelib/market.lua @@ -1,3 +1,21 @@ +MarketAction = { + Buy = 0, + Sell = 1 +} + +MarketRequest = { + MyOffers = 0xFFFE, + MyHistory = 0xFFFF +} + +MarketOfferState = { + Active = 0, + Cancelled = 1, + Expired = 2, + Accepted = 3, + AcceptedEx = 255 +} + MarketCategory = { All = 0, Armors = 1, @@ -64,23 +82,18 @@ MarketCategoryStrings = { [255] = 'Weapons' } -MarketAction = { - Buy = 0, - Sell = 1 -} - -MarketRequest = { - MyOffers = 0xFFFE, - MyHistory = 0xFFFF -} +function getMarketCategoryName(id) + if table.hasKey(MarketCategoryStrings, id) then + return MarketCategoryStrings[id] + end +end -MarketOfferState = { - Active = 0, - Cancelled = 1, - Expired = 2, - Accepted = 3, - AcceptedEx = 255 -} +function getMarketCategoryId(name) + local id = table.find(MarketCategoryStrings, name) + if id then + return id + end +end MarketItemDescription = { Armor = 1, @@ -121,6 +134,19 @@ MarketItemDescriptionStrings = { [15] = 'Weight' } +function getMarketDescriptionName(id) + if table.hasKey(MarketItemDescriptionStrings, id) then + return MarketItemDescriptionStrings[id] + end +end + +function getMarketDescriptionId(name) + local id = table.find(MarketItemDescriptionStrings, name) + if id then + return id + end +end + MarketSlotFilters = { [InventorySlotOther] = "Two-Handed", [InventorySlotLeft] = "One-Handed", @@ -134,4 +160,17 @@ MarketFilters = { } MarketFilters.First = MarketFilters.vocation -MarketFilters.Last = MarketFilters.depot \ No newline at end of file +MarketFilters.Last = MarketFilters.depot + +function getMarketSlotFilterId(name) + local id = table.find(MarketSlotFilters, name) + if id then + return id + end +end + +function getMarketSlotFilterName(id) + if table.hasKey(MarketSlotFilters, id) then + return MarketSlotFilters[id] + end +end \ No newline at end of file