Eumeus14

TestBombchuAngles Script

Apr 17th, 2017
120
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- Written by Eumeus14
  2.  
  3. -- This script will test shield-dropping a bombchu in every direction going clockwise starting from
  4. -- a user-defined angle. This script only tests full-range analog inputs (when |x| or |y| is 127)
  5. -- functions with Zelda: Majora's Mask NTSC-U, NTSC-J 1.0, NTSC-J 1.1, and apparently ROM hacks as well?
  6.  
  7. -- TODO:
  8. -- OoT functionality? Based on the way the version had to be identified, this probably needs to be in a seperate script
  9. -- Other adjustments if I think of anything
  10.  
  11. -- Known bugs:
  12. -- 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
  13. -- 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.
  14.  
  15. -- whatever weird stuff was happening to 95Shade
  16.  
  17. -- Declare variables
  18. local validROM
  19. local holdingChus
  20. local foundChus
  21. local BombchuButton = ''
  22. local AnalogX = 0
  23. local AnalogY = 0
  24. local MemoryAddresses = {}
  25. -- EmulatorHeight is just used for ui stuff
  26. local EmulatorHeight = client.bufferheight()
  27.  
  28. -- function finds bombchus on the C-buttons and sets the BombchuButton to that C-button
  29. function DetermineBombchuButton()
  30.     -- C-Left
  31.     if(memory.readbyte(MemoryAddresses.C_Left, 'RDRAM') == 7) then
  32.         BombchuButton = 'C Left'
  33.         CheckForNonzeroBombchuCount()
  34.    
  35.     -- C-Down
  36.     elseif(memory.readbyte(MemoryAddresses.C_Down, 'RDRAM') == 7) then
  37.         BombchuButton = 'C Down'
  38.         CheckForNonzeroBombchuCount()
  39.        
  40.     -- C-Right
  41.     elseif(memory.readbyte(MemoryAddresses.C_Right, 'RDRAM') == 7) then
  42.         BombchuButton = 'C Right'
  43.         CheckForNonzeroBombchuCount()
  44.        
  45.     -- Chus are not equipped
  46.     else
  47.         foundChus = false
  48.    
  49.     -- End if/else
  50.     end
  51.    
  52. -- End function DetermineBombchuButton
  53. end
  54.  
  55.  
  56. -- function checks to make sure the user has a nonzero bombchu amount
  57. function CheckForNonzeroBombchuCount()
  58.     -- set foundChus to true if bombchu count is not 0
  59.     if(memory.readbyte(MemoryAddresses.BombchuCount, 'RDRAM') ~= 0) then
  60.         foundChus = true
  61.         print('Bombchus found on ' .. BombchuButton)
  62.    
  63.     -- set it to false if bombchu count is 0
  64.     else
  65.         foundChus = false
  66.         print('Bombchu count is 0')
  67.    
  68.     -- End if/else
  69.     end
  70.    
  71. -- End function CheckForNonzeroBombchuCount
  72. end
  73.  
  74.  
  75. -- function checks if Link is holding a bombchu
  76. function CheckForBombchusInHand()
  77.     -- check if link is holding a bombchu
  78.     if(memory.readbyte(MemoryAddresses.ItemInHand, 'RDRAM') == 7) then
  79.         -- set holdingChus to true
  80.         holdingChus = true
  81.         print('Bombchus found in hand')
  82.        
  83.     else
  84.         -- set holdingChus to false
  85.         holdingChus = false
  86.    
  87.     -- End if/else
  88.     end
  89.    
  90. -- End function CheckForBombchusInHand
  91. end
  92.  
  93.  
  94. -- function Advances to the next visual frame. Button is an OPTIONAL parameter to also press a button until the next visual frame.
  95. -- 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)
  96. function VisualFrameAdvance(Button)
  97.     -- unpause or it doesn't work right for some reason
  98.     client.unpause()
  99.  
  100.     -- Get the most recent visual frame
  101.     local CurrentVisualFrameNumber = memory.read_u32_be(MemoryAddresses.VisualFrame, 'RDRAM')
  102.    
  103.     -- loop that tests for there to be a non-lag frame
  104.     -- use repeat until to ignore the first frame
  105.     repeat
  106.         -- Button is an optional parameter, so only use it if a button value was passed
  107.         if(Button ~= nil) then
  108.             joypad.set({[Button]=true}, 1)
  109.         end
  110.        
  111.         emu.frameadvance()
  112.     until(memory.read_u32_be(MemoryAddresses.VisualFrame,'RDRAM') ~= CurrentVisualFrameNumber)
  113.  
  114.     client.pause()
  115. -- End function VisualFrameAdvance
  116. end
  117.  
  118.  
  119. -- function requests an analog input from the user for a starting angle
  120. function DetermineStartingAngle()
  121.     -- run this in a loop so that the user must enter a direction  
  122.     while(AnalogX == 0 or AnalogY == 0) do 
  123.         -- prompt the user to enter an analog angle and advance a frame
  124.         gui.drawText(1, (EmulatorHeight - 65), 'Please enter an analog input on the virtual pad and advance 1 frame.')
  125.         gui.drawText(1, (EmulatorHeight - 50), 'Please note that this script will automatically round the X or Y value to 127 or -127')
  126.         gui.drawText(1, (EmulatorHeight - 35), 'Joystick adjustments will always be in a clockwise direction!')
  127.        
  128.         -- Pause the emulator
  129.         client.pause()
  130.        
  131.         -- set the analog to neutral. This may seem pointless, but if the user stopped the script while it was holding an analog angle,
  132.         -- it will incorrectly evaluate the user's selected angle and eventually lead into an infinite loop when trying to reset the stick.
  133.         joypad.setanalog({['X Axis'] = 0, ['Y Axis'] = 0}, 1)
  134.    
  135.         -- Reading the analog addresses from RDRAM doesn't always work without a preceding frameadvance()
  136.         emu.frameadvance()
  137.         AnalogX = memory.read_s8(MemoryAddresses.Analog_X, 'RDRAM')
  138.         AnalogY = memory.read_s8(MemoryAddresses.Analog_Y, 'RDRAM')
  139.    
  140.         -- Adjust the starting angle so that |AnalogX| or |AnalogY| = 127
  141.         MaximizeAnalogAngle()
  142.        
  143.     -- End while loop
  144.     end
  145.    
  146. -- End function DetermineStartingAngle
  147. end
  148.  
  149.  
  150. -- function maximizes the analog angle so that |x| or |y| = 127
  151. -- does not change anything if the stick is neutral
  152. function MaximizeAnalogAngle()
  153.     -- Make sure that |AnalogX| or |AnalogY| is 127
  154.     -- Only do this if the condition has not been satisfied
  155.     if(math.abs(AnalogX) ~= 127 or math.abs(AnalogY) ~= 127) then
  156.         -- Adjust AnalogX if it is closer to max extension than AnalogY
  157.         if(math.abs(AnalogX) > math.abs(AnalogY)) then
  158.             -- Determine whether to round to positive or negative 127
  159.             if(AnalogX < 0) then
  160.                 AnalogX = -127
  161.             else
  162.                 AnalogX = 127
  163.             end
  164.         -- Adjust AnalogY if it is closer to max extension than AnalogX
  165.         elseif(math.abs(AnalogX) < math.abs(AnalogY)) then
  166.             -- Determine whether to round to positive or negative 127
  167.             if(AnalogY < 0) then
  168.                 AnalogY = -127
  169.             else
  170.                 AnalogY = 127
  171.             end
  172.         -- Adjust both AnalogX and AnalogY
  173.         else
  174.             -- Determine whether to round AnalogX to positive or negative 127
  175.             if(AnalogX < 0) then
  176.                 AnalogX = -127
  177.             elseif(AnalogX > 0) then
  178.                 AnalogX = 127
  179.             else
  180.                 -- no change if the stick is neutral
  181.                 AnalogX = 0
  182.             end
  183.             -- Determine whether to round AnalogY to positive or negative 127
  184.             if(AnalogY < 0) then
  185.                 AnalogY = -127
  186.             elseif(AnalogY > 0) then
  187.                 AnalogY = 127
  188.             else
  189.                 -- no change if the stick is neutral
  190.                 AnalogY = 0
  191.             end
  192.         end
  193.    
  194.     -- End the collosal if/else
  195.     end
  196.  
  197. -- End function MaximizeAnalogAngle
  198. end
  199.  
  200.  
  201. -- function increments the values of AnalogX and AnalogY to move the stick clockwise
  202. function IncrementAnalogAngle()
  203.     -- moving right along the top
  204.     if(AnalogX < 127 and AnalogY == 127) then
  205.         -- increase AnalogX by 1
  206.         AnalogX = AnalogX + 1
  207.        
  208.     -- moving down the right side
  209.     elseif(AnalogY > -127 and AnalogX == 127) then
  210.         -- decrease AnalogY by 1
  211.         AnalogY = AnalogY - 1
  212.        
  213.     -- moving left along the bottom
  214.     elseif(AnalogX > -127 and AnalogY == -127) then
  215.         -- decrease AnalogX by 1
  216.         AnalogX = AnalogX - 1
  217.        
  218.     -- moving up the left side
  219.     elseif(AnalogY < 127 and AnalogX == -127) then
  220.         -- increase AnalogY by 1
  221.         AnalogY = AnalogY + 1
  222.        
  223.     -- failsafe in case neither |AnalogX| nor |AnalogY| equal 127
  224.     else
  225.         -- set AnalogX or AnalogY equal to positive or negative 127
  226.         MaximizeAnalogAngle()
  227.     end
  228.    
  229. -- End function IncrementAnalogAngle
  230. end
  231.  
  232.  
  233. -- 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)
  234. function ForceResetVirtualPad()
  235.     while(memory.read_s8(MemoryAddresses.Analog_X, 'RDRAM') ~= 0 and memory.read_s8(MemoryAddresses.Analog_Y, 'RDRAM') ~= 0) do
  236.         -- notify the user to reset the virtual pad to 0,0
  237.         client.pause()
  238.         gui.drawText(1, (EmulatorHeight - 35), 'Please reset the virtual pad stick to neutral (lua cannot do this by itself)')
  239.         emu.frameadvance()
  240.     end
  241. -- End function ForceResetVirtualPad
  242. end
  243.  
  244.  
  245. -- function sets an analog angle and shield drops the bombchu
  246. -- advances a sufficient number of frames afterwards to see what the bombchu did.
  247. function ShieldDropAngledBombchu()
  248.     -- Only pull a bombchu if link is not holding one
  249.     if(holdingChus == false) then
  250.         -- Pull a bombchu and advance a visual frame
  251.         VisualFrameAdvance(BombchuButton)
  252.     end
  253.    
  254.     -- input an analog angle and advance a visual frame
  255.     joypad.setanalog({['X Axis'] = AnalogX, ['Y Axis'] = AnalogY}, 1)
  256.     VisualFrameAdvance()
  257.    
  258.     -- advance a visual frame with the R button pressed to shield drop
  259.     -- The bombchus explodes 120 visual frames after this under normal circumstances
  260.     VisualFrameAdvance('R')
  261.    
  262.     -- release the joystick
  263.     joypad.setanalog({['X Axis'] = 0, ['Y Axis'] = 0}, 1)
  264.    
  265.     -- speed up the emulator. Don't do this any earlier as some users have reported input desyncs from high framerate
  266.     client.speedmode(3200)
  267.    
  268.     -- originally was going to make this script target the bombchu, but there seems to be no consistent way to do this
  269.    
  270.     -- advance 120 visual frames to wait for the bombchu fuse
  271.     for i = 0,120 do
  272.         VisualFrameAdvance()
  273.     end
  274.    
  275.     -- Set the speed back to normal
  276.     client.speedmode(100)
  277.     -- advance 1.5 seconds
  278.     for i = 0,30 do
  279.         VisualFrameAdvance()
  280.     end
  281.    
  282. -- End function ShieldDropAngledBombchu
  283. end
  284.  
  285.  
  286. -- function drops bombchus at different angles in a loop until every angle has been tested.
  287. function TryAllAngles()
  288.     -- Set variables to compare to in a loop.
  289.     local tempAnalogX = AnalogX
  290.     local tempAnalogY = AnalogY
  291.    
  292.     -- repeat until to check the condition after an iteration. AnalogX or AnalogY will change before then
  293.     repeat
  294.        
  295.         -- User virtual pad input overrides input from the script, so force the user to reset the virtual pad to (0,0)
  296.         ForceResetVirtualPad()
  297.         -- Load the named state
  298.         savestate.load('BombchuAngleTestState')
  299.         -- Print the attempted angle to assure the user that it is working
  300.         print('Attempted bombchu release with joystick at: x = ' .. AnalogX .. ' y = ' .. AnalogY .. '\n')
  301.         -- Drop a bombchu
  302.         ShieldDropAngledBombchu()
  303.         -- Adjust the angle
  304.         IncrementAnalogAngle()
  305.     until(AnalogX == tempAnalogX and AnalogY == tempAnalogY)
  306.    
  307. -- End function TryAllAngles
  308. end
  309.  
  310.  
  311. -- function puts the appropriate memory addresses for the ROM into a table and returns it.
  312. function SetVersionMemoryAddresses()
  313.    
  314.     -- Table containing addresses for MM NTSC-J 1.0
  315.     local MM_NTSCj_1_0_Addresses = {}
  316.     MM_NTSCj_1_0_Addresses.C_Left = 0x1EF4AD
  317.     MM_NTSCj_1_0_Addresses.C_Down = 0x1EF4AE
  318.     MM_NTSCj_1_0_Addresses.C_Right = 0x1EF4AF
  319.     MM_NTSCj_1_0_Addresses.ItemInHand = 0x4000E8
  320.     MM_NTSCj_1_0_Addresses.BombchuCount = 0x1EF507
  321.     MM_NTSCj_1_0_Addresses.VisualFrame = 0x1FA0F0
  322.     MM_NTSCj_1_0_Addresses.Analog_X = 0x1FBA42
  323.     MM_NTSCj_1_0_Addresses.Analog_Y = 0x1FBA43
  324.    
  325.     -- Table containing addresses for MM NTSC-J 1.1
  326.     local MM_NTSCj_1_1_Addresses = {}
  327.     MM_NTSCj_1_1_Addresses.C_Left = 0x1EF75D
  328.     MM_NTSCj_1_1_Addresses.C_Down = 0x1EF75E
  329.     MM_NTSCj_1_1_Addresses.C_Right = 0x1EF75F
  330.     MM_NTSCj_1_1_Addresses.ItemInHand = 0x4003A8
  331.     MM_NTSCj_1_1_Addresses.BombchuCount = 0x1EF7B7
  332.     MM_NTSCj_1_1_Addresses.VisualFrame = 0x1FA3A0
  333.     MM_NTSCj_1_1_Addresses.Analog_X = 0x1FBCF2
  334.     MM_NTSCj_1_1_Addresses.Analog_Y = 0x1FBCF3
  335.    
  336.     -- Table containing the addresses for MM NTSC-U
  337.     local MM_NTSCu_Addresses = {}
  338.     MM_NTSCu_Addresses.C_Left = 0x1EF6BD
  339.     MM_NTSCu_Addresses.C_Down = 0x1EF6BE
  340.     MM_NTSCu_Addresses.C_Right = 0x1EF6BF
  341.     MM_NTSCu_Addresses.ItemInHand = 0x3FFEF8
  342.     MM_NTSCu_Addresses.BombchuCount = 0x1EF717
  343.     MM_NTSCu_Addresses.VisualFrame = 0x1F9F80
  344.     MM_NTSCu_Addresses.Analog_X = 0x1FB872
  345.     MM_NTSCu_Addresses.Analog_Y = 0x1FB873
  346.    
  347.     -- Get the code for the ROM
  348.     local VersionID = memory.read_u16_be(0x3E, 'ROM')
  349.    
  350.     -- Determine the ROM and fill MemoryAddresses with the appropriate addresses
  351.     if(VersionID == 0x4A00) then
  352.         -- MM NTSC-J 1.0
  353.         print('MM NTSC-J 1.0 ROM detected')
  354.         MemoryAddresses = MM_NTSCj_1_0_Addresses
  355.         validROM = true
  356.        
  357.     elseif(VersionID == 0x4A01) then
  358.         -- MM NTSC-J 1.1
  359.         print('MM NTSC-J 1.1 ROM detected')
  360.         MemoryAddresses = MM_NTSCj_1_1_Addresses
  361.         validROM = true
  362.        
  363.     elseif(VersionID == 0x4500) then
  364.         -- MM NTSC-U
  365.         -- Also works with MM MQ and randomizer!
  366.         print('MM NTSC-U ROM detected')
  367.         MemoryAddresses = MM_NTSCu_Addresses
  368.         validROM = true
  369.        
  370.     else
  371.         print('Unrecognized ROM')
  372.         validROM = false
  373.     end
  374.    
  375. -- End function SetVersionMemoryAddresses
  376. end
  377.  
  378.  
  379. -- main program
  380. -- save a state to be used when repeatedly testing angles. Do this now because frames will advance when setup functions are being called.
  381. savestate.save('BombchuAngleTestState')
  382.  
  383. -- Determine the version being used and get the appropriate memory addresses
  384. SetVersionMemoryAddresses()
  385.  
  386. -- ROM is valid
  387. if(validROM == true) then
  388.     -- Check item in hand for chus
  389.     CheckForBombchusInHand()
  390.  
  391.     -- No need to find bombchus on the c-buttons if they are in hand
  392.     if(holdingChus == false) then
  393.         DetermineBombchuButton()
  394.     end
  395.  
  396.     -- Do not run the remainder of the program if chus are not equipped or held
  397.     if(foundChus == true or holdingChus == true) then
  398.         -- get an initial angle
  399.         DetermineStartingAngle()
  400.         -- begin trying angles
  401.         TryAllAngles()
  402.     else
  403.         client.pause()
  404.         gui.drawText(1, (EmulatorHeight - 35), 'You must be holding a bombchu, or have them equipped with a nonzero count')
  405.     end
  406.  
  407. -- ROM is NOT valid
  408. else
  409.     client.pause()
  410.     gui.drawText(1, (EmulatorHeight - 35), 'This ROM is not supported by the script.')
  411. end
RAW Paste Data