From c8f5de6715d3592fb0fdb33ff72a96cad37d8efd Mon Sep 17 00:00:00 2001 From: bouletmarc Date: Thu, 9 Jul 2015 09:47:32 -0400 Subject: [PATCH] First Commit First Commit, Alpha version, Will play up to first battle --- action/battle.lua | 296 ++++++++ action/shop.lua | 109 +++ action/textbox.lua | 192 +++++ action/walk.lua | 169 ++++ ai/combat.lua | 420 ++++++++++ ai/control.lua | 300 ++++++++ ai/emerald/strategies.lua | 178 +++++ ai/strategies.lua | 369 +++++++++ data/movelist.lua | 1522 +++++++++++++++++++++++++++++++++++++ data/opponents.lua | 225 ++++++ data/paths.lua | 38 + main.lua | 239 ++++++ storage/inventory.lua | 273 +++++++ storage/itemlist.lua | 62 ++ storage/pokemon.lua | 325 ++++++++ util/bridge.lua | 148 ++++ util/input.lua | 162 ++++ util/memory.lua | 198 +++++ util/menu.lua | 209 +++++ util/paint.lua | 56 ++ util/player.lua | 42 + util/settings.lua | 122 +++ util/utils.lua | 159 ++++ 23 files changed, 5813 insertions(+) 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/emerald/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/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..4cde057 --- /dev/null +++ b/action/textbox.lua @@ -0,0 +1,192 @@ +local Textbox = {} + +local Input = require "util.input" +local Memory = require "util.memory" +local Menu = require "util.menu" + +local alphabet_upper = "ABCDEF .GHIJKL ,MNOPQRS TUVWXYZ " +local alphabet_lower = "abcdef .ghijkl ,mnopqrs tuvwxyz " +local alphabet_number = "01234 56789 !?<>/- _{}[] " +-- < = male symbol +-- > = female symbol +-- { or } = " +-- [ or ] = ' + +local TableNumber = 1 +local ActualUpper = 1 + +local function getIndexForLetter(letter, Mode) + if Mode == "Upper" then + return alphabet_upper:find(letter, 1, true) + elseif Mode == "Lower" then + return alphabet_lower:find(letter, 1, true) + elseif Mode == "Number" then + return alphabet_number:find(letter, 1, true) + end +end + +function Textbox.name(letter, randomize) + local inputting = Memory.value("menu", "text_input") + if inputting then + -- Set vars + local lidx + local drow + local dcol + local NameTable = {} + local ColumnMax + --Get values + local crow = Memory.value("text_inputing", "row") + local ccol = Memory.value("text_inputing", "column") + local mode = Memory.value("text_inputing", "mode") + + --if letter then + local StringLenght = string.len(letter) + letter:gsub(".",function(letter2) + table.insert(NameTable,letter2) + + if NameTable[TableNumber] then + local Mode = "Upper" + + --its a letter + if string.match(NameTable[TableNumber], '%a') then + if string.match(NameTable[TableNumber], '%u') then + Mode = "Upper" + elseif string.match(NameTable[TableNumber], '%l') then + Mode = "Lower" + end + --its a number + elseif string.match(NameTable[TableNumber], '%d') then + Mode = "Number" + --its anything but not a letter or a number + else + if string.find(alphabet_upper, NameTable[TableNumber]) ~= nil then + Mode = "Upper" + elseif string.find(alphabet_lower, NameTable[TableNumber]) ~= nil then + Mode = "Lower" + elseif string.find(alphabet_number, NameTable[TableNumber]) ~= nil then + Mode = "Number" + end + end + + --Set lidx + lidx = getIndexForLetter(NameTable[TableNumber], Mode) + + local Waiting = Input.isWaiting() + + --Proceed + if not Waiting then + --Get/set Lower/Upper + if Mode == "Upper" and mode ~= 0 or Mode == "Lower" and mode ~= 1 or Mode == "Number" and mode ~= 2 then + if mode == 2 then + ColumnMax = 6 + else + ColumnMax = 8 + end + if crow ~= 0 then + Input.press("Up", 2) + elseif crow == 0 then + if ccol < ColumnMax then + Input.press("Right", 2) + else + Input.press("A", 2) + end + end + --Get/Set Letter + else + if mode == 2 then + ColumnMax = 6 + else + ColumnMax = 8 + end + dcol = math.fmod(lidx - 1, ColumnMax) + if ccol < dcol then + Input.press("Right", 2) + elseif ccol > dcol then + Input.press("Left", 2) + elseif ccol == dcol then + drow = math.ceil(lidx/ColumnMax)-1 + if crow < drow then + Input.press("Down", 2) + elseif crow > drow then + Input.press("Up", 2) + elseif crow == drow then + Input.press("A", 2) + TableNumber = TableNumber + 1 + end + end + end + end + end + end) + + local Waiting = Input.isWaiting() + + if TableNumber > StringLenght and not Waiting then + if Memory.value("menu", "text_length")-7 > 0 then + if mode == 2 then + ColumnMax = 6 + else + ColumnMax = 8 + end + --get column/row + if crow ~= 2 and ccol ~= ColumnMax then + Input.press("Start", 2) + elseif crow == 2 and ccol == ColumnMax then + Input.press("A", 2) + TableNumber = 1 + ActualUpper = 1 + NameTable = {} + return true + end + end + end + --[[else + if Memory.value("menu", "text_length")-7 > 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 randomize then + Input.press("A", math.random(1, 5)) + else + Input.press("A", 2) + --Input.cancel() + end + end +end + +function Textbox.isActive() + local Active = false + --if Memory.value("game", "textbox") == 1 or Memory.value("game", "textboxing") == 1 then + if Memory.value("game", "textbox") > 0 then + Active = true + end + return Active +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..dbbf0c5 --- /dev/null +++ b/action/walk.lua @@ -0,0 +1,169 @@ +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 + +-- Private functions + +local function setPath(index, region) + pathIdx = index + stepIdx = 2 + currentMap = region + path = Paths[index] +end + +local function completeStep(region) + stepIdx = stepIdx + 1 + return Walk.traverse(region) +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, slow) + local px, py = Player.position() + if px == dx and py == dy then + return true + end + --set slow mode + local SlowMode = false + if slow ~= nil and slow == true then + SlowMode = true + end + Input.press(dir(px, py, dx, dy), 0, SlowMode) +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.double("game", "map") + local px, py = Player.position() + if region == 0 and px == 0 and py == 0 then + return false + end + for tries=1,2 do + for i,p in ipairs(Paths) do + if i > 2 and p[1] == region then + local origin = p[3] + if tries == 2 or (origin[1] == px and origin[2] == py) then + setPath(i, region) + return tries == 1 + end + end + end + end +end + +function Walk.traverse(region) + local newIndex + if not path or currentMap ~= region then + Walk.strategy = nil + customIdx = 1 + customDir = 1 + setPath(pathIdx + 1, region) + newIndex = pathIdx + elseif stepIdx > #path then + return + end + local tile = path[stepIdx] + if tile.c then + Control.set(tile) + return completeStep(region) + end + if tile.s then + if Walk.strategy then + Walk.strategy = nil + return completeStep(region) + end + Walk.strategy = tile + elseif step(tile[1], tile[2], tile[3]) then + Pokemon.updateParty() + return completeStep(region) + end + return newIndex +end + +function Walk.canMove() + --return Memory.value("player", "moving") == 0 and Memory.value("player", "fighting") == 0 + --return Memory.value("player", "moving") == 1 and Memory.value("game", "battle") == 0 + return Memory.value("player", "moving") == 0 +end + +-- Custom path + +--[[function Walk.invertCustom(silent) + if not silent then + customIdx = customIdx + customDir + end + customDir = customDir * -1 +end + +function Walk.custom(cpath, increment) + if not cpath then + customIdx = 1 + customDir = 1 + return + end + if increment then + customIdx = customIdx + customDir + end + local tile = cpath[customIdx] + if not tile then + if customIdx < 1 then + customIdx = #cpath + else + customIdx = 1 + end + return customIdx + end + local t1, t2 = tile[1], tile[2] + if t2 == nil then + if Player.face(t1) then + Input.press("A", 2) + end + return t1 + end + if step(t1, t2) then + customIdx = customIdx + customDir + end +end]] + +return Walk diff --git a/ai/combat.lua b/ai/combat.lua new file mode 100644 index 0000000..c7f39c8 --- /dev/null +++ b/ai/combat.lua @@ -0,0 +1,420 @@ +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[6] = "bug" +types[7] = "ghost" --?? +types[8] = "steel" --?? + +types[10] = "fire" +types[11] = "water" +types[12] = "grass" +types[13] = "electric" +types[14] = "psychic" +types[15] = "ice" +types[16] = "dragon" +types[17] = "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/emerald/strategies.lua b/ai/emerald/strategies.lua new file mode 100644 index 0000000..93fca05 --- /dev/null +++ b/ai/emerald/strategies.lua @@ -0,0 +1,178 @@ + +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.setHour = function() + if Strategies.initialize() then + status.tempDir = false + end + local Main = Memory.value("menu", "main") + local Current = Memory.value("menu", "settings_current") + local Row = Memory.value("menu", "start_menu_row") + local Hours = Memory.value("menu", "hours_row") + local Mins = Memory.value("menu", "minutes_row") + + if Main == 29 then + local Waiting = Input.isWaiting() + if not Waiting then + if Current == 76 then + if Hours < GAME_HOURS then + Input.press("Right", 0) + elseif Hours > GAME_HOURS then + Input.press("Left", 0) + else + if Mins < GAME_MINUTES then + Input.press("Right", 0) + elseif Mins > GAME_MINUTES then + Input.press("Left", 0) + else + Input.press("A", 2) + end + end + elseif Current == 78 then + if Row == 1 then + Input.press("Up", 2) + else + Input.press("A", 2) + return true + end + end + end + else + Input.press("A", 2) + 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..81c0c50 --- /dev/null +++ b/ai/strategies.lua @@ -0,0 +1,369 @@ +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, + + setDirection = function(data) + if Player.isFacing(data.dir) then + return true + else + Input.press(data.dir, 2) + return true + 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, + + 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..751fd7e --- /dev/null +++ b/data/paths.lua @@ -0,0 +1,38 @@ +local paths = { + -- Inside truck + {65, {9,9}, {12,9}}, + -- Go to Mom House + {9, {11,17}}, + -- Mom house + {1, {15,15}, {15,14}, {15,9}}, + -- Bedroom + {2, {14,9}, {12,9}, {s="setDirection",dir="Up"}, {s="setHour"}, {12,9}, {14,9}, {14,8}}, + -- Dad TV Show + {1, {15,10}, {11,12}, {15,12}, {15,16}}, + -- Go to neightbor house + {9, {12,16}, {21,16}, {21,15}}, + -- Inside Neighbor house + {3, {9,15}, {9,9}}, + -- Inside Bedroom + {4, {8,9}, {8,10}, {12,10}, {s="setDirection",dir="Down"}, {s="speak"}, {8,10}, {8,8}}, + -- Inside house + {3, {9,10}, {9,16}}, + -- Go Help prof. + {9, {21,16}, {18,16}, {18,8}} + +} + +--Remake Path for Girl +if GAME_GENDER == 2 then + paths[2] = {9, {20,17}} + paths[3] = {3, {9,15}, {9,14}, {9,9}} + paths[4] = {4, {8,9}, {10,9}, {s="setDirection",dir="Up"}, {s="setHour"}, {10,9}, {8,9}, {8,8}} + paths[5] = {3, {9,10}, {13,12}, {9,12}, {9,16}} + paths[6] = {9, {21,16}, {12,16}, {12,15}} + paths[7] = {1, {15,15}, {15,9}} + paths[8] = {2, {14,9}, {14,10}, {10,10}, {s="setDirection",dir="Down"}, {s="speak"}, {14,10}, {14,8}} + paths[9] = {1, {15,10}, {15,16}} + paths[10][2] = {12,16} +end + +return paths diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..07330b2 --- /dev/null +++ b/main.lua @@ -0,0 +1,239 @@ +--################################################## +--############# ############ +--############# 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 = "Emerald" -- Set to Ruby/Sapphire or Emerald +GAME_HOURS = 17 -- Set the internal game hour (0-23h) +GAME_MINUTES = 35 -- Set the internal game minutes (0-59min) +GAME_GENDER = 1 -- Set the player gender (1-2 // boy-girl) + +GAME_TEXT_SPEED = 2 -- Set the Text Speed (0-2 // slow-fast) +GAME_BATTLE_ANIMATION = 1 -- Set the battle animation (0-1 // no-yes) +GAME_BATTLE_STYLE = 1 -- Set the battle style (0-1 // shift-set) +GAME_SOUND_STYLE = 1 -- Set the sound style (0-1 // stereo-mono) +GAME_BUTTON_STYLE = 0 -- Set the button style (0-2) +GAME_WINDOWS_STYLE = 4 -- Set the windows style (0-19) + +--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 = "TeStInG" -- Player name +RIVAL_NAME = "URRival" -- Rival name +MUDKIP_NAME = "Muddy" -- Set Mudkip name + +--NAMES SETTINGS TIPS : +-- - Can use up to 7 letter ingame +-- - Upper, Lower case and Number allowed +-- - Specials Characters : +-- < = male symbol +-- > = female symbol +-- { or } = " +-- [ or ] = ' + + + + +--##################################################################################### +--##################################################################################### +--########### ############### +--########### PLEASE DON'T EDIT ANYTHING BELLOW, IT'S AT YOUR RISK ############### +--########### START CODE (hard hats on) ############### +--########### ############### +--##################################################################################### +--##################################################################################### + +-- SET VALUES + +local VERSION = "0.1-BETA" + +local START_WAIT = 99 +local hasAlreadyStartedPlaying = false +local oldSeconds +local running = true +local lastHP + +--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 + +-- 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 + Utils.reset() + -- 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 + +-- MAIN LOOP + +local previousMap + +while true do + local currentMap = Memory.double("game", "map") + if currentMap ~= previousMap then + Input.clear() + previousMap = currentMap + 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() and currentMap == 0 then + if running then + if not hasAlreadyStartedPlaying then + if emu.framecount() == 1 then client.reboot_core() end + hasAlreadyStartedPlaying = true + else + resetAll() + end + else + Settings.startNewAdventure(START_WAIT) + end + else + if not running then + Bridge.liveSplit() + running = true + end + --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) + --elseif Walk.strategy then + if Walk.strategy then + if Strategies.execute(Walk.strategy) then + Walk.traverse(currentMap) + end + --elseif battleState > 0 then + -- if not Control.shouldCatch(partySize) then + -- Battle.automate() + -- end + elseif Textbox.handle() then + Walk.traverse(currentMap) + end + end + end + + if 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 + Paint.draw(currentMap) + end + + Input.advance() + emu.frameadvance() +end + +Bridge.close() diff --git a/storage/inventory.lua b/storage/inventory.lua new file mode 100644 index 0000000..75ad5c8 --- /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..75ad5cf --- /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..3a61cbb --- /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..ee07bf8 --- /dev/null +++ b/util/input.lua @@ -0,0 +1,162 @@ +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 drawText = Utils.drawText + +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 function sendButton(button, ab, slow) + local inputTable = {} + if slow ~= nil then + if slow == false then + inputTable = {[button]=true, B=true} + else + inputTable = {[button]=true} + end + else + inputTable = {[button]=true} + end + --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 slow ~= nil then + if slow == true then + drawText(0, 60, button.."+B "..remainingFrames) + else + drawText(0, 60, button.." "..remainingFrames) + end + else + drawText(0, 60, button.." "..remainingFrames) + end + end + if ab then + button = "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) +function Input.press(button, frames, slow) + 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) + sendButton(button, false, slow) + --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 + sendButton(button, true) + --sendButton(button, false) + 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 +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 + +return Input + diff --git a/util/memory.lua b/util/memory.lua new file mode 100644 index 0000000..aba8117 --- /dev/null +++ b/util/memory.lua @@ -0,0 +1,198 @@ +local Memory = {} + +local memoryNames = { + setting = { + text_speed = 0x5E0A, --139-136-128 + battle_animation = 0x5E0C, --141=on 133=off + battle_style = 0x5E0E, --135=shift 132=set + sound_style = 0x5E10, --142=mono 145=stereo + button_style = 0x5E12, -- + windows_style = 0x5E14, --0 to 7 + }, + text_inputing = { + column = 0x2065E, + row = 0x20660, + mode = 0x206A4, + }, + --[[inventory = { + item_count = 0x1892, + item_base = 0x1893, + },]] + menu = { + row = 0x5E0A, + input_row = 0x5E08, + settings_row = 0x5E14, + start_menu_row = 0x3CD92, + hours_row = 0x5E0C, --(0-23) + minutes_row = 0x5E0E, --(0-59) + + --item_row = 0x110C, + --item_row_size = 0x110D, + + column = 0x3CE5D, + current = 0x0820, -- + --size = 0x0FA3, + main_current = 0x5E00, + option_current = 0x0859, + settings_current = 0x5E01, + --shop_current = 0x0F87, + --selection = 0x0F78, + text_input = 0x20667, --1=inputing + text_length = 0x217FA, -- -7 + main = 0x0819, + --pokemon = 0x0C51, --TO DO, USED WHILE EVOLVING + --selection_mode = 0x0C35, --TO DO, USED WHEN SWAPING MOVE + --transaction_current = 0x0F8B,--TODO, USED FOR SHOPPING + }, + player = { + --name = 0x147D, + --name2 = 0x1493, + moving = 0x37593, --1 = moving + facing = 0x37368, --17=S // 34=N // 51=W // 68=E + --repel = 0x1CA1, + --party_size = 0x1CD7, + }, + game = { + --battle = 0x122D, --1=wild 2=trainer + ingame = 0x0E08, + textbox = 0x0E40, + --textbox = 0x5DF0, + --textboxing = 0x5ECC, + }, + time = { + --hours = 0x24A87, + --minutes = 0x24A88, + --seconds = 0x24A89, + --frames = 0x24A8A, + frames = 0x249C0, + }, + --[[shop = { + --transaction_amount = 0x110C, + },]] + battle = { + text = 0x24068, -- + menu = 0x5D60, --106=106(att) // 186=94(main) // 128=233(item) // 145=224(pkmon) + --menuX = 0x0FAA, --used for battle menu Row-X + --menuY = 0x0FA9, --used for battle menu Row-Y + --battle_turns = 0x06DD, --USED FOR DSUM ESCAPE?? + + opponent_id = 0x240DC, --or 0x1204?? + opponent_level = 0x24106, + opponent_type1 = 0x240FD, + opponent_type2 = 0x240FE, + --opponent_move_id = 0x240E8, --used to get opponent moves ID's + --opponent_move_pp = 0x24100, --used to get opponent moves PP's + + our_id = 0x24084, + --our_status = 0x063A, + our_level = 0x240AE, + our_type1 = 0x240A5, + our_type2 = 0x240A6, + --our_move_id = 0x24090, --used to get our moves ID's + --our_move_pp = 0x240A8, --used to get our moves PP's + + --our_pokemon_list = 0x1288 --used to retract any of our Pokemons values (slot 1-6) + + --attack_turns = 0x06DC, --NOT USED?? + --accuracy = 0x0D1E, --NOT DONE YET + --x_accuracy = 0x1063, --NOT DONE YET + --disabled = 0x0CEE, --NOT DONE YET + --paralyzed = 0x1018, --NOT DONE YET + --critical = 0x105E, --NOT DONE YET + --miss = 0x105F, --NOT DONE YET + --our_turn = 0x1FF1, --NOT DONE YET + + --opponent_next_move = 0xC6E4, --NOT USED?? + --opponent_last_move = 0x0FCC, --NOT DONE YET AND NOT USED?? + --opponent_bide = 0x106F, --NOT DONE YET AND NOT USED?? + }, + + --[[pokemon = { + exp1 = 0x1179, + exp2 = 0x117A, --NOT DONE YET + exp3 = 0x117B, + },]] +} + +local doubleNames = { + battle = { + opponent_hp = 0x24104, + opponent_max_hp = 0x24108, + opponent_attack = 0x240DE, + opponent_defense = 0x240E0, + opponent_speed = 0x240E2, + opponent_special_attack = 0x240E4, + opponent_special_defense = 0x240E6, + + our_hp = 0x240AC, + our_max_hp = 0x240B0, + our_attack = 0x24086, + our_defense = 0x24088, + our_speed = 0x2408A, + our_special_attack = 0x2408C, + our_special_defense = 0x2408E, + }, + + game = { + map = 0x37359, + }, + + player = { + x = 0x37364, + y = 0x37366, + }, + + --[[pokemon = { + attack = 0x117E, + defense = 0x1181, --NOT DONE YET + speed = 0x1183, + special = 0x1185, + },]] +} + +local function raw(address) + if string.len(tostring(address)) == 6 then + memory.usememorydomain("EWRAM") + else + memory.usememorydomain("IWRAM") + 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) + local memoryAddress = memoryNames[section] + if key then + memoryAddress = memoryAddress[key] + end + return raw(memoryAddress) +end + +function Memory.getAddress(section, key) + local memoryAddress = memoryNames[section] + if key then + memoryAddress = memoryAddress[key] + end + return memoryAddress +end + +return Memory diff --git a/util/menu.lua b/util/menu.lua new file mode 100644 index 0000000..5bc9879 --- /dev/null +++ b/util/menu.lua @@ -0,0 +1,209 @@ +local Menu = {} + +local Input = require "util.input" +local Memory = require "util.memory" + +local sliding = false + +-- 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) + return row +end + +--local function setRow(desired, throttle, scrolls, menuType, loop) +local function setRow(desired, throttle, menuType, loop) + 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 + return Menu.balance(currentRow, desired, true, loop, throttle) +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, 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 + 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, + button_style = 4, + windows_style = 5, + } + if Memory.value("setting", name) == desired then + return true + end + if setRow(rowFor[name], 2, "input") 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 +end + +function Menu.close() + 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..9e501de --- /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) + local px, py = Player.position() + drawText(0, 30, currentMap.." : "..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(0, 70, 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, 90, attack.." | "..defense.." | "..speed.." | "..scl_att.." | "..scl_def) + end]] + local enc = " encounter" + if encounters > 1 then + enc = enc.."s" + end + drawText(0, 115, 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..0482f31 --- /dev/null +++ b/util/player.lua @@ -0,0 +1,42 @@ +local Player = {} + +local Textbox = require "action.textbox" + +local Input = require "util.input" +local Memory = require "util.memory" + +local facingDirections = {Up=34, Right=68, Left=51, Down=17} + +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") ~= 0 +end + +function Player.position() + return Memory.double("player", "x"), Memory.double("player", "y") +end + +return Player diff --git a/util/settings.lua b/util/settings.lua new file mode 100644 index 0000000..c35cb5c --- /dev/null +++ b/util/settings.lua @@ -0,0 +1,122 @@ +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 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.button_style = GAME_BUTTON_STYLE +desired.windows_style = GAME_WINDOWS_STYLE + +local function isEnabled(name) + return Memory.value("setting", name) == desired[name] +end + +-- PUBLIC + +function Settings.set(...) + --set vars + local startMenu = Memory.value("menu", "main") + local menuRow = Memory.value("menu", "row") + + --set settings + if not settings_done then + for i,name in ipairs(arg) do + if not isEnabled(name) then + --open settings menu + if startMenu ~= 51 then + if menuRow ~= 1 then + Input.press("Down", 2) + else + Input.press("A", 2) + end + --set options + else + Menu.setOption(name, desired[name]) + end + return false + end + end + --setting done + settings_done = true + end + + --close option menu + if startMenu == 51 then + Input.press("B", 2) + end + if startMenu ~= 51 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 SettingsCurrent = Memory.value("menu", "settings_current") + local Row = Memory.value("menu", "row") + local GenderRow = Memory.value("menu", "settings_row") + + --press A + if startMenu == 30 then + Input.press("A", 2) + --press Start + elseif startMenu == 180 or startMenu == 20 or startMenu == 23 then + if not Setting_done and math.random(0, startWait) == 0 then + Input.press("Start") + end + --set settings + elseif startMenu == 49 or startMenu == 51 then + if not Setting_done then + if Settings.set("text_speed", "battle_animation", "battle_style", "sound_style", "button_style", "windows_style") then + Setting_done = true + end + else + if Row ~= 0 then + Input.press("Up", 2) + else + Input.press("A", 2) + end + end + --Set Gender + elseif startMenu == 19 then + if SettingsCurrent == 13 or SettingsCurrent == 14 then + if GenderRow == 1 and GAME_GENDER == 2 then + Input.press("Down", 2) + elseif GenderRow == 2 and GAME_GENDER == 1 then + Input.press("Up", 2) + else + Input.press("A", 2) + end + else + Input.press("A", 2) + end + --Set Name&start adventure + elseif startMenu == 31 then + if SettingsCurrent < 100 then + --reset setting not done + Setting_done = false + --set our name + Textbox.name(PLAYER_NAME, true) + else + --start adventure + Input.press("A", 2) + end + else + Input.press("A", 2) + end +end + +return Settings diff --git a/util/utils.lua b/util/utils.lua new file mode 100644 index 0000000..e521ed8 --- /dev/null +++ b/util/utils.lua @@ -0,0 +1,159 @@ +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 + +local Hours = 0 +local Minutes = 0 +local Seconds = 0 +local Current_frame = 0 +local Current_frame_changed = 0 + +function Utils.reset() + Hours = 0 + Minutes = 0 + Seconds = 0 + Current_frame = 0 + Current_frame_changed = 0 +end + +function Utils.igt() + local hours = Hours + local mins = Minutes + local secs = 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() + if not Utils.ingame() then + return "0:00:00" + else + --local secs = Memory.value("time", "seconds") + --local mins = Memory.value("time", "minutes") + --local hours = Memory.value("time", "hours") + local frames = Memory.value("time", "frames") + if Current_frame ~= frames then + Current_frame = frames + Current_frame_changed = Current_frame_changed + 1 + if Current_frame_changed == 60 then + Current_frame_changed = 0 + Seconds = Seconds + 1 + if Seconds == 60 then + Seconds = 0 + Minutes = Minutes + 1 + if Minutes == 60 then + Minutes = 0 + Hours = Hours + 1 + end + end + end + end + return Hours..":"..clockSegment(Minutes)..":"..clockSegment(Seconds) + end +end + +--[[function Utils.frames() + if Utils.ingame() then + + else + 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