otclient-redemption/modules/game_taskboard/controller/tab_shop.lua
kokekanon 4a87cebb11
feat: protocol 15.24 packets and features (#1604)
New Features:
- Task Board (Bounty, Weekly, Shop) with Preferred & Soulseal modals, Kill Tracker, Offline Training dialog, Vocation tutorial, infobanner notifications, redesigned NPC chat/trade, and multi-slot action bar.

Gameplay Enhancements:
- Expanded Cyclopedia stats, talisman upgrades, weekly rewards/progress, soulseal fights, taskboard shop/offers and offline training actions.

UI / Graphics:
- Per-source spell-effect opacity sliders, status icon bar, HUD/scroll improvements, many new templates/styles, screenshot UI updates, and small label/icon fixes.

Bug Fixes:
- Miscellaneous UI behavior, layout and robustness improvements.
2026-05-20 11:26:11 -03:00

169 lines
5.8 KiB
Lua

-- Shop tab logic — methods added to TaskBoardController
local confirmBox = nil
-- Server data handler
function TaskBoardController:onShopData(items)
local parsed = {}
for _, raw in ipairs(items or {}) do
table.insert(parsed, self:parseShopItem(raw))
end
self.shopItems = parsed
end
function TaskBoardController:onShopResult(itemId, result)
if result == 0 then
-- Success — server will send updated shop data
return
end
local errorMessages = {
[SHOP_ERR_NOT_FOUND] = "Item not found.",
[SHOP_ERR_ALREADY_BOUGHT] = "Already purchased.",
[SHOP_ERR_NO_POINTS] = "Not enough Hunting Task Points.",
[SHOP_ERR_NEED_BASE] = "You need the base outfit first.",
[SHOP_ERR_STORE_INBOX] = "Store inbox error."
}
local msg = errorMessages[result] or ("Purchase failed (code " .. result .. ").")
local errBox
local function close()
if errBox then
errBox:destroy();
errBox = nil
end
end
errBox = displayGeneralBox(tr('Purchase Failed'), msg, {{
text = tr('Ok'),
callback = close
}}, close, close)
end
-- Actions
function TaskBoardController:buyItem(itemId)
local item = nil
local itemIndex = nil
for index, it in ipairs(self.shopItems or {}) do
if it.id == tonumber(itemId) then
item = it
itemIndex = index
break
end
end
if not item then
return
end
local msg = tr("Do you really want to buy '%s' for %s Hunting Task Points?", item.title, comma_value(item.price))
local function yes()
g_game.taskHuntingShopPurchase(item.id)
if confirmBox then
confirmBox:destroy();
confirmBox = nil
end
end
local function cancel()
if confirmBox then
confirmBox:destroy();
confirmBox = nil
end
end
confirmBox = displayGeneralBox(tr('Confirm Purchase'), msg, {{
text = tr('Yes'),
callback = yes
}, {
text = tr('Cancel'),
callback = cancel
}}, yes, cancel)
end
function TaskBoardController:updateShopBalance(balance)
balance = tonumber(balance) or 0
self.shopBalance = balance
-- shopItems canAfford field needs to be updated
for i, item in ipairs(self.shopItems or {}) do
self.shopItems[i].canAfford = balance >= item.price
end
self.shopItems = self.shopItems
end
-- Item parser
function TaskBoardController:parseShopItem(raw)
local offerType = tonumber(raw.offerType) or SHOP_OFFER_TYPE_ITEM
local data = {
id = tonumber(raw.id) or 0,
title = raw.title or "",
description = raw.description or "",
price = tonumber(raw.price) or 0,
bought = (tonumber(raw.bought) or 0) == 1,
offerType = offerType,
backdrop = SHOP_BACKDROP_IMAGES[offerType] or SHOP_BACKDROP_IMAGES[SHOP_OFFER_TYPE_ITEM],
canAfford = false,
lookMount = 0,
previewType = nil
}
if offerType == SHOP_OFFER_TYPE_OUTFIT then
local parsedLookType = tonumber(raw.lookType) or 0
if parsedLookType > 0 then
data.lookType = parsedLookType
else
g_logger.warning(string.format("[TaskBoard][Shop] Outfit without lookType (id=%s, title=%s)",
tostring(data.id), tostring(data.title)))
end
data.lookHead = tonumber(raw.lookHead) or 0
data.lookBody = tonumber(raw.lookBody) or 0
data.lookLegs = tonumber(raw.lookLegs) or 0
data.lookFeet = tonumber(raw.lookFeet) or 0
data.lookAddons = tonumber(raw.lookAddons) or 0
data.lookMount = 0
data.isOutfit = parsedLookType > 0
data.isCreaturePreview = data.isOutfit
data.previewType = data.isCreaturePreview and 'creature' or nil
elseif offerType == SHOP_OFFER_TYPE_MOUNT then
local parsedMountType = tonumber(raw.lookType) or 0
if parsedMountType > 0 then
data.lookMount = parsedMountType
else
g_logger.warning(string.format("[TaskBoard][Shop] Mount without lookType (id=%s, title=%s)",
tostring(data.id), tostring(data.title)))
end
data.lookType = 128
data.lookHead = 95
data.lookBody = 114
data.lookLegs = 95
data.lookFeet = 114
data.lookAddons = 0
data.isMount = parsedMountType > 0
data.isCreaturePreview = data.isMount
data.previewType = data.isCreaturePreview and 'creature' or nil
elseif offerType == SHOP_OFFER_TYPE_ITEM or offerType == SHOP_OFFER_TYPE_ITEM_DOUBLE then
data.itemId = tonumber(raw.itemId) or 0
data.lookMount = 0
data.isItem = (data.itemId > 0)
data.previewType = data.isItem and 'item' or nil
elseif offerType == SHOP_OFFER_TYPE_BONUS_PROMOTION then
local defaults = SHOP_BONUS_DEFAULTS[SHOP_OFFER_TYPE_BONUS_PROMOTION] or {}
data.title = defaults.title or data.title
data.description = defaults.description or data.description
data.imageSource = defaults.image or ""
data.maxPurchases = tonumber(raw.maxPurchases) or 0
data.currentPurchases = tonumber(raw.currentPurchases) or 0
data.nextCost = tonumber(raw.nextCost) or 0
data.price = data.nextCost
data.description = string.format(data.description, data.currentPurchases)
data.lookMount = 0
data.isBonus = true
data.previewType = 'icon'
end
local balance = tonumber(self.shopBalance)
if not balance or balance <= 0 then
local player = g_game.getLocalPlayer()
balance = player and player:getResourceBalance(ResourceTypes.TASK_HUNTING) or balance or 0
end
data.canAfford = balance >= data.price
data.displayPrice = comma_value(data.price)
return data
end