diff --git a/ironmon_tracker/Battle.lua b/ironmon_tracker/Battle.lua index 7059ac9e..94081005 100644 --- a/ironmon_tracker/Battle.lua +++ b/ironmon_tracker/Battle.lua @@ -851,6 +851,7 @@ function Battle.trySwapScreenBackToMain() [TrainersOnRouteScreen] = true, [RandomEvosScreen] = true, [MoveHistoryScreen] = true, + [CatchRatesScreen] = true, [TypeDefensesScreen] = true, [CoverageCalcScreen] = true, [HealsInBagScreen] = true, diff --git a/ironmon_tracker/FileManager.lua b/ironmon_tracker/FileManager.lua index 34ad28ca..1c80b182 100644 --- a/ironmon_tracker/FileManager.lua +++ b/ironmon_tracker/FileManager.lua @@ -138,6 +138,7 @@ FileManager.LuaCode = { { name = "StatsScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "StatsScreen.lua", }, { name = "RandomEvosScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "RandomEvosScreen.lua", }, { name = "MoveHistoryScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "MoveHistoryScreen.lua", }, + { name = "CatchRatesScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "CatchRatesScreen.lua", }, { name = "TypeDefensesScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "TypeDefensesScreen.lua", }, { name = "HealsInBagScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "HealsInBagScreen.lua", }, { name = "GameOverScreen", filepath = FileManager.Folders.ScreensCode .. FileManager.slash .. "GameOverScreen.lua", }, diff --git a/ironmon_tracker/Languages/English.lua b/ironmon_tracker/Languages/English.lua index 77493ae1..56c893aa 100644 --- a/ironmon_tracker/Languages/English.lua +++ b/ironmon_tracker/Languages/English.lua @@ -577,6 +577,14 @@ ScreenResources{ PromptPokemonTitle = "Pokédex Look up", PromptPokemonDesc = "Choose a Pokémon to look up", }, + CatchRatesScreen = { + Title = "Ball Catch Rates", + PokemonsHPPercent = "Estimated HP", + PokemonsStatus = "Status", + HeaderBall = "Ball", + HeaderBag = "Bag", + HeaderRate = "Rate", + }, TypeDefensesScreen = { Immunities = "Immunities", Resistances = "Resistances", diff --git a/ironmon_tracker/Languages/French.lua b/ironmon_tracker/Languages/French.lua index 4e30df57..c22bcd94 100644 --- a/ironmon_tracker/Languages/French.lua +++ b/ironmon_tracker/Languages/French.lua @@ -577,6 +577,14 @@ ScreenResources{ PromptPokemonTitle = "Pokédex Look up", -- NEEDS TRANSLATION PromptPokemonDesc = "Choose a Pokémon to look up", -- NEEDS TRANSLATION }, + CatchRatesScreen = { + Title = "Ball Catch Rates", -- NEEDS TRANSLATION + PokemonsHPPercent = "Estimated HP", -- NEEDS TRANSLATION + PokemonsStatus = "Status", -- NEEDS TRANSLATION + HeaderBall = "Ball", -- NEEDS TRANSLATION + HeaderBag = "Bag", -- NEEDS TRANSLATION + HeaderRate = "Rate", -- NEEDS TRANSLATION + }, TypeDefensesScreen = { Immunities = "Immunities", -- NEEDS TRANSLATION Resistances = "Resistances", -- NEEDS TRANSLATION diff --git a/ironmon_tracker/Languages/German.lua b/ironmon_tracker/Languages/German.lua index f6608ad3..d98e0c4e 100644 --- a/ironmon_tracker/Languages/German.lua +++ b/ironmon_tracker/Languages/German.lua @@ -577,6 +577,14 @@ ScreenResources{ PromptPokemonTitle = "Pokédex Look up", -- NEEDS TRANSLATION PromptPokemonDesc = "Choose a Pokémon to look up", -- NEEDS TRANSLATION }, + CatchRatesScreen = { + Title = "Ball Catch Rates", -- NEEDS TRANSLATION + PokemonsHPPercent = "Estimated HP", -- NEEDS TRANSLATION + PokemonsStatus = "Status", -- NEEDS TRANSLATION + HeaderBall = "Ball", -- NEEDS TRANSLATION + HeaderBag = "Bag", -- NEEDS TRANSLATION + HeaderRate = "Rate", -- NEEDS TRANSLATION + }, TypeDefensesScreen = { Immunities = "Immunities", -- NEEDS TRANSLATION Resistances = "Resistances", -- NEEDS TRANSLATION diff --git a/ironmon_tracker/Languages/Italian.lua b/ironmon_tracker/Languages/Italian.lua index 11b4bc6f..c3c012f2 100644 --- a/ironmon_tracker/Languages/Italian.lua +++ b/ironmon_tracker/Languages/Italian.lua @@ -577,6 +577,14 @@ ScreenResources{ PromptPokemonTitle = "Pokédex Look up", -- NEEDS TRANSLATION PromptPokemonDesc = "Choose a Pokémon to look up", -- NEEDS TRANSLATION }, + CatchRatesScreen = { + Title = "Ball Catch Rates", -- NEEDS TRANSLATION + PokemonsHPPercent = "Estimated HP", -- NEEDS TRANSLATION + PokemonsStatus = "Status", -- NEEDS TRANSLATION + HeaderBall = "Ball", -- NEEDS TRANSLATION + HeaderBag = "Bag", -- NEEDS TRANSLATION + HeaderRate = "Rate", -- NEEDS TRANSLATION + }, TypeDefensesScreen = { Immunities = "Immunities", -- NEEDS TRANSLATION Resistances = "Resistances", -- NEEDS TRANSLATION diff --git a/ironmon_tracker/Languages/Japanese.lua b/ironmon_tracker/Languages/Japanese.lua index fb2c0c0d..59cfe13f 100644 --- a/ironmon_tracker/Languages/Japanese.lua +++ b/ironmon_tracker/Languages/Japanese.lua @@ -579,6 +579,14 @@ ScreenResources{ PromptPokemonTitle = "Pokédex Look up", -- NEEDS TRANSLATION PromptPokemonDesc = "Choose a Pokémon to look up", -- NEEDS TRANSLATION }, + CatchRatesScreen = { + Title = "Ball Catch Rates", -- NEEDS TRANSLATION + PokemonsHPPercent = "Estimated HP", -- NEEDS TRANSLATION + PokemonsStatus = "Status", -- NEEDS TRANSLATION + HeaderBall = "Ball", -- NEEDS TRANSLATION + HeaderBag = "Bag", -- NEEDS TRANSLATION + HeaderRate = "Rate", -- NEEDS TRANSLATION + }, TypeDefensesScreen = { Immunities = "Immunities", -- NEEDS TRANSLATION Resistances = "Resistances", -- NEEDS TRANSLATION diff --git a/ironmon_tracker/Languages/Spanish.lua b/ironmon_tracker/Languages/Spanish.lua index d7d897cd..03e35a1f 100644 --- a/ironmon_tracker/Languages/Spanish.lua +++ b/ironmon_tracker/Languages/Spanish.lua @@ -577,6 +577,14 @@ ScreenResources{ PromptPokemonTitle = "Pokédex Look up", -- NEEDS TRANSLATION PromptPokemonDesc = "Choose a Pokémon to look up", -- NEEDS TRANSLATION }, + CatchRatesScreen = { + Title = "Ball Catch Rates", -- NEEDS TRANSLATION + PokemonsHPPercent = "Estimated HP", -- NEEDS TRANSLATION + PokemonsStatus = "Status", -- NEEDS TRANSLATION + HeaderBall = "Ball", -- NEEDS TRANSLATION + HeaderBag = "Bag", -- NEEDS TRANSLATION + HeaderRate = "Rate", -- NEEDS TRANSLATION + }, TypeDefensesScreen = { Immunities = "Immunities", -- NEEDS TRANSLATION Resistances = "Resistances", -- NEEDS TRANSLATION diff --git a/ironmon_tracker/Program.lua b/ironmon_tracker/Program.lua index 4effe08c..111fed9d 100644 --- a/ironmon_tracker/Program.lua +++ b/ironmon_tracker/Program.lua @@ -55,6 +55,9 @@ Program = { offsetPokemonStatsSpaSpd = 0x60, offsetRivalName = 0x3A4C, -- SaveBlock1 offsetOptionsButtonMode = 0x13, -- SaveBlock2 + offsetPokedex = 0x18, -- SaveBlock2 + offsetPokedexOwned = 0x10, -- SaveBlock2's Pokedex struct + offsetPokedexSeen = 0x44, -- SaveBlock2's Pokedex struct sizeofBaseStatsPokemon = 0x1C, sizeofExpTablePokemon = 0x194, @@ -90,6 +93,7 @@ Program.GameData = { healingTotal = 0, -- A calculation of total HP heals healingPercentage = 0, -- A calculation of percentage heals -- Each of the below: map of [itemId] -> quanity of item + PokeBalls = {}, HPHeals = {}, PPHeals = {}, StatusHeals = {}, @@ -1434,6 +1438,7 @@ function Program.updateBagItems() Program.GameData.Items = { healingTotal = 0, healingPercentage = 0, + PokeBalls = {}, HPHeals = {}, PPHeals = {}, StatusHeals = {}, @@ -1447,8 +1452,8 @@ function Program.updateBagItems() local addressesToScan = { [saveBlock1Addr + GameSettings.bagPocket_Items_offset] = GameSettings.bagPocket_Items_Size, [saveBlock1Addr + GameSettings.bagPocket_Berries_offset] = GameSettings.bagPocket_Berries_Size, + [saveBlock1Addr + GameSettings.bagPocket_Balls_offset] = GameSettings.bagPocket_Balls_Size, -- Don't have a use for these yet, so not reading them from memory - -- [saveBlock1Addr + GameSettings.bagPocket_Balls_offset] = GameSettings.bagPocket_Balls_Size, -- [saveBlock1Addr + GameSettings.bagPocket_TmHm_offset] = GameSettings.bagPocket_TmHm_Size, } for address, size in pairs(addressesToScan) do @@ -1462,6 +1467,9 @@ function Program.updateBagItems() quantity = Utils.bit_xor(quantity, key) end if quantity > 0 then + if MiscData.PokeBalls[itemID] then + items.PokeBalls[itemID] = quantity + end if MiscData.HealingItems[itemID] then items.HPHeals[itemID] = quantity end @@ -1475,7 +1483,7 @@ function Program.updateBagItems() items.EvoStones[itemID] = quantity end -- If the item wasn't categorized anywhere, mark as "Other" - if not (items.HPHeals[itemID] or items.PPHeals[itemID] or items.StatusHeals[itemID] or items.EvoStones[itemID]) then + if not (items.PokeBalls[itemID] or items.HPHeals[itemID] or items.PPHeals[itemID] or items.StatusHeals[itemID] or items.EvoStones[itemID]) then items.Other[itemID] = quantity end end diff --git a/ironmon_tracker/data/PokemonData.lua b/ironmon_tracker/data/PokemonData.lua index 9ef59bae..3fc5f101 100644 --- a/ironmon_tracker/data/PokemonData.lua +++ b/ironmon_tracker/data/PokemonData.lua @@ -439,7 +439,7 @@ end ---@param hpCurrent number ---@param level number? Optional, the Pokémon level, used only for Nest Ball; defaults to 5 ---@param status number? Optional, defaults to "None" ----@param ball number? Optional, defaults to Poké Ball (item id = 3) +---@param ball number? Optional, defaults to Poké Ball (item id = 4) ---@param terrain number? Optional, defaults to 0 (no terrain); use 3 for UNDERWATER ---@param battleTurn number? Optional, defaults to 0; first turn of a battle ---@return number @@ -449,7 +449,7 @@ function PokemonData.calcCatchRate(pokemonID, hpMax, hpCurrent, level, status, b end level = level or 5 status = status or MiscData.StatusType.None - ball = ball or 3 + ball = ball or 4 terrain = terrain or 0 battleTurn = battleTurn or 0 @@ -467,35 +467,40 @@ function PokemonData.calcCatchRate(pokemonID, hpMax, hpCurrent, level, status, b -- Determine ball type bonus multiplier local ballBonusMap = { - [0] = 255, --Master Ball - [1] = 20, --Ultra Ball - [2] = 15, --Great Ball - [3] = 10, --Poke Ball - [4] = 15, --Safari Ball - [5] = 30, --Net Ball; only for WATER or BUG types - [6] = 35, --Dive Ball; only when map type is UNDERWATER - [7] = 40, --Nest Ball; subtract level of enemy, floor is 10 - [8] = 30, --Repeat Ball; only if pokemon is flagged as caught already - [9] = 10, --Timer Ball; add turn counter, caps at 40 - [10] = 10, --Luxury Ball - [11] = 10, --Premier Ball + [1] = 255, -- Master Ball + [2] = 20, -- Ultra Ball + [3] = 15, -- Great Ball + [4] = 10, -- Poke Ball + [5] = 15, -- Safari Ball + [6] = 30, -- Net Ball; only for WATER or BUG types + [7] = 35, -- Dive Ball; only when map type is UNDERWATER + [8] = 40, -- Nest Ball; subtract level of enemy, floor is 10 + [9] = 30, -- Repeat Ball; only if pokemon is flagged as caught already + [10] = 10, -- Timer Ball; add turn counter, caps at 40 + [11] = 10, -- Luxury Ball + [12] = 10, -- Premier Ball } local ballBonus - if ball <= 4 or ball >= 10 then + if ball <= 5 or ball >= 11 then ballBonus = ballBonusMap[ball] or 10 -- default: poké ball - elseif ball == 5 and (pokemon.types[1] == PokemonData.Types.WATER or pokemon.types[2] == PokemonData.Types.WATER or pokemon.types[1] == PokemonData.Types.BUG or pokemon.types[2] == PokemonData.Types.BUG) then - ballBonus = ballBonusMap[5] - elseif ball == 6 and terrain == 3 then -- terrain 3: UNDERWATER - ballBonus = ballBonusMap[6] - elseif ball == 7 then - ballBonus = math.max(10, 40 - level) + elseif ball == 6 and (pokemon.types[1] == PokemonData.Types.WATER or pokemon.types[2] == PokemonData.Types.WATER or pokemon.types[1] == PokemonData.Types.BUG or pokemon.types[2] == PokemonData.Types.BUG) then + ballBonus = ballBonusMap[ball] + elseif ball == 7 and terrain == 3 then -- terrain 3: UNDERWATER + ballBonus = ballBonusMap[ball] elseif ball == 8 then - -- Data not available yet for calculation, default to poké ball - ballBonus = 10 + ballBonus = math.max(10, 40 - level) elseif ball == 9 then + local dexAddr = Utils.getSaveBlock2Addr() + Program.Addresses.offsetPokedex + Program.Addresses.offsetPokedexOwned + local bitIndex = math.floor((pokemonID - 1) / 8) + local bitRemainder = (pokemonID - 1) % 8 + local dexValue = Memory.readbyte(dexAddr + bitIndex) + if Utils.getbits(dexValue, bitRemainder, 1) == 1 then -- if 1, has caught the mon previously + ballBonus = ballBonusMap[ball] + end + elseif ball == 10 then ballBonus = math.min(10 + battleTurn, 40) end - ballBonus = ballBonus / 10 + ballBonus = (ballBonus or 10) / 10 -- Determine status bonus multiplier local statusBonusMap = { diff --git a/ironmon_tracker/screens/CatchRatesScreen.lua b/ironmon_tracker/screens/CatchRatesScreen.lua new file mode 100644 index 00000000..03b1b7b6 --- /dev/null +++ b/ironmon_tracker/screens/CatchRatesScreen.lua @@ -0,0 +1,362 @@ +CatchRatesScreen = { + Colors = { + text = "Default text", + highlight = "Intermediate text", + border = "Upper box border", + boxFill = "Upper box background", + positive = "Positive text", + negative = "Negative text", + }, + Data = { + pokemon = nil, + catchRates = {}, + }, +} +SCREEN = CatchRatesScreen +local CANVAS = { + X = Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN, + Y = Constants.SCREEN.MARGIN + 10, + W = Constants.SCREEN.RIGHT_GAP - (Constants.SCREEN.MARGIN * 2), + H = Constants.SCREEN.HEIGHT - (Constants.SCREEN.MARGIN * 2) - 10, +} +local CELL1_W = 32 + +local PERCENT_FORMAT = "%s%%" + +SCREEN.Buttons = { + PokemonsName = { + type = Constants.ButtonTypes.NO_BORDER, + getText = function() return "Pokémon:" end, + box = { CANVAS.X + 2, CANVAS.Y + 2, CANVAS.W / 2 - 4, 11 }, + draw = function(self, shadowcolor) + local x, y, w, h = self.box[1], self.box[2], self.box[3], self.box[4] + local text, textColor + if SCREEN.Data.isReady and SCREEN.Data.pokemon then + text = PokemonData.Pokemon[SCREEN.Data.pokemon.pokemonID].name + textColor = Theme.COLORS[SCREEN.Colors.highlight] + else + text = Constants.BLANKLINE + textColor = Theme.COLORS[SCREEN.Colors.text] + end + Drawing.drawText(x + w + 1, y, text, textColor, shadowcolor) + end, + }, + PokemonsHPPercent = { + type = Constants.ButtonTypes.NO_BORDER, + getText = function() return string.format("%s:", Resources.CatchRatesScreen.PokemonsHPPercent) end, + box = { CANVAS.X + 2, CANVAS.Y + 13, CANVAS.W / 2 - 4, 11 }, + draw = function(self, shadowcolor) + local x, y, w, h = self.box[1], self.box[2], self.box[3], self.box[4] + local text, textColor + if SCREEN.Data.isReady and SCREEN.Data.hpPercent then + -- Round to nearest tens place + local roundedPerc = math.floor(SCREEN.Data.hpPercent / 10 + 0.5) * 10 + text = string.format(PERCENT_FORMAT, roundedPerc) + textColor = Theme.COLORS[SCREEN.Colors.highlight] + else + text = Constants.BLANKLINE + textColor = Theme.COLORS[SCREEN.Colors.text] + end + Drawing.drawText(x + w + 1, y, text, textColor, shadowcolor) + if (SCREEN.Data.hpAdjust or 0) ~= 0 then + local textW = Utils.calcWordPixelLength(text) + local hpAdjust = string.format("%s " .. PERCENT_FORMAT, + SCREEN.Data.hpAdjust > 0 and "+" or "--", + math.abs(SCREEN.Data.hpAdjust) + ) + Drawing.drawText(x + w + textW + 3, y, hpAdjust, Theme.COLORS[SCREEN.Colors.text], shadowcolor) + end + end, + }, + PokemonsStatus = { + type = Constants.ButtonTypes.NO_BORDER, + getText = function() return string.format("%s:", Resources.CatchRatesScreen.PokemonsStatus) end, + box = { CANVAS.X + 2, CANVAS.Y + 24, CANVAS.W / 2 - 4, 11 }, + draw = function(self, shadowcolor) + local x, y, w, h = self.box[1], self.box[2], self.box[3], self.box[4] + local text, textColor + if SCREEN.Data.isReady and SCREEN.Data.status then + text = string.format("%s", SCREEN.Data.status) + textColor = Theme.COLORS[SCREEN.Colors.highlight] + else + text = Constants.BLANKLINE + textColor = Theme.COLORS[SCREEN.Colors.text] + end + Drawing.drawText(x + w + 1, y, text, textColor, shadowcolor) + end, + }, + HPMinus = { + type = Constants.ButtonTypes.STAT_STAGE, + getText = function() return Constants.STAT_STATES[2].text end, + textColor = Constants.STAT_STATES[2].textColor, + box = { CANVAS.X + CANVAS.W - 19, CANVAS.Y + 14, 8, 8 }, + onClick = function(self) + if not SCREEN.Data.isReady then + return + end + SCREEN.Data.hpAdjust = math.max(SCREEN.Data.hpAdjust - 10, -90) -- min of 90 + Program.redraw(true) + end, + }, + HPPlus = { + type = Constants.ButtonTypes.STAT_STAGE, + getText = function() return Constants.STAT_STATES[1].text end, + textColor = Constants.STAT_STATES[1].textColor, + box = { CANVAS.X + CANVAS.W - 11, CANVAS.Y + 14, 8, 8 }, + onClick = function(self) + if not SCREEN.Data.isReady then + return + end + SCREEN.Data.hpAdjust = math.min(SCREEN.Data.hpAdjust + 10, 90) -- max of 90 + Program.redraw(true) + end, + }, + TableHeaders = { + type = Constants.ButtonTypes.NO_BORDER, + getText = function() return Utils.toUpperUTF8(Resources.CatchRatesScreen.HeaderBall) end, + textColor = SCREEN.Colors.highlight, + box = { CANVAS.X + 3, CANVAS.Y + 35, CANVAS.W / 2, 11 }, + draw = function(self, shadowcolor) + local x, y, w, h = self.box[1], self.box[2], self.box[3], self.box[4] + local textColor = Theme.COLORS[self.textColor] + local bagText = Utils.toUpperUTF8(Resources.CatchRatesScreen.HeaderBag) + local rateText = Utils.toUpperUTF8(Resources.CatchRatesScreen.HeaderRate) + Drawing.drawText(x + w, y, bagText, textColor, shadowcolor) + Drawing.drawText(x + w + CELL1_W, y, rateText, textColor, shadowcolor) + end, + }, + CurrentPage = { + type = Constants.ButtonTypes.NO_BORDER, + getText = function(self) return SCREEN.Pager:getPageText() end, + box = { CANVAS.X + 56, CANVAS.Y + 126, 50, 10, }, + isVisible = function() return SCREEN.Pager.totalPages > 1 end, + }, + PrevPage = { + type = Constants.ButtonTypes.PIXELIMAGE, + image = Constants.PixelImages.LEFT_ARROW, + box = { CANVAS.X + 46, CANVAS.Y + 127, 10, 10, }, + isVisible = function() return SCREEN.Pager.totalPages > 1 end, + onClick = function(self) SCREEN.Pager:prevPage() end + }, + NextPage = { + type = Constants.ButtonTypes.PIXELIMAGE, + image = Constants.PixelImages.RIGHT_ARROW, + box = { CANVAS.X + 86, CANVAS.Y + 127, 10, 10, }, + isVisible = function() return SCREEN.Pager.totalPages > 1 end, + onClick = function(self) SCREEN.Pager:nextPage() end + }, + Back = Drawing.createUIElementBackButton(function() + Program.changeScreenView(SCREEN.previousScreen or TrackerScreen) + SCREEN.previousScreen = nil + end), +} + +SCREEN.Pager = { + Buttons = {}, + currentPage = 0, + totalPages = 0, + defaultSort = function(a, b) return a.index < b.index end, + realignButtonsToGrid = function(self, x, y, colSpacer, rowSpacer, sortFunc) + table.sort(self.Buttons, sortFunc or self.defaultSort) + local cutoffX = CANVAS.X + CANVAS.W + local cutoffY = CANVAS.Y + CANVAS.H - 10 + local totalPages = Utils.gridAlign(self.Buttons, x, y, colSpacer, rowSpacer, true, cutoffX, cutoffY) + self.currentPage = 1 + self.totalPages = totalPages or 1 + end, + getPageText = function(self) + if self.totalPages <= 1 then return Resources.AllScreens.Page end + local text = string.format("%s/%s", self.currentPage, self.totalPages) + local bufferSize = 5 - text:len() + return string.rep(" ", bufferSize) .. text + end, + prevPage = function(self) + if self.totalPages <= 1 then return end + self.currentPage = ((self.currentPage - 2 + self.totalPages) % self.totalPages) + 1 + Program.redraw(true) + end, + nextPage = function(self) + if self.totalPages <= 1 then return end + self.currentPage = (self.currentPage % self.totalPages) + 1 + Program.redraw(true) + end, +} + +function CatchRatesScreen.initialize() + for _, button in pairs(SCREEN.Buttons) do + if button.textColor == nil then + button.textColor = SCREEN.Colors.text + end + if button.boxColors == nil then + button.boxColors = { SCREEN.Colors.border, SCREEN.Colors.boxFill } + end + end + SCREEN.clearBuiltData() + SCREEN.refreshButtons() +end + +function CatchRatesScreen.buildScreen(pokemon) + pokemon = pokemon or TrackerAPI.getEnemyPokemon() + if type(pokemon) ~= "table" or not PokemonData.isValid(pokemon.pokemonID) then + return false + end + SCREEN.clearBuiltData() + SCREEN.refreshDataValues(pokemon) + + -- Catch Rate table rows + for ballId, _ in ipairs(MiscData.PokeBalls or {}) do + local button = { + type = Constants.ButtonTypes.NO_BORDER, + getCustomText = function() return Resources.Game.ItemNames[ballId] end, + textColor = SCREEN.Colors.text, + ballId = ballId, + quantity = (Program.GameData.Items.PokeBalls[ballId] or 0), + hasInBag = (Program.GameData.Items.PokeBalls[ballId] or 0) > 0, + catchRate = SCREEN.Data.catchRates[ballId], + dimensions = { width = CANVAS.W / 2, height = 11 }, + boxColors = { SCREEN.Colors.border, SCREEN.Colors.boxFill }, + isVisible = function(self) return self.pageVisible == SCREEN.Pager.currentPage end, + updateSelf = function(self) + self.quantity = Program.GameData.Items.PokeBalls[ballId] or 0 + self.hasInBag = self.quantity > 0 + self.catchRate = SCREEN.Data.catchRates[ballId] + end, + draw = function(self, shadowcolor) + local x, y, w, h = self.box[1], self.box[2], self.box[3], self.box[4] + local bagText = self.quantity + local rateText = string.format(PERCENT_FORMAT, self.catchRate) + local textColor = Theme.COLORS[self.textColor] + local rateColor + local borderColor = Theme.COLORS[self.boxColors[1]] + local bgColor = Theme.COLORS[self.boxColors[2]] + if self.quantity == 0 then + textColor = textColor - 0x60000000 + elseif self.catchRate >= 100 then + rateColor = Theme.COLORS[SCREEN.Colors.positive] + end + -- Draw table row border and dividers + gui.drawRectangle(x, y - 1, w * 2 - 6, h + 2, borderColor, bgColor) + gui.drawLine(x + w - 2, y - 1, x + w - 2, y + h + 1, borderColor) + gui.drawLine(x + w + CELL1_W - 2, y - 1, x + w + CELL1_W - 2, y + h + 1, borderColor) + -- Draw each cell's text + Drawing.drawText(x + 2, y, self:getCustomText(), textColor, shadowcolor) + Drawing.drawNumber(x + w, y, bagText, 5, textColor, shadowcolor) + Drawing.drawNumber(x + w + CELL1_W, y, rateText, 5, rateColor or textColor, shadowcolor) + end, + } + table.insert(SCREEN.Pager.Buttons, button) + end + + -- List the balls owned in the player's bag first, ordered by catch rate + local function sortFunc(a, b) + return a.hasInBag and not b.hasInBag + or (a.hasInBag == b.hasInBag and a.catchRate > b.catchRate) + or (a.hasInBag == b.hasInBag and a.catchRate == b.catchRate and a.ballId < b.ballId) + end + local tableHeadersY = SCREEN.Buttons.TableHeaders.box[2] + SCREEN.Pager:realignButtonsToGrid(CANVAS.X + 3, tableHeadersY + 12, 20, 2, sortFunc) + + SCREEN.Data.isReady = true + return true +end + +function CatchRatesScreen.refreshDataValues(pokemon) + pokemon = pokemon or TrackerAPI.getEnemyPokemon() + if not pokemon then + return + end + SCREEN.Data.pokemon = pokemon + + -- POKEMON'S ESTIMATED HP PERCENT + local hpMax = pokemon.stats.hp + local hpCurrent = pokemon.curHP + -- Estimated calculation borrowed from `PokemonData.calcCatchRate()` + local estimatedCurrHP = math.floor(math.ceil(hpCurrent / hpMax * 10) / 10 * hpMax) + SCREEN.Data.hpPercent = math.floor(estimatedCurrHP / hpMax * 100) + + -- POKEMON'S STATUS + if pokemon.status ~= MiscData.StatusType.None then + SCREEN.Data.status = MiscData.StatusCodeMap[pokemon.status] + else + SCREEN.Data.status = nil + end + + -- ALL CATCH RATES + SCREEN.Data.catchRates = {} + local estimatedHP = math.floor(hpMax * (SCREEN.Data.hpPercent + SCREEN.Data.hpAdjust) / 100 + 0.5) + local terrain = Memory.readword(GameSettings.gBattleTerrain) -- Used for Dive Ball only + for ballId, _ in ipairs(MiscData.PokeBalls or {}) do + SCREEN.Data.catchRates[ballId] = PokemonData.calcCatchRate( + pokemon.pokemonID, + hpMax, + estimatedHP, + pokemon.level, + pokemon.status, + ballId, + terrain, + Battle.turnCount + ) + end +end + +function CatchRatesScreen.refreshButtons() + for _, button in pairs(SCREEN.Buttons) do + if type(button.updateSelf) == "function" then + button:updateSelf() + end + end + for _, button in pairs(SCREEN.Pager.Buttons) do + if type(button.updateSelf) == "function" then + button:updateSelf() + end + end +end + +function CatchRatesScreen.clearBuiltData() + SCREEN.Data = {} + SCREEN.Data.hpAdjust = 0 + SCREEN.Data.isReady = false + SCREEN.Pager.Buttons = {} +end + +-- USER INPUT FUNCTIONS +function CatchRatesScreen.checkInput(xmouse, ymouse) + Input.checkButtonsClicked(xmouse, ymouse, SCREEN.Buttons) + Input.checkButtonsClicked(xmouse, ymouse, SCREEN.Pager.Buttons) +end + +-- DRAWING FUNCTIONS +function CatchRatesScreen.drawScreen() + Drawing.drawBackgroundAndMargins() + + local canvas = { + x = CANVAS.X, + y = CANVAS.Y, + width = CANVAS.W, + height = CANVAS.H, + text = Theme.COLORS[SCREEN.Colors.text], + border = Theme.COLORS[SCREEN.Colors.border], + fill = Theme.COLORS[SCREEN.Colors.boxFill], + shadow = Utils.calcShadowColor(Theme.COLORS[SCREEN.Colors.boxFill]), + } + + -- Draw top border box + gui.defaultTextBackground(canvas.fill) + gui.drawRectangle(canvas.x, canvas.y, canvas.width, canvas.height, canvas.border, canvas.fill) + + -- Draw header text + local headerText = Utils.toUpperUTF8(Resources.CatchRatesScreen.Title) + local headerColor = Theme.COLORS["Header text"] + local headerShadow = Utils.calcShadowColor(Theme.COLORS["Main background"]) + Drawing.drawText(canvas.x, Constants.SCREEN.MARGIN - 2, headerText, headerColor, headerShadow) + + -- Draw all buttons + SCREEN.refreshDataValues() + SCREEN.refreshButtons() + for _, button in pairs(SCREEN.Buttons) do + Drawing.drawButton(button, canvas.shadow) + end + for _, button in pairs(SCREEN.Pager.Buttons) do + Drawing.drawButton(button, canvas.shadow) + end +end \ No newline at end of file diff --git a/ironmon_tracker/screens/TrackerScreen.lua b/ironmon_tracker/screens/TrackerScreen.lua index 98cdeeec..bdd6e62e 100644 --- a/ironmon_tracker/screens/TrackerScreen.lua +++ b/ironmon_tracker/screens/TrackerScreen.lua @@ -283,9 +283,9 @@ TrackerScreen.Buttons = { MovesHistory = { -- Invisible clickable button type = Constants.ButtonTypes.NO_BORDER, - textColor = "Intermediate text", -- set later after highlight color is calculated - clickableArea = { Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN + 1, 81, 77, 10 }, - box = { Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN + 69, 81, 10, 10 }, + textColor = "Header text", -- set later after highlight color is calculated + clickableArea = { Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN + 1, 81, 75, 10 }, + box = { Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN + 1, 81, 75, 10 }, boxColors = { "Header text", "Main background" }, isVisible = function() local pokemon = Tracker.getViewedPokemon() or {} @@ -300,7 +300,25 @@ TrackerScreen.Buttons = { if hasMoves then Program.changeScreenView(MoveHistoryScreen) end - end + end, + }, + CatchRates = { + -- Invisible clickable button + type = Constants.ButtonTypes.NO_BORDER, + textColor = "Header text", + clickableArea = { Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN + 77, 81, 50, 10 }, + box = { Constants.SCREEN.WIDTH + Constants.SCREEN.MARGIN + 77, 81, 50, 10 }, + boxColors = { "Header text", "Main background" }, + isVisible = function() + return Options["Show Poke Ball catch rate"] and not Battle.isViewingOwn and Battle.isWildEncounter + end, + onClick = function(self) + local pokemon = TrackerAPI.getEnemyPokemon() + if CatchRatesScreen.buildScreen(pokemon) then + CatchRatesScreen.previousScreen = TrackerScreen + Program.changeScreenView(CatchRatesScreen) + end + end, }, NotepadTracking = { type = Constants.ButtonTypes.PIXELIMAGE,