mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
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.
169 lines
5.8 KiB
Lua
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
|