From 9c038e2f39f2cb56e80dab5bf27c1227d267450c Mon Sep 17 00:00:00 2001 From: AndreFaramir Date: Sun, 8 Apr 2012 17:13:33 -0300 Subject: [PATCH] implement npc trade interface --- modules/game/game.otmod | 1 + modules/game_npctrade/callbacks.txt | 13 ++ modules/game_npctrade/npctrade.lua | 316 +++++++++++++++++++++++++++ modules/game_npctrade/npctrade.otmod | 15 ++ modules/game_npctrade/npctrade.otui | 237 ++++++++++++++++++++ src/otclient/luafunctions.cpp | 3 + 6 files changed, 585 insertions(+) create mode 100644 modules/game_npctrade/callbacks.txt create mode 100644 modules/game_npctrade/npctrade.lua create mode 100644 modules/game_npctrade/npctrade.otmod create mode 100644 modules/game_npctrade/npctrade.otui diff --git a/modules/game/game.otmod b/modules/game/game.otmod index 498b117b..e46bae4b 100644 --- a/modules/game/game.otmod +++ b/modules/game/game.otmod @@ -22,6 +22,7 @@ Module - game_battle - game_minimap - game_hotkeys + - game_npctrade @onLoad: | importStyle 'styles/items.otui' diff --git a/modules/game_npctrade/callbacks.txt b/modules/game_npctrade/callbacks.txt new file mode 100644 index 00000000..65d0bfed --- /dev/null +++ b/modules/game_npctrade/callbacks.txt @@ -0,0 +1,13 @@ +void processOpenNpcTrade(const std::vector>& items); + void processPlayerGoods(int money, const std::vector>& goods); + void processCloseNpcTrade(); + +g_lua.callGlobalField("g_game", "onOpenNpcTrade", items); + g_lua.callGlobalField("g_game", "onPlayerGoods", goods); + g_lua.callGlobalField("g_game", "onCloseNpcTrade"); + + // npc trade related + void inspectNpcTrade(const ItemPtr& item); + void buyItem(const ItemPtr& item, int amount, bool ignoreCapacity, bool buyWithBackpack); + void sellItem(const ItemPtr& item, int amount, bool ignoreEquipped); + void closeNpcTrade(); \ No newline at end of file diff --git a/modules/game_npctrade/npctrade.lua b/modules/game_npctrade/npctrade.lua new file mode 100644 index 00000000..e4327695 --- /dev/null +++ b/modules/game_npctrade/npctrade.lua @@ -0,0 +1,316 @@ +NPCTrade = {} + +local npcWindow +local itemsPanel +local radioTabs +local radioItems +local buyTab +local sellTab +local searchText +local setupPanel +local quantity +local quantityScroll +local nameLabel +local priceLabel +local moneyGoods +local moneyLabel +local weightLabel +local capacityLabel +local offerSelected +local setupButton +local cacheItems +local cacheGoods + +local buyWithBackpack = false +local ignoreCapacity = false +local ignoreEquipped = true + +-- public functions +function NPCTrade.init() + cacheItems = {} + cacheGoods = {} + + npcWindow = displayUI('npctrade.otui') + npcWindow:setVisible(false) + + itemsPanel = npcWindow:recursiveGetChildById('itemsPanel') + buyTab = npcWindow:getChildById('buyTab') + sellTab = npcWindow:getChildById('sellTab') + searchText = npcWindow:getChildById('searchText') + + setupPanel = npcWindow:recursiveGetChildById('setupPanel') + quantityLabel = setupPanel:getChildById('quantity') + quantityScroll = setupPanel:getChildById('quantityScroll') + nameLabel = setupPanel:getChildById('name') + priceLabel = setupPanel:getChildById('price') + moneyLabel = setupPanel:getChildById('money') + weightLabel = setupPanel:getChildById('weight') + capacityLabel = setupPanel:getChildById('capacity') + setupButton = setupPanel:getChildById('setupButton') + + radioTabs = RadioGroup.create() + radioTabs:addWidget(buyTab) + radioTabs:addWidget(sellTab) + radioTabs:selectWidget(buyTab) + + connect(g_game, { onOpenNpcTrade = NPCTrade.onOpenNpcTrade, + onPlayerGoods = NPCTrade.onPlayerGoods, + onCloseNpcTrade = NPCTrade.onCloseNpcTrade } ) +end + +function NPCTrade.terminate() + radioTabs:destroy() + radioTabs = nil + npcWindow:destroy() + npcWindow = nil + itemsPanel = nil + buyButton = nil + sellButton = nil + searchText = nil + + setupPanel = nil + quantityLabel = nil + quantityScroll = nil + nameLabel = nil + priceLabel = nil + moneyLabel = nil + weightLabel = nil + capacityLabel = nil + offerSelected = nil + setupButton = nil + + cacheItems = nil + cacheGoods = nil + + disconnect(g_game, { onOpenNpcTrade = NPCTrade.onOpenNpcTrade, + onPlayerGoods = NPCTrade.onPlayerGoods, + onCloseNpcTrade = NPCTrade.onCloseNpcTrade } ) +end + +-- private functions +function NPCTrade.show() + if g_game.isOnline() then + npcWindow:show() + npcWindow:raise() + end +end + +function NPCTrade.hide() + npcWindow:hide() +end + +function NPCTrade.setList(widget, checked) + setupButton:setText(widget:getText()) + widget:setOn(checked) + NPCTrade.createItemsOnPanel() + + NPCTrade.resetSetup() + NPCTrade.refreshItemsPanel() +end + +function NPCTrade.resetSetup() + nameLabel:clearText() + weightLabel:clearText() + priceLabel:clearText() + searchText:clearText() + if offerSelected then + radioItems.selectedWidget:setChecked(false) + offerSelected = nil + end + setupPanel:disable() +end + +function NPCTrade.updateSetup() + if offerSelected then + if radioItems.selectedWidget:isEnabled() then + if setupButton:getText() == 'Buy' then + quantityScroll:setMaximum(math.max(0, math.min(100, math.floor(moneyGoods/NPCTrade.getOfferPrice(offerSelected))))) + else + quantityScroll:setMaximum(math.max(0, math.min(100, cacheGoods[offerSelected[1]:getId()]))) + end + else + NPCTrade.resetSetup() + end + end +end + +function NPCTrade.getOfferPrice(offer) + if setupButton:getText() == 'Buy' then + local price = offer[4] + if buyWithBackpack then + if offer[1]:isStackable() then + return price*quantityScroll:getValue() + 20; + else + return price*quantityScroll:getValue() + math.ceil(quantityScroll:getValue()/20)*20 + end + else + return price*quantityScroll:getValue() + end + else + return offer[5]*quantityScroll:getValue() + end +end + +function NPCTrade.setItem(widget) + offerSelected = widget.offer + + local offer = widget.offer + local price = NPCTrade.getOfferPrice(widget.offer) + local freeCapacity = g_game.getLocalPlayer():getFreeCapacity() + nameLabel:setText(offer[2]) + weightLabel:setText(string.format("%.2f", offer[3]/100) .. " oz") + priceLabel:setText(price .. " gold") + capacityLabel:setText(string.format("%.2f", freeCapacity) .. " oz") + + quantityLabel:setText(1) + quantityScroll:setValue(1) + NPCTrade.updateSetup() + + setupPanel:enable() +end + +function NPCTrade.setQuantity(quantity) + if quantityLabel and offerSelected then + local price = NPCTrade.getOfferPrice(offerSelected) + quantityLabel:setText(quantity) + weightLabel:setText(string.format("%.2f", offerSelected[3]*quantity/100) .. " oz") + priceLabel:setText(price .. " gold") + end +end + +function NPCTrade.setupButton() + if setupButton:getText() == 'Buy' then + g_game.buyItem(offerSelected[1], quantityScroll:getValue(), buyWithBackpack, ignoreCapacity) + else + g_game.sellItem(offerSelected[1], quantityScroll:getValue(), ignoreEquipped) + end +end + +function NPCTrade.onOpenNpcTrade(items) + -- items[item] = item + -- item[1] = ItemPtr + -- item[2] = name + -- item[3] = weight + -- item[4] = buyPrice + -- item[5] = sellPrice + + cacheItems = items + + NPCTrade.createItemsOnPanel() + + NPCTrade.show() +end + +function NPCTrade.swithBuyWithBackpack() + buyWithBackpack = not buyWithBackpack + if offerSelected then + priceLabel:setText(NPCTrade.getOfferPrice(offerSelected) .. " gold") + end + return true +end + +function NPCTrade.itemPopup(self, mousePosition, mouseButton) + if mouseButton == MouseRightButton then + local menu = createWidget('PopupMenu') + menu:addOption('Look', function() return g_game.inspectNpcTrade(self.offer[1]) end) + menu:addSeparator() + if setupButton:getText() == 'Buy' then + menu:addOption((buyWithBackpack and 'Buy no backpack' or 'Buy with backpack'), NPCTrade.swithBuyWithBackpack) + menu:addOption((ignoreCapacity and 'Consider capacity' or 'Ignore capacity'), function() ignoreCapacity = not ignoreCapacity return true end) + else + menu:addOption((ignoreEquipped and 'Consider equipped' or 'Ignore equipped'), function() ignoreEquipped = not ignoreEquipped return true end) + end + menu:display(mousePosition) + end +end + +function NPCTrade.createItemsOnPanel() + NPCTrade.resetSetup() + + offerSelected = nil + itemsPanel:destroyChildren() + + radioItems = RadioGroup.create() + + for i, v in pairs(cacheItems) do + local price = NPCTrade.getOfferPrice(v) + if price >= 0 then + local itemBox = createWidget('NPCItemBox', itemsPanel) + itemBox.offer = v + itemBox:getChildById('item'):setItem(v[1]) + itemBox:getChildById('nameLabel'):setText(v[2]) + itemBox:getChildById('weightLabel'):setText(string.format("%.2f", v[3]/100) .. " oz") + itemBox:getChildById('priceLabel'):setText(price .. " gold") + + itemBox.onMouseRelease = NPCTrade.itemPopup + itemBox:getChildById('item').onMouseRelease = function (self, mousePosition, mouseButton) NPCTrade.itemPopup(itemBox, mousePosition, mouseButton) end + + radioItems:addWidget(itemBox) + end + end +end + +function NPCTrade.searchFilter(filter) + local items = itemsPanel:getChildCount() + for i = 1, items do + local itemWidget = itemsPanel:getChildByIndex(i) + if filter ~= "" then + if string.find(itemWidget.offer[2], filter) then + itemWidget:show() + else + itemWidget:hide() + end + else + itemWidget:show() + end + end +end + +function NPCTrade.refreshItemsPanel() + if setupButton:getText() == 'Buy' then + local items = itemsPanel:getChildCount() + for i = 1, items do + local itemWidget = itemsPanel:getChildByIndex(i) + if moneyGoods < NPCTrade.getOfferPrice(itemWidget.offer) then + itemWidget:disable() + else + itemWidget:enable() + end + end + else + local items = itemsPanel:getChildCount() + for i = 1, items do + local itemWidget = itemsPanel:getChildByIndex(i) + if cacheGoods[itemWidget.offer[1]:getId()] then + itemWidget:enable() + else + itemWidget:disable() + end + end + end +end + +function NPCTrade.onPlayerGoods(money, goods) + moneyGoods = money + + moneyLabel:setText(money .. " gold") + local freeCapacity = g_game.getLocalPlayer():getFreeCapacity() + capacityLabel:setText(string.format("%.2f", freeCapacity) .. " oz") + + cacheGoods = {} + for i,v in pairs(goods) do + cacheGoods[v[1]:getId()] = v[2] + end + + NPCTrade.refreshItemsPanel() + NPCTrade.updateSetup() +end + +function NPCTrade.onCloseNpcTrade() + NPCTrade.hide() +end + +-- void inspectNpcTrade(const ItemPtr& item); +-- void buyItem(const ItemPtr& item, int amount, bool ignoreCapacity, bool buyWithBackpack); +-- void sellItem(const ItemPtr& item, int amount, bool ignoreEquipped); +-- void closeNpcTrade(); \ No newline at end of file diff --git a/modules/game_npctrade/npctrade.otmod b/modules/game_npctrade/npctrade.otmod new file mode 100644 index 00000000..adc158d3 --- /dev/null +++ b/modules/game_npctrade/npctrade.otmod @@ -0,0 +1,15 @@ +Module + name: game_npctrade + description: NPC trade interface + author: OTClient team + website: https://github.com/edubart/otclient + + dependencies: + - game + + @onLoad: | + dofile 'npctrade' + NPCTrade.init() + + @onUnload: | + NPCTrade.terminate() \ No newline at end of file diff --git a/modules/game_npctrade/npctrade.otui b/modules/game_npctrade/npctrade.otui new file mode 100644 index 00000000..f38d5665 --- /dev/null +++ b/modules/game_npctrade/npctrade.otui @@ -0,0 +1,237 @@ +NPCOfferLabel < Label + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-auto-resize: true + +NPCPanel < ScrollablePanel + image-source: /core_styles/styles/images/panel_flat.png + image-border: 1 + +NPCItemBox < UICheckBox + border-width: 1 + border-color: #000000 + @onCheckChange: NPCTrade.setItem(self) + + Item + id: item + phantom: true + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 2 + + Label + id: nameLabel + phantom: true + text: cavalo + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + text-auto-resize: true + margin-top: 5 + + Label + id: weightLabel + phantom: true + text: 8.00 oz + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + text-auto-resize: true + margin-top: 5 + + Label + id: priceLabel + phantom: true + text: 200 gold + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + text-auto-resize: true + margin-top: 5 + + $checked: + border-color: #FFFFFF + + $hover !checked: + border-color: #AAAAAA + +MainWindow + id: npcWindow + text: NPC Trade + size: 550 550 + + @onEscape: NPCTrade.hide() + + TabButton + id: buyTab + tooltip: List of items that you're able to buy + text: Buy + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: parent.top + margin-right: 5 + margin-top: 5 + @onCheckChange: NPCTrade.setList(self, self:isChecked()) + + TabButton + id: sellTab + tooltip: List of items that you're able to sell + text: Sell + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: parent.top + margin-left: 5 + margin-top: 5 + @onCheckChange: NPCTrade.setList(self, self:isChecked()) + + Label + id: searchLabel + text: Search: + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + TextEdit + id: searchText + width: 200 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + @onTextChange: NPCTrade.searchFilter(self:getText()) + + NPCPanel + height: 250 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + + NPCPanel + id: itemsPanel + height: 250 + anchors.left: parent.left + anchors.right: prev.left + anchors.top: parent.top + anchors.bottom: parent.bottom + vertical-scrollbar: itemsPanelListScrollBar + layout: + type: grid + cell-size: 160 90 + flow: true + auto-spacing: true + + FlatPanel + id: setupPanel + height: 150 + enabled: false + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + Label + id: quantityLabel + text: Quantity + anchors.left: parent.left + anchors.top: parent.top + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + NPCOfferLabel + id: quantity + + HorizontalScrollBar + id: quantityScroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + minimum: 1 + maximum: 100 + value: 1 + step: 1 + @onValueChange: NPCTrade.setQuantity(self:getValue()) + + Label + id: nameLabel + text: Name + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + NPCOfferLabel + id: name + + Label + id: priceLabel + text: Price + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + NPCOfferLabel + id: price + + Label + id: moneyLabel + text: Money + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + NPCOfferLabel + id: money + + Label + id: weightLabel + text: Weight + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + NPCOfferLabel + id: weight + + Label + id: capacityLabel + text: Capacity + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + margin-left: 2 + + NPCOfferLabel + id: capacity + + Button + id: setupButton + text: Buy + width: 64 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + @onClick: NPCTrade.setupButton() + + Button + text: Close + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: NPCTrade.hide() diff --git a/src/otclient/luafunctions.cpp b/src/otclient/luafunctions.cpp index ed6c63c9..9e4030f3 100644 --- a/src/otclient/luafunctions.cpp +++ b/src/otclient/luafunctions.cpp @@ -243,6 +243,9 @@ void OTClient::registerLuaFunctions() g_lua.registerClass(); g_lua.bindClassStaticFunction("create", &Item::create); g_lua.bindClassMemberFunction("getCount", &Item::getCount); + g_lua.bindClassMemberFunction("getId", &Item::getId); + g_lua.bindClassMemberFunction("isStackable", &Item::isStackable); + g_lua.registerClass(); g_lua.registerClass();