require = depend or require require "lib.setup" require "boilerplate" require "addrs" require "classes" require "menu classes" require "menu input handlers" require "messages" require "flag manager" -- TODO: make OoT versions for most of these menus --[[ control scheme: L opens/closes the menu (while the menu is open, the game is paused) D-Pad/Joystick/C-Buttons navigate through items and pages A select menu items B/R go back a menu (or closes) Z hides the menu without closing --]] -- don't change these; the other modes are mostly broken: local run_while_paused = true local alt_input = true local eat_input = true local fn = oot and 'data/cm oot save.lua' or 'data/cm mm save.lua' local saved = deserialize(fn) or {} local function save() serialize(fn, saved) end local fades_killed = false local function set(f, v) -- wrapper for addresses that *might* be undefined if f then f(v) end end local function Setter(t) return function() for func, value in pairs(t) do func(value) end end end local passives = {} local Passive = Class(Callbacks) function Passive:init(...) Callbacks.init(self, ...) table.insert(passives, self) end function Passive:tick() if self.state then self:tick_on() end end function Passive:tick_on() end local levitate = Passive() function levitate:tick_on() if bit.band(addrs.buttons(), 0x800) > 0 then self:hold() end end function levitate:hold() addrs.link_actor.y_vel(10) end local supersonic = Passive() function supersonic:tick_on() if bit.band(addrs.buttons(), 0x8000) > 0 then self:hold() end end function supersonic:hold() addrs.link_actor.lin_vel(20) end local infinite_items = Passive() function infinite_items:tick_on() for k, v in pairs(addrs.quantities) do v(69) end end local any_item = Passive() function any_item:tick_on() addrs.buttons_enabled(0) end local function soft_reset() if oot then -- FIXME: Link voids out on title screen. -- need to load title screen save? addrs.warp_begin(0x14) addrs.warp_destination(0x00CD) addrs.fade_type(0x0B) addrs.entrance_mod_setter(0xFFF3) else addrs.warp_begin(0x14) addrs.warp_destination(0x1C00) addrs.fade_type(0x0B) addrs.entrance_mod_setter(0xFFFA) end end local function save_pos() local la = addrs.link_actor saved.pos = {} local pos = saved.pos pos.x = la.x() pos.y = la.y() pos.z = la.z() pos.a = la.angle() -- also save ISG for glitch testers ;) pos.isg = la.sword_active() save() end local function load_pos() local la = addrs.link_actor local pos = saved.pos if pos == nil then return end la.x(pos.x) la.y(pos.y) la.z(pos.z) -- also set xyz copies so collision detection doesn't interfere la.x_copy(pos.x) la.y_copy(pos.y) la.z_copy(pos.z) la.angle(pos.a) la.sword_active(pos.isg) end local function reload_scene() local ev = addrs.exit_value() addrs.warp_begin(0x14) addrs.warp_destination(ev) end local function save_scene() saved.scene = addrs.exit_value() save() end local function load_scene() if saved.scene == nil then return end addrs.warp_begin(0x14) addrs.warp_destination(saved.scene) end local function save_scene_pos() saved.scenepos = {} local sp = saved.scenepos sp.scene = addrs.exit_value() local la = addrs.link_actor sp.x = la.x() sp.y = la.y() sp.z = la.z() sp.a = la.angle() --sp.room = la.room_number() save() end local function load_scene_pos() local sp = saved.scenepos if sp == nil then return end addrs.warp_begin(0x14) addrs.warp_destination(sp.scene) local fade = fades_killed and 0x0B or 0x01 addrs.fade_type(fade) local vt = oot and 1 or -4 -- TODO: check if there's a better type for OoT addrs.voidout_type(vt) addrs.voidout_x(sp.x) addrs.voidout_y(sp.y) addrs.voidout_z(sp.z) addrs.voidout_angle(sp.a) addrs.voidout_var(0x0BFF) -- puts camera behind link instead of at entrance --voidout_room_number(sp.room) end local function kill_fades() local et = addrs.entrance_table if et == nil then return end local et_size = 1244 local new_fade = 0x0B -- instant local fades = new_fade*0x80 + new_fade for i=0, et_size*4 - 1, 4 do local a = et.addr + i if R1(a) ~= 0x80 then -- don't mess up the pointers -- the lower word works like this: -- mmIIIIIIIOOOOOOO -- m = mode; I = fade in; O = fade out (probably). local mode = bit.band(R2(a+2), 0xC000) W2(a+2, mode + fades) end end fades_killed = true end local function timestop() -- doesn't set it up quite like the glitch, but this is the main effect set(addrs.timestop, 4) -- normally -1 end local time_menu = oot and Menu{ Screen{ Text("Time Menu #1/1"), Oneshot("Set Time to 06:00", Setter{[addrs.time]=0x4000}), Oneshot("Set Time to 12:00", Setter{[addrs.time]=0x8000}), Oneshot("Set Time to 18:00", Setter{[addrs.time]=0xC000}), Oneshot("Set Time to 00:00", Setter{[addrs.time]=0x0000}), Text(""), Back(), }, } or Menu{ Screen{ Text("Day/Time Menu #1/1"), Oneshot("Set Day to Zeroth", Setter{[addrs.day]=0, [addrs.days_elapsed]=0}), Oneshot("Set Day to First", Setter{[addrs.day]=1, [addrs.days_elapsed]=1}), Oneshot("Set Day to Second", Setter{[addrs.day]=2, [addrs.days_elapsed]=2}), Oneshot("Set Day to Final", Setter{[addrs.day]=3, [addrs.days_elapsed]=3}), Oneshot("Set Day to New", Setter{[addrs.day]=4, [addrs.days_elapsed]=4}), Oneshot("Set Time to 00:00", Setter{[addrs.time]=0x0000, [addrs.day_night]=1}), Oneshot("Set Time to 03:00", Setter{[addrs.time]=0x2000, [addrs.day_night]=1}), Oneshot("Set Time to 06:00", Setter{[addrs.time]=0x4000, [addrs.day_night]=0}), Oneshot("Set Time to 09:00", Setter{[addrs.time]=0x6000, [addrs.day_night]=0}), Oneshot("Set Time to 12:00", Setter{[addrs.time]=0x8000, [addrs.day_night]=0}), Oneshot("Set Time to 15:00", Setter{[addrs.time]=0xA000, [addrs.day_night]=0}), Oneshot("Set Time to 18:00", Setter{[addrs.time]=0xC000, [addrs.day_night]=1}), Oneshot("Set Time to 21:00", Setter{[addrs.time]=0xE000, [addrs.day_night]=1}), Text(""), Oneshot("Time flow: Fast", Setter{[addrs.time_speed]=2}), Oneshot("Time flow: Normal", Setter{[addrs.time_speed]=0}), Oneshot("Time flow: Slow (iSoT)", Setter{[addrs.time_speed]=-2}), Oneshot("Time flow: Stopped", Setter{[addrs.time_speed]=-3}), Oneshot("Time flow: Backwards", Setter{[addrs.time_speed]=-5}), --Oneshot("Disable time flow (Scene)", Setter{[addrs.scene_time_speed]=0}), Oneshot("Timestop glitch", timestop), Text(""), Back(), }, } globalize{ Setter = Setter, Passive = Passive, reload_scene = reload_scene, } local warp_menu = require(mm and "menus.warp" or "menus.warp oot") local progress_menu = require "menus.progress" local playas_menu = require "menus.playas" local main_menu = Menu{ Screen{ Text("Main Menu #1/2"), Toggle("D-Up to Levitate", levitate), Toggle("A to Run Fast", supersonic), Toggle("Infinite Items", infinite_items), Toggle("Use Any Item", any_item), Text(""), Oneshot("Have Everything", Setter{[dofile]="setup hundred.lua"}), LinkTo("Set Progress...", progress_menu), Text(""), Oneshot("Escape Cutscene", Setter{[addrs.cutscene_status_2]=3}), Text(""), LinkTo("Play as...", playas_menu), --Oneshot("Store Epona", Setter{[addrs.stored_epona]=1}), Oneshot("Kill Link", Setter{[addrs.hearts]=0}), Text(""), Back(), }, Screen{ Text("Main Menu #2/2"), LinkTo("Warp to...", warp_menu), LinkTo("Set Day/Time...", time_menu), Text(""), Oneshot("Store Position", save_pos), Oneshot("Restore Position", load_pos), Text(""), Oneshot("Reload Scene", reload_scene), Oneshot("Store Scene", save_scene), Oneshot("Restore Scene", load_scene), Text(""), Oneshot("Store Scene & Position", save_scene_pos), Oneshot("Restore Scene & Position", load_scene_pos), Text(""), Oneshot("Kill Transitions", kill_fades), Text(""), Oneshot("Soft Reset (Warp to Title)", soft_reset), Text(""), Back(), }, } local input = InputHandler() input = JoyWrapper(input) local handle = MenuHandler(main_menu, T_TL) local was_paused = false while mm or oot do local ctrl, pressed = input:update() if eat_input then local old_menu = handle.menu handle_eat_input(handle, ctrl, pressed) if alt_input and handle.menu ~= old_menu then run_while_paused = true if old_menu == nil then was_paused = client.ispaused() client.pause() end if handle.menu == nil and not was_paused then client.unpause() end end elseif alt_input then handle_alt_input(handle, ctrl, pressed) else for _, v in ipairs{'left', 'right', 'up', 'down'} do ctrl[v] = ctrl['d_'..v] pressed[v] = pressed['d_'..v] end ctrl.enter = ctrl.L pressed.enter = pressed.L handle:update(ctrl, pressed) end for i, passive in ipairs(passives) do passive:tick() end if run_while_paused then emu.yield() gui.cleartext() else emu.frameadvance() end end