User File #638733845237897291

Upload All User Files

#638733845237897291 - [DQ9] Scrape Map Data (USA)

DQ9_ScrapeMapData_USA.lua
4 downloads
Uploaded 7 days ago by TKG (see all 11)
Prints a bunch of map data from your inventory to the lua console as comma separated values (CSV), so you can paste it into a spreadsheet and split it into columns (Data -> Split text to columns) or otherwise save it as a csv file.
To print to the console, load either a church save or quicksave. If you're already in the game, open the map menu or create a quicksave. Whenever you discard or obtain new maps, you can reprint to the console by clicking the restart button.
To select all and copy in the lua console, click anywhere -> ctrl + home -> ctrl + shift + end -> ctrl + C. Make sure not to select any unneeded lines like "script returned but is still running registered functions" or "user clicked stop...script stopped." You can avoid printing those lines by checking the "STDOUT" button in the lua console. It needs to be unchecked to print map data though.
"Deftness (36th)", "Deftness (37th)", and "Map Method Metal Slimes (35th)" are used for creating map methods of metal slimes inside grottoes.
Screenshots of MelonGx's "dq9ds_due_10may" file:
-- Memory map by eidako/yab:
-- https://gamefaqs.gamespot.com/ds/937281-dragon-quest-ix-sentinels-of-the-starry-skies/faqs/68804

local tbl_addr = {
   mapCount = 0x020F98CC, -- (uint8) number of maps in inventory (0-99)
   mapBase = 0x020F98CE   -- (uint8) treasure map base addr (status + current map + type)
}

local tbl_offset = {
   mapIndex = 0x1C,
   discover = 0x1,      -- Ascii2f[10] same as ascii but characters are 0x2F less(?)
   conquer = 0xB,       -- Ascii2f[10] same as ascii but characters are 0x2F less(?)
   location = 0x15,
   treasure = 0x16,
   fqOrLegacyId = 0x17, -- (uint8) final quality (normal), boss number (legacy)
   bossLv = 0x18,       -- (uint8) legacy only
   seedOrTurns = 0x1A   -- (uint16) map seed (normal), minimum turns (legacy)
}

-- mult/inc parameters for AT positions
local tbl_param = {
   {2371908317, 2518396845}, -- 13th (environment)
   {2298363417, 639546082},  -- 14th (depth)
   {729943717, 1381971571},  -- 15th (starting monster rank)
   {1601471041, 1695770928}, -- 16th (boss)
   {4009059357, 1588911645}, -- 29th (prefix)
   {1315599961, 2518522002}, -- 30th (suffix)
   {4114186725, 33727075},   -- 31st (locale)
   {2335052929, 1680572000}, -- 32nd (level)
   {3248503605, 527630783},  -- 35th (map method monster ID)
   {2202098577, 1194991756}, -- 36th (map method deftness 1)
   {1876961981, 3253908437}  -- 37th (map method deftness 2)
}

local tbl_ascii = {
   {0x00, 0x04, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x4D, 0x53, 0x54, 0x55, 0x58, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, 0x67, 0x69, 0x6A, 0x6B, 0x6D, 0x6F, 0x71, 0x72, 0x74, 0x76, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xF0, 0xFF},

   {"", "'", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "EUR", ",", ",,", "OE", "oe", "!", "GBP", "<<", ">>", "?", "!", '"', "#", "$", "%", "&", "(", ")", "+", "-", ".", "/", ";", "=", "?", "[", "]", "_", "A", "A", "A", "A", "AE", "C", "E", "E", "E", "E", "I", "I", "I", "I", "N", "O", "O", "O", "O", "O", "U", "U", "U", "U", "ss", "a", "a", "a", "a", "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i", "n", "o", "o", "o", "o", "o", "o", "o", "o", "o", "~", ":-)", "*", "@", "cent", "a", "deg", "<-", "^", "->", "v", "-", " "}
}

