diff --git a/data/images/game/prey/prey_choose_blocked.png b/data/images/game/prey/prey_choose_blocked.png new file mode 100644 index 0000000000..87172b1e27 Binary files /dev/null and b/data/images/game/prey/prey_choose_blocked.png differ diff --git a/modules/game_prey/prey.lua b/modules/game_prey/prey.lua index ecd9c9b8d0..b13f6f2d7a 100644 --- a/modules/game_prey/prey.lua +++ b/modules/game_prey/prey.lua @@ -21,10 +21,26 @@ local PREY_ACTION_BONUSREROLL = 1 local PREY_ACTION_MONSTERSELECTION = 2 local PREY_ACTION_REQUEST_ALL_MONSTERS = 3 local PREY_ACTION_CHANGE_FROM_ALL = 4 -local PREY_ACTION_LOCK_PREY = 5 +local PREY_ACTION_OPTION = 5 +local PREY_OPTION_UNTOGGLE = 0 +local PREY_OPTION_TOGGLE_AUTOREROLL = 1 +local PREY_OPTION_TOGGLE_LOCK_PREY = 2 local preyDescription = {} +local raceEntriesBySlot = {} +local selectedRaceEntryBySlot = {} +local selectedRaceWidgetBySlot = {} +local raceSearchTextsBySlot = {} + +local refreshRaceList +local setRaceSelection +local updateRaceSelectionDisplay +local restoreRaceListItemBackground +local setWidgetTreePhantom +local updatePickSpecificPreyButton +local refreshRerollButtonState + function bonusDescription(bonusType, bonusValue, bonusGrade) if bonusType == PREY_BONUS_DAMAGE_BOOST then return 'Damage bonus (' .. bonusGrade .. '/10)' @@ -55,7 +71,7 @@ end function init() connect(g_game, { onGameStart = check, - onGameEnd = hide, + onGameEnd = onGameEnd, onResourcesBalanceChange = Prey.onResourcesBalanceChange, onPreyFreeRerolls = onPreyFreeRerolls, onPreyTimeLeft = onPreyTimeLeft, @@ -76,23 +92,23 @@ function init() preyTracker:setContentMaximumHeight(110) preyTracker:setContentMinimumHeight(70) preyTracker:hide() - + -- Hide buttons similar to unjustifiedpoints implementation local toggleFilterButton = preyTracker:recursiveGetChildById('toggleFilterButton') if toggleFilterButton then toggleFilterButton:setVisible(false) end - + local contextMenuButton = preyTracker:recursiveGetChildById('contextMenuButton') if contextMenuButton then contextMenuButton:setVisible(false) end - + local newWindowButton = preyTracker:recursiveGetChildById('newWindowButton') if newWindowButton then newWindowButton:setVisible(false) end - + -- Set up the miniwindow title and icon local titleWidget = preyTracker:getChildById('miniwindowTitle') if titleWidget then @@ -101,42 +117,102 @@ function init() -- Fallback to old method if miniwindowTitle doesn't exist preyTracker:setText('Prey') end - + local iconWidget = preyTracker:getChildById('miniwindowIcon') if iconWidget then iconWidget:setImageSource('/images/game/prey/icon-prey-widget') end - + -- Position lockButton where toggleFilterButton was (to the left of minimize button) local lockButton = preyTracker:recursiveGetChildById('lockButton') local minimizeButton = preyTracker:recursiveGetChildById('minimizeButton') - + if lockButton and minimizeButton then lockButton:breakAnchors() lockButton:addAnchor(AnchorTop, minimizeButton:getId(), AnchorTop) lockButton:addAnchor(AnchorRight, minimizeButton:getId(), AnchorLeft) - lockButton:setMarginRight(7) -- Same margin as toggleFilterButton had + lockButton:setMarginRight(7) -- Same margin as toggleFilterButton had lockButton:setMarginTop(0) end - + if g_game.isOnline() then check() end setUnsupportedSettings() end +local pickSpecificPreyBonusBySlot = {} + +local pickSpecificPreyDescriptionDefault = +'If you like to select another prey creature, click here to choose from all available creatures.\nThe newly selected prey will be active for 2 hours hunting time again.\nYour current bonus will not be affected.' +local pickSpecificPreyDescriptionTemplate = +'If you like to select another prey creature, click here to choose from all available creatures.\nThe newly selected prey will be active for 2 hours hunting time again.\nYour current bonus +%s%% %s will not be affected.' + local descriptionTable = { - ['shopPermButton'] = 'Go to the Store to purchase the Permanent Prey Slot. Once you have completed the purchase, you can activate a prey here, no matter if your character is on a free or a Premium account.', + ['shopPermButton'] = + 'Go to the Store to purchase the Permanent Prey Slot. Once you have completed the purchase, you can activate a prey here, no matter if your character is on a free or a Premium account.', ['shopTempButton'] = 'You can activate this prey whenever your account has Premium Status.', ['preyWindow'] = '', - ['noBonusIcon'] = 'This prey is not available for your character yet.\nCheck the large blue button(s) to learn how to unlock this prey slot', - ['selectPrey'] = 'Click here to get a bonus with a higher value. The bonus for your prey will be selected randomly from one of the following: damage boost, damage reduction, bonus XP, improved loot. Your prey will be active for 2 hours hunting time again. Your prey creature will stay the same.', - ['pickSpecificPrey'] = 'Available only for protocols 12+', - ['rerollButton'] = 'If you like to select another prey crature, click here to get a new list with 9 creatures to choose from.\nThe newly selected prey will be active for 2 hours hunting time again.', + ['noBonusIcon'] = + 'This prey is not available for your character yet.\nCheck the large blue button(s) to learn how to unlock this prey slot', + ['selectPrey'] = + 'Click here to get a bonus with a higher value. The bonus for your prey will be selected randomly from one of the following: damage boost, damage reduction, bonus XP, improved loot. Your prey will be active for 2 hours hunting time again. Your prey creature will stay the same.', + ['pickSpecificPrey'] = pickSpecificPreyDescriptionDefault, + ['rerollButton'] = + 'If you like to select another prey crature, click here to get a new list with 9 creatures to choose from.\nThe newly selected prey will be active for 2 hours hunting time again.', ['preyCandidate'] = 'Select a new prey creature for the next 2 hours hunting time.', - ['choosePreyButton'] = 'Click on this button to confirm selected monsters as your prey creature for the next 2 hours hunting time.' + ['choosePreyButton'] = + 'Click on this button to confirm selected monsters as your prey creature for the next 2 hours hunting time.', + ['automaticBonusReroll'] = + 'Do you want to enable the Automatic Bonus Reroll?\nEach time the Automatic Bonus Reroll is triggered, 1 of your Prey Wildcards will be consumed.', + ['preyLock'] = + 'Do you want to enable the Lock Prey?\nEach time the Lock Prey is triggered, 5 of your Prey Wildcards will be consumed.' } +local function getSlotIndexFromWidget(widget) + while widget do + local id = widget:getId() + if id then + local slotNumber = id:match('^slot(%d+)$') + if slotNumber then + return tonumber(slotNumber) - 1 + end + end + widget = widget:getParent() + end + + return nil +end + +local function getPickSpecificPreyDescription(slot) + local bonusInfo = pickSpecificPreyBonusBySlot[slot] + + if bonusInfo then + local bonusName = getBonusDescription(bonusInfo.type) + + if bonusName then + return string.format(pickSpecificPreyDescriptionTemplate, bonusInfo.value, bonusName) + end + end + + return pickSpecificPreyDescriptionDefault +end + +local function setPickSpecificPreyBonus(slot, bonusType, bonusValue) + if slot == nil then + return + end + + if bonusType and bonusType ~= PREY_BONUS_NONE and bonusValue ~= nil then + pickSpecificPreyBonusBySlot[slot] = { + type = bonusType, + value = bonusValue + } + else + pickSpecificPreyBonusBySlot[slot] = nil + end +end + function onHover(widget) if type(widget) == 'string' then return preyWindow.description:setText(descriptionTable[widget]) @@ -147,8 +223,16 @@ function onHover(widget) desc = desc:sub(1, desc:len() - 46) return preyWindow.description:setText(desc) end - if widget:isVisible() then + if widget and widget:isVisible() then local id = widget:getId() + if id == 'pickSpecificPrey' then + local slot = getSlotIndexFromWidget(widget) + if slot then + preyWindow.description:setText(getPickSpecificPreyDescription(slot)) + return + end + end + local desc = descriptionTable[id] if desc then preyWindow.description:setText(desc) @@ -159,7 +243,7 @@ end function terminate() disconnect(g_game, { onGameStart = check, - onGameEnd = hide, + onGameEnd = onGameEnd, onResourcesBalanceChange = Prey.onResourcesBalanceChange, onPreyFreeRerolls = onPreyFreeRerolls, onPreyTimeLeft = onPreyTimeLeft, @@ -189,17 +273,15 @@ end local n = 0 function setUnsupportedSettings() - local t = {'slot1', 'slot2', 'slot3'} + local t = { 'slot1', 'slot2', 'slot3' } for i, slot in pairs(t) do local panel = preyWindow[slot] - for j, state in pairs({panel.active, panel.inactive}) do - state.select.price.text:setText('-------') + for j, state in pairs({ panel.active, panel.inactive }) do + state.select.price.text:setText('5') end - panel.active.autoRerollPrice.text:setText('-') - panel.active.lockPreyPrice.text:setText('-') + panel.active.autoRerollPrice.text:setText('1') + panel.active.lockPreyPrice.text:setText('5') panel.active.choose.price.text:setText(1) - panel.active.autoReroll.autoRerollCheck:disable() - panel.active.lockPrey.lockPreyCheck:disable() end end @@ -207,11 +289,11 @@ function check() if g_game.getFeature(GamePrey) then if not preyButton then preyButton = modules.game_mainpanel.addToggleButton('preyButton', tr('Prey Dialog'), - '/images/options/button_preydialog', toggle) + '/images/options/button_preydialog', toggle) end if not preyTrackerButton then preyTrackerButton = modules.game_mainpanel.addToggleButton('preyTrackerButton', tr('Prey Tracker'), - '/images/options/button_prey', toggleTracker) + '/images/options/button_prey', toggleTracker) end elseif preyButton then preyButton:destroy() @@ -235,6 +317,78 @@ function toggleTracker() end end +local function resetPreyWindowState() + if not preyWindow then + return + end + + preyWindow.description:setText('') + preyWindow.gold:setText('0') + preyWindow.wildCards:setText('0') + + for slot = 0, 2 do + onPreyInactive(slot, 0, 0) + + local prey = preyWindow['slot' .. (slot + 1)] + if prey then + if prey.title then + prey.title:setText('') + end + + if prey.inactive and prey.inactive.list then + prey.inactive.list:setVisible(true) + prey.inactive.list:destroyChildren() + end + + if prey.inactive then + if prey.inactive.fullList then + clearFullListEntries(prey.inactive.fullList) + prey.inactive.fullList:setVisible(false) + end + + if prey.inactive.preview then + resetPreyPreviewWidget(prey.inactive.preview) + prey.inactive.preview:setVisible(false) + end + + if prey.inactive.select then + prey.inactive.select:setVisible(true) + end + + if prey.inactive.reroll then + prey.inactive.reroll:setVisible(true) + end + end + + if prey.active and prey.active.creatureAndBonus then + local creatureAndBonus = prey.active.creatureAndBonus + if creatureAndBonus.timeLeft then + creatureAndBonus.timeLeft:setPercent(0) + creatureAndBonus.timeLeft:setText('') + end + + if creatureAndBonus.bonus and creatureAndBonus.bonus.grade then + creatureAndBonus.bonus.grade:destroyChildren() + end + end + end + end + + preyDescription = {} + rerollPrice = 0 + bonusRerolls = 0 + bankGold = 0 + inventoryGold = 0 + raceEntriesBySlot = {} + selectedRaceEntryBySlot = {} + selectedRaceWidgetBySlot = {} +end + +function onGameEnd() + resetPreyWindowState() + hide() +end + function hide() preyWindow:hide() if msgWindow then @@ -275,7 +429,7 @@ function onPreyFreeRerolls(slot, timeleft) if not prey then return end - for i, panel in pairs({prey.active, prey.inactive}) do + for i, panel in pairs({ prey.active, prey.inactive }) do local progressBar = panel.reroll.button.time local price = panel.reroll.price.text progressBar:setPercent(percent) @@ -299,7 +453,7 @@ function onPreyTimeLeft(slot, timeLeft) local tracker = preyTracker.contentsPanel[slot] tracker.time:setPercent(percent) tracker.time:setTooltip(text) - for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do + for i, element in pairs({ tracker.creatureName, tracker.creature, tracker.preyType, tracker.time }) do element:setTooltip(text) element.onClick = function() show() @@ -318,19 +472,25 @@ end function onPreyRerollPrice(price) rerollPrice = price - local t = {'slot1', 'slot2', 'slot3'} - for i, slot in pairs(t) do + local t = { 'slot1', 'slot2', 'slot3' } + for index, slot in ipairs(t) do local panel = preyWindow[slot] - for j, state in pairs({panel.active, panel.inactive}) do - local price = state.reroll.price.text - local progressBar = state.reroll.button.time - if progressBar:getText() ~= 'Free' then - price:setText(comma_value(rerollPrice)) - else - price:setText('0') - progressBar:setPercent(0) + if panel then + for j, state in pairs({ panel.active, panel.inactive }) do + if state and state.reroll and state.reroll.price and state.reroll.button then + local priceWidget = state.reroll.price.text + local progressBar = state.reroll.button.time + if progressBar:getText() ~= 'Free' then + priceWidget:setText(comma_value(rerollPrice)) + else + priceWidget:setText('0') + progressBar:setPercent(0) + end + end end end + + refreshRerollButtonState(index - 1) end end @@ -340,21 +500,28 @@ function setTimeUntilFreeReroll(slot, timeUntilFreeReroll) -- minutes return end local percent = (timeUntilFreeReroll / (20 * 60)) * 100 - local desc = timeleftTranslation(timeUntilFreeReroll * 60) - for i, panel in pairs({prey.active, prey.inactive}) do - local reroll = panel.reroll.button.time - reroll:setPercent(percent) - reroll:setText(desc) - local price = panel.reroll.price.text - if timeUntilFreeReroll > 0 then - price:setText(comma_value(rerollPrice)) - else - price:setText('Free') + timeUntilFreeReroll = timeUntilFreeReroll > 720000 and 0 or timeUntilFreeReroll + local desc = timeleftTranslation(timeUntilFreeReroll) + for i, panel in pairs({ prey.active, prey.inactive }) do + if panel and panel.reroll and panel.reroll.button and panel.reroll.price then + local reroll = panel.reroll.button.time + reroll:setPercent(percent) + reroll:setText(desc) + local price = panel.reroll.price.text + if timeUntilFreeReroll > 0 then + price:setText(comma_value(rerollPrice)) + else + price:setText('Free') + end end end + + refreshRerollButtonState(slot) end function onPreyLocked(slot, unlockState, timeUntilFreeReroll, wildcards) + setPickSpecificPreyBonus(slot) + -- tracker slot = 'slot' .. (slot + 1) local tracker = preyTracker.contentsPanel[slot] @@ -382,7 +549,7 @@ function onPreyInactive(slot, timeUntilFreeReroll, wildcards) tracker.creatureName:setText('Inactive') tracker.time:setPercent(0) tracker.preyType:setImageSource('/images/game/prey/prey_no_bonus') - for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do + for i, element in pairs({ tracker.creatureName, tracker.creature, tracker.preyType, tracker.time }) do element:setTooltip('Inactive Prey. \n\nClick in this window to open the prey dialog.') element.onClick = function() show() @@ -398,12 +565,20 @@ function onPreyInactive(slot, timeUntilFreeReroll, wildcards) prey.active:hide() prey.locked:hide() prey.inactive:show() - local rerollButton = prey.inactive.reroll.button.rerollButton - rerollButton:setImageSource('/images/game/prey/prey_reroll_blocked') - rerollButton:disable() - rerollButton.onClick = function() - g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + setInactiveMode(slot, false, prey) + raceEntriesBySlot[slot] = nil + selectedRaceEntryBySlot[slot] = nil + selectedRaceWidgetBySlot[slot] = nil + local rerollButton = prey.inactive.reroll and prey.inactive.reroll.button and + prey.inactive.reroll.button.rerollButton + if rerollButton then + rerollButton.onClick = function() + showListRerollConfirmation(slot) + end end + + setPickSpecificPreyBonus(slot) + updatePickSpecificPreyButton(slot, wildcards) end function setBonusGradeStars(slot, grade) @@ -485,190 +660,1256 @@ function capitalFormatStr(str) return formatted:trim() end -function onItemBoxChecked(widget) - - for i, slot in pairs({'slot1', 'slot2', 'slot3'}) do - local list = preyWindow[slot].inactive.list:getChildren() - if table.find(list, widget) then - for i, child in pairs(list) do - if child ~= widget then - child:setChecked(false) - end - end - end +local function getPreySlotWidget(slot) + if not preyWindow then + return nil end - widget:setChecked(true) + return preyWindow['slot' .. (slot + 1)] end -function onPreyActive(slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, - timeUntilFreeReroll, wildcards) -- locktype always 0 for protocols <12 - local tracker = preyTracker.contentsPanel['slot' .. (slot + 1)] - currentHolderName = capitalFormatStr(currentHolderName) - local percent = (timeLeft / (2 * 60 * 60)) * 100 - if tracker then - tracker.creature:show() - tracker.noCreature:hide() - tracker.creatureName:setText(currentHolderName) - tracker.creature:setOutfit(currentHolderOutfit) - tracker.preyType:setImageSource(getSmallIconPath(bonusType)) - tracker.time:setPercent(percent) - preyDescription[slot] = preyDescription[slot] or {} - preyDescription[slot].one = 'Creature: ' .. currentHolderName .. '\nDuration: ' - preyDescription[slot].two = - '\nValue: ' .. bonusGrade .. '/10' .. '\nType: ' .. getBonusDescription(bonusType) .. '\n' .. - getTooltipBonusDescription(bonusType, bonusValue) .. '\n\nClick in this window to open the prey dialog.' - for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do - element:setTooltip(preyDescription[slot].one .. timeleftTranslation(timeLeft, true) .. - preyDescription[slot].two) - element.onClick = function() - show() - end - end - end - local prey = preyWindow['slot' .. (slot + 1)] - if not prey then +local function setChoosePreyButtonEnabled(button, enabled) + if not button then return end - prey.inactive:hide() - prey.locked:hide() - prey.active:show() - prey.title:setText(currentHolderName) - local creatureAndBonus = prey.active.creatureAndBonus - creatureAndBonus.creature:setOutfit(currentHolderOutfit) - setTimeUntilFreeReroll(slot, timeUntilFreeReroll) - creatureAndBonus.bonus.icon:setImageSource(getBigIconPath(bonusType)) - creatureAndBonus.bonus.icon.onHoverChange = function(widget, hovered) - onHover(slot) - end - setBonusGradeStars(slot, bonusGrade) - creatureAndBonus.timeLeft:setPercent(percent) - creatureAndBonus.timeLeft:setText(timeleftTranslation(timeLeft)) - -- bonus reroll - prey.active.choose.selectPrey.onClick = function() - g_game.preyAction(slot, PREY_ACTION_BONUSREROLL, 0) - end - -- creature reroll - prey.active.reroll.button.rerollButton.onClick = function() - g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + + local imagePath + if enabled then + button:enable() + imagePath = '/images/game/prey/prey_choose.png' + else + button:disable() + imagePath = '/images/game/prey/prey_choose_blocked.png' end + + button:setImageSource(imagePath) end -function onPreySelection(slot, names, outfits, timeUntilFreeReroll, wildcards) - -- tracker - local tracker = preyTracker.contentsPanel['slot' .. (slot + 1)] - if tracker then - tracker.creature:hide() - tracker.noCreature:show() - tracker.creatureName:setText('Inactive') - tracker.time:setPercent(0) - tracker.preyType:setImageSource('/images/game/prey/prey_no_bonus') - for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do - element:setTooltip('Inactive Prey. \n\nClick in this window to open the prey dialog.') - element.onClick = function() - show() - end - end - end - -- main window - local prey = preyWindow['slot' .. (slot + 1)] - setTimeUntilFreeReroll(slot, timeUntilFreeReroll) - if not prey then +local function updateChoosePreyButtonState(slot) + local prey = getPreySlotWidget(slot) + if not prey or not prey.inactive or not prey.inactive.choose then return end - prey.active:hide() - prey.locked:hide() - prey.inactive:show() - prey.title:setText(tr('Select monster')) - local rerollButton = prey.inactive.reroll.button.rerollButton - rerollButton.onClick = function() - g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + + local button = prey.inactive.choose.choosePreyButton + if not button then + return end + + local hasSelection = false local list = prey.inactive.list - list:destroyChildren() - for i, name in ipairs(names) do - local box = g_ui.createWidget('PreyCreatureBox', list) - name = capitalFormatStr(name) - box:setTooltip(name) - box.creature:setOutfit(outfits[i]) - end - prey.inactive.choose.choosePreyButton.onClick = function() - for i, child in pairs(list:getChildren()) do - if child:isChecked() then - return g_game.preyAction(slot, PREY_ACTION_MONSTERSELECTION, i - 1) + if list then + for _, child in pairs(list:getChildren()) do + if child.isChecked and child:isChecked() then + hasSelection = true + break end end - return showMessage(tr('Error'), tr('Select monster to proceed.')) end + + if not hasSelection and selectedRaceEntryBySlot[slot] then + hasSelection = true + end + + setChoosePreyButtonEnabled(button, hasSelection) end -function onPreySelectionChangeMonster(slot, names, outfits, bonusType, bonusValue, bonusGrade, timeUntilFreeReroll, - wildcards) - -- tracker - local tracker = preyTracker.contentsPanel['slot' .. (slot + 1)] - if tracker then - tracker.creature:hide() - tracker.noCreature:show() - tracker.creatureName:setText('Inactive') - tracker.time:setPercent(0) - tracker.preyType:setImageSource('/images/game/prey/prey_no_bonus') - for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do - element:setTooltip('Inactive Prey. \n\nClick in this window to open the prey dialog.') - element.onClick = function() - show() - end +local function getWildcardCountOrDefault(wildcards) + local playerBalance + local player = g_game.getLocalPlayer() + if player and ResourceTypes and ResourceTypes.PREY_WILDCARDS then + playerBalance = player:getResourceBalance(ResourceTypes.PREY_WILDCARDS) + end + + if type(wildcards) == 'number' then + if playerBalance then + return math.max(wildcards, playerBalance) end + return wildcards end - -- main window - local prey = preyWindow['slot' .. (slot + 1)] - setTimeUntilFreeReroll(slot, timeUntilFreeReroll) - if not prey then - return + + return playerBalance or 0 +end + +local function showPreyConfirmationWindow(title, description, confirmAction) + local confirmWindow + local wasPreyWindowVisible = preyWindow and preyWindow:isVisible() + local preyVisibilityRestored = false + + local function restorePreyWindowVisibility() + if not preyVisibilityRestored and wasPreyWindowVisible and preyWindow then + preyVisibilityRestored = true + preyWindow:show() + preyWindow:raise() + preyWindow:focus() + end end - prey.active:hide() - prey.locked:hide() - prey.inactive:show() - prey.title:setText(tr('Select monster')) - local rerollButton = prey.inactive.reroll.button.rerollButton - rerollButton.onClick = function() - g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + + if wasPreyWindowVisible then + preyWindow:hide() end - local list = prey.inactive.list - list:destroyChildren() - for i, name in ipairs(names) do - local box = g_ui.createWidget('PreyCreatureBox', list) - name = capitalFormatStr(name) - box:setTooltip(name) - box.creature:setOutfit(outfits[i]) + + local function closeWindow() + if confirmWindow then + confirmWindow:destroy() + confirmWindow = nil + restorePreyWindowVisibility() + end end - prey.inactive.choose.choosePreyButton.onClick = function() - for i, child in pairs(list:getChildren()) do - if child:isChecked() then - return g_game.preyAction(slot, PREY_ACTION_MONSTERSELECTION, i - 1) - end + + local function confirm() + if confirmAction then + confirmAction() end - return showMessage(tr('Error'), tr('Select monster to proceed.')) + closeWindow() + end + + confirmWindow = displayGeneralBox(tr(title), description, { + { + text = tr('No'), + callback = closeWindow + }, + { + text = tr('Yes'), + callback = confirm + }, + }, confirm, closeWindow) + + if confirmWindow then + confirmWindow.onDestroy = restorePreyWindowVisibility + else + restorePreyWindowVisibility() end end -function onPreyListSelection(slot, races, nextFreeReroll, wildcards) +local function showPreyWildcardConfirmation(description, confirmAction) + showPreyConfirmationWindow('Confirmation of Using Prey Wildcards', description, confirmAction) end -function onPreyWildcardSelection(slot, races, nextFreeReroll, wildcards) +local function showPickSpecificPreyConfirmation(slot, wildcardCount) + local description = tr(string.format( + 'Are you sure you want to use 5 of your remaining %s Prey Wildcards?', + tostring(wildcardCount) + )) + + showPreyWildcardConfirmation(description, function() + g_game.preyAction(slot, PREY_ACTION_REQUEST_ALL_MONSTERS, 0) + end) end -function Prey.onResourcesBalanceChange(balance, oldBalance, type) - if type == ResourceTypes.BANK_BALANCE then -- bank gold - bankGold = balance - elseif type == ResourceTypes.GOLD_EQUIPPED then -- inventory gold - inventoryGold = balance - elseif type == ResourceTypes.PREY_WILDCARDS then -- bonus rerolls - bonusRerolls = balance - end +local function showBonusRerollConfirmation(slot, wildcardCount) + local description = tr(string.format( + 'Are you sure you want to use 1 of your remaining %s Prey Wildcards?', + tostring(wildcardCount) + )) + + showPreyWildcardConfirmation(description, function() + g_game.preyAction(slot, PREY_ACTION_BONUSREROLL, 0) + end) +end + +local function getPlayerTotalGold() local player = g_game.getLocalPlayer() - g_logger.debug('' .. tostring(type) .. ', ' .. tostring(balance)) - if player then - preyWindow.wildCards:setText(tostring(player:getResourceBalance(ResourceTypes.PREY_WILDCARDS))) - preyWindow.gold:setText(comma_value(player:getTotalMoney())) + if player and player.getTotalMoney then + return player:getTotalMoney() + end + + return (bankGold or 0) + (inventoryGold or 0) +end + +local function getRerollPriceFromPanel(panel) + if not panel or not panel.reroll or not panel.reroll.price then + return nil + end + + local priceWidget = panel.reroll.price.text + if not priceWidget then + return nil + end + + local text = priceWidget:getText() + if not text or text == '' then + return nil + end + + if text:lower() == 'free' then + return 0 + end + + local digits = text:gsub('[^%d]', '') + if digits == '' then + return nil + end + + return tonumber(digits) +end + +local function getDisplayedRerollPrice(slot) + local prey = getPreySlotWidget(slot) + if not prey then + return rerollPrice + end + + local function getPrice(panel, requireVisibility) + if not panel then + return nil + end + + if requireVisibility and panel.isVisible and not panel:isVisible() then + return nil + end + + return getRerollPriceFromPanel(panel) + end + + local price = getPrice(prey.active, true) or getPrice(prey.inactive, true) + if price ~= nil then + return price + end + + price = getPrice(prey.active, false) or getPrice(prey.inactive, false) + if price ~= nil then + return price + end + + return rerollPrice +end + +function refreshRerollButtonState(slot) + local prey = getPreySlotWidget(slot) + if not prey then + return + end + + local totalGold = getPlayerTotalGold() + + local function updatePanel(panel) + if not panel or not panel.reroll or not panel.reroll.button then + return + end + + local button = panel.reroll.button.rerollButton + if not button then + return + end + + local price = getRerollPriceFromPanel(panel) + if price == nil then + price = rerollPrice + end + + local isFree = price == nil or price <= 0 + local canAfford = isFree or totalGold >= price + + if canAfford then + button:setImageSource('/images/game/prey/prey_reroll') + button:enable() + else + button:setImageSource('/images/game/prey/prey_reroll_blocked') + button:disable() + end + end + + updatePanel(prey.active) + updatePanel(prey.inactive) +end + +local function showListRerollConfirmation(slot) + local price = getDisplayedRerollPrice(slot) + + if price == nil then + price = rerollPrice or 0 + end + + if price <= 0 then + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + return + end + + local totalGold = getPlayerTotalGold() + local description = tr(string.format( + 'Do you want to spend %s gold for a List Reroll?\nYou current have %s gold available for the purchase.', + comma_value(price), + comma_value(totalGold) + )) + + showPreyConfirmationWindow('Confirmation of Using List Reroll', description, function() + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + end) +end + +function updatePickSpecificPreyButton(slot, wildcards) + local prey = getPreySlotWidget(slot) + if not prey then + return + end + + local wildcardCount = getWildcardCountOrDefault(wildcards) + local hasWildcardsAvailable = wildcardCount > 5 + + local function refreshButton(panel) + if not panel or not panel.select or not panel.select.pickSpecificPrey then + return + end + + local button = panel.select.pickSpecificPrey + + button:setTooltip(getPickSpecificPreyDescription(slot)) + + if hasWildcardsAvailable then + button:setImageSource('/images/game/prey/prey_select') + button:enable() + button.onClick = function() + showPickSpecificPreyConfirmation(slot, wildcardCount) + end + else + button:setImageSource('/images/game/prey/prey_select_blocked') + button:disable() + button.onClick = nil + end + end + + refreshButton(prey.active) + refreshButton(prey.inactive) +end + +function resetPreyPreviewWidget(preview) + if not preview then + return + end + + if preview.placeholder then + preview.placeholder:setVisible(true) + end + + if preview.creature then + preview.creature:setVisible(false) + end +end + +setWidgetTreePhantom = function(widget, phantom) + if not widget or widget:isDestroyed() then + return + end + + widget:setPhantom(phantom) + + for _, child in pairs(widget:getChildren()) do + setWidgetTreePhantom(child, phantom) + end +end + +function setInactiveMode(slot, showFullList, prey) + prey = prey or getPreySlotWidget(slot) + if not prey or not prey.inactive then + return prey + end + + local inactive = prey.inactive + + if inactive.list then + inactive.list:setVisible(not showFullList) + setWidgetTreePhantom(inactive.list, showFullList) + end + + if inactive.fullList then + inactive.fullList:setVisible(showFullList) + end + + if inactive.preview then + inactive.preview:setVisible(showFullList) + if not showFullList or not selectedRaceEntryBySlot[slot] then + resetPreyPreviewWidget(inactive.preview) + end + end + + if inactive.select then + inactive.select:setVisible(not showFullList) + end + + if inactive.reroll then + inactive.reroll:setVisible(not showFullList) + end + + return prey +end + +local function isDescendantOf(widget, ancestor) + if not widget or not ancestor then + return false + end + local current = widget + while current do + if current == ancestor then + return true + end + current = current:getParent() + end + return false +end + +local function getFullListEntriesContainer(fullList) + if not fullList then + return nil + end + + local entries = fullList.entries + if not entries then + return nil + end + + if entries.entriesContainer then + return entries.entriesContainer + end + + if entries.getChildById then + local contents = entries:getChildById('entriesContainer') or entries:getChildById('contentsPanel') + if contents then + entries.entriesContainer = contents + return contents + end + end + + entries.entriesContainer = entries + return entries +end + +function clearFullListEntries(fullList) + local container = getFullListEntriesContainer(fullList) + if container then + container:destroyChildren() + end + + if fullList and fullList.entries and fullList.entries.verticalScrollBar then + local scrollbar = fullList.entries.verticalScrollBar + if scrollbar.getMinimum and scrollbar.setValue then + scrollbar:setValue(scrollbar:getMinimum()) + end + end +end + +local suppressSearchTextHandler = false + +local function setSearchTextSilently(widget, text) + if not widget or widget:getText() == text then + return + end + + suppressSearchTextHandler = true + widget:setText(text) + suppressSearchTextHandler = false +end + +local function updateRaceSearchClearButton(slot) + local prey = getPreySlotWidget(slot) + if not prey or not prey.inactive or not prey.inactive.fullList then + return + end + + local fullList = prey.inactive.fullList + local clearButton = fullList.searchClearButton + if not clearButton then + return + end + + local text = raceSearchTextsBySlot[slot] + clearButton:setVisible(text and text:len() > 0) +end + +local function updateRaceSearchUI(slot) + local prey = getPreySlotWidget(slot) + if not prey or not prey.inactive or not prey.inactive.fullList then + return + end + + local fullList = prey.inactive.fullList + local searchEdit = fullList.searchEdit + local clearButton = fullList.searchClearButton + local text = raceSearchTextsBySlot[slot] or '' + + if searchEdit then + searchEdit.preySlot = slot + setSearchTextSilently(searchEdit, text) + searchEdit:setCursorPos(-1) + end + + if clearButton then + clearButton.preySlot = slot + clearButton.searchEdit = searchEdit + end + + updateRaceSearchClearButton(slot) +end + +local function uncheckChildrenExcept(parent, except) + if not parent then + return + end + for _, child in pairs(parent:getChildren()) do + if child ~= except and child.setChecked then + if not child:isDestroyed() then + child:setChecked(false) + restoreRaceListItemBackground(child) + end + end + uncheckChildrenExcept(child, except) + end +end + +function onItemBoxChecked(widget) + for _, slotId in ipairs({ 'slot1', 'slot2', 'slot3' }) do + local slotWidget = preyWindow[slotId] + if slotWidget and slotWidget.inactive then + local list = slotWidget.inactive.list + if list and isDescendantOf(widget, list) then + uncheckChildrenExcept(list, widget) + widget:setChecked(true) + local slotIndex = tonumber(slotId:match('slot(%d+)')) + if slotIndex then + updateChoosePreyButtonState(slotIndex - 1) + end + return + end + local fullList = slotWidget.inactive.fullList + if fullList then + local entriesContainer = getFullListEntriesContainer(fullList) or fullList + if entriesContainer and isDescendantOf(widget, entriesContainer) then + uncheckChildrenExcept(entriesContainer, widget) + widget:setChecked(true) + local slotIndex = tonumber(slotId:match('slot(%d+)')) + if slotIndex then + updateChoosePreyButtonState(slotIndex - 1) + end + return + end + end + end + end + widget:setChecked(true) +end + +local function setWidgetTextToFit(widget, text, formatter) + if not widget then + return '' + end + + text = text or '' + formatter = formatter or function(value) return value end + + local ellipsis = '...' + local paddingLeft = widget.getPaddingLeft and widget:getPaddingLeft() or 0 + local paddingRight = widget.getPaddingRight and widget:getPaddingRight() or 0 + local availableWidth = widget:getWidth() - paddingLeft - paddingRight + + if availableWidth <= 0 then + local formatted = formatter(text) + widget:setText(formatted) + return formatted + end + + local function setAndCheck(value) + local formattedValue = formatter(value) + widget:setText(formattedValue) + local textSize = widget:getTextSize() + return textSize.width <= availableWidth, formattedValue + end + + local fits, formatted = setAndCheck(text) + if fits then + return formatted + end + + local bestFormatted + local left, right = 0, #text + while left <= right do + local mid = math.floor((left + right) / 2) + local candidate = text:sub(1, mid) .. ellipsis + local candidateFits, candidateFormatted = setAndCheck(candidate) + if candidateFits then + bestFormatted = candidateFormatted + left = mid + 1 + else + right = mid - 1 + end + end + + if bestFormatted then + widget:setText(bestFormatted) + return bestFormatted + end + + local _, ellipsisFormatted = setAndCheck(ellipsis) + widget:setText(ellipsisFormatted) + return ellipsisFormatted +end + +local function buildRaceEntry(raceId) + local raceData = g_things.getRaceData(raceId) + local name = raceData and raceData.name or nil + + if name and name ~= '' then + name = capitalFormatStr(name) + else + name = tr('Unknown Creature (%d)', raceId) + end + + local outfit = raceData and raceData.outfit or nil + local realSize + + if outfit and outfit.type then + local creatureType = g_things.getThingType(outfit.type, ThingCategoryCreature) + realSize = creatureType and creatureType:getRealSize() or nil + end + + return { + raceId = raceId, + name = name, + searchName = name:lower(), + outfit = outfit, + realSize = realSize + } +end + +restoreRaceListItemBackground = function(widget) + if not widget or widget:isDestroyed() then + return + end + + if widget:isChecked() then + if widget.checkedBackground then + widget:setBackgroundColor(widget.checkedBackground) + end + if widget.checkedTextColor then + widget:setColor(widget.checkedTextColor) + end + return + end + + if widget.baseBackground then + widget:setBackgroundColor(widget.baseBackground) + end + if widget.baseTextColor then + widget:setColor(widget.baseTextColor) + end +end + +updateRaceSelectionDisplay = function(slot) + local prey = getPreySlotWidget(slot) + if not prey or not prey.inactive or not prey.inactive.fullList then + return + end + + local entry = selectedRaceEntryBySlot[slot] + + if prey.title then + if entry then + setWidgetTextToFit(prey.title, entry.name, function(value) + return tr('Selected: %s', value) + end) + else + prey.title:setText(tr('Select your prey creature')) + end + end + + local preview = prey.inactive and prey.inactive.preview + if preview then + local creatureWidget = preview.creature + local placeholder = preview.placeholder + if entry and entry.outfit and creatureWidget then + local spriteSize = g_gameConfig.getSpriteSize() + if spriteSize <= 0 then + spriteSize = 32 + end + local widgetSize = math.min(creatureWidget:getWidth(), creatureWidget:getHeight()) + local referenceSize = math.max(entry.realSize or spriteSize * 2, spriteSize * 2) + local targetSize = widgetSize > 0 and widgetSize or spriteSize * 2 + local scale = math.floor((targetSize / referenceSize) * 100) + + if scale < 1 then + scale = 1 + elseif scale > 100 then + scale = 100 + end + creatureWidget:setCreatureSize(size) + creatureWidget:setOutfit(entry.outfit) + creatureWidget:setVisible(true) + if placeholder then + placeholder:setVisible(false) + end + else + if creatureWidget then + creatureWidget:setVisible(false) + end + if placeholder then + placeholder:setVisible(true) + end + end + end + + local chooseButton = prey.inactive.choose and prey.inactive.choose.choosePreyButton + if chooseButton then + setChoosePreyButtonEnabled(chooseButton, entry ~= nil) + end +end + +setRaceSelection = function(slot, widget, skipUncheck) + local previousWidget = selectedRaceWidgetBySlot[slot] + if widget and widget:isDestroyed() then + widget = nil + end + + if widget and not skipUncheck then + onItemBoxChecked(widget) + elseif not widget and previousWidget and not previousWidget:isDestroyed() and previousWidget.setChecked then + previousWidget:setChecked(false) + restoreRaceListItemBackground(previousWidget) + elseif widget and skipUncheck then + if previousWidget and previousWidget ~= widget and not previousWidget:isDestroyed() and previousWidget.setChecked then + previousWidget:setChecked(false) + restoreRaceListItemBackground(previousWidget) + end + if not widget:isChecked() then + widget:setChecked(true) + end + end + + if previousWidget and previousWidget ~= widget then + restoreRaceListItemBackground(previousWidget) + end + + selectedRaceWidgetBySlot[slot] = widget + selectedRaceEntryBySlot[slot] = widget and widget.raceData or nil + + if widget then + restoreRaceListItemBackground(widget) + end + + updateRaceSelectionDisplay(slot) + updateChoosePreyButtonState(slot) +end + +refreshRaceList = function(slot) + local prey = getPreySlotWidget(slot) + if not prey or not prey.inactive or not prey.inactive.fullList then + return + end + + local fullList = prey.inactive.fullList + clearFullListEntries(fullList) + + local entriesPanel = getFullListEntriesContainer(fullList) + if not entriesPanel then + return + end + + local currentSelectionId = selectedRaceEntryBySlot[slot] and selectedRaceEntryBySlot[slot].raceId or nil + local selectionRestored = false + + local backgroundA = '#484848' + local backgroundB = '#414141' + local useAlternate = false + + local searchFilter = raceSearchTextsBySlot[slot] + if searchFilter and searchFilter:len() > 0 then + searchFilter = searchFilter:lower() + else + searchFilter = nil + end + + for _, entry in ipairs(raceEntriesBySlot[slot] or {}) do + if not searchFilter or (entry.searchName and entry.searchName:find(searchFilter, 1, true)) then + local item = g_ui.createWidget('PreyCreatureListItem', entriesPanel) + setWidgetTextToFit(item, entry.name) + item:setTooltip(entry.name) + item.raceData = entry + item.preySlot = slot + item.baseBackground = useAlternate and backgroundB or backgroundA + item.checkedBackground = '#585858' + item.baseTextColor = '#c0c0c0' + item.checkedTextColor = '#ffffff' + item:setBackgroundColor(item.baseBackground) + item:setColor(item.baseTextColor) + item.onCheckChange = function(widget) + restoreRaceListItemBackground(widget) + end + restoreRaceListItemBackground(item) + + useAlternate = not useAlternate + + if currentSelectionId and entry.raceId == currentSelectionId then + setRaceSelection(slot, item, true) + selectionRestored = true + end + end + end + + if not selectionRestored and currentSelectionId then + setRaceSelection(slot, nil, true) + elseif not currentSelectionId then + updateRaceSelectionDisplay(slot) + end + + updateChoosePreyButtonState(slot) +end + +function onRaceSearchTextChanged(widget) + if not widget or not widget.preySlot then + return + end + + if suppressSearchTextHandler then + updateRaceSearchClearButton(widget.preySlot) + return + end + + local slot = widget.preySlot + local text = widget:getText() or '' + local trimmed = text:trim() + + if text ~= trimmed then + setSearchTextSilently(widget, trimmed) + widget:setCursorPos(-1) + text = trimmed + end + + if trimmed:len() == 0 then + raceSearchTextsBySlot[slot] = nil + else + raceSearchTextsBySlot[slot] = trimmed + end + + updateRaceSearchClearButton(slot) + refreshRaceList(slot) +end + +function onRaceSearchClearClicked(widget) + if not widget or not widget.preySlot then + return + end + + local slot = widget.preySlot + local searchEdit = widget.searchEdit + + if not searchEdit or searchEdit:isDestroyed() then + local prey = getPreySlotWidget(slot) + if prey and prey.inactive and prey.inactive.fullList then + searchEdit = prey.inactive.fullList.searchEdit + end + end + + raceSearchTextsBySlot[slot] = nil + + if searchEdit and not searchEdit:isDestroyed() then + setSearchTextSilently(searchEdit, '') + searchEdit:setCursorPos(0) + end + + updateRaceSearchClearButton(slot) + refreshRaceList(slot) +end + +function onPreyRaceListItemClicked(widget) + if not widget or not widget.preySlot then + return + end + setRaceSelection(widget.preySlot, widget, false) +end + +function onPreyRaceListItemHoverChange(widget, hovered) + if not widget or widget:isDestroyed() then + return + end + + restoreRaceListItemBackground(widget) +end + +local suppressOptionCheckHandler = false + +local function setOptionCheckedSilently(checkbox, checked) + if checkbox:isChecked() == checked then + return + end + suppressOptionCheckHandler = true + checkbox:setChecked(checked) + suppressOptionCheckHandler = false +end + +local function getToggleCheckboxes(slot) + local prey = preyWindow and preyWindow['slot' .. (slot + 1)] + if not prey or not prey.active then + return nil, nil + end + + local autoCheckbox = prey.active.autoReroll and prey.active.autoReroll.autoRerollCheck + local lockCheckbox = prey.active.lockPrey and prey.active.lockPrey.lockPreyCheck + + return autoCheckbox, lockCheckbox +end + +local function sendOption(slot, option) + g_game.preyAction(slot, PREY_ACTION_OPTION, option) +end + +local function handleToggleOptions(checkbox, slot, currentOption, checked) + if suppressOptionCheckHandler then + return + end + + local autoCheckbox, lockCheckbox = getToggleCheckboxes(slot) + local otherCheckbox = currentOption == PREY_OPTION_TOGGLE_AUTOREROLL and lockCheckbox or autoCheckbox + + if checked then + local confirmWindow + local wasPreyWindowVisible = preyWindow and preyWindow:isVisible() + local preyVisibilityRestored = false + + local function restorePreyWindowVisibility() + if not preyVisibilityRestored and wasPreyWindowVisible and preyWindow then + preyVisibilityRestored = true + preyWindow:show() + preyWindow:raise() + preyWindow:focus() + end + end + + if wasPreyWindowVisible then + preyWindow:hide() + end + + local function closeWindow() + if confirmWindow then + confirmWindow:destroy() + confirmWindow = nil + restorePreyWindowVisibility() + end + end + + local function confirm() + if otherCheckbox and otherCheckbox:isChecked() then + setOptionCheckedSilently(otherCheckbox, false) + sendOption(slot, PREY_OPTION_UNTOGGLE) + end + + setOptionCheckedSilently(checkbox, true) + sendOption(slot, currentOption) + closeWindow() + end + + local function cancel() + setOptionCheckedSilently(checkbox, false) + closeWindow() + end + + local description = currentOption == PREY_OPTION_TOGGLE_AUTOREROLL and + tr(descriptionTable['automaticBonusReroll']) or tr(descriptionTable['preyLock']) + + confirmWindow = displayGeneralBox(tr('Confirmation of Using Prey Wildcards'), description, { + { + text = tr('No'), + callback = cancel + }, + { + text = tr('Yes'), + callback = confirm + }, + }, confirm, cancel) + + if confirmWindow then + confirmWindow.onDestroy = restorePreyWindowVisibility + end + + return + end + + sendOption(slot, PREY_OPTION_UNTOGGLE) +end + +function onPreyActive(slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, + timeUntilFreeReroll, wildcards, option) -- locktype always 0 for protocols <12 + + local tracker = preyTracker.contentsPanel['slot' .. (slot + 1)] + currentHolderName = capitalFormatStr(currentHolderName) + local percent = (timeLeft / (2 * 60 * 60)) * 100 + if tracker then + tracker.creature:show() + tracker.noCreature:hide() + tracker.creatureName:setText(currentHolderName) + tracker.creature:setOutfit(currentHolderOutfit) + tracker.preyType:setImageSource(getSmallIconPath(bonusType)) + tracker.time:setPercent(percent) + preyDescription[slot] = preyDescription[slot] or {} + preyDescription[slot].one = 'Creature: ' .. currentHolderName .. '\nDuration: ' + preyDescription[slot].two = + '\nValue: ' .. bonusGrade .. '/10' .. '\nType: ' .. getBonusDescription(bonusType) .. '\n' .. + getTooltipBonusDescription(bonusType, bonusValue) .. '\n\nClick in this window to open the prey dialog.' + for i, element in pairs({ tracker.creatureName, tracker.creature, tracker.preyType, tracker.time }) do + element:setTooltip(preyDescription[slot].one .. timeleftTranslation(timeLeft, true) .. + preyDescription[slot].two) + element.onClick = function() + show() + end + end + end + local prey = preyWindow['slot' .. (slot + 1)] + if not prey then + return + end + prey.inactive:hide() + prey.locked:hide() + prey.active:show() + prey.title:setText(currentHolderName) + local creatureAndBonus = prey.active.creatureAndBonus + creatureAndBonus.creature:setOutfit(currentHolderOutfit) + setTimeUntilFreeReroll(slot, timeUntilFreeReroll) + creatureAndBonus.bonus.icon:setImageSource(getBigIconPath(bonusType)) + creatureAndBonus.bonus.icon.onHoverChange = function(widget, hovered) + onHover(slot) + end + setBonusGradeStars(slot, bonusGrade) + creatureAndBonus.timeLeft:setPercent(percent) + creatureAndBonus.timeLeft:setText(timeleftTranslation(timeLeft)) + -- bonus reroll + local wildcardCount = getWildcardCountOrDefault(wildcards) + setPickSpecificPreyBonus(slot, bonusType, bonusValue) + prey.active.choose.selectPrey.onClick = function() + showBonusRerollConfirmation(slot, wildcardCount) + end + -- creature reroll + prey.active.reroll.button.rerollButton.onClick = function() + showListRerollConfirmation(slot) + end + + setOptionCheckedSilently(prey.active.autoReroll.autoRerollCheck, option == PREY_OPTION_TOGGLE_AUTOREROLL) + prey.active.autoReroll.autoRerollCheck.onCheckChange = function(widget, checked) + handleToggleOptions(widget, slot, PREY_OPTION_TOGGLE_AUTOREROLL, checked) + end + + setOptionCheckedSilently(prey.active.lockPrey.lockPreyCheck, option == PREY_OPTION_TOGGLE_LOCK_PREY) + prey.active.lockPrey.lockPreyCheck.onCheckChange = function(widget, checked) + handleToggleOptions(widget, slot, PREY_OPTION_TOGGLE_LOCK_PREY, checked) + end + + updatePickSpecificPreyButton(slot, wildcards) +end + +function onPreySelection(slot, names, outfits, timeUntilFreeReroll, wildcards, option) + -- tracker + local tracker = preyTracker.contentsPanel['slot' .. (slot + 1)] + if tracker then + tracker.creature:hide() + tracker.noCreature:show() + tracker.creatureName:setText('Inactive') + tracker.time:setPercent(0) + tracker.preyType:setImageSource('/images/game/prey/prey_no_bonus') + for i, element in pairs({ tracker.creatureName, tracker.creature, tracker.preyType, tracker.time }) do + element:setTooltip('Inactive Prey. \n\nClick in this window to open the prey dialog.') + element.onClick = function() + show() + end + end + end + -- main window + local prey = preyWindow['slot' .. (slot + 1)] + setTimeUntilFreeReroll(slot, timeUntilFreeReroll) + if not prey then + return + end + prey.active:hide() + prey.locked:hide() + prey.inactive:show() + setInactiveMode(slot, false, prey) + raceEntriesBySlot[slot] = nil + selectedRaceEntryBySlot[slot] = nil + selectedRaceWidgetBySlot[slot] = nil + setPickSpecificPreyBonus(slot) + prey.title:setText(tr('Select monster')) + local rerollButton = prey.inactive.reroll and prey.inactive.reroll.button and + prey.inactive.reroll.button.rerollButton + if rerollButton then + rerollButton.onClick = function() + showListRerollConfirmation(slot) + end + end + local list = prey.inactive.list + list:destroyChildren() + for i, name in ipairs(names) do + local box = g_ui.createWidget('PreyCreatureBox', list) + name = capitalFormatStr(name) + box:setTooltip(name) + box.creature:setOutfit(outfits[i]) + local backgroundColor = (i % 2 == 1) and '#484848' or '#414141' + box:setBackgroundColor(backgroundColor) + end + prey.inactive.choose.choosePreyButton.onClick = function() + for i, child in pairs(list:getChildren()) do + if child:isChecked() then + return g_game.preyAction(slot, PREY_ACTION_MONSTERSELECTION, i - 1) + end + end + return showMessage(tr('Error'), tr('Select monster to proceed.')) + end + + updateChoosePreyButtonState(slot) + updatePickSpecificPreyButton(slot, wildcards) +end + +function onPreySelectionChangeMonster(slot, names, outfits, bonusType, bonusValue, bonusGrade, timeUntilFreeReroll, + wildcards, option) + -- tracker + local tracker = preyTracker.contentsPanel['slot' .. (slot + 1)] + if tracker then + tracker.creature:hide() + tracker.noCreature:show() + tracker.creatureName:setText('Inactive') + tracker.time:setPercent(0) + tracker.preyType:setImageSource('/images/game/prey/prey_no_bonus') + for i, element in pairs({ tracker.creatureName, tracker.creature, tracker.preyType, tracker.time }) do + element:setTooltip('Inactive Prey. \n\nClick in this window to open the prey dialog.') + element.onClick = function() + show() + end + end + end + -- main window + local prey = preyWindow['slot' .. (slot + 1)] + setTimeUntilFreeReroll(slot, timeUntilFreeReroll) + if not prey then + return + end + prey.active:hide() + prey.locked:hide() + prey.inactive:show() + setInactiveMode(slot, false, prey) + raceEntriesBySlot[slot] = nil + selectedRaceEntryBySlot[slot] = nil + selectedRaceWidgetBySlot[slot] = nil + setPickSpecificPreyBonus(slot) + prey.title:setText(tr('Select monster')) + local rerollButton = prey.inactive.reroll and prey.inactive.reroll.button and + prey.inactive.reroll.button.rerollButton + if rerollButton then + rerollButton.onClick = function() + showListRerollConfirmation(slot) + end + end + local list = prey.inactive.list + list:destroyChildren() + for i, name in ipairs(names) do + local box = g_ui.createWidget('PreyCreatureBox', list) + name = capitalFormatStr(name) + box:setTooltip(name) + box.creature:setOutfit(outfits[i]) + local backgroundColor = (i % 2 == 1) and '#484848' or '#414141' + box:setBackgroundColor(backgroundColor) + end + prey.inactive.choose.choosePreyButton.onClick = function() + for i, child in pairs(list:getChildren()) do + if child:isChecked() then + return g_game.preyAction(slot, PREY_ACTION_MONSTERSELECTION, i - 1) + end + end + return showMessage(tr('Error'), tr('Select monster to proceed.')) + end + + updateChoosePreyButtonState(slot) + updatePickSpecificPreyButton(slot, wildcards) +end + +function onPreyListSelection(slot, races, nextFreeReroll, wildcards, option) + setTimeUntilFreeReroll(slot, nextFreeReroll) + + local prey = getPreySlotWidget(slot) + if not prey then + return + end + + prey.active:hide() + prey.locked:hide() + prey.inactive:show() + setPickSpecificPreyBonus(slot) + selectedRaceEntryBySlot[slot] = nil + selectedRaceWidgetBySlot[slot] = nil + + setInactiveMode(slot, true, prey) + + local fullList = prey.inactive.fullList + if not fullList then + return + end + + prey.title:setText(tr('Select your prey creature')) + + clearFullListEntries(fullList) + + local preview = prey.inactive.preview + if preview then + resetPreyPreviewWidget(preview) + end + + raceEntriesBySlot[slot] = {} + for _, raceId in ipairs(races) do + table.insert(raceEntriesBySlot[slot], buildRaceEntry(raceId)) + end + table.sort(raceEntriesBySlot[slot], function(a, b) + return a.name < b.name + end) + + raceSearchTextsBySlot[slot] = nil + updateRaceSearchUI(slot) + + local chooseButton = prey.inactive.choose and prey.inactive.choose.choosePreyButton + if chooseButton then + setChoosePreyButtonEnabled(chooseButton, false) + chooseButton.onClick = function() + local selected = selectedRaceEntryBySlot[slot] + if not selected then + return showMessage(tr('Error'), tr('Select monster to proceed.')) + end + g_game.preyAction(slot, PREY_ACTION_CHANGE_FROM_ALL, selected.raceId) + end + end + + refreshRaceList(slot) + + updateChoosePreyButtonState(slot) + + local rerollButton = prey.inactive.reroll and prey.inactive.reroll.button and + prey.inactive.reroll.button.rerollButton + if rerollButton then + rerollButton.onClick = function() + showListRerollConfirmation(slot) + end + end + + updateChoosePreyButtonState(slot) + updatePickSpecificPreyButton(slot, wildcards) +end + +function onPreyWildcardSelection(slot, races, nextFreeReroll, wildcards, option) + updatePickSpecificPreyButton(slot, wildcards) +end + +function Prey.onResourcesBalanceChange(balance, oldBalance, type) + if type == ResourceTypes.BANK_BALANCE then -- bank gold + bankGold = balance + elseif type == ResourceTypes.GOLD_EQUIPPED then -- inventory gold + inventoryGold = balance + elseif type == ResourceTypes.PREY_WILDCARDS then -- bonus rerolls + bonusRerolls = balance + for slot = 0, 2 do + updatePickSpecificPreyButton(slot, balance) + end + end + local player = g_game.getLocalPlayer() + g_logger.debug('' .. tostring(type) .. ', ' .. tostring(balance)) + if player then + preyWindow.wildCards:setText(tostring(player:getResourceBalance(ResourceTypes.PREY_WILDCARDS))) + preyWindow.gold:setText(comma_value(player:getTotalMoney())) + end + + if type == ResourceTypes.BANK_BALANCE or type == ResourceTypes.GOLD_EQUIPPED then + for slot = 0, 2 do + refreshRerollButtonState(slot) + end end end diff --git a/modules/game_prey/prey.otui b/modules/game_prey/prey.otui index 2e5e1e64a0..68a2ce2c89 100644 --- a/modules/game_prey/prey.otui +++ b/modules/game_prey/prey.otui @@ -203,12 +203,12 @@ ActivePreyPanel < Panel margin-top: 2 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - image-source: /images/ui/panel_flat - image-border: 1 + anchors.right: parent.right size: 155 15 font: verdana-11px-rounded text: Automatic Bonus Rer... margin-left: 5 + text-offset: 20 1 CardLabel id: autoRerollPrice @@ -230,12 +230,12 @@ ActivePreyPanel < Panel margin-top: 2 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - image-source: /images/ui/panel_flat - image-border: 1 + anchors.right: parent.right size: 155 15 font: verdana-11px-rounded text: Lock Prey margin-left: 5 + text-offset: 20 1 CardLabel id: lockPreyPrice @@ -349,6 +349,33 @@ InactivePreyPanel < Panel anchors.right: prev.left margin-right: 2 + FlatPanel + id: preview + anchors.left: parent.left + anchors.right: choose.left + anchors.bottom: parent.bottom + margin-right: 6 + height: 92 + padding: 4 + image-source: /images/ui/panel_flat + image-border: 1 + visible: false + + Label + id: placeholder + anchors.centerIn: parent + font: verdana-11px-rounded + text-align: center + !text: tr('No creature selected') + color: #cfcfcf + + UICreature + id: creature + anchors.centerIn: parent + size: 64 64 + phantom: true + visible: false + FlatPanel id: list anchors.top: parent.top @@ -363,6 +390,70 @@ InactivePreyPanel < Panel cell-spacing: 3 num-columns: 3 + FlatPanel + id: fullList + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: preview.top + margin-top: 20 + margin-bottom: 8 + padding: 6 + visible: false + image-source: /images/ui/panel_flat + image-border: 1 + + TextEdit + id: searchEdit + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 20 + height: 20 + padding-left: 4 + padding-right: 18 + placeholder: Type to search + placeholder-color: #6e706f + @onTextChange: modules.game_prey.onRaceSearchTextChanged(self) + + UIWidget + id: searchClearButton + anchors.top: searchEdit.top + anchors.right: searchEdit.right + margin-top: 2 + margin-right: 2 + size: 16 16 + image-source: /images/ui/button-clear-18x18-up + visible: false + @onClick: modules.game_prey.onRaceSearchClearClicked(self) + + $pressed: + image-source: /images/ui/button-clear-18x18-down + + UIScrollArea + id: entries + anchors.top: searchEdit.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 6 + padding-left: 2 + padding-right: 14 + padding-bottom: 2 + vertical-scrollbar: entriesScroll + layout: + type: verticalBox + + VerticalScrollBar + id: entriesScroll + anchors.top: entries.top + anchors.bottom: entries.bottom + anchors.right: parent.right + margin-right: 2 + step: 20 + pixel-scroll: true + ChoosePrey < FlatPanel size: 55 92 padding: 10 @@ -502,6 +593,30 @@ PreyCreatureBox < UICheckBox image-color: #ffffff88 color: #aaaaaa88 +PreyCreatureListItem < UICheckBox + height: 22 + margin-bottom: 2 + padding: 3 + focusable: false + text-align: left + font: verdana-11px-rounded + color: #c0c0c0 + border-width: 1 + border-color: alpha + background-color: alpha + text-offset: 4 0 + size-policy-horizontal: expanding + @onClick: modules.game_prey.onPreyRaceListItemClicked(self) + @onHoverChange: modules.game_prey.onPreyRaceListItemHoverChange(self) + + $checked on: + background-color: #1c4d75ff + border-color: #6fb1e9ff + color: #ffffff + + $!checked: + border-color: alpha + SlotPanel < Panel size: 210 320 image-source: /images/ui/window diff --git a/src/client/const.h b/src/client/const.h index 192cd9eefc..671a9ece0c 100644 --- a/src/client/const.h +++ b/src/client/const.h @@ -420,7 +420,13 @@ namespace Otc PREY_ACTION_MONSTERSELECTION = 2, PREY_ACTION_REQUEST_ALL_MONSTERS = 3, PREY_ACTION_CHANGE_FROM_ALL = 4, - PREY_ACTION_LOCK_PREY = 5, + PREY_ACTION_OPTION = 5, + }; + enum PreyOption_t : uint8_t + { + PREY_OPTION_UNTOGGLE = 0, + PREY_OPTION_TOGGLE_AUTOREROLL = 1, + PREY_OPTION_TOGGLE_LOCK_PREY = 2, }; enum PreyConfigState : uint8_t { diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 5e0e096453..7e107837c8 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -5252,6 +5252,7 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) uint32_t nextFreeReroll = 0; // next free roll uint8_t wildcards = 0; // wildcards + uint8_t option = 0; // prey option toggle state switch (state) { case Otc::PREY_STATE_LOCKED: @@ -5284,11 +5285,13 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) const uint16_t timeLeft = msg->getU16(); // time left if (g_game.getClientVersion() > 1149) { // correct unconfirmed version nextFreeReroll = msg->getU32(); - wildcards = msg->getU8(); + wildcards = 0; // wildcards + option = msg->getU8(); } else { nextFreeReroll = msg->getU16(); } - return g_lua.callGlobalField("g_game", "onPreyActive", slot, monster.name, monster.outfit, bonusType, bonusValue, bonusGrade, timeLeft, nextFreeReroll, wildcards); + + return g_lua.callGlobalField("g_game", "onPreyActive", slot, monster.name, monster.outfit, bonusType, bonusValue, bonusGrade, timeLeft, nextFreeReroll, wildcards, option); } case Otc::PREY_STATE_SELECTION: {