--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
0 B
|👍
/👎