local tbl_contents = {
   statusId = {0x09, 0x0A, 0x0C, 0x11, 0x12, 0x14}, -- If a map is currently being followed: +0x20
   statusName = {"New", "Discovered", "Cleared", "New", "Discovered", "Cleared"}, -- Normal/Normal/Normal/Legacy/Legacy/Legacy

   dropId = {0, 1, 3, 5, 7}, -- 1st = +1, 2nd = +2, 3rd = +4
   dropName = {"-/-/-", "1/-/-", "1/2/-", "1/-/3", "1/2/3"},

   legacyName = {"Dragonlord", "Malroth", "Baramos", "Zoma", "Psaro", "Estark", "Nimzo", "Murdaw", "Mortamor", "Nokturnus", "Orgodemir", "Dhoulmagus", "Rhapthorne"},

   rankMin = {2, 56, 61, 76, 81, 101, 121, 141, 161, 181, 201, 221},
   rankMax = {55, 60, 75, 80, 100, 120, 140, 160, 180, 200, 220, 248},

   envMin = {0, 30, 70, 80, 90},
   envMax = {29, 69, 79, 89, 99},
   envName = {"Caves", "Ruins", "Ice", "Water", "Fire"},

   depthMod = {3, 3, 3, 5, 5, 5, 5, 7, 7, 6, 5, 3},
   depthInc = {2, 4, 4, 6, 6, 8, 10, 10, 10, 11, 12, 14},

   smrMod = {3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 2, 1},
   smrInc = {1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 8, 9},

   bossMod = {275, 275, 300, 300, 280, 205, 170, 120, 90, 80, 560, 560},
   bossInc = {0, 0, 100, 100, 200, 275, 350, 400, 450, 480, 0, 0},
   bossMin = {0, 100, 200, 275, 350, 400, 450, 480, 500, 520, 540, 550},
   bossMax = {99, 199, 274, 349, 399, 449, 479, 499, 519, 539, 549, 559},
   bossName = {"Equinox", "Nemean", "Shogum", "Trauminator", "Elusid", "Sir Sanguinus", "Atlas", "Hammibal", "Fowleye", "Excalipurr", "T-Wrecks", "Greygnarl"},

   prefixMod = {5, 5, 5, 5, 6, 6, 10, 10, 5},
   prefixInc = {1, 1, 4, 4, 7, 7, 7, 7, 12},
   prefixName = {"Clay", "Rock", "Granite", "Basalt", "Graphite", "Iron", "Copper", "Bronze", "Steel", "Silver", "Gold", "Platinum", "Ruby", "Emerald", "Sapphire", "Diamond"},

   localeMod = {2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 3},
   localeInc = {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 6},
   localeName = {"Cave", "Tunnel", "Cave", "Cave", "Cave", "Mine", "Mine", "Crevasse", "Marsh", "Mine", "Lair", "Lair", "Icepit", "Lake", "Crater", "Path", "Path", "Snowhall", "Moor", "Dungeon", "Crypt", "Crypt", "Crypt", "Crypt", "Crypt", "Nest", "Ruins", "Tundra", "Waterway", "Nest", "World", "World", "World", "World", "World", "Abyss", "Maze", "Glacier", "Chasm", "Void"},

   suffixInc = {1, 1, 1, 4, 4, 4, 7, 7, 7, 10, 10, 10},
   suffixName = {"Joy", "Bliss", "Glee", "Doubt", "Woe", "Dolour", "Regret", "Bane", "Fear", "Dread", "Hurt", "Gloom", "Doom", "Evil", "Ruin", "Death"}
}

local tbl_monsterSpawn = {
   {0, 1426, 4856, 7282, 10427, 11917, 15820, 16385, 16950, 18725, 20026, 21846, 29648, 30428, 31130, 31555},
   {1425, 4855, 7281, 10426, 11916, 15819, 16384, 16949, 18724, 20025, 21845, 29647, 30427, 31129, 31554, 32767},
   {"LMS (Ruins MR7)", "-", "GS (Ruins MR11)", "-", "MM (Ruins MR3)", "-", "LMS (Fire MR4)", "LMS (Fire MR4) / GJ (Caves MR7) / MKS (Water MR8)", "GJ (Caves MR7) / MKS (Water MR8)", "-", "PKJ (Ruins MR12)", "-", "GJ (Ruins MR8)", "GJ (Ruins MR8) / MKS (Fire MR9)", "GJ (Ruins MR8) / MKS (Fire MR9) / GS (Ice MR10)", "GJ (Ruins MR8/10) / MKS (Fire MR9/Caves MR10) / GS (Ice MR10)"}
}

local scriptComplete = false

local function getHex(decimal, digits)
   return decimal and string.format("%0" .. digits .. "X", decimal) or "?"
end

local function lookup(searchKey, searchTbl1, searchTbl2, returnTbl, searchMode)
   for i = 1, #searchTbl1 do
       if (searchMode == "exact" and searchKey == searchTbl1[i]) or
          (searchMode == "range" and searchKey >= searchTbl1[i] and searchKey <= searchTbl2[i]) then
           return {index = i, value = returnTbl[i]}
       end
   end
   return {index = 1, value = "?"}
end

local function maskCurrentMap(statusType)
   local currentMap = 0x20
   return bit.band(statusType, currentMap) == currentMap and statusType - currentMap or statusType
end

local function getAscii(nameBase)
   local ascii = ""
   for i = 0, 9 do
      local char = lookup(memory.readbyte(nameBase + i), tbl_ascii[1], nil, tbl_ascii[2], "exact")
      ascii = ascii..char.value
   end
   return ascii == "" and "?????" or ascii
end

local function getOutput(seed, a, c)
   local hi = bit.rshift(seed, 16)
   local lo = bit.band(seed, 65535) * a + c
   local cr = bit.rshift(lo, 16)
   hi = bit.band(hi * a + cr, 65535)
   return bit.band(hi, 32767)
end

local function getMR(smr, depth)
   if depth >= 13 then return smr.."/"..(smr + 1).."/"..(smr + 2).."/"..(smr + 3)
   elseif depth >= 9 then return smr.."/"..(smr + 1).."/"..(smr + 2)
   elseif depth >= 5 then return smr.."/"..(smr + 1)
   end
   return smr
end

