--[[ SNES Madou Monogatari - Hanamaru Daiyouchienji Semi-automated Map Maker, written by gocha. W-I-P! (>_<) ]]-- -- Usually, you don't need to adjust the following size, but if the script -- didn't work properly because of something which begins a dialog, -- decrease the size and try to avoid it (I don't know if it will succeed though). -- The size is originally 256x159, but it must be the multiple of 8. -- Therefore, if you have no problem, just set it to 256x152 (152=8*19). local imagePieceSize = { x = 256, y = 8*19 } local debugDisplay = true -- [ startup ] ----------------------------------------------------------------- require("gd") if emu then error("Unknown SNES EmuLua host.") end emu = snes9x if not emu then error("Unknown SNES EmuLua host.") end -- Instant ROM check if string.char(unpack(memory.readbyterange(0x80ffb2, 4))) ~= "ADYJ" then error("Unsupported ROM: make sure if the current ROM is Madou Monogatari") end -- [ ROM/RAM addresses ] ------------------------------------------------------- RAM = { fadeLevel = 0x7e0c5b, -- screen fade level pauseFlag = 0x7e0266, -- becomes 1 when the game is paused overworldFlag = 0x7e0980, -- becomes 1 when the game is overworld map frames = 0x7e1300, -- ingame frame counter arlePosWord = 0x7e16c5, -- Arle's position in OW (0byyyyyyyyyxxxxxxx) arleXPosR = 0x7e0025, arleYPosR = 0x7e0027, arleXPosW = 0x7e4827, arleYPosW = 0x7e4829, bgXPosW = 0x7e16e2, bgYPosW = 0x7e16e6, roomLeft = 0x7e1733, roomTop = 0x7e172f, roomRight = 0x7e1735, roomBottom = 0x7e1731, disableOWMove = 0x7e1401, encounter = 0x7e1408 } if memory.readbyte(RAM.fadeLevel) ~= 15 or memory.readbyte(RAM.overworldFlag) ~= 1 then gui.popup("Let the game show the overworld screen, or the script won't run.") return end if memory.readword(RAM.arleXPosW) % 8 ~= 0 or memory.readword(RAM.arleYPosW) % 8 ~= 0 then gui.popup("Arle's position is odd... you cheated?") return end if input.popup([[This script scans the game screen continuously, then makes a large map of the current room. Before running the script, you may need to confirm some conditions. * The game shows the overworld screen. To make Arle stand about the center of the room is good. * Make sure if "graphic clip windows" is OFF. Usually, you can toggle it by pressing 8. * Make sure if "text in message" is OFF, or annoying texts will appear in the screenshot. * [NYI] Auto frameskip is not bad, but if you want to control animated sprites properly, set the frame skip rate to 0. Make sure if the frameskip amount is set to 0 (NOT auto frameskip) Warnings: * DO NOT LOAD ANY SAVESTATES DURING RUNNING THE SCRIPT. Continue?]], "okcancel") ~= "ok" then return end local savScriptStart = savestate.create() savestate.save(savScriptStart, "quiet") local snapInfo = { x = 0, y = 0, width = imagePieceSize.x, height = imagePieceSize.y } local room = { left = memory.readword(RAM.roomLeft), top = memory.readword(RAM.roomTop), right = memory.readword(RAM.roomRight), bottom = memory.readword(RAM.roomBottom) } room.scrollWidth, room.scrollHeight = room.right - room.left, room.bottom - room.top room.width, room.height = room.scrollWidth + 256, room.scrollHeight + 152 -- why it isn't 159? room.xPieces, room.yPieces = math.ceil(room.width / snapInfo.width), math.ceil(room.height / snapInfo.height) local gdMap = gd.createTrueColor(room.width, room.height) local snapProgress = { x = 0, y = 0 } if not gdMap then error("Memory allocation error (gd.createTrueColor failed)") end -- [ main routines ] ----------------------------------------------------------- local captStates = { enterPause = 0, quitPause = 1, doScreenShot = 2, didScreenShot = 3, endMapMaking = 4 } local captState = captStates.enterPause local stateTimeCount = 0 local aborted = true emu.registerexit( function() if aborted then memory.writebyte(RAM.disableOWMove, 0) end end) emu.registerbefore( function() local pad = {} local tookScreenShot = false if captState == captStates.enterPause then -- adjust screen position local targetX, targetY = room.left + (snapProgress.x * snapInfo.width), room.top + (snapProgress.y * snapInfo.height) local targetStepX, targetStepY = math.floor((targetX + 0x70) / 8), math.floor((targetY + 0x58) / 8) local stepXMax, stepYMax = math.floor((room.right + 256) / 8), math.floor((room.bottom + 159) / 8) if targetStepX > stepXMax then targetStepX = stepXMax end if targetStepY > stepYMax then targetStepY = stepYMax end local bgX, bgY = memory.readword(RAM.bgXPosW), memory.readword(RAM.bgYPosW) local arleX, arleY = memory.readword(RAM.arleXPosW), memory.readword(RAM.arleYPosW) local stepX, stepY = math.floor(arleX/8), math.floor(arleY/8) local realStepX, realStepY = math.floor(memory.readword(RAM.arlePosWord) % 128), math.floor(memory.readword(RAM.arlePosWord) / 128) local moveSpeed = 8 -- do not change if stepX == targetStepX and stepY == targetStepY then if stateTimeCount >= 4 and memory.readbyte(RAM.overworldFlag) == 1 and memory.readword(RAM.frames) % 2 == 0 then pad.start = 1 -- autofire the start button memory.writebyte(RAM.disableOWMove, 0) end else if stepX < targetStepX then bgX = bgX + moveSpeed arleX = arleX + moveSpeed stepX = stepX + 1 elseif stepX > targetStepX then bgX = bgX - moveSpeed arleX = arleX - moveSpeed stepX = stepX - 1 end if stepY < targetStepY then bgY = bgY + moveSpeed arleY = arleY + moveSpeed stepY = stepY + 1 elseif stepY > targetStepY then bgY = bgY - moveSpeed arleY = arleY - moveSpeed stepY = stepY - 1 end -- clipping if bgX < room.left then bgX = room.left end if bgY < room.top then bgY = room.top end if bgX > room.right then bgX = room.right end if bgY > room.bottom then bgY = room.bottom end memory.writeword(RAM.bgXPosW, bgX) memory.writeword(RAM.bgYPosW, bgY) memory.writeword(RAM.arleXPosW, arleX) memory.writeword(RAM.arleYPosW, arleY) memory.writebyte(RAM.disableOWMove, 1) if stepX == targetStepX and stepY == targetStepY then stateTimeCount = 0 end end elseif captState == captStates.quitPause then if memory.readbyte(RAM.pauseFlag) == 1 and memory.readword(RAM.frames) % 2 == 0 then pad.start = 1 -- autofire the start button end elseif captState == captStates.doScreenShot then if memory.readbyte(RAM.fadeLevel) == 15 then if stateTimeCount >= 30 then local gdSnap = gd.createFromGdStr(gui.gdscreenshot()) local mapX, mapY = snapProgress.x * snapInfo.width, snapProgress.y * snapInfo.height if (snapProgress.x + 1) == room.xPieces then mapX = room.width - snapInfo.width end if (snapProgress.y + 1) == room.yPieces then mapY = room.height - snapInfo.height end gd.copy(gdMap, gdSnap, mapX, mapY, snapInfo.x, snapInfo.y, snapInfo.width, snapInfo.height) tookScreenShot = true snapProgress.x = snapProgress.x + 1 if not (snapProgress.x < room.xPieces) then snapProgress.x = 0 snapProgress.y = snapProgress.y + 1 end end else stateTimeCount = 0 end elseif captState == captStates.didScreenShot then savestate.load(savScriptStart, "quiet") end joypad.set(1, pad) stateTimeCount = stateTimeCount + 1 -- state transition local newState = captState if captState == captStates.enterPause then if memory.readbyte(RAM.pauseFlag) == 1 then newState = captStates.quitPause end elseif captState == captStates.quitPause then if memory.readbyte(RAM.overworldFlag) == 1 and memory.readbyte(RAM.fadeLevel) == 15 then newState = captStates.doScreenShot end elseif captState == captStates.doScreenShot then if tookScreenShot then if not (snapProgress.y < room.yPieces) then newState = captStates.endMapMaking else newState = captStates.didScreenShot end end elseif captState == captStates.didScreenShot then newState = captStates.enterPause end if captState ~= newState then captState = newState stateTimeCount = 0 end memory.writebyte(RAM.encounter, 0) end) emu.registerafter( function() end) gui.register( function() if not debugDisplay and captState ~= captStates.enterPause then return end local fadeLevel = memory.readbyte(RAM.fadeLevel) if fadeLevel > 15 then fadeLevel = 0 end fadeLevel = 15 gui.opacity(0.68 * fadeLevel / 15.0) local targetX, targetY = room.left + (snapProgress.x * snapInfo.width), room.top + (snapProgress.y * snapInfo.height) local targetStepX, targetStepY = math.floor((targetX + 0x70) / 8), math.floor((targetY + 0x58) / 8) local stepXMax, stepYMax = math.floor((room.right + 256) / 8), math.floor((room.bottom + 159) / 8) if targetStepX > stepXMax then targetStepX = stepXMax end if targetStepY > stepYMax then targetStepY = stepYMax end local bgX, bgY = memory.readword(RAM.bgXPosW), memory.readword(RAM.bgYPosW) local arleX, arleY = memory.readword(RAM.arleXPosW), memory.readword(RAM.arleYPosW) local stepX, stepY = math.floor(arleX/8), math.floor(arleY/8) local realStepX, realStepY = math.floor(memory.readword(RAM.arlePosWord) % 128), math.floor(memory.readword(RAM.arlePosWord) / 128) gui.text(2, 2, string.format([[ ARLE (%03X,%03X) BACK (%03X,%03X)-(%03X,%03X) STEP (%d,%d:%d,%d)-(%d,%d)-(%d,%d) SNAP (%d,%d)-(%d,%d) STAT %d(%d)]], arleX, arleY, bgX, bgY, targetX, targetY, stepX, stepY, realStepX, realStepY, targetStepX, targetStepY, stepXMax, stepYMax, snapProgress.x+1, snapProgress.y+1, room.xPieces, room.yPieces, captState, stateTimeCount)) end) while captState ~= captStates.endMapMaking do emu.frameadvance() end gdMap:pngEx("hanamaroom.png", 1) emu.message("Saved the map of the room.") savestate.load(savScriptStart, "quiet") aborted = false