From 1df131ce79ee3527dec2fe807c7d39ad6a7c2a40 Mon Sep 17 00:00:00 2001 From: bouletmarc Date: Sun, 24 May 2015 18:39:11 -0400 Subject: [PATCH] First Commit Files are not even cleaned, its the really first commit, not done yet, beta or even alpha version. --- Readme.md | 45 ++ action/battle.lua | 296 ++++++++ action/shop.lua | 109 +++ action/textbox.lua | 231 ++++++ action/walk.lua | 193 +++++ ai/combat.lua | 419 ++++++++++ ai/control.lua | 300 ++++++++ ai/crystal/strategies.lua | 979 ++++++++++++++++++++++++ ai/strategies.lua | 546 +++++++++++++ data/movelist.lua | 1522 +++++++++++++++++++++++++++++++++++++ data/opponents.lua | 225 ++++++ data/paths.lua | 24 + main.lua | 324 ++++++++ storage/inventory.lua | 273 +++++++ storage/itemlist.lua | 62 ++ storage/pokemon.lua | 325 ++++++++ util/bridge.lua | 148 ++++ util/input.lua | 166 ++++ util/memory.lua | 188 +++++ util/menu.lua | 228 ++++++ util/paint.lua | 56 ++ util/player.lua | 43 ++ util/settings.lua | 215 ++++++ util/utils.lua | 121 +++ 24 files changed, 7038 insertions(+) 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/crystal/strategies.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/itemlist.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/Readme.md b/Readme.md new file mode 100644 index 0000000..13c70f0 --- /dev/null +++ b/Readme.md @@ -0,0 +1,45 @@ +# PokéBot Generation II + +An automated computer program that speedruns Pokémon Cristal (gold/silver not made yet). + +### Run the bot locally + +Running the PokéBot on your own machine is easy. You will need a Windows environment (it runs great in VMs on Mac/Linux too). + +1. First, clone this repository (or download and unzip it) to your computer. + +2. Download the [BizHawk 1.6.1](http://sourceforge.net/projects/bizhawk/files/BizHawk/BizHawk-1.6.1.zip/download) emulator and extract the ZIP file anywhere you like to “install” it. + + **Note:** BizHawk v1.6.1 (Windows only) is the only version known to work. Later versions like v1.7.2a do not seem to work, due to differences with reading bytes from memory. + +3. Run [the BizHawk prerequisites installer](http://sourceforge.net/projects/bizhawk/files/Prerequisites/bizhawk_prereqs_v1.1.zip/download), which should update a C++ distributable needed by BizHawk. + +4. Procure a ROM file of Pokémon Crystal (not yet for gold/silver) english version (japan might not work and you should personally own the game). + +5. Open the ROM file with BizHawk (drag the `.gb` file onto EmuHawk), and Pokémon should start up. Otherwise select Open ROM in EmuHawk. + +6. The colors may look weird. To fix this, go to _GB_ → _Palette Editor_, and then find the `POKEMON ***.pal` file which should be under _Gameboy_ → _Palettes_ in the directory where BizHawk was extracted. + +7. If you want to test the full run, set [`RESET_FOR_TIME` in `main.lua`](https://github.com/kylecoburn/PokeBot/blob/0fd1258ca17f7d74edbac72fa0afc2b5c6d58bb3/main.lua#L3) to `false` instead of `true`. + +8. 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](Seeds.md). 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 + +Michael Jondahl: Combat algorithm, Java bridge for connecting the bot to Twitch chat, LiveSplit, Twitter, etc. + +Marc-Andre Boulet(Bouletmarc): PokeBot GenerationII + +### 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..dbbd194 --- /dev/null +++ b/action/battle.lua @@ -0,0 +1,296 @@ +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" + +-- HELPERS + +local function potionsForHit(potion, curr_hp, max_hp) + if not potion then + return + end + local ours, killAmount = Combat.inKillRange() + if ours then + return Utils.canPotionWith(potion, killAmount, curr_hp, max_hp) + end +end + +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 + if Memory.value("battle", "text") == 3 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 + if battleMenu == 106 then + return true + --elseif battleMenu == 94 then + elseif battleMenu == 186 then + local rowSelected = Memory.value("battle", "menuY") + local columnSelected = Memory.value("battle", "menuX") + if columnSelected == 0 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) + Menu.select(attackIndex, true, false, false, 3) + end +end + +function movePP(name) + local midx = Pokemon.battleMove(name) + if not midx then + return 0 + end + --return Memory.raw(0x102C + midx) + return Memory.raw(0x0634 + midx) +end +Battle.pp = movePP + +-- UTILS + +--[[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.handle() + else + Textbox.handle() + end +end + +function Battle.opponent() + return Pokemon.getName(Memory.value("battle", "opponent_id")) +end + +-- HANDLE + +function Battle.run() + if Memory.double("battle", "opponent_hp") < 1 then + Input.cancel() + --elseif Memory.value("battle", "menu") ~= 94 then + elseif Memory.value("battle", "menu") ~= 186 then + --if Memory.value("menu", "text_length") == 127 then + -- Input.press("B") + --else + Input.press("B", 2) + --Input.cancel() + --end + elseif Memory.value("battle", "menu") == 186 then + --elseif Textbox.handle() then + --local rowSelected = Memory.value("battle", "menuY") + --local columnSelected = Memory.value("battle", "menuX") + --local selected = Memory.value("menu", "selection") + --if selected == 239 then + --if rowSelected == 2 and columnSelected == 2 then + -- Input.press("A", 2) + --else + Input.escape() + --end + end +end + +function Battle.handle() + --if not Control.shouldCatch() then + --if Control.shouldFight() then + -- Battle.fight() + --else + Battle.run() + --end + --end +end + +function Battle.handleWild() + if Memory.value("game", "battle") ~= 1 then + return true + end + Battle.handle() +end + +function Battle.fight(move, skipBuffs) + if move then + if type(move) ~= "number" then + move = Pokemon.battleMove(move) + end + attack(move) + else + move = Combat.bestMove() + if move then + --Battle.accurateAttack = move.accuracy == 100 + attack(move.midx) + --elseif Memory.value("menu", "text_length") == 127 then + -- 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 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, skipBuffs) + --else + Battle.run() + --end + elseif state == 2 then + Battle.fight(moveName, skipBuffs) + end + end + --end +end + +-- SACRIFICE + +--[[function Battle.sacrifice(...) + local sacrifice = Pokemon.getSacrifice(...) + if sacrifice then + Battle.swap(sacrifice) + return true + end + return false +end + +function Battle.redeployNidoking() + if Pokemon.isDeployed("nidoking") then + return false + end + 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 + local __, turns = Combat.bestMove() + if turns == 1 then + if Pokemon.isDeployed("spearow") then + forced = "growl" + else + forced = "sand_attack" + end + end + Battle.automate(forced) + end + return true +end]] + +return Battle diff --git a/action/shop.lua b/action/shop.lua new file mode 100644 index 0000000..9193ccb --- /dev/null +++ b/action/shop.lua @@ -0,0 +1,109 @@ +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" + +--local yellow = YELLOW + +--[[function Shop.transaction(options) + local item, itemMenu, menuIdx, quantityMenu + if options.sell then + menuIdx = 1 + itemMenu = yellow and 28 or 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 = yellow and 122 or 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 + local mainMenu = yellow and 245 or 32 + if Menu.isCurrently(mainMenu, "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..98d9ec0 --- /dev/null +++ b/action/textbox.lua @@ -0,0 +1,231 @@ +local Textbox = {} + +local Input = require "util.input" +local Memory = require "util.memory" +local Menu = require "util.menu" + +--local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ *():;[]ab-?!mf/.," +local alphabet_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ -?1/., " +local alphabet_lower = "abcdefghijklmnopqrstuvw 2 then + Input.press("Left", 2) + end + end + --Get/Set Letter + else + --local LineIndex = math.ceil(lidx/9) + crow = Memory.value("text_inputing", "row") + drow = math.ceil(lidx/9)-1 + if crow < drow then + Input.press("Down", 2) + elseif crow > drow then + Input.press("Up", 2) + elseif crow == drow then + ccol = Memory.value("text_inputing", "column") + dcol = math.fmod(lidx - 1, 9) + if ccol < dcol then + Input.press("Right", 2) + elseif ccol > dcol then + Input.press("Left", 2) + elseif ccol == dcol then + --if Menu.sidle(ccol, dcol, 8, true) then + Input.press("A", 2) + if Memory.value("menu", "text_length") == TableNumber then + TableNumber = TableNumber + 1 + end + end + end + end + end + end + end) + local Waiting = Input.isWaiting() + if TableNumber > StringLenght and not Waiting then + if Memory.value("menu", "text_length") > 0 then + --get column/row + crow = Memory.value("text_inputing", "row") + ccol = Memory.value("text_inputing", "column") + if crow ~= 4 then + Input.press("Start", 2) + elseif ccol < 6 then + Input.press("Start", 2) + elseif crow == 4 and ccol >= 6 then + Input.press("A", 2) + TableNumber = 1 + ActualUpper = 1 + NameTable = {} + return true + end + end + end + --[[else + if Memory.value("menu", "text_length") > 0 then + Input.press("Start") + return true + end + + lidx = nidoIdx + + crow = Memory.value("menu", "input_row") + drow = math.ceil(lidx / 9) + if Menu.balance(crow, drow, true, 6, true) then + ccol = math.floor(Memory.value("menu", "column") / 2) + dcol = math.fmod(lidx - 1, 9) + if Menu.sidle(ccol, dcol, 9, true) then + Input.press("A") + end + end]] + --end + else + --Reset Values + TableNumber = 1 + ActualUpper = 1 + NameTable = {} + + --if Memory.raw(0x10B7) == 3 then + -- Input.press("A", 2) + --elseif randomize then + if randomize then + Input.press("A", math.random(1, 5)) + else + Input.press("A", 2) + --Input.cancel() + end + end +end + +--[[function Textbox.getName() + if nidoName == "a" then + return "ポ" + end + if nidoName == "b" then + return "モ" + end + if nidoName == "m" then + return "♂" + end + if nidoName == "f" then + return "♀" + end + return nidoName +end]] + +--[[function Textbox.setName(index) + if index >= 0 and index < #alphabet then + nidoIdx = index + 1 + nidoName = getLetterAt(index) + end +end]] + +function Textbox.isActive() + if Memory.value("game", "textbox") == 65 then + return true + elseif Memory.value("game", "textbox") == 1 then + return false + end +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..b1d9c53 --- /dev/null +++ b/action/walk.lua @@ -0,0 +1,193 @@ +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 Pokemon = require "storage.pokemon" + + +local path, stepIdx, currentMap, currentMap2 +local pathIdx = 0 +local customIdx = 1 +local customDir = 1 +--local custom_done = false + +-- Private functions + +local function setPath(index, region, region2) + --if PRINT_PATH then + -- print("Path Idx : "..index.." *******") + --end + pathIdx = index + --stepIdx = 2 + stepIdx = 3 + currentMap = region + currentMap2 = region2 + path = Paths[index] +end + +--[[local function setPathCustom(index, region, Idx) + if PRINT_PATH then + print("Path Idx : "..index.." *******") + end + if PRINT_STEP then + print("Step Idx : "..stepIdx) + end + pathIdx = index + stepIdx = Idx + currentMap = region + path = Paths[index] + custom_done = true +end]] + +local function completeStep(region, region2) + --if PRINT_STEP then + -- print("Step Idx : "..stepIdx) + --end + stepIdx = stepIdx + 1 + return Walk.traverse(region, region2) +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, hold) + local px, py = Player.position() + if px == dx and py == dy then + return true + end + Input.press(dir(px, py, dx, dy), 0, hold) +end +Walk.step = step + +-- Table functions + +function Walk.reset() + path = nil + pathIdx = 0 + customIdx = 1 + customDir = 1 + currentMap = nil + currentMap2 = nil + Walk.strategy = nil +end + +function Walk.init() + local region = Memory.value("game", "map") + local region2 = Memory.value("game", "map2") + local px, py = Player.position() + if region == 0 or region2 == 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 and p[2] == region2 then + local origin = p[3] + if tries == 2 or (origin[1] == px and origin[2] == py) then + setPath(i, region, region2) + return tries == 1 + end + end + end + end +end + +function Walk.traverse(region, region2) + local newIndex + if not path or currentMap ~= region or currentMap2 ~= region2 then + Walk.strategy = nil + customIdx = 1 + customDir = 1 + --if PATH_IDX ~= 0 and STEP_IDX ~= 0 and not custom_done then + -- setPathCustom(PATH_IDX, region, STEP_IDX) + -- newIndex = pathIdx + --else + setPath(pathIdx + 1, region, region2) + newIndex = pathIdx + --end + elseif stepIdx > #path then + return + end + local tile = path[stepIdx] + if tile.c then + Control.set(tile) + return completeStep(region, region2) + end + if tile.s then + if Walk.strategy then + Walk.strategy = nil + return completeStep(region, region2) + end + Walk.strategy = tile + elseif step(tile[1], tile[2]) then + Pokemon.updateParty() + return completeStep(region, region2) + end + return newIndex +end + +function Walk.canMove() + --return Memory.value("player", "moving") == 0 and Memory.value("player", "fighting") == 0 + --return Memory.value("player", "moving") == 1 and Memory.value("game", "battle") == 0 + return Memory.value("player", "moving") == 1 +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..a9874b2 --- /dev/null +++ b/ai/combat.lua @@ -0,0 +1,419 @@ +local Combat = {} + +local Movelist = require "data.movelist" +local Opponents = require "data.opponents" +local Utils = require "util.utils" + +local Memory = require "util.memory" +local Pokemon = require "storage.pokemon" + +local damageMultiplier = { -- http://bulbapedia.bulbagarden.net/wiki/Type_chart#Generation_II + 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, steel=0.5, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=1.0, ice=1.0, dragon=1.0, dark=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, steel=2.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=0.5, ice=2.0, dragon=1.0, dark=2.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, steel=0.5, fire=1.0, water=1.0, grass=2.0, electric=0.5, psychic=1.0, ice=1.0, dragon=1.0, dark=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, steel=0.0, fire=1.0, water=1.0, grass=2.0, electric=1.0, psychic=1.0, ice=1.0, dragon=1.0, dark=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, steel=2.0, fire=2.0, water=1.0, grass=0.5, electric=2.0, psychic=1.0, ice=1.0, dragon=1.0, dark=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, steel=0.5, fire=2.0, water=1.0, grass=1.0, electric=1.0, psychic=1.0, ice=2.0, dragon=1.0, dark=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, steel=0.5, fire=0.5, water=1.0, grass=2.0, electric=1.0, psychic=2.0, ice=1.0, dragon=1.0, dark=2.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, steel=1.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=0.0, ice=1.0, dragon=1.0, dark=0.5, }, + steel = {normal=1.0, fighting=1.0, flying=1.0, poison=1.0, ground=1.0, rock=2.0, bug=1.0, ghost=1.0, steel=0.5, fire=0.5, water=0.5, grass=1.0, electric=0.5, psychic=1.0, ice=2.0, dragon=1.0, dark=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, steel=2.0, fire=0.5, water=0.5, grass=2.0, electric=1.0, psychic=1.0, ice=2.0, dragon=0.5, dark=1.0, }, + 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, steel=1.0, fire=2.0, water=0.5, grass=0.5, electric=1.0, psychic=1.0, ice=1.0, dragon=0.5, dark=1.0, }, + 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, steel=0.5, fire=0.5, water=2.0, grass=0.5, electric=1.0, psychic=1.0, ice=1.0, dragon=0.5, dark=1.0, }, + 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, steel=1.0, fire=1.0, water=2.0, grass=0.5, electric=0.5, psychic=1.0, ice=1.0, dragon=0.5, dark=1.0, }, + 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, steel=0.5, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=0.5, ice=1.0, dragon=1.0, dark=0.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, steel=0.5, fire=1.0, water=0.5, grass=2.0, electric=1.0, psychic=1.0, ice=0.5, dragon=2.0, dark=1.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, steel=0.5, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=1.0, ice=1.0, dragon=2.0, dark=1.0, }, + dark = {normal=1.0, fighting=0.5, flying=1.0, poison=1.0, ground=1.0, rock=1.0, bug=1.0, ghost=2.0, steel=1.0, fire=1.0, water=1.0, grass=1.0, electric=1.0, psychic=2.0, ice=1.0, dragon=1.0, dark=0.5, }, +} + +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[9] = "steel" +types[20] = "fire" +types[21] = "water" +types[22] = "grass" +types[23] = "electric" +types[24] = "psychic" +types[25] = "ice" +types[26] = "dragon" +types[27] = "dark" + +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 + if Memory.value("battle", "x_accuracy") == 1 and defender.speed < attacker.speed then + return 9001, 9001 + 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 + attFactor, defFactor = attacker.spec_att, defender.spec_def + 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 move.multiple then + damage = damage * move.multiple + end + 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 = Memory.value("battle", "opponent_move_id") + else + base = Memory.value("battle", "our_move_id") + 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.value("battle", "our_move_pp") + 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 = 9001, 9001 + 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 = 9001, 9001 + 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 + Utils.drawText(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"), + spec_att = Memory.double("battle", "our_special_attack"), + spec_def = Memory.double("battle", "our_special_defense"), + 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 currSpec = ours.spec_att + local booster = toBoost.mp + if (currSpec < 140) == (booster > 1) then + --ours.spec = math.floor(currSpec * booster) + ours.spec_att = 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", "our_special"), + spec_att = Memory.double("battle", "our_special_attack"), + spec_def = Memory.double("battle", "our_special_defense"), + 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(0x116F) > 1 + return Memory.value("battle", "our_status") == 14 --###################### +end +Combat.isSleeping = isSleeping + +local function isConfused() + --return Memory.raw(0x106B) > 0 + return Memory.value("battle", "our_status") == 15 --###################### +end +Combat.isConfused = isConfused + +-- HP + +function Combat.hp() + return Pokemon.index(0, "hp") +end + +function Combat.redHP() + return math.ceil(Pokemon.index(0, "max_hp") * 0.2) +end + +function Combat.inRedBar() + return Combat.hp() <= Combat.redHP() +end + +-- Combat AI + +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 + Utils.drawText(0, 21, ours.speed.." "..enemy.speed) + Utils.drawText(0, 28, turnsToDie.." "..ours.hp.." | "..turnsToKill.." "..enemy.hp) + end + local hpReq = enemyAttack.damage + local isConfused = isConfused() + if isConfused then + hpReq = hpReq + math.floor(ours.hp * 0.2) + end + if ours.hp <= hpReq then + local outsped = enemyAttack.outspeed + if outsped and outsped ~= true then + outsped = Memory.value("battle", "attack_turns") > 0 + end + if outsped 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.975 + 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..b06678d --- /dev/null +++ b/ai/control.lua @@ -0,0 +1,300 @@ +local Control = {} + +local Battle +local Strategies +local Combat = require "ai.combat" +local Bridge = require "util.bridge" +local Memory = require "util.memory" +local Paint = require "util.paint" +local Utils = require "util.utils" +local Inventory = require "storage.inventory" +local Pokemon = require "storage.pokemon" + +local potionInBattle = true +local encounters = 0 + +local canDie, shouldFight, minExp +local shouldCatch, attackIdx +local extraEncounter, maxEncounters +local battleYolo + +Control.areaName = "Unknown" +Control.moonEncounters = nil +Control.getMoonExp = true +Control.yolo = false + +--[[local function withinOneKill(forExp) + return Pokemon.getExp() + 80 > forExp +end]] + +local controlFunctions = { + + a = function(data) + Control.areaName = data.a + return true + end, + + potion = function(data) + if data.b ~= nil then + Control.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, + + -- EXP + + --[[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, + + -- CATCH + + 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,]] + +} + +-- COMBAT + +function Control.battlePotion(enable) + potionInBattle = enable +end + +function Control.canDie(enabled) + if enabled == nil then + return canDie + end + canDie = enabled +end + +local function isNewFight() + if Memory.double("battle", "opponent_hp") == Memory.double("battle", "opponent_max_hp") then + 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 + local minimumCount = 1 + if pokeballs < minimumCount then + 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 + 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) + else + Inventory.use("pokeball", nil, true) + end + return true + end + end + end +end]] + +-- Items + +function Control.canRecover() + return potionInBattle and (not battleYolo or not Control.yolo) +end + +function Control.set(data) + controlFunctions[data.c](data) +end + +function Control.setYolo(enabled) + Control.yolo = enabled +end + +function Control.setPotion(enabled) + potionInBattle = enabled +end + +function Control.encounters() + return encounters +end + +function Control.encounter(battleState) + if battleState > 0 then + local wildBattle = false + if battleState == 1 then + wildBattle = true + end + --[[local isCritical + local battleMenu = Memory.value("battle", "menu") + if battleMenu == 94 then + isCritical = false + Control.missed = false + elseif Memory.double("battle", "our_hp") == 0 then + if Memory.value("battle", "critical") == 1 then + isCritical = true + end + elseif not Control.missed then + local turnMarker = Memory.value("battle", "our_turn") + if turnMarker == 100 or turnMarker == 128 then + local isMiss = Memory.value("battle", "miss") == 1 + if isMiss then + if not Control.ignoreMiss and Battle.accurateAttack and Memory.value("battle", "accuracy") == 7 then + Bridge.chat("gen 1 missed :( (1 in 256 chance)") + end + Control.missed = true + end + end + end + if isCritical ~= nil and isCritical ~= Control.criticaled then + Control.criticaled = isCritical + end]] + if wildBattle then + local opponentHP = Memory.double("battle", "opponent_hp") + if not Control.inBattle then + Control.escaped = false + if opponentHP > 0 then + Control.killedCatch = false + Control.inBattle = true + encounters = encounters + 1 + Paint.wildEncounters(encounters) + Bridge.encounter() + --if Control.moonEncounters then + -- Control.moonEncounters = Control.moonEncounters + 1 + --end + end + else + --if opponentHP == 0 and shouldCatch and not Control.killedCatch then + --if opponentHP == 0 and shouldCatch then + --local gottaCatchEm = {"pidgey", "spearow", "paras", "oddish"} + --local opponent = Battle.opponent() + --for i,catch in ipairs(gottaCatchEm) do + -- if opponent == catch then + -- if not Pokemon.inParty(catch) then + -- Bridge.chat("accidentally killed "..Utils.capitalize(catch).." with a "..(isCritical and "critical" or "high damage range").." :(") + -- Control.killedCatch = true + -- end + -- break + -- end + --end + --end + end + end + elseif Control.inBattle then + if Memory.value("battle", "battle_turns") == 0 then + Control.escaped = true + end + Control.inBattle = false + end +end + +function Control.reset() + canDie = false + oneHits = false + --shouldCatch = nil + --shouldFight = nil + extraEncounter = nil + potionInBattle = true + encounters = 0 + battleYolo = false + maxEncounters = nil + + Control.yolo = false + Control.inBattle = false +end + +function Control.init() + local LowerGameName = string.lower(GAME_NAME) + Battle = require "action.battle" + Strategies = require("ai."..LowerGameName..".strategies") +end + +return Control diff --git a/ai/crystal/strategies.lua b/ai/crystal/strategies.lua new file mode 100644 index 0000000..6add671 --- /dev/null +++ b/ai/crystal/strategies.lua @@ -0,0 +1,979 @@ + +local Combat = require "ai.combat" +local Control = require "ai.control" +local Strategies = require "ai.strategies" + +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 Player = require "util.player" +local Utils = require "util.utils" + +local Inventory = require "storage.inventory" +local Pokemon = require "storage.pokemon" + +local status = Strategies.status + +local strategyFunctions = Strategies.functions + +--local bulbasaurScl +--local UsingSTRATS = "" + +-- TIME CONSTRAINTS + +Strategies.timeRequirements = { + + --[[charmander = function() + return 2.39 + end, + + pidgey = function() + local timeLimit = 7.55 + return timeLimit + end, + + glitch = function() + local timeLimit = 10.15 + if Pokemon.inParty("pidgey") then + timeLimit = timeLimit + 0.67 + end + return timeLimit + end,]] + +} + +-- HELPERS + +--[[local function pidgeyDSum() + local sx, sy = Player.position() + if status.tries == nil then + if status.tries then + status.tries.idx = 1 + status.tries.x, status.tries.y = sx, sy + else + status.tries = 0 + end + end + if status.tries ~= 0 and Control.escaped then + if status.tries[status.tries.idx] == 0 then + tries.idx = tries.idx + 1 + if tries.idx > 3 then + tries = 0 + end + return pidgeyDSum() + end + if status.tries.x ~= sx or status.tries.y ~= sy then + status.tries[status.tries.idx] = status.tries[status.tries.idx] - 1 + status.tries.x, status.tries.y = sx, sy + end + sy = 47 + else + sy = 48 + end + if sx == 8 then + sx = 9 + else + sx = 8 + end + Walk.step(sx, sy) +end + +local function tackleDSum() + local sx, sy = Player.position() + if status.tries == nil then + if status.tries then + status.tries.idx = 1 + status.tries.x, status.tries.y = sx, sy + else + status.tries = 0 + end + end + if status.tries ~= 0 and Control.escaped then + if status.tries[status.tries.idx] == 0 then + tries.idx = tries.idx + 1 + if tries.idx > 3 then + tries = 0 + end + return tackleDSum() + end + if status.tries.x ~= sx or status.tries.y ~= sy then + status.tries[status.tries.idx] = status.tries[status.tries.idx] - 1 + status.tries.x, status.tries.y = sx, sy + end + --sx = 1 + --else + --sx = 2 + end + if sy == 6 then + sy = 8 + else + sy = 6 + end + Walk.step(sx, sy) +end]] + +-- STRATEGIES + +local strategyFunctions = Strategies.functions + +--[[strategyFunctions.grabPCPotion = function() + if Inventory.contains("potion") then + return true + end + Player.interact("Up") +end + +strategyFunctions.checkStrats = function() + UsingSTRATS = STRATS + return true +end]] + +strategyFunctions.talk_mom = function() + if Strategies.initialize() then + status.tempDir = false + end + local Direction = Memory.value("player", "facing") + if Direction == 8 then + Input.press("Down", 2) + else + local CurrentMenu = Memory.value("menu", "current") + if CurrentMenu == 32 and not status.tempDir then + Input.press("A", 2) + elseif CurrentMenu == 32 and status.tempDir then + return true + --elseif CurrentMenu == 79 then --french + elseif CurrentMenu == 110 then --english + local OptionMenu = Memory.value("menu", "option_current") + local DaysRow = Memory.value("menu", "days_row") + if OptionMenu == 0 or OptionMenu == 11 then + Input.press("A", 2) + elseif OptionMenu == 17 then + status.tempDir = true + --set days + if DaysRow < GAME_DAY then + Input.press("Up", 2) + elseif DaysRow > GAME_DAY then + Input.press("Down", 2) + else + Input.press("A", 2) + end + end + end + end +end + +--strategyFunctions.bulbasaurIChooseYou = function() +strategyFunctions.totodileIChooseYou = function() + if Strategies.initialize() then + status.tempDir = false + end + --if Pokemon.inParty("bulbasaur") then + --if Pokemon.inParty("totodile") then + --Bridge.caught("bulbasaur") + -- Bridge.caught("totodile") + -- return true + --end + if Player.face("Up") then + --if Textbox.isActive() then + if Textbox.name(TOTODILE_NAME) then + -- status.tempDir = true + --else + -- if status.tempDir then + -- status.tempDir = false + return true + -- else + -- Input.press("A", 2) + -- end + end + --Textbox.name(BULBASAUR_NAME) + --Textbox.name(TOTODILE_NAME) + end +end + +--[[strategyFunctions.fightCharmander = function() + if status.tries < 9000 and Pokemon.index(0, "level") == 6 then + if status.tries > 200 then + bulbasaurScl = Pokemon.index(0, "special") + if bulbasaurScl < 12 then + if UsingSTRATS == "Pidgey" then + return Strategies.reset("Bad Bulbasaur for pidgey strats - "..bulbasaurScl.." special") + else + UsingSTRATS = "PP" + end + end + status.tries = 9001 + return true + else + status.tries = status.tries + 1 + end + end + if Battle.isActive() and Memory.double("battle", "opponent_hp") > 0 and Strategies.resetTime(Strategies.getTimeRequirement("charmander"), "kill Charmander") then + return true + end + Battle.automate() +end + +strategyFunctions.dodgePalletBoy = function() + return Strategies.dodgeUp(0x0223, 14, 14, 15, 7) +end + +strategyFunctions.shopViridian = function() + if Strategies.initialize() then + status.tempDir = 5 + end + bulbasaurScl = Pokemon.index(0, "special") + if bulbasaurScl == 16 then + if UsingSTRATS == "Pidgey" then + return Strategies.reset("We are already at 16special, we got no chance for Weedle") + else + UsingSTRATS = "PP" + end + end + if UsingSTRATS == "PP" then + status.tempDir = 1 + end + return Shop.transaction{ + buy = {{name="pokeball", index=0, amount=status.tempDir}, {name="paralyze_heal", index=2, amount=1}, {name="burn_heal", index=3, amount=1}} + } +end + +strategyFunctions.dodgeViridianOldMan = function() + if UsingSTRATS == "PP" then + local bidx = Pokemon.indexOf("bulbasaur") + if Memory.raw(0x101E) ~= 73 then + if Pokemon.index(bidx, "level") >= 7 then + return Strategies.reset("We need leech seed for the brock skip glitch") + end + end + end + return Strategies.dodgeUp(0x0273, 18, 6, 17, 9) +end + +strategyFunctions.healTreePotion = function() + if Battle.handleWild() then + if Inventory.contains("potion") then + if Pokemon.info("bulbasaur", "hp") <= 12 then + if Menu.pause() then + Inventory.use("potion", "bulbasaur") + end + else + return true + end + elseif Menu.close() then + return true + end + end +end + +strategyFunctions.catchPidgey = function() + if UsingSTRATS == "PP" then + local px, py = Player.position() + if px < 10 and py < 46 then + px = 10 + elseif px == 10 and py < 46 then + py = 46 + elseif px > 8 and py == 46 then + px = 8 + elseif px == 8 and py == 46 then + return true + end + Walk.step(px, py) + else + if Strategies.initialize() then + status.tempDir = false + status.tries = nil + local bidx = Pokemon.indexOf("bulbasaur") + local scl = Pokemon.index(bidx, "special") + if scl == 16 then + if UsingSTRATS == "" then + UsingSTRATS = "PP" + return true + else + return Strategies.reset("We are already at 16special, we got no chance for Weedle") + end + end + end + if Battle.isActive() then + local isPidgey = Pokemon.isOpponent("pidgey") + status.tries = nil + if isPidgey then + local pidgeyHP = Memory.raw(0xCFE7) + gui.text(100, 134, pidgeyHP.."HP") + if Memory.value("menu", "text_input") == 240 then + Textbox.name(PIDGEY_NAME, true) + elseif Memory.value("battle", "menu") == 95 then + Input.press("A") + elseif status.tempDir then + local pokeballs = Inventory.count("pokeball") + if pokeballs < 2 then + if Memory.value("menu", "selection") == 233 then + Input.press("Right", 2) + elseif Memory.value("menu", "selection") == 239 then + Input.press("A", 2) + end + --Battle.run() + elseif not Control.shouldCatch(3) then + Battle.run() + end + else + local pidgeyHPtable = {17, 16, 15, 13, 10, 8} + if Utils.match(pidgeyHP, pidgeyHPtable) then + status.tempDir = true + elseif not Utils.match(pidgeyHP, pidgeyHPtable) and pidgeyHP > 8 then + Battle.fight("tackle", false, true) --perform tackle + else + Battle.run() + end + end + else + if Memory.value("battle", "menu") == 95 then + Input.cancel() + elseif not Control.shouldCatch() then + if Control.shouldFight() then + Battle.fight() + else + Battle.run() + end + end + end + else + local hasPidgey = Pokemon.inParty("pidgey") + Pokemon.updateParty() + if hasPidgey then + if status.tempDir then + Bridge.caught("pidgey") + status.tempDir = false + end + return true + end + local pokeballs = Inventory.count("pokeball") + if pokeballs < 2 then + if not hasPidgey then + if UsingSTRATS == "Pidgey" then + return Strategies.reset("Ran too low on PokeBalls", pokeballs) + else + UsingSTRATS = "PP" + print("Ran too low on PokeBalls, going to PP-Strats") + return true + end + end + else + local timeLimit = Strategies.getTimeRequirement("pidgey") + local resetMessage = "find a Pidgey" + if Strategies.resetTime(timeLimit, resetMessage, false, true) then + return true + end + pidgeyDSum() + end + end + end +end + +strategyFunctions.grabAntidote = function() + local px, py = Player.position() + if py < 11 then + return true + end + if Inventory.contains("antidote") then + py = 10 + else + Player.interact("Up") + end + Walk.step(px, py) +end + +strategyFunctions.grabForestPotion = function() + if Strategies.initialize() then + status.tempDir = false + end + if Battle.handleWild() then + if not Textbox.isActive() and not status.tempDir then + Input.press("A", 2) + elseif Textbox.isActive() and not status.tempDir then + Input.press("A", 2) + status.tempDir = true + elseif not Textbox.isActive() and status.tempDir then + return true + end + end +end + +strategyFunctions.fightWeedle = function() + if Battle.isTrainer() then + status.canProgress = true + return Strategies.buffTo("growl", 0, 39) --Peform 1x Growl + elseif status.canProgress then + return true + end +end + +strategyFunctions.checkSpec = function() + if Strategies.initialize() then + local WillReset + if not Inventory.contains("potion") then WillReset = true end + if not Inventory.contains("pokeball") then WillReset = true end + if not Inventory.contains("antidote") then WillReset = true end + if not Inventory.contains("paralyze_heal") then WillReset = true end + if not Inventory.contains("burn_heal") then WillReset = true end + if WillReset then + return Strategies.reset("We need 5 items for the brock skip glitch") + end + end + if UsingSTRATS == "" then + local bidx = Pokemon.indexOf("bulbasaur") + local scl = Pokemon.index(bidx, "special") + local hasPidgey = Pokemon.inParty("pidgey") + if hasPidgey then + if scl == 16 then + UsingSTRATS = "Pidgey" + print("Performing Pidgey Strats") + return true + else + UsingSTRATS = "PP" + end + else + UsingSTRATS = "PP" + end + elseif UsingSTRATS == "Pidgey" then + local bidx = Pokemon.indexOf("bulbasaur") + local scl = Pokemon.index(bidx, "special") + if scl == 16 then + print("Performing Pidgey Strats") + return true + else + return Strategies.reset("We need 16special on Bulbasaur for the brock skip glitch") + end + elseif UsingSTRATS == "PP" then + local bidx = Pokemon.indexOf("bulbasaur") + if Memory.raw(0x101E) ~= 73 then + if Pokemon.index(bidx, "level") >= 7 then + return Strategies.reset("We need leech seed for the brock skip glitch") + end + end + print("Performing PP Strats") + return true + end +end + +strategyFunctions.equipForGlitch = function() + if UsingSTRATS == "Pidgey" then + return true + else + if Strategies.initialize() then + status.tempDir = false + end + local TacklePP = Memory.raw(0x102D) + local GrowlPP = Memory.raw(0x102E) + local bidx = Pokemon.indexOf("bulbasaur") + --in Battle + if Battle.isActive() then + status.tries = nil + if Memory.value("battle", "menu") == 95 then + Input.press("A") + else + TacklePP = Memory.raw(0x102D) + if not status.tempDir then + if GrowlPP > 36 then + Battle.fight("growl", false, true) --perform 3x Growl + else + if TacklePP > 16 then --perform tackle until 16pp + Battle.fight() + elseif TacklePP == 16 then + if Memory.raw(0x101E) ~= 73 then + return Strategies.reset("We need leech seed for the brock skip glitch") + else + status.tempDir = true + end + end + end + end + if status.tempDir then + if Pokemon.battleMove("tackle") == 1 then + Battle.swapMove(1, 3) + elseif Pokemon.battleMove("tackle") == 3 then + Battle.swapMove(3, 2) + elseif Pokemon.battleMove("tackle") == 2 then + if Memory.value("battle", "menu") == 106 then + Input.press("B") + else + if Pokemon.index(bidx, "level") ~= 8 then + status.tempDir = false + return Strategies.reset("Can't be Lvl"..Pokemon.index(bidx, "level").." for the brock skip glitch with the PP Strats") + else + Battle.run() + end + end + end + end + end + else --out battle + TacklePP = Memory.raw(0x102D) + if not status.tempDir then + if TacklePP == 16 then + if Pokemon.index(bidx, "level") ~= 8 then + return Strategies.reset("Can't be Lvl"..Pokemon.index(bidx, "level").." for the brock skip glitch with the PP Strats") + end + if Memory.raw(0x101E) ~= 73 then + return Strategies.reset("We need leech seed for the brock skip glitch") + end + status.tempDir = true + elseif TacklePP < 16 then + return Strategies.reset("Ran too low on Tackle for the PP Strats "..TacklePP.."PP available") + end + end + if status.tempDir then + if Pokemon.battleMove("tackle") == 2 then + status.tempDir = false + return true + end + end + + local timeLimit = Strategies.getTimeRequirement("glitch") + local resetMessage = "perform enough Tackle for the PP Strats glitch" + if Strategies.resetTime(timeLimit, resetMessage) then + return true + end + tackleDSum() + end + end +end + +strategyFunctions.checkInventory = function() + if Strategies.initialize() then + local WillReset + if not Inventory.contains("potion") then WillReset = true end + if not Inventory.contains("pokeball") then WillReset = true end + if not Inventory.contains("antidote") then WillReset = true end + if not Inventory.contains("paralyze_heal") then WillReset = true end + if not Inventory.contains("burn_heal") then WillReset = true end + if WillReset then + return Strategies.reset("We need 5 items for the brock skip glitch") + else + return true + end + end +end + +strategyFunctions.checkForPidgey = function() + if UsingSTRATS == "Pidgey" then + return true + else + if Strategies.initialize() then + status.tempDir = false + local hasPidgey = Pokemon.inParty("pidgey") + if not hasPidgey then + return true + end + end + local map = Memory.value("game", "map") + local px, py = Player.position() + if not status.tempDir then --go to pc to depose + if map == 2 then + if px > 13 then + px = 13 + else + if py > 25 then + py = 25 + end + end + elseif map == 58 then + if py > 5 then + py = 5 + else + if px < 13 then + px = 13 + else + if py > 4 then + py = 4 + else -- deposit pidgey + if Memory.value("player", "party_size") == 1 then + if Menu.close() then + status.tempDir = 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 + local menuColumn = Menu.getCol() + if menuColumn == 10 then + Input.press("A") + elseif menuColumn == 5 then + Menu.select(1) -- select pidgey + else + Menu.select(1) -- select deposit box + end + else + Input.press("A") + end + end + end + end + end + end + end + else --get back to the spot + if map == 58 then + if px > 4 then + px = 4 + else + if py < 8 then + py = 8 + end + end + elseif map == 2 then + if px < 18 then + px = 18 + else + return true + end + end + end + Walk.step(px, py, true) + end +end + +strategyFunctions.prepareSave = function() + local main = Memory.value("menu", "main") + local row = Memory.value("menu", "row") + if main == 128 then + if row == 4 then + Input.press("B") + else + Input.press("Down") + end + else + if row == 4 then + return true + end + Input.press("Start") + end +end + +strategyFunctions.performSkip = function() + local current = Memory.value("menu", "current") + local selection = Memory.value("menu", "selection") + local skip = Memory.value("menu", "pokemon") + if current == 15 then + if Memory.value("menu", "pokemon") ~= 0 then + Input.press("Start", 0) + else + Player.disinteract("left") + end + else + if selection == 115 then + Input.press("A") + elseif selection == 65 then + if skip == 207 then + return true + else + Input.press("A") + end + else + Input.press("Start", 0) + end + end +end + +strategyFunctions.performReset = function() + local skip = Memory.value("menu", "pokemon") + if skip == 197 or skip == 204 then + return Strategies.SkipReset() + else + Input.press("A") + end +end + +strategyFunctions.openPokemonMenu = function() + if UsingSTRATS == "Pidgey" then + if Textbox.isActive() then + return true + else + Input.press("Start") + end + else + if Strategies.initialize() then + status.tempDir = false + end + local main = Memory.value("menu", "main") + local row = Memory.value("menu", "row") + if main == 128 then + if status.tempDir then + Input.press("B") + else + if row == 0 then + Input.press("Down") + else + Input.press("A") + end + end + elseif main == 103 then + status.tempDir = true + Input.press("B") + elseif main == 8 then + status.tempDir = false + return true + else + if status.tempDir then + Input.press("B") + else + Input.press("Start") + end + end + end +end + +strategyFunctions.speakToGlithGuy = function() + local main = Memory.value("menu", "main") + if not Textbox.isActive() then + Player.interact("Left") + else + if main == 167 then + return true + else + Input.press("A") + end + end +end + +strategyFunctions.leaveGlitchGuy = function() + local map = Memory.value("game", "map") + local px, py = Player.position() + if map == 2 then --Pewter City + if py == 16 then + px = 40 + end + elseif map == 14 then --Route3 + if px < 17 then + px = 17 + else + if py > 7 then + py = 7 + else + if px < 60 then + px = 60 + else + if py > -1 then + py = -1 + end + end + end + end + elseif map == 15 then --Center Route + if px < 90 then + px = 90 + end + elseif map == 3 then --Cerulean City + if px < 8 then + px = 8 + else + if py < 36 then + py = 36 + end + end + elseif map == 16 then --Out of Cerulean + if py < 36 then + py = 36 + end + elseif map == 10 then --Saffron City + if py < 29 then + py = 29 + else + if px < 9 then + px = 9 + end + end + elseif map == 182 then --Saffron City Poke Center + return true + end + Walk.step(px, py, true) +end + +strategyFunctions.checkPidgeyHP = function() + if UsingSTRATS == "PP" then + return true + else + if Strategies.initialize() then + status.tempDir = false + status.canProgress = true + end + local pidx = Pokemon.indexOf("pidgey") + local hp = Pokemon.index(pidx, "hp") + if hp ~= 16 and status.canProgress then + return true + else + status.canProgress = false + local px, py = Player.position() + if px < 13 then + Walk.step(13, py) + else + if Memory.value("player", "party_size") == 2 then --Depose Pidgey + 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 + local menuColumn = Menu.getCol() + if menuColumn == 10 then + Input.press("A") + elseif menuColumn == 5 then + Menu.select(1) --select pidgey + else + Menu.select(1) --select deposit box + end + else + Input.press("A") + end + end + else + if not status.tempDir then --swap box for saving + if Memory.value("menu", "shop_current") == 20 or Memory.value("menu", "shop_current") == 73 then + if Memory.value("menu", "column") == 1 then + if Memory.value("menu", "row") ~= 3 then + Input.press("Down") + else + Input.press("A", 2) + end + elseif Memory.value("menu", "column") == 15 then --select yes to save + Input.press("A", 2) + elseif Memory.value("menu", "column") == 12 then --select box + if Memory.value("menu", "row") ~= 1 then + --Menu.select(1) --select box2 + Input.press("Down") + else + Input.press("A") + status.tempDir = true + end + end + else + Input.press("A") + end + else --Resetting + if Memory.value("menu", "selection") == 65 then + return Strategies.SkipReset() + else + Input.press("A") + end + end + end + end + end + end +end + +strategyFunctions.walkBack = function() + local px, py = Player.position() + if px > 3 then + Walk.step(3, py) + else + return true + end +end + +strategyFunctions.getAbra = function() + local party_size = Memory.value("player", "party_size") + local text_input = Memory.value("menu", "text_input") + local textbox_active = Memory.value("game", "textbox") + local hasAbra = Pokemon.inParty("abra") + if textbox_active == 1 then + if party_size == 1 then + Input.press("A") + else + if text_input == 240 then + Textbox.name(ABRA_NAME, true) + else + Input.press("A") + end + end + else + if hasAbra then + return true + else + Input.press("A") + end + end +end + +strategyFunctions.performTeleportGlitch = function() + if Strategies.initialize() then + status.tempDir = false + end + local map = Memory.value("game", "map") + local main = Memory.value("menu", "main") + local px, py = Player.position() + if not status.tempDir then + if px == 5 then + status.tempDir = true + Walk.step(4, py, true) + end + else + if main ~= 128 then + Input.press("Start", 0) + else + status.tempDir = false + return true + end + end +end + +strategyFunctions.fightGymGuy = function() + local abraHP = Pokemon.info("abra", "hp") + if abraHP == 0 then + return true + end + if Battle.isTrainer() then + status.canProgress = true + return Strategies.buffTo("teleport", 0, 1) --Perform teleport + end +end + +strategyFunctions.closingAutomation = function() + if Memory.value("menu", "shop_current") == 0 then + return Strategies.reset("We need need to encounter a MissingNo, Not a Trainer") + else + if Memory.value("menu", "main") == 123 then + Input.press("B") + elseif Memory.value("menu", "main") == 32 then + return true + end + end +end + +strategyFunctions.battleMissingNo = function() + if Battle.isActive() then + Battle.run() + else + if Textbox.isActive() then + Input.press("A") + else + local px, py = Player.position() + if py < 1 then + py = 1 + else + return true + end + Walk.step(px, py, true) + end + end +end]] + +-- PROCESS + +function Strategies.completeGameStrategy() + status = Strategies.status +end + +function Strategies.resetGame() + --maxEtherSkip = false + status = Strategies.status + stats = Strategies.stats +end + +return Strategies diff --git a/ai/strategies.lua b/ai/strategies.lua new file mode 100644 index 0000000..04069fd --- /dev/null +++ b/ai/strategies.lua @@ -0,0 +1,546 @@ +local Strategies = {} + +local Combat = require "ai.combat" +local Control = require "ai.control" + +local Battle = require "action.battle" +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 Player = require "util.player" +local Utils = require "util.utils" + +local Inventory = require "storage.inventory" +local Pokemon = require "storage.pokemon" + +--local yellow = YELLOW +local splitNumber, splitTime = 0, 0 +local resetting, itemPos1, itemPos2, itemNumber + +local status = {tries = 0, canProgress = nil, initialized = false, tempDir = false} +Strategies.status = status + +local strategyFunctions + +-- RISK/RESET + +function Strategies.getTimeRequirement(name) + return Strategies.timeRequirements[name]() +end + +-- RISK/RESET + +function Strategies.hardReset(message, extra, wait) + resetting = true + if Strategies.seed then + if extra then + extra = extra.." | "..Strategies.seed + else + extra = Strategies.seed + end + end + --Reset values + --RUNNING4CONTINUE = false + --RUNNING4NEWGAME = true + + Bridge.chat(message, extra) + if wait and INTERNAL and not STREAMING_MODE then + strategyFunctions.wait() + else + client.reboot_core() + end + return true +end + +function Strategies.reset(reason, extra, wait) + local time = Utils.elapsedTime() + local resetMessage = "reset" + if time then + resetMessage = resetMessage.." after "..time + end + resetMessage = resetMessage.." at "..Control.areaName + local separator + if Strategies.deepRun and not Control.yolo then + separator = " BibleThump" + else + separator = ":" + end + resetMessage = resetMessage..separator.." "..reason + if status.tweeted then + Strategies.tweetProgress(resetMessage) + end + return Strategies.hardReset(resetMessage, extra, wait) +end + +-- RESET TO CONTINUE + +--[[function Strategies.SkipReset(message) + RUNNING4CONTINUE = true + EXTERNALDONE = false + client.reboot_core() + return true +end]] + +function Strategies.death(extra) + local reason = "Died" + --[[local reason + if Control.missed then + reason = "Missed" + elseif Control.criticaled then + reason = "Critical'd" + elseif Control.yolo then + reason = "Yolo strats" + else + reason = "Died" + end]] + return Strategies.reset(reason, extra) +end + +function Strategies.overMinute(min) + if type(min) == "string" then + min = Strategies.getTimeRequirement(min) + end + return Utils.igt() > (min * 60) +end + +function Strategies.resetTime(timeLimit, reason, once) + if Strategies.overMinute(timeLimit) then + reason = "Took too long to "..reason + if RESET_FOR_TIME then + return Strategies.reset(reason) + end + if once then + print(reason.." "..Utils.elapsedTime()) + end + end +end + +-- HELPERS + +function Strategies.initialize() + if not status.initialized then + status.initialized = true + return true + end +end + +--[[function Strategies.buffTo(buff, defLevel, usePPAmount, oneHit) + if Battle.isActive() then + status.canProgress = true + local forced + if not usePPAmount then + if defLevel and Memory.double("battle", "opponent_defense") > defLevel then + forced = buff + end + else + local AvailablePP = Battle.pp(buff) + if not oneHit then + if AvailablePP > usePPAmount then + forced = buff + end + else + if Strategies.initialize() then + status.tempDir = AvailablePP + end + if AvailablePP > status.tempDir-1 then + forced = buff + end + end + end + Battle.automate(forced, true) + elseif status.canProgress then + return true + else + Battle.automate() + end +end + +function Strategies.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]] + +-- GENERALIZED STRATEGIES + +Strategies.functions = { + + split = function(data) + Bridge.split(data and data.finished) + if not INTERNAL then + splitNumber = splitNumber + 1 + + local timeDiff + splitTime, timeDiff = Utils.timeSince(splitTime) + if timeDiff then + print(splitNumber..". "..Control.areaName..": "..Utils.elapsedTime().." ("..timeDiff..")") + end + end + return true + end, + + interact = function(data) + if Battle.handleWild() then + if Battle.isActive() then + return true + end + if Textbox.isActive() then + if status.tries > 0 then + return true + end + status.tries = status.tries - 1 + Input.cancel() + elseif Player.interact(data.dir) then + status.tries = status.tries + 1 + end + end + end, + + confirm = function(data) + if Battle.handleWild() then + if Textbox.isActive() then + status.tries = status.tries + 1 + Input.cancel(data.type or "A") + else + if status.tries > 0 then + return true + end + Player.interact(data.dir) + end + end + end, + + --[[teleport = function(data) + if Memory.value("game", "map") == data.map then + return true + end + if not Pokemon.use("teleport") then + Menu.pause() + end + end,]] + + speak = function() + if Strategies.initialize() then + status.tempDir = false + end + if Textbox.isActive() then + Input.press("A", 2) + status.tempDir = true + else + if status.tempDir then + status.tempDir = false + return true + else + Input.press("A", 2) + end + end + end, + + --[[deposeAll = function(data) + if Memory.value("player", "party_size") == 1 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 + local menuColumn = Menu.getCol() + if menuColumn == 10 then + Input.press("A") + elseif menuColumn == 5 then + local depositIndex = 0 + if Pokemon.indexOf(data.keep) == 0 then + depositIndex = 1 + end + Menu.select(depositIndex) + else + Menu.select(1) + end + else + Input.press("A") + end + end + end + end, + + swapItem = function(data) + if Strategies.initialize() then + status.tempDir = false + end + if not data.pos2 then -- 1x position mode + item name + itemPos1 = Inventory.indexOf(data.item) + itemPos2 = data.pos1-1 + else -- 2x position mode + itemPos1 = data.pos1-1 + itemPos2 = data.pos2-1 + end + local main = Memory.value("menu", "main") + local selection = Memory.value("menu", "selection_mode") + if status.tempDir and selection == 0 then + return true + end + if main == 128 then + if Menu.getCol() ~= 5 then + Menu.select(2, true) + else + if selection == 0 then + if Menu.select(itemPos1, 1, true, nil, true) then + Input.press("Select") + end + else + if Menu.select(itemPos2, 1, true, nil, true) then + Input.press("Select") + status.tempDir = true + end + end + end + else + Menu.pause() + end + end, + + tossItem = function(data) + if Strategies.initialize() then + status.canProgress = false + if not data.item then + itemPos1 = data.pos-1 + status.tempDir = Memory.raw(0x131E+itemPos1*2+1) + else + status.tempDir = Inventory.count(data.item) + end + if data.amount then + itemNumber = data.amount + else + itemNumber = status.tempDir + end + end + if not data.pos then --tossing by item name + itemPos1 = Inventory.indexOf(data.item) + else --tossing by item position + itemPos1 = data.pos-1 + end + local main = Memory.value("menu", "main") + if main == 60 and Memory.value("menu", "shop_current") == 20 and status.canProgress then + return true + end + if main == 128 or Menu.getCol() == 14 and main ~= 209 then + if Menu.getCol() ~= 5 and Menu.getCol() ~= 14 then + Menu.select(2, true) + else + if Memory.value("menu", "text_input") == 146 then + if Memory.value("menu", "row") == 0 then + Menu.select(1, true) + else + if Memory.value("menu", "shop_current") ~= 248 then + Input.press("A") + else + local currAmount = Memory.value("shop", "transaction_amount") + if Menu.balance(currAmount, itemNumber, false, 99, true) then + Input.press("A") + status.canProgress = true + end + end + end + else + if Menu.select(itemPos1, 1, true, nil, true) then + Input.press("A") + end + end + end + elseif main == 209 then + Input.press("A") + else + Menu.pause() + end + end, + + tossTM = function(data) + if Strategies.initialize() then + status.canProgress = false + status.tries = 0 + if data.amount then + itemNumber = data.amount + else + itemPos1 = data.pos-1 + status.tempDir = Memory.raw(0x131E+itemPos1*2+1) + itemNumber = status.tempDir + end + end + if not data.pos then --tossing by item name + itemPos1 = Inventory.indexOf(data.item) + else --tossing by item position + itemPos1 = data.pos-1 + end + local main = Memory.value("menu", "main") + if main == 60 and Memory.value("menu", "shop_current") == 20 and status.canProgress then + return true + end + if status.tries == 0 then + if main == 128 or Menu.getCol() == 14 and main ~= 209 then + if Menu.getCol() ~= 5 and Menu.getCol() ~= 14 then + Menu.select(2, true) + else + if Memory.value("menu", "text_input") == 146 then + if Memory.value("menu", "row") == 0 then + Menu.select(1, true, false, nil, true) + else + if main == 128 then + Input.press("A") + else + status.tries = 1 + end + end + else + if Menu.select(itemPos1, 1, true, nil, true) then + Input.press("A") + end + end + end + end + else + if main == 128 or Menu.getCol() == 14 and main ~= 209 then + local currAmount = Memory.value("shop", "transaction_amount") + if Menu.balance(currAmount, itemNumber, false, 99, true) then + Input.press("A") + status.canProgress = true + end + else + Input.press("A") + end + end + end,]] + + openMenu = function() + if Textbox.isActive() then + return true + else + Input.press("Start", 2) + end + end, + + closeMenu = function() + if not Textbox.isActive() then + return true + else + Input.press("B") + end + end, + + allowDeath = function(data) + Control.canDie(data.on) + return true + end, + + --[[champion = function() + if status.canProgress then + if status.tries > 1500 then + return Strategies.hardReset("Beat the game in "..status.canProgress.." !") + end + if status.tries == 0 then + Bridge.tweet("Beat Pokemon "..GAME_NAME.." in "..status.canProgress.."!") + if Strategies.seed then + print(Utils.frames().." frames, with seed "..Strategies.seed) + print("Please save this seed number to share, if you would like proof of your run!") + end + end + status.tries = status.tries + 1 + elseif Memory.value("menu", "shop_current") == 252 then + Strategies.functions.split({finished=true}) + status.canProgress = Utils.elapsedTime() + else + Input.cancel() + end + end]] +} + +strategyFunctions = Strategies.functions + +function Strategies.execute(data) + if strategyFunctions[data.s](data) then + status = {tries=0} + Strategies.status = status + Strategies.completeGameStrategy() + -- print(data.s) + if resetting then + return nil + end + return true + end + return false +end + +function Strategies.init(midGame) + if not STREAMING_MODE then + splitTime = Utils.timeSince(0) + end + if midGame then + Combat.factorPP(true) + end +end + +function Strategies.softReset() + status = {tries=0} + Strategies.status = status + stats = {} + Strategies.stats = stats + Strategies.updates = {} + + splitNumber, splitTime = 0, 0 + resetting = nil + Strategies.deepRun = false + Strategies.resetGame() +end + +return Strategies diff --git a/data/movelist.lua b/data/movelist.lua new file mode 100644 index 0000000..74df8a2 --- /dev/null +++ b/data/movelist.lua @@ -0,0 +1,1522 @@ +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, + multiple = 2, + }, + { + 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 = "turns", + power = 15, + max_pp = 20, + accuracy = 85, + multiple = 2, + }, + { + 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..69c4157 --- /dev/null +++ b/data/opponents.lua @@ -0,0 +1,225 @@ +local Opponents = { + + --[[RivalGyarados = { + type1 = "water", + type2 = "flying", + def = 71, + id = 22, + spec = 87, + hp = 126, + speed = 72, + level = 38, + att = 106, + moves = { + { + name = "Hydro-Pump", + accuracy = 80, + power = 120, + id = 56, + special = true, + max_pp = 5, + move_type = "water", + } + }, + boost = { + stat = "spec", + mp = 1.5 + } + }, + + HypnoHeadbutt = { + type1 = "psychic", + type2 = "psychic", + def = 58, + id = 129, + spec = 88, + hp = 107, + speed = 56, + level = 34, + att = 60, + moves = { + { + name = "Headbutt", + accuracy = 100, + power = 70, + id = 29, + special = false, + max_pp = 15, + move_type = "normal", + } + } + }, + + HypnoConfusion = { + type1 = "psychic", + type2 = "psychic", + def = 58, + id = 129, + spec = 88, + hp = 107, + speed = 56, + level = 34, + att = 60, + moves = { + { + name = "Confusion", + accuracy = 100, + 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 = { + { + name = "Self-Destruct", + accuracy = 100, + 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 = { + { + name = "Stomp", + move_type = "normal", + accuracy = 100, + 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 = { + { + name = "Aurora-Beam", + accuracy = 100, + 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 = { + { + name = "Hydro-Pump", + accuracy = 80, + 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 = { + { + name = "Wing-Attack", + accuracy = 100, + 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 = { + { + name = "Sky-Attack", + accuracy = 90, + power = 140, + id = 143, + special = false, + max_pp = 5, + move_type = "flying", + } + } + },]] + +} + +return Opponents diff --git a/data/paths.lua b/data/paths.lua new file mode 100644 index 0000000..a96f8de --- /dev/null +++ b/data/paths.lua @@ -0,0 +1,24 @@ +local paths = { + -- Bed room + {7, 24, {3,3}, {7,3}, {7,0}}, + -- Mom house + {6, 24, {9,1}, {9,3}, {7,3}, {s="talk_mom"}, {7,3}, {8,3}, {8,7}, {7,7}, {7,8}}, + -- Go to lab + {4, 24, {13,6}, {6,6}, {6,3}}, + -- Choose your character! + {5, 24, {4,4}, {c="a",a="Prof Orm Lab"}, {s="speak"}, {s="speak"}, {s="speak"}, {s="speak"}, {4,4}, {7,4}, {s="totodileIChooseYou"}, {5,3}, {5,8}, {s="speak"}, {5,12}}, + +-- 1: GO TAKE THE EGG + + -- Go to route 29 + {4, 24, {6,4}, {6,7}, {2,7}, {2,9}, {-1,9}}, + -- Route 29 + {3, 24, {59,9}, {44,9}, {44,15}, {39,15}, {39,16}, {31,16}, {31,10} , {36,10}, {36,7}, {23,7}, {23,5}, {21,5}, {21,3}, {16,3}, {16,7}, {14,7}, {14,8}, {4,8}, {4,7}, {-1,7}}, + -- CherryGrove City + {3, 26, {39,7}, {28,7}, {28,5}, {17,5}, {17,-1}}, + -- Route 30 + {1, 26, {7,53}, {7,48}, {12,48}, {12,35}, {9,35}, {s="interact",dir="Left"}, {9,35}, {9,31}}, + +} + +return paths diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..4119dc1 --- /dev/null +++ b/main.lua @@ -0,0 +1,324 @@ +--################################################## +--############# ############ +--############# SETTING ############ +--############# ############ +--################################################## + +--Reset Settings +RESET_FOR_TIME = false -- Set to false if you just want to see the bot finish a run without reset for time +RESET_FOR_ENCOUNTERS = false -- Set to false if you just want to see the bot finish a run without reset for encounters + +--Game Settings +GAME_NAME = "Crystal" -- Set to Gold/Silver or Crystal +GAME_RUN = "" -- Set to "" or "" for the run you want +GAME_HOURS = 17 -- Set the internal game hour (0-23h) +GAME_MINUTES = 35 -- Set the internal game minutes (0-59min) +GAME_DAY = 3 -- Set the internal game day (0-6 // sunday-saturday) +GAME_GENDER = 1 -- Set the player gender (1-2 // boy-girl) + +GAME_TEXT_SPEED = 128 -- Set the Text Speed (247-249 // slow-fast) +GAME_BATTLE_ANIMATION = 133 -- Set the battle animation (141-142 // no-yes) +GAME_BATTLE_STYLE = 132 -- Set the battle style (130-131 // choice-set) +GAME_SOUND_STYLE = 145 -- Set the sound style (132 or 141 // stereo-mono) +GAME_PRINT_STYLE = 127 -- Set the print style (64=normal // 96=dark // 127=dark+ // 0=clear+ // 32=clear) +GAME_ACCOUNT_STYLE = 0 -- Set the account style (0-1 // no-yes) +GAME_WINDOWS_STYLE = 3 -- Set the windows style (0-7) + +--GAME_TEXT_SPEED = 249 -- Set the Text Speed (247-249 // slow-fast) +--GAME_BATTLE_ANIMATION = 141 -- Set the battle animation (141-142 // no-yes) +--GAME_BATTLE_STYLE = 131 -- Set the battle style (130-131 // choice-set) +--GAME_SOUND_STYLE = 132 -- Set the sound style (132 or 141 // stereo-mono) +--GAME_PRINT_STYLE = 127 -- Set the print style (64=normal // 96=dark // 127=dark+ // 0=clear+ // 32=clear) +--GAME_ACCOUNT_STYLE = 0 -- Set the account style (0-1 // no-yes) +--GAME_WINDOWS_STYLE = 3 -- Set the windows style (0-7) + +--Connection Settings +INTERNAL = false -- Allow connection with LiveSplit ? +STREAMING_MODE = false -- Enable Streaming mode + +--Script Settings +CUSTOM_SEED = nil -- Set to a known seed to replay it, or leave nil for random runs +PAINT_ON = true -- Display contextual information while the bot runs + +--Names Settings +PLAYER_NAME = "TeSt" -- Player name +RIVAL_NAME = "URRival" -- Rival name +TOTODILE_NAME = "ToTo" -- Set Totodile name + +--Advanced area Settings +PATH_IDX = 0 -- Start the bot to the specified path idx +STEP_IDX = 0 -- Start the bot to the specified step idx +PRINT_PATH = false -- Print the current path in the console. +PRINT_STEP = false -- Print the current step in the console. + +--NAMES SETTINGS TIPS : +-- - Can use up to 7 letter ingame +-- - Upper and Lower case allowed +-- - Specials Characters : <=Special X, {=Pk, }=Mn + +--##################################################################################### +--##################################################################################### +--########### ############### +--########### PLEASE DON'T EDIT ANYTHING BELLOW, IT'S AT YOUR RISK ############### +--########### START CODE (hard hats on) ############### +--########### ############### +--##################################################################################### +--##################################################################################### + +-- SET VALUES + +local VERSION = "1.0" + +--YELLOW = memory.getcurrentmemorydomainsize() > 30000 + +local START_WAIT = 99 +local hasAlreadyStartedPlaying = false +local oldSeconds +local running = true +local lastHP +--FirstSpawnDone = false +--FirstSpawnDone2 = false + +--RUNNING4CONTINUE = false --used to continue a game +--RUNNING4NEWGAME = true --used to make a new game (remove last save also) +--EXTERNALDONE = false --used when the above settings are done externally +--local InternalDone = false --used when the above settings are done internally +--local UsingCustomPath = false --used when we set a custom path + +-- SET DIR + +--[[local lowerGameRun = string.lower(GAME_RUN) +local lowerGameName = string.lower(GAME_NAME) +local secondStratDir = "" +local secondPaintDir = "" +if lowerGameRun == "no save corruption" then + if lowerGameName == "red" or lowerGameName == "blue" then + secondStratDir = ".red-blue" + secondPaintDir = secondStratDir + end +else + secondStratDir = YELLOW and ".yellow" or ".red-blue" +end]] + +-- LOAD DIR + +local LowerGameName = string.lower(GAME_NAME) + +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."..LowerGameName..".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" + +-- GLOBAL + +function p(...) --print + local string + if #arg == 0 then + string = arg[0] + else + string = "" + for i,str in ipairs(arg) do + if str == true then + string = string.."\n" + else + string = string..str.." " + end + end + end + print(string) +end + +-- RESET + +local function resetAll() + Strategies.softReset() + Combat.reset() + Control.reset() + Walk.reset() + Paint.reset() + Bridge.reset() + oldSeconds = 0 + running = false + --FirstSpawnDone = false + --FirstSpawnDone2 = false + -- client.speedmode = 200 + + if CUSTOM_SEED then + Strategies.seed = CUSTOM_SEED + p("RUNNING WITH A FIXED SEED ("..Strategies.seed.."), every run will play out identically!", true) + else + Strategies.seed = os.time() + p("Starting a new run with seed "..Strategies.seed, true) + end + math.randomseed(Strategies.seed) +end + +-- EXECUTE + +local OWNER = "Bouletmarc" +p("Welcome to PokeBot Version "..VERSION, true) +p("Actually running Pokemon "..GAME_NAME.." Speedruns by "..OWNER, true) + +Control.init() + +--STREAMING_MODE = not walk.init() +--if INTERNAL and STREAMING_MODE then +-- RESET_FOR_TIME = true +--end + +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 + p("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.", true) +end +--if STREAMING_MODE then +-- Bridge.init() +--else + Input.setDebug(true) +--end + +--if PATH_IDX ~= 0 and STEP_IDX ~= 0 then +-- UsingCustomPath = true +--end + +-- MAIN LOOP + +local previousMap +local previousMap2 + +local RebootDone = false + +while true do + local currentMap = Memory.value("game", "map") + local currentMap2 = Memory.value("game", "map2") + if currentMap ~= previousMap or currentMap2 ~= previousMap2 then + Input.clear() + previousMap = currentMap + previousMap2 = currentMap2 + end + if Strategies.frames then + if Memory.value("game", "battle") == 0 then + Strategies.frames = Strategies.frames + 1 + end + Utils.drawText(0, 80, Strategies.frames) + end + --if Bridge.polling then + -- Settings.pollForResponse() + --end + + if not Input.update() then + --if not Utils.ingame() then + if not Utils.ingame() and currentMap == 0 then + --if not Utils.ingame() and currentMap == 0 and currentMap2 == 0 then + --if currentMap == 0 then + if running then + if not hasAlreadyStartedPlaying then + client.reboot_core() --remove this for the new bizhawk + hasAlreadyStartedPlaying = true + else + --if not RUNNING4CONTINUE then + resetAll() --reset if not running to continue + --RUNNING4NEWGAME = true --set back on in case we done a reboot + --else + -- running = false --continue adventure + --end + end + else + --if UsingCustomPath then + -- if not EXTERNALDONE then --continue adventure + -- RUNNING4CONTINUE, RUNNING4NEWGAME = true, false + -- elseif EXTERNALDONE and InternalDone then + -- RUNNING4NEWGAME = true --set back to new game + -- end + --end + Settings.startNewAdventure(START_WAIT) --start/continue adventure + end + --else + --if not running then + -- Bridge.liveSplit() + -- running = true + --end + --Settings.choosePlayerNames() --set names + --end + else + if not running then + --Bridge.liveSplit() + running = true + end + --open and close menu for first spawn + --if Settings.FirstSpawn() then + --[[local Done = false + local MenuValue = Memory.value("menu", "main") + if MenuValue ~= 121 and Textbox.isActive() then + Input.press("Start", 2) + elseif MenuValue == 121 then + Input.press("B", 2) + Done = true + elseif MenuValue == 2 and not Textbox.isActive() and Done then + FirstSpawn = false + end + end]] + --else + --if RUNNING4NEWGAME then --remove last save game + -- Settings.RemoveLastAdventure(START_WAIT) + --elseif RUNNING4CONTINUE then --continue the last adventure + -- EXTERNALDONE = true + -- InternalDone = true + -- Settings.ContinueAdventure() + --else + local battleState = Memory.value("game", "battle") + Control.encounter(battleState) + --local curr_hp = Pokemon.index(0, "hp") + --if curr_hp == 0 and not Control.canDie() and Pokemon.index(0) > 0 then + -- Strategies.death(currentMap, currentMap2) + --elseif Walk.strategy then + if Walk.strategy then + if Strategies.execute(Walk.strategy) then + Walk.traverse(currentMap, currentMap2) + end + elseif battleState > 0 then + --if not Control.shouldCatch(partySize) then + Battle.automate() + --end + elseif Textbox.handle() then + Walk.traverse(currentMap, currentMap2) + end + --end + end + end + + --[[if STREAMING_MODE then + local newSeconds = Memory.value("time", "seconds") + if newSeconds ~= oldSeconds and (newSeconds > 0 or Memory.value("time", "frames") > 0) then + Bridge.time(Utils.elapsedTime()) + oldSeconds = newSeconds + end + elseif PAINT_ON then]] + if PAINT_ON then + Paint.draw(currentMap, currentMap2) + end + + Input.advance() + emu.frameadvance() +end + +Bridge.close() diff --git a/storage/inventory.lua b/storage/inventory.lua new file mode 100644 index 0000000..c35a0cd --- /dev/null +++ b/storage/inventory.lua @@ -0,0 +1,273 @@ +local Inventory = {} + +local Input = require "util.input" +local Memory = require "util.memory" +local Menu = require "util.menu" +local Utils = require "util.utils" + +local Pokemon = require "storage.pokemon" + +local ItemList = require "storage.itemlist" + +--[[local items = { + pokeball = 4, + bicycle = 6, + moon_stone = 10, + antidote = 11, + burn_heal = 12, + 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, + coin_case = 69, + 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 = Memory.value("inventory", "item_base") + +-- Data + +function Inventory.indexOf(name) + --local searchID = items[name] + local searchID = ItemList.items[name] + for i=0,19 do + --local iidx = ITEM_BASE + i * 2 + local SubIndex = i * 2 + local iidx = ITEM_BASE + SubIndex + 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 + local SubIndex = index * 2 + return Memory.raw(ITEM_BASE + SubIndex + 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 == Menu.pokemon 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.value("inventory", "item_count") == 20 +end + +function Inventory.use(item, poke, midfight, BagMenu) + if midfight then + local battleMenu = Memory.value("battle", "menu") + --if battleMenu == 94 then + --open bag menu + if battleMenu == 186 then + local rowSelected = Memory.value("battle", "menuY") + local ColumnSelected = Memory.value("battle", "menuX") + if ColumnSelected == 1 then + if rowSelected == 1 then + Input.press("Down") + else + --select bag + Input.press("A") + end + else + Input.press("Left") + end + --elseif battleMenu == 233 then + --inside bag menu + elseif battleMenu == 128 then + --if its not done + if not give_done then + if column ~= BagMenu then + --select proper bag menu + Menu.setCol(BagMenu) + else + if Memory.value("menu", "shop_current") ~= 70 then + --select the item + Menu.select(Inventory.indexOf(item)+1, "accelerate", "input") + else + --accept the use + Menu.select(1, true, "input") + end + end + --if its done + else + Menu.close() + end + elseif Utils.onPokemonSelect(battleMenu) then + if poke then + --if type(poke) == "string" then + -- poke = Pokemon.indexOf(poke) + --end + Menu.select(poke, true, "input") + else + Input.press("A") + end + else + Input.press("B") + end + return + end + + local main = Memory.value("menu", "main") + local column = Menu.getCol() + local give_done = false + --select item menu + if main == 121 then + Menu.select(3, true) + --inside bag menu + elseif main == 50 then + --if its not done + if not give_done then + if column ~= BagMenu then + --select proper bag menu + Menu.setCol(BagMenu) + else + if Memory.value("menu", "shop_current") ~= 66 then + --select the item + Menu.select(Inventory.indexOf(item)+1, "accelerate", "input") + else + --accept the use + Menu.select(1, true, "input") + end + end + --if its done + else + Menu.close() + end + --inside pokemon menu + elseif main == 127 then + local idx = 1 + if poke then + idx = poke + end + if Memory.value("menu", "input_row") ~= idx then + Menu.select(idx, true, "input") + else + Input.press("A", 1) + give_done = true + end + else + return false + end + + --#################################### + --[[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 == Menu.pokemon then + Input.press("B") + else + return false + end]] + return true +end + +return Inventory + diff --git a/storage/itemlist.lua b/storage/itemlist.lua new file mode 100644 index 0000000..59fee82 --- /dev/null +++ b/storage/itemlist.lua @@ -0,0 +1,62 @@ +ItemList = {} + +ItemList.items = { + masterball = 1, + ultraball = 2, + bright_powder = 3, + greatball = 4, + pokeball = 5, + --teru_sama = 6, + bicycle = 7, + moon_stone = 8, + antidote = 9, + burn_heal = 10, + ice_heal = 11, + awakening = 12, + paralyze_heal = 13, + full_restore = 14, + max_potion = 15, + hyper_potion = 16, + super_potion = 17, + potion = 18, + escape_rope = 19, + repel = 20, + --carbos = 38, + rare_candy = 32, + --helix_fossil = 42, + --nugget = 49, + --pokedoll = 51, + super_repel = 42, + --fresh_water = 60, + --soda_pop = 61, + coin_case = 54, + --pokeflute = 73, + --ether = 80, + --max_ether = 81, + --elixer = 82, + --x_accuracy = 46, + --x_speed = 67, + --x_special = 68, + --horn_drill = 207, + --bubblebeam = 211, + --water_gun = 212, + --ice_beam = 213, + --thunderbolt = 224, + --earthquake = 226, + --dig = 228, + --tm34 = 234, + --rock_slide = 248, +} + +ItemList.moves = { + cut = 15, + fly = 19, + surf = 57, + strength = 70, + teleport = 100, + watefall = 127, + whirlpool = 250, +} + +--return ItemList + diff --git a/storage/pokemon.lua b/storage/pokemon.lua new file mode 100644 index 0000000..31a695e --- /dev/null +++ b/storage/pokemon.lua @@ -0,0 +1,325 @@ +local Pokemon = {} + +local Bridge = require "util.bridge" +local Input = require "util.input" +local Memory = require "util.memory" +local Menu = require "util.menu" + +local pokeIDs = { + pidgey = 16, + spearow = 21, + rattata = 19, + nidoranF = 29, + nidoranM = 32, + + chikorita = 152, + bayleef = 153, + meganium = 154, + + cyndaquil = 155, + quilava = 156, + typhlosion = 157, + + totodile = 158, + croconaw = 159, + feraligatr = 160, + + sentret = 161, + furret = 162, + hoothoot = 163, + marill = 183, + azumarill = 184, + sudowoodo = 185, + politoed = 186, + hoppip = 187, +} + +local moveList = { + cut = 15, + fly = 19, + surf = 57, + strength = 70, + teleport = 100, + watefall = 127, + whirlpool = 250, + + 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, + ice_beam = 58, + bubblebeam = 61, + leech_seed = 73, + thunderbolt = 85, + earthquake = 89, + dig = 91, + rock_slide = 157, +} + +--[[local data = { + hp = {1, true}, + status = {4}, + moves = {8}, + pp = {28}, + level = {33}, + max_hp = {34, true}, + + attack = {36, true}, + defense = {38, true}, + speed = {40, true}, + special = {42, true}, +}]] + +local previousPartySize + +--[[local function getAddress(index) + return 0x116B + index * 0x2C +end]] + +--local function index(index, offset) +--[[local function index(index) + 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 address = getAddress(index) + 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=0,3 do + --if mid == Memory.raw(0x101B + i) then + if mid == Memory.raw(0x062E + 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) +--[[function Pokemon.info(name) + --return index(indexOf(name), offset) + return index(indexOf(name)) +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.getSacrifice(...) + for i,name in ipairs(arg) do + local pokemonIndex = indexOf(name) + if pokemonIndex ~= -1 and index(pokemonIndex, "hp") > 0 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("tododile", "paras", "spearow", "pidgey", "nidoran", "squirtle") + --local poke = Pokemon.inParty("tododile") + --if poke then + -- Bridge.caught(poke) + -- previousPartySize = partySize + --end + end +end + +--[[function Pokemon.pp(index, move) + local midx = Pokemon.battleMove(move) + return Memory.raw(getAddress(index) + 28 + midx) +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(...) + local deployedID = Memory.value("battle", "our_id") + for i,name in ipairs(arg) do + if deployedID == pokeIDs[name] then + return name + end + end +end + +function Pokemon.isEvolving() + return false + --return Memory.value("menu", "pokemon") == 144 +end + +--[[function Pokemon.getExp() + return Memory.raw(0x117A) * 256 + Memory.raw(0x117B) +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 battlemenu = Memory.value("battle", "menu") + local pokeName = Pokemon.forMove(move) + local column = Memory.value("battle", "menuX") + local row = Memory.value("battle", "menuY") + if battlemenu == 186 then + if column == 2 then + Input.press("Left", 1) + else + if row == 2 then + Input.press("Up", 1) + else + --select move menu + Input.press("A", 1) + end + end + elseif battlemenu == 106 then + local midx = 1 + if move then + midx = move + end + Menu.select(midx, true, "input") + else + return false + end + + + --[[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 == Menu.pokemon then + Menu.select(pokeName, true) + elseif main == 228 then + Input.press("B") + else + return false + end]] + return true +end + +--[[function Pokemon.getDVs(name) + local index = Pokemon.indexOf(name) + local baseAddress = getAddress(index) + local attackDefense = Memory.raw(baseAddress + 0x1B) + local speedSpecial = Memory.raw(baseAddress + 0x1C) + return bit.rshift(attackDefense, 4), bit.band(attackDefense, 15), bit.rshift(speedSpecial, 4), bit.band(speedSpecial, 15) +end]] + +return Pokemon + diff --git a/util/bridge.lua b/util/bridge.lua new file mode 100644 index 0000000..0152dfb --- /dev/null +++ b/util/bridge.lua @@ -0,0 +1,148 @@ +local Bridge = {} + +--local socket +--if INTERNAL then +-- socket = require("socket") +--end + +local utils = require("util.utils") + +local client = nil +local timeStopped = true + +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() + if socket then + -- io.popen("java -jar Main.jar") + client = socket.connect("127.0.0.1", 13378) + if client then + client:settimeout(0.005) + client:setoption("keepalive", true) + print("Connected to Java!"); + return true + else + print("Error connecting to Java!"); + end + end +end]] + +--[[function Bridge.tweet(message) + if INTERNAL and STREAMING_MODE then + print("tweet::"..message) + return send("tweet", message) + end +end + +function Bridge.pollForName() + Bridge.polling = true + send("poll_name") +end + +function Bridge.chat(message, extra, newLine) + if extra then + p(message.." || "..extra, newLine) + else + p(message, newLine) + end + return send("msg", "/me "..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(finished) + 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..c5a8c8b --- /dev/null +++ b/util/input.lua @@ -0,0 +1,166 @@ +local Input = {} + +local Bridge = require "util.bridge" +local Memory = require "util.memory" +local Utils = require "util.utils" + +local lastSend +local currentButton, remainingFrames, setForFrame +local debug +local bCancel = true + +local Waiting = false + +local function bridgeButton(btn) + if btn ~= lastSend then + lastSend = btn + Bridge.input(btn) + end +end + +--local function sendButton(button, ab, hold, newgame) +local function sendButton(button, ab, hold) + local inputTable = {} + if hold then + inputTable = {[button]=true, B=true} + else + --if not newgame then + inputTable = {[button]=true} + --else + -- inputTable = {Up=true, B=true, Select=true} + --end + end + joypad.set(inputTable) + if debug then + if hold then + gui.text(0, 7, button.."+B") + else + --if not newgame then + gui.text(0, 7, button.." "..remainingFrames) + --else + -- gui.text(0, 7, "Up+B+Select") + --end + end + end + if ab then + buttonbutton = "A,B" + end + bridgeButton(button) + setForFrame = button +end + +function Input.isWaiting() + if setForFrame and not Waiting then + Waiting = true + elseif not setForFrame and Waiting then + Waiting = false + end + return Waiting +end + +--function Input.press(button, frames, hold, newgame) +function Input.press(button, frames, hold) + 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, false, hold) + --sendButton(button, false, hold, newgame) +end + +--function Input.cancel(accept) +function Input.cancel() + --if accept and Memory.value("menu", "shop_current") == 20 then + --if accept and Memory.value("menu", "shop_current") == 30 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 rowSelected = Memory.value("battle", "menuY") + local columnSelected = Memory.value("battle", "menuX") + if not Input.isWaiting() then + if rowSelected == 1 then + Input.press("Down", 2) + else + if columnSelected == 1 then + Input.press("Right", 2) + else + Input.press("A", 2) + end + end + end + --local inputTable = {Right=true, Down=true} + --joypad.set(inputTable) + --bridgeButton("D,R") +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") + 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..814f3c5 --- /dev/null +++ b/util/memory.lua @@ -0,0 +1,188 @@ +local Memory = {} + +local memoryNames = { + setting = { + text_speed = 0x04E8, --247=1 // 248=2 // 249=3 ###### 139-136-128 + battle_animation = 0x0510, --141=no // 142=yes ###### 141=on 133=off + battle_style = 0x0538, --130=shift // 131=set ##### 135=shift 132=set + sound_style = 0x0562, --132=stereo // 141=mono ##### 142=mono 145=stereo + print_style = 0x0FD0, --64=normal // 96=darker // 127=darkest // 0=lightest // 32=lighter + account_style = 0x0FD1, --0=no // 1=yes + windows_style = 0x0FCE, --0 to 7 + }, + text_inputing = { + column = 0x0330, + row = 0x0331, + }, + inventory = { + item_count = 0x1892, + item_base = 0x1893, + }, + menu = { + row = 0x0F88, + input_row = 0x0FA9, + settings_row = 0x0F63, + hours_row = 0x061C, --(0-23) + minutes_row = 0x0626, --(0-59) + days_row = 0x1002, --(0-6) + + column = 0x0F65, + current = 0x00DF, --32=off 79=on instead of 20=on + size = 0x0FA3, + option_current = 0x0F84,--DONE used while settings options (5=startmenu, 7=optionmenu) + shop_current = 0x0F87, --DONE 95=main 94=buy 80=ammount 30=accept 74=sell instead of 32=main 158/161=amount 20=buy/accept 248=sell + selection = 0x0F78, --DONE ?? going like 1 or 2 or 4 etc... + text_input = 0x0F69, --DONE 65=inputing + text_length = 0x06D2, --DONE + main = 0x04AA, --DONE 121=open + --pokemon = 0x0C51, --TO DO, USED WHILE EVOLVING + --selection_mode = 0x0C35, --TO DO, USED WHEN SWAPING MOVE + --transaction_current = 0x0F8B,--TODO, USED FOR SHOPPING + --################################################################ + --main_current = 0x0C27, --NOT USED?? + --scroll_offset = 0x0C36, --NOT USED + --################################################################ + }, + player = { + name = 0x147D, + name2 = 0x1493, + moving = 0x14E1, --if not 1 then moving + x = 0x1CB8, + y = 0x1CB7, + facing = 0x14DE, --0=S // 4=N // 8=W // 12=E instead of 4=S // 8=N // 2=W // 1=E + repel = 0x1CA1, + party_size = 0x1CD7, + }, + game = { + map = 0x1CB6, + map2 = 0x1CB5, + battle = 0x122D, --1=wild 2=trainer + battle_type = 0x1230, --ex:7=shiny/cant escape + ingame = 0x02CE, + textbox = 0x10ED, --1=false 65=On + --textbox = 0x0FA4, --or 0x0FAA + --inside_area = 0x02D0, --can be used while inside a area we can use escape_rope? + }, + time = { + hours = 0x14C4, --DONE or 0xD4C5 + minutes = 0x14C6, --DONE + seconds = 0x14C7, --DONE + frames = 0x14C8, --DONE + }, + shop = { + transaction_amount = 0x110C,--DONE + }, + battle = { + text = 0x0FCF, --DONE 1=11(texting) // 3=1(not) + menu = 0x0FB6, --DONE 106=106(att) // 186=94(free) // 128=233(item) // 145=224(pkmon) + menuX = 0x0FAA, --DONE used for battle Row-X + menuY = 0x0FA9, --DONE used for battle Row-Y + battle_turns = 0x06DD, --DONE?? USED FOR DSUM ESCAPE?? + + opponent_id = 0x1206, --DONE or 0x1204 + opponent_level = 0x1213, --DONE + opponent_type1 = 0x1224, --DONE + opponent_type2 = 0x1225, --DONE + opponent_move_id = 0x1208, --DONE used to get opponent moves ID + opponent_move_pp = 0x120E, --DONE used to get opponent moves PP + + our_id = 0x1205, --DONE old=1014 + our_status = 0x063A, --DONE + our_level = 0x0639, --DONE + our_type1 = 0x064A, --DONE + our_type2 = 0x064B, --DONE + our_move_id = 0x062E, --DONE used to get our moves ID + our_move_pp = 0x0634, --DONE used to get our moves PP + + --attack_turns = 0x06DC, --DONE?? NOT USED?? + --accuracy = 0x0D1E, + --x_accuracy = 0x1063, + --disabled = 0x0CEE, + --paralyzed = 0x1018, + --critical = 0x105E, + --miss = 0x105F, + --our_turn = 0x1FF1, + + --TO DO GET SLEEPING STATUS + --TO DO GET CONFUSED STATUS + --TO DO GET PARALIZED STATUS ? + --TO DO GET CRITICAL'D STATUS ? + + --opponent_next_move = 0x0CDD, --C6E4 ?? NOT USED? + --opponent_last_move = 0x0FCC, + --opponent_bide = 0x106F, + }, + + --[[pokemon = { + exp1 = 0x1179, + exp2 = 0x117A, --NOT USED, WAS ONLY FOR REMOVE LAST ADVENTURE + exp3 = 0x117B, + },]] +} + +local doubleNames = { + battle = { + opponent_hp = 0x1216, --DONE 10FF index +278? // + opponent_max_hp = 0x1218, --DONE + opponent_attack = 0x121A, --DONE + opponent_defense = 0x121C, --DONE + opponent_speed = 0x121E, --DONE + opponent_special_attack = 0x1220,--DONE + opponent_special_defense = 0x1222,--DONE + + our_hp = 0x063C, --DONE + our_max_hp = 0x063E, --DONE + our_attack = 0x0640, --DONE + our_defense = 0x0642, --DONE + our_speed = 0x0644, --DONE + our_special_attack = 0x0646, --DONE + our_special_defense = 0x0648, --DONE + }, + + --[[pokemon = { + attack = 0x117E, + defense = 0x1181, --NOT USED AT ALL?? + speed = 0x1183, + special = 0x1185, + },]] +} + +--local yellow = YELLOW + +local function raw(address) +--local function raw(address, forYellow) + --if yellow and not forYellow and address > 0x0F12 and address < 0x1F00 then + -- address = address - 1 + --end + return memory.readbyte(address) +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, forYellow) + local memoryAddress = memoryNames[section] + if key then + memoryAddress = memoryAddress[key] + end + return raw(memoryAddress, forYellow) +end + +return Memory diff --git a/util/menu.lua b/util/menu.lua new file mode 100644 index 0000000..49aa0c9 --- /dev/null +++ b/util/menu.lua @@ -0,0 +1,228 @@ +local Menu = {} + +local Input = require "util.input" +local Memory = require "util.memory" + +--local yellow = GAME_NAME == "yellow" + +local sliding = false + +--Menu.pokemon = yellow and 51 or 103 +--Menu.pokemon = 103 --NOT USED? + +-- Private functions + +--local function getRow(menuType, scrolls) +local function getRow(menuType) + if menuType 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 function setRow(desired, throttle, menuType, loop) + --local currentRow = getRow(menuType, scrolls) + local currentRow = getRow(menuType) + 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 + --if menuType ~= "hours" or menuType ~= "minutes" then + return Menu.balance(currentRow, desired, true, loop, throttle) + --else + -- return Menu.balance(currentRow, desired, false, loop, throttle) + --end +end + +local function isCurrently(desired, menuType) + if menuType then + if menuType ~= "main" then + menuType = menuType.."_current" + end + 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, menuType) + return false +end + +--function Menu.select(option, throttle, scrolls, menuType, dontPress, loop) +function Menu.select(option, throttle, menuType, dontPress, loop) + --Reset MenuType + local menuTypeSent + if menuType == "option" then + menuTypeSent = nil + else + menuTypeSent = menuType + end + --if setRow(option, throttle, scrolls, menuType, loop) then + if setRow(option, throttle, menuTypeSent, loop) then + local delay = 1 + if throttle or menuType == "option" 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 + local goUp + if inverted then + if desired < current then + goUp = true + else + goUp = false + end + else + goUp = false + end + 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, looping, throttle) + return Menu.sidle(Menu.getCol(), desired, looping, throttle) +end + +-- Options + +function Menu.setOption(name, desired) + local rowFor = { + text_speed = 0, + battle_animation = 1, + battle_style = 2, + sound_style = 3, + print_style = 4, + account_style = 5, + windows_style = 6 + } + if Memory.value("setting", name) == desired then + return true + end + --if setRow(rowFor[name], true, false, "settings") then + if setRow(rowFor[name], 2, "settings") then + Menu.setCol(desired, false, 2) + end + return false +end + +-- Pause menu + +function Menu.isOpen() + return Memory.value("game", "textbox") == 1 or Memory.value("menu", "current") == 79 + --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 + if Memory.value("game", "textbox") == 0 and Memory.value("menu", "main") == 0 then + return true + end + Input.press("B") +end + +function Menu.pause() + if Memory.value("game", "textbox") == 1 then + --if Memory.value("battle", "menu") == 95 then + if Memory.value("battle", "text") == 3 then + Input.cancel() + --[[else + local main = Memory.value("menu", "main") + if main > 2 and main ~= 64 then + return true + end + Input.press("B")]] + elseif Memory.value("battle", "text") == 11 then + return true + else + Input.press("B") + end + 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..948f769 --- /dev/null +++ b/util/paint.lua @@ -0,0 +1,56 @@ +local Paint = {} + +local Memory = require "util.memory" +local Player = require "util.player" +local Utils = require "util.utils" + +local Pokemon = require "storage.pokemon" + +local encounters = 0 +local elapsedTime = Utils.elapsedTime +local drawText = Utils.drawText + +function Paint.draw(currentMap, currentMap2) + local px, py = Player.position() + drawText(0, 14, currentMap..","..currentMap2.." : "..px.." "..py) + drawText(0, 0, elapsedTime()) + + --[[if Memory.value("game", "battle") > 0 then + local curr_hp = Pokemon.index(0, "hp") + local hpStatus + if curr_hp == 0 then + hpStatus = "DEAD" + elseif curr_hp <= math.ceil(Pokemon.index(0, "max_hp") * 0.2) then + hpStatus = "RED" + end + if hpStatus then + drawText(120, 7, hpStatus) + end + end + + local tidx = Pokemon.indexOf("totodile") + if tidx ~= -1 then + local attack = Pokemon.index(tidx, "attack") + local defense = Pokemon.index(tidx, "defense") + local speed = Pokemon.index(tidx, "speed") + local scl_att = Pokemon.index(tidx, "special_attack") + local scl_def = Pokemon.index(tidx, "special_defense") + drawText(0, 134, attack.." Att/"..defense.." Def/"..speed.." Spd/"..scl_att.." Scl_Att/"..scl_def.." Scl_Def") + end]] + local enc = " encounter" + if encounters > 1 then + enc = enc.."s" + end + drawText(0, 90, 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..4ad9982 --- /dev/null +++ b/util/player.lua @@ -0,0 +1,43 @@ +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} +local facingDirections = {Up=4, Right=12, Left=8, Down=0} + +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, opposite) + if Player.face(direction) then + if not opposite then + Input.press("A", 2) + else + Input.press("B", 2) + end + return true + end +end + +function Player.isMoving() + return Memory.value("player", "moving") ~= 1 +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..89f8905 --- /dev/null +++ b/util/settings.lua @@ -0,0 +1,215 @@ +local Settings = {} + +local Textbox = require "action.textbox" + +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 START_WAIT = 99 + +--local tempDir + +local settings_menu = 7 +local settings_done = false +local Setting_done = false + +local desired = {} +desired.text_speed = GAME_TEXT_SPEED +desired.battle_animation = GAME_BATTLE_ANIMATION +desired.battle_style = GAME_BATTLE_STYLE +desired.sound_style = GAME_SOUND_STYLE +desired.print_style = GAME_PRINT_STYLE +desired.account_style = GAME_ACCOUNT_STYLE +desired.windows_style = GAME_WINDOWS_STYLE + +local function isEnabled(name) + return Memory.value("setting", name) == desired[name] +end + +-- PUBLIC + +function Settings.set(...) + if not settings_done then + for i,name in ipairs(arg) do + if not isEnabled(name) then + if Menu.open(settings_menu, 2, "option") then + Menu.setOption(name, desired[name]) + end + return false + end + end + --setting done + settings_done = true + end + --close option menu + local OptionValue = Memory.value("menu", "option_current") + if OptionValue ~= 5 then + Input.press("B", 1) + end + if OptionValue == 5 then + settings_done = false + return true + end +end + +function Settings.startNewAdventure(startWait) + local startMenu = Memory.value("menu", "main") + local MenuCurrent = Memory.value("menu", "current") + local ShopCurrent = Memory.value("menu", "shop_current") + local InputRow = Memory.value("menu", "input_row") + local HoursRow = Memory.value("menu", "hours_row") + local MinutesRow = Memory.value("menu", "minutes_row") + + --set settings + if startMenu == 122 then + if not Setting_done then + if Settings.set("text_speed", "battle_animation", "battle_style", "sound_style", "print_style", "account_style", "windows_style") then + Setting_done = true + end + else + Input.press("A", 2) + end + --press A or Start + elseif startMenu == 127 then + --if MenuCurrent == 59 then --french + if MenuCurrent == 104 then --english + Input.press("A", 2) + else + if not Setting_done and math.random(0, startWait) == 0 then + Input.press("Start") + end + end + else + --Set Name + --if MenuCurrent == 79 then --french + if MenuCurrent == 110 then --english + if InputRow == 1 and GAME_GENDER == 2 then + Input.press("Down", 2) + elseif InputRow == 2 and GAME_GENDER == 1 then + Input.press("Up", 2) + else + Input.press("A", 2) + end + --Set hours/minutes/name + elseif MenuCurrent == 32 or MenuCurrent == 107 then + --if ShopCurrent == 77 then --french + if ShopCurrent == 78 then --english + --set hours + if HoursRow < GAME_HOURS then + Input.press("Up", 1) + elseif HoursRow > GAME_HOURS then + Input.press("Down", 1) + elseif HoursRow == GAME_HOURS then + Input.press("A", 1) + end + elseif ShopCurrent == 30 then + --set minutes + if MinutesRow < GAME_MINUTES then + Input.press("Up", 1) + elseif MinutesRow > GAME_MINUTES then + Input.press("Down", 1) + elseif MinutesRow == GAME_MINUTES then + Input.press("A", 1) + end + end + --elseif MenuCurrent == 231 then --french + elseif MenuCurrent == 232 then --english + --remake setting not done + Setting_done = false + --set our name + Textbox.name(PLAYER_NAME, true) + else + Input.press("A") + end + end +end + +--[[function Settings.FirstSpawn() + if not FirstSpawnDone then + local MenuValue = Memory.value("menu", "main") + if MenuValue == 121 then + Input.press("B", 2) + FirstSpawnDone2 = true + elseif MenuValue == 0 then + if Textbox.isActive() then + Input.press("Start", 2) + elseif not Textbox.isActive() and FirstSpawnDone2 then + FirstSpawnDone = true + return true + end + end + else + return true + end +end]] + +--[[function Settings.RemoveLastAdventure(startWait) + if not tempDir then + if Memory.value("menu", "size") ~= 2 and math.random(0, startWait) == 0 then + Input.press("Start") + elseif Memory.value("menu", "size") == 2 then + Input.press("B") + tempDir = true + end + else + if Utils.ingame() then + if Memory.value("menu", "pokemon") ~= 0 then + Input.press("B") + elseif Memory.value("menu", "pokemon") == 0 then + if Memory.value("menu", "size") == 2 then + Input.press("", 0, false, true) + else + if Memory.value("menu", "row") == 1 then + Input.press("A") + else + Input.press("Down") + end + end + end + else + tempDir = false + RUNNING4NEWGAME = false --stop the function after removed + end + end +end]] + +--[[function Settings.ContinueAdventure() + local current = Memory.value("menu", "current") + local row = Memory.value("menu", "row") + if row == 0 then + if current == 32 then + RUNNING4CONTINUE = false --stop ContinueAdventure + elseif current ~= 55 then + Input.press("A") + end + else + Input.press("Up") + end +end]] + +--[[function Settings.choosePlayerNames() + local name = PLAYER_NAME + if dirText ~= "glitch" then + if (Memory.value("player", "name") ~= 141) or (Memory.value("player", "name2") ~= 136) then + name = RIVAL_NAME + end + else + if (Memory.value("player", "name") ~= 141) or (Memory.value("player", "name2") ~= 136) then + name = "> " + end + end + Textbox.name(name, true) +end + +function Settings.pollForResponse() + local response = Bridge.process() + if response then + Bridge.polling = false + Textbox.setName(tonumber(response)) + end +end]] + +return Settings diff --git a/util/utils.lua b/util/utils.lua new file mode 100644 index 0000000..eb75e14 --- /dev/null +++ b/util/utils.lua @@ -0,0 +1,121 @@ +local Utils = {} + +local Memory = require "util.memory" + +local EMP = 1 + +-- GENERAL + +function Utils.dist(x1, y1, x2, y2) + return math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2)) +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 + return false +end + +function Utils.key(needle, haystack) + for key,val in pairs(haystack) do + if needle == val then + return key + end + end + return nil +end + +function Utils.capitalize(string) + return string:sub(1, 1):upper()..string:sub(2) +end + +-- GAME + +function Utils.canPotionWith(potion, forDamage, curr_hp, max_hp) + local potion_hp + if potion == "full_restore" then + potion_hp = 9001 + elseif potion == "super_potion" then + potion_hp = 50 + else + potion_hp = 20 + end + return math.min(curr_hp + potion_hp, max_hp) >= forDamage - 1 +end + +function Utils.ingame() + return Memory.value("game", "ingame") > 0 +end + +function Utils.onPokemonSelect(battleMenu) + --return battleMenu == 8 or battleMenu == 48 or battleMenu == 184 or battleMenu == 224 + return battleMenu == 145 +end + +function Utils.drawText(x, y, message) + gui.text(x * EMP, y * EMP, message) +end + +-- TIME + +function Utils.igt() + local hours = Memory.value("time", "hours") + local mins = Memory.value("time", "minutes") + local secs = Memory.value("time", "seconds") + return (hours * 60 + mins) * 60 + secs +end + +local function clockSegment(unit) + if unit < 10 then + unit = "0"..unit + end + return unit +end + +function Utils.timeSince(prevTime) + local currTime = Utils.igt() + local diff = currTime - prevTime + local timeString + if diff > 0 then + local secs = diff % 60 + local mins = math.floor(diff / 60) + timeString = clockSegment(mins)..":"..clockSegment(secs) + end + return currTime, timeString +end + +function Utils.elapsedTime() + local secs = Memory.value("time", "seconds") + local mins = Memory.value("time", "minutes") + local hours = Memory.value("time", "hours") + return hours..":"..clockSegment(mins)..":"..clockSegment(secs) +end + +function Utils.frames() + local totalFrames = Memory.value("time", "hours") * 60 + totalFrames = (totalFrames + Memory.value("time", "minutes")) * 60 + totalFrames = (totalFrames + Memory.value("time", "seconds")) * 60 + totalFrames = totalFrames + Memory.value("time", "frames") + return totalFrames +end + +return Utils