local function getSuffix(rand, bossIndex)
   local mod = 6
   if bossIndex > 9 then mod = 7 end
   local suffixIndex = rand % mod + tbl_contents.suffixInc[bossIndex]
   return tbl_contents.suffixName[suffixIndex]
end

local function getLv(rand, depth, smr, bossIndex)
   local lv = rand % 11 - 5 + (depth + smr + bossIndex - 4) * 3
   if lv < 1 then return 1
   elseif lv > 99 then return 99
   end
   return lv
end

local function getDeftness(rand)
   local dft = math.ceil(rand / 32768 * 100 - 2) * 20 -- 2 = normal encounter (not behind)
   return (dft <= 0) and 0 or (dft > 999 and "-" or dft)
end

local function main()
   if scriptComplete then return end
   scriptComplete = true

   local maps = memory.readbyte(tbl_addr.mapCount)
   if maps > 0 then
      local HEADER_ROW = "#,Status,Discovered by,Conquered by,Drops,Name,Lv,Min Turns,FQ,Rank,Seed,@,Type,Depth,Monster Ranks,Boss,Deftness (36th), Deftness (37th), Map Method Metal Slimes (35th),Link"
      print(HEADER_ROW)

      for i = 1, maps do
         local base = tbl_offset.mapIndex * (i - 1) + tbl_addr.mapBase
         local statusMasked = maskCurrentMap(memory.readbyte(base))
         local status = lookup(statusMasked, tbl_contents.statusId, nil, tbl_contents.statusName, "exact")
         local disc = getAscii(base + tbl_offset.discover)
         local conq = getAscii(base + tbl_offset.conquer)
         local drops = lookup(memory.readbyte(base + tbl_offset.treasure), tbl_contents.dropId, nil, tbl_contents.dropName, "exact")
         local loc = getHex(memory.readbyte(base + tbl_offset.location), 2)
         local finalQualityOrLegacyId = memory.readbyte(base + tbl_offset.fqOrLegacyId)
         local seedTurns = memory.readword(base + tbl_offset.seedOrTurns)
         local LEGACY_NEW = 0x11

         local mapData = i..","..status.value..',"'..disc..'","'..conq..'",'.."'"..drops.value..","

         -- Normal
         if statusMasked < LEGACY_NEW then
            local tbl_output = {}
            for p = 1, 11 do
               tbl_output[p] = getOutput(seedTurns, tbl_param[p][1], tbl_param[p][2])
            end

            local rank = lookup(finalQualityOrLegacyId, tbl_contents.rankMin, tbl_contents.rankMax, tbl_contents.rankMin, "range")
            local environ = lookup(tbl_output[1] % 100, tbl_contents.envMin, tbl_contents.envMax, tbl_contents.envName, "range")
            local depth = tbl_output[2] % tbl_contents.depthMod[rank.index] + tbl_contents.depthInc[rank.index]
            local smr = tbl_output[3] % tbl_contents.smrMod[rank.index] + tbl_contents.smrInc[rank.index]
            local mr = getMR(smr, depth)
            local bossMath = tbl_output[4] % tbl_contents.bossMod[rank.index] + tbl_contents.bossInc[rank.index]
            local boss = lookup(bossMath, tbl_contents.bossMin, tbl_contents.bossMax, tbl_contents.bossName, "range")
            local prefix = tbl_contents.prefixName[tbl_output[5] % tbl_contents.prefixMod[smr] + tbl_contents.prefixInc[smr]]
            local locale = tbl_contents.localeName[(tbl_output[7] % tbl_contents.localeMod[depth - 1] + tbl_contents.localeInc[depth - 1]) * 5 - 5 + environ.index]
            local suffix = getSuffix(tbl_output[6], boss.index)
            local lv = getLv(tbl_output[8], depth, smr, boss.index)
            local metal = lookup(tbl_output[9], tbl_monsterSpawn[1], tbl_monsterSpawn[2], tbl_monsterSpawn[3], "range")
            local dft36 = getDeftness(tbl_output[10])
            local dft37 = getDeftness(tbl_output[11])

            local rankHex = getHex(rank.value, 2)
            local seedHex = getHex(seedTurns, 4)
            local YAB_LINK = "https://www.yabd.org/apps/dq9/grottodetails.php?map=" -- Append rank and seed
            
            mapData = mapData..prefix.." "..locale.." "..suffix..","..lv..",-,"..finalQualityOrLegacyId..",'"..rankHex..",'"..seedHex..",'"..loc..","..environ.value..",B"..depth..",'"..mr..","..boss.value..","..dft36..","..dft37..","..metal.value..","..YAB_LINK..rankHex..seedHex

         -- Legacy
         elseif statusMasked >= LEGACY_NEW then
            local legacy = tbl_contents.legacyName[finalQualityOrLegacyId]
            local lv = memory.readbyte(base + tbl_offset.bossLv)
            mapData = mapData..legacy.."'s Map,"..lv..","..seedTurns..",-,-,-,'"..loc..",Legacy,-,-,"..legacy..",-,-,-,-"
         end
         print(mapData)
         mapData = nil
      end
   end
end

memory.registerread(tbl_addr.mapBase, main)