From ca314bd529cdeb8ab1a74884536889c609d32996 Mon Sep 17 00:00:00 2001 From: metabob Date: Mon, 15 Apr 2019 16:13:00 +0200 Subject: [PATCH 1/5] - game_market ui/ux improvements - added server opcode support 238 - added client opcode support 233 --- modules/client_locales/neededtranslations.lua | 2 + modules/game_market/market.lua | 106 +++++++++++++----- modules/game_market/marketprotocol.lua | 15 ++- .../ui/general/marketcombobox.otui | 2 +- modules/game_market/ui/marketoffers.otui | 22 +++- .../game_market/ui/marketoffers/browse.otui | 4 +- .../ui/marketoffers/itemoffers.otui | 4 + .../ui/myoffers/currentoffers.otui | 4 + modules/gamelib/protocol.lua | 2 + src/client/protocolcodes.h | 1 + 10 files changed, 122 insertions(+), 40 deletions(-) diff --git a/modules/client_locales/neededtranslations.lua b/modules/client_locales/neededtranslations.lua index 993e09e8b..f118d9742 100644 --- a/modules/client_locales/neededtranslations.lua +++ b/modules/client_locales/neededtranslations.lua @@ -284,6 +284,7 @@ neededTranslations = { "Position", "Premium", "Premium Account (%s) days left", + "Press %s button to update", "Price", "Primary", "Process", @@ -320,6 +321,7 @@ neededTranslations = { "Secondary", "Select", "Select all", + "Select an item to view the offers", "Select object", "Select Outfit", "Select your language", diff --git a/modules/game_market/market.lua b/modules/game_market/market.lua index b6e3936b2..1a4031a0c 100644 --- a/modules/game_market/market.lua +++ b/modules/game_market/market.lua @@ -28,10 +28,10 @@ offersTabBar = nil selectionTabBar = nil marketOffersPanel = nil -browsePanel = nil +BROWSE_PANEL = nil overviewPanel = nil itemOffersPanel = nil -itemDetailsPanel = nil +ITEM_DETAILS_PANEL = nil itemStatsPanel = nil myOffersPanel = nil currentOffersPanel = nil @@ -40,7 +40,6 @@ itemsPanel = nil selectedOffer = {} selectedMyOffer = {} -nameLabel = nil feeLabel = nil balanceLabel = nil totalPriceEdit = nil @@ -49,8 +48,10 @@ amountEdit = nil searchEdit = nil radioItemSet = nil selectedItem = nil +selectedItemLabel = nil +selectedItemTitleLabel = nil offerTypeList = nil -categoryList = nil +CATEGORY_LIST = nil subCategoryList = nil slotFilterList = nil createOfferButton = nil @@ -443,7 +444,9 @@ local function updateSelectedItem(widget) Market.resetCreateOffer() if Market.isItemSelected() then selectedItem:setItem(selectedItem.item.displayItem) - nameLabel:setText(selectedItem.item.marketData.name) + selectedItemLabel:setText('') + selectedItemTitleLabel:setText(selectedItem.item.marketData.name) + Market:hideOffersTableInstructions() clearOffers() Market.enableCreateOffer(true) -- update offer types @@ -726,8 +729,10 @@ local function initMarketItems() } -- add new market item + if marketItems[marketData.category] ~= nil then table.insert(marketItems[marketData.category], marketItem) itemSet[marketData.tradeAs] = true + end end end end @@ -741,13 +746,17 @@ local function initInterface() -- setup 'Market Offer' section tabs marketOffersPanel = g_ui.loadUI('ui/marketoffers') - mainTabBar:addTab(tr('Market Offers'), marketOffersPanel) + local mopTab = mainTabBar:addTab(tr('Market Offers'), marketOffersPanel) + mopTab.onClick = function() + mainTabBar:selectTab(mopTab) + Market.refreshOffers() + end selectionTabBar = marketOffersPanel:getChildById('leftTabBar') selectionTabBar:setContentWidget(marketOffersPanel:getChildById('leftTabContent')) - browsePanel = g_ui.loadUI('ui/marketoffers/browse') - selectionTabBar:addTab(tr('Browse'), browsePanel) + BROWSE_PANEL = g_ui.loadUI('ui/marketoffers/browse') + selectionTabBar:addTab(tr('Browse'), BROWSE_PANEL) -- Currently not used -- "Reserved for more functionality later" @@ -760,8 +769,8 @@ local function initInterface() itemStatsPanel = g_ui.loadUI('ui/marketoffers/itemstats') displaysTabBar:addTab(tr('Statistics'), itemStatsPanel) - itemDetailsPanel = g_ui.loadUI('ui/marketoffers/itemdetails') - displaysTabBar:addTab(tr('Details'), itemDetailsPanel) + ITEM_DETAILS_PANEL = g_ui.loadUI('ui/marketoffers/itemdetails') + displaysTabBar:addTab(tr('Details'), ITEM_DETAILS_PANEL) itemOffersPanel = g_ui.loadUI('ui/marketoffers/itemoffers') displaysTabBar:addTab(tr('Offers'), itemOffersPanel) @@ -769,7 +778,11 @@ local function initInterface() -- setup 'My Offer' section tabs myOffersPanel = g_ui.loadUI('ui/myoffers') - mainTabBar:addTab(tr('My Offers'), myOffersPanel) + local moTab = mainTabBar:addTab(tr('My Offers'), myOffersPanel) + moTab.onClick = function() + mainTabBar:selectTab(moTab) + Market.refreshMyOffers() + end offersTabBar = myOffersPanel:getChildById('offersTabBar') offersTabBar:setContentWidget(myOffersPanel:getChildById('offersTabContent')) @@ -790,8 +803,9 @@ local function initInterface() sellButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Sell, 'Sell') end -- setup selected item - nameLabel = marketOffersPanel:getChildById('nameLabel') selectedItem = marketOffersPanel:getChildById('selectedItem') + selectedItemLabel = selectedItem:getChildById('selectedItemLabel') + selectedItemTitleLabel = marketOffersPanel:getChildById('selectedItemTitleLabel') -- setup create new offer totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit') @@ -811,10 +825,10 @@ local function initInterface() Market.enableCreateOffer(false) -- setup filters - filterButtons[MarketFilters.Vocation] = browsePanel:getChildById('filterVocation') - filterButtons[MarketFilters.Level] = browsePanel:getChildById('filterLevel') - filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot') - filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll') + filterButtons[MarketFilters.Vocation] = BROWSE_PANEL:getChildById('filterVocation') + filterButtons[MarketFilters.Level] = BROWSE_PANEL:getChildById('filterLevel') + filterButtons[MarketFilters.Depot] = BROWSE_PANEL:getChildById('filterDepot') + filterButtons[MarketFilters.SearchAll] = BROWSE_PANEL:getChildById('filterSearchAll') -- set filter default values clearFilters() @@ -824,10 +838,10 @@ local function initInterface() filter.onCheckChange = Market.updateCurrentItems end - searchEdit = browsePanel:getChildById('searchEdit') - categoryList = browsePanel:getChildById('categoryComboBox') - subCategoryList = browsePanel:getChildById('subCategoryComboBox') - slotFilterList = browsePanel:getChildById('slotComboBox') + searchEdit = BROWSE_PANEL:getChildById('searchEdit') + CATEGORY_LIST = BROWSE_PANEL:getChildById('categoryComboBox') + subCategoryList = BROWSE_PANEL:getChildById('subCategoryComboBox') + slotFilterList = BROWSE_PANEL:getChildById('slotComboBox') slotFilterList:addOption(MarketSlotFilters[255]) slotFilterList:setEnabled(false) @@ -836,22 +850,22 @@ local function initInterface() if i >= MarketCategory.Ammunition and i <= MarketCategory.WandsRods then subCategoryList:addOption(getMarketCategoryName(i)) else - categoryList:addOption(getMarketCategoryName(i)) + CATEGORY_LIST:addOption(getMarketCategoryName(i)) end end - categoryList:addOption(getMarketCategoryName(255)) -- meta weapons - categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + CATEGORY_LIST:addOption(getMarketCategoryName(255)) -- meta weapons + CATEGORY_LIST:setCurrentOption(getMarketCategoryName(MarketCategory.First)) subCategoryList:setEnabled(false) -- hook item filters - categoryList.onOptionChange = onChangeCategory + CATEGORY_LIST.onOptionChange = onChangeCategory subCategoryList.onOptionChange = onChangeSubCategory slotFilterList.onOptionChange = onChangeSlotFilter -- setup tables buyOfferTable = itemOffersPanel:recursiveGetChildById('buyingTable') sellOfferTable = itemOffersPanel:recursiveGetChildById('sellingTable') - detailsTable = itemDetailsPanel:recursiveGetChildById('detailsTable') + detailsTable = ITEM_DETAILS_PANEL:recursiveGetChildById('detailsTable') buyStatsTable = itemStatsPanel:recursiveGetChildById('buyStatsTable') sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable') buyOfferTable.onSelectionChange = onSelectBuyOffer @@ -917,10 +931,37 @@ function terminate() Market = nil end +function Market.showMyOffersTableInstructions() + local instruction = tr('Press %s button to update', tr('Refresh Offers')) + if sellMyOfferTable then sellMyOfferTable:setText(instruction) end + if buyMyOfferTable then buyMyOfferTable:setText(instruction) end +end + +function Market.hideMyOffersTableInstructions() + if sellMyOfferTable then sellMyOfferTable:setText('') end + if buyMyOfferTable then buyMyOfferTable:setText('') end +end + +function Market.showOffersTableInstructions() + local instruction = tr('Select an item to view the offers') + if sellOfferTable then sellOfferTable:setText(instruction) end + if buyOfferTable then buyOfferTable:setText(instruction) end +end + +function Market.hideOffersTableInstructions() + if sellOfferTable then sellOfferTable:setText('') end + if buyOfferTable then buyOfferTable:setText('') end +end + function Market.reset() balanceLabel:setColor('#bbbbbb') - categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + CATEGORY_LIST:setCurrentOption(getMarketCategoryName(MarketCategory.First)) searchEdit:setText('') + + -- When uses closes market at this screen we need to show this instruction again when it gets opened, + -- since we cannot load offers for the user ourselves due to the bot protection. + Market:showMyOffersTableInstructions() + clearFilters() clearMyOffers() if not table.empty(information) then @@ -944,11 +985,13 @@ function Market.clearSelectedItem() clearOffers() radioItemSet:selectWidget(nil) - nameLabel:setText('No item selected.') + Market:showOffersTableInstructions() selectedItem:setItem(nil) selectedItem.item = nil selectedItem.ref:setChecked(false) selectedItem.ref = nil + selectedItemLabel:setText('?') + selectedItemTitleLabel:setText(tr('No item selected.')) detailsTable:clearData() buyStatsTable:clearData() @@ -1008,7 +1051,7 @@ function Market.decrementAmount() end function Market.updateCurrentItems() - local id = getMarketCategoryId(categoryList:getCurrentOption().text) + local id = getMarketCategoryId(CATEGORY_LIST:getCurrentOption().text) if id == MarketCategory.MetaWeapons then id = getMarketCategoryId(subCategoryList:getCurrentOption().text) end @@ -1030,7 +1073,7 @@ end function Market.refreshItemsWidget(selectItem) local selectItem = selectItem or 0 - itemsPanel = browsePanel:recursiveGetChildById('itemsPanel') + itemsPanel = BROWSE_PANEL:recursiveGetChildById('itemsPanel') local layout = itemsPanel:getLayout() layout:disableUpdates() @@ -1084,6 +1127,7 @@ function Market.refreshOffers() end function Market.refreshMyOffers() + Market:hideMyOffersTableInstructions() clearMyOffers() MarketProtocol.sendMarketBrowseMyOffers() end @@ -1265,3 +1309,7 @@ end function Market.onMarketBrowse(offers) updateOffers(offers) end + +function Market.onMarketResourceBalance(balance, money) + return +end \ No newline at end of file diff --git a/modules/game_market/marketprotocol.lua b/modules/game_market/marketprotocol.lua index d9ca25232..9cd907bd6 100644 --- a/modules/game_market/marketprotocol.lua +++ b/modules/game_market/marketprotocol.lua @@ -129,6 +129,17 @@ local function parseMarketBrowse(protocol, msg) return true end +local function parseMarketResourcesBalance(protocol, msg) + msg:getU8() + local balance = msg:getU64() -- bank + msg:getU8() + msg:getU8() + local money = msg:getU64() -- inventory + + signalcall(Market.onMarketResourceBalance, balance, money) + return true +end + -- public functions function initProtocol() connect(g_game, { onGameStart = MarketProtocol.registerProtocol, @@ -161,6 +172,7 @@ function MarketProtocol.registerProtocol() ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave) ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail) ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerSendResourceBalance, parseMarketResourcesBalance) end MarketProtocol.updateProtocol(g_game.getProtocolGame()) end @@ -171,6 +183,7 @@ function MarketProtocol.unregisterProtocol() ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave) ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail) ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerSendResourceBalance, parseMarketResourcesBalance) end MarketProtocol.updateProtocol(nil) end @@ -244,4 +257,4 @@ function MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount) else g_logger.error('MarketProtocol.sendMarketAcceptOffer does not support the current protocol.') end -end +end \ No newline at end of file diff --git a/modules/game_market/ui/general/marketcombobox.otui b/modules/game_market/ui/general/marketcombobox.otui index 5177073ca..4c9bbdade 100644 --- a/modules/game_market/ui/general/marketcombobox.otui +++ b/modules/game_market/ui/general/marketcombobox.otui @@ -15,4 +15,4 @@ MarketComboBoxPopupMenu < ComboBoxRoundedPopupMenu MarketComboBox < ComboBoxRounded font: verdana-11px-rounded size: 86 20 - text-offset: 3 2 + text-offset: 9 9 diff --git a/modules/game_market/ui/marketoffers.otui b/modules/game_market/ui/marketoffers.otui index f8e04f828..2709a6ce2 100644 --- a/modules/game_market/ui/marketoffers.otui +++ b/modules/game_market/ui/marketoffers.otui @@ -39,21 +39,31 @@ Panel size: 34 34 padding: 1 font: verdana-11px-rounded - border-color: white + border-color: #535353 + border-width: 1 anchors.top: rightTabBar.bottom anchors.left: rightTabContent.left margin-top: 6 margin-left: 6 + Label + id: selectedItemLabel + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: selectedItem.top + anchors.left: selectedItem.left + size: 34 34 + text-align: center + !text: '?' + Label - id: nameLabel + id: selectedItemTitleLabel !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 + anchors.top: selectedItem.top + anchors.left: selectedItem.right + margin-left: 6 Label id: createLabel diff --git a/modules/game_market/ui/marketoffers/browse.otui b/modules/game_market/ui/marketoffers/browse.otui index 90716979b..1b396f5eb 100644 --- a/modules/game_market/ui/marketoffers/browse.otui +++ b/modules/game_market/ui/marketoffers/browse.otui @@ -12,7 +12,7 @@ MarketItemBox < UICheckBox text-offset: 0 22 text-align: right anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter + anchors.left: parent.left margin: 1 $checked: @@ -105,9 +105,7 @@ Panel anchors.right: parent.right anchors.bottom: parent.bottom margin-top: 10 - margin-left: 3 margin-bottom: 30 - margin-right: 3 VerticalScrollBar id: itemsPanelListScrollBar diff --git a/modules/game_market/ui/marketoffers/itemoffers.otui b/modules/game_market/ui/marketoffers/itemoffers.otui index 8f3926021..82132671d 100644 --- a/modules/game_market/ui/marketoffers/itemoffers.otui +++ b/modules/game_market/ui/marketoffers/itemoffers.otui @@ -68,6 +68,8 @@ Panel column-style: OfferTableColumn header-column-style: false header-row-style: false + color: #cccccc + !text: tr('Select an item to view the offers') OfferTableHeaderRow id: header @@ -141,6 +143,8 @@ Panel column-style: OfferTableColumn header-column-style: false header-row-style: false + color: #cccccc + !text: tr('Select an item to view the offers') OfferTableHeaderRow id: header diff --git a/modules/game_market/ui/myoffers/currentoffers.otui b/modules/game_market/ui/myoffers/currentoffers.otui index 420ec4c5e..5a02c042d 100644 --- a/modules/game_market/ui/myoffers/currentoffers.otui +++ b/modules/game_market/ui/myoffers/currentoffers.otui @@ -68,6 +68,8 @@ Panel column-style: OfferTableColumn header-column-style: false header-row-style: false + color: #cccccc + !text: tr('Press %s button to update', tr('Refresh Offers')) OfferTableHeaderRow id: header @@ -141,6 +143,8 @@ Panel column-style: OfferTableColumn header-column-style: false header-row-style: false + color: #cccccc + !text: tr('Press %s button to update', tr('Refresh Offers')) OfferTableHeaderRow id: header diff --git a/modules/gamelib/protocol.lua b/modules/gamelib/protocol.lua index 205a938ce..dfe6bcd7c 100644 --- a/modules/gamelib/protocol.lua +++ b/modules/gamelib/protocol.lua @@ -91,6 +91,7 @@ GameServerOpcodes = { GameServerCoinBalance = 223, -- 1080 GameServerStoreError = 224, -- 1080 GameServerRequestPurchaseData = 225, -- 1080 + GameServerSendResourceBalance = 238, GameServerQuestLog = 240, GameServerQuestLine = 241, GameServerCoinBalanceUpdating = 242, -- 1080 @@ -187,6 +188,7 @@ ClientOpcodes = { ClientBugReport = 230, ClientRuleViolation = 231, ClientDebugReport = 232, + ClientStoreEvent = 233, ClientTransferCoins = 239, -- 1080 ClientRequestQuestLog = 240, ClientRequestQuestLine = 241, diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index 0d95444ff..3e3e0339f 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -148,6 +148,7 @@ namespace Proto { GameServerCoinBalance = 223, // 1080 GameServerStoreError = 224, // 1080 GameServerRequestPurchaseData = 225, // 1080 + GameServerSendResourceBalance = 238, GameServerQuestLog = 240, GameServerQuestLine = 241, GameServerCoinBalanceUpdating = 242, // 1080 From bbbcd68cfc9df140ea7d4e0ff778bc7867dab867 Mon Sep 17 00:00:00 2001 From: metabob Date: Mon, 15 Apr 2019 16:13:44 +0200 Subject: [PATCH 2/5] fixes compile errors on linux --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 095ab2914..28a7937ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ include(src/client/CMakeLists.txt) # functions map for reading backtraces if(NOT APPLE) - set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -Wl,-Map=${PROJECT_NAME}.map") + set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -no-pie -Wl,-Map=${PROJECT_NAME}.map") endif() option(USE_PCH "Use precompiled header (speed up compile)" OFF) From 0671cea18cc774c660e21653586b4d47f57a5c04 Mon Sep 17 00:00:00 2001 From: metabob Date: Mon, 15 Apr 2019 16:14:48 +0200 Subject: [PATCH 3/5] - game_store module --- modules/game_interface/interface.otmod | 1 + modules/game_store/store.lua | 385 ++++++++++++++++++ modules/game_store/store.otmod | 9 + modules/game_store/store.otui | 62 +++ modules/game_store/storeprotocol.lua | 378 +++++++++++++++++ .../ui/general/storealertwindow.otui | 45 ++ .../game_store/ui/general/storecombobox.otui | 18 + .../ui/general/storepromptwindow.otui | 50 +++ .../game_store/ui/myoffers/offerhistory.otui | 9 + modules/game_store/ui/storeoffers/browse.otui | 213 ++++++++++ modules/game_store/ui/storetoffers.otui | 19 + 11 files changed, 1189 insertions(+) create mode 100644 modules/game_store/store.lua create mode 100644 modules/game_store/store.otmod create mode 100644 modules/game_store/store.otui create mode 100644 modules/game_store/storeprotocol.lua create mode 100644 modules/game_store/ui/general/storealertwindow.otui create mode 100644 modules/game_store/ui/general/storecombobox.otui create mode 100644 modules/game_store/ui/general/storepromptwindow.otui create mode 100644 modules/game_store/ui/myoffers/offerhistory.otui create mode 100644 modules/game_store/ui/storeoffers/browse.otui create mode 100644 modules/game_store/ui/storetoffers.otui diff --git a/modules/game_interface/interface.otmod b/modules/game_interface/interface.otmod index 63eb5f363..6ff5e1d2d 100644 --- a/modules/game_interface/interface.otmod +++ b/modules/game_interface/interface.otmod @@ -27,6 +27,7 @@ Module - game_playermount - game_ruleviolation - game_market + - game_store - game_spelllist - game_cooldown - game_modaldialog diff --git a/modules/game_store/store.lua b/modules/game_store/store.lua new file mode 100644 index 000000000..1d40aa535 --- /dev/null +++ b/modules/game_store/store.lua @@ -0,0 +1,385 @@ +Store = {} + + + +-- LOCAL GLOBALS +-- prefer local variables over globals for better code organization + +local PROTOCOL = runinsandbox('storeprotocol') +local STORE_BUTTON +local STORE_WINDOW +local OPTION_CHANGE_HANDLERS = {} +local IS_WAITING_ON_USER_INPUT_FLAG = false +local PATH_TO_ICONS = '/images/store/64/' +local DEFAULT_OFFER_ICON_NAME = 'unknown' -- is localed at PATH_TO_ICONS and is a PNG-file +local TOP_BUTTON_ICON_PATH = '/images/topbuttons/chest' +local STYLES_TABLE = { + 'ui/general/storecombobox', + 'ui/general/storealertwindow', + 'ui/general/storepromptwindow', + 'store' +} +local COIN_BALANCE_LABEL +local BROWSE_PANEL +local CATEGORY_LIST +local ITEM_DETAILS_PANEL + +local function initInterface() + local mainTabBar = STORE_WINDOW:getChildById('mainTabBar') + local mainTabContentPanel = STORE_WINDOW:getChildById('mainTabContentPanel') + mainTabBar:setContentWidget(mainTabContentPanel) + + COIN_BALANCE_LABEL = STORE_WINDOW:getChildById('balanceLabel') + + BROWSE_PANEL = g_ui.loadUI('ui/storeoffers/browse') + mainTabBar:addTab(tr('Browse'), BROWSE_PANEL) + + CATEGORY_LIST = BROWSE_PANEL:getChildById('categoryComboBox') + + + ITEM_DETAILS_PANEL = BROWSE_PANEL:getChildById('itemDetailsPanel') + local itemDetailsPanelTabBar = BROWSE_PANEL:getChildById('itemDetailsPanelTabBar') + itemDetailsPanelTabBar:addTab(tr('Item Details'), ITEM_DETAILS_PANEL) + + + local transactionHistoryPanel = g_ui.loadUI('ui/myoffers/offerhistory') + mainTabBar:addTab(tr('Transaction History'), transactionHistoryPanel) +end + +local function getImageSourcePathFromOfferIcon(icon) + local path, count = string.gsub( PATH_TO_ICONS .. icon, ".png", "" ) + + if g_resources.fileExists(path .. '.png') then + return path + else + return PATH_TO_ICONS .. DEFAULT_OFFER_ICON_NAME + end +end + +function init() + local function toggle() return 0 end + + for i, style in pairs(STYLES_TABLE) do + g_ui.importStyle(style) + end + + STORE_BUTTON = modules.client_topmenu.addRightGameToggleButton('storeButton', tr('Store'), TOP_BUTTON_ICON_PATH, toggle) + PROTOCOL.initProtocol() + STORE_WINDOW = g_ui.createWidget('StoreWindow', rootWidget) + STORE_WINDOW:hide() + + initInterface() + + STORE_BUTTON.onClick = function() + if STORE_BUTTON:isOn() == true then + Store.close() + else + Store.open() + end + end + + connect(g_game, { onGameEnd = Store.close }) +end + +function terminate() + PROTOCOL.terminateProtocol() + Store.close() + disconnect(g_game, { onGameEnd = Store.close }) +end + +function Store.clearOffers() + local itemsPanel = BROWSE_PANEL:recursiveGetChildById('itemsPanel') + itemsPanel:destroyChildren() +end + +function Store.close() + STORE_WINDOW:hide() + STORE_BUTTON:setOn(false) + Store.clearOffers() + modules.game_interface.getRootPanel():focus() + CATEGORY_LIST.onOptionChange = nil +end + +function Store.open() + STORE_BUTTON:setOn(true) + Store.hideItemDetails() + Store.showStoreIsUpdatingMessage() + + if lastCategory then -- assuming that the category didn't get removed server-side... + -- update categories in case the shop has been updated + --StoreProtocol.sendOpenStore() -- changes will be reflected either when the user restarts the client, or reloads the module... + StoreProtocol.sendRequestStoreOffers(lastCategory) + else + StoreProtocol.sendOpenStore() + end + STORE_WINDOW:show() + STORE_WINDOW:focus() +end + +function Store.onCheckChange(itemBox) + local offer = itemBox.offer + + local selectedItem = ITEM_DETAILS_PANEL:getChildById('selectedItem') + local itemNameLabel = ITEM_DETAILS_PANEL:getChildById('itemName') + local itemPriceLabel = ITEM_DETAILS_PANEL:getChildById('itemPrice') + local itemAmountLabel = ITEM_DETAILS_PANEL:getChildById('itemAmount') + local itemDescription = ITEM_DETAILS_PANEL:recursiveGetChildById('itemDescription') + local buyButton = ITEM_DETAILS_PANEL:getChildById('buyButton') + + selectedItem:setImageSource(getImageSourcePathFromOfferIcon(offer.icons[1])) + itemNameLabel:setText(tr("Name") .. ": " .. offer.name) + itemPriceLabel:setText(tr("Price") .. ": " .. offer.price .. ' ' .. tr("coins")) + itemAmountLabel:setText(tr("Amount") .. ": x" .. offer.amount) + + if offer.description and offer.description ~= "" then + itemDescription:setText(offer.description) + else + itemDescription:setText(tr("This item has no description")) + end + + itemDescription:resizeToText() + + buyButton.onClick = function() + Store.purchaseHandler(offer.id, offer.type) + end + + Store.showItemDetails() +end + +function Store.hideItemDetails() + local children = ITEM_DETAILS_PANEL:getChildren() + ITEM_DETAILS_PANEL:setText(tr("Select an item to preview details and make a purchase")) + + for i, child in pairs(children) do + child:hide() + end +end + +function Store.showItemDetails() + local children = ITEM_DETAILS_PANEL:getChildren() + ITEM_DETAILS_PANEL:setText("") + + for i, child in pairs(children) do + child:show() + end +end + +-- fires when 1) user presses the buy button and when 2) onRequestPurchaseData is fired +function Store.purchaseHandler(thingId, offerType) + STORE_WINDOW:lock() + + if offerType == StoreProtocol.OfferTypes.OFFER_TYPE_NAMECHANGE then + if not IS_WAITING_ON_USER_INPUT_FLAG then + -- use a flag to prevent duplicate prompts in case server messes up + IS_WAITING_ON_USER_INPUT_FLAG = true + + Store.showNameInputPrompt(function(name) + StoreProtocol.sendBuyStoreOffer(thingId, StoreProtocol.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE, name) + IS_WAITING_ON_USER_INPUT_FLAG = false + end, function() + IS_WAITING_ON_USER_INPUT_FLAG = false + end) + end + else + StoreProtocol.sendBuyStoreOffer(thingId, offerType) + end +end + +-- multipurpose way to display an alert containing an arbitrary long message to the user; feel free to use +function Store.showAlertWindow(message, onClickHandler) + local alert = g_ui.createWidget('StoreAlertWindow', rootWidget) + local alertMessage = alert:recursiveGetChildById('alert') + local confirmButton = alert:getChildById('confirmButton') + alertMessage:setText(message) + alertMessage:resizeToText() + alert:lock() + STORE_WINDOW:unlock() + alert:focus() + + confirmButton.onClick = function() + if onClickHandler then onClickHandler(alert) end + + STORE_WINDOW:focus() + alert:unlock() + alert:destroy() + end +end + +-- TODO: make it generic +function Store.showNameInputPrompt(onConfirmHandler, onCancelHandler) + local promptWindow = g_ui.createWidget('StorePromptWindow', rootWidget) + promptWindow:show() + promptWindow:focus() + + local confirmButton = promptWindow:recursiveGetChildById('confirmButton') + local cancelButton = promptWindow:recursiveGetChildById('cancelButton') + local nameInput = promptWindow:recursiveGetChildById('nameInput') + + confirmButton.onClick = function() + local name = nameInput:getDisplayedText() + if onConfirmHandler then onConfirmHandler(name) end + + promptWindow:hide() + promptWindow:destroy() + STORE_WINDOW:unlock() + end + + cancelButton.onClick = function() + if onCancelHandler then onCancelHandler() end + + promptWindow:hide() + promptWindow:destroy() + STORE_WINDOW:unlock() + end +end + +function Store.setItemsPanelText(text) + local itemsPanel = BROWSE_PANEL:recursiveGetChildById('itemsPanel') + itemsPanel:setText(text) +end + +function Store.showStoreIsUpdatingMessage() + Store.setItemsPanelText(tr("Fetching items...")) +end + +function Store.hideStoreIsUpdatingMessage() + Store.setItemsPanelText("") +end + +-- fires when users selects another option (category) from the select list +function Store.onOptionChange() + Store.clearOffers() + Store.showStoreIsUpdatingMessage() + + lastCategory = CATEGORY_LIST:getCurrentOption().text + for handlerName,handler in pairs(OPTION_CHANGE_HANDLERS) do + if handlerName == CATEGORY_LIST:getCurrentOption().text then + handler() + end + end +end + +function Store.updateBalance(balance) + COIN_BALANCE_LABEL:setText(tr('Coin Balance: ') .. balance) + COIN_BALANCE_LABEL:resizeToText() +end + + + +-- PACKET HANDLERS +-- these functions are called in StoreProtocol and get fired after we receive and parse a packet + +function Store.onCoinBalance(balance) + Store.updateBalance(balance) +end + +function Store.onStoreError(error, message) + Store.showAlertWindow(message) +end + +function Store.onRequestPurchaseData(offerId, clientOfferType) + -- print('onRequestPurchaseData') + --Store.purchaseHandler(offerId, clientOfferType) +end + +function Store.onCoinBalanceUpdating() + -- print('onCoinBalanceUpdating') +end + +function Store.onOpenStore(categories) + CATEGORY_LIST:clearOptions() + OPTION_CHANGE_HANDLERS = {} + + for k,category in pairs(categories) do + if k == #category then break end + local catName = category.name + -- register a listener for when this category gets selected + OPTION_CHANGE_HANDLERS[catName] = function() + StoreProtocol.sendRequestStoreOffers(catName) + end + + CATEGORY_LIST:addOption(catName) + CATEGORY_LIST.onOptionChange = Store.onOptionChange + end +end + +function Store.onStoreOffers(category) + -- print('got ', #category.offers, ' offers') + + local itemsPanel = BROWSE_PANEL:recursiveGetChildById('itemsPanel') + + local layout = itemsPanel:getLayout() + Store.clearOffers() + Store.showStoreIsUpdatingMessage() + layout:disableUpdates() + Store.hideItemDetails() + + local radioItemSet = UIRadioGroup.create() + local enabledOfferCount = 0 + + for i,offer in pairs(category.offers) do + + if offer.type ~= 255 then + -- setup item box and its event handling + local itemBox = g_ui.createWidget('StoreItemBox', itemsPanel) + itemBox.offer = offer -- well need this to handle the checkChangeEvent + + -- display amount + local nameLabel = itemBox:getChildById('nameLabel') + if offer.type == StoreProtocol.OfferTypes.OFFER_TYPE_PREMIUM then + nameLabel:setText(offer.amount .. " " .. tr("days")) + else + nameLabel:setText(offer.amount .. 'x') + end + + -- display price + local priceLabel = itemBox:getChildById('priceLabel') + priceLabel:setText(offer.price .. " " .. tr("coins")) + + + local itemWidget = itemBox:getChildById('item') + itemWidget:setMarginLeft(3) + itemWidget:setMarginTop(3) + + local iconPath = getImageSourcePathFromOfferIcon(offer.icons[1]) -- TODO: check for gender when offertype is outfit + itemWidget:setImageSource(iconPath) + + if offer.type == StoreProtocol.OfferTypes.OFFER_TYPE_NONE then + itemBox:disable() + itemBox:setTooltip(offer.disableReason) + else + itemBox.onCheckChange = Store.onCheckChange + end + + radioItemSet:addWidget(itemBox) + enabledOfferCount = enabledOfferCount + 1 + end + end + + layout:enableUpdates() + + Store.hideStoreIsUpdatingMessage() + if enabledOfferCount == 0 and #category.offers > 0 then + Store.setItemsPanelText(tr("You already own all the offers from this category")) + elseif #category.offers == 0 then + Store.setItemsPanelText(tr("There are currently no offers in this category")) + end + + layout:update() +end + +function Store.onOpenTransactionHistory() + --print('onOpenTransactionHistory') +end + +function Store.onCompletePurchase(message, balance) + Store.updateBalance(balance) + + -- We need to update the offer list because some items such as outfits can be bought only once + -- and thus should be removed from the UI. Using this window we capture a click event from user + -- and update the offer list automatically. + Store.showAlertWindow(message, function() + local category = CATEGORY_LIST:getCurrentOption().text + StoreProtocol.sendRequestStoreOffers(category) + end) +end \ No newline at end of file diff --git a/modules/game_store/store.otmod b/modules/game_store/store.otmod new file mode 100644 index 000000000..31a71dc9f --- /dev/null +++ b/modules/game_store/store.otmod @@ -0,0 +1,9 @@ +Module + name: game_store + description: In-game store + author: metabob + website: https://github.com/metabobb/otclient + sandboxed: true + scripts: [ storeprotocol, store ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/modules/game_store/store.otui b/modules/game_store/store.otui new file mode 100644 index 000000000..e94d15883 --- /dev/null +++ b/modules/game_store/store.otui @@ -0,0 +1,62 @@ +StoreWindow < MainWindow + id: storeWindow + !text: 'Store' + size: 700 530 + + @onEscape: Store.close() + + // Main Panel Window + + MarketTabBar + id: mainTabBar + width: 164 + height: 25 + anchors.top: parent.top + anchors.left: parent.left + + Panel + id: mainTabContentPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding: 3 + border-width: 1 + border-color: #000000 + margin-bottom: 20 + + Label + id: balanceLabel + !text: 'Coin Balance' .. ': ' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.right: parent.right + + Button + id: closeButton + !text: tr('Close') + anchors.top: mainTabContentPanel.bottom + anchors.horizontalCenter: mainTabContentPanel.horizontalCenter + margin-top: 5 + width: 110 + @onClick: function() store = Store; store.close() end + + Button + id: refreshOffersButton + !text: tr('Refresh Offers') + anchors.top: mainTabContentPanel.bottom + anchors.right: mainTabContentPanel.right + margin-top: 5 + width: 110 + @onClick: Store.refreshOffers() + + Button + id: resetButton + !text: tr('Reset Store') + !tooltip: tr('Reset selection, filters & search') + anchors.top: mainTabContentPanel.bottom + anchors.left: mainTabContentPanel.left + margin-top: 5 + width: 110 + @onClick: Store.reset() \ No newline at end of file diff --git a/modules/game_store/storeprotocol.lua b/modules/game_store/storeprotocol.lua new file mode 100644 index 000000000..d76def7bf --- /dev/null +++ b/modules/game_store/storeprotocol.lua @@ -0,0 +1,378 @@ +StoreProtocol = {} + +-- PUBLIC ENUMS + +StoreProtocol.OfferTypes = { + OFFER_TYPE_NONE = 0, -- (this will disable offer) + OFFER_TYPE_ITEM = 1, + OFFER_TYPE_STACKABLE = 2, + OFFER_TYPE_OUTFIT = 3, + OFFER_TYPE_OUTFIT_ADDON = 4, + OFFER_TYPE_MOUNT = 5, + OFFER_TYPE_NAMECHANGE = 6, + OFFER_TYPE_SEXCHANGE = 7, + OFFER_TYPE_PROMOTION = 8, + OFFER_TYPE_HOUSE = 9, + OFFER_TYPE_EXPBOOST = 10, + OFFER_TYPE_PREYSLOT = 11, + OFFER_TYPE_PREYBONUS = 12, + OFFER_TYPE_TEMPLE = 13, + OFFER_TYPE_BLESSINGS = 14, + OFFER_TYPE_PREMIUM = 15, + OFFER_TYPE_POUNCH = 16, + OFFER_TYPE_ALLBLESSINGS = 17 +} + +StoreProtocol.ClientOfferTypes = { + CLIENT_STORE_OFFER_OTHER = 0, + CLIENT_STORE_OFFER_NAMECHANGE = 1 +} + +StoreProtocol.HistoryTypes = { + HISTORY_TYPE_NONE = 0, + HISTORY_TYPE_GIFT = 1, + HISTORY_TYPE_REFUND = 2 +} + +StoreProtocol.States = { + STATE_NONE = 0, + STATE_NEW = 1, + STATE_SALE = 2, + STATE_TIMED = 3 +} + +StoreProtocol.StoreErrors = { + STORE_ERROR_PURCHASE = 0, + STORE_ERROR_NETWORK = 1, + STORE_ERROR_HISTORY = 2, + STORE_ERROR_TRANSFER = 3, + STORE_ERROR_INFORMATION = 4 +} + +StoreProtocol.ServiceTypes = { + SERVICE_STANDARD = 0, + SERVICE_OUTFITS = 3, + SERVICE_MOUNTS = 4, + SERVICE_BLESSINGS = 5 +} + +local protocol +local isSilent + +local function send(msg) + if protocol and not isSilent then + --print('sent', msg) + protocol:send(msg) + end +end + +function initProtocol() + connect(g_game, { onGameStart = StoreProtocol.registerProtocol, + onGameEnd = StoreProtocol.unregisterProtocol }) + + -- reloading module + if g_game.isOnline() then + StoreProtocol.registerProtocol() + end + + StoreProtocol.setIsSilent(false) +end + +function terminateProtocol() + disconnect(g_game, { onGameStart = StoreProtocol.registerProtocol, + onGameEnd = StoreProtocol.unregisterProtocol }) + + -- reloading module + StoreProtocol.unregisterProtocol() + StoreProtocol = nil +end + +-- PRIVATE + +local function parseCoinBalance(protocol, msg) + msg:getU8() + local balance = msg:getU32() + msg:getU32() -- duplicate of balance + + signalcall(Store.onCoinBalance, balance) + return true +end + +local function parseCoinBalanceUpdating(protocol, msg) + msg:getU8() -- 0x00 is updating + + signalcall(Store.onCoinBalanceUpdating) + return true +end + +local function parseStoreError(protocol, msg) + --[[ + STORE_ERROR_PURCHASE=0, + STORE_ERROR_NETWORK, + STORE_ERROR_HISTORY, + STORE_ERROR_TRANSFER, + STORE_ERROR_INFORMATION + ]] + + + local error = msg:getU8() + local message = msg:getString() + + signalcall(Store.onStoreError, error, message) + return true +end + +local function parseRequestPurchaseData(protocol, msg) + local offerId = msg:getU32() + local clientOfferType = msg:getU8() + + signalcall(Store.onRequestPurchaseData, offerId, clientOfferType) + return true +end + +local function parseOpenStore(protocol, msg) + local categories = {} + msg:getU8() + local categoriesCount = msg:getU16() + + for i=1, categoriesCount do + local category = {} + + category.name = msg:getString() + category.description = msg:getString() + category.state = msg:getU8() + category.icons = {} + + local iconsCount = msg:getU8() + for j=1, iconsCount do + category.icons[j] = msg:getString() + end + + category.parentCategory = msg:getString() + + categories[i] = category + end + + signalcall(Store.onOpenStore, categories) + return true +end + +local function parseStoreOffers(protocol, msg) + local category = {} + category.name = msg:getString() + category.offers = {} + + local offerCount = msg:getU16() + + for i=1, offerCount do + local offer = {} + + offer.id = msg:getU32() + offer.name = msg:getString() -- this is item amount and item name + + -- create a pseudo amount field + local match = string.gmatch(offer.name, "(%d+)x ") + local amount = match(1) + if amount then + offer.amount = amount + else + offer.amount = 1 + end + + offer.description = msg:getString() + offer.price = msg:getU32() + offer.state = msg:getU8() + + offer.basePrice = nil + offer.validUntil = nil + if offer.state == StoreProtocol.States.STATE_SALE then + offer.validUntil = msg:getU32() + offer.basePrice = msg:getU32() + end + + offer.type = msg:getU8() + + offer.disableReason = "" + if offer.type == 0 then + offer.disableReason = msg:getString() + end + + if offer.type == StoreProtocol.OfferTypes.OFFER_TYPE_PREMIUM then + local daysCount = string.gmatch(offer.name, "(%d+) Days") + offer.amount = daysCount(1) + end + + offer.icons = {} + local iconCount = msg:getU8() + for j=1, iconCount do + offer.icons[j] = msg:getString() + end + + msg:getU16() -- 0 + + category.offers[i] = offer + end + + signalcall(Store.onStoreOffers, category) + return true +end + +local function parseOpenTransactionHistory(protocol, msg) + local history = {} + local pageNumber = msg:getU32() + local isLastPage = msg:getU32() -- 0,1 last page + local historyEntryCount = msg:getU8() + + for i=1, historyEntryCount do + local historyEntry = {} + + historyEntry.time = msg:getU32() + historyEntry.mode = msg:getU8() + historyEntry.amount = msg:getU32() + historyEntry.description = msg:getString() + + history[i] = historyEntry + end + + signalcall(Store.onOpenTransactionHistory, pageNumber, isLastPage, history) + return true +end + +local function parseCompletePurchase(protocol, msg) + msg:getU8() + local message = msg:getString() + local balance = msg:getU32() + msg:getU32() -- duplicate of balance + + signalcall(Store.onCompletePurchase, message, balance) + return true +end + +function StoreProtocol.updateProtocol(_protocol) + protocol = _protocol +end + +local function logUnsupportedError(mthdname) + return g_logger.error(string.format('StoreProtocol.%s does not support the current protocol.', mthdname)) +end +-- PUBLIC + +function StoreProtocol.setIsSilent(_isSilent) + isSilent = _isSilent +end + +function StoreProtocol.registerProtocol() + if g_game.getFeature(GameIngameStore) then + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerCoinBalance, parseCoinBalance) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerStoreError, parseStoreError) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerRequestPurchaseData, parseRequestPurchaseData) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerCoinBalanceUpdating, parseCoinBalanceUpdating) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerStore, parseOpenStore) -- GameServerOpenStore + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerStoreOffers, parseStoreOffers) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerStoreTransactionHistory, parseOpenTransactionHistory) -- GameServerStoreTransactionHistory + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerStoreCompletePurchase, parseCompletePurchase) -- GameServerCompletePurchase + end + + StoreProtocol.updateProtocol(g_game.getProtocolGame()) +end + +function StoreProtocol.unregisterProtocol() + if g_game.getFeature(GameIngameStore) then + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerCoinBalance, parseCoinBalance) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerStoreError, parseStoreError) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerRequestPurchaseData, parseRequestPurchaseData) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerCoinBalanceUpdating, parseCoinBalanceUpdating) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerStore, parseOpenStore) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerStoreOffers, parseStoreOffers) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerStoreTransactionHistory, parseOpenTransactionHistory) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerStoreCompletePurchase, parseCompletePurchase) + end + + StoreProtocol.updateProtocol(nil) +end + +function StoreProtocol.sendStoreEvent() + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendStoreEvent') end + + local msg = OutputMessage.create() + -- not used + send(msg) +end + +function StoreProtocol.sendTransferCoins(transferReceiver, amount) + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendTransferCoins') end + + local player = g_game.getLocalPlayer() + + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientTransferCoins) + msg:addString(transferReceiver) + msg:addU32(amount) + send(msg) +end + +function StoreProtocol.sendOpenStore() + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendOpenStore') end + + + --[[ + GameStore.ServiceTypes = { + SERVICE_STANDERD = 0, + SERVICE_OUTFITS = 3, + SERVICE_MOUNTS = 4, + SERVICE_BLESSINGS = 5 + } + ]] + + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientOpenStore) + --msg:addU8(StoreProtocol.ServiceTypes.SERVICE_OUTFITS) -- TODO: add an option to set service type + msg:addU8(0) -- TODO: add an option to set service type + send(msg) +end + +function StoreProtocol.sendRequestStoreOffers(categoryName) + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendRequestStoreOffers') end + + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientRequestStoreOffers) + + if g_game.getClientVersion() >= 1092 then + msg:addU8(StoreProtocol.ServiceTypes.SERVICE_STANDARD) -- TODO: add an option to set service type + end + + msg:addString(categoryName) + send(msg) +end + +function StoreProtocol.sendBuyStoreOffer(offerId, offerType, newName) + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendBuyStoreOffer') end + + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientBuyStoreOffer) + msg:addU32(offerId) + msg:addU8(offerType) + + if offerType == StoreProtocol.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then + msg:addString(newName) + end + send(msg) +end + +function StoreProtocol.sendOpenTransactionHistory(entriesPerPageCount) + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendOpenTransactionHistory') end + + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientOpenTransactionHistory) + msg:addU8(entriesPerPageCount) + send(msg) +end + +function StoreProtocol.sendRequestTransactionHistory(page) + if not g_game.getFeature(GameIngameStore) then logUnsupportedError('sendRequestTransactionHistory') end + + local msg = OutputMessage.create(page) + msg:addU8(ClientOpcodes.ClientRequestTransactionHistory) + msg:addU32(page) + send(msg) +end \ No newline at end of file diff --git a/modules/game_store/ui/general/storealertwindow.otui b/modules/game_store/ui/general/storealertwindow.otui new file mode 100644 index 000000000..564bfa671 --- /dev/null +++ b/modules/game_store/ui/general/storealertwindow.otui @@ -0,0 +1,45 @@ +StoreAlertWindow < MainWindow + size: 270 150 + text: Store Notification + + Panel + id: message + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 60 + margin-top: 3 + margin-left: 3 + margin-bottom: 30 + margin-right: 3 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 28 + pixels-scroll: true + + ScrollablePanel + anchors.fill: parent + text-wrap: true + vertical-scrollbar: itemsPanelListScrollBar + background-color: #494949 + border-color: #272727 + border-width: 1 + layout: verticalBox + padding: 5 + margin-right: 12 + + Panel + id: alert + text-wrap: true + text: You have successfully purchased this outfit bitch + + Button + id: confirmButton + text: Close + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 10 \ No newline at end of file diff --git a/modules/game_store/ui/general/storecombobox.otui b/modules/game_store/ui/general/storecombobox.otui new file mode 100644 index 000000000..a5d0730bf --- /dev/null +++ b/modules/game_store/ui/general/storecombobox.otui @@ -0,0 +1,18 @@ +StoreComboBoxPopupMenuButton < ComboBoxRoundedPopupMenuButton + height: 18 + font: verdana-11px-rounded + text-offset: 2 2 + +StoreComboBoxPopupMenuSeparator < UIWidget + image-source: /images/combobox_rounded + image-repeated: true + image-clip: 1 59 89 1 + height: 1 + phantom: true + +StoreComboBoxPopupMenu < ComboBoxRoundedPopupMenu + +StoreComboBox < ComboBoxRounded + font: verdana-11px-rounded + size: 86 20 + text-offset: 3 2 diff --git a/modules/game_store/ui/general/storepromptwindow.otui b/modules/game_store/ui/general/storepromptwindow.otui new file mode 100644 index 000000000..8820247d8 --- /dev/null +++ b/modules/game_store/ui/general/storepromptwindow.otui @@ -0,0 +1,50 @@ +StorePromptWindow < MainWindow + size: 270 135 + text: Store Alert + + Panel + id: message + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-top: 3 + margin-left: 3 + margin-right: 3 + + Label + text: New name: + font: verdana-11px-monochrome + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + height: 22 + width: 100 + text-auto-resize: true + + InputBoxLineEdit + id: nameInput + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + height: 22 + + Button + id: confirmButton + text: Change name + color: #CB5C5C + anchors.top: prev.bottom + anchors.left: parent.left + text-auto-resize: true + margin-top: 10 + + Button + id: cancelButton + text: Cancel + margin-left: 15 + anchors.top: prev.top + anchors.left: confirmButton.right + anchors.right: parent.right + anchors.bottom: prev.bottom + text-auto-resize: true \ No newline at end of file diff --git a/modules/game_store/ui/myoffers/offerhistory.otui b/modules/game_store/ui/myoffers/offerhistory.otui new file mode 100644 index 000000000..67b4a9e34 --- /dev/null +++ b/modules/game_store/ui/myoffers/offerhistory.otui @@ -0,0 +1,9 @@ +Panel + background-color: #22283322 + margin: 1 + + Label + !text: tr('Not implemented') + anchors.top: parent.top + anchors.left: parent.left + margin-left: 10 diff --git a/modules/game_store/ui/storeoffers/browse.otui b/modules/game_store/ui/storeoffers/browse.otui new file mode 100644 index 000000000..939a7b04c --- /dev/null +++ b/modules/game_store/ui/storeoffers/browse.otui @@ -0,0 +1,213 @@ +StoreItemBox < UICheckBox + id: itemBox + border-width: 1 + border-color: #272727 + background-color: #555555 + color: #aaaaaa + text-align: center + margin-left: 6 + margin-top: 6 + + Item + id: item + phantom: true + virtual: true + size: 64 64 + image-clip: 0 0 64 64 + text-offset: 0 22 + margin-left: -3 + margin-top: -3 + text-align: right + anchors.top: parent.top + anchors.left: parent.left + + $checked: + border-color: #ffffff + background-color: #353535 + + $hover !checked: + border-color: #aaaaaa + + $disabled: + image-color: #ffffff88 + color: #aaaaaa88 + + Label + id: nameLabel + font: verdana-11px-rounded + margin-left: 3 + border-color: #000000 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + + Label + id: priceLabel + font: verdana-11px-rounded + margin-left: 3 + border-color: #000000 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + +Panel + id: mainPanel + background-color: #535353 + margin: 1 + + MarketComboBox + id: categoryComboBox + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-offset: 6 1 + margin-top: 3 + margin-right: 0 + margin-left: 0 + + Panel + id: itemsContainer + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 213 + border-color: #000000 + border-width: 1 + margin-top: 3 + margin-bottom: 30 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 28 + pixels-scroll: true + + ScrollablePanel + id: itemsPanel + anchors.left: parent.left + anchors.right: prev.left + anchors.top: parent.top + anchors.bottom: parent.bottom + vertical-scrollbar: itemsPanelListScrollBar + background-color: #494949 + color: #A2A2A2 + border-width: 1 + border-color: #272727 + layout: + type: grid + cell-size: 76 101 + flow: true + auto-spacing: true + + MarketTabBar + id: itemDetailsPanelTabBar + width: 187 + height: 25 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 6 + margin-left: 3 + + Panel + id: itemDetailsPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + background-color: #535353 + border-color: #000000 + border-width: 1 + margin-top: -1 + margin-left: 3 + margin-right: 3 + margin-bottom: 3 + text-align: center + + Item + id: selectedItem + phantom: true + virtual: true + size: 64 64 + image-clip: 0 0 64 64 + image-source: /images/store/64/Product_Blessing_AllPvE + text-align: right + anchors.top: parent.top + anchors.left: parent.left + margin-top: 30 + margin-left: 20 + + Label + id: itemName + font: verdana-11px-rounded + border-color: #000000 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 20 + text-wrap: true + width: 200 + + Label + id: itemPrice + font: verdana-11px-rounded + border-color: #000000 + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 6 + width: 200 + + Label + id: itemAmount + font: verdana-11px-rounded + border-color: #000000 + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 6 + width: 200 + + Button + id: buyButton + !text: tr('Buy') + font: verdana-11px-rounded + border-color: #000000 + anchors.top: selectedItem.bottom + anchors.left: selectedItem.left + anchors.right: itemName.right + margin-top: 10 + margin-left: 10 + margin-right: 10 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + margin-right: 30 + margin-top: 30 + margin-bottom: 30 + step: 28 + pixels-scroll: true + + ScrollablePanel + anchors.left: itemAmount.right + anchors.top: itemsPanelListScrollBar.top + anchors.bottom: itemsPanelListScrollBar.bottom + anchors.right: parent.right + text-wrap: true + background-color: #494949 + border-color: #272727 + border-width: 1 + border-width-right: 0 + vertical-scrollbar: itemsPanelListScrollBar + layout: verticalBox + padding: 5 + margin-right: 43 + margin-left: 20 + + Panel + id: itemDescription + text-wrap: true + color: #A2A2A2 + text-align: left diff --git a/modules/game_store/ui/storetoffers.otui b/modules/game_store/ui/storetoffers.otui new file mode 100644 index 000000000..f304d9c3f --- /dev/null +++ b/modules/game_store/ui/storetoffers.otui @@ -0,0 +1,19 @@ +Panel + id: storeOffersPanel + + + StoreComboBox + id: storeComboBox + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Panel + id: storeOffersItemsPanel + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: prev.right + anchors.bottom: parent.bottom + border-width: 1 + border-color: #000000 + margin-top: 3 \ No newline at end of file From ceddbf3338f8d4105311b6afd85eddf082ce9f7f Mon Sep 17 00:00:00 2001 From: metabob Date: Mon, 15 Apr 2019 16:33:05 +0200 Subject: [PATCH 4/5] - images directory with a default image --- data/images/store/64/.gitignore | 3 +++ data/images/store/64/unknown.png | Bin 0 -> 2952 bytes 2 files changed, 3 insertions(+) create mode 100644 data/images/store/64/.gitignore create mode 100644 data/images/store/64/unknown.png diff --git a/data/images/store/64/.gitignore b/data/images/store/64/.gitignore new file mode 100644 index 000000000..a08b7afc4 --- /dev/null +++ b/data/images/store/64/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!unknown.png \ No newline at end of file diff --git a/data/images/store/64/unknown.png b/data/images/store/64/unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..86c1a4e18208aa68693cf675865ed97370625cca GIT binary patch literal 2952 zcmV;33wQL1P)wK&>j!xA>|WW4BHbSh=ytno>Nvj4KxM4T+$To% zJ)5%D@_Dh)m2*CV5eze3TAp&1FH575QMWxL~zalirvSz>)!yWEN@@W z0O&*!=~hBNO9^G~<316mIYB7^6d<*K0p%}n;xNV_ z1SBiBfkn};N#Cn8w^mDzDvqz!y`9TBGh>V)859Cyg4Y2N#4ivHgGBupev0_$hwvK& zL|+8+BI3JGQR59!Mv+W%!Z2r_v#-6DJ*!srk1poe=OHidP3@|#T6Ouad-V=&c9+e? zTDJS6Y$FP&Tx9~3V4O-f7IUAN;EkEM&w*t>v=H&EIqEJd9;zu(mYKvpEe*Byy@;E5 z?t47!Ww+bO(M&q+Dz#m00yITcqinW&S*?0e>B_h&n|d-VDy?`6xL{fM zMx|NL!Q@0adE;$a{sd{5Y}a+E!7h&!6SIIL+za>COoXVw)g=WA(NKq?ypU&`$cYk>$_bSXO->LVNH1Kgy#=kL0yizcBXcIIiNpcpX+bxpgx(AIv5*I35S_)k?HS zvZ^Y1RO~W2;ez_d3;;JW2HEF97ZM}mh-@b2Q-rxgi+k#1W+I`=0qiF#4>CBS!GKY9M$ zZZF%-WzgKcdr$gwfV!jso)}jBN}h%P&F&!P;Xy;Kj=f2*n#7v6UR9QM0t66ZZ^*_j+(-Zmh2vo%7{>%5}-QWCLKK}SjY#8L>8*fUd^}N2s z0FNSCHn?@8I1^DV{Wg}q%Z0o-Hh}PB2}DJ-m1(OkN|2qDN&N~F!0CPCYY&R1_FYWc z?)UP|vG45cOioTu<@W6pIaX~IUoe428~Rm0-l#q$T_Q1mR%|ZWY6fk;Iu;{A10$yJ zkI&$lIqFO5pY1f$VHjky*~rD^WeK4p_;RVs2()Mdt*V-bCa8!U2Mh4McPX@VU^Vn< z)?T9kVY5w`G*vR5@xK6uZJ0oF*MDnN?f3uk=V$Ez{&amVXHTBU?|L5OBd+=LJ zQ2>cxo$irbtm3(v2&Y!{WE?fQjc2V@UC0Ys?w}Fut^Yd;eC72oP0_|IH1sM+X;;3) z0MxcQVkAb|#4=CNNIoE^W?11mOeGV<8|Dl?R&^@(dHSm#vt=0rz>S-teK#?Azqu4M zPc74Q*Z^4wgE&D=V9qx|z!t|=VFHq7zQa~pO zq8`9h75_A0STq5gvd^3lU{R?GyiH(ooW7QhHunZPJawnB5UWfG=humJy?7>TW-7!HTg2N3)dR}`Ipr!Y> z0V!(*4hA+}2!p5!Ui>*=K^F!CQq;~J;7#`T;4|lPr{72U`q+kq3zQ{z7@Ekm3z|@U zD*)|yEVH2{q+*(jJ^&2)WyL($gwaBsJlm39crcrad5q@-82jLbr*gX5PpjWq?Smoc z3>PbPOE@sch9TAuU}jM)o@;UdRa@Y6bgF!=PP3$jPnh|XAeSia! zn<)}eEsCQacZ2$fY%8Feu#k_?!4>w4it}BA0q&%}U0>4Gyk=f*wqny^ol>4{^ne%@jIL-XtE0#vqx~ zQ%4gWr9Z(#t4Pt=O@O3HieT6l+GQW0BHgMlAHmWMuhd6^PZ`J_?dW^5V+TXK{?cfM1A$jjQGuQf2Ov1~f5sI1HoRUqZmptjC)g zX9{Qp03{wh+sug?4D5iP;;8J1W@*r|&NWTIXZCl#UQY{}8wa+=GXpfoano*Dm`j)x zDeVa)k`nF&rEXucGM3ZW15yT;oglQ4*lhQ^NO-V07NurmHlYvRL(5Avq>o-h4G$U> zCr85PG~jTAtj7347}U)9*XQdDrSsTs83)a)eKRH|(I0d^S9SVRo2*J0W+8bbM%DO! zcLc*6YNp`O7sFtd?RFQA&m8q+ZUDz&q0XtMaNi7xs+$-LDr?)Eu}(OfNfO%juCFCY(m8kt2BwwH8tQc zN`4p8ckcVy7Q+^2=20D=x|l{E8yX;y5+sFwc!nJs1F; z%IZ8VRY1gSilZZj#aLZi3nxg5kRrI+m#dRw%+#ms3+OtJ@3Irjx=kb)ZM6fkmIlEz z1k3|u3yhjy8Vz+5>y;j6XLPvEb-3X;Necx4mENEsG?5_|iA0^D&e#VHGkk&poX>Mq zeD Date: Mon, 15 Apr 2019 18:12:09 +0200 Subject: [PATCH 5/5] - fix category would not refresh itself after disconnect and store open fixed by forcing a category refresh --- modules/game_store/store.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/game_store/store.lua b/modules/game_store/store.lua index 1d40aa535..cf3bef4ef 100644 --- a/modules/game_store/store.lua +++ b/modules/game_store/store.lua @@ -98,6 +98,7 @@ function Store.close() Store.clearOffers() modules.game_interface.getRootPanel():focus() CATEGORY_LIST.onOptionChange = nil + lastCategory = nil end function Store.open()