--How many frames ahead of the "action" to start savestates preFrames = 45 --Game-specific RAM addresses RAM = { words = { hour = 0x7e09e0, min = 0x7e09de, sec = 0x7e09dc, mil = 0x7e09da, missiles = 0x7e09c8, superMissiles = 0x7e09cc, powerBombs = 0x7e09d0, energyTanks = 0x7e09c4, reserveTanks = 0x7e09d4, items = 0x7e09a4, weapons = 0x7e09a8, health = 0x7e09c2, reserve = 0x7e09d6 }, bytes = { itemScene = 0x7e0009, doorScene = 0x7e05f5, elevatorScene = 0x7e0e18, transition = 0x7e0925, inGame = 0x7e0054 --128 if in game } } --Game-specific information that needs to be monitored gameInfo = { time = { hour = 0, min = 0, sec = 0, mil = 0 }, pct = 0, scene = { item = 0, door = 0, elevator = 0, transition = 0, transitionStarted = false, }, health = 0, reserve = 0 } --Locations to output text output = { time = { x = 0, y = 190 }, gameInfo = { x = 0, y = 200 } } --Tells the script when to do nothing regarding savestates function DoNothing() UpdateGameInfo() if not GameActive() or memory.readbyte(RAM.bytes.inGame) ~= 128 then return true end return false end --Sets game information at the beginning of an attempt function SetGameInfo() --set in-game time gameInfo.hour = memory.readword(RAM.words.hour) gameInfo.min = memory.readword(RAM.words.min) gameInfo.sec = memory.readword(RAM.words.sec) gameInfo.mil = math.floor(memory.readword(RAM.words.mil) * 100 / 60) gameInfo.scene.item = memory.readbyte(RAM.bytes.itemScene) gameInfo.scene.door = memory.readbyte(RAM.bytes.doorScene) gameInfo.scene.elevator = memory.readbyte(RAM.bytes.elevatorScene) gameInfo.scene.transition = memory.readbyte(RAM.bytes.transition) gameInfo.health = memory.readword(RAM.words.health) gameInfo.reserve = memory.readword(RAM.words.reserve) --set percentage local missiles = math.floor(memory.readbyte(RAM.words.missiles) / 5) local superMissiles = math.floor(memory.readbyte(RAM.words.superMissiles) / 5) local powerBombs = math.floor(memory.readbyte(RAM.words.powerBombs) / 5) local energyTanks = math.floor((memory.readword(RAM.words.energyTanks) - 99) / 100) local reserveTanks = math.floor(memory.readword(RAM.words.reserveTanks) / 100) local items = memory.readword(RAM.words.items) local variaSuit = items % 2 local springBall = math.floor((items % 4) / 2) local morphBall = math.floor((items % 8) / 4) local screwAttack = math.floor((items % 16) / 8) local gravitySuit = math.floor((items % 64) / 32) local hiJumpBoots = math.floor((items % 512) / 256) local spaceJump = math.floor((items % 1024) / 512) local bomb = math.floor((items % 8192) / 4096) local speedBooster = math.floor((items % 16384) / 8192) local grappleBeam = math.floor((items % 32768) / 16384) local xRayScope = math.floor(items / 32768) local weapons = memory.readword(RAM.words.weapons) local waveBeam = weapons % 2 local iceBeam = math.floor((weapons % 4) / 2) local spazerBeam = math.floor((weapons % 8) / 4) local plasmaBeam = math.floor((weapons % 16) / 8) local chargeBeam = math.floor((weapons % 8192) / 4096) gameInfo.pct = missiles + superMissiles + powerBombs + energyTanks + reserveTanks + variaSuit + springBall + morphBall + screwAttack + gravitySuit + hiJumpBoots + spaceJump + bomb + speedBooster + grappleBeam + xRayScope + waveBeam + iceBeam + spazerBeam + plasmaBeam + chargeBeam end function CalculateRtaTime() frames = snes9x.framecount() hours = math.floor(frames / 216000) minutes = math.floor((frames - hours * 216000) / 3600) seconds = math.floor((frames - hours * 216000 - minutes * 3600) / 60) milliseconds = math.floor((frames - hours * 216000 - minutes * 3600 - seconds * 60) * 100 / 60) end --Outputs relevant game information function OutputGameInfo() CalculateRtaTime() gui.text(output.gameInfo.x, output.gameInfo.y, "Pct: " .. gameInfo.pct .. "%") gui.text(output.gameInfo.x, output.gameInfo.y + 10, "In-Game Time: " .. gameInfo.hour .. ":" .. string.format("%02d",gameInfo.min) .. ":" .. string.format("%02d",gameInfo.sec) .. "." .. string.format("%02d",gameInfo.mil) .. " RTA Time: " .. hours .. ":" .. string.format("%02d",minutes) .. ":" .. string.format("%02d",seconds) .. "." .. string.format("%02d",milliseconds)) end function InScene() if gameInfo.scene.transition == 10 then gameInfo.scene.transitionStarted = true end if ((gameInfo.scene.item + gameInfo.scene.elevator > 0) or (gameInfo.scene.door > 0 and gameInfo.scene.transition >= 10 and gameInfo.scene.transitionStarted)) then if (gameInfo.scene.item + gameInfo.scene.door + gameInfo.scene.elevator ~= 111) then return true end end gameInfo.scene.transitionStarted = false return false end function GameActive() if (gameInfo.health > 0 or gameInfo.reserve > 0) and (not InScene()) then return true end return false end --Tells the script that the attempt should continue function ContinueAttempt() if GameActive() then return true end return false end --Use this function to update info that may be checked later (for example, if you gain a life) function UpdateGameInfo() --set in-game time gameInfo.hour = memory.readword(RAM.words.hour) gameInfo.min = memory.readword(RAM.words.min) gameInfo.sec = memory.readword(RAM.words.sec) gameInfo.mil = math.floor(memory.readword(RAM.words.mil) * 100 / 60) gameInfo.scene.item = memory.readbyte(RAM.bytes.itemScene) gameInfo.scene.door = memory.readbyte(RAM.bytes.doorScene) gameInfo.scene.elevator = memory.readbyte(RAM.bytes.elevatorScene) gameInfo.scene.transition = memory.readbyte(RAM.bytes.transition) gameInfo.health = memory.readword(RAM.words.health) gameInfo.reserve = memory.readword(RAM.words.reserve) end --Tells the script that the last segment was okay (i.e. you didn't die) function SegmentOk() if gameInfo.health > 0 or gameInfo.reserve > 0 then return true end return false end --Handles all the hotkey stuff function HandleKeys() keys = input.get() if press('Q') then segment.done = true end if press('R') then segment.failed = true end if press('C') then segment.failed = true segment.bestTime = 999999 segment.bestInGameTime = 999999 end if press('B') then segment.done = true segment.rollback = true end last_keys = keys end --Nothing below this point should have to change between games (though it might anyway) segment = { prevStart = savestate.create(), prevBest = savestate.create(), start = savestate.create(), best = savestate.create(), bestTime = 999999, lastTime = 0, curTime = 0, prevCurTime = 0, bestInGameTime = 999999, lastInGameTime = 0, curInGameTime = 0, prevCurInGameTime = 0, done = false, actionStart = 0, failed = false, rollback = false } firstSegment = true segment.curTime = 0 function press(button) if keys[button] and not last_keys[button] then return true end return false end function StartSegment() if firstSegment then savestate.save(segment.prevStart) firstSegment = false end savestate.save(segment.start) segment.done = false segment.curTime = movie.framecount() segment.curInGameTime = gameInfo.hour * 216000 + gameInfo.min * 3600 + gameInfo.sec * 60 + memory.readword(RAM.words.mil) if segment.rollback then segment.curTime = segment.prevCurTime segment.curInGameTime = segment.prevCurInGameTime end segment.rollback = false segment.bestTime = 999999 segment.bestInGameTime = 999999 segment.lastTime = 0 segment.lastInGameTime = 0 end function OutputTime() gui.text(output.time.x,output.time.y,string.format("Last: %d (%d) Best: %d (%d)", segment.lastTime, segment.lastInGameTime, segment.bestTime, segment.bestInGameTime)) end while true do SetGameInfo() StartSegment() local closerStart = false while not segment.done do segment.failed = false savestate.load(segment.start) while not segment.done and not segment.failed and (DoNothing()) do HandleKeys() snes9x.frameadvance() OutputGameInfo() --get a save state that's still in the transition, but close to the action for player control if movie.framecount() == segment.actionStart - preFrames then savestate.save(segment.start) closerStart = true end end SetGameInfo() if not closerStart then segment.actionStart = movie.framecount() end while not segment.done and not segment.failed and ContinueAttempt() do snes9x.frameadvance() UpdateGameInfo() OutputGameInfo() OutputTime() HandleKeys() end segment.lastTime = (movie.framecount() - segment.curTime) segment.lastInGameTime = gameInfo.hour * 216000 + gameInfo.min * 3600 + gameInfo.sec * 60 + memory.readword(RAM.words.mil) - segment.curInGameTime if ((segment.bestTime > segment.lastTime) or (segment.bestTime == segment.lastTime and segment.bestInGameTime >= segment.lastInGameTime)) and (segment.lastTime > 10) and not segment.failed and not segment.done and SegmentOk() then segment.bestTime = segment.lastTime segment.bestInGameTime = segment.lastInGameTime savestate.save(segment.best) end end if not segment.rollback then segment.prevCurTime = segment.curTime segment.prevCurInGameTime = segment.curInGameTime savestate.save(segment.prevBest) savestate.load(segment.best) savestate.save(segment.prevStart) segment.prevStart, segment.start = segment.start, segment.prevStart else savestate.load(segment.prevBest) savestate.save(segment.best) savestate.load(segment.prevStart) savestate.save(segment.start) end end