Dessyreqt icon

Super Metroid Recorder Script

Dessyreqt | PRO | 11/14/14 09:06:00 AM UTC | 0 ⭐ | 756 👁️ | Never ⏰ | []
Lua |

9.87 KB

|

None

|

0 👍

/

0 👎

--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

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