From 52232581f227b829ea283d795ddaf60a52ce24fe Mon Sep 17 00:00:00 2001 From: Kyle Coburn Date: Sat, 12 Jul 2014 18:47:39 -0700 Subject: [PATCH] Initial public commit --- LICENSE | 20 + Readme.md | 32 + action/battle.lua | 249 ++++ action/shop.lua | 105 ++ action/textbox.lua | 77 ++ action/walk.lua | 160 +++ ai/combat.lua | 386 ++++++ ai/control.lua | 247 ++++ ai/strategies.lua | 2712 +++++++++++++++++++++++++++++++++++++++++ data/movelist.lua | 1520 +++++++++++++++++++++++ data/opponents.lua | 175 +++ data/paths.lua | 454 +++++++ main.lua | 198 +++ storage/inventory.lua | 200 +++ storage/pokemon.lua | 274 +++++ util/bridge.lua | 132 ++ util/input.lua | 119 ++ util/memory.lua | 138 +++ util/menu.lua | 193 +++ util/paint.lua | 68 ++ util/player.lua | 38 + util/settings.lua | 52 + util/utils.lua | 57 + 23 files changed, 7606 insertions(+) create mode 100644 LICENSE create mode 100644 Readme.md create mode 100644 action/battle.lua create mode 100644 action/shop.lua create mode 100644 action/textbox.lua create mode 100644 action/walk.lua create mode 100644 ai/combat.lua create mode 100644 ai/control.lua create mode 100644 ai/strategies.lua create mode 100644 data/movelist.lua create mode 100644 data/opponents.lua create mode 100644 data/paths.lua create mode 100644 main.lua create mode 100644 storage/inventory.lua create mode 100644 storage/pokemon.lua create mode 100644 util/bridge.lua create mode 100644 util/input.lua create mode 100644 util/memory.lua create mode 100644 util/menu.lua create mode 100644 util/paint.lua create mode 100644 util/player.lua create mode 100644 util/settings.lua create mode 100644 util/utils.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1a293f1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Kyle Coburn and Michael Jondahl + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..b0e9b1f --- /dev/null +++ b/Readme.md @@ -0,0 +1,32 @@ +PokéBot +======= +An automated computer program that speedruns Pokémon. + +Pokémon Red Any%: [1:51:11](https://www.youtube.com/watch?v=M4pOlQ-mIoc) (23 June 2014) + +Watch Live +========== +### [http://www.twitch.tv/thepokebot](http://www.twitch.tv/thepokebot) +PokéBot's official streaming channel on Twitch. Consider following there to find out when we're streaming, or follow the [Twitter feed](https://twitter.com/thepokebot) for announcements when we get personal best pace runs going. + +Try it out +========== +Running the PokéBot on your own machine is easy. You will need a Windows environment (it runs great in VM's on Mac too). First, clone this repository (or download and unzip it) to your computer. Install the [BizHawk 1.6.1](http://down.emucr.com/v3/10194002) emulator, and procure a ROM file of Pokémon Red (you should personally own the game). + +Open the ROM file with BizHawk, and Pokémon Red should start up. Then, under the 'Tools' menu, select 'Lua Console'. Click the open folder button, and navigate to the PokéBot folder you downloaded. Select 'main.lua' and press open. The bot should start running! + +Seeds +===== +PokéBot comes with a built-in run recording feature that takes advantage of random number seeding to reproduce runs in their entirety. Any time the bot resets or beats the game, it will log a number to the Lua console that is the seed for the run. If you set 'CUSTOM_SEED' in main.lua to that number, the bot will reproduce your run, allowing you to share your times with others. Note that making any other modifications will prevent this from working. So if you want to make changes to the bot and share your time, be sure to fork the repo and push your changes. + +Credits +======= +### Developers +Kyle Coburn: Original concept, Red routing + +Michael Jondahl: Combat algorithm, Java bridge for connecting the bot to Twitch chat, Livesplit, Twitter, etc + +### Special thanks +To Livesplit for providing custom component for integrating in-game time splits. + +To the Pokémon speedrunning community members who inspired the idea, and shared ways to improve the bot. \ No newline at end of file diff --git a/action/battle.lua b/action/battle.lua new file mode 100644 index 0000000..4d7d5cf --- /dev/null +++ b/action/battle.lua @@ -0,0 +1,249 @@ +local battle = {} + +local textbox = require "action.textbox" + +local combat = require "ai.combat" +local control = require "ai.control" + +local memory = require "util.memory" +local menu = require "util.menu" +local input = require "util.input" +local utils = require "util.utils" + +local inventory = require "storage.inventory" +local pokemon = require "storage.pokemon" + +local function potionsForHit(potion, currHP, maxHP) + if (not potion) then + return + end + local ours, killAmount = combat.inKillRange() + if (ours) then + local potionHP + if (potion == "full_restore") then + potionHP = 999 + elseif (potion == "super_potion") then + potionHP = 50 + else + potionHP = 20 + end + if (not currHP) then + currHP = pokemon.index(0, "hp") + maxHP = pokemon.index(0, "max_hp") + end + return math.min(currHP + potionHP, maxHP) >= killAmount - 2 + end +end +battle.potionsForHit = potionsForHit + +local function recover() + if (control.canRecover()) then + local currentHP = pokemon.index(0, "hp") + if (currentHP > 0) then + local maxHP = pokemon.index(0, "max_hp") + if (currentHP < maxHP) then + local first, second + if (potionIn == "full") then + first, second = "full_restore", "super_potion" + if (maxHP - currentHP > 54) then + first = "full_restore" + second = "super_potion" + else + first = "super_potion" + second = "full_restore" + end + else + if (maxHP - currentHP > 22) then + first = "super_potion" + second = "potion" + else + first = "potion" + second = "super_potion" + end + end + local potion = inventory.contains(first, second) + if (potionsForHit(potion, currentHP, maxHP)) then + inventory.use(potion, nil, true) + return true + end + end + end + end + if (memory.value("battle", "paralyzed") == 64) then + local heals = inventory.contains("paralyze_heal", "full_restore") + if (heals) then + inventory.use(heals, nil, true) + return true + end + end +end + +local function openBattleMenu() + if (memory.value("battle", "text") == 1) then + input.cancel() + return false + end + local battleMenu = memory.value("battle", "menu") + local col = menu.getCol() + if (battleMenu == 106 or (battleMenu == 94 and col == 5)) then + return true + elseif (battleMenu == 94) then + local rowSelected = memory.value("menu", "row") + if (col == 9) then + if (rowSelected == 1) then + input.press("Up") + else + input.press("A") + end + else + input.press("Left") + end + else + input.press("B") + end +end + +local function attack(attackIndex) + if (memory.double("battle", "opponent_hp") < 1) then + input.cancel() + elseif (openBattleMenu()) then + menu.select(attackIndex, true, false, false, false, 3) + end +end + +-- Table functions + +function battle.swapMove(sidx, fidx) + if (openBattleMenu()) then + local selection = memory.value("menu", "selection_mode") + local swapSelect + if (selection == sidx) then + swapSelect = fidx + else + swapSelect = sidx + end + if (menu.select(swapSelect, false, false, nil, true, 3)) then + input.press("Select") + end + end +end + +function battle.isActive() + return memory.value("game", "battle") > 0 +end + +function battle.isTrainer() + local battleType = memory.value("game", "battle") + if (battleType == 2) then + return true + end + if (battleType == 1) then + battle.run() + else + textbox.handle() + end +end + +function battle.opponent() + return pokemon.getName(memory.value("battle", "opponent_id")) +end + +function battle.run() + if (memory.double("battle", "opponent_hp") < 1) then + input.cancel() + elseif (memory.value("battle", "menu") ~= 94) then + if (memory.value("menu", "text_length") == 127) then + input.press("B") + else + input.cancel() + end + elseif (textbox.handle()) then + local selected = memory.value("menu", "selection") + if (selected == 239) then + input.press("A", 2) + else + input.escape() + end + end +end + +function battle.handleWild() + if (memory.value("game", "battle") ~= 1) then + return true + end + battle.run() +end + +function battle.fight(move, isNumber, skipBuffs) + if (move) then + if (not isNumber) then + move = pokemon.battleMove(move) + end + attack(move) + else + move = combat.bestMove() + if (move) then + attack(move.midx) + elseif (memory.value("menu", "text_length") == 127) then + print("Faito B!") + input.press("B") + else + input.cancel() + end + end +end + +function battle.swap(target) + local battleMenu = memory.value("battle", "menu") + if (utils.onPokemonSelect(battleMenu)) then + if (menu.getCol() == 0) then + menu.select(pokemon.indexOf(target), true) + else + input.press("A") + end + elseif (battleMenu == 94) then + local selected = memory.value("menu", "selection") + if (selected == 199) then + input.press("A", 2) + elseif (menu.getCol() == 9) then + input.press("Right", 0) + else + input.press("Up", 0) + end + else + input.cancel() + end +end + +function movePP(name) + local midx = pokemon.battleMove(name) + if (not midx) then + return 0 + end + return memory.raw(0xD02C + midx) +end +battle.pp = movePP + +function battle.automate(moveName, skipBuffs) + if (not recover()) then + local state = memory.value("game", "battle") + if (state == 0) then + input.cancel() + else + if (moveName and movePP(moveName) == 0) then + moveName = nil + end + if (state == 1) then + if (control.shouldFight()) then + battle.fight(moveName, false, skipBuffs) + else + battle.run() + end + elseif (state == 2) then + battle.fight(moveName, false, skipBuffs) + end + end + end +end + +return battle diff --git a/action/shop.lua b/action/shop.lua new file mode 100644 index 0000000..4f58ed7 --- /dev/null +++ b/action/shop.lua @@ -0,0 +1,105 @@ +local shop = {} + +local textbox = require "action.textbox" + +local input = require "util.input" +local memory = require "util.memory" +local menu = require "util.menu" +local player = require "util.player" + +local inventory = require "storage.inventory" + +function shop.transaction(options) + local item, itemMenu, menuIdx, quantityMenu + if (options.sell) then + menuIdx = 1 + itemMenu = 29 + quantityMenu = 158 + for i,sit in ipairs(options.sell) do + local idx = inventory.indexOf(sit.name) + if (idx ~= -1) then + item = sit + item.index = idx + item.amount = inventory.count(sit.name) + break + end + end + end + if (not item and options.buy) then + menuIdx = 0 + itemMenu = 123 + quantityMenu = 161 + for i,bit in ipairs(options.buy) do + local needed = (bit.amount or 1) - inventory.count(bit.name) + if (needed > 0) then + item = bit + item.amount = needed + break + end + end + end + if (not item) then + if (not textbox.isActive()) then + return true + end + input.press("B") + elseif (player.isFacing(options.direction or "Left")) then + if (textbox.isActive()) then + if (menu.isCurrently(32, "shop")) then + menu.select(menuIdx, true, false, "shop") + elseif (menu.getCol() == 15) then + input.press("A") + elseif (menu.isCurrently(itemMenu, "transaction")) then + if (menu.select(item.index, "accelerate", true, "transaction", true)) then + if (menu.isCurrently(quantityMenu, "shop")) then + local currAmount = memory.value("shop", "transaction_amount") + if (menu.balance(currAmount, item.amount, false, 99, true)) then + input.press("A") + end + else + input.press("A") + end + end + else + input.press("B") + end + else + input.press("A", 2) + end + else + player.interact(options.direction or "Left") + end + return false +end + +function shop.vend(options) + local item + menuIdx = 0 + for i,bit in ipairs(options.buy) do + local needed = (bit.amount or 1) - inventory.count(bit.name) + if (needed > 0) then + item = bit + item.buy = needed + break + end + end + if (not item) then + if (not textbox.isActive()) then + return true + end + input.press("B") + elseif (player.face(options.direction)) then + if (textbox.isActive()) then + if (memory.value("battle", "text") > 1 and memory.value("battle", "menu") ~= 95) then + menu.select(item.index, true) + else + input.press("A") + end + else + input.press("A", 2) + end + end + return false +end + +return shop diff --git a/action/textbox.lua b/action/textbox.lua new file mode 100644 index 0000000..707d184 --- /dev/null +++ b/action/textbox.lua @@ -0,0 +1,77 @@ +local textbox = {} + +local input = require "util.input" +local memory = require "util.memory" +local menu = require "util.menu" +local utils = require "util.utils" + +local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ *():;[]ポモ-?!♂♀/.," + +local nidoName = "A" +local nidoIdx = 1 + +local function getLetterAt(index) + return alphabet[index] +end + +local function getIndexForLetter(letter) + return alphabet:find(letter, 1, true) +end + +function textbox.name(letter, randomize) + local inputting = memory.value("menu", "text_input") == 240 + if (inputting) then + if (memory.value("menu", "text_length") > 0) then + input.press("Start") + return true + end + local lidx + if (letter) then + lidx = getIndexForLetter(letter) + else + lidx = nidoIdx + end + + local crow = memory.value("menu", "input_row") + local drow = math.ceil(lidx / 9) + if (menu.balance(crow, drow, true, 6, true)) then + local ccol = math.floor(memory.value("menu", "column") / 2) + local dcol = math.fmod(lidx - 1, 9) + if (menu.sidle(ccol, dcol, 9, true)) then + input.press("A") + end + end + else + -- TODO cancel more when menu isn't up + if (memory.raw(0x10B7) == 3) then + input.press("A", 2) + elseif (randomize) then + input.press("A", math.random(1, 5)) + else + input.cancel() + end + end +end + +function textbox.getName() + return nidoName +end + +function textbox.setName(index) + nidoIdx = index + 1 + nidoName = getLetterAt(index) +end + +function textbox.isActive() + return memory.value("game", "textbox") == 1 +end + +function textbox.handle() + if (not textbox.isActive()) then + return true + end + input.cancel() +end + + +return textbox diff --git a/action/walk.lua b/action/walk.lua new file mode 100644 index 0000000..4df5bfc --- /dev/null +++ b/action/walk.lua @@ -0,0 +1,160 @@ +local walk = {} + +local control = require "ai.control" +local paths = require "data.paths" + +local input = require "util.input" +local memory = require "util.memory" +local player = require "util.player" +local utils = require "util.utils" + +local pokemon = require "storage.pokemon" + +local path, stepIdx, currentMap +local pathIdx = 0 +local customIdx = 1 +local customDir = 1 + +-- Private functions + +local function setPath(index, region) + pathIdx = index + stepIdx = 2 + currentMap = region + path = paths[index] +end + +-- Helper functions + +function dir(px, py, dx, dy) + local direction + if (py > dy) then + direction = "Up" + elseif (py < dy) then + direction = "Down" + elseif (px > dx) then + direction = "Left" + else + direction = "Right" + end + return direction +end +walk.dir = dir + +function step(dx, dy) + local px, py = player.position() + if (px == dx and py == dy) then + return true + end + input.press(dir(px, py, dx, dy), 0) +end +walk.step = step + +local function completeStep(region) + stepIdx = stepIdx + 1 + return walk.traverse(region) +end + +-- Table functions + +function walk.reset() + path = nil + pathIdx = 0 + customIdx = 1 + customDir = 1 + currentMap = nil + walk.strategy = nil +end + +function walk.init() + local region = memory.value("game", "map") + local px, py = player.position() + if (region == 0 and px == 0 and py == 0) then + return false + end + for tries=1,2 do + for i,p in ipairs(paths) do + if (i > 2 and p[1] == region) then + local origin = p[2] + if (tries == 2 or (origin[1] == px and origin[2] == py)) then + setPath(i, region) + return tries == 1 + end + end + end + end +end + +function walk.traverse(region) + local newIndex + if (not path or currentMap ~= region) then + walk.strategy = nil + setPath(pathIdx + 1, region) + newIndex = pathIdx + customIdx = 1 + customDir = 1 + elseif stepIdx > #path then + return + end + local tile = path[stepIdx] + if (tile.c) then + control.set(tile) + return completeStep(region) + end + if (tile.s) then + if (walk.strategy) then + walk.strategy = nil + return completeStep(region) + end + walk.strategy = tile + elseif step(tile[1], tile[2]) then + pokemon.updateParty() + return completeStep(region) + end + return newIndex +end + +function walk.canMove() + return memory.value("player", "moving") == 0 and memory.value("player", "fighting") == 0 +end + +-- Custom path + +function walk.invertCustom(silent) + if (not silent) then + customIdx = customIdx + customDir + end + customDir = customDir * -1 +end + +function walk.custom(cpath, increment) + if (not cpath) then + customIdx = 1 + customDir = 1 + return + end + if (increment) then + customIdx = customIdx + customDir + end + local tile = cpath[customIdx] + if (not tile) then + if (customIdx < 1) then + customIdx = #cpath + else + customIdx = 1 + end + return customIdx + end + local t1, t2 = tile[1], tile[2] + if (t2 == nil) then + if (player.face(t1)) then + input.press("A", 2) + end + return t1 + end + if (step(t1, t2)) then + customIdx = customIdx + customDir + end +end + +return walk diff --git a/ai/combat.lua b/ai/combat.lua new file mode 100644 index 0000000..c4e8d56 --- /dev/null +++ b/ai/combat.lua @@ -0,0 +1,386 @@ +local combat = {} + +local movelist = require "data.movelist" +local opponents = require "data.opponents" + +local memory = require "util.memory" +local utils = require "util.utils" + +local damageMultiplier = { -- http://bulbapedia.bulbagarden.net/wiki/Type_chart#Generation_I + normal = {normal=1.0, fighting=1.0, flying=1.0, poison=1.0, ground=1.0, rock=0.5, bug=1.0, ghost=0.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=1.0, ice=1.0, dragon=1.0, }, + fighting = {normal=2.0, fighting=1.0, flying=0.5, poison=0.5, ground=1.0, rock=2.0, bug=0.5, ghost=0.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=0.5, ice=2.0, dragon=1.0, }, + flying = {normal=1.0, fighting=2.0, flying=1.0, poison=1.0, ground=1.0, rock=0.5, bug=2.0, ghost=1.0, fire=1.0, water=1.0, grass=2.0, electric=0.5, psychic=1.0, ice=1.0, dragon=1.0, }, + poison = {normal=1.0, fighting=1.0, flying=1.0, poison=0.5, ground=0.5, rock=0.5, bug=2.0, ghost=0.5, fire=1.0, water=1.0, grass=2.0, electric=1.0, psychic=1.0, ice=1.0, dragon=1.0, }, + ground = {normal=1.0, fighting=1.0, flying=0.0, poison=2.0, ground=1.0, rock=2.0, bug=0.5, ghost=1.0, fire=2.0, water=1.0, grass=0.5, electric=2.0, psychic=1.0, ice=1.0, dragon=1.0, }, + rock = {normal=1.0, fighting=0.5, flying=2.0, poison=1.0, ground=0.5, rock=1.0, bug=2.0, ghost=1.0, fire=2.0, water=1.0, grass=1.0, electric=1.0, psychic=1.0, ice=2.0, dragon=1.0, }, + bug = {normal=1.0, fighting=0.5, flying=0.5, poison=2.0, ground=1.0, rock=1.0, bug=1.0, ghost=0.5, fire=0.5, water=1.0, grass=2.0, electric=1.0, psychic=2.0, ice=1.0, dragon=1.0, }, + ghost = {normal=0.0, fighting=1.0, flying=1.0, poison=1.0, ground=1.0, rock=1.0, bug=1.0, ghost=2.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=0.0, ice=1.0, dragon=1.0, }, + fire = {normal=1.0, fighting=1.0, flying=1.0, poison=1.0, ground=1.0, rock=0.5, bug=2.0, ghost=1.0, fire=0.5, water=0.5, grass=2.0, electric=1.0, psychic=1.0, ice=2.0, dragon=0.5, }, + water = {normal=1.0, fighting=1.0, flying=1.0, poison=1.0, ground=2.0, rock=2.0, bug=1.0, ghost=1.0, fire=2.0, water=0.5, grass=0.5, electric=1.0, psychic=1.0, ice=1.0, dragon=0.5, }, + grass = {normal=1.0, fighting=1.0, flying=0.5, poison=0.5, ground=2.0, rock=2.0, bug=0.5, ghost=1.0, fire=0.5, water=2.0, grass=0.5, electric=1.0, psychic=1.0, ice=1.0, dragon=0.5, }, + electric = {normal=1.0, fighting=1.0, flying=2.0, poison=1.0, ground=0.0, rock=1.0, bug=1.0, ghost=1.0, fire=1.0, water=2.0, grass=0.5, electric=0.5, psychic=1.0, ice=1.0, dragon=0.5, }, + psychic = {normal=1.0, fighting=2.0, flying=1.0, poison=2.0, ground=1.0, rock=1.0, bug=1.0, ghost=1.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=0.5, ice=1.0, dragon=1.0, }, + ice = {normal=1.0, fighting=1.0, flying=2.0, poison=1.0, ground=2.0, rock=1.0, bug=1.0, ghost=1.0, fire=1.0, water=0.5, grass=2.0, electric=1.0, psychic=1.0, ice=0.5, dragon=2.0, }, + dragon = {normal=1.0, fighting=1.0, flying=1.0, poison=1.0, ground=1.0, rock=1.0, bug=1.0, ghost=1.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=1.0, ice=1.0, dragon=2.0, }, +} + +local types = {} +types[0] = "normal" +types[1] = "fighting" +types[2] = "flying" +types[3] = "poison" +types[4] = "ground" +types[5] = "rock" +types[7] = "bug" +types[8] = "ghost" +types[20] = "fire" +types[21] = "water" +types[22] = "grass" +types[23] = "electric" +types[24] = "psychic" +types[25] = "ice" +types[26] = "dragon" + +local savedEncounters = {} +local enablePP = false + +local floor = math.floor + +local function isDisabled(mid) + return mid == memory.value("battle", "disabled") +end +combat.isDisabled = isDisabled + +local function calcDamage(move, attacker, defender, rng) + if (move.fixed) then + return move.fixed, move.fixed + end + if (move.power == 0 or isDisabled(move.id)) then + return 0, 0 + end + if (move.power > 9000) then + local oid = defender.id + if (oid ~= 14 and oid ~= 147 and oid ~= 171 and (oidd ~= 151 or memory.value("game", "map") == 120)) then -- ??? + if (memory.value("battle", "x_accuracy") == 1 and defender.speed < attacker.speed) then + return 9001, 9001 + end + end + return 0, 0 + end + if (move.name == "Thrash" and combat.disableThrash) then + return 0, 0 + end + + local attFactor, defFactor + if move.special then + attFactor, defFactor = attacker.spec, defender.spec + else + attFactor, defFactor = attacker.att, defender.def + end + local damage = floor(floor(floor(2 * attacker.level / 5 + 2) * math.max(1, attFactor) * move.power / math.max(1, defFactor)) / 50) + 2 + + if (move.move_type == attacker.type1 or move.move_type == attacker.type2) then + damage = floor(damage * 1.5) -- STAB + end + + local dmp = damageMultiplier[move.move_type] + local typeEffect1, typeEffect2 = dmp[defender.type1], dmp[defender.type2] + if (defender.type1 == defender.type2) then + typeEffect2 = 1 + end + damage = floor(damage * typeEffect1 * typeEffect2) + if (rng) then + return damage, damage + end + return floor(damage * 217 / 255), damage +end + +local function getOpponentType(ty) + local t1= types[memory.value("battle", "opponent_type1")] + if ty~=0 then + t1 = types[memory.value("battle", "opponent_type2")] + if not t1 then + return memory.value("battle", "opponent_type2") + end + end + if t1 then + return t1 + end + return memory.value("battle", "opponent_type1") +end +combat.getOpponentType = getOpponentType + +function getOurType(ty) + local t1 = types[memory.value("battle", "our_type1")] + if (ty ~= 0) then + t1 = types[memory.value("battle", "our_type2")] + if (not t1) then + return memory.value("battle", "opponent_type2") + end + end + if t1 then + return t1 + end + return memory.value("battle", "opponent_type1") +end +combat.getOurType = getOurType + +local function getMoves(who)--Get the moveset of us [0] or them [1] + local moves = {} + local base + if (who == 1) then + base = 0xCFED + else + base = 0xD01C + end + for idx=0, 3 do + local val = memory.raw(base + idx) + if (val > 0) then + local moveTable = movelist.get(val) + if (who == 0) then + moveTable.pp = memory.raw(0xD02D + idx) + end + moves[idx + 1] = moveTable + end + end + return moves +end +combat.getMoves = getMoves + +local function modPlayerStats(user, enemy, move) + local effect = move.effects + if (effect) then + local diff = effect.diff + local hitThem = diff < 0 + local stat = effect.stat + if (hitThem) then + enemy[stat] = math.max(2, enemy[stat] + diff) + else + user[stat] = user[stat] + diff + end + end + return user, enemy +end + +local function calcBestHit(attacker, defender, ours, rng) + local bestTurns, bestMinTurns = 9999, 9999 + local bestDmg = -1 + local ourMaxHit + local ret = nil + for idx,move in ipairs(attacker.moves) do + if (not move.pp or move.pp > 0) then + local minDmg, maxDmg = calcDamage(move, attacker, defender, rng) + if (maxDmg) then + local minTurns, maxTurns + if (maxDmg <= 0) then + minTurns, maxTurns = 9999, 9999 + else + minTurns = math.ceil(defender.hp / maxDmg) + maxTurns = math.ceil(defender.hp / minDmg) + end + if (ours) then + local replaces + if (not ret or minTurns < bestMinTurns or maxTurns < bestTurns) then + replaces = true + elseif (maxTurns == bestTurns and move.name == "Thrash") then + replaces = defender.hp == memory.double("battle", "opponent_max_hp") + elseif (maxTurns == bestTurns and ret.name == "Thrash") then + replaces = defender.hp ~= memory.double("battle", "opponent_max_hp") + elseif (move.fast and not ret.fast) then + replaces = maxTurns <= bestTurns + elseif (ret.fast) then + replaces = maxTurns < bestTurns + elseif (enablePP) then + if (maxTurns < 2 or maxTurns == bestMaxTurns) then + if (ret.name == "Earthquake" and (move.name == "Ice-Beam" or move.name == "Thunderbolt")) then + replaces = true + elseif (move.pp > ret.pp) then + if (ret.name == "Horn-Drill") then + replaces = true + elseif (move.name ~= "Earthquake") then + replaces = true + end + end + end + elseif (minDmg > bestDmg) then + replaces = true + end + if (replaces) then + ret = move + bestMinTurns = minTurns + bestTurns = maxTurns + bestDmg = minDmg + ourMaxHit = maxDmg + end + elseif (maxDmg > bestDmg) then -- Opponents automatically hit max + ret = move + bestTurns = minTurns + bestDmg = maxDmg + end + end + end + end + if (ret) then + ret.damage = bestDmg + ret.maxDamage = ourMaxHit + ret.minTurns = bestMinTurns + return ret, bestTurns + end +end + +local function getBestMove(ours, enemy, draw) + if (enemy.hp < 1) then + return + end + local bm, bestUs = calcBestHit(ours, enemy, true) + local jj, bestEnemy = calcBestHit(enemy, ours, false) + if (not bm) then + return + end + if draw and bm.midx then + gui.text(0, 35, ''..bm.midx.." "..bm.name) + end + return bm, bestUs, bestEnemy +end + +local function activePokemon(preset) + local ours = { + id = memory.value("battle", "our_id"), + level = memory.value("battle", "our_level"), + hp = memory.double("battle", "our_hp"), + att = memory.double("battle", "our_attack"), + def = memory.double("battle", "our_defense"), + spec = memory.double("battle", "our_special"), + speed = memory.double("battle", "our_speed"), + type1 = getOurType(0), + type2 = getOurType(1), + moves = getMoves(0), + } + + local enemy + if (preset) then + enemy = opponents[preset] + local toBoost = enemy.boost + if (toBoost) then + local currSpec = ours.spec + local booster = toBoost.mp + if ((currSpec < 140) == (booster > 1)) then + ours.spec = math.floor(currSpec * booster) + end + end + else + enemy = { + id = memory.value("battle", "opponent_id"), + level = memory.value("battle", "opponent_level"), + hp = memory.double("battle", "opponent_hp"), + att = memory.double("battle", "opponent_attack"), + def = memory.double("battle", "opponent_defense"), + spec = memory.double("battle", "opponent_special"), + speed = memory.double("battle", "opponent_speed"), + type1 = getOpponentType(0), + type2 = getOpponentType(1), + moves = getMoves(1), + } + end + return ours, enemy +end +combat.activePokemon = activePokemon + +local function isSleeping() + return memory.raw(0xD16F) > 1 +end +combat.isSleeping = isSleeping + +-- Combat AI functions + +function combat.factorPP(enabled) + enablePP = enabled +end + +function combat.reset() + enablePP = false +end + +function combat.healthFor(opponent) + local ours, enemy = activePokemon(opponent) + local enemyAttack, turnsToDie = calcBestHit(enemy, ours, false) + return enemyAttack.damage +end + +function combat.inKillRange(draw) + local ours, enemy = activePokemon() + local enemyAttack, __ = calcBestHit(enemy, ours, false) + local __, turnsToKill = calcBestHit(ours, enemy, true) + if (not turnsToKill or not enemyAttack) then + return false + end + if (draw) then + gui.text(0, 21, ours.speed.." "..enemy.speed) + gui.text(0, 28, turnsToDie.." "..ours.hp.." | "..turnsToKill.." "..enemy.hp) + end + local hpReq = enemyAttack.damage + local isConfused = memory.value("battle", "confused") > 0 + if (isConfused) then + hpReq = hpReq + math.floor(ours.hp * 0.2) + end + if (ours.hp < hpReq) then + local outspeed = enemyAttack.outspeed + if (outspeed and outspeed ~= true) then + outspeed = memory.value("battle", "turns") > 0 + end + if (outspeed or isConfused or turnsToKill > 1 or ours.speed <= enemy.speed or isSleeping()) then + return ours, hpReq + end + end +end + +local function getBattlePokemon() + local ours, enemy = activePokemon() + if (enemy.hp == 0) then + return + end + for idx=1,4 do + local move = ours.moves[idx] + if (move) then + move.midx = idx + end + end + return ours, enemy +end + +function combat.nonKill() + local ours, enemy = getBattlePokemon() + if (not enemy) then + return + end + local bestDmg = -1 + local ret = nil + for idx,move in ipairs(ours.moves) do + if (not move.pp or move.pp > 0) then + local __, maxDmg = calcDamage(move, ours, enemy, true) + local threshold = maxDmg * 0.95 + if (threshold and threshold < enemy.hp and threshold > bestDmg) then + ret = move + bestDmg = threshold + end + end + end + return ret +end + +function combat.bestMove() + local ours, enemy = getBattlePokemon() + if (enemy) then + return getBestMove(ours, enemy) + end +end + +function combat.enemyAttack() + local ours, enemy = activePokemon() + if (enemy.hp == 0) then + return + end + return calcBestHit(enemy, ours, false) +end + +return combat diff --git a/ai/control.lua b/ai/control.lua new file mode 100644 index 0000000..12ca3a7 --- /dev/null +++ b/ai/control.lua @@ -0,0 +1,247 @@ +local control = {} + +local combat = require "ai.combat" + +local bridge = require "util.bridge" +local memory = require "util.memory" +local menu = require "util.menu" +local paint = require "util.paint" +local player = require "util.player" +local utils = require "util.utils" + +local inventory = require "storage.inventory" +local pokemon = require "storage.pokemon" + +local game_controls + +local shouldFight, minExp, skipHiker +local shouldCatch, attackIdx +local encounters = 0, extraEncounter +local potionIn = true +local fightEncounter, caveFights = 0, 0 +local maxEncounters +local isYolo, battleYolo + +local function battlePotion(enable) + potionIn = enable +end +control.battlePotion = battlePotion + +local controlFunctions = { + potion = function(data) + if (data.b ~= nil) then + battlePotion(data.b) + end + battleYolo = data.yolo + end, + + encounters = function(data) + if (RESET_FOR_TIME) then + maxEncounters = data.limit + extraEncounter = data.extra + end + end, + + pp = function(data) + combat.factorPP(data.on) + end, + + setThrash = function(data) + combat.disableThrash = data.disable + end, + + disableCatch = function() + shouldCatch = nil + shouldFight = nil + end, + +-- RED + + viridianExp = function() + minExp = 210 + shouldFight = {{name="rattata",lvl={2,3}}, {name="pidgey",lvl={2}}} + end, + + viridianBackupExp = function() + minExp = 210 + shouldFight = {{name="rattata",lvl={2,3}}, {name="pidgey",lvl={2,3}}} + end, + + nidoranBackupExp = function() + minExp = 210 + shouldFight = {{name="rattata"}, {name="pidgey"}, {name="nidoran"}, {name="nidoranf",lvl={2}}} + end, + + moon1Exp = function() + if (skipHiker) then + minExp = 2704 + shouldFight = {{name="zubat",lvl={9,10}}, {name="geodude"}} + oneHits = true + end + end, + + moon2Exp = function() + if (skipHiker) then + minExp = 3011 + shouldFight = {{name="zubat"}, {name="geodude"}, {name="paras"}} + end + end, + + moon3Exp = function() + if (skipHiker) then + minExp = 3798 + end + end, + + catchNidoran = function() + shouldCatch = {{name="nidoran",lvl={3,4}}, {name="spearow"}} + end, + + catchFlier = function() + shouldCatch = {{name="spearow",alt="pidgey",hp=15}, {name="pidgey",alt="spearow",hp=15}} + end, + + catchParas = function() + shouldCatch = {{name="paras",hp=16}} + end, + + catchOddish = function() + shouldCatch = {{name="oddish",alt="paras",hp=26}} + end, + +} + +-- Combat + +local function isNewFight() + if (fightEncounter < encounters and memory.double("battle", "opponent_hp") == memory.double("battle", "opponent_max_hp")) then + fightEncounter = encounters + return true + end +end + + +function control.shouldFight() + if (not shouldFight) then + return false + end + local expTotal = pokemon.getExp() + if (expTotal < minExp) then + local oid = memory.value("battle", "opponent_id") + local olvl = memory.value("battle", "opponent_level") + for i,p in ipairs(shouldFight) do + if (oid == pokemon.getID(p.name) and (not p.lvl or utils.match(olvl, p.lvl))) then + if (oneHits) then + local move = combat.bestMove() + if (move and move.maxDamage * 0.925 < memory.double("battle", "opponent_hp")) then + return false + end + end + return true + end + end + end +end + +function control.canCatch(partySize) + if (not partySize) then + partySize = memory.value("player", "party_size") + end + local pokeballs = inventory.count("pokeball") + local minimumCount = 4 - partySize + if (pokeballs < minimumCount) then + require("ai.strategies").reset("Not enough PokeBalls", pokeballs) + return false + end + return true +end + +function control.shouldCatch(partySize) + if (maxEncounters and encounters > maxEncounters) then + local extraCount = extraEncounter and pokemon.inParty(extraEncounter) + if (not extraCount or encounters > maxEncounters + 1) then + require("ai.strategies").reset("Too many encounters", encounters) + return false + end + end + if (not shouldCatch) then + return false + end + if (not partySize) then + partySize = memory.value("player", "party_size") + end + if (partySize == 4) then + shouldCatch = nil + return false + end + if (not control.canCatch(partySize)) then + return true + end + local oid = memory.value("battle", "opponent_id") + for i,poke in ipairs(shouldCatch) do + if (oid == pokemon.getID(poke.name) and not pokemon.inParty(poke.name, poke.alt)) then + if (not poke.lvl or utils.match(memory.value("battle", "opponent_level"), poke.lvl)) then + local penultimate = poke.hp and memory.double("battle", "opponent_hp") > poke.hp + if (penultimate) then + penultimate = combat.nonKill() + end + if (penultimate) then + require("action.battle").fight(penultimate.midx, true) + else + inventory.use("pokeball", nil, true) + end + return true + end + end + end +end + +-- Items + +function control.canRecover() + return potionIn and (not battleYolo or not isYolo) +end + +function control.set(data) + controlFunctions[data.c](data) +end + +function control.setYolo(enabled) + isYolo = enabled +end + +function control.setPotion(enabled) + potionIn = enabled +end + +function control.encounters() + return encounters +end + +function control.mtMoonExp() + print("Skipping Hiker strats") + skipHiker = true +end + +function control.wildEncounter() + encounters = encounters + 1 + paint.wildEncounters(encounters) + bridge.encounter() +end + +function control.reset() + oneHits = false + shouldCatch = nil + shouldFight = nil + extraEncounter = nil + skipHiker = false + potionIn = true + encounters = 0 + fightEncounter = 0 + caveFights = 0 + battleYolo = false + isYolo = false + maxEncounters = nil +end + +return control diff --git a/ai/strategies.lua b/ai/strategies.lua new file mode 100644 index 0000000..16d2be2 --- /dev/null +++ b/ai/strategies.lua @@ -0,0 +1,2712 @@ +local strategies = {} + +local combat = require "ai.combat" +local control = require "ai.control" + +local battle = require "action.battle" +local shop = require "action.shop" +local textbox = require "action.textbox" +local walk = require "action.walk" + +local bridge = require "util.bridge" +local input = require "util.input" +local memory = require "util.memory" +local menu = require "util.menu" +local paint = require "util.paint" +local player = require "util.player" +local utils = require "util.utils" + +local inventory = require "storage.inventory" +local pokemon = require "storage.pokemon" + +local tries = 0 +local tempDir, canProgress, initialized +local areaName +local nidoAttack, nidoSpeed, nidoSpecial = 0, 0, 0 +local squirtleAtt, squirtleDef, squirtleSpd, squirtleScl +local deepRun, resetting +local level4Nidoran = true +local skipHiker, yolo, riskGiovanni, maxEtherSkip + +local timeRequirements = { + mankey = function() + local timeLimit = 33 + if (pokemon.inParty("paras")) then + timeLimit = timeLimit + 0.75 + end + return timeLimit + end, + + goldeen = function() + local timeLimit = 38 + if (pokemon.inParty("paras")) then + timeLimit = timeLimit + 0.75 + end + return timeLimit + end, + + misty = function() + local timeLimit = 40 + if (pokemon.inParty("paras")) then + timeLimit = timeLimit + 0.75 + end + return timeLimit + end, + + vermilion = function() + return 44.25 + end, + + trash = function() + local timeLimit = 47 + if (nidoSpecial > 44) then + timeLimit = timeLimit + 0.25 + end + if (nidoAttack > 53) then + timeLimit = timeLimit + 0.25 + end + if (nidoAttack >= 54 and nidoSpecial >= 45) then + timeLimit = timeLimit + 0.25 + end + return timeLimit + end, + + safari_carbos = function() + return 70.5 + end, + + e4center = function() + return 102 + end, + + blue = function() + return 108.2 + end, +} + +-- Reset + +local function initialize() + if (not initialized) then + initialized = true + return true + end +end + +local function hardReset(message, extra) + resetting = true + if (extra) then + message = message.." | "..extra + end + if (strategies.seed) then + message = message.." | "..strategies.seed + end + bridge.chat(message) + client.reboot_core() + return true +end + +local function reset(reason, extra) + local time = paint.elapsedTime() + local resetString = "Reset" + if (time) then + resetString = resetString.." after "..time + end + if (areaName) then + resetString = " "..resetString.." at "..areaName + end + local separator + if (deepRun and not yolo) then + separator = " BibleThump" + else + separator = ":" + end + resetString = resetString..separator.." "..reason + return hardReset(resetString, extra) +end +strategies.reset = reset + +local function resetDeath(extra) + local reason + if (strategies.criticaled) then + reason = "Critical'd" + elseif (yolo) then + reason = "Yolo strats" + else + reason = "Died" + end + return reset(reason, extra) +end +strategies.death = resetDeath + +local function overMinute(min) + return utils.igt() > min * 60 +end + +local function resetTime(timeLimit, reason, once) + if (overMinute(timeLimit)) then + reason = "Took too long to "..reason + if (RESET_FOR_TIME) then + return reset(reason) + end + if (once) then + print(reason.." "..paint.elapsedTime()) + end + end +end + +local function getTimeRequirement(name) + return timeRequirements[name]() +end + +local function setYolo(name) + local minimumTime = getTimeRequirement(name) + local shouldYolo = overMinute(minimumTime) + if (yolo ~= shouldYolo) then + yolo = shouldYolo + control.setYolo(shouldYolo) + local prefix + if (yolo) then + prefix = "en" + else + prefix = "dis" + end + if (areaName) then + print("YOLO "..prefix.."abled at "..areaName) + else + print("YOLO "..prefix.."abled") + end + end + return yolo +end + +-- Local functions + +local function hasHealthFor(opponent, extra) + if (not extra) then + extra = 0 + end + return pokemon.index(0, "hp") + extra > combat.healthFor(opponent) +end + +local function damaged(factor) + if (not factor) then + factor = 1 + end + return pokemon.index(0, "hp") * factor < pokemon.index(0, "max_hp") +end + +local function opponentDamaged(factor) + if (not factor) then + factor = 1 + end + return memory.double("battle", "opponent_hp") * factor < memory.double("battle", "opponent_max_hp") +end + +local function redHP() + return math.ceil(pokemon.index(0, "max_hp") * 0.2) +end + +local function buffTo(buff, defLevel) + if (battle.isActive()) then + canProgress = true + local forced + if (memory.double("battle", "opponent_defense") > defLevel) then + forced = buff + end + battle.automate(forced, true) + elseif (canProgress) then + return true + else + battle.automate() + end +end + +local function dodgeUp(npc, sx, sy, dodge, offset) + if (not battle.handleWild()) then + return false + end + local px, py = player.position() + if (py < sy - 1) then + return true + end + local wx, wy = px, py + if (py < sy) then + wy = py - 1 + elseif (px == sx or px == dodge) then + if (px - memory.raw(npc) == offset) then + if (px == sx) then + wx = dodge + else + wx = sx + end + else + wy = py - 1 + end + end + walk.step(wx, wy) +end + +local function dodgeH(options) + local left = 1 + if (options.left) then + left = -1 + end + local px, py = player.position() + if (px * left > options.sx * left + (options.dist or 1) * left) then + return true + end + local wx, wy = px, py + if (px * left > options.sx * left) then + wx = px + 1 * left + elseif (py == options.sy or py == options.dodge) then + if (py - memory.raw(options.npc) == options.offset) then + if (py == options.sy) then + wy = options.dodge + else + wy = options.sy + end + else + wx = px + 1 * left + end + end + walk.step(wx, wy) +end + +local function completedMenuFor(data) + local count = inventory.count(data.item) + if (count == 0 or count + (data.amount or 1) <= tries) then + return true + end + return false +end + +local function closeMenuFor(data) + if ((not tempDir and not data.close) or data.chain or menu.close()) then + return true + end +end + +local function useItem(data) + local main = memory.value("menu", "main") + if (tries == 0) then + tries = inventory.count(data.item) + if (tries == 0) then + if (closeMenuFor(data)) then + return true + end + return false + end + end + if (completedMenuFor(data)) then + if (closeMenuFor(data)) then + return true + end + else + if (inventory.use(data.item, data.poke)) then + tempDir = true + else + menu.pause() + end + end +end + +local function completedSkillFor(data) + if (data.map) then + if (data.map ~= memory.value("game", "map")) then + return true + end + elseif (data.x or data.y) then + local px, py = player.position() + if (data.x == px or data.y == py) then + return true + end + elseif (data.done) then + if (memory.raw(data.done) > (data.val or 0)) then + return true + end + elseif (tries > 0 and not menu.isOpen()) then + return true + end + return false +end + +local function isPrepared(...) + if (tries == 0) then + tries = {} + for i,name in ipairs(arg) do + tries[i] = {name, inventory.count(name)} + end + end + local item, found + for i,itemState in ipairs(tries) do + local name = itemState[1] + local count = itemState[2] + if (count > 0 and count == inventory.count(name)) then + local opp = itemState[3] + if (not opp or opp == memory.value("battle", "opponent_id")) then + return false + end + end + end + return true +end + +local function prepare(...) + if (tries == 0) then + tries = {} + for i,name in ipairs(arg) do + tries[i] = {name, inventory.count(name)} + end + end + local item, found + for i,itemState in ipairs(tries) do + local name = itemState[1] + local count = itemState[2] + if (count > 0 and count == inventory.count(name)) then + local opp = itemState[3] + found = true + if (not opp or opp == memory.value("battle", "opponent_id")) then + item = name + break + end + end + end + if (not item) then + if (not found) then + return true + end + battle.automate() + elseif (battle.isActive()) then + inventory.use(item, nil, true) + else + input.cancel() + end +end + +-- DSum + +local function nidoranDSum(disabled) + local sx, sy = player.position() + if (not disabled and tries == nil) then + local opName = battle.opponent() + local opLevel = memory.value("battle", "opponent_level") + if (opName == "rattata") then + if (opLevel == 2) then + tries = {0, 4, 12} + elseif (opLevel == 3) then + tries = {0, 14, 11} + else + -- tries = {0, 0, 10} -- TODO can't escape + end + elseif (opName == "spearow") then + elseif (opName == "nidoran") then + tries = {0, 6, 12} + elseif (opName == "nidoranf") then + if (opLevel == 3) then + tries = {4, 6, 12} + else + tries = {5, 6, 12} + end + end + if (tries) then + tries.idx = 1 + tries.x, tries.y = sx, sy + else + tries = 0 + end + end + if (not disabled and tries ~= 0) then + if (tries[tries.idx] == 0) then + tries.idx = tries.idx + 1 + if (tries.idx > 3) then + tries = 0 + end + return nidoranDSum() + end + if (tries.x ~= sx or tries.y ~= sy) then + tries[tries.idx] = tries[tries.idx] - 1 + tries.x, tries.y = sx, sy + end + if (tries.idx == 2) then + sy = 11 + else + sy = 12 + end + else + sy = 11 + end + if (sx == 33) then + sx = 32 + else + sx = 33 + end + walk.step(sx, sy) +end + +-- Strategies + +local strategyFunctions +strategyFunctions = { + + a = function(data) + areaName = data.a + return true + end, + + startFrames = function() + strategies.frames = 0 + return true + end, + + reportFrames = function() + print("FR "..strategies.frames) + local repels = memory.value("player", "repel") + if (repels > 0) then + print("S "..repels) + end + strategies.frames = nil + return true + end, + + tweetMisty = function() + local elt = paint.elapsedTime() + setYolo("misty") + print("Misty: "..elt) + return true + end, + + tweetVictoryRoad = function() + local elt = paint.elapsedTime() + bridge.tweet("Entering Victory Road at "..elt.." on our way to the Elite Four! http://www.twitch.tv/thepokebot") + return true + end, + + split = function(data) + bridge.split(control.encounters(), data and data.finished) + return true + end, + + wait = function() + print("Please save state") + input.press("Start", 9001) + end, + + emuSpeed = function(data) + -- client.speedmode = data.percent + return true + end, + +-- Global + + interact = function(data) + if (battle.handleWild()) then + if (battle.isActive()) then + return true + end + if (textbox.isActive()) then + if (tries > 0) then + return true + end + tries = tries - 1 + input.cancel() + elseif (player.interact(data.dir)) then + tries = tries + 1 + end + end + end, + + confirm = function(data) + if (battle.handleWild()) then + if (textbox.isActive()) then + tries = tries + 1 + input.cancel(data.type or "A") + else + if (tries > 0) then + return true + end + player.interact(data.dir) + end + end + end, + + item = function(data) + if (battle.handleWild()) then + if (data.full and not inventory.isFull()) then + if (closeMenuFor(data)) then + return true + end + return false + end + return useItem(data) + end + end, + + potion = function(data) + local curr_hp = pokemon.index(0, "hp") + if (curr_hp == 0) then + return false + end + local toHP = data.hp + if (yolo and data.yolo ~= nil) then + toHP = data.yolo + elseif (type(toHP) == "string") then + toHP = combat.healthFor(toHP) + end + local toHeal = toHP - curr_hp + if (toHeal > 0) then + local toPotion + if (data.forced) then + toPotion = inventory.contains(data.forced) + else + local p_first, p_second, p_third + if (toHeal > 50) then + if (data.full) then + p_first = "full_restore" + else + p_first = "super_potion" + end + p_second, p_third = "super_potion", "potion" + else + if (toHeal > 20) then + p_first, p_second = "super_potion", "potion" + else + p_first, p_second = "potion", "super_potion" + end + if (data.full) then + p_third = "full_restore" + end + end + toPotion = inventory.contains(p_first, p_second, p_third) + end + if (toPotion) then + if (menu.pause()) then + inventory.use(toPotion) + tempDir = true + end + return false + end + end + if (closeMenuFor(data)) then + return true + end + end, + + teach = function(data) + if (data.full and not inventory.isFull()) then + return true + end + local itemName + if (data.item) then + itemName = data.item + else + itemName = data.move + end + if (pokemon.hasMove(data.move)) then + local main = memory.value("menu", "main") + if (main == 128) then + if (data.chain) then + return true + end + elseif (main < 3) then + return true + end + input.press("B") + else + local replacement + if (data.replace) then + replacement = pokemon.moveIndex(data.replace, data.poke) - 1 + else + replacement = 0 + end + if (inventory.teach(itemName, data.poke, replacement, data.alt)) then + tempDir = true + else + menu.pause() + end + end + end, + + skill = function(data) + if (completedSkillFor(data)) then + if (not textbox.isActive()) then + return true + end + input.press("B") + elseif (not data.dir or player.face(data.dir)) then + if (pokemon.use(data.move)) then + tries = tries + 1 + else + menu.pause() + end + end + end, + + fly = function(data) + if (memory.value("game", "map") == data.map) then + return true + end + local cities = { + pallet = {62, "Up"}, + viridian = {63, "Up"}, + lavender = {66, "Down"}, + celadon = {68, "Down"}, + fuchsia = {69, "Down"}, + cinnabar = {70, "Down"}, + } + + local main = memory.value("menu", "main") + if (main == 228) then + local currentFly = memory.raw(0x1FEF) + local destination = cities[data.dest] + local press + if (destination[1] - currentFly == 0) then + press = "A" + else + press = destination[2] + end + input.press(press) + elseif (not pokemon.use("fly")) then + menu.pause() + end + end, + + bicycle = function() + if (memory.raw(0x1700) == 1) then + if (textbox.handle()) then + return true + end + else + return useItem({item="bicycle"}) + end + end, + + fightXAccuracy = function() + return prepare("x_accuracy") + end, + + waitToTalk = function() + if (battle.isActive()) then + canProgress = false + battle.automate() + elseif (textbox.isActive()) then + canProgress = true + input.cancel() + elseif (canProgress) then + return true + end + end, + + waitToPause = function() + local main = memory.value("menu", "main") + if (main == 128) then + if (canProgress) then + return true + end + elseif (battle.isActive()) then + canProgress = false + battle.automate() + elseif (main == 123) then + canProgress = true + input.press("B") + elseif (textbox.handle()) then + input.press("Start", 2) + end + end, + + waitToFight = function(data) + if (battle.isActive()) then + canProgress = true + battle.automate() + elseif (canProgress) then + return true + elseif (textbox.handle()) then + if (data.dir) then + player.interact(data.dir) + else + input.cancel() + end + end + end, + + allowDeath = function(data) + strategies.canDie = data.on + return true + end, + +-- Route + + squirtleIChooseYou = function() + if (pokemon.inParty("squirtle")) then + bridge.caught("squirtle") + return true + end + if (player.face("Up")) then + textbox.name("A") + end + end, + + fightBulbasaur = function() + if (tries < 9000 and pokemon.index(0, "level") == 6) then + if (tries > 200) then + squirtleAtt = pokemon.index(0, "attack") + squirtleDef = pokemon.index(0, "defense") + squirtleSpd = pokemon.index(0, "speed") + squirtleScl = pokemon.index(0, "special") + if (squirtleAtt < 11 and squirtleScl < 12) then + return reset("Bad Squirtle - "..squirtleAtt.." attack, "..squirtleScl.." special") + end + tries = 9001 + else + tries = tries + 1 + end + end + if (battle.isActive() and memory.double("battle", "opponent_hp") > 0 and resetTime(2.15, "kill Bulbasaur")) then + return true + end + return buffTo("tail_whip", 6) + end, + + dodgePalletBoy = function() + return dodgeUp(0x0223, 14, 14, 15, 7) + end, + + viridianBuyPokeballs = function() + return shop.transaction{ + buy = {{name="pokeball", index=0, amount=8}} + } + end, + + catchNidoran = function() + if (not control.canCatch()) then + return true + end + local pokeballs = inventory.count("pokeball") + local caught = memory.value("player", "party_size") - 1 + if (pokeballs < 5 - caught * 2) then + return reset("Ran out of PokeBalls", pokeballs) + end + if (battle.isActive()) then + local isNidoran = pokemon.isOpponent("nidoran") + if (isNidoran and memory.value("battle", "opponent_level") > 2) then + if (initialize()) then + bridge.pollForName() + end + end + tries = nil + if (memory.value("menu", "text_input") == 240) then + textbox.name() + elseif (memory.value("battle", "menu") == 95) then + if (isNidoran) then + input.press("A") + else + input.cancel() + end + elseif (not control.shouldCatch()) then + if (control.shouldFight()) then + battle.fight() + else + battle.run() + end + end + else + local noDSum + pokemon.updateParty() + local hasNidoran = pokemon.inParty("nidoran") + if (hasNidoran) then + if (not tempDir) then + bridge.caught("nidoran") + tempDir = true + end + if (pokemon.getExp() > 205) then + local nidoranLevel = pokemon.info("nidoran", "level") + level4Nidoran = nidoranLevel == 4 + print("Level "..nidoranLevel.." Nidoran") + return true + end + noDSum = true + end + local timeLimit = 6.25 + if (pokemon.inParty("spearow")) then + timeLimit = timeLimit + 0.67 + end + local resetMessage + if (hasNidoran) then + resetMessage = "get an experience kill before Brock" + else + resetMessage = "find a Nidoran" + end + if (resetTime(timeLimit, resetMessage)) then + return true + end + if (not noDSum and overMinute(timeLimit - 0.25)) then + noDSum = true + end + nidoranDSum(noDSum) + end + end, + +-- 1: NIDORAN + + dodgeViridianOldMan = function() + return dodgeUp(0x0273, 18, 6, 17, 9) + end, + + grabAntidote = function() + local px, py = player.position() + if (py < 11) then + return true + end + if (pokemon.info("spearow", "level") == 3) then + if (px < 26) then + px = 26 + else + py = 10 + end + elseif (inventory.contains("antidote")) then + py = 10 + else + player.interact("Up") + end + walk.step(px, py) + end, + + fightWeedle = function() + if (battle.isTrainer()) then + canProgress = true + local squirtleOut = pokemon.isDeployed("squirtle") + if (squirtleOut and memory.value("battle", "our_status") > 0 and not inventory.contains("antidote")) then + return reset("Poisoned, but we skipped the antidote") + end + local sidx = pokemon.indexOf("spearow") + if (sidx ~= -1 and pokemon.index(sidx, "level") > 3) then + sidx = -1 + end + if (sidx == -1) then + return buffTo("tail_whip", 5) + end + if (pokemon.index(sidx, "hp") < 1) then + local battleMenu = memory.value("battle", "menu") + if (utils.onPokemonSelect(battleMenu)) then + menu.select(pokemon.indexOf("squirtle"), true) + elseif (battleMenu == 95) then + input.press("A") + elseif (squirtleOut) then + battle.automate() + else + input.cancel() + end + elseif (squirtleOut) then + battle.swap("spearow") + else + local peck = combat.bestMove() + local forced + if (peck and peck.damage and peck.damage + 1 >= memory.double("battle", "opponent_hp")) then + forced = "growl" + end + battle.fight(forced) + end + elseif (canProgress) then + return true + end + end, + + equipForBrock = function(data) + if (initialize()) then + if (pokemon.info("squirtle", "level") < 8) then + return reset("Not level 8 before Brock", pokemon.getExp()) + end + if (data.anti) then + local poisoned = pokemon.info("squirtle", "status") > 0 + if (not poisoned) then + return true + end + if (not inventory.contains("antidote")) then + return reset("Poisoned, but we skipped the antidote") + end + if (inventory.contains("potion") and pokemon.info("squirtle", "hp") > 8) then + return true + end + end + end + local main = memory.value("menu", "main") + local nidoranIndex = pokemon.indexOf("nidoran") + if (nidoranIndex == 0) then + if (menu.close()) then + return true + end + elseif (menu.pause()) then + local column = menu.getCol() + if (pokemon.info("squirtle", "status") > 0) then + inventory.use("antidote", "squirtle") + elseif (inventory.contains("potion") and pokemon.info("squirtle", "hp") < 15) then + inventory.use("potion", "squirtle") + else + if (main == 128) then + if (column == 11) then + menu.select(1, true) + elseif (column == 12) then + menu.select(1, true) + else + input.press("B") + end + elseif (main == 103) then + if (memory.value("menu", "selection_mode") == 1) then + menu.select(nidoranIndex, true) + else + menu.select(0, true) + end + else + input.press("B") + end + end + end + end, + + fightBrock = function() + local squirtleHP = pokemon.info("squirtle", "hp") + if (squirtleHP == 0) then + return resetDeath() + end + if (battle.isActive()) then + if (tries < 1) then + tries = 1 + end + local bubble, turnsToKill, turnsToDie = combat.bestMove() + if (not pokemon.isDeployed("squirtle")) then + battle.swap("squirtle") + elseif (turnsToDie and turnsToDie < 2 and inventory.contains("potion")) then + inventory.use("potion", "squirtle", true) + else + local battleMenu = memory.value("battle", "menu") + local bideTurns = memory.value("battle", "opponent_bide") + if (battleMenu == 95 and menu.getCol() == 1) then + input.press("A") + elseif (bideTurns > 0) then + local onixHP = memory.double("battle", "opponent_hp") + if (not canProgress) then + canProgress = onixHP + tempDir = bideTurns + end + if (turnsToKill) then + local forced + if (turnsToDie < 2 or turnsToKill < 2 or tempDir - bideTurns > 1) then + elseif (onixHP == canProgress) then + forced = "tail_whip" + end + battle.fight(forced) + else + input.cancel() + end + elseif (utils.onPokemonSelect(battleMenu)) then + menu.select(pokemon.indexOf("nidoran"), true) + else + canProgress = false + battle.fight() + end + if (tries < 9000) then + local nidx = pokemon.indexOf("nidoran") + if (pokemon.index(nidx, "level") == 8) then + local att = pokemon.index(nidx, "attack") + local def = pokemon.index(nidx, "defense") + local spd = pokemon.index(nidx, "speed") + local scl = pokemon.index(nidx, "special") + bridge.stats(att.." "..def.." "..spd.." "..scl) + nidoAttack = att + nidoSpeed = spd + nidoSpecial = scl + if (tries > 300) then + local statDiff = (16 - att) + (15 - spd) + (13 - scl) + if (def < 12) then + statDiff = statDiff + 1 + end + local resets = att < 15 or spd < 14 or scl < 12 or statDiff > 3 + if (not resets and att == 15 and spd == 14) then + resets = true + end + local nStatus = "Att: "..att..", Def: "..def..", Speed: "..spd..", Special: "..scl + if (resets) then + return reset("Bad Nidoran - "..nStatus) + end + tries = 9001 + local superlative + local exclaim = "!" + if (statDiff == 0) then + if (def == 14) then + superlative = " god" + exclaim = "! Kreygasm" + else + superlative = " perfect" + end + elseif (att == 16 and spd == 15) then + if (statDiff == 1) then + superlative = " great" + elseif (statDiff == 2) then + superlative = " good" + end + elseif (statDiff == 1) then + superlative = " good" + elseif (statDiff == 2) then + superlative = "n okay" + exclaim = "." + else + superlative = " min stat" + exclaim = "." + end + nStatus = "Beat Brock with a"..superlative.." Nidoran"..exclaim.." "..nStatus + bridge.chat(nStatus) + else + tries = tries + 1 + end + end + end + end + elseif (tries > 0) then + return true + elseif (textbox.handle()) then + player.interact("Up") + end + end, + +-- 2: BROCK + + pewterMart = function() + return shop.transaction{ + buy = {{name="potion", index=1, amount=7}, {name="escape_rope", index=2}} + } + end, + + battleModeSet = function() + if (memory.value("setting", "battle_style") == 10) then + if (menu.close()) then + return true + end + elseif (menu.pause()) then + local main = memory.value("menu", "main") + if (main == 128) then + if (menu.getCol() ~= 11) then + input.press("B") + else + menu.select(5, true) + end + elseif (main == 228) then + menu.setOption("battle_style", 8, 10) + else + input.press("B") + end + end + end, + + leer = function(data) + local bm = combat.bestMove() + if (not bm or bm.minTurns < 3) then + if (battle.isActive()) then + canProgress = true + elseif (canProgress) then + return true + end + battle.automate() + return false + end + local opp = battle.opponent() + local defLimit = 9001 + for i,poke in ipairs(data) do + if (opp == poke[1] and (not poke[3] or nidoAttack > poke[3])) then + defLimit = poke[2] + break + end + end + return buffTo("leer", defLimit) + end, + + shortsKid = function() + control.battlePotion(not pokemon.isOpponent("rattata") or damaged(2)) + return strategyFunctions.leer({{"rattata",9}, {"ekans",10}}) + end, + + potionBeforeCocoons = function() + if (yolo or nidoSpeed > 14) then + return true + end + return strategyFunctions.potion({hp=6}) + end, + + swapHornAttack = function() + if (pokemon.battleMove("horn_attack") == 1) then + return true + end + battle.swapMove(1, 3) + end, + + fightMetapod = function() + if (battle.isActive()) then + canProgress = true + if (memory.double("battle", "opponent_hp") > 0 and pokemon.isOpponent("metapod")) then + return true + end + battle.automate() + elseif (canProgress) then + return true + else + battle.automate() + end + end, + + catchFlierBackup = function() + if (initialize()) then + strategies.canDie = true + end + if (not control.canCatch()) then + return true + end + local caught = pokemon.inParty("pidgey", "spearow") + if (battle.isActive()) then + if (memory.double("battle", "our_hp") == 0) then + if (pokemon.info("squirtle", "hp") == 0) then + strategies.canDie = false + elseif (utils.onPokemonSelect(memory.value("battle", "menu"))) then + menu.select(pokemon.indexOf("squirtle"), true) + else + input.press("A") + end + elseif (not control.shouldCatch()) then + battle.run() + end + else + local birdPath + local px, py = player.position() + if (caught) then + if (px > 33) then + return true + end + local startY = 9 + if (px > 28) then + startY = py + end + birdPath = {{32,startY}, {32,11}, {34,11}} + elseif (px == 37) then + if (py == 10) then + py = 11 + else + py = 10 + end + walk.step(px, py) + else + birdPath = {{32,10}, {32,11}, {34,11}, {34,10}, {37,10}} + end + if (birdPath) then + walk.custom(birdPath) + end + end + end, + +-- 3: ROUTE 3 + + startMtMoon = function() + strategies.moonEncounters = 0 + strategies.canDie = nil + skipHiker = nidoAttack > 15 -- RISK or level4Nidoran + if (skipHiker) then + control.mtMoonExp() + end + return true + end, + + evolveNidorino = function() + if (pokemon.inParty("nidorino")) then + bridge.caught("nidorino") + return true + end + if (battle.isActive()) then + tries = 0 + canProgress = true + if (memory.double("battle", "opponent_hp") == 0) then + input.press("A") + else + battle.automate() + end + elseif (tries > 3600) then + print("Broke from Nidorino on tries") + return true + else + if (canProgress) then + tries = tries + 1 + end + input.press("A") + end + end, + + teachWaterGun = function() + if (battle.handleWild()) then + if (not pokemon.inParty("nidorino")) then + print("") + print("") + print("") + print("") + print("") + return reset("Did not evolve to Nidorino", pokemon.info("nidoran", "level")) + end + return strategyFunctions.teach({move="water_gun",replace="tackle"}) + end + end, + + fightHiker = function() + if (skipHiker) then + return true + end + return strategyFunctions.interact({dir="Left"}) + end, + + evolveNidoking = function() + if (battle.handleWild()) then + if (not inventory.contains("moon_stone")) then + if (initialize()) then + bridge.caught("nidoking") + end + if (menu.close()) then + return true + end + elseif (not inventory.use("moon_stone")) then + menu.pause() + end + end + end, + + helix = function() + if (battle.handleWild()) then + if (inventory.contains("helix_fossil")) then + return true + end + player.interact("Up") + end + end, + + reportMtMoon = function() + if (battle.pp("horn_attack") == 0) then + print("ERR: Ran out of Horn Attacks") + end + if (strategies.moonEncounters) then + local parasStatus + local conjunction = "but" + local goodEncounters = strategies.moonEncounters < 10 + local parasCatch + if (pokemon.inParty("paras")) then + parasCatch = "paras" + if (goodEncounters) then + conjunction = "and" + end + parasStatus = "we found a Paras!" + else + parasCatch = "no_paras" + if (not goodEncounters) then + conjunction = "and" + end + parasStatus = "we didn't find a Paras :(" + end + bridge.caught(parasCatch) + bridge.chat(strategies.moonEncounters.." Moon encounters, "..conjunction.." "..parasStatus) + strategies.moonEncounters = nil + end + + local timeLimit = 26 + if (nidoAttack > 15 and nidoSpeed > 14) then + timeLimit = timeLimit + 0.25 + end + if (not skipHiker) then + timeLimit = timeLimit + 0.25 + end + if (pokemon.inParty("paras")) then + timeLimit = timeLimit + 1.0 + end + resetTime(timeLimit, "complete Mt. Moon", true) + return true + end, + +-- 4: MT. MOON + + dodgeCerulean = function() + return dodgeH{ + npc = 0x0242, + sx = 14, sy = 18, + dodge = 19, + offset = 10, + dist = 4 + } + end, + + dodgeCeruleanLeft = function() + return dodgeH{ + npc = 0x0242, + sx = 16, sy = 18, + dodge = 17, + offset = 10, + dist = -7, + left = true + } + end, + + rivalSandAttack = function(data) + if (battle.isActive()) then + local forced + if (not pokemon.isDeployed("nidoking")) then + local battleMenu = memory.value("battle", "menu") + if (utils.onPokemonSelect(battleMenu)) then + menu.select(pokemon.indexOf("nidoking"), true) + elseif (battleMenu == 95 and menu.getCol() == 1) then + input.press("A") + else + local __, turns = combat.bestMove() + if (turns == 1 and battle.pp("sand_attack") > 0) then + forced = "sand_attack" + end + battle.fight(forced) + end + return false + end + local opponent = battle.opponent() + if (opponent == "pidgeotto") then + canProgress = true + combat.disableThrash = true + if (memory.value("battle", "accuracy") < 7) then + local __, turns = combat.bestMove() + local putIn, takeOut + if (turns == 1) then + local sacrifice + local temp = pokemon.inParty("pidgey", "spearow") + if (temp and pokemon.info(temp, "hp") > 0) then + sacrifice = temp + end + if (not sacrifice) then + if (yolo) then + temp = pokemon.inParty("oddish") + else + temp = pokemon.inParty("oddish", "paras", "squirtle") + end + if (temp and pokemon.info(temp, "hp") > 0) then + sacrifice = temp + end + end + if (sacrifice) then + battle.swap(sacrifice) + return false + end + end + end + elseif (opponent == "raticate") then + combat.disableThrash = opponentDamaged() or (not yolo and pokemon.index(0, "hp") < 32) -- RISK + elseif (opponent == "ivysaur") then + if (not yolo and damaged(5) and inventory.contains("super_potion")) then + inventory.use("super_potion", nil, true) + return false + end + combat.disableThrash = opponentDamaged() + else + combat.disableThrash = false + end + battle.automate(forced) + canProgress = true + elseif (canProgress) then + combat.disableThrash = false + return true + else + textbox.handle() + end + end, + + teachThrash = function() + if (initialize()) then + if (pokemon.hasMove("thrash") or pokemon.info("nidoking", "level") < 21) then + return true + end + end + if (strategyFunctions.teach({move="thrash",item="rare_candy",replace="leer"})) then + if (menu.close()) then + local att = pokemon.index(0, "attack") + local def = pokemon.index(0, "defense") + local spd = pokemon.index(0, "speed") + local scl = pokemon.index(0, "special") + local statDesc = att.." "..def.." "..spd.." "..scl + nidoAttack = att + nidoSpeed = spd + nidoSpecial = scl + bridge.stats(statDesc) + print(statDesc) + return true + end + end + end, + + redbarMankey = function() + if (not setYolo("mankey")) then + return true + end + local curr_hp, red_hp = pokemon.index(0, "hp"), redHP() + if (curr_hp <= red_hp) then + return true + end + if (initialize()) then + if (pokemon.info("nidoking", "level") < 21 or inventory.count("potion") < 3) then -- RISK + return true + end + bridge.chat("Using Poison Sting to attempt to redbar off Mankey") + end + if (battle.isActive()) then + canProgress = true + local enemyMove, enemyTurns = combat.enemyAttack() + if (enemyTurns) then + if (enemyTurns < 2) then + return true + end + local scratchDmg = enemyMove.damage + if (curr_hp - red_hp > scratchDmg) then + return true + end + end + battle.automate("poison_sting") + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + potionBeforeGoldeen = function() + if (initialize()) then + if (setYolo("goldeen") or pokemon.index(0, "hp") > 7) then + return true + end + end + return strategyFunctions.potion({hp=64, chain=true}) + end, + + potionBeforeMisty = function() + local healAmount = 70 + if (yolo) then + if (nidoAttack > 53 and nidoSpeed > 50) then + healAmount = 45 + elseif (nidoAttack > 53) then + healAmount = 65 + end + else + if (nidoAttack > 54 and nidoSpeed > 51) then -- RISK + healAmount = 45 + elseif (nidoAttack > 53 and nidoSpeed > 50) then + healAmount = 65 + end + end + return strategyFunctions.potion({hp=healAmount}) + end, + +-- 6: MISTY + + potionBeforeRocket = function() + local minAttack = 55 -- RISK + if (yolo) then + minAttack = minAttack - 1 + end + if (nidoAttack >= minAttack) then + return true + end + return strategyFunctions.potion({hp=10}) + end, + + jingleSkip = function() + if (canProgress) then + local px, py = player.position() + if (px < 4) then + return true + end + input.press("Left", 0) + else + input.press("A", 0) + canProgress = true + end + end, + + catchOddish = function() + if (not control.canCatch()) then + return true + end + local caught = pokemon.inParty("oddish", "paras") + local battleValue = memory.value("game", "battle") + local px, py = player.position() + if (battleValue > 0) then + if (battleValue == 2) then + tries = 2 + battle.automate() + else + if (tries == 0 and py == 31) then + tries = 1 + end + if (not control.shouldCatch()) then + battle.run() + end + end + elseif (tries == 1 and py == 31) then + player.interact("Left") + else + local path + if (caught) then + if (not tempDir) then + bridge.caught(pokemon.inParty("oddish")) + tempDir = true + end + if (py < 21) then + py = 21 + elseif (py < 24) then + if (px < 16) then + px = 17 + else + py = 24 + end + elseif (py < 25) then + py = 25 + elseif (px > 15) then + px = 15 + elseif (py < 28) then + py = 28 + elseif (py > 29) then + py = 29 + elseif (px ~= 11) then + px = 11 + elseif (py ~= 29) then + py = 29 + else + return true + end + walk.step(px, py) + elseif (px == 12) then + local dy + if (py == 30) then + dy = 31 + else + dy = 30 + end + walk.step(px, dy) + else + local path = {{15,19}, {15,25}, {15,25}, {15,27}, {14,27}, {14,30}, {12,30}} + walk.custom(path) + end + end + end, + + vermilionMart = function() + if (initialize()) then + setYolo("vermilion") + end + local buyArray, sellArray + if (not inventory.contains("pokeball") or (not yolo and nidoAttack < 53)) then + sellArray = {{name="pokeball"}, {name="antidote"}, {name="tm34"}, {name="nugget"}} + buyArray = {{name="super_potion",index=1,amount=3}, {name="paralyze_heal",index=4,amount=2}, {name="repel",index=5,amount=3}} + else + sellArray = {{name="antidote"}, {name="tm34"}, {name="nugget"}} + buyArray = {{name="super_potion",index=1,amount=3}, {name="repel",index=5,amount=3}} + end + return shop.transaction{ + sell = sellArray, + buy = buyArray + } + end, + + trashcans = function() + local progress = memory.value("progress", "trashcans") + if (textbox.isActive()) then + if (not canProgress) then + if (progress < 2) then + tries = tries + 1 + end + canProgress = true + end + input.cancel() + else + if (progress == 3) then + local px, py = player.position() + if (px == 4 and py == 6) then + tries = tries + 1 + + local timeLimit = getTimeRequirement("trash") + 1 + if (resetTime(timeLimit, "complete Trashcans ("..tries.." tries)")) then + return true + end + setYolo("trash") + local prefix + local suffix = "!" + if (tries < 2) then + prefix = "PERFECT" + elseif (tries < 4) then + prefix = "Amazing" + elseif (tries < 7) then + prefix = "Great" + elseif (tries < 10) then + prefix = "Good" + elseif (tries < 24) then + prefix = "Ugh" + suffix = "." + else + prefix = "Reset me now" + suffix = " BibleThump" + end + bridge.chat(prefix..", "..tries.." try Trashcans"..suffix, paint.elapsedTime()) + return true + end + local completePath = { + Down = {{2,11}, {8,7}}, + Right = {{2,12}, {3,12}, {2,6}, {3,6}}, + Left = {{9,8}, {8,8}, {7,8}, {6,8}, {5,8}, {9,10}, {8,10}, {7,10}, {6,10}, {5,10}, {}, {}, {}, {}, {}, {}}, + } + local walkIn = "Up" + for dir,tileset in pairs(completePath) do + for i,tile in ipairs(tileset) do + if (px == tile[1] and py == tile[2]) then + walkIn = dir + break + end + end + end + input.press(walkIn, 0) + elseif (progress == 2) then + if (canProgress) then + canProgress = false + walk.invertCustom() + end + local inverse = { + Up = "Down", + Right = "Left", + Down = "Up", + Left = "Right" + } + player.interact(inverse[tempDir]) + else + local trashPath = {{2,11},{"Left"},{2,11}, {2,12},{4,12},{4,11},{"Right"},{4,11}, {4,9},{"Left"},{4,9}, {4,7},{"Right"},{4,7}, {4,6},{2,6},{2,7},{"Left"},{2,7}, {2,6},{4,6},{4,8},{7,8},{"Down"},{7,8}, {9,8},{"Up"},{9,8}, {8,8},{8,11},{"Right"},{8,11}} + if (tempDir and type(tempDir) == "number") then + local px, py = player.position() + local dx, dy = px, py + if (py < 12) then + dy = 12 + elseif (tempDir == 1) then + dx = 2 + else + dx = 8 + end + if (px ~= dx or py ~= dy) then + walk.step(dx, dy) + return + end + tempDir = nil + end + tempDir = walk.custom(trashPath, canProgress) + canProgress = false + end + end + end, + + fightSurge = function() + if (battle.isActive()) then + canProgress = true + local forced + if (pokemon.isOpponent("voltorb")) then + combat.disableThrash = true + local __, enemyTurns = combat.enemyAttack() + if (not enemyTurns or enemyTurns > 2) then + forced = "bubblebeam" + elseif (enemyTurns == 2 and not opponentDamaged()) then + local curr_hp, red_hp = pokemon.index(0, "hp"), redHP() + local afterHit = curr_hp - 20 + if (afterHit > 5 and afterHit <= red_hp) then + forced = "bubblebeam" + end + end + else + combat.disableThrash = false + end + battle.automate(forced) + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + +-- 7: SURGE + + dodgeBicycleGirlRight = function() + return dodgeH{ + npc = 0x0222, + sx = 4, sy = 5, + dodge = 4, + offset = -2 + } + end, + + dodgeBicycleGirlLeft = function() + return dodgeH{ + npc = 0x0222, + sx = 4, sy = 4, + dodge = 5, + offset = -2, + dist = 0, + left = true + } + end, + + procureBicycle = function() + if (inventory.contains("bicycle")) then + if (not textbox.isActive()) then + return true + end + input.cancel() + elseif (textbox.handle()) then + player.interact("Up") + end + end, + + swapBicycle = function() + local bicycleIdx = inventory.indexOf("bicycle") + if (bicycleIdx < 3) then + return true + end + local main = memory.value("menu", "main") + if (main == 128) then + if (menu.getCol() ~= 5) then + menu.select(2, true) + else + local selection = memory.value("menu", "selection_mode") + if (selection == 0) then + if (menu.select(0, "accelerate", true, nil, true)) then + input.press("Select") + end + else + if (menu.select(bicycleIdx, "accelerate", true, nil, true)) then + input.press("Select") + end + end + end + else + menu.pause() + end + end, + + redbarCubone = function() + if (battle.isActive()) then + local forced + canProgress = true + if (pokemon.isOpponent("cubone")) then + local enemyMove, enemyTurns = combat.enemyAttack() + if (enemyTurns) then + local curr_hp, red_hp = pokemon.index(0, "hp"), redHP() + local clubDmg = enemyMove.damage + local afterHit = curr_hp - clubDmg + if (afterHit > -2 and afterHit < red_hp) then + forced = "thunderbolt" + else + afterHit = afterHit - clubDmg + if (afterHit > -4 and afterHit < red_hp) then + forced = "thunderbolt" + end + end + if (forced and initialize()) then + bridge.chat("Using Thunderbolt to attempt to redbar off Cubone") + end + end + end + battle.automate(forced) + elseif (canProgress) then + return true + else + battle.automate() + end + end, + + shopPokeDoll = function() + return shop.transaction{ + direction = "Down", + buy = {{name="pokedoll", index=0}} + } + end, + + shopBuffs = function() + local minSpecial = 45 + if (yolo) then + minSpecial = minSpecial - 1 + end + if (nidoAttack >= 54 and nidoSpecial >= minSpecial) then + riskGiovanni = true + print("Giovanni skip strats!") + end + + local xspecAmt = 4 + if (riskGiovanni) then + xspecAmt = xspecAmt + 1 + elseif (nidoSpecial < 46) then + xspecAmt = xspecAmt - 1 + end + return shop.transaction{ + direction = "Up", + buy = {{name="x_accuracy", index=0, amount=10}, {name="x_speed", index=5, amount=4}, {name="x_special", index=6, amount=xspecAmt}} + } + end, + + shopVending = function() + return shop.vend{ + direction = "Up", + buy = {{name="fresh_water", index=0}, {name="soda_pop", index=1}} + } + end, + + giveWater = function() + if (not inventory.contains("fresh_water", "soda_pop")) then + return true + end + if (textbox.isActive()) then + input.cancel("A") + else + local cx, cy = memory.raw(0x0223) - 3, memory.raw(0x0222) - 3 + local px, py = player.position() + if (utils.dist(cx, cy, px, py) == 1) then + player.interact(walk.dir(px, py, cx, cy)) + else + walk.step(cx, cy) + end + end + end, + + shopExtraWater = function() + return shop.vend{ + direction = "Up", + buy = {{name="fresh_water", index=0}} + } + end, + + shopTM07 = function() + return shop.transaction{ + direction = "Up", + buy = {{name="horn_drill", index=3}} + } + end, + + shopRepels = function() + return shop.transaction{ + direction = "Up", + buy = {{name="super_repel", index=3, amount=9}} + } + end, + + swapRepels = function() + local repelIdx = inventory.indexOf("super_repel") + if (repelIdx < 3) then + return true + end + local main = memory.value("menu", "main") + if (main == 128) then + if (menu.getCol() ~= 5) then + menu.select(2, true) + else + local selection = memory.value("menu", "selection_mode") + if (selection == 0) then + if (menu.select(1, "accelerate", true, nil, true)) then + input.press("Select") + end + else + if (menu.select(repelIdx, "accelerate", true, nil, true)) then + input.press("Select") + end + end + end + else + menu.pause() + end + end, + +-- 8: FLY + + lavenderRival = function() + if (battle.isActive()) then + canProgress = true + local forced + if (nidoSpecial > 44) then -- RISK + local __, enemyTurns = combat.enemyAttack() + if (enemyTurns and enemyTurns < 2 and pokemon.isOpponent("pidgeotto", "gyarados")) then + battle.automate() + return false + end + end + if (pokemon.isOpponent("gyarados") or prepare("x_accuracy")) then + battle.automate() + end + elseif (canProgress) then + return true + else + input.cancel() + end + end, + + pokeDoll = function() + if (battle.isActive()) then + canProgress = true + inventory.use("pokedoll", nil, true) + elseif (canProgress) then + return true + else + input.cancel() + end + end, + + digFight = function() + if (battle.isActive()) then + canProgress = true + local backupIndex = pokemon.indexOf("paras", "squirtle") + if (pokemon.isDeployed("nidoking")) then + if (pokemon.info("nidoking", "hp") == 0) then + if (utils.onPokemonSelect(memory.value("battle", "menu"))) then + menu.select(backupIndex, true) + else + input.press("A") + end + else + battle.automate() + end + elseif (pokemon.info("nidoking", "hp") == 0 and pokemon.index(backupIndex, "hp") == 0 and pokemon.isDeployed("paras", "squirtle")) then + return resetDeath() + else + battle.fight("dig") + end + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + thunderboltFirst = function() + local forced + if (pokemon.isOpponent("zubat")) then + canProgress = true + forced = "thunderbolt" + elseif (canProgress) then + return true + end + battle.automate(forced) + end, + +-- 8: POKÉFLUTE + + playPokeflute = function() + if (battle.isActive()) then + return true + end + if (memory.value("battle", "menu") == 95) then + input.press("A") + elseif (menu.pause()) then + inventory.use("pokeflute") + end + end, + + drivebyRareCandy = function() + if (textbox.isActive()) then + canProgress = true + input.cancel() + elseif (canProgress) then + return true + else + local px, py = player.position() + if (py < 13) then + tries = 0 + return + end + if (py == 13 and tries % 2 == 0) then + input.press("A", 2) + else + input.press("Up") + tries = 0 + end + tries = tries + 1 + end + end, + + safariCarbos = function() + if (initialize()) then + setYolo("safari_carbos") + end + local minSpeed = 50 + if (yolo) then + minSpeed = minSpeed - 1 + end + if (nidoSpeed >= minSpeed) then + return true + end + if (inventory.contains("carbos")) then + if (walk.step(20, 20)) then + return true + end + else + local px, py = player.position() + if (px < 21) then + walk.step(21, py) + elseif (px == 21 and py == 13) then + player.interact("Left") + else + walk.step(21, 13) + end + end + end, + + centerSkipFullRestore = function() + if (initialize()) then + if (yolo or inventory.contains("full_restore")) then + return true + end + end + local px, py = player.position() + if (px < 21) then + px = 21 + elseif (py < 9) then + py = 9 + else + return strategyFunctions.interact({dir="Down"}) + end + walk.step(px, py) + end, + + silphElevator = function() + if (textbox.isActive()) then + canProgress = true + menu.select(9, false, true) + else + if (canProgress) then + return true + end + player.interact("Up") + end + end, + + fightSilphMachoke = function() + if (battle.isActive()) then + canProgress = true + if (nidoSpecial > 44) then + return prepare("x_accuracy") + end + battle.automate("thrash") + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + silphCarbos = function() + if (nidoSpeed > 50) then + return true + end + return strategyFunctions.interact({dir="Left"}) + end, + + silphRival = function() + if (battle.isActive()) then + canProgress = true + if (prepare("x_accuracy", "x_speed")) then + local forced + if (pokemon.isOpponent("pidgeot")) then + if (riskGiovanni or nidoSpecial < 45 or pokemon.info("nidoking", "hp") > 85) then + forced = "thunderbolt" + end + elseif (pokemon.isOpponent("alakazam", "growlithe")) then + forced = "earthquake" + end + battle.automate(forced) + end + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + fightSilphGiovanni = function() + if (battle.isActive()) then + canProgress = true + local forced + if (pokemon.isOpponent("nidorino")) then + if (battle.pp("horn_drill") > 2) then + forced = "horn_drill" + else + forced = "earthquake" + end + elseif (pokemon.isOpponent("rhyhorn")) then + forced = "ice_beam" + elseif (pokemon.isOpponent("kangaskhan")) then + forced = "horn_drill" + end + battle.automate(forced) + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + +-- 9: SILPH CO. + + fightHypno = function() + if (battle.isActive()) then + local forced + if (pokemon.isOpponent("hypno")) then + if (pokemon.info("nidoking", "hp") > combat.healthFor("KogaWeezing") * 0.9) then + if (combat.isDisabled(85)) then + forced = "ice_beam" + else + forced = "thunderbolt" + end + end + end + battle.automate(forced) + canProgress = true + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + fightKoga = function() + if (battle.isActive()) then + local forced + if (pokemon.isOpponent("weezing")) then + if (opponentDamaged(2)) then + inventory.use("pokeflute", nil, true) + return false + end + forced = "thunderbolt" + strategies.canDie = true + end + battle.fight(forced) + canProgress = true + elseif (canProgress) then + deepRun = true + return true + else + textbox.handle() + end + end, + +-- 10: KOGA + + dodgeGirl = function() + local gx, gy = memory.raw(0x0223) - 5, memory.raw(0x0222) + local px, py = player.position() + if (py > gy) then + if (px > 3) then + px = 3 + else + return true + end + elseif (gy - py ~= 1 or px ~= gx) then + py = py + 1 + elseif (px == 3) then + px = 2 + else + px = 3 + end + walk.step(px, py) + end, + + cinnabarCarbos = function() + local px, py = player.position() + if (px == 21) then + return true + end + local minSpeed = 51 + if (yolo) then + minSpeed = minSpeed - 1 + end + if (nidoSpeed > minSpeed) then -- TODO >= + walk.step(21, 20) + else + if (py == 20) then + py = 21 + elseif (px == 17 and not inventory.contains("carbos")) then + player.interact("Right") + return false + else + px = 21 + end + walk.step(px, py) + end + end, + + fightErika = function() + if (battle.isActive()) then + canProgress = true + local forced + local curr_hp, red_hp = pokemon.index(0, "hp"), redHP() + local razorDamage = 34 + if (curr_hp > razorDamage and curr_hp - razorDamage < red_hp) then + if (opponentDamaged()) then + forced = "thunderbolt" + elseif (nidoSpecial < 45) then + forced = "ice_beam" + else + forced = "thunderbolt" + end + elseif (riskGiovanni) then + forced = "ice_beam" + end + battle.automate(forced) + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + +-- 11: ERIKA + + waitToReceive = function() + local main = memory.value("menu", "main") + if (main == 128) then + if (canProgress) then + return true + end + elseif (main == 32 or main == 123) then + canProgress = true + input.cancel() + else + input.press("Start", 2) + end + end, + +-- 14: SABRINA + + earthquakeElixer = function(data) + if (battle.pp("earthquake") >= data.min) then + if (closeMenuFor(data)) then + return true + end + return false + end + if (initialize()) then + if (areaName) then + print("EQ Elixer: "..areaName) + end + end + return useItem({item="elixer", poke="nidoking", chain=data.chain, close=data.close}) + end, + + checkGiovanni = function() + if (initialize()) then + local earthquakePP = battle.pp("earthquake") + if (earthquakePP > 1) then + if (riskGiovanni and earthquakePP > 2 and battle.pp("horn_drill") > 4 and (yolo or pokemon.info("nidoking", "hp") > combat.healthFor("GiovanniRhyhorn") * 0.925)) then -- RISK + bridge.chat("Using risky strats on Giovanni to skip the extra Max Ether...") + else + riskGiovanni = false + end + return true + end + local message = "Ran out of Earthquake PP :(" + if (not yolo) then + message = message.." Time for safe strats." + end + bridge.chat(message) + riskGiovanni = false + end + return strategyFunctions.potion({hp=50, yolo=10}) + end, + + fightGiovanniMachoke = function(data) + return prepare("x_special") + end, + + fightGiovanni = function() + if (battle.isActive()) then + canProgress = true + if (riskGiovanni and not prepare("x_special")) then + return false + end + local forced + if (pokemon.isOpponent("rhydon")) then + forced = "ice_beam" + end + battle.automate(forced) + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + +-- 15: GIOVANNI + + viridianRival = function() + if (battle.isActive()) then + if (not canProgress) then + if (nidoSpecial < 45 or pokemon.index(0, "speed") < 134) then + tempDir = "x_special" + else + print("Skip X Special strats!") + end + canProgress = true + end + if (prepare("x_accuracy", tempDir)) then + local forced + if (pokemon.isOpponent("pidgeot")) then + forced = "thunderbolt" + elseif (riskGiovanni) then + if (pokemon.isOpponent("rhyhorn") or opponentDamaged()) then + forced = "ice_beam" + elseif (pokemon.isOpponent("gyarados")) then + forced = "thunderbolt" + elseif (pokemon.isOpponent("growlithe", "alakazam")) then + forced = "earthquake" + end + end + battle.automate(forced) + end + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + ether = function(data) + local main = memory.value("menu", "main") + data.item = tempDir + if (tempDir and completedMenuFor(data)) then + if (closeMenuFor(data)) then + return true + end + else + if (not tempDir) then + if (data.max) then + -- TODO don't skip center if not in redbar + maxEtherSkip = nidoAttack > 53 and battle.pp("earthquake") > 0 and battle.pp("horn_drill") > 3 + if (maxEtherSkip) then + return true + end + bridge.chat("Grabbing the Max Ether to skip the Elite 4 Center") + end + tempDir = inventory.contains("ether", "max_ether") + if (not tempDir) then + return true + end + tries = inventory.count(tempDir) + end + if (memory.value("menu", "main") == 144 and menu.getCol() == 5) then + if (memory.value("battle", "menu") ~= 95) then + menu.select(pokemon.battleMove("horn_drill"), true) + else + input.cancel() + end + elseif (menu.pause()) then + inventory.use(tempDir, "nidoking") + end + end + end, + + pickMaxEther = function() + if (not canProgress) then + if (maxEtherSkip) then + return true + end + if (memory.value("player", "moving") == 0) then + if (player.isFacing("Right")) then + canProgress = true + end + tries = not tries + if (tries) then + input.press("Right", 1) + end + end + return false + end + if (inventory.contains("max_ether")) then + return true + end + player.interact("Right") + end, + + push = function(data) + local pos + if (data.dir == "Up" or data.dir == "Down") then + pos = data.y + else + pos = data.x + end + local newP = memory.raw(pos) + if (tries == 0) then + tries = {start=newP} + elseif (tries.start ~= newP) then + return true + end + input.press(data.dir, 0) + end, + + healBeforeLorelei = function() + if (initialize()) then + local canPotion + if (inventory.contains("potion") and hasHealthFor("LoreleiDewgong", 20)) then + canPotion = true + elseif (inventory.contains("super_potion") and hasHealthFor("LoreleiDewgong", 50)) then + canPotion = true + end + if (not canPotion) then + return true + end + bridge.chat("Healing before Lorelei to skip the Elite 4 Center...") + end + return strategyFunctions.potion({hp=combat.healthFor("LoreleiDewgong")}) + end, + + depositPokemon = function() + local toSize + if (hasHealthFor("LoreleiDewgong")) then + toSize = 1 + else + toSize = 2 + end + if (memory.value("player", "party_size") == toSize) then + if (menu.close()) then + return true + end + else + if (not textbox.isActive()) then + player.interact("Up") + else + local pc = memory.value("menu", "size") + if (memory.value("battle", "menu") ~= 95 and (pc == 2 or pc == 4)) then + if (menu.getCol() == 10) then + input.press("A") + else + menu.select(1) + end + else + input.press("A") + end + end + end + end, + + centerSkip = function() + setYolo("e4center") + local message = "Skipping the Center and attempting to redbar " + if (hasHealthFor("LoreleiDewgong")) then + message = message.."off Lorelei..." + else + message = message.."the Elite 4!" + end + bridge.chat(message) + return true + end, + + lorelei = function() + if (battle.isActive()) then + canProgress = true + if (not pokemon.isDeployed("nidoking")) then + local battleMenu = memory.value("battle", "menu") + if (utils.onPokemonSelect(battleMenu)) then + menu.select(0, true) + elseif (battleMenu == 95 and menu.getCol() == 1) then + input.press("A") + else + battle.automate() + end + return false + end + if (pokemon.isOpponent("dewgong")) then + local sacrifice = pokemon.inParty("pidgey", "spearow", "squirtle", "paras", "oddish") + if (sacrifice and pokemon.info(sacrifice, "hp") > 0) then + battle.swap(sacrifice) + return false + end + end + if (prepare("x_accuracy")) then + battle.automate() + end + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + +-- 16: LORELEI + + bruno = function() + if (battle.isActive()) then + canProgress = true + local forced + if (pokemon.isOpponent("onix")) then + forced = "ice_beam" + -- local curr_hp, red_hp = pokemon.info("nidoking", "hp"), redHP() + -- if (curr_hp > red_hp) then + -- local enemyMove, enemyTurns = combat.enemyAttack() + -- if (enemyTurns and enemyTurns > 1) then + -- local rockDmg = enemyMove.damage + -- if (curr_hp - rockDmg <= red_hp) then + -- forced = "thunderbolt" + -- end + -- end + -- end + end + if (prepare("x_accuracy")) then + battle.automate(forced) + end + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + agatha = function() + if (battle.isActive()) then + canProgress = true + if (combat.isSleeping()) then + inventory.use("pokeflute", nil, true) + return false + end + if (pokemon.isOpponent("gengar")) then + local currentHP = pokemon.info("nidoking", "hp") + if (not yolo and currentHP <= 56 and not isPrepared("x_accuracy", "x_speed")) then + local toPotion = inventory.contains("full_restore", "super_potion") + if (toPotion) then + inventory.use(toPotion, nil, true) + return false + end + end + if (not prepare("x_accuracy", "x_speed")) then + return false + end + end + battle.automate() + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + prepareForLance = function() + local enableFull + if (hasHealthFor("LanceGyarados", 100)) then + enableFull = inventory.count("super_potion") < 2 + elseif (hasHealthFor("LanceGyarados", 50)) then + enableFull = not inventory.contains("super_potion") + else + enableFull = true + end + local min_recovery = combat.healthFor("LanceGyarados") + return strategyFunctions.potion({hp=min_recovery, full=enableFull, chain=true}) + end, + + lance = function() + if (tries == 0) then + tries = {{"x_special", inventory.count("x_special")}, {"x_speed", inventory.count("x_speed"), 89}} + end + return prepare() + end, + + prepareForBlue = function() + if (initialize()) then + setYolo("blue") + end + local skyDmg = combat.healthFor("BlueSky") + local wingDmg = combat.healthFor("BluePidgeot") + return strategyFunctions.potion({hp=skyDmg-50, yolo=wingDmg, full=true}) + end, + + blue = function() + if (battle.isActive()) then + canProgress = true + if (memory.value("battle", "turns") > 0 and not isPrepared("x_accuracy", "x_speed")) then + local toPotion = inventory.contains("full_restore", "super_potion") + if (battle.potionsForHit(toPotion)) then + inventory.use(toPotion, nil, true) + return false + end + end + if (not tempDir) then + if (nidoSpecial > 45 and pokemon.index(0, "speed") > 52 and inventory.contains("x_special")) then + tempDir = "x_special" + else + tempDir = "x_speed" + end + print(tempDir.." strats") + tempDir = "x_speed" -- TODO find min stats, remove override + end + if (prepare("x_accuracy", "x_speed")) then + local forced = "horn_drill" + if (pokemon.isOpponent("alakazam")) then + if (tempDir == "x_speed") then + forced = "earthquake" + end + elseif (pokemon.isOpponent("rhydon")) then + if (tempDir == "x_special") then + forced = "ice_beam" + end + end + battle.automate(forced) + end + elseif (canProgress) then + return true + else + textbox.handle() + end + end, + + champion = function() + if (canProgress) then + if (tries > 1500) then + return hardReset("Beat the game in "..canProgress.." !") + end + if (tries == 0) then + bridge.tweet("Beat Pokemon Red in "..canProgress.."!") + if (strategies.seed) then + print(memory.value("game", "frames").." frames, with seed "..strategies.seed) + print("Please save this seed number to share, if you would like proof of your run!") + end + end + tries = tries + 1 + elseif (memory.value("menu", "shop_current") == 252) then + strategyFunctions.split({finished=true}) + canProgress = paint.elapsedTime() + else + input.cancel() + end + end +} + +function strategies.execute(data) + if (strategyFunctions[data.s](data)) then + tries = 0 + canProgress = false + initialized = false + tempDir = nil + if (resetting) then + return nil + end + return true + end + return false +end + +function strategies.init(midGame) + if (midGame) then + combat.factorPP(true) + end +end + +function strategies.softReset() + canProgress = false + initialized = false + maxEtherSkip = false + tempDir = nil + strategies.canDie = nil + strategies.moonEncounters = nil + tries = 0 + deepRun = false + resetting = nil + yolo = false +end + +return strategies diff --git a/data/movelist.lua b/data/movelist.lua new file mode 100644 index 0000000..5e9f604 --- /dev/null +++ b/data/movelist.lua @@ -0,0 +1,1520 @@ +local movelist = {} +-- http://bulbapedia.bulbagarden.net/wiki/List_of_moves#List_of_moves + +local moves = { + { + name = 'Pound', + id = 1, + move_type = 'normal', + special = false, + power = 40, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Karate-Chop', + id = 2, + move_type = 'fighting', + special = false, + power = 50, + max_pp = 25, + accuracy = 100, + }, + { + name = 'Double-Slap', + id = 3, + move_type = 'normal', + special = false, + power = 15, + max_pp = 10, + accuracy = 85, + }, + { + name = 'Comet-Punch', + id = 4, + move_type = 'normal', + special = false, + power = 18, + max_pp = 15, + accuracy = 85, + }, + { + name = 'Mega-Punch', + id = 5, + move_type = 'normal', + special = false, + power = 80, + max_pp = 20, + accuracy = 85, + }, + { + name = 'Pay-Day', + id = 6, + move_type = 'normal', + special = false, + power = 40, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Fire-Punch', + id = 7, + move_type = 'fire', + special = false, + power = 75, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Ice-Punch', + id = 8, + move_type = 'ice', + special = false, + power = 75, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Thunder-Punch', + id = 9, + move_type = 'electric', + special = false, + power = 75, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Scratch', + id = 10, + move_type = 'normal', + special = false, + power = 40, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Vice-Grip', + id = 11, + move_type = 'normal', + special = false, + power = 55, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Guillotine', + id = 12, + move_type = 'normal', + special = false, + power = 0, + max_pp = 5, + accuracy = 70, + }, + { + name = 'Razor-Wind', + id = 13, + move_type = 'normal', + special = true, + power = 80, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Swords-Dance', + id = 14, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + effects = {stat="att",diff=2}, + }, + { + name = 'Cut', + id = 15, + move_type = 'normal', + special = false, + power = 50, + max_pp = 30, + accuracy = 95, + }, + { + name = 'Gust', + id = 16, + move_type = 'flying', + special = true, + power = 40, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Wing-Attack', + id = 17, + move_type = 'flying', + special = false, + power = 35, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Whirlwind', + id = 18, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 70, + }, + { + name = 'Fly', + id = 19, + move_type = 'flying', + special = false, + power = 70, + max_pp = 15, + accuracy = 95, + }, + { + name = 'Bind', + id = 20, + move_type = 'normal', + special = false, + power = 15, + max_pp = 20, + accuracy = 85, + }, + { + name = 'Slam', + id = 21, + move_type = 'normal', + special = false, + power = 80, + max_pp = 20, + accuracy = 75, + }, + { + name = 'Vine-Whip', + id = 22, + move_type = 'grass', + special = false, + power = 35, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Stomp', + id = 23, + move_type = 'normal', + special = false, + power = 65, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Double-Kick', + id = 24, + move_type = 'fighting', + special = false, + power = 30, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Mega-Kick', + id = 25, + move_type = 'normal', + special = false, + power = 120, + max_pp = 5, + accuracy = 75, + }, + { + name = 'Jump-Kick', + id = 26, + move_type = 'fighting', + special = false, + power = 70, + max_pp = 25, + accuracy = 95, + }, + { + name = 'Rolling-Kick', + id = 27, + move_type = 'fighting', + special = false, + power = 60, + max_pp = 15, + accuracy = 85, + }, + { + name = 'Sand-Attack', + id = 28, + move_type = 'ground', + special = false, + power = 0, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Headbutt', + id = 29, + move_type = 'normal', + special = false, + power = 70, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Horn-Attack', + id = 30, + move_type = 'normal', + special = false, + power = 65, + max_pp = 25, + accuracy = 100, + }, + { + name = 'Fury-Attack', + id = 31, + move_type = 'normal', + special = false, + outspeed = true, + power = 15, + max_pp = 20, + accuracy = 85, + }, + { + name = 'Horn-Drill', + id = 32, + move_type = 'normal', + special = false, + power = 9001, + max_pp = 5, + accuracy = 0, + }, + { + name = 'Tackle', + id = 33, + move_type = 'normal', + special = false, + power = 35, + max_pp = 35, + accuracy = 95, + }, + { + name = 'Body-Slam', + id = 34, + move_type = 'normal', + special = false, + power = 85, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Wrap', + id = 35, + move_type = 'normal', + special = false, + outspeed = "turns", + power = 15, + max_pp = 20, + accuracy = 90, + }, + { + name = 'Take-Down', + id = 36, + move_type = 'normal', + special = false, + power = 90, + max_pp = 20, + accuracy = 85, + }, + { + name = 'Thrash', + id = 37, + move_type = 'normal', + special = false, + power = 90, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Double-Edge', + id = 38, + move_type = 'normal', + special = false, + power = 100, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Tail-Whip', + id = 39, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 100, + effects = {stat="def",diff=-1}, + }, + { + name = 'Poison-Sting', + id = 40, + move_type = 'poison', + special = false, + fast = true, + power = 15, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Twineedle', + id = 41, + move_type = 'bug', + special = false, + power = 25, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Pin-Missile', + id = 42, + move_type = 'bug', + special = false, + power = 14, + max_pp = 20, + accuracy = 95, + }, + { + name = 'Leer', + id = 43, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 100, + effects = {stat="def",diff=-1}, + }, + { + name = 'Bite', + id = 44, + move_type = 'normal', + special = false, + power = 60, + max_pp = 25, + accuracy = 100, + }, + { + name = 'Growl', + id = 45, + move_type = 'normal', + special = false, + power = 0, + max_pp = 40, + accuracy = 100, + effects = {stat="att",diff=-1}, + }, + { + name = 'Roar', + id = 46, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 70, + }, + { + name = 'Sing', + id = 47, + move_type = 'normal', + special = false, + power = 0, + max_pp = 15, + accuracy = 55, + }, + { + name = 'Supersonic', + id = 48, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 55, + }, + { + name = 'Sonic-Boom', + id = 49, + move_type = 'normal', + special = true, + power = 20, + fixed = 20, + max_pp = 20, + accuracy = 90, + }, + { + name = 'Disable', + id = 50, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Acid', + id = 51, + move_type = 'poison', + special = true, + power = 40, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Ember', + id = 52, + move_type = 'fire', + special = true, + power = 40, + max_pp = 25, + accuracy = 100, + }, + { + name = 'Flamethrower', + id = 53, + move_type = 'fire', + special = true, + power = 95, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Mist', + id = 54, + move_type = 'ice', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + }, + { + name = 'Water-Gun', + id = 55, + move_type = 'water', + special = true, + power = 40, + max_pp = 25, + accuracy = 100, + }, + { + name = 'Hydro-Pump', + id = 56, + move_type = 'water', + special = true, + power = 120, + max_pp = 5, + accuracy = 80, + }, + { + name = 'Surf', + id = 57, + move_type = 'water', + special = true, + power = 95, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Ice-Beam', + id = 58, + move_type = 'ice', + special = true, + power = 95, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Blizzard', + id = 59, + move_type = 'ice', + special = true, + power = 120, + max_pp = 5, + accuracy = 70, + }, + { + name = 'Psybeam', + id = 60, + move_type = 'psychic', + special = true, + power = 65, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Bubble-Beam', + id = 61, + move_type = 'water', + special = true, + fast = true, + power = 65, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Aurora-Beam', + id = 62, + move_type = 'ice', + special = true, + power = 65, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Hyper-Beam', + id = 63, + move_type = 'normal', + special = true, + power = 150, + max_pp = 5, + accuracy = 90, + }, + { + name = 'Peck', + id = 64, + move_type = 'flying', + special = false, + power = 35, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Drill-Peck', + id = 65, + move_type = 'flying', + special = false, + power = 80, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Submission', + id = 66, + move_type = 'fighting', + special = false, + power = 80, + max_pp = 25, + accuracy = 80, + }, + { + name = 'Low-Kick', + id = 67, + move_type = 'fighting', + special = false, + power = 50, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Counter', + id = 68, + move_type = 'fighting', + special = false, + power = 0, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Seismic-Toss', + id = 69, + move_type = 'fighting', + special = false, + power = 0, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Strength', + id = 70, + move_type = 'normal', + special = false, + power = 80, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Absorb', + id = 71, + move_type = 'grass', + special = true, + power = 20, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Mega-Drain', + id = 72, + move_type = 'grass', + special = true, + power = 40, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Leech-Seed', + id = 73, + move_type = 'grass', + special = false, + power = 0, + max_pp = 10, + accuracy = 90, + }, + { + name = 'Growth', + id = 74, + move_type = 'normal', + special = false, + power = 0, + max_pp = 40, + accuracy = 0, + effects = {stat="spec",diff=1}, -- and att + }, + { + name = 'Razor-Leaf', + id = 75, + move_type = 'grass', + special = false, + power = 55, + max_pp = 25, + accuracy = 95, + }, + { + name = 'Solar-Beam', + id = 76, + move_type = 'grass', + special = true, + power = 120, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Poison-Powder', + id = 77, + move_type = 'poison', + special = false, + power = 0, + max_pp = 35, + accuracy = 75, + }, + { + name = 'Stun-Spore', + id = 78, + move_type = 'grass', + special = false, + power = 0, + max_pp = 30, + accuracy = 75, + }, + { + name = 'Sleep-Powder', + id = 79, + move_type = 'grass', + special = false, + power = 0, + max_pp = 15, + accuracy = 75, + }, + { + name = 'Petal-Dance', + id = 80, + move_type = 'grass', + special = true, + power = 70, + max_pp = 20, + accuracy = 100, + }, + { + name = 'String-Shot', + id = 81, + move_type = 'bug', + special = false, + power = 0, + max_pp = 40, + accuracy = 95, + effects = {stat="speed",diff=-1}, + }, + { + name = 'Dragon-Rage', + id = 82, + move_type = 'dragon', + special = true, + power = 40, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Fire-Spin', + id = 83, + move_type = 'fire', + special = true, + power = 15, + max_pp = 15, + accuracy = 85, + }, + { + name = 'Thunder-Shock', + id = 84, + move_type = 'electric', + special = true, + power = 40, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Thunderbolt', + id = 85, + move_type = 'electric', + special = true, + power = 95, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Thunder-Wave', + id = 86, + move_type = 'electric', + special = false, + power = 0, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Thunder', + id = 87, + move_type = 'electric', + special = true, + power = 120, + max_pp = 10, + accuracy = 70, + }, + { + name = 'Rock-Throw', + id = 88, + move_type = 'rock', + special = false, + power = 50, + max_pp = 15, + accuracy = 90, + }, + { + name = 'Earthquake', + id = 89, + move_type = 'ground', + special = false, + power = 100, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Fissure', + id = 90, + move_type = 'ground', + special = false, + power = 0, -- ? + max_pp = 5, + accuracy = 0, + }, + { + name = 'Dig', + id = 91, + move_type = 'ground', + special = false, + power = 100, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Toxic', + id = 92, + move_type = 'poison', + special = false, + power = 0, + max_pp = 10, + accuracy = 90, + }, + { + name = 'Confusion', + id = 93, + move_type = 'psychic', + special = true, + power = 50, + max_pp = 25, + accuracy = 100, + }, + { + name = 'Psychic', + id = 94, + move_type = 'psychic', + special = true, + power = 90, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Hypnosis', + id = 95, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 20, + accuracy = 60, + }, + { + name = 'Meditate', + id = 96, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 40, + accuracy = 0, + effects = {stat="att",diff=1}, + }, + { + name = 'Agility', + id = 97, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + effects = {stat="speed",diff=2}, + }, + { + name = 'Quick-Attack', + id = 98, + move_type = 'normal', + special = false, + outspeed = true, + power = 40, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Rage', + id = 99, + move_type = 'normal', + special = false, + power = 20, + max_pp = 20, + accuracy = 100, + effects = {stat="att",diff=1}, + }, + { + name = 'Teleport', + id = 100, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 20, + accuracy = 0, + }, + { + name = 'Night-Shade', + id = 101, + move_type = 'ghost', + special = true, + power = 0, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Mimic', + id = 102, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Screech', + id = 103, + move_type = 'normal', + special = false, + power = 0, + max_pp = 40, + accuracy = 85, + effects = {stat="def",diff=-2}, + }, + { + name = 'Double-Team', + id = 104, + move_type = 'normal', + special = false, + power = 0, + max_pp = 15, + accuracy = 0, + }, + { + name = 'Recover', + id = 105, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 0, + }, + { + name = 'Harden', + id = 106, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + effects = {stat="def",diff=1}, + }, + { + name = 'Minimize', + id = 107, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 0, + }, + { + name = 'Smokescreen', + id = 108, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Confuse-Ray', + id = 109, + move_type = 'ghost', + special = false, + power = 0, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Withdraw', + id = 110, + move_type = 'water', + special = false, + power = 0, + max_pp = 40, + accuracy = 0, + effects = {stat="def",diff=1}, + }, + { + name = 'Defense-Curl', + id = 111, + move_type = 'normal', + special = false, + power = 0, + max_pp = 40, + accuracy = 0, + effects = {stat="def",diff=1}, + }, + { + name = 'Barrier', + id = 112, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + effects = {stat="def",diff=2}, + }, + { + name = 'Light-Screen', + id = 113, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + }, + { + name = 'Haze', + id = 114, + move_type = 'ice', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + }, + { + name = 'Reflect', + id = 115, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 20, + accuracy = 0, + }, + { + name = 'Focus-Energy', + id = 116, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + }, + { + name = 'Bide', + id = 117, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Metronome', + id = 118, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 0, + }, + { + name = 'Mirror-Move', + id = 119, + move_type = 'flying', + special = false, + power = 0, + max_pp = 20, + accuracy = 0, + }, + { + name = 'Self-Destruct', + id = 120, + move_type = 'normal', + special = false, + power = 260, + max_pp = 5, + accuracy = 100, + }, + { + name = 'Egg-Bomb', + id = 121, + move_type = 'normal', + special = false, + power = 100, + max_pp = 10, + accuracy = 75, + }, + { + name = 'Lick', + id = 122, + move_type = 'ghost', + special = false, + power = 20, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Smog', + id = 123, + move_type = 'poison', + special = true, + power = 20, + max_pp = 20, + accuracy = 70, + }, + { + name = 'Sludge', + id = 124, + move_type = 'poison', + special = true, + power = 65, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Bone-Club', + id = 125, + move_type = 'ground', + special = false, + power = 65, + max_pp = 20, + accuracy = 85, + }, + { + name = 'Fire-Blast', + id = 126, + move_type = 'fire', + special = true, + power = 120, + max_pp = 5, + accuracy = 85, + }, + { + name = 'Waterfall', + id = 127, + move_type = 'water', + special = false, + power = 80, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Clamp', + id = 128, + move_type = 'water', + special = false, + power = 35, + max_pp = 10, + accuracy = 85, + }, + { + name = 'Swift', + id = 129, + move_type = 'normal', + special = true, + power = 60, + max_pp = 20, + accuracy = 0, + }, + { + name = 'Skull-Bash', + id = 130, + move_type = 'normal', + special = false, + power = 100, + max_pp = 15, + accuracy = 100, + effects = {stat="def",diff=1}, + }, + { + name = 'Spike-Cannon', + id = 131, + move_type = 'normal', + special = false, + power = 20, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Constrict', + id = 132, + move_type = 'normal', + special = false, + power = 10, + max_pp = 35, + accuracy = 100, + }, + { + name = 'Amnesia', + id = 133, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 20, + accuracy = 0, + effects = {stat="spec",diff=2}, -- Special defense + }, + { + name = 'Kinesis', + id = 134, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 15, + accuracy = 80, + }, + { + name = 'Soft-Boiled', + id = 135, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 0, + }, + { + name = 'High-Jump-Kick', + id = 136, + move_type = 'fighting', + special = false, + power = 85, + max_pp = 20, + accuracy = 90, + }, + { + name = 'Glare', + id = 137, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Dream-Eater', + id = 138, + move_type = 'psychic', + special = true, + power = 0, -- 100 + max_pp = 15, + accuracy = 100, + }, + { + name = 'Poison-Gas', + id = 139, + move_type = 'poison', + special = false, + power = 0, + max_pp = 40, + accuracy = 90, + }, + { + name = 'Barrage', + id = 140, + move_type = 'normal', + special = false, + power = 15, + max_pp = 20, + accuracy = 85, + }, + { + name = 'Leech-Life', + id = 141, + move_type = 'bug', + special = false, + power = 20, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Lovely-Kiss', + id = 142, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 75, + }, + { + name = 'Sky-Attack', + id = 143, + move_type = 'flying', + special = false, + power = 140, + max_pp = 5, + accuracy = 90, + }, + { + name = 'Transform', + id = 144, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 0, + }, + { + name = 'Bubble', + id = 145, + move_type = 'water', + special = true, + power = 20, + max_pp = 30, + accuracy = 100, + }, + { + name = 'Dizzy-Punch', + id = 146, + move_type = 'normal', + special = false, + power = 70, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Spore', + id = 147, + move_type = 'grass', + special = false, + power = 0, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Flash', + id = 148, + move_type = 'normal', + special = false, + power = 0, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Psywave', + id = 149, + move_type = 'psychic', + special = true, + power = 0, + max_pp = 15, + accuracy = 100, + }, + { + name = 'Splash', + id = 150, + move_type = 'normal', + special = false, + power = 0, + max_pp = 40, + accuracy = 0, + }, + { + name = 'Acid-Armor', + id = 151, + move_type = 'poison', + special = false, + power = 0, + max_pp = 40, + accuracy = 0, + effects = {stat="def",diff=2}, + }, + { + name = 'Crabhammer', + id = 152, + move_type = 'water', + special = false, + power = 90, + max_pp = 10, + accuracy = 90, + }, + { + name = 'Explosion', + id = 153, + move_type = 'normal', + special = false, + power = 170, + max_pp = 5, + accuracy = 100, + }, + { + name = 'Fury-Swipes', + id = 154, + move_type = 'normal', + special = false, + power = 18, + max_pp = 15, + accuracy = 80, + }, + { + name = 'Bonemerang', + id = 155, + move_type = 'ground', + special = false, + power = 50, + max_pp = 10, + accuracy = 90, + }, + { + name = 'Rest', + id = 156, + move_type = 'psychic', + special = false, + power = 0, + max_pp = 10, + accuracy = 0, + }, + { + name = 'Rock-Slide', + id = 157, + move_type = 'rock', + special = false, + power = 75, + max_pp = 10, + accuracy = 90, + }, + { + name = 'Hyper-Fang', + id = 158, + move_type = 'normal', + special = false, + power = 80, + max_pp = 15, + accuracy = 90, + }, + { + name = 'Sharpen', + id = 159, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + effects = {stat="att",diff=1}, + }, + { + name = 'Conversion', + id = 160, + move_type = 'normal', + special = false, + power = 0, + max_pp = 30, + accuracy = 0, + }, + { + name = 'Tri-Attack', + id = 161, + move_type = 'normal', + special = true, + power = 80, + max_pp = 10, + accuracy = 100, + }, + { + name = 'Super-Fang', + id = 162, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 90, + }, + { + name = 'Slash', + id = 163, + move_type = 'normal', + special = false, + power = 70, + max_pp = 20, + accuracy = 100, + }, + { + name = 'Substitute', + id = 164, + move_type = 'normal', + special = false, + power = 0, + max_pp = 10, + accuracy = 0, + }, + { + name = 'Struggle', + id = 165, + move_type = 'normal', + special = false, + power = 50, + max_pp = 10, + accuracy = 100, + }, +} + +function movelist.get(id) + return moves[id] +end + +return movelist diff --git a/data/opponents.lua b/data/opponents.lua new file mode 100644 index 0000000..312c230 --- /dev/null +++ b/data/opponents.lua @@ -0,0 +1,175 @@ +local opponents = { + + KogaHypno = { + type1 = "psychic", + type2 = "psychic", + def = 58, + id = 129, + spec = 88, + hp = 107, + speed = 56, + level = 34, + att = 60, + moves = { + { + accuracy = 100, + name = "Confusion", + power = 50, + id = 93, + special = true, + max_pp = 25, + move_type = "psychic", + } + } + }, + + KogaWeezing = { + type1 = "poison", + type2 = "poison", + def = 115, + id = 143, + spec = 84, + hp = 115, + speed = 63, + level = 43, + att = 90, + moves = { + { + accuracy = 100, + name = "Self-Destruct", + power = 260, + id = 120, + special = false, + max_pp = 5, + move_type = "normal", + } + } + }, + + GiovanniRhyhorn = { + type1 = "ground", + type2 = "rock", + def = 97, + id = 18, + spec = 39, + hp = 134, + speed = 34, + level = 45, + att = 89, + moves = { + { + move_type = "normal", + accuracy = 100, + name = "Stomp", + power = 65, + id = 23, + special = false, + max_pp = 20, + damage = 21, + } + } + }, + + LoreleiDewgong = { + type1 = "water", + type2 = "ice", + def = 100, + id = 120, + spec = 116, + hp = 169, + speed = 89, + level = 54, + att = 90, + moves = { + { + accuracy = 100, + name = "Aurora-Beam", + power = 65, + id = 62, + special = true, + max_pp = 20, + move_type = "ice", + } + }, + boost = { + stat = "spec", + mp = 2 / 3 + } + }, + + LanceGyarados = { + type1 = "water", + type2 = "flying", + def = 105, + id = 22, + spec = 130, + hp = 187, + speed = 108, + level = 58, + att = 160, + moves = { + { + accuracy = 80, + name = "Hydro-Pump", + power = 120, + id = 56, + special = true, + max_pp = 5, + move_type = "water", + } + }, + boost = { + stat = "spec", + mp = 1.5 + } + }, + + BluePidgeot = { + type1 = "normal", + type2 = "flying", + def = 106, + id = 151, + spec = 100, + hp = 182, + speed = 125, + level = 61, + att = 113, + moves = { + { + accuracy = 100, + name = "Wing-Attack", + power = 35, + id = 17, + special = false, + max_pp = 35, + move_type = "flying", + } + } + }, + + BlueSky = { + type1 = "normal", + type2 = "flying", + def = 106, + id = 151, + spec = 100, + hp = 182, + speed = 125, + level = 61, + att = 113, + moves = { + { + accuracy = 90, + name = "Sky-Attack", + power = 140, + id = 143, + special = false, + max_pp = 5, + move_type = "flying", + } + } + }, + +} + +return opponents \ No newline at end of file diff --git a/data/paths.lua b/data/paths.lua new file mode 100644 index 0000000..3ebfb70 --- /dev/null +++ b/data/paths.lua @@ -0,0 +1,454 @@ +local paths = { + -- Red's room + {38, {3,6}, {5,6}, {5,1}, {7,1}}, + -- Red's house + {39, {7,1}, {7,6}, {3,6}, {3,8}}, + -- Into the Wild + {0, {5,6}, {10,6}, {10,1}}, + -- Choose your character! + {40, {5,3}, {s="a",a="Pallet Rival"}, {5,4}, {7,4}, {s="squirtleIChooseYou"}, {5,4}, {5,6}, {s="fightBulbasaur"}, {s="split"}, {5,12}}, + +-- 1: RIVAL 1 + + -- Let's try this escape again + {0, {12,12}, {s="a",a="Pallet Town"}, {c="viridianExp"}, {c="encounters",limit=4}, {9,12}, {9,2}, {10,2}, {10,-1}}, + -- First encounters + {12, {10,35}, {10,30}, {8,30}, {8,24}, {12,24}, {12,20}, {9,20}, {9,14}, {14,14}, {s="dodgePalletBoy"}, {14,2}, {11,2}, {11,-1}}, + -- To the Mart + {1, {21,35}, {21,30}, {19,30}, {19,20}, {29,20}, {29,19}}, + -- Viridian Mart + {42, {2,5}, {3,5}, {3,8}}, + -- Backtracking + {1, {29,20}, {c="encounters",limit=5}, {29,21}, {26,21}, {26,30}, {20,30}, {20,36}}, + -- Parkour + {12, {10, 0}, {10,3}, {8,3}, {8,18}, {9,18}, {9,22}, {12,22}, {12,24}, {10,24}, {10,36}}, + -- To Oak's lab + {0, {10,0}, {10,7}, {9,7}, {9,12}, {12,12}, {12,11}}, + -- Parcel delivery + {40, {5,11}, {5,3}, {4,3}, {4,1}, {5,1}, {s="interact",dir="Down"}, {4,1}, {4,12}}, + -- Leaving home + {0, {12,12}, {c="viridianBackupExp"}, {9,12}, {9,2}, {10,2}, {10,-1}}, + -- The grass again!? + {12, {10,35}, {10,30}, {8,30}, {8,24}, {12,24}, {12,20}, {9,20}, {9,14}, {14,14}, {s="dodgePalletBoy"}, {14,2}, {11,2}, {11,-1}}, + -- Back to the Mart + {1, {21,35}, {21,30}, {19,30}, {19,20}, {29,20}, {29,19}}, + -- Viridian Mart redux + {42, {3,7}, {3,5}, {2,5}, {s="viridianBuyPokeballs"}, {3,5}, {3,8}}, + -- Sidequest + {1, {29,20}, {15,20}, {15,17}, {-1, 17}}, + -- Nidoran + {33, {39, 9}, {s="a",a="Nidoran grass"}, {c="nidoranBackupExp"}, {c="encounters",limit=7,extra="spearow"}, {35, 9}, {35,12}, {33,12}, {c="catchNidoran"}, {s="catchNidoran"}, {33,12}, {s="split"}, {37,12}, {37,9}, {40,9}}, + +-- 2: NIDORAN + + -- Out of Viridian City + {1, {0,17}, {16,17}, {16,16}, {18,16}, {18,6}, {s="dodgeViridianOldMan"}, {17, 0}, {17, -1}}, + -- To the Forest + {13, {7,71}, {7,57}, {4,57}, {4,52}, {10,52}, {10,44}, {3,44}, {3,43}}, + -- Forest entrance + {50, {4,7}, {s="a",a="Viridian Forest"}, {4,1}, {5,1}, {5,0}}, + -- Viridian Forest + {51, {17,47}, {17,43}, {26,43}, {26,34}, {25,34}, {25,32}, {27,32}, {27,20}, {25,20}, {25,12}, {s="grabAntidote"}, {25,9}, {17,9}, {17,16}, {13,16}, {13,3}, {7,3}, {7,22}, {1,22}, {1,19}, {s="interact",dir="Up"}, {1,18}, {s="fightWeedle"}, {c="encounters",limit=22,extra="paras"}, {1,16}, {c="potion",b=false}, {s="equipForBrock",anti=true}, {1,5}, {s="equipForBrock"}, {1,-1}}, + -- Forest exit + {47, {4,7}, {4,1}, {5,1}, {5,0}}, + -- Road to Pewter City + {13, {3,11}, {s="a",a="Pewter City"}, {3,8}, {8,8}, {8,-1}}, + -- Pewter City + {2, {18,35}, {18,22}, {19,22}, {19,13}, {10,13}, {10,18}, {16,18}, {16,17}}, + -- Brock + {54, {4,13}, {s="a",a="Brock's Gym"}, {4,8}, {1,8}, {1,4}, {4,4}, {4,2}, {s="interact",dir="Up"}, {s="fightBrock"}, {s="split"}, {s="emuSpeed",percent=100}, {4,14}}, + +-- 3: BROCK + + -- To Pewter Mart + {2, {16,18}, {c="potion",b=true}, {10,18}, {10,13}, {21,13}, {21,18}, {23,18}, {23,17}}, + -- Pewter Mart + {56, {3,7}, {3,5}, {2,5}, {s="pewterMart"}, {2,6}, {3,6}, {3,8}}, + -- Leaving Pewter City + {2, {23,18}, {40,18}}, + -- Route 3 + {14, {0,10}, {s="a",a="Route 3"}, {c="catchFlier"}, {c="pp",on=true}, {s="battleModeSet"}, {8,10}, {8,8}, {11,8}, {11,6}, {s="leer",{"caterpie",8},{"weedle",7}}, {11,4}, {12,4}, {s="potion",hp=19}, {13,4}, {s="interact",dir="Right"}, {s="shortsKid"}, {13,5}, {s="potionBeforeCocoons"}, {18,5}, {s="interact",dir="Right"}, {s="swapHornAttack"}, {18,6}, {22,6}, {22,5}, {s="potion",hp=4}, {24,5}, {s="interact",dir="Down"}, {s="fightMetapod"}, {27,5}, {27,9}, {s="catchFlierBackup"}, {37,8}, {37,5}, {49,5}, {49,10}, {57,10}, {57,8}, {59,8}, {59,-1}}, + -- To the Center + {15, {9,16}, {c="pp",on=false}, {12,16}, {12,6}, {11,6}, {11,5}}, + -- PP up + {68, {3,7}, {3,3}, {s="confirm",dir="Up"}, {3,8}}, + -- Enter Mt. Moon + {15, {11,6}, {s="a",a="Mt. Moon"}, {18,6}, {s="split"}, {18,5}}, + +-- 4: ROUTE 3 + + -- Mt. Moon F1 + {59, {14,35}, {s="startMtMoon"}, {c="catchParas"}, {14,29}, {5,29}, {5,31}, {s="interact",dir="Down"}, {5,26}, {14,26}, {14,22}, {21,22}, {21,15}, {24,15}, {24,27}, {25,27}, {25,31}, {s="interact",dir="Left"}, {25,32}, {33,32}, {33,31}, {34,31}, {s="interact",dir="Right"}, {34,7}, {30,7}, {s="evolveNidorino"}, {28,7}, {s="teachWaterGun"}, {c="moon1Exp"}, {16,7}, {16,17}, {7,17}, {7,6}, {6,6}, {s="fightHiker"}, {6,2}, {3,2}, {s="interact",dir="Left"}, {5,2}, {5,5}}, + -- Mt. Moon B2 + {60, {5,5}, {5,17}, {21,17}}, + -- Mt. Moon B3 + {61, {21,17}, {23,17}, {23,14}, {27,14}, {27,16}, {33,16}, {33,14}, {36,14}, {36,24}, {32, 24}, {32,31}, {10,31}, {10,18}, {s="evolveNidoking"}, {c="encounters",limit=nil}, {10,17}, {12,17}, {c="moon2Exp"}, {13,17}, {13,15}, {s="potion",hp=7}, {13,7}, {c="moon3Exp"}, {s="helix"}, {13,4}, {3,4}, {3,7}, {5,7}}, + -- Mt. Moon escape + {60, {23,3}, {27,3}}, + +-- 5: MT. MOON + + -- To Cerulean + {15, {24,6}, {s="reportMtMoon"}, {s="split"}, {35,6}, {35,10}, {61,10}, {61,8}, {79,8}, {79,10}, {90,10}}, + -- Enter Cerulean + {3, {0,18}, {s="a",a="Cerulean"}, {14,18}, {s="dodgeCerulean"}, {19,18}, {19,17}}, + -- Cerulean Center + {64, {3,7}, {3,3}, {s="confirm",dir="Up"}, {3,8}}, + -- To the house + {3, {19,18}, {16,18}, {s="dodgeCeruleanLeft"}, {8,16}, {8,12}, {9,12}, {9,11}}, + -- In the house + {230, {2,7}, {2,0}}, + -- Outback + {3, {9,9}, {9,8}, {14,8}, {s="interact",dir="Right"}, {9,8}, {9,10}}, + -- Out the house + {230, {2,1}, {2,8}}, + -- Rival 2 + {3, {9,12}, {s="a",a="Cerulean Rival"}, {21,12}, {21,6}, {s="rivalSandAttack"}, {21,-1}}, + -- Nugget bridge + {35, {11,35}, {s="a",a="Nugget Bridge"}, {11,32}, {s="interact",dir="Up"}, {10,32}, {10,29}, {s="interact",dir="Up"}, {11,29}, {11,26}, {s="interact",dir="Up"}, {10,26}, {10,24}, {s="teachThrash"}, {10,23}, {s="interact",dir="Up"}, {11,23}, {11,21}, {s="teachThrash"}, {11,20}, {s="interact",dir="Up"}, {s="redbarMankey"}, {10,20}, {10,19}, {s="teachThrash"}, {10,15}, {s="waitToFight"}, {s="teachThrash"}, {s="split"}, {10,8}, {20,8}}, + +-- 6: NUGGET BRIDGE + + -- To Bill's + {36, {0,8}, {9,8}, {9,7}, {11,7}, {11,9}, {14,9}, {14,6}, {15,6}, {15,4}, {17,4}, {17,7}, {18,7}, {s="interact",dir="Down"}, {20,7}, {20,8}, {22,8}, {22,6}, {35,6}, {35,4}, {36,4}, {s="interact",dir="Right"}, {36,5}, {38,5}, {38,4}, {s="interact",dir="Up"}, {45,4}, {45,3}}, + -- Save Bill + {88, {2,7}, {2,5}, {5,5}, {s="confirm",dir="Right"}, {1,5}, {s="interact",dir="Up"}, {4,5}, {s="interact",dir="Up"}, {s="waitToTalk"}, {s="potionBeforeGoldeen"}, {s="item",item="escape_rope"}}, + -- To Misty + {3, {19,18}, {19,20}, {30,20}, {30,19}}, + -- Misty + {65, {4,13}, {s="a",a="Misty's Gym"}, {c="potion",b=false}, {4,8}, {2,8}, {2,5}, {7,5}, {7,3}, {6,3}, {5,3}, {s="waitToFight"}, {s="potionBeforeMisty"}, {5,2}, {s="waitToFight",dir="Left"}, {s="split"}, {s="tweetMisty"}, {5,3}, {7,3}, {7,5}, {5,5}, {5,14}}, + +-- 7: MISTY + + -- Past the policeman + {3, {30,20}, {c="potion",b=true,yolo=true}, {8,20}, {8,12}, {27,12}, {27,11}}, + -- Wrecked house + {62, {2,7}, {2,2}, {3,2}, {3,0}}, + -- Cerulean Rocket + {3, {27,9}, {28,9}, {s="potionBeforeRocket"}, {33,9}, {33,18}, {36,18}, {36,31}, {25,31}, {25,36}}, + -- Out of Cerulean + {16, {15,0}, {15,28}, {17,28}, {17,27}}, + -- Underground entrance + {71, {3,7}, {3,4}, {4,4}}, + -- Underground to Vermilion + {119, {5,4}, {4,4}, {s="jingleSkip"}, {2,4}, {2,41}}, + -- Underground exit + {74, {4,4}, {3,8}}, + -- Oddish + -- TODO Bubblebeam split + {17, {17,14}, {s="a",a="Vermilion City"}, {c="catchOddish"}, {17,15}, {s="potion",hp=9,yolo=5}, {17,19}, {s="catchOddish"}, {11,29}, {s="potion",hp=9,yolo=5}, {11,29}, {s="waitToFight",dir="Down"}, {10,29}, {10,30}, {s="potion",hp=9,yolo=5}, {10,31}, {9,31}, {9,32}, {s="potion",hp=20,yolo=18,chain=true}, {s="teach",move="bubblebeam",replace="water_gun"}, {9,36}}, + -- Enter Vermilion + {5, {19,0}, {c="disableCatch"}, {19,6}, {21,6}, {21,14}, {23,14}, {23,13}}, + -- Vermilion mart + {91, {3,7}, {3,5}, {2,5}, {s="vermilionMart"}, {3,5}, {3,8}}, + -- To S.S. Anne + {5, {23,14}, {30,14}, {30,26}, {18,26}, {18,31}}, + -- Mew + {94, {14,0}, {s="a",a="S.S. Anne"}, {14,3}}, + -- First deck + {95, {27,0}, {27,1}, {26,1}, {26,7}, {2,7}, {2,6}}, + -- Rival 3 + {96, {2,4}, {2,11}, {3,11}, {3,12}, {s="potion",hp=20,yolo=16}, {37,12}, {37,8}, {s="rivalSandAttack"}, {37,5}, {36,5}, {36,4}}, + -- Old man Cut + {101, {0,7}, {0,4}, {4,4}, {4,3}, {s="interact",dir="Up"}, {4,5}, {0,5}, {0,7}}, + -- Second deck out + {96, {36,4}, {36,12}, {3,12}, {3,11}, {2,11}, {2,4}}, + -- First deck out + {95, {2,6}, {2,7}, {26,7}, {26,-1}}, + -- Departure + {94, {14,2}}, + -- To Surge + {5, {18,29}, {18,26}, {30,26}, {30,14}, {15,14}, {15,17}, {s="potion",hp=20,yolo=5,forced="potion",chain=true}, {s="potion",hp=5,chain=true}, {s="teach",move="cut",poke="oddish",alt="paras",chain=true}, {s="skill",move="cut",done=0x0D4D}, {15,20}, {12,20}, {12,19}}, + -- Trashcans + {92, {4,17}, {s="a",a="Surge's Gym"}, {4,16}, {2,16}, {2,11}, {s="trashcans"}, {4,6}, {4,3}, {5,3}, {5,2}, {s="interact",dir="Up"}, {s="fightSurge"}, {s="split"}, {4,2}, {4,13}, {5,13}, {5,18}}, + +-- 8: SURGE + + -- To bicycle house + {5, {12,20}, {s="a",a="Bicycle Shop"}, {15,20}, {15,19}, {s="skill",move="cut",done=0x0D4D}, {15,14}, {9,14}, {9,13}}, + -- Bicycle cert + {90, {2,7}, {2,5}, {0,5}, {0,1}, {2,1}, {s="confirm",dir="Right"}, {s="teach",move="dig",poke="paras",alt="squirtle",chain=true}, {s="skill",move="dig",map=90}}, + -- Cerulean warp + {3, {19,18}, {19,23}, {16,23}, {16,26}, {13,26}, {13,25}}, + -- Bicycle shop + {66, {2,7}, {2,5}, {4,5}, {s="dodgeBicycleGirlRight"}, {6,4}, {6,4}, {s="procureBicycle"}, {s="dodgeBicycleGirlLeft"}, {3,8}}, + -- Bicycle out of Cerulean + {3, {13,26}, {s="swapBicycle"}, {s="teach",move="thunderbolt",replace="horn_attack",chain=true}, {s="bicycle"}, {19,26}, {19,27}, {s="skill",move="cut",done=0x0D4D}, {19,29}, {36,29}, {36,16}, {40,16}}, + -- TPP's Bane + {20, {0,8}, {s="a",a="Route 9"}, {4,8}, {s="skill",move="cut",done=0x0C17,val=2}, {13,8}, {13,9}, {s="interact",dir="Down"}, {12,9}, {12,12}, {23,12}, {23,11}, {29,11}, {29,12}, {41,12}, {41,10}, {40,10}, {40,9}, {s="interact",dir="Up"}, {41,9}, {41,6}, {39,6}, {39,4}, {45,4}, {45,3}, {51,3}, {51,8}, {60,8}}, + -- To the cave + {21, {0,8}, {3,8}, {3,10}, {13,10}, {13,15}, {14,15}, {14,26}, {3,26}, {3,18}, {8,18}, {8,17}}, + -- Rock tunnel + {82, {15,3}, {s="a",a="Rock Tunnel"}, {c="potion",b=false}, {s="item",item="repel"}, {15,6}, {23,6}, {23,7}, {s="interact",dir="Down"}, {s="redbarCubone"}, {22,7}, {22,10}, {37,10}, {37,3}}, + -- B1 + {232, {33,25}, {33,30}, {27,30}, {s="interact",dir="Left"}, {27,31}, {14,31}, {14,29}, {s="interact",dir="Up"}, {17,29}, {17,24}, {25,24}, {25,16}, {37,16}, {37,11}, {s="item",item="repel"}, {37,3}, {27,3}}, + -- B2 + {82, {5,3}, {5,9}, {11,9}, {11,14}, {17,14}, {17,11}}, + -- B1 + {232, {23,11}, {14,11}, {14,17}, {8,17}, {8,10}, {7,10}, {s="interact",dir="Left"}, {7,11}, {5,11}, {s="item",item="repel"}, {5,3}, {3,3}}, + -- Out of the Tunnel + {82, {37,17}, {32,17}, {32,23}, {37,23}, {37,28}, {28,28}, {26,24}, {23,24}, {s="interact",dir="Left"}, {23,27}, {15,27}, {15,33}}, + -- To Lavender Town + {21, {8,54}, {s="a",a="Lavender Town"}, {15,54}, {15,65}, {11,65}, {11,69}, {6,69}, {6,72}}, + -- Through Lavender + {4, {6,0}, {6,6}, {0,6}, {0,8}, {-1,8}}, + -- Leave Lavender + {19, {59,8}, {52,8}, {52,13}, {47,13}, {s="interact",dir="Left"}, {47,14}, {42,14}, {42,7}, {40,7}, {40,6}, {29,6}, {29,7}, {23,7}, {23,12}, {14,12}, {14,4}, {13,4}, {13,3}}, + -- Underground entrance + {80, {3,7}, {3,6}, {4,6}, {4,4}}, + -- Underground + {121, {47,2}, {s="bicycle"}, {47,5}, {22,5}, {s="interact",dir="Left"}, {2,5}}, + -- Underground exit + {77, {4,4}, {4,8}}, + -- To Celadon + {18, {5,14}, {s="bicycle"}, {8,14}, {8,8}, {4,8}, {4,3}, {-1,3}}, + -- Celadon + {6, {49,11}, {s="a",a="Celadon Mart"}, {14,11}, {14,14}, {10,14}, {10,13}}, + -- Department store + {122, {16,7}, {c="potion",b=true,yolo=true}, {c="pp",on=true}, {16,3}, {12,3}, {12,1}}, + -- F2 + {123, {12,2}, {16,2}, {16,1}}, + -- F3 + {124, {16,2}, {12,2}, {12,1}}, + -- F4: Poke Doll + {125, {12,2}, {10,2}, {10,5}, {5,5}, {s="shopPokeDoll"}, {11,5}, {11,2}, {16,2}, {16,1}}, + -- F5: Buffs + {136, {16,2}, {8,2}, {8,5}, {5,5}, {s="shopBuffs"}, {9,5}, {9,2}, {12,2}, {12,1}}, + -- Roof + {126, {15,3}, {12,3}, {s="shopVending"}, {6,3}, {6,4}, {s="giveWater"}, {6,4}, {7,3}, {12,3}, {s="shopExtraWater"}, {15,3}, {15,2}}, + -- F5 + {136, {12,2}, {16,2}, {16,1}}, + -- F4 + {125, {16,2}, {12,2}, {12,1}}, + -- F3 + {124, {12,2}, {16,2}, {16,1}}, + -- F2: TM and repel + {123, {16,2}, {8,2}, {8,5}, {6,5}, {s="shopTM07"}, {5,5}, {s="shopRepels"}, {9,5}, {9,2}, {12,2}, {12,1}}, + -- Exit department store + {122, {12, 2}, {12,6}, {16,6}, {16,8}}, + -- Leave Celadon + {6, {10,14}, {s="bicycle"}, {10,15}, {2,15}, {2,18}, {-1,18}}, + -- Cut out of Celadon + {27, {39,10}, {34,10}, {s="teach",move="horn_drill",replace="bubblebeam",full=true,chain=true}, {s="skill",move="cut",dir="Up",done=0x0D4D}, {34,6}, {27,6}, {27,4}, {23,4}}, + -- Old man's hall + {186, {7,2}, {-1,2}}, + -- To the Fly house + {27, {17,4}, {s="a",a="HM02 Fly"}, {10,4}, {10,6}, {7,6}, {7,5}}, + -- Fly house + {188, {2,7}, {2,4}, {s="interact",dir="Up"}, {2,5}, {s="split"}, {2,8}}, + +-- 9: FLY + + -- Fly to Lavender + {27, {7,6}, {s="swapRepels"}, {s="teach",move="fly",poke="spearow",alt="pidgey",chain=true}, {s="teach",move="horn_drill",replace="bubblebeam",chain=true}, {s="item",item="super_repel",chain=true}, {s="potion",hp=6,chain=true}, {s="teach",move="rock_slide",replace="poison_sting",chain=true}, {s="fly",dest="lavender",map=4}}, + -- To the tower + {4, {3,6}, {s="a",a="Pokemon Tower"}, {14,6}, {14,5}}, + -- Pokemon Tower + {142, {10,17}, {10,10}, {18,10}, {18,9}}, + -- F2: Rival + {143, {18,9}, {c="setThrash",disable=true}, {18,7}, {16,7}, {16,5}, {15,5}, {s="lavenderRival"}, {5,5}, {5,8}, {3,8}, {3,9}}, + -- F3 + {144, {3,9}, {3,10}, {6,10}, {6,13}, {8,13}, {8,6}, {17,6}, {17,9}, {18,9}}, + -- F4 + {145, {18,9}, {s="allowDeath",on=true}, {c="potion",b=false}, {18,7}, {16,7}, {s="interact",dir="Left"}, {s="digFight"}, {16,9}, {c="potion",b=true,yolo=true}, {14,9}, {14,10}, {13,10}, {s="interact",dir="Left"}, {14,10}, {14,8}, {11,8}, {11,9}, {10,9}, {10,12}, {7,12}, {7,11}, {4,11}, {4,10}, {3,10}, {3,9}}, + -- F5 + {146, {3,9}, {4,9}, {4,11}, {s="interact",dir="Down"}, {4,6}, {13,6}, {13,9}, {9,9}, {9,12}, {14,12}, {14,10}, {18,10}, {s="allowDeath",on=false}, {18,9}}, + -- F6 + {147, {18,9}, {18,7}, {15,7}, {15,3}, {11,3}, {11,5}, {10,5}, {s="interact",dir="Left"}, {10,6}, {6,6}, {6,7}, {s="interact",dir="Down"}, {6,14}, {10,14}, {10,16}, {s="pokeDoll"}, {9,16}}, + -- F7: Top + {148, {9,16}, {10,16}, {10,9}, {s="fightXAccuracy"}, {c="setThrash",disable=false}, {10,7}, {s="thunderboltFirst"}, {10,4}, {s="interact",dir="Up"}}, + -- Old man's house + {149, {3,7}, {3,6}, {2,6}, {2,1}, {s="interact",dir="Right"}, {2,8}}, + +-- 10: POKéFLUTE + + -- Lavender -> Celadon + {4, {7,10}, {s="split"}, {s="fly",dest="celadon",map=6}}, + -- To Celadon Center + {6, {41,10}, {41,9}}, + -- Celadon Center + {133, {3,7}, {3,3}, {s="confirm",dir="Up"}, {3,8}}, + -- Leave Celadon + {6, {41,10}, {s="a",a="Snorlax"}, {s="item",item="super_repel",chain=true}, {s="bicycle"}, {41,11}, {14,11}, {14,14}, {2,14}, {2,18}, {-1,18}}, + -- トトロだ! + {27, {39,10}, {27,10}, {s="playPokeflute"}, {23,10}}, + -- Snorlax pass + {186, {7,8}, {-1,8}}, + -- Bicycle road + {27, {17,10}, {s="a",a="Bicycle Road"}, {12,10}, {12,13}, {11,13}, {11,18}}, + -- Forced down inputs + {28, {11,0}, {11,5}, {15,5}, {15,12}, {s="drivebyRareCandy"}, {15,14}, {18,14}, {18,122}, {13,122}, {13,143}}, + -- Cycling road exit + {29, {13,0}, {13,8}, {34,8}}, + -- Exit building + {190, {0,4}, {8,4}}, + -- Enter Safari City + {29, {40,8}, {s="item",item="super_repel",chain=true}, {s="teach",move="ice_beam",replace="rock_slide",chain=true}, {s="bicycle"}, {50,8}}, + -- Safari City + {7, {0,16}, {s="a",a="Safari Zone"}, {3,16}, {3,20}, {23,20}, {23,14}, {29,14}, {29,15}, {35,15}, {35,8}, {37,8}, {37,2}, {22,2}, {22,4}, {18,4}, {18,3}}, + -- Safari entrance + {156, {3,5}, {3,2}, {4,2}, {s="confirm",dir="Right"}}, + -- Safari 1 + {220, {15,25}, {s="bicycle"}, {15,16}, {28,16}, {28,11}, {30,11}}, + -- Safari 2 + {217, {0,23}, {4,23}, {4,24}, {20,24}, {20,20}, {s="safariCarbos"}, {12,20}, {12,22}, {11,22}, {10,22}, {s="item",item="super_repel",chain=true}, {s="item",item="carbos",poke="nidoking",close=true}, {9,22}, {9,8}, {12,8}, {12,6}, {17,6}, {17,8}, {20,8}, {s="centerSkipFullRestore"}, {20,3}, {7,3}, {7,5}, {-1,5}}, + -- Safari 3 + {218, {39,31}, {22,31}, {22,22}, {16,22}, {16,28}, {13,28}, {13,9}, {28,9}, {28,3}, {3,3}, {3,36}}, + -- Safari 4 + {219, {21,0}, {21,5}, {19,5}, {19,6}, {s="interact",dir="Down"}, {19,5}, {7,5}, {7,6}, {3,6}, {3,3}}, + -- Warden + {222, {2,7}, {2,6}, {3,6}, {3,4}, {s="interact",dir="Up"}, {3,8}}, + -- Safari Warp + {219, {3,4}, {s="skill",move="dig",map=219}}, + -- Celadon again + {6, {41,10}, {s="bicycle"}, {41,11}, {50,11}}, + -- To Saffron + {18, {0,3}, {s="a",a="Saffron City"}, {4,3}, {4,9}, {10,9}, {10,10}, {12,10}}, + -- Thirsty guard + {76, {0,4}, {6,4}}, + -- Saffron entry + {18, {18,10}, {s="bicycle"}, {20,10}}, + -- Saffron City + {10, {0,18}, {3,18}, {3,22}, {18,22}, {18,21}}, + -- Silph Co + {181, {10,17}, {s="a",a="Silph Co."}, {10,9}, {8,9}, {8,1}, {20,1}, {20,0}}, + -- Elivator + {236, {1,3}, {1,2}, {3,2}, {3,1}, {s="silphElevator"}, {2,1}, {2,4}}, + -- F10 + {234, {12,1}, {12,3}, {4,3}, {4,9}, {s="fightSilphMachoke"}, {6,9}, {6,11}, {s="silphCarbos"}, {6,16}, {3,16}, {3,14}, {s="interact",dir="Right"}, {3,15}, {1,15}, {s="item",item="carbos",poke="nidoking",full=true}, {1,12}, {s="interact",dir="Right"}, {1,16}, {3,16}, {s="teach",move="surf",poke="squirtle",replace="tail_whip",chain=true}, {s="teach",move="earthquake",replace="thrash",chain=true}, {s="item",item="carbos",poke="nidoking",close=true}, {6,16}, {6,9}, {4,9}, {4,1}, {8,1}, {8,0}}, + -- F9 + {233, {14,1}, {14,3}, {24,3}, {24,16}, {17,16}, {17,15}}, + -- Warped + {210, {9,15}, {9,16}, {20,16}, {s="interact",dir="Right"}, {9,16}, {9,15}}, + -- Warp back + {233, {17,15}, {17,14}, {17,15}}, + -- First card + {210, {9,15}, {9,13}, {8,13}, {s="interact",dir="Left"}, {6,13}, {6,16}, {3,16}, {3,15}}, + -- Warp down + {208, {3,15}, {3,14}, {18,14}, {18,9}, {s="interact",dir="Left"}, {14,9}, {14,11}, {11,11}}, + -- Rival 5 + {212, {5,3}, {s="a",a="Silph Rival"}, {4,3}, {4,2}, {3,2}, {c="potion",b=false}, {s="silphRival"}, {3,7}, {c="potion",b=true,yolo=true}, {5,7}}, + -- Giovanni + {235, {3,2}, {s="a",a="Silph Giovanni"}, {3,11}, {2,11}, {2,16}, {s="interact",dir="Right"}, {2,15}, {5,15}, {s="potion",hp=16,yolo=12}, {6,15}, {6,14}, {s="interact",dir="Up"}, {6,13}, {s="fightXAccuracy"}, {s="fightSilphGiovanni"}, {s="split"}, {s="waitToPause"}, {s="skill",move="dig",map=235}}, + +-- 11: SILPH CO. + + -- Fly to Fuschia + {6, {41,10}, {s="fly",dest="fuchsia",map=7}}, + -- To Koga + {7, {19,28}, {s="a",a="Koga's Gym"}, {5,28}, {5,27}}, + -- Koga + -- TODO save turn frames? + {157, {4,17}, {9,17}, {9,9}, {7,9}, {s="interact",dir="Up"}, {9,9}, {9,1}, {1,1}, {1,2}, {s="potion",hp="KogaHypno",yolo=18,chain=true}, {s="earthquakeElixer",min=2,close=true}, {1,3}, {2,3}, {2,5}, {1,5}, {c="potion",b=false}, {1,7}, {s="fightHypno"}, {1,9}, {2,9}, {s="earthquakeElixer",min=4}, {4,9}, {s="interact",dir="Down"}, {s="fightKoga"}, {s="split"}, {1,9}, {1,5}, {2,5}, {2,3}, {1,3}, {1,1}, {9,1}, {9,16}, {5,16}, {5,18}}, + +-- 12: KOGA + + -- To the Warden + {7, {5,28}, {s="bicycle"}, {6,28}, {6,30}, {24,30}, {30,30}, {30,28}, {27,28}, {27,27}}, + -- HM04 Strength + {155, {4,7}, {4,6}, {2,6}, {2,4}, {s="interact",dir="Up"}, {4,4}, {4,8}}, + -- Fly home + {7, {27,28}, {s="fly",dest="pallet",map=0}}, + -- Pallet to Cinnabar + {0, {5,6}, {s="item",item="super_repel",chain=true}, {s="item",item="rare_candy",amount=3,poke="nidoking",chain=true}, {s="bicycle"}, {s="allowDeath",on=false}, {3,6}, {s="dodgeGirl"}, {3,17}, {s="skill",move="surf",dir="Right",x=4}, {4,18}}, + -- To Cinnabar + {32, {4,0}, {4,14}, {3,14}, {3,90}}, + -- Enter Cinnabar Mansion + {8, {3,0}, {s="a",a="Cinnabar Mansion"}, {3,4}, {6,4}, {6,3}}, + -- F1 + {165, {5,27}, {5,10}}, + -- F2 + {214, {5,11}, {10,11}, {10,5}, {6,5}, {6,1}}, + -- F3 + {215, {6,2}, {11,2}, {11,6}, {10,6}, {s="confirm",dir="Up"}, {14,6}, {14,11}, {16,11}, {16,14}}, + -- F1 drop + {165, {16,14}, {16,15}, {13,15}, {13,20}, {s="cinnabarCarbos"}, {21,23}}, + -- B1 + {216, {23,22}, {23,15}, {21,15}, {s="item",item="super_repel",chain=true}, {s="item",item="carbos",poke="nidoking",close=true}, {17,15}, {17,19}, {18,19}, {18,23}, {17,23}, {17,26}, {18,26}, {s="confirm",dir="Up"}, {14,26}, {14,22}, {12,22}, {12,15}, {24,15}, {24,18}, {26,18}, {26,6}, {24,6}, {24,4}, {20,4}, {s="confirm",dir="Up"}, {24,4}, {24,6}, {12,6}, {12,2}, {11,2}, {s="interact",dir="Left"}, {12,2}, {12,7}, {4,7}, {4,9}, {2,9}, {s="interact",dir="Left"}, {5,9}, {5,10}, {s="teach",move="strength",poke="squirtle",replace="tackle",chain=true}, {s="item",item="rare_candy",amount=2,poke="nidoking",close=true}, {5,12}, {s="interact",dir="Down"}, {5,12}, {s="skill",move="dig",map=216}}, + -- Celadon once again + {6, {41,10}, {s="bicycle"}, {41,13}, {36,13}, {36,23}, {25,23}, {25,30}, {35,30}, {35,31}, {s="skill",move="cut",dir="Down",done=0x0D4D}, {35,34}, {5,34}, {5,29}, {12,29}, {12,27}}, + -- Erika + {134, {4,17}, {s="a",a="Erika's Gym"}, {4,16}, {1,16}, {1,9}, {0,9}, {0,4}, {1,4}, {s="skill",move="cut",done=0x0D4D}, {4,4}, {s="interact",dir="Up"}, {s="fightErika"}, {s="split"}, {4,5}, {5,5}, {5,6}, {s="skill",move="cut",dir="Down",done=0x0D4D}, {5,18}}, + +-- 13: ERIKA + + -- Fly to Cinnabar + {6, {12,28}, {s="fly",dest="cinnabar",map=8}}, + -- Cinnabar + {8, {11,12}, {s="earthquakeElixer",min=4,chain=true}, {s="bicycle"}, {18,12}, {18,3}}, + -- Cinnabar Gym + {166, {16,17}, {s="a",a="Blaine's Gym"}, {16,14}, {18,14}, {18,10}, {15,10}, {15,8}, {s="confirm",dir="Up"}, {16,8}, {16,7}, {18,7}, {18,1}, {12,1}, {12,2}, {10,2}, {s="confirm",dir="Up",type="B"}, {12,2}, {12,7}, {10,7}, {10,8}, {9,8}, {s="confirm",dir="Up",type="B"}, {9,11}, {12,11}, {12,13}, {10,13}, {10,14}, {9,14}, {s="confirm",dir="Up",type="B"}, {9,16}, {1,16}, {1,14}, {s="confirm",dir="Up"}, {2,14}, {2,13}, {4,13}, {4,9}, {1,9}, {1,8}, {s="confirm",dir="Up",type="B"}, {2,8}, {2,7}, {4,7}, {4,5}, {3,5}, {3,4}, {c="potion",b=false}, {s="waitToFight",dir="Up"}, {s="split"}, {s="waitToReceive"}, {s="skill",move="dig",map=166}}, + +-- 14: BLAINE + + -- Celadon too many times + {6, {41,10}, {c="potion",b=true,yolo=true}, {s="bicycle"}, {41,11}, {50,11}}, + -- Exit Celadon + {18, {0,3}, {4,3}, {4,9}, {10,9}, {10,10}, {12,10}}, + -- Saffron gate + {76, {0,4}, {s="a",a="Saffron City"}, {6,4}}, + -- Saffron edge + {18, {18,10}, {s="earthquakeElixer",min=4,chain=true}, {s="bicycle"}, {20,10}}, + -- Saffron again + {10, {0,18}, {3,18}, {3,6}, {31,6}, {31,4}, {34,4}, {34,3}}, + -- Sabrina + {178, {8,17}, {s="a",a="Sabrina's Gym"}, {8,16}, {11,16}, {11,15}, {16,17}, {16,15}, {15,15}, {18,3}, {18,5}, {15,5}, {1,5}, {11,11}, {11,8}, {10,8}, {s="waitToFight",dir="Left"}, {s="split"}, {11,8}, {11,11}, {s="earthquakeElixer",min=4,chain=true}, {s="skill",move="dig",map=178}}, + +-- 15: SABRINA + + -- Celadon + {6, {41,10}, {s="fly",dest="viridian",map=1}}, + -- Viridian again + {1, {23,26}, {s="bicycle"}, {19,26}, {19,4}, {27,4}, {27,3}, {34,3}, {34,8}, {32,8}, {32,7}}, + -- Giovanni Gym + {45, {16,17}, {c="potion",b=false}, {s="a",a="Giovanni's Gym"}, {16,16}, {14,16}, {14,9}, {13,9}, {13,7}, {15,7}, {15,4}, {12,4}, {12,5}, {10,5}, {s="a",a="Machoke"}, {10,4}, {s="fightGiovanniMachoke"}, {10,5}, {s="a",a="Giovanni's Gym"}, {13,5}, {13,4}, {15,4}, {15,7}, {13,7}, {13,11}, {14,11}, {14,16}, {16,16}, {16,18}}, + -- Reset Gym + {1, {32,8}, {32,7}}, + -- Giovanni + {45, {16,17}, {c="potion",b=false}, {16,16}, {14,16}, {14,9}, {13,9}, {13,7}, {15,7}, {15,4}, {12,4}, {12,5}, {10,5}, {10,2}, {7,2}, {7,4}, {2,4}, {s="checkGiovanni"}, {2,2}, {s="interact",dir="Up"}, {s="fightGiovanni"}, {s="split"}, {2,4}, {7,4}, {7,2}, {10,2}, {10,5}, {12,5}, {12,4}, {15,4}, {15,7}, {13,7}, {13,11}, {14,11}, {14,16}, {16,16}, {16,18}}, + +-- 16: GIOVANNI + + -- Leave Viridian + {1, {32,8}, {s="bicycle"}, {32,12}, {17,12}, {17,16}, {16,16}, {16,17}, {-1,17}}, + -- To Pokemon League + {33, {39,9}, {s="a",a="Viridian Rival"}, {35,9}, {35,12}, {31,12}, {31,5}, {29,5}, {s="viridianRival"}, {16,5}, {16,12}, {5,12}, {5,10}, {11,10}, {11,6}, {8,6}, {8,5}}, + -- Pokemon League 1 + {193, {4,7}, {4,0}}, + -- PL 2 + {34, {7,139}, {s="ether",max=true,chain=true}, {s="bicycle"}, {7,132}, {14,132}, {14,124}, {9,124}, {9,116}, {10,116}, {10,104}, {s="skill",move="surf",y=103}, {10,92}, {7,92}, {7,90}, {s="pickMaxEther"}, {7,72}, {8,72}, {8,71}, {s="item",item="super_repel",chain=true}, {s="bicycle"}, {8,66}, {10,66}, {10,57}, {12,57}, {12,48}, {6,48}, {6,32}, {4,32}, {4,31}}, + -- Victory Road + {108, {8,17}, {s="a",a="Victory Road"}, {s="tweetVictoryRoad"}, {8,16}, {4,16}, {4,14}, {5,14}, {s="skill",move="strength"}, {5,15}, {4,15}, {4,16}, {7,16}, {s="push",dir="Right",x=0x0255,y=0x0254}, {7,17}, {9,17}, {9,15}, {8,15}, {8,14}, {15,14}, {15,15}, {16,15}, {16,14}, {s="push",dir="Up",x=0x0255,y=0x0254}, {14,14}, {14,12}, {16,12}, {16,11}, {17,11}, {17,12}, {14,12}, {14,14}, {8,14}, {8,16}, {5,16}, {5,12}, {11,12}, {11,6}, {7,6}, {7,8}, {3,8}, {3,5}, {2,5}, {2,1}, {1,1}}, + -- F2 + {194, {0,8}, {0,9}, {3,9}, {3,13}, {5,13}, {5,14}, {s="item",item="super_repel",chain=true}, {s="skill",move="strength"}, {4,14}, {4,13}, {3,13}, {3,15}, {4,15}, {4,16}, {3,16}, {s="push",dir="Left",x=0x02B5,y=0x02B4}, {3,11}, {5,11}, {5,8}, {14,8}, {14,14}, {21,14}, {21,16}, {28,16}, {28,11}, {23,11}, {23,7}}, + -- F3 + {198, {23,7}, {23,6}, {22,6}, {22,4}, {s="skill",move="strength"}, {22,2}, {23,2}, {23,1}, {7,1}, {7,0}, {6,0}, {6,1}, {7,1}, {7,2}, {3,2}, {3,1}, {2,1}, {2,4}, {1,4}, {1,5}, {2,5}, {2,4}, {4,4}, {4,2}, {7,2}, {7,1}, {20,1}, {20,6}, {17,6}, {17,4}, {9,4}, {9,10}, {5,10}, {5,8}, {1,8}, {1,15}, {11,15}, {11,16}, {20,16}, {20,15}, {23,15}}, + -- F2 + {194, {22,16}, {s="healBeforeLorelei"}, {s="item",item="super_repel",chain=true}, {s="skill",move="strength"}, {s="bicycle"}, {22,17}, {24,17}, {24,16}, {11,16}, {s="push",dir="Left",x=0x02D5,y=0x02D4}, {21,16}, {21,14}, {25,14}}, + -- F3 + {198, {27,15}, {27,8}, {26,8}}, + -- F2 Exit + {194, {27,7}, {30,7}}, + -- Victory end + {34, {14,32}, {18,32}, {18,20}, {14,20}, {14,10}, {13,10}, {13,6}, {10,6}, {10,-1}}, + -- Elite Four entrance + {9, {10,17}, {s="a",a="Elite Four"}, {10,5}}, + -- Last Center + {174, {7,11}, {15,9}, {15,8}, {s="depositPokemon"}, {7,8}, {7,7}, {s="centerSkip"}, {4,7}, {3,7}, {3,2}, {8,2}, {8,0}}, + +-- 17: LORELEI + + {245, {4,5}, {s="a",a="Lorelei"}, {c="potion",b=false}, {4,2}, {s="interact",dir="Right"}, {s="lorelei"}, {s="split"}, {4,0}}, + + {246, {4,5}, {s="a",a="Bruno"}, {s="item",item="elixer",poke="nidoking"}, {4,2}, {s="interact",dir="Right"}, {s="bruno"}, {s="split"}, {4,0}}, + + {247, {4,5}, {s="a",a="Agatha"}, {s="potion",hp=113,full=true}, {4,2}, {s="interact",dir="Right"}, {s="agatha"}, {s="split"}, {4,1}, {s="prepareForLance"}, {s="ether"}, {4,0}}, + + {113, {6,11}, {s="a",a="Lance"}, {6,2}, {s="lance"}, {s="waitToFight"}, {s="split"}, {5,2}, {5,1}, {s="prepareForBlue"}, {5,-1}}, + + {120, {4,3}, {s="a",a="Blue"}, {s="blue"}, {3,0}}, + + {118, {4,2}, {s="champion"}} +} + +return paths diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..7a6e4e5 --- /dev/null +++ b/main.lua @@ -0,0 +1,198 @@ +-- Customization settings + +GAME_NAME = "red" -- Only currently supported option +RESET_FOR_TIME = true -- Set to false if you just want to see the bot finish a run + +local CUSTOM_SEED = nil -- Set to a known seed to replay it, or leave nil for random ones +local PAINT_ON = true -- Displays contextual information while the bot runs + +-- Start code (hard hats on) + +local START_WAIT = 99 +local VERSION = "1.0" + +local battle = require "action.battle" +local textbox = require "action.textbox" +local walk = require "action.walk" + +local combat = require "ai.combat" +local control = require "ai.control" +local strategies = require "ai.strategies" + +local bridge = require "util.bridge" +local input = require "util.input" +local memory = require "util.memory" +local menu = require "util.menu" +local paint = require "util.paint" +local utils = require "util.utils" +local settings = require "util.settings" + +local pokemon = require "storage.pokemon" + +local YELLOW = GAME_NAME == "yellow" + +local hasAlreadyStartedPlaying = false +local inBattle, oldSecs +local running = true +local previousPartySize = 0 +local lastHP +local criticaled = false + +local function startNewAdventure() + local startMenu, withBattleStyle + if (YELLOW) then + startMenu = memory.raw(0x0F95) == 0 + withBattleStyle = "battle_style" + else + startMenu = memory.value("player", "name") ~= 0 + end + if (startMenu and menu.getCol() ~= 0) then + if (settings.set("text_speed", "battle_animation", withBattleStyle)) then + menu.select(0) + end + elseif (math.random(0, START_WAIT) == 0) then + input.press("Start") + end +end + +local function choosePlayerNames() + local name + if (memory.value("player", "name2") == 80) then + name = "W" + else + name = "B" + end + textbox.name(name, true) +end + +local function pollForResponse() + local response = bridge.process() + if (response) then + bridge.polling = false + textbox.setName(tonumber(response)) + end +end + +local function resetAll() + strategies.softReset() + combat.reset() + control.reset() + walk.reset() + paint.reset() + bridge.reset() + oldSecs = 0 + running = false + previousPartySize = 0 + -- client.speedmode = 200 + if (CUSTOM_SEED) then + strategies.seed = CUSTOM_SEED + print("RUNNING WITH A FIXED SEED ("..strategies.seed.."), every run will play out identically!") + else + strategies.seed = os.time() + end + math.randomseed(strategies.seed) +end + +-- Execute + +print("Welcome to PokeBot "..GAME_NAME.." version "..VERSION) +local productionMode = not walk.init() and false +if (CUSTOM_SEED) then + client.reboot_core() +else + hasAlreadyStartedPlaying = utils.ingame() +end + +strategies.init(hasAlreadyStartedPlaying) +if (RESET_FOR_TIME and hasAlreadyStartedPlaying) then + RESET_FOR_TIME = false + print("Disabling time-limit resets as the game is already running. Please reset the emulator and restart the script if you'd like to go for a fast time.") +end +if (productionMode) then + bridge.init() +else + input.setDebug(true) +end + +local previousMap + +while true do + local currentMap = memory.value("game", "map") + if (currentMap ~= previousMap) then + input.clear() + previousMap = currentMap + end + if (not input.update()) then + if (not utils.ingame()) then + if (currentMap == 0) then + if (running) then + if (not hasAlreadyStartedPlaying) then + client.reboot_core() + hasAlreadyStartedPlaying = true + else + resetAll() + end + else + startNewAdventure() + end + else + if (not running) then + bridge.liveSplit() + running = true + end + choosePlayerNames() + end + else + local battleState = memory.value("game", "battle") + if (battleState > 0) then + if (battleState == 1) then + if (not inBattle) then + control.wildEncounter() + if (strategies.moonEncounters) then + strategies.moonEncounters = strategies.moonEncounters + 1 + end + inBattle = true + end + end + local isCritical + local battleMenu = memory.value("battle", "menu") + if (battleMenu == 94) then + isCritical = false + elseif (memory.double("battle", "our_hp") == 0) then + if (memory.value("battle", "critical") == 1) then + isCritical = true + end + end + if (isCritical ~= nil and isCritical ~= criticaled) then + criticaled = isCritical + strategies.criticaled = criticaled + end + else + inBattle = false + end + local currentHP = pokemon.index(0, "hp") + if (currentHP == 0 and not strategies.canDie and pokemon.index(0) > 0) then + strategies.death(currentMap) + elseif (walk.strategy) then + if (strategies.execute(walk.strategy)) then + walk.traverse(currentMap) + end + elseif (battleState > 0) then + if (not control.shouldCatch(partySize)) then + battle.automate() + end + elseif (textbox.handle()) then + walk.traverse(currentMap) + end + end + end + + if (PAINT_ON) then + paint.draw(currentMap) + end + + input.advance() + emu.frameadvance() +end + +bridge.close() diff --git a/storage/inventory.lua b/storage/inventory.lua new file mode 100644 index 0000000..dfa8d60 --- /dev/null +++ b/storage/inventory.lua @@ -0,0 +1,200 @@ +local inventory = {} + +local pokemon = require "storage.pokemon" + +local input = require "util.input" +local memory = require "util.memory" +local menu = require "util.menu" +local utils = require "util.utils" + +local items = { + pokeball = 4, + bicycle = 6, + moon_stone = 10, + antidote = 11, + paralyze_heal = 15, + full_restore = 16, + super_potion = 19, + potion = 20, + escape_rope = 29, + carbos = 38, + repel = 30, + + rare_candy = 40, + helix_fossil = 42, + nugget = 49, + pokedoll = 51, + super_repel = 56, + fresh_water = 60, + soda_pop = 61, + pokeflute = 73, + ether = 80, + max_ether = 81, + elixer = 82, + + x_accuracy = 46, + x_speed = 67, + x_special = 68, + + cut = 196, + fly = 197, + surf = 198, + strength = 199, + + horn_drill = 207, + bubblebeam = 211, + water_gun = 212, + ice_beam = 213, + thunderbolt = 224, + earthquake = 226, + dig = 228, + tm34 = 234, + rock_slide = 248, +} + +local ITEM_BASE = 0xD31E + +-- Data + +function inventory.indexOf(name) + local searchID = items[name] + for i=0,19 do + local iidx = ITEM_BASE + i * 2 + if (memory.raw(iidx) == searchID) then + return i + end + end + return -1 +end + +function inventory.count(name) + local index = inventory.indexOf(name) + if (index ~= -1) then + return memory.raw(ITEM_BASE + index * 2 + 1) + end + return 0 +end + +function inventory.contains(...) + for i,name in ipairs(arg) do + if (inventory.count(name) > 0) then + return name + end + end +end + +-- Actions + +function inventory.teach(item, poke, replaceIdx, altPoke) + local main = memory.value("menu", "main") + local column = menu.getCol() + if (main == 144) then + if (column == 5) then + menu.select(replaceIdx, true) + else + input.press("A") + end + elseif (main == 128) then + if (column == 5) then + menu.select(inventory.indexOf(item), "accelerate", true) + elseif (column == 11) then + menu.select(2, true) + elseif (column == 14) then + menu.select(0, true) + end + elseif (main == 103) then + input.press("B") + elseif (main == 64 or main == 96 or main == 192) then + if (column == 5) then + menu.select(replaceIdx, true) + elseif (column == 14) then + input.press("A") + elseif (column == 15) then + menu.select(0, true) + else + local idx = 0 + if (poke) then + idx = pokemon.indexOf(poke, altPoke) + end + menu.select(idx, true) + end + else + return false + end + return true +end + +function inventory.isFull() + return memory.raw(0xD345) > 0 +end + +function inventory.use(item, poke, midfight) + if (midfight) then + local battleMenu = memory.value("battle", "menu") + if (battleMenu == 94) then + local rowSelected = memory.value("menu", "row") + if (menu.getCol() == 9) then + if (rowSelected == 0) then + input.press("Down") + else + input.press("A") + end + else + input.press("Left") + end + elseif (battleMenu == 233) then + menu.select(inventory.indexOf(item), "accelerate", true) + elseif (utils.onPokemonSelect(battleMenu)) then + if (poke) then + if (type(poke) == "string") then + poke = pokemon.indexOf(poke) + end + menu.select(poke, true) + else + input.press("A") + end + else + input.press("B") + end + return + end + + local main = memory.value("menu", "main") + local column = menu.getCol() + if (main == 144) then + if (memory.value("battle", "menu") == 95) then + input.press("B") + else + local idx = 0 + if (poke) then + idx = pokemon.indexOf(poke) + end + menu.select(idx, true) + end + elseif (main == 128 or main == 60) then + if (column == 5) then + menu.select(inventory.indexOf(item), "accelerate", true) + elseif (column == 11) then + menu.select(2, true) + elseif (column == 14) then + menu.select(0, true) + else + local index = 0 + if (poke) then + index = pokemon.indexOf(poke) + end + menu.select(index, true) + end + elseif (main == 228) then + if (column == 14 and memory.value("battle", "menu") == 95) then + input.press("B") + end + elseif (main == 103) then + input.press("B") + else + return false + end + return true +end + +return inventory diff --git a/storage/pokemon.lua b/storage/pokemon.lua new file mode 100644 index 0000000..1764253 --- /dev/null +++ b/storage/pokemon.lua @@ -0,0 +1,274 @@ +local pokemon = {} + +local bridge = require "util.bridge" +local input = require "util.input" +local memory = require "util.memory" +local menu = require "util.menu" +local utils = require "util.utils" + +local pokeIDs = { + rhydon = 1, + kangaskhan = 2, + nidoran = 3, + spearow = 5, + voltorb = 6, + nidoking = 7, + ivysaur = 9, + gengar = 14, + nidoranf = 15, + nidoqueen = 16, + cubone = 17, + rhyhorn = 18, + gyarados = 22, + growlithe = 33, + onix = 34, + pidgey = 36, + jinx = 72, + meowth = 77, + pikachu = 84, + zubat = 107, + ekans = 108, + paras = 109, + weedle = 112, + kakuna = 113, + dewgong = 120, + caterpie = 123, + metapod = 124, + hypno = 129, + weezing = 143, + alakazam = 149, + pidgeotto = 150, + pidgeot = 151, + rattata = 165, + raticate = 166, + nidorino = 167, + geodude = 169, + squirtle = 177, + oddish = 185, +} + +local moveList = { + cut = 15, + fly = 19, + sand_attack = 28, + horn_attack = 30, + horn_drill = 32, + tackle = 33, + thrash = 37, + tail_whip = 39, + poison_sting = 40, + leer = 43, + growl = 45, + water_gun = 55, + surf = 57, + ice_beam = 58, + bubblebeam = 61, + strength = 70, + thunderbolt = 85, + earthquake = 89, + dig = 91, + rock_slide = 157, +} + +local data = { + hp = {1, true}, + status = {4}, + moves = {8}, + level = {33}, + max_hp = {34, true}, + + attack = {36, true}, + defense = {38, true}, + speed = {40, true}, + special = {42, true}, +} + +local function getAddress(index) + return 0xD16B + index * 0x2C +end + +local function index(index, offset) + local double + if (not offset) then + offset = 0 + else + local dataTable = data[offset] + offset = dataTable[1] + double = dataTable[2] + end + local address = getAddress(index) + offset + local value = memory.raw(address) + if (double) then + value = value + memory.raw(address + 1) + end + return value +end +pokemon.index = index + +local function indexOf(...) + for ni,name in ipairs(arg) do + local pid = pokeIDs[name] + for i=0,5 do + local atIdx = index(i) + if (atIdx == pid) then + return i + end + end + end + return -1 +end +pokemon.indexOf = indexOf + +-- Table functions + +function pokemon.battleMove(name) + local mid = moveList[name] + for i=1,4 do + if (mid == memory.raw(0xD01B + i)) then + return i + end + end +end + +function pokemon.moveIndex(move, pokemon) + local pokemonIdx + if (pokemon) then + pokemonIdx = indexOf(pokemon) + else + pokemonIdx = 0 + end + local address = getAddress(pokemonIdx) + 7 + local mid = moveList[move] + for i=1,4 do + if (mid == memory.raw(address + i)) then + return i + end + end +end + +function pokemon.info(name, offset) + return index(indexOf(name), offset) +end + +function pokemon.getID(name) + return pokeIDs[name] +end + +function pokemon.getName(id) + for name,pid in pairs(pokeIDs) do + if (pid == id) then + return name + end + end +end + +function pokemon.inParty(...) + for i,name in ipairs(arg) do + if (indexOf(name) ~= -1) then + return name + end + end +end + +function pokemon.forMove(move) + local moveID = moveList[move] + for i=0,5 do + local address = getAddress(i) + for j=8,11 do + if (memory.raw(address + j) == moveID) then + return i + end + end + end + return -1 +end + +function pokemon.hasMove(move) + return pokemon.forMove(move) ~= -1 +end + +function pokemon.updateParty() + local partySize = memory.value("player", "party_size") + if (partySize ~= previousPartySize) then + local poke = pokemon.inParty("oddish", "paras", "spearow", "pidgey", "nidoran", "squirtle") + if (poke) then + bridge.caught(poke) + previousPartySize = partySize + end + end +end + +-- General + +function pokemon.isOpponent(...) + local oid = memory.value("battle", "opponent_id") + for i,name in ipairs(arg) do + if (oid == pokeIDs[name]) then + return name + end + end +end + +function pokemon.isDeployed(...) + for i,name in ipairs(arg) do + if (memory.value("battle", "our_id") == pokeIDs[name]) then + return name + end + end +end + +function pokemon.isEvolving() + return memory.value("menu", "pokemon") == 144 +end + +function pokemon.getExp() + return memory.raw(0xD17A) * 256 + memory.raw(0xD17B) +end + +function pokemon.inRedBar() + local curr_hp, max_hp = index(0, "hp"), index(0, "max_hp") + return curr_hp / max_hp <= 0.2 +end + +function pokemon.use(move) + local main = memory.value("menu", "main") + local pokeName = pokemon.forMove(move) + if (main == 141) then + input.press("A") + elseif (main == 128) then + local column = menu.getCol() + if (column == 11) then + menu.select(1, true) + elseif (column == 10 or column == 12) then + local midx = 0 + local menuSize = memory.value("menu", "size") + if (menuSize == 4) then + if (move == "dig") then + midx = 1 + elseif (move == "surf") then + if (pokemon.inParty("paras")) then + midx = 1 + end + end + elseif (menuSize == 5) then + if (move == "dig") then + midx = 2 + elseif (move == "surf") then + midx = 1 + end + end + menu.select(midx, true) + else + input.press("B") + end + elseif (main == 103) then + menu.select(pokeName, true) + elseif (main == 228) then + input.press("B") + else + return false + end + return true +end + +return pokemon diff --git a/util/bridge.lua b/util/bridge.lua new file mode 100644 index 0000000..461ee81 --- /dev/null +++ b/util/bridge.lua @@ -0,0 +1,132 @@ +local bridge = {} + +local utils = require("util.utils") + +local client = nil +local timeStopped = false + +local function send(prefix, body) + if (client) then + local message = prefix + if (body) then + message = message..","..body + end + client:send(message..'\n') + return true + end +end + +local function readln() + if (client) then + local s, status, partial = client:receive('*l') + if status == "closed" then + client = nil + return nil + end + if s and s ~= '' then + return s + end + end +end + +-- Wrapper functions + +function bridge.init() +end + +function bridge.tweet(message) -- Two of the same tweet in a row will only send one + print('tweet::'..message) + return send("tweet", message) +end + +function bridge.pollForName() + bridge.polling = true + send("poll_name") +end + +function bridge.chat(message, extra) + if (extra) then + print(message.." || "..extra) + else + print(message) + end + return send("msg", message) +end + +function bridge.time(message) + if (not timeStopped) then + return send("time", message) + end +end + +function bridge.stats(message) + return send("stats", message) +end + +function bridge.command(command) + return send("livesplit_command", command) +end + +function bridge.comparisonTime() + return send("livesplit_getcomparisontime") +end + +function bridge.process() + local response = readln() + if (response) then + -- print('>'..response) + if (response:find("name:")) then + return response:gsub("name:", "") + else + + end + end +end + +function bridge.input(key) + send("input", key) +end + +function bridge.caught(name) + if (name) then + send("caught", name) + end +end + +function bridge.hp(curr, max) + send("hp", curr..","..max) +end + +function bridge.liveSplit() + send("start") + timeStopped = false +end + +function bridge.split(encounters, finished) + if (encounters) then + -- database.split(utils.igt(), encounters) + end + if (finished) then + timeStopped = true + end + send("split") +end + +function bridge.encounter() + send("encounter") +end + +function bridge.reset() + send("reset") + timeStopped = false +end + +function bridge.close() + if client then + client:close() + client = nil + end + print("Bridge closed") +end + +return bridge diff --git a/util/input.lua b/util/input.lua new file mode 100644 index 0000000..55f6669 --- /dev/null +++ b/util/input.lua @@ -0,0 +1,119 @@ +local input = {} + +local bridge = require "util.bridge" +local memory = require "util.memory" + +local lastSend +local currentButton, remainingFrames, setForFrame +local debug +local bCancel = true + +local function bridgeButton(btn) + if (btn ~= lastSend) then + lastSend = btn + bridge.input(btn) + end +end + +local function sendButton(button, ab) + local inputTable = {[button] = true} + joypad.set(inputTable) + if (debug) then + gui.text(0, 7, button.." "..remainingFrames) + end + if (ab) then + button = "AB" + end + bridgeButton(button) + setForFrame = button +end + +function input.press(button, frames) + if (setForFrame) then + print("ERR: Reassigning "..setForFrame.." to "..button) + return + end + if (frames == nil or frames > 0) then + if (button == currentButton) then + return + end + if (not frames) then + frames = 1 + end + currentButton = button + remainingFrames = frames + else + remainingFrames = 0 + end + bCancel = button ~= "B" + sendButton(button) +end + +function input.cancel(accept) + if (accept and memory.value("menu", "shop_current") == 20) then + input.press(accept) + else + local button + if (bCancel) then + button = "B" + else + button = "A" + end + remainingFrames = 0 + sendButton(button, true) + bCancel = not bCancel + end +end + +function input.escape() + local inputTable = {Right=true, Down=true} + joypad.set(inputTable) + bridgeButton("Escape") +end + +function input.clear() + currentButton = nil + remainingFrames = -1 +end + +function input.update() + if (currentButton) then + remainingFrames = remainingFrames - 1 + if (remainingFrames >= 0) then + if (remainingFrames > 0) then + sendButton(currentButton) + return true + end + else + currentButton = nil + end + end + setForFrame = nil +end + +function input.advance() + if (not setForFrame) then + bridgeButton("e") + -- print("e") + end +end + +function input.setDebug(enabled) + debug = enabled +end + +function input.test(fn, completes) + while (true) do + if (not input.update()) then + if (fn() and completes) then + break + end + end + emu.frameadvance() + end + if (completes) then + print(completes.." complete!") + end +end + +return input diff --git a/util/memory.lua b/util/memory.lua new file mode 100644 index 0000000..4923692 --- /dev/null +++ b/util/memory.lua @@ -0,0 +1,138 @@ +local memory = {} + +local memoryNames = { + setting = { + text_speed = 0x0D3D, + battle_animation = 0x0D3E, + battle_style = 0x0D3F, + yellow_bitmask = 0x1354, + }, + menu = { + settings_row = 0x0C24, + column = 0x0C25, + row = 0x0C26, + current = 0x1FFC, + main_current = 0x0C27, + input_row = 0x0C2A, + size = 0x0C28, + pokemon = 0x0C51, + shop_current = 0x0C52, + transaction_current = 0x0F8B, + selection = 0x0C30, + selection_mode = 0x0C35, + scroll_offset = 0x0C36, + text_input = 0x04B6, + text_length = 0x0EE9, + main = 0x1FF5, + }, + player = { + name = 0xD158, + name2 = 0xD159, + moving = 0x1528, + x = 0xD362, + y = 0xD361, + facing = 0x152A, + repel = 0x10DB, + party_size = 0xD163, + }, + game = { + map = 0xD35E, + frames = 0xDA45, + battle = 0xD057, + textbox = 0x0FC4, + }, + shop = { + transaction_amount = 0x0F96, + }, + progress = { + trashcans = 0x1773, + }, + pokemon = { + exp1 = 0xD179, + exp2 = 0xD17A, + exp3 = 0xD17B, + }, + battle = { + confused = 0x106B, + turns = 0x1067, + text = 0x1125, + menu = 0x0C50, + accuracy = 0x0D1E, + x_accuracy = 0x1063, + disabled = 0x0CEE, + paralyzed = 0x1018, + + opponent_move = 0x0FEE, + critical = 0x105E, + + opponent_bide = 0x106F, + opponent_id = 0xCFE5, + opponent_level = 0xCFF3, + opponent_type1 = 0xCFEA, + opponent_type2 = 0xCFEB, + + our_id = 0xD014, + our_status = 0xD018, + our_level = 0xD022, + our_type1 = 0xD019, + our_type2 = 0xD01A, + }, +} + +local doubleNames = { + pokemon = { + attack = 0xD17E, + defense = 0xD181, + speed = 0xD183, + special = 0xD185, + }, + battle = { + opponent_hp = 0xCFE6, + opponent_max_hp = 0xCFF4, + opponent_attack = 0xCFF6, + opponent_defense = 0xCFF8, + opponent_speed = 0xCFFA, + opponent_special = 0xCFFC, + + our_hp = 0xD015, + our_max_hp = 0xD023, + our_attack = 0xD025, + our_defense = 0xD027, + our_speed = 0xD029, + our_special = 0xD02B, + }, +} + +local function raw(value) + return mainmemory.readbyte(value) +end +memory.raw = raw + +function memory.string(first, last) + local a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ():;[]abcdefghijklmnopqrstuvwxyz?????????????????????????????????????????-???!.????????*?/.?0123456789" + local str = "" + while first <= last do + local v = raw(first) - 127 + if v < 1 then + return str + end + str = str..string.sub(a, v, v) + first = first + 1 + end + return str +end + +function memory.double(section, key) + local first = doubleNames[section][key] + return raw(first) + raw(first + 1) +end + +function memory.value(section, key) + local memoryAddress = memoryNames[section] + if (key) then + memoryAddress = memoryAddress[key] + end + return raw(memoryAddress) +end + +return memory diff --git a/util/menu.lua b/util/menu.lua new file mode 100644 index 0000000..42fd953 --- /dev/null +++ b/util/menu.lua @@ -0,0 +1,193 @@ +local menu = {} + +local input = require "util.input" +local memory = require "util.memory" + +local YELLOW = GAME_NAME == "yellow" + +local sliding = false + +-- Private functions + +local function getRow(menuType, scrolls) + if (menuType and menuType == "settings") then + menuType = menuType.."_row" + else + menuType = "row" + end + local row = memory.value("menu", menuType) + if (scrolls) then + row = row + memory.value("menu", "scroll_offset") + end + return row +end + +local function setRow(desired, throttle, scrolls, menuType, loop) + local currentRow = getRow(menuType, scrolls) + if (throttle == "accelerate") then + if (sliding) then + throttle = false + else + local dist = math.abs(desired - currentRow) + if (dist < 15) then + throttle = true + else + throttle = false + sliding = true + end + end + else + sliding = false + end + return menu.balance(currentRow, desired, true, loop, throttle) +end + +local function isCurrently(desired, menuType) + if (menuType) then + menuType = menuType.."_current" + else + menuType = "current" + end + return memory.value("menu", menuType) == desired +end +menu.isCurrently = isCurrently + +-- Menu + +function menu.getCol() + return memory.value("menu", "column") +end + +function menu.open(desired, atIndex, menuType) + if (isCurrently(desired, menuType)) then + return true + end + menu.select(atIndex, false, false, menuType) + return false +end + +function menu.select(option, throttle, scrolls, menuType, dontPress, loop) + if (setRow(option, throttle, scrolls, menuType, loop)) then + local delay = 1 + if (throttle) then + delay = 2 + end + if (not dontPress) then + input.press("A", delay) + end + return true + end +end + +function menu.cancel(desired, menuType) + if (not isCurrently(desired, menuType)) then + return true + end + input.press("B") + return false +end + +-- Selections + +function menu.balance(current, desired, inverted, looping, throttle) + if (current == desired) then + sliding = false + return true + end + if (not throttle) then + throttle = 0 + else + throttle = 1 + end + local goUp = current > desired == inverted + if (looping and math.abs(current - desired) > math.floor(looping / 2)) then + goUp = not goUp + end + if (goUp) then + input.press("Up", throttle) + else + input.press("Down", throttle) + end + return false +end + +function menu.sidle(current, desired, looping, throttle) + if (current == desired) then + return true + end + if (not throttle) then + throttle = 0 + else + throttle = 1 + end + local goLeft = current > desired + if (looping and math.abs(current - desired) > math.floor(looping / 2)) then + goLeft = not goLeft + end + if (goLeft) then + input.press("Left", throttle) + else + input.press("Right", throttle) + end + return false +end + +function menu.setCol(desired) + return menu.sidle(menu.getCol(), desired) +end + +-- Options + +function menu.setOption(name, desired) + if (YELLOW) then + local rowFor = { + text_speed = 0, + battle_animation = 1, + battle_style = 2 + } + local currentRow = memory.raw(0x0D3D) + if (menu.balance(currentRow, rowFor[name], true, false, true)) then + input.press("Left") + end + else + local rowFor = { + text_speed = 3, + battle_animation = 8, + battle_style = 13 + } + if (memory.value("setting", name) == desired) then + return true + end + if (setRow(rowFor[name], true, false, "settings")) then + menu.setCol(desired) + end + end + return false +end + +-- Pause menu + +function menu.isOpen() + return memory.value("game", "textbox") == 1 or memory.value("menu", "current") == 24 +end + +function menu.close() + if (memory.value("game", "textbox") == 0 and memory.value("menu", "main") < 8) then + return true + end + input.press("B") +end + +function menu.pause() + if (memory.value("game", "textbox") == 1) then + local main = memory.value("menu", "main") + if (main > 2 and main ~= 64) then + return true + end + input.press("B") + else + input.press("Start", 2) + end +end + +return menu diff --git a/util/paint.lua b/util/paint.lua new file mode 100644 index 0000000..a06f97c --- /dev/null +++ b/util/paint.lua @@ -0,0 +1,68 @@ +local paint = {} + +local memory = require "util.memory" +local player = require "util.player" + +local inventory = require "storage.inventory" +local pokemon = require "storage.pokemon" + +local encounters = 0 + +function elapsedTime() + local secs = memory.raw(0xDA44) + if (secs < 10) then + secs = "0"..secs + end + local mins = memory.raw(0xDA43) + if (mins < 10) then + mins = "0"..mins + end + return memory.raw(0xDA41)..":"..mins..":"..secs +end +paint.elapsedTime = elapsedTime + +function paint.draw(currentMap) + local px, py = player.position() + gui.text(0, 14, currentMap..": "..px.." "..py) + gui.text(0, 0, elapsedTime()) + + if (memory.value("battle", "our_id") > 0) then + local hp = pokemon.index(0, "hp") + local hpStatus + if (hp == 0) then + hpStatus = "DEAD" + elseif (hp <= math.ceil(pokemon.index(0, "max_hp") * 0.2)) then + hpStatus = "RED" + end + if (hpStatus) then + gui.text(120, 7, hpStatus) + end + end + + local nidx = pokemon.indexOf("nidoran", "nidorino", "nidoking") + if (nidx ~= -1) then + local att = pokemon.index(nidx, "attack") + local def = pokemon.index(nidx, "defense") + local spd = pokemon.index(nidx, "speed") + local scl = pokemon.index(nidx, "special") + gui.text(100, 0, att.." "..def.." "..spd.." "..scl) + end + local enc = " encounter" + if (encounters ~= 1) then + enc = enc.."s" + end + gui.text(0, 116, memory.value("battle", "critical")) + gui.text(0, 125, memory.value("player", "repel")) + gui.text(0, 134, encounters..enc) + return true +end + +function paint.wildEncounters(count) + encounters = count +end + +function paint.reset() + encounters = 0 +end + +return paint diff --git a/util/player.lua b/util/player.lua new file mode 100644 index 0000000..a38fae9 --- /dev/null +++ b/util/player.lua @@ -0,0 +1,38 @@ +local player = {} + +local textbox = require "action.textbox" + +local input = require "util.input" +local memory = require "util.memory" + +local facingDirections = {Up=8, Right=1, Left=2, Down=4} + +function player.isFacing(direction) + return memory.value("player", "facing") == facingDirections[direction] +end + +function player.face(direction) + if (player.isFacing(direction)) then + return true + end + if (textbox.handle()) then + input.press(direction, 0) + end +end + +function player.interact(direction) + if (player.face(direction)) then + input.press("A", 2) + return true + end +end + +function player.isMoving() + return memory.value("player", "moving") ~= 0 +end + +function player.position() + return memory.value("player", "x"), memory.value("player", "y") +end + +return player diff --git a/util/settings.lua b/util/settings.lua new file mode 100644 index 0000000..b949719 --- /dev/null +++ b/util/settings.lua @@ -0,0 +1,52 @@ +local settings = {} + +local memory = require "util.memory" +local menu = require "util.menu" + +local YELLOW = GAME_NAME == "yellow" + +local settings_menu +if (YELLOW) then + settings_menu = 93 +else + settings_menu = 94 +end + +local desired = {} +if (YELLOW) then + desired.text_speed = 1 + desired.battle_animation = 128 + desired.battle_style = 64 +else + desired.text_speed = 1 + desired.battle_animation = 10 + -- desired.battle_style = +end + +local function isEnabled(name) + if (YELLOW) then + local matching = { + text_speed = 0xF, + battle_animation = 128, + battle_style = 64 + } + local settingMask = memory.value("setting", "yellow_bitmask") + return bit.band(settingMask, matching[name]) == desired[name] + else + return memory.value("setting", name) == desired[name] + end +end + +function settings.set(...) + for i,name in ipairs(arg) do + if (not isEnabled(name)) then + if (menu.open(settings_menu, 1)) then + menu.setOption(name, desired[name]) + end + return false + end + end + return menu.cancel(settings_menu) +end + +return settings diff --git a/util/utils.lua b/util/utils.lua new file mode 100644 index 0000000..764b802 --- /dev/null +++ b/util/utils.lua @@ -0,0 +1,57 @@ +local utils = {} + +local memory = require "util.memory" + +function utils.dist(x1, y1, x2, y2) + return math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2)) +end + +function utils.ingame() + return memory.raw(0x020E) > 0 +end + +function utils.each(table, func) + for key,val in pairs(table) do + func(key.." = "..tostring(val)..",") + end +end + +function utils.eachi(table, func) + for idx,val in ipairs(table) do + if (val) then + func(idx.." "..val) + else + func(idx) + end + end +end + +function utils.match(needle, haystack) + for i,val in ipairs(haystack) do + if (needle == val) then + return true + end + end +end + +function utils.key(needle, haystack) + for key,val in pairs(haystack) do + if (needle == val) then + return key + end + end +end + +function utils.igt() + local secs = memory.raw(0xDA44) + local mins = memory.raw(0xDA43) + local hours = memory.raw(0xDA41) + return secs + mins * 60 + hours * 3600 +end + + +function utils.onPokemonSelect(battleMenu) + return battleMenu == 8 or battleMenu == 48 or battleMenu == 184 or battleMenu == 224 +end + +return utils