1BUY = 1
2SELL = 2
3CURRENCY = 'gold'
4CURRENCY_DECIMAL = false
5WEIGHT_UNIT = 'oz'
6LAST_INVENTORY = 10
7
8npcWindow = nil
9itemsPanel = nil
10radioTabs = nil
11radioItems = nil
12searchText = nil
13setupPanel = nil
14quantity = nil
15quantityScroll = nil
16idLabel = nil
17nameLabel = nil
18priceLabel = nil
19moneyLabel = nil
20weightDesc = nil
21weightLabel = nil
22capacityDesc = nil
23capacityLabel = nil
24tradeButton = nil
25buyTab = nil
26sellTab = nil
27initialized = false
28
29showWeight = true
30buyWithBackpack = nil
31ignoreCapacity = nil
32ignoreEquipped = nil
33showAllItems = nil
34sellAllButton = nil
35sellAllWithDelayButton = nil
36playerFreeCapacity = 0
37playerMoney = 0
38tradeItems = {}
39playerItems = {}
40selectedItem = nil
41
42cancelNextRelease = nil
43
44sellAllWithDelayEvent = nil
45
46function init()
47 npcWindow = g_ui.displayUI('npctrade')
48 npcWindow:setVisible(false)
49
50 itemsPanel = npcWindow:recursiveGetChildById('itemsPanel')
51 searchText = npcWindow:recursiveGetChildById('searchText')
52
53 setupPanel = npcWindow:recursiveGetChildById('setupPanel')
54 quantityScroll = setupPanel:getChildById('quantityScroll')
55 idLabel = setupPanel:getChildById('id')
56 nameLabel = setupPanel:getChildById('name')
57 priceLabel = setupPanel:getChildById('price')
58 moneyLabel = setupPanel:getChildById('money')
59 weightDesc = setupPanel:getChildById('weightDesc')
60 weightLabel = setupPanel:getChildById('weight')
61 capacityDesc = setupPanel:getChildById('capacityDesc')
62 capacityLabel = setupPanel:getChildById('capacity')
63 tradeButton = npcWindow:recursiveGetChildById('tradeButton')
64
65 buyWithBackpack = npcWindow:recursiveGetChildById('buyWithBackpack')
66 ignoreCapacity = npcWindow:recursiveGetChildById('ignoreCapacity')
67 ignoreEquipped = npcWindow:recursiveGetChildById('ignoreEquipped')
68 showAllItems = npcWindow:recursiveGetChildById('showAllItems')
69 sellAllButton = npcWindow:recursiveGetChildById('sellAllButton')
70 sellAllWithDelayButton = npcWindow:recursiveGetChildById('sellAllWithDelayButton')
71 buyTab = npcWindow:getChildById('buyTab')
72 sellTab = npcWindow:getChildById('sellTab')
73
74 radioTabs = UIRadioGroup.create()
75 radioTabs:addWidget(buyTab)
76 radioTabs:addWidget(sellTab)
77 radioTabs:selectWidget(buyTab)
78 radioTabs.onSelectionChange = onTradeTypeChange
79
80 cancelNextRelease = false
81
82 if g_game.isOnline() then
83 playerFreeCapacity = g_game.getLocalPlayer():getFreeCapacity()
84 end
85
86 connect(g_game, { onGameEnd = hide,
87 onOpenNpcTrade = onOpenNpcTrade,
88 onCloseNpcTrade = onCloseNpcTrade,
89 onPlayerGoods = onPlayerGoods } )
90
91 connect(LocalPlayer, { onFreeCapacityChange = onFreeCapacityChange,
92 onInventoryChange = onInventoryChange } )
93
94 initialized = true
95end
96
97function terminate()
98 initialized = false
99 npcWindow:destroy()
100 removeEvent(sellAllWithDelayEvent)
101
102 disconnect(g_game, { onGameEnd = hide,
103 onOpenNpcTrade = onOpenNpcTrade,
104 onCloseNpcTrade = onCloseNpcTrade,
105 onPlayerGoods = onPlayerGoods } )
106
107 disconnect(LocalPlayer, { onFreeCapacityChange = onFreeCapacityChange,
108 onInventoryChange = onInventoryChange } )
109end
110
111function show()
112 if g_game.isOnline() then
113 if #tradeItems[BUY] > 0 then
114 radioTabs:selectWidget(buyTab)
115 else
116 radioTabs:selectWidget(sellTab)
117 end
118
119 npcWindow:show()
120 npcWindow:raise()
121 npcWindow:focus()
122 end
123end
124
125function hide()
126 removeEvent(sellAllWithDelayEvent)
127
128 npcWindow:hide()
129
130 local layout = itemsPanel:getLayout()
131 layout:disableUpdates()
132
133 clearSelectedItem()
134
135 searchText:clearText()
136 setupPanel:disable()
137 itemsPanel:destroyChildren()
138
139 if radioItems then
140 radioItems:destroy()
141 radioItems = nil
142 end
143
144 layout:enableUpdates()
145 layout:update()
146end
147
148function onItemBoxChecked(widget)
149 if widget:isChecked() then
150 local item = widget.item
151 selectedItem = item
152 refreshItem(item)
153 tradeButton:enable()
154
155 if getCurrentTradeType() == SELL then
156 quantityScroll:setValue(quantityScroll:getMaximum())
157 end
158 end
159end
160
161function onQuantityValueChange(quantity)
162 if selectedItem then
163 weightLabel:setText(string.format('%.2f', selectedItem.weight*quantity) .. ' ' .. WEIGHT_UNIT)
164 priceLabel:setText(formatCurrency(getItemPrice(selectedItem)))
165 end
166end
167
168function onTradeTypeChange(radioTabs, selected, deselected)
169 tradeButton:setText(selected:getText())
170 selected:setOn(true)
171 deselected:setOn(false)
172
173 local currentTradeType = getCurrentTradeType()
174 buyWithBackpack:setVisible(currentTradeType == BUY)
175 ignoreCapacity:setVisible(currentTradeType == BUY)
176 ignoreEquipped:setVisible(currentTradeType == SELL)
177 showAllItems:setVisible(currentTradeType == SELL)
178 sellAllButton:setVisible(currentTradeType == SELL)
179 sellAllWithDelayButton:setVisible(currentTradeType == SELL)
180
181 refreshTradeItems()
182 refreshPlayerGoods()
183end
184
185function onTradeClick()
186 removeEvent(sellAllWithDelayEvent)
187 if getCurrentTradeType() == BUY then
188 g_game.buyItem(selectedItem.ptr, quantityScroll:getValue(), ignoreCapacity:isChecked(), buyWithBackpack:isChecked())
189 else
190 g_game.sellItem(selectedItem.ptr, quantityScroll:getValue(), ignoreEquipped:isChecked())
191 end
192end
193
194function onSearchTextChange()
195 refreshPlayerGoods()
196end
197
198function itemPopup(self, mousePosition, mouseButton)
199 if cancelNextRelease then
200 cancelNextRelease = false
201 return false
202 end
203
204 if mouseButton == MouseRightButton then
205 local menu = g_ui.createWidget('PopupMenu')
206 menu:setGameMenu(true)
207 menu:addOption(tr('Look'), function() return g_game.inspectNpcTrade(self:getItem()) end)
208 menu:display(mousePosition)
209 return true
210 elseif ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton)
211 or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then
212 cancelNextRelease = true
213 g_game.inspectNpcTrade(self:getItem())
214 return true
215 end
216 return false
217end
218
219function onBuyWithBackpackChange()
220 if selectedItem then
221 refreshItem(selectedItem)
222 end
223end
224
225function onIgnoreCapacityChange()
226 refreshPlayerGoods()
227end
228
229function onIgnoreEquippedChange()
230 refreshPlayerGoods()
231end
232
233function onShowAllItemsChange()
234 refreshPlayerGoods()
235end
236
237function setCurrency(currency, decimal)
238 CURRENCY = currency
239 CURRENCY_DECIMAL = decimal
240end
241
242function setShowWeight(state)
243 showWeight = state
244 weightDesc:setVisible(state)
245 weightLabel:setVisible(state)
246end
247
248function setShowYourCapacity(state)
249 capacityDesc:setVisible(state)
250 capacityLabel:setVisible(state)
251 ignoreCapacity:setVisible(state)
252end
253
254function clearSelectedItem()
255 idLabel:clearText()
256 nameLabel:clearText()
257 weightLabel:clearText()
258 priceLabel:clearText()
259 tradeButton:disable()
260 quantityScroll:setMinimum(0)
261 quantityScroll:setMaximum(0)
262 if selectedItem then
263 radioItems:selectWidget(nil)
264 selectedItem = nil
265 end
266end
267
268function getCurrentTradeType()
269 if tradeButton:getText() == tr('Buy') then
270 return BUY
271 else
272 return SELL
273 end
274end
275
276function getItemPrice(item, single)
277 local amount = 1
278 local single = single or false
279 if not single then
280 amount = quantityScroll:getValue()
281 end
282 if getCurrentTradeType() == BUY then
283 if buyWithBackpack:isChecked() then
284 if item.ptr:isStackable() then
285 return item.price*amount + 20
286 else
287 return item.price*amount + math.ceil(amount/20)*20
288 end
289 end
290 end
291 return item.price*amount
292end
293
294function getSellQuantity(item)
295 if not item or not playerItems[item:getId()] then return 0 end
296 local removeAmount = 0
297 if ignoreEquipped:isChecked() then
298 local localPlayer = g_game.getLocalPlayer()
299 for i=1,LAST_INVENTORY do
300 local inventoryItem = localPlayer:getInventoryItem(i)
301 if inventoryItem and inventoryItem:getId() == item:getId() then
302 removeAmount = removeAmount + inventoryItem:getCount()
303 end
304 end
305 end
306 return playerItems[item:getId()] - removeAmount
307end
308
309function canTradeItem(item)
310 if getCurrentTradeType() == BUY then
311 return (ignoreCapacity:isChecked() or (not ignoreCapacity:isChecked() and playerFreeCapacity >= item.weight)) and playerMoney >= getItemPrice(item, true)
312 else
313 return getSellQuantity(item.ptr) > 0
314 end
315end
316
317function refreshItem(item)
318 idLabel:setText(item.ptr:getId())
319 nameLabel:setText(item.name)
320 weightLabel:setText(string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT)
321 priceLabel:setText(formatCurrency(getItemPrice(item)))
322
323 if getCurrentTradeType() == BUY then
324 local capacityMaxCount = math.floor(playerFreeCapacity / item.weight)
325 if ignoreCapacity:isChecked() then
326 capacityMaxCount = 65535
327 end
328 local priceMaxCount = math.floor(playerMoney / getItemPrice(item, true))
329 local finalCount = math.max(0, math.min(getMaxAmount(), math.min(priceMaxCount, capacityMaxCount)))
330 quantityScroll:setMinimum(1)
331 quantityScroll:setMaximum(finalCount)
332 else
333 quantityScroll:setMinimum(1)
334 quantityScroll:setMaximum(math.max(0, math.min(getMaxAmount(), getSellQuantity(item.ptr))))
335 end
336
337 setupPanel:enable()
338end
339
340function refreshTradeItems()
341 local layout = itemsPanel:getLayout()
342 layout:disableUpdates()
343
344 clearSelectedItem()
345
346 searchText:clearText()
347 setupPanel:disable()
348 itemsPanel:destroyChildren()
349
350 if radioItems then
351 radioItems:destroy()
352 end
353 radioItems = UIRadioGroup.create()
354
355 local currentTradeItems = tradeItems[getCurrentTradeType()]
356 for key,item in pairs(currentTradeItems) do
357 local itemBox = g_ui.createWidget('NPCItemBox', itemsPanel)
358 itemBox.item = item
359
360 local text = ''
361 local name = item.name
362 text = text .. name
363 if showWeight then
364 local weight = string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT
365 text = text .. '\n' .. weight
366 end
367 local price = formatCurrency(item.price)
368 text = text .. '\n' .. price
369 itemBox:setText(text)
370
371 local itemWidget = itemBox:getChildById('item')
372 itemWidget:setItem(item.ptr)
373 itemWidget.onMouseRelease = itemPopup
374
375 radioItems:addWidget(itemBox)
376 end
377
378 layout:enableUpdates()
379 layout:update()
380end
381
382function refreshPlayerGoods()
383 if not initialized then return end
384
385 checkSellAllTooltip()
386
387 moneyLabel:setText(formatCurrency(playerMoney))
388 capacityLabel:setText(string.format('%.2f', playerFreeCapacity) .. ' ' .. WEIGHT_UNIT)
389
390 local currentTradeType = getCurrentTradeType()
391 local searchFilter = searchText:getText():lower()
392 local foundSelectedItem = false
393
394 local items = itemsPanel:getChildCount()
395 for i=1,items do
396 local itemWidget = itemsPanel:getChildByIndex(i)
397 local item = itemWidget.item
398
399 local canTrade = canTradeItem(item)
400 itemWidget:setOn(canTrade)
401 itemWidget:setEnabled(canTrade)
402
403 local searchCondition = (searchFilter == '') or (searchFilter ~= '' and string.find(item.name:lower(), searchFilter) ~= nil)
404 local showAllItemsCondition = (currentTradeType == BUY) or (showAllItems:isChecked()) or (currentTradeType == SELL and not showAllItems:isChecked() and canTrade)
405 itemWidget:setVisible(searchCondition and showAllItemsCondition)
406
407 if selectedItem == item and itemWidget:isEnabled() and itemWidget:isVisible() then
408 foundSelectedItem = true
409 end
410 end
411
412 if not foundSelectedItem then
413 clearSelectedItem()
414 end
415
416 if selectedItem then
417 refreshItem(selectedItem)
418 end
419end
420
421function onOpenNpcTrade(items)
422 tradeItems[BUY] = {}
423 tradeItems[SELL] = {}
424 for key,item in pairs(items) do
425 if item[4] > 0 then
426 local newItem = {}
427 newItem.ptr = item[1]
428 newItem.name = item[2]
429 newItem.weight = item[3] / 100
430 newItem.price = item[4]
431 table.insert(tradeItems[BUY], newItem)
432 end
433
434 if item[5] > 0 then
435 local newItem = {}
436 newItem.ptr = item[1]
437 newItem.name = item[2]
438 newItem.weight = item[3] / 100
439 newItem.price = item[5]
440 table.insert(tradeItems[SELL], newItem)
441 end
442 end
443
444 refreshTradeItems()
445 addEvent(show) -- player goods has not been parsed yet
446end
447
448function closeNpcTrade()
449 g_game.closeNpcTrade()
450 addEvent(hide)
451end
452
453function onCloseNpcTrade()
454 addEvent(hide)
455end
456
457function onPlayerGoods(money, items)
458 playerMoney = money
459
460 playerItems = {}
461 for key,item in pairs(items) do
462 local id = item[1]:getId()
463 if not playerItems[id] then
464 playerItems[id] = item[2]
465 else
466 playerItems[id] = playerItems[id] + item[2]
467 end
468 end
469
470 refreshPlayerGoods()
471end
472
473function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
474 playerFreeCapacity = freeCapacity
475
476 if npcWindow:isVisible() then
477 refreshPlayerGoods()
478 end
479end
480
481function onInventoryChange(inventory, item, oldItem)
482 refreshPlayerGoods()
483end
484
485function getTradeItemData(id, type)
486 if table.empty(tradeItems[type]) then
487 return false
488 end
489
490 if type then
491 for key,item in pairs(tradeItems[type]) do
492 if item.ptr and item.ptr:getId() == id then
493 return item
494 end
495 end
496 else
497 for _,items in pairs(tradeItems) do
498 for key,item in pairs(items) do
499 if item.ptr and item.ptr:getId() == id then
500 return item
501 end
502 end
503 end
504 end
505 return false
506end
507
508function checkSellAllTooltip()
509 sellAllButton:setEnabled(true)
510 sellAllButton:removeTooltip()
511 sellAllWithDelayButton:setEnabled(true)
512 sellAllWithDelayButton:removeTooltip()
513
514 local total = 0
515 local info = ''
516 local first = true
517
518 for key, amount in pairs(playerItems) do
519 local data = getTradeItemData(key, SELL)
520 if data then
521 amount = getSellQuantity(data.ptr)
522 if amount > 0 then
523 if data and amount > 0 then
524 info = info..(not first and "\n" or "")..
525 amount.." "..
526 data.name.." ("..
527 data.price*amount.." gold)"
528
529 total = total+(data.price*amount)
530 if first then first = false end
531 end
532 end
533 end
534 end
535 if info ~= '' then
536 info = info.."\nTotal: "..total.." gold"
537 sellAllButton:setTooltip(info)
538 sellAllWithDelayButton:setTooltip(info)
539 else
540 sellAllButton:setEnabled(false)
541 sellAllWithDelayButton:setEnabled(false)
542 end
543end
544
545function formatCurrency(amount)
546 if CURRENCY_DECIMAL then
547 return string.format("%.02f", amount/100.0) .. ' ' .. CURRENCY
548 else
549 return amount .. ' ' .. CURRENCY
550 end
551end
552
553function getMaxAmount()
554 if getCurrentTradeType() == SELL and g_game.getFeature(GameDoubleShopSellAmount) then
555 return 10000
556 end
557 return 100
558end
559
560function sellAll(delayed)
561 removeEvent(sellAllWithDelayEvent)
562 local queue = {}
563 for _,entry in ipairs(tradeItems[SELL]) do
564 local sellQuantity = getSellQuantity(entry.ptr)
565 while sellQuantity > 0 do
566 local maxAmount = math.min(sellQuantity, getMaxAmount())
567 if delayed then
568 g_game.sellItem(entry.ptr, maxAmount, ignoreEquipped:isChecked())
569 sellAllWithDelayEvent = scheduleEvent(function() sellAll(true) end, 1100)
570 return
571 end
572 table.insert(queue, {entry.ptr, maxAmount, ignoreEquipped:isChecked()})
573 sellQuantity = sellQuantity - maxAmount
574 end
575 end
576 for _, entry in ipairs(queue) do
577 g_game.sellItem(entry[1], entry[2], entry[3])
578 end
579end
580