No Description
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.

market.lua 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. --[[
  2. Finalizing Market:
  3. Note: Feel free to work on any area and submit
  4. it as a pull request from your git fork.
  5. BeniS's Skype: benjiz69
  6. List:
  7. * Add offer management:
  8. - Current Offers
  9. - Offer History
  10. * Clean up the interface building
  11. - Add a new market interface file to handle building?
  12. * Extend information features
  13. - Hover over offers for purchase information (balance after transaction, etc)
  14. ]]
  15. Market = {}
  16. local protocol = runinsandbox('marketprotocol')
  17. marketWindow = nil
  18. mainTabBar = nil
  19. displaysTabBar = nil
  20. offersTabBar = nil
  21. selectionTabBar = nil
  22. marketOffersPanel = nil
  23. browsePanel = nil
  24. overviewPanel = nil
  25. itemOffersPanel = nil
  26. itemDetailsPanel = nil
  27. itemStatsPanel = nil
  28. myOffersPanel = nil
  29. currentOffersPanel = nil
  30. offerHistoryPanel = nil
  31. itemsPanel = nil
  32. selectedOffer = {}
  33. selectedMyOffer = {}
  34. nameLabel = nil
  35. feeLabel = nil
  36. balanceLabel = nil
  37. totalPriceEdit = nil
  38. piecePriceEdit = nil
  39. amountEdit = nil
  40. searchEdit = nil
  41. radioItemSet = nil
  42. selectedItem = nil
  43. offerTypeList = nil
  44. categoryList = nil
  45. subCategoryList = nil
  46. slotFilterList = nil
  47. createOfferButton = nil
  48. buyButton = nil
  49. sellButton = nil
  50. anonymous = nil
  51. filterButtons = {}
  52. buyOfferTable = nil
  53. sellOfferTable = nil
  54. detailsTable = nil
  55. buyStatsTable = nil
  56. sellStatsTable = nil
  57. buyCancelButton = nil
  58. sellCancelButton = nil
  59. buyMyOfferTable = nil
  60. sellMyOfferTable = nil
  61. offerExhaust = {}
  62. marketOffers = {}
  63. marketItems = {}
  64. information = {}
  65. currentItems = {}
  66. lastCreatedOffer = 0
  67. fee = 0
  68. averagePrice = 0
  69. loaded = false
  70. local function isItemValid(item, category, searchFilter)
  71. if not item or not item.marketData then
  72. return false
  73. end
  74. if not category then
  75. category = MarketCategory.All
  76. end
  77. if item.marketData.category ~= category and category ~= MarketCategory.All then
  78. return false
  79. end
  80. -- filter item
  81. local slotFilter = false
  82. if slotFilterList:isEnabled() then
  83. slotFilter = getMarketSlotFilterId(slotFilterList:getCurrentOption().text)
  84. end
  85. local marketData = item.marketData
  86. local filterVocation = filterButtons[MarketFilters.Vocation]:isChecked()
  87. local filterLevel = filterButtons[MarketFilters.Level]:isChecked()
  88. local filterDepot = filterButtons[MarketFilters.Depot]:isChecked()
  89. if slotFilter then
  90. if slotFilter ~= 255 and item.thingType:getClothSlot() ~= slotFilter then
  91. return false
  92. end
  93. end
  94. local player = g_game.getLocalPlayer()
  95. if filterLevel and marketData.requiredLevel and player:getLevel() < marketData.requiredLevel then
  96. return false
  97. end
  98. if filterVocation and marketData.restrictVocation > 0 then
  99. local voc = Bit.bit(information.vocation)
  100. if not Bit.hasBit(marketData.restrictVocation, voc) then
  101. return false
  102. end
  103. end
  104. if filterDepot and Market.getDepotCount(item.marketData.tradeAs) <= 0 then
  105. return false
  106. end
  107. if searchFilter then
  108. return marketData.name:lower():find(searchFilter)
  109. end
  110. return true
  111. end
  112. local function clearItems()
  113. currentItems = {}
  114. Market.refreshItemsWidget()
  115. end
  116. local function clearOffers()
  117. marketOffers[MarketAction.Buy] = {}
  118. marketOffers[MarketAction.Sell] = {}
  119. buyOfferTable:clearData()
  120. sellOfferTable:clearData()
  121. end
  122. local function clearMyOffers()
  123. marketOffers[MarketAction.Buy] = {}
  124. marketOffers[MarketAction.Sell] = {}
  125. buyMyOfferTable:clearData()
  126. sellMyOfferTable:clearData()
  127. end
  128. local function clearFilters()
  129. for _, filter in pairs(filterButtons) do
  130. if filter and filter:isChecked() ~= filter.default then
  131. filter:setChecked(filter.default)
  132. end
  133. end
  134. end
  135. local function clearFee()
  136. feeLabel:setText('')
  137. fee = 20
  138. end
  139. local function refreshTypeList()
  140. offerTypeList:clearOptions()
  141. offerTypeList:addOption('Buy')
  142. if Market.isItemSelected() then
  143. if Market.getDepotCount(selectedItem.item.marketData.tradeAs) > 0 then
  144. offerTypeList:addOption('Sell')
  145. end
  146. end
  147. end
  148. local function addOffer(offer, offerType)
  149. if not offer then
  150. return false
  151. end
  152. local id = offer:getId()
  153. local player = offer:getPlayer()
  154. local amount = offer:getAmount()
  155. local price = offer:getPrice()
  156. local timestamp = offer:getTimeStamp()
  157. local itemName = offer:getItem():getMarketData().name
  158. buyOfferTable:toggleSorting(false)
  159. sellOfferTable:toggleSorting(false)
  160. buyMyOfferTable:toggleSorting(false)
  161. sellMyOfferTable:toggleSorting(false)
  162. if amount < 1 then return false end
  163. if offerType == MarketAction.Buy then
  164. if offer.warn then
  165. buyOfferTable:setColumnStyle('OfferTableWarningColumn', true)
  166. end
  167. local row = nil
  168. if offer.var == MarketRequest.MyOffers then
  169. row = buyMyOfferTable:addRow({
  170. {text = itemName},
  171. {text = price*amount},
  172. {text = price},
  173. {text = amount},
  174. {text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
  175. })
  176. else
  177. row = buyOfferTable:addRow({
  178. {text = player},
  179. {text = amount},
  180. {text = price*amount},
  181. {text = price},
  182. {text = string.gsub(os.date('%c', timestamp), " ", " ")}
  183. })
  184. end
  185. row.ref = id
  186. if offer.warn then
  187. row:setTooltip(tr('This offer is 25%% below the average market price'))
  188. buyOfferTable:setColumnStyle('OfferTableColumn', true)
  189. end
  190. else
  191. if offer.warn then
  192. sellOfferTable:setColumnStyle('OfferTableWarningColumn', true)
  193. end
  194. local row = nil
  195. if offer.var == MarketRequest.MyOffers then
  196. row = sellMyOfferTable:addRow({
  197. {text = itemName},
  198. {text = price*amount},
  199. {text = price},
  200. {text = amount},
  201. {text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
  202. })
  203. else
  204. row = sellOfferTable:addRow({
  205. {text = player},
  206. {text = amount},
  207. {text = price*amount},
  208. {text = price},
  209. {text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
  210. })
  211. end
  212. row.ref = id
  213. if offer.warn then
  214. row:setTooltip(tr('This offer is 25%% above the average market price'))
  215. sellOfferTable:setColumnStyle('OfferTableColumn', true)
  216. end
  217. end
  218. buyOfferTable:toggleSorting(false)
  219. sellOfferTable:toggleSorting(false)
  220. buyOfferTable:sort()
  221. sellOfferTable:sort()
  222. buyMyOfferTable:toggleSorting(false)
  223. sellMyOfferTable:toggleSorting(false)
  224. buyMyOfferTable:sort()
  225. sellMyOfferTable:sort()
  226. return true
  227. end
  228. local function mergeOffer(offer)
  229. if not offer then
  230. return false
  231. end
  232. local id = offer:getId()
  233. local offerType = offer:getType()
  234. local amount = offer:getAmount()
  235. local replaced = false
  236. if offerType == MarketAction.Buy then
  237. if averagePrice > 0 then
  238. offer.warn = offer:getPrice() <= averagePrice - math.floor(averagePrice / 4)
  239. end
  240. for i = 1, #marketOffers[MarketAction.Buy] do
  241. local o = marketOffers[MarketAction.Buy][i]
  242. -- replace existing offer
  243. if o:isEqual(id) then
  244. marketOffers[MarketAction.Buy][i] = offer
  245. replaced = true
  246. end
  247. end
  248. if not replaced then
  249. table.insert(marketOffers[MarketAction.Buy], offer)
  250. end
  251. else
  252. if averagePrice > 0 then
  253. offer.warn = offer:getPrice() >= averagePrice + math.floor(averagePrice / 4)
  254. end
  255. for i = 1, #marketOffers[MarketAction.Sell] do
  256. local o = marketOffers[MarketAction.Sell][i]
  257. -- replace existing offer
  258. if o:isEqual(id) then
  259. marketOffers[MarketAction.Sell][i] = offer
  260. replaced = true
  261. end
  262. end
  263. if not replaced then
  264. table.insert(marketOffers[MarketAction.Sell], offer)
  265. end
  266. end
  267. return true
  268. end
  269. local function updateOffers(offers)
  270. if not buyOfferTable or not sellOfferTable then
  271. return
  272. end
  273. balanceLabel:setColor('#bbbbbb')
  274. selectedOffer[MarketAction.Buy] = nil
  275. selectedOffer[MarketAction.Sell] = nil
  276. selectedMyOffer[MarketAction.Buy] = nil
  277. selectedMyOffer[MarketAction.Sell] = nil
  278. -- clear existing offer data
  279. buyOfferTable:clearData()
  280. buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
  281. sellOfferTable:clearData()
  282. sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
  283. sellButton:setEnabled(false)
  284. buyButton:setEnabled(false)
  285. buyCancelButton:setEnabled(false)
  286. sellCancelButton:setEnabled(false)
  287. for _, offer in pairs(offers) do
  288. mergeOffer(offer)
  289. end
  290. for type, offers in pairs(marketOffers) do
  291. for i = 1, #offers do
  292. addOffer(offers[i], type)
  293. end
  294. end
  295. end
  296. local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
  297. if not selectedItem then
  298. return
  299. end
  300. -- update item details
  301. detailsTable:clearData()
  302. for k, desc in pairs(descriptions) do
  303. local columns = {
  304. {text = getMarketDescriptionName(desc[1])..':'},
  305. {text = desc[2]}
  306. }
  307. detailsTable:addRow(columns)
  308. end
  309. -- update sale item statistics
  310. sellStatsTable:clearData()
  311. if table.empty(saleStats) then
  312. sellStatsTable:addRow({{text = 'No information'}})
  313. else
  314. local offerAmount = 0
  315. local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
  316. for _, stat in pairs(saleStats) do
  317. if not stat:isNull() then
  318. offerAmount = offerAmount + 1
  319. transactions = transactions + stat:getTransactions()
  320. totalPrice = totalPrice + stat:getTotalPrice()
  321. local newHigh = stat:getHighestPrice()
  322. if newHigh > highestPrice then
  323. highestPrice = newHigh
  324. end
  325. local newLow = stat:getLowestPrice()
  326. -- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft
  327. if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then
  328. lowestPrice = newLow
  329. end
  330. end
  331. end
  332. if offerAmount >= 5 and transactions >= 10 then
  333. averagePrice = math.round(totalPrice / transactions)
  334. else
  335. averagePrice = 0
  336. end
  337. sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}})
  338. sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
  339. if totalPrice > 0 and transactions > 0 then
  340. sellStatsTable:addRow({{text = 'Average Price:'},
  341. {text = math.floor(totalPrice/transactions)}})
  342. else
  343. sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
  344. end
  345. sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
  346. end
  347. -- update buy item statistics
  348. buyStatsTable:clearData()
  349. if table.empty(purchaseStats) then
  350. buyStatsTable:addRow({{text = 'No information'}})
  351. else
  352. local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
  353. for _, stat in pairs(purchaseStats) do
  354. if not stat:isNull() then
  355. transactions = transactions + stat:getTransactions()
  356. totalPrice = totalPrice + stat:getTotalPrice()
  357. local newHigh = stat:getHighestPrice()
  358. if newHigh > highestPrice then
  359. highestPrice = newHigh
  360. end
  361. local newLow = stat:getLowestPrice()
  362. -- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft
  363. if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then
  364. lowestPrice = newLow
  365. end
  366. end
  367. end
  368. buyStatsTable:addRow({{text = 'Total Transations:'},{text = transactions}})
  369. buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
  370. if totalPrice > 0 and transactions > 0 then
  371. buyStatsTable:addRow({{text = 'Average Price:'},
  372. {text = math.floor(totalPrice/transactions)}})
  373. else
  374. buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
  375. end
  376. buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
  377. end
  378. end
  379. local function updateSelectedItem(widget)
  380. selectedItem.item = widget.item
  381. selectedItem.ref = widget
  382. Market.resetCreateOffer()
  383. if Market.isItemSelected() then
  384. selectedItem:setItem(selectedItem.item.displayItem)
  385. nameLabel:setText(selectedItem.item.marketData.name)
  386. clearOffers()
  387. Market.enableCreateOffer(true) -- update offer types
  388. MarketProtocol.sendMarketBrowse(selectedItem.item.marketData.tradeAs) -- send browsed msg
  389. else
  390. Market.clearSelectedItem()
  391. end
  392. end
  393. local function updateBalance(balance)
  394. local balance = tonumber(balance)
  395. if not balance then
  396. return
  397. end
  398. if balance < 0 then balance = 0 end
  399. information.balance = balance
  400. balanceLabel:setText('Balance: '..balance..' gold')
  401. balanceLabel:resizeToText()
  402. end
  403. local function updateFee(price, amount)
  404. fee = math.ceil(price / 100 * amount)
  405. if fee < 20 then
  406. fee = 20
  407. elseif fee > 1000 then
  408. fee = 1000
  409. end
  410. feeLabel:setText('Fee: '..fee)
  411. feeLabel:resizeToText()
  412. end
  413. local function destroyAmountWindow()
  414. if amountWindow then
  415. amountWindow:destroy()
  416. amountWindow = nil
  417. end
  418. end
  419. local function cancelMyOffer(actionType)
  420. local offer = selectedMyOffer[actionType]
  421. MarketProtocol.sendMarketCancelOffer(offer:getTimeStamp(), offer:getCounter())
  422. Market.refreshMyOffers()
  423. end
  424. local function openAmountWindow(callback, actionType, actionText)
  425. if not Market.isOfferSelected(actionType) then
  426. return
  427. end
  428. amountWindow = g_ui.createWidget('AmountWindow', rootWidget)
  429. amountWindow:lock()
  430. local offer = selectedOffer[actionType]
  431. local item = offer:getItem()
  432. local maximum = offer:getAmount()
  433. if actionType == MarketAction.Sell then
  434. local depot = Market.getDepotCount(item:getId())
  435. if maximum > depot then
  436. maximum = depot
  437. end
  438. else
  439. maximum = math.min(maximum, math.floor(information.balance / offer:getPrice()))
  440. end
  441. if item:isStackable() then
  442. maximum = math.min(maximum, MarketMaxAmountStackable)
  443. else
  444. maximum = math.min(maximum, MarketMaxAmount)
  445. end
  446. local itembox = amountWindow:getChildById('item')
  447. itembox:setItemId(item:getId())
  448. local scrollbar = amountWindow:getChildById('amountScrollBar')
  449. scrollbar:setText(offer:getPrice()..'gp')
  450. scrollbar.onValueChange = function(widget, value)
  451. widget:setText((value*offer:getPrice())..'gp')
  452. itembox:setText(value)
  453. end
  454. scrollbar:setRange(1, maximum)
  455. scrollbar:setValue(1)
  456. local okButton = amountWindow:getChildById('buttonOk')
  457. if actionText then
  458. okButton:setText(actionText)
  459. end
  460. local okFunc = function()
  461. local counter = offer:getCounter()
  462. local timestamp = offer:getTimeStamp()
  463. callback(scrollbar:getValue(), timestamp, counter)
  464. destroyAmountWindow()
  465. end
  466. local cancelButton = amountWindow:getChildById('buttonCancel')
  467. local cancelFunc = function()
  468. destroyAmountWindow()
  469. end
  470. amountWindow.onEnter = okFunc
  471. amountWindow.onEscape = cancelFunc
  472. okButton.onClick = okFunc
  473. cancelButton.onClick = cancelFunc
  474. end
  475. local function onSelectSellOffer(table, selectedRow, previousSelectedRow)
  476. updateBalance()
  477. for _, offer in pairs(marketOffers[MarketAction.Sell]) do
  478. if offer:isEqual(selectedRow.ref) then
  479. selectedOffer[MarketAction.Buy] = offer
  480. end
  481. end
  482. local offer = selectedOffer[MarketAction.Buy]
  483. if offer then
  484. local price = offer:getPrice()
  485. if price > information.balance then
  486. balanceLabel:setColor('#b22222') -- red
  487. buyButton:setEnabled(false)
  488. else
  489. local slice = (information.balance / 2)
  490. if (price/slice) * 100 <= 40 then
  491. color = '#008b00' -- green
  492. elseif (price/slice) * 100 <= 70 then
  493. color = '#eec900' -- yellow
  494. else
  495. color = '#ee9a00' -- orange
  496. end
  497. balanceLabel:setColor(color)
  498. buyButton:setEnabled(true)
  499. end
  500. end
  501. end
  502. local function onSelectBuyOffer(table, selectedRow, previousSelectedRow)
  503. updateBalance()
  504. for _, offer in pairs(marketOffers[MarketAction.Buy]) do
  505. if offer:isEqual(selectedRow.ref) then
  506. selectedOffer[MarketAction.Sell] = offer
  507. if Market.getDepotCount(offer:getItem():getId()) > 0 then
  508. sellButton:setEnabled(true)
  509. else
  510. sellButton:setEnabled(false)
  511. end
  512. end
  513. end
  514. end
  515. local function onSelectMyBuyOffer(table, selectedRow, previousSelectedRow)
  516. for _, offer in pairs(marketOffers[MarketAction.Buy]) do
  517. if offer:isEqual(selectedRow.ref) then
  518. selectedMyOffer[MarketAction.Buy] = offer
  519. buyCancelButton:setEnabled(true)
  520. end
  521. end
  522. end
  523. local function onSelectMySellOffer(table, selectedRow, previousSelectedRow)
  524. for _, offer in pairs(marketOffers[MarketAction.Sell]) do
  525. if offer:isEqual(selectedRow.ref) then
  526. selectedMyOffer[MarketAction.Sell] = offer
  527. sellCancelButton:setEnabled(true)
  528. end
  529. end
  530. end
  531. local function onChangeCategory(combobox, option)
  532. local id = getMarketCategoryId(option)
  533. if id == MarketCategory.MetaWeapons then
  534. -- enable and load weapons filter/items
  535. subCategoryList:setEnabled(true)
  536. slotFilterList:setEnabled(true)
  537. local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text)
  538. Market.loadMarketItems(subId)
  539. else
  540. subCategoryList:setEnabled(false)
  541. slotFilterList:setEnabled(false)
  542. Market.loadMarketItems(id) -- load standard filter
  543. end
  544. end
  545. local function onChangeSubCategory(combobox, option)
  546. Market.loadMarketItems(getMarketCategoryId(option))
  547. slotFilterList:clearOptions()
  548. local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text)
  549. local slots = MarketCategoryWeapons[subId].slots
  550. for _, slot in pairs(slots) do
  551. if table.haskey(MarketSlotFilters, slot) then
  552. slotFilterList:addOption(MarketSlotFilters[slot])
  553. end
  554. end
  555. slotFilterList:setEnabled(true)
  556. end
  557. local function onChangeSlotFilter(combobox, option)
  558. Market.updateCurrentItems()
  559. end
  560. local function onChangeOfferType(combobox, option)
  561. local item = selectedItem.item
  562. local maximum = item.thingType:isStackable() and MarketMaxAmountStackable or MarketMaxAmount
  563. if option == 'Sell' then
  564. maximum = math.min(maximum, Market.getDepotCount(item.marketData.tradeAs))
  565. amountEdit:setMaximum(maximum)
  566. else
  567. amountEdit:setMaximum(maximum)
  568. end
  569. end
  570. local function onTotalPriceChange()
  571. local amount = amountEdit:getValue()
  572. local totalPrice = totalPriceEdit:getValue()
  573. local piecePrice = math.floor(totalPrice/amount)
  574. piecePriceEdit:setValue(piecePrice, true)
  575. if Market.isItemSelected() then
  576. updateFee(piecePrice, amount)
  577. end
  578. end
  579. local function onPiecePriceChange()
  580. local amount = amountEdit:getValue()
  581. local totalPrice = totalPriceEdit:getValue()
  582. local piecePrice = piecePriceEdit:getValue()
  583. totalPriceEdit:setValue(piecePrice*amount, true)
  584. if Market.isItemSelected() then
  585. updateFee(piecePrice, amount)
  586. end
  587. end
  588. local function onAmountChange()
  589. local amount = amountEdit:getValue()
  590. local piecePrice = piecePriceEdit:getValue()
  591. local totalPrice = piecePrice * amount
  592. totalPriceEdit:setValue(piecePrice*amount, true)
  593. if Market.isItemSelected() then
  594. updateFee(piecePrice, amount)
  595. end
  596. end
  597. local function onMarketMessage(messageMode, message)
  598. Market.displayMessage(message)
  599. end
  600. local function initMarketItems()
  601. for c = MarketCategory.First, MarketCategory.Last do
  602. marketItems[c] = {}
  603. end
  604. -- save a list of items which are already added
  605. local itemSet = {}
  606. -- populate all market items
  607. local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0)
  608. for i = 1, #types do
  609. local itemType = types[i]
  610. local item = Item.create(itemType:getId())
  611. if item then
  612. local marketData = itemType:getMarketData()
  613. if not table.empty(marketData) and not itemSet[marketData.tradeAs] then
  614. -- Some items use a different sprite in Market
  615. item:setId(marketData.showAs)
  616. -- create new marketItem block
  617. local marketItem = {
  618. displayItem = item,
  619. thingType = itemType,
  620. marketData = marketData
  621. }
  622. -- add new market item
  623. table.insert(marketItems[marketData.category], marketItem)
  624. itemSet[marketData.tradeAs] = true
  625. end
  626. end
  627. end
  628. end
  629. local function initInterface()
  630. -- TODO: clean this up
  631. -- setup main tabs
  632. mainTabBar = marketWindow:getChildById('mainTabBar')
  633. mainTabBar:setContentWidget(marketWindow:getChildById('mainTabContent'))
  634. -- setup 'Market Offer' section tabs
  635. marketOffersPanel = g_ui.loadUI('ui/marketoffers')
  636. mainTabBar:addTab(tr('Market Offers'), marketOffersPanel)
  637. selectionTabBar = marketOffersPanel:getChildById('leftTabBar')
  638. selectionTabBar:setContentWidget(marketOffersPanel:getChildById('leftTabContent'))
  639. browsePanel = g_ui.loadUI('ui/marketoffers/browse')
  640. selectionTabBar:addTab(tr('Browse'), browsePanel)
  641. -- Currently not used
  642. -- "Reserved for more functionality later"
  643. --overviewPanel = g_ui.loadUI('ui/marketoffers/overview')
  644. --selectionTabBar:addTab(tr('Overview'), overviewPanel)
  645. displaysTabBar = marketOffersPanel:getChildById('rightTabBar')
  646. displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent'))
  647. itemStatsPanel = g_ui.loadUI('ui/marketoffers/itemstats')
  648. displaysTabBar:addTab(tr('Statistics'), itemStatsPanel)
  649. itemDetailsPanel = g_ui.loadUI('ui/marketoffers/itemdetails')
  650. displaysTabBar:addTab(tr('Details'), itemDetailsPanel)
  651. itemOffersPanel = g_ui.loadUI('ui/marketoffers/itemoffers')
  652. displaysTabBar:addTab(tr('Offers'), itemOffersPanel)
  653. displaysTabBar:selectTab(displaysTabBar:getTab(tr('Offers')))
  654. -- setup 'My Offer' section tabs
  655. myOffersPanel = g_ui.loadUI('ui/myoffers')
  656. mainTabBar:addTab(tr('My Offers'), myOffersPanel)
  657. offersTabBar = myOffersPanel:getChildById('offersTabBar')
  658. offersTabBar:setContentWidget(myOffersPanel:getChildById('offersTabContent'))
  659. currentOffersPanel = g_ui.loadUI('ui/myoffers/currentoffers')
  660. offersTabBar:addTab(tr('Current Offers'), currentOffersPanel)
  661. offerHistoryPanel = g_ui.loadUI('ui/myoffers/offerhistory')
  662. offersTabBar:addTab(tr('Offer History'), offerHistoryPanel)
  663. balanceLabel = marketWindow:getChildById('balanceLabel')
  664. -- setup offers
  665. buyButton = itemOffersPanel:getChildById('buyButton')
  666. buyButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Buy, 'Buy') end
  667. sellButton = itemOffersPanel:getChildById('sellButton')
  668. sellButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Sell, 'Sell') end
  669. -- setup selected item
  670. nameLabel = marketOffersPanel:getChildById('nameLabel')
  671. selectedItem = marketOffersPanel:getChildById('selectedItem')
  672. -- setup create new offer
  673. totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit')
  674. piecePriceEdit = marketOffersPanel:getChildById('piecePriceEdit')
  675. amountEdit = marketOffersPanel:getChildById('amountEdit')
  676. feeLabel = marketOffersPanel:getChildById('feeLabel')
  677. totalPriceEdit.onValueChange = onTotalPriceChange
  678. piecePriceEdit.onValueChange = onPiecePriceChange
  679. amountEdit.onValueChange = onAmountChange
  680. offerTypeList = marketOffersPanel:getChildById('offerTypeComboBox')
  681. offerTypeList.onOptionChange = onChangeOfferType
  682. anonymous = marketOffersPanel:getChildById('anonymousCheckBox')
  683. createOfferButton = marketOffersPanel:getChildById('createOfferButton')
  684. createOfferButton.onClick = Market.createNewOffer
  685. Market.enableCreateOffer(false)
  686. -- setup filters
  687. filterButtons[MarketFilters.Vocation] = browsePanel:getChildById('filterVocation')
  688. filterButtons[MarketFilters.Level] = browsePanel:getChildById('filterLevel')
  689. filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot')
  690. filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll')
  691. -- set filter default values
  692. clearFilters()
  693. -- hook filters
  694. for _, filter in pairs(filterButtons) do
  695. filter.onCheckChange = Market.updateCurrentItems
  696. end
  697. searchEdit = browsePanel:getChildById('searchEdit')
  698. categoryList = browsePanel:getChildById('categoryComboBox')
  699. subCategoryList = browsePanel:getChildById('subCategoryComboBox')
  700. slotFilterList = browsePanel:getChildById('slotComboBox')
  701. slotFilterList:addOption(MarketSlotFilters[255])
  702. slotFilterList:setEnabled(false)
  703. for i = MarketCategory.First, MarketCategory.Last do
  704. if i >= MarketCategory.Ammunition and i <= MarketCategory.WandsRods then
  705. subCategoryList:addOption(getMarketCategoryName(i))
  706. else
  707. categoryList:addOption(getMarketCategoryName(i))
  708. end
  709. end
  710. categoryList:addOption(getMarketCategoryName(255)) -- meta weapons
  711. categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First))
  712. subCategoryList:setEnabled(false)
  713. -- hook item filters
  714. categoryList.onOptionChange = onChangeCategory
  715. subCategoryList.onOptionChange = onChangeSubCategory
  716. slotFilterList.onOptionChange = onChangeSlotFilter
  717. -- setup tables
  718. buyOfferTable = itemOffersPanel:recursiveGetChildById('buyingTable')
  719. sellOfferTable = itemOffersPanel:recursiveGetChildById('sellingTable')
  720. detailsTable = itemDetailsPanel:recursiveGetChildById('detailsTable')
  721. buyStatsTable = itemStatsPanel:recursiveGetChildById('buyStatsTable')
  722. sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable')
  723. buyOfferTable.onSelectionChange = onSelectBuyOffer
  724. sellOfferTable.onSelectionChange = onSelectSellOffer
  725. -- setup my offers
  726. buyMyOfferTable = currentOffersPanel:recursiveGetChildById('myBuyingTable')
  727. sellMyOfferTable = currentOffersPanel:recursiveGetChildById('mySellingTable')
  728. buyMyOfferTable.onSelectionChange = onSelectMyBuyOffer
  729. sellMyOfferTable.onSelectionChange = onSelectMySellOffer
  730. buyCancelButton = currentOffersPanel:getChildById('buyCancelButton')
  731. buyCancelButton.onClick = function() cancelMyOffer(MarketAction.Buy) end
  732. sellCancelButton = currentOffersPanel:getChildById('sellCancelButton')
  733. sellCancelButton.onClick = function() cancelMyOffer(MarketAction.Sell) end
  734. buyStatsTable:setColumnWidth({120, 270})
  735. sellStatsTable:setColumnWidth({120, 270})
  736. detailsTable:setColumnWidth({80, 330})
  737. buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
  738. sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
  739. buyMyOfferTable:setSorting(3, TABLE_SORTING_DESC)
  740. sellMyOfferTable:setSorting(3, TABLE_SORTING_DESC)
  741. end
  742. function init()
  743. g_ui.importStyle('market')
  744. g_ui.importStyle('ui/general/markettabs')
  745. g_ui.importStyle('ui/general/marketbuttons')
  746. g_ui.importStyle('ui/general/marketcombobox')
  747. g_ui.importStyle('ui/general/amountwindow')
  748. offerExhaust[MarketAction.Sell] = 10
  749. offerExhaust[MarketAction.Buy] = 20
  750. registerMessageMode(MessageModes.Market, onMarketMessage)
  751. protocol.initProtocol()
  752. connect(g_game, { onGameEnd = Market.reset })
  753. connect(g_game, { onGameEnd = Market.close })
  754. marketWindow = g_ui.createWidget('MarketWindow', rootWidget)
  755. marketWindow:hide()
  756. initInterface() -- build interface
  757. end
  758. function terminate()
  759. Market.close()
  760. unregisterMessageMode(MessageModes.Market, onMarketMessage)
  761. protocol.terminateProtocol()
  762. disconnect(g_game, { onGameEnd = Market.reset })
  763. disconnect(g_game, { onGameEnd = Market.close })
  764. destroyAmountWindow()
  765. marketWindow:destroy()
  766. Market = nil
  767. end
  768. function Market.reset()
  769. balanceLabel:setColor('#bbbbbb')
  770. categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First))
  771. searchEdit:setText('')
  772. clearFilters()
  773. clearMyOffers()
  774. if not table.empty(information) then
  775. Market.updateCurrentItems()
  776. end
  777. end
  778. function Market.displayMessage(message)
  779. if marketWindow:isHidden() then return end
  780. local infoBox = displayInfoBox(tr('Market Error'), message)
  781. infoBox:lock()
  782. end
  783. function Market.clearSelectedItem()
  784. if Market.isItemSelected() then
  785. Market.resetCreateOffer(true)
  786. offerTypeList:clearOptions()
  787. offerTypeList:setText('Please Select')
  788. offerTypeList:setEnabled(false)
  789. clearOffers()
  790. radioItemSet:selectWidget(nil)
  791. nameLabel:setText('No item selected.')
  792. selectedItem:setItem(nil)
  793. selectedItem.item = nil
  794. selectedItem.ref:setChecked(false)
  795. selectedItem.ref = nil
  796. detailsTable:clearData()
  797. buyStatsTable:clearData()
  798. sellStatsTable:clearData()
  799. Market.enableCreateOffer(false)
  800. end
  801. end
  802. function Market.isItemSelected()
  803. return selectedItem and selectedItem.item
  804. end
  805. function Market.isOfferSelected(type)
  806. return selectedOffer[type] and not selectedOffer[type]:isNull()
  807. end
  808. function Market.getDepotCount(itemId)
  809. return information.depotItems[itemId] or 0
  810. end
  811. function Market.enableCreateOffer(enable)
  812. offerTypeList:setEnabled(enable)
  813. totalPriceEdit:setEnabled(enable)
  814. piecePriceEdit:setEnabled(enable)
  815. amountEdit:setEnabled(enable)
  816. anonymous:setEnabled(enable)
  817. createOfferButton:setEnabled(enable)
  818. local prevAmountButton = marketOffersPanel:recursiveGetChildById('prevAmountButton')
  819. local nextAmountButton = marketOffersPanel:recursiveGetChildById('nextAmountButton')
  820. prevAmountButton:setEnabled(enable)
  821. nextAmountButton:setEnabled(enable)
  822. end
  823. function Market.close(notify)
  824. if notify == nil then notify = true end
  825. if not marketWindow:isHidden() then
  826. marketWindow:hide()
  827. marketWindow:unlock()
  828. modules.game_interface.getRootPanel():focus()
  829. Market.clearSelectedItem()
  830. Market.reset()
  831. if notify then
  832. MarketProtocol.sendMarketLeave()
  833. end
  834. end
  835. end
  836. function Market.incrementAmount()
  837. amountEdit:setValue(amountEdit:getValue() + 1)
  838. end
  839. function Market.decrementAmount()
  840. amountEdit:setValue(amountEdit:getValue() - 1)
  841. end
  842. function Market.updateCurrentItems()
  843. local id = getMarketCategoryId(categoryList:getCurrentOption().text)
  844. if id == MarketCategory.MetaWeapons then
  845. id = getMarketCategoryId(subCategoryList:getCurrentOption().text)
  846. end
  847. Market.loadMarketItems(id)
  848. end
  849. function Market.resetCreateOffer(resetFee)
  850. piecePriceEdit:setValue(1)
  851. totalPriceEdit:setValue(1)
  852. amountEdit:setValue(1)
  853. refreshTypeList()
  854. if resetFee then
  855. clearFee()
  856. else
  857. updateFee(0, 0)
  858. end
  859. end
  860. function Market.refreshItemsWidget(selectItem)
  861. local selectItem = selectItem or 0
  862. itemsPanel = browsePanel:recursiveGetChildById('itemsPanel')
  863. local layout = itemsPanel:getLayout()
  864. layout:disableUpdates()
  865. Market.clearSelectedItem()
  866. itemsPanel:destroyChildren()
  867. if radioItemSet then
  868. radioItemSet:destroy()
  869. end
  870. radioItemSet = UIRadioGroup.create()
  871. local select = nil
  872. for i = 1, #currentItems do
  873. local item = currentItems[i]
  874. local itemBox = g_ui.createWidget('MarketItemBox', itemsPanel)
  875. itemBox.onCheckChange = Market.onItemBoxChecked
  876. itemBox.item = item
  877. if selectItem > 0 and item.marketData.tradeAs == selectItem then
  878. select = itemBox
  879. selectItem = 0
  880. end
  881. local itemWidget = itemBox:getChildById('item')
  882. itemWidget:setItem(item.displayItem)
  883. local amount = Market.getDepotCount(item.marketData.tradeAs)
  884. if amount > 0 then
  885. itemWidget:setText(amount)
  886. itemBox:setTooltip('You have '.. amount ..' in your depot.')
  887. end
  888. radioItemSet:addWidget(itemBox)
  889. end
  890. if select then
  891. radioItemSet:selectWidget(select, false)
  892. end
  893. layout:enableUpdates()
  894. layout:update()
  895. end
  896. function Market.refreshOffers()
  897. if Market.isItemSelected() then
  898. Market.onItemBoxChecked(selectedItem.ref)
  899. else
  900. Market.refreshMyOffers()
  901. end
  902. end
  903. function Market.refreshMyOffers()
  904. clearMyOffers()
  905. MarketProtocol.sendMarketBrowseMyOffers()
  906. end
  907. function Market.loadMarketItems(category)
  908. clearItems()
  909. -- check search filter
  910. local searchFilter = searchEdit:getText()
  911. if searchFilter and searchFilter:len() > 2 then
  912. if filterButtons[MarketFilters.SearchAll]:isChecked() then
  913. category = MarketCategory.All
  914. end
  915. end
  916. if category == MarketCategory.All then
  917. -- loop all categories
  918. for category = MarketCategory.First, MarketCategory.Last do
  919. for i = 1, #marketItems[category] do
  920. local item = marketItems[category][i]
  921. if isItemValid(item, category, searchFilter) then
  922. table.insert(currentItems, item)
  923. end
  924. end
  925. end
  926. else
  927. -- loop specific category
  928. for i = 1, #marketItems[category] do
  929. local item = marketItems[category][i]
  930. if isItemValid(item, category, searchFilter) then
  931. table.insert(currentItems, item)
  932. end
  933. end
  934. end
  935. Market.refreshItemsWidget()
  936. end
  937. function Market.createNewOffer()
  938. local type = offerTypeList:getCurrentOption().text
  939. if type == 'Sell' then
  940. type = MarketAction.Sell
  941. else
  942. type = MarketAction.Buy
  943. end
  944. if not Market.isItemSelected() then
  945. return
  946. end
  947. local spriteId = selectedItem.item.marketData.tradeAs
  948. local piecePrice = piecePriceEdit:getValue()
  949. local amount = amountEdit:getValue()
  950. local anonymous = anonymous:isChecked() and 1 or 0
  951. -- error checking
  952. local errorMsg = ''
  953. if type == MarketAction.Buy then
  954. if information.balance < ((piecePrice * amount) + fee) then
  955. errorMsg = errorMsg..'Not enough balance to create this offer.\n'
  956. end
  957. elseif type == MarketAction.Sell then
  958. if information.balance < fee then
  959. errorMsg = errorMsg..'Not enough balance to create this offer.\n'
  960. end
  961. if Market.getDepotCount(spriteId) < amount then
  962. errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n'
  963. end
  964. end
  965. if piecePrice > piecePriceEdit.maximum then
  966. errorMsg = errorMsg..'Price is too high.\n'
  967. elseif piecePrice < piecePriceEdit.minimum then
  968. errorMsg = errorMsg..'Price is too low.\n'
  969. end
  970. if amount > amountEdit.maximum then
  971. errorMsg = errorMsg..'Amount is too high.\n'
  972. elseif amount < amountEdit.minimum then
  973. errorMsg = errorMsg..'Amount is too low.\n'
  974. end
  975. if amount * piecePrice > MarketMaxPrice then
  976. errorMsg = errorMsg..'Total price is too high.\n'
  977. end
  978. if information.totalOffers >= MarketMaxOffers then
  979. errorMsg = errorMsg..'You cannot create more offers.\n'
  980. end
  981. local timeCheck = os.time() - lastCreatedOffer
  982. if timeCheck < offerExhaust[type] then
  983. local waitTime = math.ceil(offerExhaust[type] - timeCheck)
  984. errorMsg = errorMsg..'You must wait '.. waitTime ..' seconds before creating a new offer.\n'
  985. end
  986. if errorMsg ~= '' then
  987. Market.displayMessage(errorMsg)
  988. return
  989. end
  990. MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, piecePrice, anonymous)
  991. lastCreatedOffer = os.time()
  992. Market.resetCreateOffer()
  993. end
  994. function Market.acceptMarketOffer(amount, timestamp, counter)
  995. if timestamp > 0 and amount > 0 then
  996. MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount)
  997. Market.refreshOffers()
  998. end
  999. end
  1000. function Market.onItemBoxChecked(widget)
  1001. if widget:isChecked() then
  1002. updateSelectedItem(widget)
  1003. end
  1004. end
  1005. -- protocol callback functions
  1006. function Market.onMarketEnter(depotItems, offers, balance, vocation)
  1007. if not loaded then
  1008. initMarketItems()
  1009. loaded = true
  1010. end
  1011. updateBalance(balance)
  1012. averagePrice = 0
  1013. information.totalOffers = offers
  1014. local player = g_game.getLocalPlayer()
  1015. if player then
  1016. information.player = player
  1017. end
  1018. if vocation == -1 then
  1019. if player then
  1020. information.vocation = player:getVocation()
  1021. end
  1022. else
  1023. -- vocation must be compatible with < 950
  1024. information.vocation = vocation
  1025. end
  1026. -- set list of depot items
  1027. information.depotItems = depotItems
  1028. -- update the items widget to match depot items
  1029. if Market.isItemSelected() then
  1030. local spriteId = selectedItem.item.marketData.tradeAs
  1031. MarketProtocol.silent(true) -- disable protocol messages
  1032. Market.refreshItemsWidget(spriteId)
  1033. MarketProtocol.silent(false) -- enable protocol messages
  1034. else
  1035. Market.refreshItemsWidget()
  1036. end
  1037. if table.empty(currentItems) then
  1038. Market.loadMarketItems(MarketCategory.First)
  1039. end
  1040. if g_game.isOnline() then
  1041. marketWindow:lock()
  1042. marketWindow:show()
  1043. end
  1044. end
  1045. function Market.onMarketLeave()
  1046. Market.close(false)
  1047. end
  1048. function Market.onMarketDetail(itemId, descriptions, purchaseStats, saleStats)
  1049. updateDetails(itemId, descriptions, purchaseStats, saleStats)
  1050. end
  1051. function Market.onMarketBrowse(offers)
  1052. updateOffers(offers)
  1053. end