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.
672 lines
23 KiB
Lua
672 lines
23 KiB
Lua
-- Preferred monsters modal - methods added to TaskBoardController
|
|
-- Uses openModal/closeModal to open a secondary HTML window without
|
|
-- affecting self.ui which points to taskboard.html
|
|
local cachedRemoveCost = 0
|
|
local cachedAllRaceIds = {}
|
|
local cachedRawSlots = {}
|
|
|
|
local MONSTER_ROW_HEIGHT_PX = 44
|
|
local MONSTER_OVERSCAN_ROWS = 2
|
|
local MONSTER_BIND_RETRY_DELAY_MS = 25
|
|
local MONSTER_BIND_MAX_RETRIES = 20
|
|
local MONSTER_SPACER_SEGMENT_PX = 20000
|
|
local MONSTER_ROW_HEIGHT_MIN_PX = 36
|
|
local MONSTER_ROW_HEIGHT_MAX_PX = 72
|
|
|
|
-- Split a large pixel value across three spacer segments (max 20000px each).
|
|
-- Needed because OTClient widget heights are capped; three divs simulate tall spacers.
|
|
local function splitSpacerPx(totalPx)
|
|
local px = math.max(0, tonumber(totalPx) or 0)
|
|
local a = math.min(px, MONSTER_SPACER_SEGMENT_PX)
|
|
px = px - a
|
|
local b = math.min(px, MONSTER_SPACER_SEGMENT_PX)
|
|
local c = px - b
|
|
return a, b, c
|
|
end
|
|
|
|
local function clampRowHeightPx(value)
|
|
local px = math.floor((tonumber(value) or MONSTER_ROW_HEIGHT_PX) + 0.5)
|
|
return math.max(MONSTER_ROW_HEIGHT_MIN_PX, math.min(MONSTER_ROW_HEIGHT_MAX_PX, px))
|
|
end
|
|
|
|
local function getPreferredUnlockPrice(slotNum, serverPrice)
|
|
local slot = tonumber(slotNum) or 0
|
|
local price = tonumber(serverPrice) or 0
|
|
if price > 0 then
|
|
return price
|
|
end
|
|
return tonumber(PREFERRED_UNLOCK_COST and PREFERRED_UNLOCK_COST[slot]) or 0
|
|
end
|
|
|
|
local function getPreferredActionCost()
|
|
local assignCost = tonumber(PREFERRED_SLOT_ASSIGN_COST) or 0
|
|
if assignCost > 0 then
|
|
return assignCost
|
|
end
|
|
return tonumber(cachedRemoveCost) or 0
|
|
end
|
|
|
|
local function updateCachedSlot(slotNum, key, value)
|
|
local slot = tonumber(slotNum) or 0
|
|
if slot <= 0 then
|
|
return
|
|
end
|
|
|
|
for index, slotData in ipairs(cachedRawSlots) do
|
|
if (tonumber(slotData.slot) or index) == slot then
|
|
slotData[key] = value
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Modal open / close
|
|
function TaskBoardController:showPreferred()
|
|
if self.preferredModal then
|
|
return
|
|
end
|
|
|
|
self.preferredModal = self:openModal('template/html/modal_preferred.html', {
|
|
mode = 'html'
|
|
})
|
|
addEvent(function()
|
|
if not self.preferredModal or not self.preferredModal.ui then
|
|
return
|
|
end
|
|
self:ensurePreferredMonsterScrollBound()
|
|
self:resetPreferredMonsterScroll()
|
|
end)
|
|
g_game.bountyPreferredAction(BOUNTY_PREF_ACTION_REQUEST, 0, 0)
|
|
end
|
|
|
|
function TaskBoardController:hidePreferred()
|
|
if self._preferredBindRetryEvent then
|
|
removeEvent(self._preferredBindRetryEvent)
|
|
self._preferredBindRetryEvent = nil
|
|
end
|
|
|
|
local scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
if scrollWidget and scrollWidget.verticalScrollBar then
|
|
local sb = scrollWidget.verticalScrollBar
|
|
if sb._preferredValueHook then
|
|
disconnect(sb, 'onValueChange', sb._preferredValueHook)
|
|
sb._preferredValueHook = nil
|
|
end
|
|
end
|
|
|
|
if self.preferredModal then
|
|
self:closeModal(self.preferredModal)
|
|
self.preferredModal = nil
|
|
end
|
|
|
|
self.allAvailableMonsters = {}
|
|
self.visibleMonsters = {}
|
|
self.availableMonsters = {}
|
|
self.monsterTopSpacerPxA = 0
|
|
self.monsterTopSpacerPxB = 0
|
|
self.monsterTopSpacerPxC = 0
|
|
self.monsterBottomSpacerPxA = 0
|
|
self.monsterBottomSpacerPxB = 0
|
|
self.monsterBottomSpacerPxC = 0
|
|
self.preferredSlots = {}
|
|
self.selectedRaceId = 0
|
|
self.searchText = ""
|
|
|
|
self._preferredViewportStart = 0
|
|
self._preferredViewportEnd = 0
|
|
self._preferredViewportRowHeight = 0
|
|
self._preferredApplyingScrollSnap = false
|
|
self._preferredViewportRefreshing = false
|
|
self._preferredRowHeightPx = MONSTER_ROW_HEIGHT_PX
|
|
self._preferredRowHeightMeasured = false
|
|
|
|
cachedRemoveCost = 0
|
|
cachedAllRaceIds = {}
|
|
cachedRawSlots = {}
|
|
end
|
|
|
|
-- Server data
|
|
function TaskBoardController:onPreferredServerData(slots, removeCost, availableRaceIds)
|
|
cachedRawSlots = slots or {}
|
|
cachedRemoveCost = tonumber(removeCost) or 0
|
|
cachedAllRaceIds = availableRaceIds or {}
|
|
|
|
self:rebuildPreferredSlots()
|
|
self:rebuildAvailableMonsters()
|
|
end
|
|
|
|
-- Slots
|
|
function TaskBoardController:rebuildPreferredSlots()
|
|
local result = self.preferredSlots or {}
|
|
table.clear(result)
|
|
local actionCost = getPreferredActionCost()
|
|
local selectedRaceId = tonumber(self.selectedRaceId) or 0
|
|
local player = g_game.getLocalPlayer()
|
|
local balance = player and player:getResourceBalance(ResourceTypes.BOUNTY_POINTS) or 0
|
|
|
|
for index, slotData in ipairs(cachedRawSlots) do
|
|
local slotNum = tonumber(slotData.slot) or index
|
|
local unlocked = index == 1 or tonumber(slotData.locked) ~= 1
|
|
local preferredId = tonumber(slotData.preferred) or 0
|
|
local unwantedId = tonumber(slotData.unwanted) or 0
|
|
local lockPrice = getPreferredUnlockPrice(slotNum, slotData.price)
|
|
|
|
local prefName, prefOutfit, unwName, unwOutfit
|
|
if preferredId > 0 then
|
|
local rd = g_things.getRaceData(preferredId)
|
|
prefName = rd and rd.name and rd.name:capitalize() or 'Unknown'
|
|
prefOutfit = rd and rd.outfit or nil
|
|
else
|
|
prefName = 'Empty'
|
|
end
|
|
if unwantedId > 0 then
|
|
local rd = g_things.getRaceData(unwantedId)
|
|
unwName = rd and rd.name and rd.name:capitalize() or 'Unknown'
|
|
unwOutfit = rd and rd.outfit or nil
|
|
else
|
|
unwName = 'Empty'
|
|
end
|
|
|
|
local hasPreferred = preferredId > 0
|
|
local hasUnwanted = unwantedId > 0
|
|
local canAssign = selectedRaceId ~= 0
|
|
|
|
table.insert(result, {
|
|
slotNum = slotNum,
|
|
slotTitle = slotNum == 1 and 'Main Slot' or 'Additional Slots',
|
|
unlocked = unlocked and 1 or 0,
|
|
locked = unlocked and 0 or 1,
|
|
lockPrice = lockPrice,
|
|
canUnlock = lockPrice > 0 and balance >= lockPrice,
|
|
preferredId = preferredId,
|
|
preferredName = prefName,
|
|
preferredOutfit = prefOutfit,
|
|
showPreferredCreature = hasPreferred and 1 or 0,
|
|
showPreferredPlaceholder = hasPreferred and 0 or 1,
|
|
showPreferredAssign = hasPreferred and 0 or 1,
|
|
showPreferredClear = hasPreferred and 1 or 0,
|
|
unwantedId = unwantedId,
|
|
unwantedName = unwName,
|
|
unwantedOutfit = unwOutfit,
|
|
showUnwantedCreature = hasUnwanted and 1 or 0,
|
|
showUnwantedPlaceholder = hasUnwanted and 0 or 1,
|
|
showUnwantedAssign = hasUnwanted and 0 or 1,
|
|
showUnwantedClear = hasUnwanted and 1 or 0,
|
|
actionCost = actionCost,
|
|
preferredButtonText = hasPreferred and 'Clear' or 'Assign',
|
|
unwantedButtonText = hasUnwanted and 'Clear' or 'Assign',
|
|
preferredAction = hasPreferred and 'clearPreferred' or 'assignPreferred',
|
|
unwantedAction = hasUnwanted and 'clearUnwanted' or 'assignUnwanted',
|
|
canPreferredAct = hasPreferred or canAssign,
|
|
canUnwantedAct = hasUnwanted or canAssign,
|
|
clearCost = actionCost,
|
|
canClear = balance >= actionCost
|
|
})
|
|
end
|
|
self.preferredSlots = result
|
|
end
|
|
|
|
-- Monster list
|
|
function TaskBoardController:getPreferredMonsterScrollWidget()
|
|
if not self.preferredModal or not self.preferredModal.ui then
|
|
return nil
|
|
end
|
|
return self.preferredModal.ui:querySelector("#monsterListScroll")
|
|
end
|
|
|
|
function TaskBoardController:onPreferredMonsterScrollChange(widget, virtualOffset)
|
|
if self._preferredApplyingScrollSnap then
|
|
return
|
|
end
|
|
self:refreshMonsterViewport((virtualOffset and virtualOffset.y) or 0)
|
|
end
|
|
|
|
function TaskBoardController:bindPreferredMonsterScroll()
|
|
local scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
if not scrollWidget then
|
|
return
|
|
end
|
|
if scrollWidget._preferredVirtualized then
|
|
return
|
|
end
|
|
|
|
scrollWidget._preferredVirtualized = true
|
|
scrollWidget._skipScrollLayoutRecalc = true
|
|
scrollWidget._preferredOriginalUpdateScrollBars = scrollWidget.updateScrollBars
|
|
scrollWidget.updateScrollBars = function()
|
|
end
|
|
|
|
-- Without AutoFocusNone, OTClient calls focusPreviousChild() automatically when a
|
|
-- focused row widget is destroyed during virtual-scroll re-renders (uiwidget.cpp
|
|
-- removeChild), shifting focus to an adjacent row without any user interaction.
|
|
scrollWidget:setAutoFocusPolicy(AutoFocusNone)
|
|
|
|
scrollWidget.onScrollChange = function(widget, virtualOffset)
|
|
self:onPreferredMonsterScrollChange(widget, virtualOffset)
|
|
end
|
|
|
|
if scrollWidget.verticalScrollBar then
|
|
local sb = scrollWidget.verticalScrollBar
|
|
sb:setStep(self:getPreferredMonsterRowHeight(scrollWidget))
|
|
end
|
|
end
|
|
|
|
function TaskBoardController:ensurePreferredMonsterScrollBound(retryCount)
|
|
local scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
if scrollWidget and scrollWidget.verticalScrollBar then
|
|
self:bindPreferredMonsterScroll()
|
|
self:refreshMonsterViewport(tonumber(scrollWidget.verticalScrollBar:getValue()) or 0)
|
|
return true
|
|
end
|
|
|
|
retryCount = tonumber(retryCount) or 0
|
|
if retryCount >= MONSTER_BIND_MAX_RETRIES then
|
|
g_logger.warning('[taskboard/preferred] failed to bind monster scroll: vertical scrollbar not ready')
|
|
return false
|
|
end
|
|
|
|
if self._preferredBindRetryEvent then
|
|
removeEvent(self._preferredBindRetryEvent)
|
|
self._preferredBindRetryEvent = nil
|
|
end
|
|
|
|
self._preferredBindRetryEvent = scheduleEvent(function()
|
|
self._preferredBindRetryEvent = nil
|
|
if self.preferredModal then
|
|
self:ensurePreferredMonsterScrollBound(retryCount + 1)
|
|
end
|
|
end, MONSTER_BIND_RETRY_DELAY_MS)
|
|
|
|
return false
|
|
end
|
|
|
|
function TaskBoardController:updatePreferredMonsterScrollRange(totalRows, viewportHeight, rowHeight)
|
|
local scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
rowHeight = math.max(1, math.floor((tonumber(rowHeight) or self:getPreferredMonsterRowHeight(scrollWidget)) + 0.5))
|
|
local visibleRows = math.max(1, math.floor(viewportHeight / rowHeight))
|
|
local maxScroll = math.max((math.max(1, totalRows - visibleRows + 1) - 1) * rowHeight, 0)
|
|
|
|
if scrollWidget and scrollWidget.verticalScrollBar then
|
|
local sb = scrollWidget.verticalScrollBar
|
|
sb:setMinimum(0)
|
|
sb:setMaximum(maxScroll)
|
|
sb:setStep(rowHeight)
|
|
|
|
if (tonumber(sb:getValue()) or 0) > maxScroll then
|
|
self._preferredApplyingScrollSnap = true
|
|
sb:setValue(maxScroll)
|
|
self._preferredApplyingScrollSnap = false
|
|
end
|
|
end
|
|
|
|
return maxScroll, visibleRows
|
|
end
|
|
|
|
function TaskBoardController:getPreferredMonsterRowHeight(scrollWidget)
|
|
local measured = clampRowHeightPx(self._preferredRowHeightPx)
|
|
if self._preferredRowHeightMeasured then
|
|
return measured
|
|
end
|
|
|
|
scrollWidget = scrollWidget or self:getPreferredMonsterScrollWidget()
|
|
if not scrollWidget or not scrollWidget.querySelectorAll then
|
|
return measured
|
|
end
|
|
|
|
local rows = scrollWidget:querySelectorAll(".monsterRow")
|
|
local h = 0
|
|
if rows and #rows >= 2 then
|
|
local delta = math.abs((tonumber(rows[#rows]:getY()) or 0) - (tonumber(rows[1]:getY()) or 0))
|
|
local pitch = delta / (#rows - 1)
|
|
if pitch > 0 then
|
|
h = pitch
|
|
end
|
|
elseif rows and #rows == 1 then
|
|
h = tonumber(rows[1]:getHeight()) or 0
|
|
if rows[1].getMarginTop and rows[1].getMarginBottom then
|
|
h = h + (tonumber(rows[1]:getMarginTop()) or 0) + (tonumber(rows[1]:getMarginBottom()) or 0)
|
|
end
|
|
end
|
|
|
|
if h > 0 then
|
|
local rounded = math.floor(h + 0.5)
|
|
if rounded >= MONSTER_ROW_HEIGHT_MIN_PX and rounded <= MONSTER_ROW_HEIGHT_MAX_PX then
|
|
self._preferredRowHeightPx = rounded
|
|
self._preferredRowHeightMeasured = true
|
|
return rounded
|
|
end
|
|
end
|
|
|
|
return measured
|
|
end
|
|
|
|
function TaskBoardController:resetPreferredMonsterScroll()
|
|
local scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
if not scrollWidget or not scrollWidget.verticalScrollBar then
|
|
self:ensurePreferredMonsterScrollBound()
|
|
scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
end
|
|
|
|
if scrollWidget and scrollWidget.verticalScrollBar then
|
|
self._preferredApplyingScrollSnap = true
|
|
scrollWidget.verticalScrollBar:setValue(0)
|
|
self._preferredApplyingScrollSnap = false
|
|
elseif scrollWidget then
|
|
scrollWidget:setVirtualOffset({
|
|
x = 0,
|
|
y = 0
|
|
})
|
|
end
|
|
|
|
self:refreshMonsterViewport(0)
|
|
end
|
|
|
|
function TaskBoardController:refreshMonsterViewport(scrollValue)
|
|
if self._preferredViewportRefreshing then
|
|
return
|
|
end
|
|
self._preferredViewportRefreshing = true
|
|
|
|
local list = self.allAvailableMonsters or {}
|
|
local total = #list
|
|
local viewportHeight = 400
|
|
|
|
local scrollWidget = self:getPreferredMonsterScrollWidget()
|
|
local rowHeight = self:getPreferredMonsterRowHeight(scrollWidget)
|
|
if scrollWidget and scrollWidget.getPaddingRect then
|
|
local paddingRect = scrollWidget:getPaddingRect()
|
|
if paddingRect and paddingRect.height and paddingRect.height > 0 then
|
|
viewportHeight = paddingRect.height
|
|
end
|
|
end
|
|
|
|
if total == 0 then
|
|
local visible = self.visibleMonsters or {}
|
|
table.clear(visible)
|
|
self.visibleMonsters = visible
|
|
self.availableMonsters = self.visibleMonsters
|
|
self.monsterTopSpacerPxA, self.monsterTopSpacerPxB, self.monsterTopSpacerPxC = 0, 0, 0
|
|
self.monsterBottomSpacerPxA, self.monsterBottomSpacerPxB, self.monsterBottomSpacerPxC = 0, 0, 0
|
|
self._preferredViewportStart = 0
|
|
self._preferredViewportEnd = 0
|
|
self._preferredViewportRowHeight = rowHeight
|
|
self:updatePreferredMonsterScrollRange(0, viewportHeight, rowHeight)
|
|
self._preferredViewportRefreshing = false
|
|
return
|
|
end
|
|
|
|
local y = tonumber(scrollValue) or 0
|
|
local maxScroll, visibleRows = self:updatePreferredMonsterScrollRange(total, viewportHeight, rowHeight)
|
|
y = math.max(0, math.min(y, maxScroll))
|
|
|
|
local snappedY = math.min(math.floor(y / rowHeight) * rowHeight, maxScroll)
|
|
|
|
if scrollWidget and scrollWidget.verticalScrollBar and not self._preferredApplyingScrollSnap then
|
|
local currentY = scrollWidget.verticalScrollBar:getValue()
|
|
if currentY ~= snappedY then
|
|
self._preferredApplyingScrollSnap = true
|
|
scrollWidget.verticalScrollBar:setValue(snappedY)
|
|
self._preferredApplyingScrollSnap = false
|
|
y = snappedY
|
|
else
|
|
y = currentY
|
|
end
|
|
end
|
|
|
|
local firstVisibleIndex = math.min(math.floor(y / rowHeight) + 1, math.max(1, total - visibleRows + 1))
|
|
local startIndex = math.max(1, firstVisibleIndex - MONSTER_OVERSCAN_ROWS)
|
|
local endIndex = math.min(total, firstVisibleIndex + visibleRows + MONSTER_OVERSCAN_ROWS - 1)
|
|
|
|
if self._preferredViewportStart == startIndex and self._preferredViewportEnd == endIndex and
|
|
self._preferredViewportRowHeight == rowHeight then
|
|
self._preferredViewportRefreshing = false
|
|
return
|
|
end
|
|
|
|
local visible = self.visibleMonsters or {}
|
|
table.clear(visible)
|
|
for i = startIndex, endIndex do
|
|
visible[#visible + 1] = list[i]
|
|
end
|
|
|
|
self.visibleMonsters = visible
|
|
self.availableMonsters = visible
|
|
self.monsterTopSpacerPxA, self.monsterTopSpacerPxB, self.monsterTopSpacerPxC =
|
|
splitSpacerPx((startIndex - 1) * rowHeight)
|
|
self.monsterBottomSpacerPxA, self.monsterBottomSpacerPxB, self.monsterBottomSpacerPxC = splitSpacerPx((total -
|
|
endIndex) *
|
|
rowHeight)
|
|
self._preferredViewportStart = startIndex
|
|
self._preferredViewportEnd = endIndex
|
|
self._preferredViewportRowHeight = rowHeight
|
|
self._preferredViewportRefreshing = false
|
|
end
|
|
|
|
function TaskBoardController:rebuildAvailableMonsters()
|
|
local usedIds = {}
|
|
for _, slotData in ipairs(cachedRawSlots) do
|
|
local prefId = tonumber(slotData.preferred) or 0
|
|
local unwId = tonumber(slotData.unwanted) or 0
|
|
if prefId > 0 then
|
|
usedIds[prefId] = true
|
|
end
|
|
if unwId > 0 then
|
|
usedIds[unwId] = true
|
|
end
|
|
end
|
|
|
|
local filter = (self.searchText or ""):lower()
|
|
|
|
local sorted = {}
|
|
for _, raceId in ipairs(cachedAllRaceIds) do
|
|
if not usedIds[raceId] then
|
|
local rd = g_things.getRaceData(raceId)
|
|
local name = rd and rd.name or 'Unknown'
|
|
name = name:capitalize()
|
|
if filter == '' or name:lower():find(filter, 1, true) then
|
|
table.insert(sorted, {
|
|
raceId = raceId,
|
|
name = name,
|
|
outfit = rd and rd.outfit or nil
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
table.sort(sorted, function(a, b)
|
|
return a.name < b.name
|
|
end)
|
|
|
|
self.allAvailableMonsters = sorted
|
|
self._preferredViewportStart = 0
|
|
self._preferredViewportEnd = 0
|
|
self._preferredViewportRowHeight = 0
|
|
self._preferredRowHeightMeasured = false
|
|
self._preferredRowHeightPx = MONSTER_ROW_HEIGHT_PX
|
|
self:ensurePreferredMonsterScrollBound()
|
|
self:resetPreferredMonsterScroll()
|
|
end
|
|
|
|
-- HTML event handlers
|
|
function TaskBoardController:filterMonsters(text)
|
|
self.searchText = text or ""
|
|
self:rebuildAvailableMonsters()
|
|
end
|
|
|
|
function TaskBoardController:selectMonster(raceId)
|
|
self.selectedRaceId = tonumber(raceId) or 0
|
|
self:rebuildPreferredSlots()
|
|
end
|
|
|
|
function TaskBoardController:isRaceAssigned(raceId, targetSlotNum, targetField)
|
|
local targetRaceId = tonumber(raceId) or 0
|
|
local targetSlot = tonumber(targetSlotNum) or 0
|
|
if targetRaceId == 0 then
|
|
return false
|
|
end
|
|
|
|
for index, slotData in ipairs(cachedRawSlots) do
|
|
local slotNum = tonumber(slotData.slot) or index
|
|
local preferredId = tonumber(slotData.preferred) or 0
|
|
local unwantedId = tonumber(slotData.unwanted) or 0
|
|
|
|
if preferredId == targetRaceId and not (slotNum == targetSlot and targetField == 'preferred') then
|
|
return true
|
|
end
|
|
|
|
if unwantedId == targetRaceId and not (slotNum == targetSlot and targetField == 'unwanted') then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function TaskBoardController:onPreferredSlotAction(slotNum, action, cost)
|
|
if action == 'assignPreferred' then
|
|
self:assignPreferred(slotNum)
|
|
elseif action == 'assignUnwanted' then
|
|
self:assignUnwanted(slotNum)
|
|
elseif action == 'clearPreferred' then
|
|
self:clearPreferred(slotNum, cost)
|
|
elseif action == 'clearUnwanted' then
|
|
self:clearUnwanted(slotNum, cost)
|
|
end
|
|
end
|
|
|
|
function TaskBoardController:assignPreferred(slotNum)
|
|
if self.selectedRaceId == 0 then
|
|
return
|
|
end
|
|
local parsedSlot = tonumber(slotNum) or 0
|
|
if parsedSlot == 0 then
|
|
return
|
|
end
|
|
if self:isRaceAssigned(self.selectedRaceId, parsedSlot, 'preferred') then
|
|
return
|
|
end
|
|
|
|
g_game.bountyPreferredAction(BOUNTY_PREF_ACTION_SET_PREFERRED, parsedSlot, self.selectedRaceId)
|
|
updateCachedSlot(parsedSlot, 'preferred', self.selectedRaceId)
|
|
self.selectedRaceId = 0
|
|
self:rebuildPreferredSlots()
|
|
self:rebuildAvailableMonsters()
|
|
end
|
|
|
|
function TaskBoardController:assignUnwanted(slotNum)
|
|
if self.selectedRaceId == 0 then
|
|
return
|
|
end
|
|
local parsedSlot = tonumber(slotNum) or 0
|
|
if parsedSlot == 0 then
|
|
return
|
|
end
|
|
if self:isRaceAssigned(self.selectedRaceId, parsedSlot, 'unwanted') then
|
|
return
|
|
end
|
|
|
|
g_game.bountyPreferredAction(BOUNTY_PREF_ACTION_SET_UNWANTED, parsedSlot, self.selectedRaceId)
|
|
updateCachedSlot(parsedSlot, 'unwanted', self.selectedRaceId)
|
|
self.selectedRaceId = 0
|
|
self:rebuildPreferredSlots()
|
|
self:rebuildAvailableMonsters()
|
|
end
|
|
|
|
function TaskBoardController:clearPreferred(slotNum, cost)
|
|
local parsedSlot = tonumber(slotNum) or 0
|
|
if parsedSlot == 0 then
|
|
return
|
|
end
|
|
local parsedCost = tonumber(cost) or getPreferredActionCost()
|
|
|
|
local msgBox
|
|
local function yes()
|
|
if msgBox then
|
|
msgBox:destroy();
|
|
msgBox = nil
|
|
end
|
|
g_game.bountyPreferredAction(BOUNTY_PREF_ACTION_REMOVE_PREFERRED, parsedSlot, 0)
|
|
updateCachedSlot(parsedSlot, 'preferred', 0)
|
|
self:rebuildPreferredSlots()
|
|
self:rebuildAvailableMonsters()
|
|
end
|
|
local function cancel()
|
|
if msgBox then
|
|
msgBox:destroy();
|
|
msgBox = nil
|
|
end
|
|
end
|
|
msgBox = displayGeneralBox(tr('Clear Preferred'),
|
|
tr('Remove preferred monster? This costs %d Bounty Task Points.', parsedCost), {{
|
|
text = tr('Yes'),
|
|
callback = yes
|
|
}, {
|
|
text = tr('Cancel'),
|
|
callback = cancel
|
|
}}, yes, cancel)
|
|
end
|
|
|
|
function TaskBoardController:clearUnwanted(slotNum, cost)
|
|
local parsedSlot = tonumber(slotNum) or 0
|
|
if parsedSlot == 0 then
|
|
return
|
|
end
|
|
local parsedCost = tonumber(cost) or getPreferredActionCost()
|
|
|
|
local msgBox
|
|
local function yes()
|
|
if msgBox then
|
|
msgBox:destroy();
|
|
msgBox = nil
|
|
end
|
|
g_game.bountyPreferredAction(BOUNTY_PREF_ACTION_REMOVE_UNWANTED, parsedSlot, 0)
|
|
updateCachedSlot(parsedSlot, 'unwanted', 0)
|
|
self:rebuildPreferredSlots()
|
|
self:rebuildAvailableMonsters()
|
|
end
|
|
local function cancel()
|
|
if msgBox then
|
|
msgBox:destroy();
|
|
msgBox = nil
|
|
end
|
|
end
|
|
msgBox = displayGeneralBox(tr('Clear Unwanted'),
|
|
tr('Remove unwanted monster? This costs %d Bounty Task Points.', parsedCost), {{
|
|
text = tr('Yes'),
|
|
callback = yes
|
|
}, {
|
|
text = tr('Cancel'),
|
|
callback = cancel
|
|
}}, yes, cancel)
|
|
end
|
|
|
|
function TaskBoardController:buyPreferredSlot(slotNum, price)
|
|
local parsedSlot = tonumber(slotNum) or 0
|
|
if parsedSlot == 0 then
|
|
return
|
|
end
|
|
local parsedPrice = tonumber(price) or getPreferredUnlockPrice(parsedSlot, 0)
|
|
|
|
local msgBox
|
|
local function yes()
|
|
if msgBox then
|
|
msgBox:destroy();
|
|
msgBox = nil
|
|
end
|
|
g_game.bountyPreferredAction(BOUNTY_PREF_ACTION_BUY_SLOT, parsedSlot, 0)
|
|
end
|
|
local function cancel()
|
|
if msgBox then
|
|
msgBox:destroy();
|
|
msgBox = nil
|
|
end
|
|
end
|
|
msgBox = displayGeneralBox(tr('Unlock Slot'),
|
|
tr('Unlock this preferred slot for %d Bounty Task Points?', parsedPrice), {{
|
|
text = tr('Yes'),
|
|
callback = yes
|
|
}, {
|
|
text = tr('Cancel'),
|
|
callback = cancel
|
|
}}, yes, cancel)
|
|
end
|