Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Written by Eumeus14
- -- This script will test shield-dropping a bombchu in every direction going clockwise starting from
- -- a user-defined angle. This script only tests full-range analog inputs (when |x| or |y| is 127)
- -- functions with Zelda: Majora's Mask NTSC-U, NTSC-J 1.0, NTSC-J 1.1, and apparently ROM hacks as well?
- -- TODO:
- -- OoT functionality? Based on the way the version had to be identified, this probably needs to be in a seperate script
- -- Other adjustments if I think of anything
- -- Known bugs:
- -- For the initial instructions to display, it requires the user to advance a frame if the emulator is already paused, but not if the emulator is unpaused
- -- In most cases, the emulator will be paused when the script starts, but Ideally this frame advance will not be necessary. I currently have no idea how to fix this.
- -- whatever weird stuff was happening to 95Shade
- -- Declare variables
- local validROM
- local holdingChus
- local foundChus
- local BombchuButton = ''
- local AnalogX = 0
- local AnalogY = 0
- local MemoryAddresses = {}
- -- EmulatorHeight is just used for ui stuff
- local EmulatorHeight = client.bufferheight()
- -- function finds bombchus on the C-buttons and sets the BombchuButton to that C-button
- function DetermineBombchuButton()
- -- C-Left
- if(memory.readbyte(MemoryAddresses.C_Left, 'RDRAM') == 7) then
- BombchuButton = 'C Left'
- CheckForNonzeroBombchuCount()
- -- C-Down
- elseif(memory.readbyte(MemoryAddresses.C_Down, 'RDRAM') == 7) then
- BombchuButton = 'C Down'
- CheckForNonzeroBombchuCount()
- -- C-Right
- elseif(memory.readbyte(MemoryAddresses.C_Right, 'RDRAM') == 7) then
- BombchuButton = 'C Right'
- CheckForNonzeroBombchuCount()
- -- Chus are not equipped
- else
- foundChus = false
- -- End if/else
- end
- -- End function DetermineBombchuButton
- end
- -- function checks to make sure the user has a nonzero bombchu amount
- function CheckForNonzeroBombchuCount()
- -- set foundChus to true if bombchu count is not 0
- if(memory.readbyte(MemoryAddresses.BombchuCount, 'RDRAM') ~= 0) then
- foundChus = true
- print('Bombchus found on ' .. BombchuButton)
- -- set it to false if bombchu count is 0
- else
- foundChus = false
- print('Bombchu count is 0')
- -- End if/else
- end
- -- End function CheckForNonzeroBombchuCount
- end
- -- function checks if Link is holding a bombchu
- function CheckForBombchusInHand()
- -- check if link is holding a bombchu
- if(memory.readbyte(MemoryAddresses.ItemInHand, 'RDRAM') == 7) then
- -- set holdingChus to true
- holdingChus = true
- print('Bombchus found in hand')
- else
- -- set holdingChus to false
- holdingChus = false
- -- End if/else
- end
- -- End function CheckForBombchusInHand
- end
- -- function Advances to the next visual frame. Button is an OPTIONAL parameter to also press a button until the next visual frame.
- -- Means of finding the next visual frame was modified by ProbablyButter (originally it called emu.islagged(), but this didn't work for everyone or with certain settings)
- function VisualFrameAdvance(Button)
- -- unpause or it doesn't work right for some reason
- client.unpause()
- -- Get the most recent visual frame
- local CurrentVisualFrameNumber = memory.read_u32_be(MemoryAddresses.VisualFrame, 'RDRAM')
- -- loop that tests for there to be a non-lag frame
- -- use repeat until to ignore the first frame
- repeat
- -- Button is an optional parameter, so only use it if a button value was passed
- if(Button ~= nil) then
- joypad.set({[Button]=true}, 1)
- end
- emu.frameadvance()
- until(memory.read_u32_be(MemoryAddresses.VisualFrame,'RDRAM') ~= CurrentVisualFrameNumber)
- client.pause()
- -- End function VisualFrameAdvance
- end
- -- function requests an analog input from the user for a starting angle
- function DetermineStartingAngle()
- -- run this in a loop so that the user must enter a direction
- while(AnalogX == 0 or AnalogY == 0) do
- -- prompt the user to enter an analog angle and advance a frame
- gui.drawText(1, (EmulatorHeight - 65), 'Please enter an analog input on the virtual pad and advance 1 frame.')
- gui.drawText(1, (EmulatorHeight - 50), 'Please note that this script will automatically round the X or Y value to 127 or -127')
- gui.drawText(1, (EmulatorHeight - 35), 'Joystick adjustments will always be in a clockwise direction!')
- -- Pause the emulator
- client.pause()
- -- set the analog to neutral. This may seem pointless, but if the user stopped the script while it was holding an analog angle,
- -- it will incorrectly evaluate the user's selected angle and eventually lead into an infinite loop when trying to reset the stick.
- joypad.setanalog({['X Axis'] = 0, ['Y Axis'] = 0}, 1)
- -- Reading the analog addresses from RDRAM doesn't always work without a preceding frameadvance()
- emu.frameadvance()
- AnalogX = memory.read_s8(MemoryAddresses.Analog_X, 'RDRAM')
- AnalogY = memory.read_s8(MemoryAddresses.Analog_Y, 'RDRAM')
- -- Adjust the starting angle so that |AnalogX| or |AnalogY| = 127
- MaximizeAnalogAngle()
- -- End while loop
- end
- -- End function DetermineStartingAngle
- end
- -- function maximizes the analog angle so that |x| or |y| = 127
- -- does not change anything if the stick is neutral
- function MaximizeAnalogAngle()
- -- Make sure that |AnalogX| or |AnalogY| is 127
- -- Only do this if the condition has not been satisfied
- if(math.abs(AnalogX) ~= 127 or math.abs(AnalogY) ~= 127) then
- -- Adjust AnalogX if it is closer to max extension than AnalogY
- if(math.abs(AnalogX) > math.abs(AnalogY)) then
- -- Determine whether to round to positive or negative 127
- if(AnalogX < 0) then
- AnalogX = -127
- else
- AnalogX = 127
- end
- -- Adjust AnalogY if it is closer to max extension than AnalogX
- elseif(math.abs(AnalogX) < math.abs(AnalogY)) then
- -- Determine whether to round to positive or negative 127
- if(AnalogY < 0) then
- AnalogY = -127
- else
- AnalogY = 127
- end
- -- Adjust both AnalogX and AnalogY
- else
- -- Determine whether to round AnalogX to positive or negative 127
- if(AnalogX < 0) then
- AnalogX = -127
- elseif(AnalogX > 0) then
- AnalogX = 127
- else
- -- no change if the stick is neutral
- AnalogX = 0
- end
- -- Determine whether to round AnalogY to positive or negative 127
- if(AnalogY < 0) then
- AnalogY = -127
- elseif(AnalogY > 0) then
- AnalogY = 127
- else
- -- no change if the stick is neutral
- AnalogY = 0
- end
- end
- -- End the collosal if/else
- end
- -- End function MaximizeAnalogAngle
- end
- -- function increments the values of AnalogX and AnalogY to move the stick clockwise
- function IncrementAnalogAngle()
- -- moving right along the top
- if(AnalogX < 127 and AnalogY == 127) then
- -- increase AnalogX by 1
- AnalogX = AnalogX + 1
- -- moving down the right side
- elseif(AnalogY > -127 and AnalogX == 127) then
- -- decrease AnalogY by 1
- AnalogY = AnalogY - 1
- -- moving left along the bottom
- elseif(AnalogX > -127 and AnalogY == -127) then
- -- decrease AnalogX by 1
- AnalogX = AnalogX - 1
- -- moving up the left side
- elseif(AnalogY < 127 and AnalogX == -127) then
- -- increase AnalogY by 1
- AnalogY = AnalogY + 1
- -- failsafe in case neither |AnalogX| nor |AnalogY| equal 127
- else
- -- set AnalogX or AnalogY equal to positive or negative 127
- MaximizeAnalogAngle()
- end
- -- End function IncrementAnalogAngle
- end
- -- function halts the script in a loop until the user resets the virtual pad (cuz for some reason you can't just do this in lua)
- function ForceResetVirtualPad()
- while(memory.read_s8(MemoryAddresses.Analog_X, 'RDRAM') ~= 0 and memory.read_s8(MemoryAddresses.Analog_Y, 'RDRAM') ~= 0) do
- -- notify the user to reset the virtual pad to 0,0
- client.pause()
- gui.drawText(1, (EmulatorHeight - 35), 'Please reset the virtual pad stick to neutral (lua cannot do this by itself)')
- emu.frameadvance()
- end
- -- End function ForceResetVirtualPad
- end
- -- function sets an analog angle and shield drops the bombchu
- -- advances a sufficient number of frames afterwards to see what the bombchu did.
- function ShieldDropAngledBombchu()
- -- Only pull a bombchu if link is not holding one
- if(holdingChus == false) then
- -- Pull a bombchu and advance a visual frame
- VisualFrameAdvance(BombchuButton)
- end
- -- input an analog angle and advance a visual frame
- joypad.setanalog({['X Axis'] = AnalogX, ['Y Axis'] = AnalogY}, 1)
- VisualFrameAdvance()
- -- advance a visual frame with the R button pressed to shield drop
- -- The bombchus explodes 120 visual frames after this under normal circumstances
- VisualFrameAdvance('R')
- -- release the joystick
- joypad.setanalog({['X Axis'] = 0, ['Y Axis'] = 0}, 1)
- -- speed up the emulator. Don't do this any earlier as some users have reported input desyncs from high framerate
- client.speedmode(3200)
- -- originally was going to make this script target the bombchu, but there seems to be no consistent way to do this
- -- advance 120 visual frames to wait for the bombchu fuse
- for i = 0,120 do
- VisualFrameAdvance()
- end
- -- Set the speed back to normal
- client.speedmode(100)
- -- advance 1.5 seconds
- for i = 0,30 do
- VisualFrameAdvance()
- end
- -- End function ShieldDropAngledBombchu
- end
- -- function drops bombchus at different angles in a loop until every angle has been tested.
- function TryAllAngles()
- -- Set variables to compare to in a loop.
- local tempAnalogX = AnalogX
- local tempAnalogY = AnalogY
- -- repeat until to check the condition after an iteration. AnalogX or AnalogY will change before then
- repeat
- -- User virtual pad input overrides input from the script, so force the user to reset the virtual pad to (0,0)
- ForceResetVirtualPad()
- -- Load the named state
- savestate.load('BombchuAngleTestState')
- -- Print the attempted angle to assure the user that it is working
- print('Attempted bombchu release with joystick at: x = ' .. AnalogX .. ' y = ' .. AnalogY .. '\n')
- -- Drop a bombchu
- ShieldDropAngledBombchu()
- -- Adjust the angle
- IncrementAnalogAngle()
- until(AnalogX == tempAnalogX and AnalogY == tempAnalogY)
- -- End function TryAllAngles
- end
- -- function puts the appropriate memory addresses for the ROM into a table and returns it.
- function SetVersionMemoryAddresses()
- -- Table containing addresses for MM NTSC-J 1.0
- local MM_NTSCj_1_0_Addresses = {}
- MM_NTSCj_1_0_Addresses.C_Left = 0x1EF4AD
- MM_NTSCj_1_0_Addresses.C_Down = 0x1EF4AE
- MM_NTSCj_1_0_Addresses.C_Right = 0x1EF4AF
- MM_NTSCj_1_0_Addresses.ItemInHand = 0x4000E8
- MM_NTSCj_1_0_Addresses.BombchuCount = 0x1EF507
- MM_NTSCj_1_0_Addresses.VisualFrame = 0x1FA0F0
- MM_NTSCj_1_0_Addresses.Analog_X = 0x1FBA42
- MM_NTSCj_1_0_Addresses.Analog_Y = 0x1FBA43
- -- Table containing addresses for MM NTSC-J 1.1
- local MM_NTSCj_1_1_Addresses = {}
- MM_NTSCj_1_1_Addresses.C_Left = 0x1EF75D
- MM_NTSCj_1_1_Addresses.C_Down = 0x1EF75E
- MM_NTSCj_1_1_Addresses.C_Right = 0x1EF75F
- MM_NTSCj_1_1_Addresses.ItemInHand = 0x4003A8
- MM_NTSCj_1_1_Addresses.BombchuCount = 0x1EF7B7
- MM_NTSCj_1_1_Addresses.VisualFrame = 0x1FA3A0
- MM_NTSCj_1_1_Addresses.Analog_X = 0x1FBCF2
- MM_NTSCj_1_1_Addresses.Analog_Y = 0x1FBCF3
- -- Table containing the addresses for MM NTSC-U
- local MM_NTSCu_Addresses = {}
- MM_NTSCu_Addresses.C_Left = 0x1EF6BD
- MM_NTSCu_Addresses.C_Down = 0x1EF6BE
- MM_NTSCu_Addresses.C_Right = 0x1EF6BF
- MM_NTSCu_Addresses.ItemInHand = 0x3FFEF8
- MM_NTSCu_Addresses.BombchuCount = 0x1EF717
- MM_NTSCu_Addresses.VisualFrame = 0x1F9F80
- MM_NTSCu_Addresses.Analog_X = 0x1FB872
- MM_NTSCu_Addresses.Analog_Y = 0x1FB873
- -- Get the code for the ROM
- local VersionID = memory.read_u16_be(0x3E, 'ROM')
- -- Determine the ROM and fill MemoryAddresses with the appropriate addresses
- if(VersionID == 0x4A00) then
- -- MM NTSC-J 1.0
- print('MM NTSC-J 1.0 ROM detected')
- MemoryAddresses = MM_NTSCj_1_0_Addresses
- validROM = true
- elseif(VersionID == 0x4A01) then
- -- MM NTSC-J 1.1
- print('MM NTSC-J 1.1 ROM detected')
- MemoryAddresses = MM_NTSCj_1_1_Addresses
- validROM = true
- elseif(VersionID == 0x4500) then
- -- MM NTSC-U
- -- Also works with MM MQ and randomizer!
- print('MM NTSC-U ROM detected')
- MemoryAddresses = MM_NTSCu_Addresses
- validROM = true
- else
- print('Unrecognized ROM')
- validROM = false
- end
- -- End function SetVersionMemoryAddresses
- end
- -- main program
- -- save a state to be used when repeatedly testing angles. Do this now because frames will advance when setup functions are being called.
- savestate.save('BombchuAngleTestState')
- -- Determine the version being used and get the appropriate memory addresses
- SetVersionMemoryAddresses()
- -- ROM is valid
- if(validROM == true) then
- -- Check item in hand for chus
- CheckForBombchusInHand()
- -- No need to find bombchus on the c-buttons if they are in hand
- if(holdingChus == false) then
- DetermineBombchuButton()
- end
- -- Do not run the remainder of the program if chus are not equipped or held
- if(foundChus == true or holdingChus == true) then
- -- get an initial angle
- DetermineStartingAngle()
- -- begin trying angles
- TryAllAngles()
- else
- client.pause()
- gui.drawText(1, (EmulatorHeight - 35), 'You must be holding a bombchu, or have them equipped with a nonzero count')
- end
- -- ROM is NOT valid
- else
- client.pause()
- gui.drawText(1, (EmulatorHeight - 35), 'This ROM is not supported by the script.')
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement