-- name: \\#f00\\Debug\\#00f\\Tool \\#0f0\\v1.0 -- description: \\#ff8080\\Edit the save you are playing. Turn stars and flags on or off, change coin records, and warp into a course or stand in front of its entrance. Set lives, health, coins, position, and the camera, or freeze Mario. Show coordinates, horizontal speed, velocities, and the current action on screen, and draw colored axis lines from Mario. Move, scale, and set the decimals of that readout. Settings save as you change them. Open it with /debugtool, /dt, or this mod's menu button.\n\n\\#80ff80\\Developed by iZePlayz.\n\n\\#8080ff\\You may modify this mod and release your own modified version of it, for example for a romhack, without asking for permission if you clearly say that it is a modified version of this original mod, Debug Tool, and that the original was developed by iZePlayz. -- pausable: true local sOpen = false local sKeepOpen = mod_storage_load_bool("keep_open", false) local sTab = 1 local sPage = 1 local sScroll = 0 local sScrollTarget = 0 local sMouseWasDown = false local sMouseClicked = false local sWasMouseLocked = true local sMouseX = 0 local sMouseY = 0 local sSelection = 1 local sColumn = 1 local sFocus = "list" local sChrome = 1 local sPreviousButtons = 0 local PAGE_MIN = 8 local PAGE_MAX = 20 local function load_page_size() local value = mod_storage_load_integer("page_size", 12) if value == nil or value > PAGE_MAX then value = 12 elseif value < PAGE_MIN then value = PAGE_MIN end return value end local sPageSize = load_page_size() local UI_SCALE_MIN = 50 local UI_SCALE_MAX = 150 local UI_OPACITY_MIN = 10 local UI_OPACITY_MAX = 100 local function load_ranged_integer(key, default, minValue, maxValue) local value = mod_storage_load_integer(key, default) if value == nil or value < minValue or value > maxValue then value = default end return value end local sUiScale = load_ranged_integer("ui_scale", 100, UI_SCALE_MIN, UI_SCALE_MAX) local sUiOpacity = load_ranged_integer("ui_opacity", 100, UI_OPACITY_MIN, UI_OPACITY_MAX) local VISIBLE_ROW_CHOICES = {} for n = PAGE_MIN, PAGE_MAX do VISIBLE_ROW_CHOICES[#VISIBLE_ROW_CHOICES + 1] = n end local UI_SCALE_CHOICES = {} for n = UI_SCALE_MIN, UI_SCALE_MAX, 10 do UI_SCALE_CHOICES[#UI_SCALE_CHOICES + 1] = n end local OPACITY_CHOICES = {} for n = 10, 100, 10 do OPACITY_CHOICES[#OPACITY_CHOICES + 1] = n end local THICKNESS_CHOICES = {} for n = 1, 12 do THICKNESS_CHOICES[#THICKNESS_CHOICES + 1] = n end local function snap_ui_scale(value) value = math.max(UI_SCALE_MIN, math.min(UI_SCALE_MAX, math.floor(value))) local snapped = math.floor((value + 5) / 10) * 10 if snapped < UI_SCALE_MIN then snapped = UI_SCALE_MIN end if snapped > UI_SCALE_MAX then snapped = UI_SCALE_MAX end return snapped end sUiScale = snap_ui_scale(sUiScale) local sLastScrollFrame = -100 local sRepeatButton = 0 local sRepeatTimer = 0 local COL_ALL = 8 local COL_NONE = 9 local REPEAT_DELAY = 12 local REPEAT_RATE = 4 local sRepeatSteps = 0 local sScrollHold = 0 local sScrollHoldTimer = 0 local sScrollHoldSteps = 0 local sScrollMouseDir = 0 local sFlagTexture = get_texture_info("exclamation_box_seg8_texture_08015E28") local sRedStar = get_texture_info("gd_texture_red_star_0") local sWhiteStar = get_texture_info("gd_texture_white_star_0") local sHand = get_texture_info("gd_texture_hand_open") local sGear = get_texture_info("ttc_seg7_texture_07000000") local TAB_COUNT = 6 local HEADER_COUNT = 7 local sCloseFromStart = false local sBlockFrames = 0 local sPauseLock = nil local sPausedLastFrame = false local sFramePause = false local function arm_close_block() -- Keep eating Start and B after the editor closes. The game can still -- see the key for a frame and would pause or make Mario punch. sBlockFrames = 8 if sPauseLock == nil then sPauseLock = sFramePause end end local COURSE_ROWS = { { name = "Course 1 (BoB)", course = COURSE_BOB, stars = 7, numbered = true }, { name = "Course 2 (WF)", course = COURSE_WF, stars = 7, numbered = true }, { name = "Course 3 (JRB)", course = COURSE_JRB, stars = 7, numbered = true }, { name = "Course 4 (CCM)", course = COURSE_CCM, stars = 7, numbered = true }, { name = "Course 5 (BBH)", course = COURSE_BBH, stars = 7, numbered = true }, { name = "Course 6 (HMC)", course = COURSE_HMC, stars = 7, numbered = true }, { name = "Course 7 (LLL)", course = COURSE_LLL, stars = 7, numbered = true }, { name = "Course 8 (SSL)", course = COURSE_SSL, stars = 7, numbered = true }, { name = "Course 9 (DDD)", course = COURSE_DDD, stars = 7, numbered = true }, { name = "Course 10 (SL)", course = COURSE_SL, stars = 7, numbered = true }, { name = "Course 11 (WDW)", course = COURSE_WDW, stars = 7, numbered = true }, { name = "Course 12 (TTM)", course = COURSE_TTM, stars = 7, numbered = true }, { name = "Course 13 (THI)", course = COURSE_THI, stars = 7, numbered = true }, { name = "Course 14 (TTC)", course = COURSE_TTC, stars = 7, numbered = true }, { name = "Course 15 (RR)", course = COURSE_RR, stars = 7, numbered = true }, { name = "Bowser 1 (BitDW)", course = COURSE_BITDW, stars = 1 }, { name = "Bowser 2 (BitFS)", course = COURSE_BITFS, stars = 1 }, { name = "Bowser 3 (BitS)", course = COURSE_BITS, stars = 1 }, { name = "Princess's Secret Slide (PSS)", course = COURSE_PSS, stars = 2 }, { name = "Metal Cap (CotMC)", course = COURSE_COTMC, stars = 1 }, { name = "Wing Cap (TotWC)", course = COURSE_TOTWC, stars = 1 }, { name = "Vanish Cap (VCutM)", course = COURSE_VCUTM, stars = 1 }, { name = "Wing Mario Over Rainbow (WMotR)", course = COURSE_WMOTR, stars = 1 }, { name = "Secret Aquarium (SA)", course = COURSE_SA, stars = 1 }, { name = "Castle Secret Stars", courseIndex = -1, stars = 7 }, } local FLAG_ROWS = { { name = "File exists", flag = SAVE_FLAG_FILE_EXISTS }, { name = "Wing Cap switch", flag = SAVE_FLAG_HAVE_WING_CAP }, { name = "Metal Cap switch", flag = SAVE_FLAG_HAVE_METAL_CAP }, { name = "Vanish Cap switch", flag = SAVE_FLAG_HAVE_VANISH_CAP }, { name = "Key 1 (BitDW)", flag = SAVE_FLAG_HAVE_KEY_1 }, { name = "Key 2 (BitFS)", flag = SAVE_FLAG_HAVE_KEY_2 }, { name = "Basement key door", flag = SAVE_FLAG_UNLOCKED_BASEMENT_DOOR }, { name = "Upstairs key door", flag = SAVE_FLAG_UNLOCKED_UPSTAIRS_DOOR }, { name = "Dire, Dire Docks moved (DDD)", flag = SAVE_FLAG_DDD_MOVED_BACK }, { name = "Moat drained", flag = SAVE_FLAG_MOAT_DRAINED }, { name = "Secret Slide door (PSS)", flag = SAVE_FLAG_UNLOCKED_PSS_DOOR }, { name = "Whomp's Fortress door (WF)", flag = SAVE_FLAG_UNLOCKED_WF_DOOR }, { name = "Cool, Cool Mountain door (CCM)", flag = SAVE_FLAG_UNLOCKED_CCM_DOOR }, { name = "Jolly Roger Bay door (JRB)", flag = SAVE_FLAG_UNLOCKED_JRB_DOOR }, { name = "Bowser 1 door (BitDW)", flag = SAVE_FLAG_UNLOCKED_BITDW_DOOR }, { name = "Bowser 2 door (BitFS)", flag = SAVE_FLAG_UNLOCKED_BITFS_DOOR }, { name = "50-star door", flag = SAVE_FLAG_UNLOCKED_50_STAR_DOOR }, { name = "Toad star 1", flag = SAVE_FLAG_COLLECTED_TOAD_STAR_1 }, { name = "Toad star 2", flag = SAVE_FLAG_COLLECTED_TOAD_STAR_2 }, { name = "Toad star 3", flag = SAVE_FLAG_COLLECTED_TOAD_STAR_3 }, { name = "Mips star 1", flag = SAVE_FLAG_COLLECTED_MIPS_STAR_1 }, { name = "Mips star 2", flag = SAVE_FLAG_COLLECTED_MIPS_STAR_2 }, { name = "Bob-omb Battlefield cannon (BoB)", cannon = COURSE_BOB }, { name = "Whomp's Fortress cannon (WF)", cannon = COURSE_WF }, { name = "Jolly Roger Bay cannon (JRB)", cannon = COURSE_JRB }, { name = "Cool, Cool Mountain cannon (CCM)", cannon = COURSE_CCM }, { name = "Hazy Maze Cave cannon (HMC)", cannon = COURSE_HMC }, { name = "Lethal Lava Land cannon (LLL)", cannon = COURSE_LLL }, { name = "Shifting Sand Land cannon (SSL)", cannon = COURSE_SSL }, { name = "Snowman's Land cannon (SL)", cannon = COURSE_SL }, { name = "Wet-Dry World cannon (WDW)", cannon = COURSE_WDW }, { name = "Tall, Tall Mountain cannon (TTM)", cannon = COURSE_TTM }, { name = "Tiny-Huge Island cannon (THI)", cannon = COURSE_THI }, { name = "Rainbow Ride cannon (RR)", cannon = COURSE_RR }, } local COIN_ROWS = { { name = "Course 1 (BoB)", course = COURSE_BOB }, { name = "Course 2 (WF)", course = COURSE_WF }, { name = "Course 3 (JRB)", course = COURSE_JRB }, { name = "Course 4 (CCM)", course = COURSE_CCM }, { name = "Course 5 (BBH)", course = COURSE_BBH }, { name = "Course 6 (HMC)", course = COURSE_HMC }, { name = "Course 7 (LLL)", course = COURSE_LLL }, { name = "Course 8 (SSL)", course = COURSE_SSL }, { name = "Course 9 (DDD)", course = COURSE_DDD }, { name = "Course 10 (SL)", course = COURSE_SL }, { name = "Course 11 (WDW)", course = COURSE_WDW }, { name = "Course 12 (TTM)", course = COURSE_TTM }, { name = "Course 13 (THI)", course = COURSE_THI }, { name = "Course 14 (TTC)", course = COURSE_TTC }, { name = "Course 15 (RR)", course = COURSE_RR }, } local WARP_ROWS = { { name = "Course 1 (BoB)", level = LEVEL_BOB, area = 1, acts = 6, front = true }, { name = "Course 2 (WF)", level = LEVEL_WF, area = 1, acts = 6, front = true }, { name = "Course 3 (JRB)", level = LEVEL_JRB, area = 1, acts = 6, front = true }, { name = "Course 3 (JRB) Ship", level = LEVEL_JRB, area = 2 }, { name = "Course 4 (CCM)", level = LEVEL_CCM, area = 1, acts = 6, front = true }, { name = "Course 4 (CCM) Slide", level = LEVEL_CCM, area = 2 }, { name = "Course 5 (BBH)", level = LEVEL_BBH, area = 1, acts = 6, front = true }, { name = "Course 6 (HMC)", level = LEVEL_HMC, area = 1, acts = 6, front = true }, { name = "Course 7 (LLL)", level = LEVEL_LLL, area = 1, acts = 6, front = true }, { name = "Course 7 (LLL) Volcano", level = LEVEL_LLL, area = 2 }, { name = "Course 8 (SSL)", level = LEVEL_SSL, area = 1, acts = 6, front = true }, { name = "Course 8 (SSL) Pyramid", level = LEVEL_SSL, area = 2 }, { name = "Course 9 (DDD)", level = LEVEL_DDD, area = 1, acts = 6, front = true }, { name = "Course 9 (DDD) Sub", level = LEVEL_DDD, area = 2 }, { name = "Course 10 (SL)", level = LEVEL_SL, area = 1, acts = 6, front = true }, { name = "Course 10 (SL) Igloo", level = LEVEL_SL, area = 2 }, { name = "Course 11 (WDW)", level = LEVEL_WDW, area = 1, acts = 6, front = true }, { name = "Course 11 (WDW) Downtown", level = LEVEL_WDW, area = 2 }, { name = "Course 12 (TTM)", level = LEVEL_TTM, area = 1, acts = 6, front = true }, { name = "Course 12 (TTM) Slide", level = LEVEL_TTM, area = 2 }, { name = "Course 13 (THI) Huge", level = LEVEL_THI, area = 1, acts = 6, front = true }, { name = "Course 13 (THI) Tiny", level = LEVEL_THI, area = 2, acts = 6, front = true }, { name = "Course 13 (THI) Wiggler", level = LEVEL_THI, area = 3 }, { name = "Course 14 (TTC)", level = LEVEL_TTC, area = 1, acts = 6, front = true }, { name = "Course 15 (RR)", level = LEVEL_RR, area = 1, acts = 6, front = true }, { name = "Bowser 1 (BitDW)", level = LEVEL_BITDW, area = 1, front = true }, { name = "Bowser 1 (BitDW) Fight", level = LEVEL_BOWSER_1, area = 1, death = { LEVEL_BITDW, 1, 0x0C } }, { name = "Bowser 2 (BitFS)", level = LEVEL_BITFS, area = 1, front = true }, { name = "Bowser 2 (BitFS) Fight", level = LEVEL_BOWSER_2, area = 1, death = { LEVEL_BITFS, 1, 0x0C } }, { name = "Bowser 3 (BitS)", level = LEVEL_BITS, area = 1, front = true }, { name = "Bowser 3 (BitS) Fight", level = LEVEL_BOWSER_3, area = 1, death = { LEVEL_BITS, 1, 0x0C } }, { name = "Princess's Secret Slide (PSS)", level = LEVEL_PSS, area = 1, front = true }, { name = "Metal Cap (CotMC)", level = LEVEL_COTMC, area = 1, front = true }, { name = "Wing Cap (TotWC)", level = LEVEL_TOTWC, area = 1, front = true }, { name = "Vanish Cap (VCutM)", level = LEVEL_VCUTM, area = 1, front = true }, { name = "Wing Mario Over Rainbow (WMotR)", level = LEVEL_WMOTR, area = 1, front = true }, { name = "Secret Aquarium (SA)", level = LEVEL_SA, area = 1, front = true }, { name = "Castle Grounds", level = LEVEL_CASTLE_GROUNDS, area = 1 }, { name = "Castle", level = LEVEL_CASTLE, area = 1, death = { LEVEL_CASTLE_GROUNDS, 1, 0x03 } }, { name = "Castle Upstairs", level = LEVEL_CASTLE, area = 2 }, { name = "Castle Basement", level = LEVEL_CASTLE, area = 3 }, { name = "Castle Courtyard", level = LEVEL_CASTLE_COURTYARD, area = 1 }, } local function front_available(row) return row.front or row.death ~= nil end local function warp_column_enabled(row, column) local acts = row.acts or 1 if column > acts then return front_available(row) end return column >= 1 and column <= acts end local function snap_warp_column(prevRow, prevColumn) local row = WARP_ROWS[sSelection] local acts = row.acts or 1 local onFront = prevRow ~= nil and prevColumn > (prevRow.acts or 1) local target = onFront and (acts + 1) or math.min(math.max(prevColumn, 1), acts) if not warp_column_enabled(row, target) then target = acts end sColumn = target end local function warp_cols(row) return (row.acts or 1) + 1 end local function step_warp_column(delta) local row = WARP_ROWS[sSelection] local cols = warp_cols(row) local column = sColumn for _ = 1, cols do column = column + delta if column < 1 then column = cols elseif column > cols then column = 1 end if warp_column_enabled(row, column) then sColumn = column return end end end local sWarpUndo = {} local sWarpRedo = {} local function footer_slot_enabled(index) if sTab ~= 4 then return true end if index == 3 then return #sWarpUndo > 0 end if index == 4 then return #sWarpRedo > 0 end return true end local function footer_count() if sTab == 1 or sTab == 2 then return 4 end if sTab == 4 then return 7 end if sTab == 6 then return 3 end return 2 end local function step_focus_slot(delta) local limit = sFocus == "header" and HEADER_COUNT or footer_count() local slot = sChrome for _ = 1, limit do slot = slot + delta if slot < 1 then slot = limit elseif slot > limit then slot = 1 end if sFocus == "header" or footer_slot_enabled(slot) then sChrome = slot return end end end local function file_index() return get_current_save_file_num() - 1 end local function course_index(row) return row.courseIndex or (row.course - 1) end local function bit_count(value) local count = 0 while value ~= 0 do count = count + (value & 1) value = value >> 1 end return count end local function refresh_mario_star_count() local total = 0 local file = file_index() for _, row in ipairs(COURSE_ROWS) do total = total + bit_count(save_file_get_star_flags(file, course_index(row)) & 0x7F) end if gMarioStates[0] ~= nil then gMarioStates[0].numStars = total end end local function persist() save_file_set_flags(SAVE_FLAG_FILE_EXISTS) save_file_do_save(file_index(), 1) refresh_mario_star_count() end local function toggle_star_at(rowIndex, starIndex) local row = COURSE_ROWS[rowIndex] local mask = 1 << (starIndex - 1) local file = file_index() local index = course_index(row) local flags = save_file_get_star_flags(file, index) if (flags & mask) ~= 0 then save_file_remove_star_flags(file, index, mask) else save_file_set_star_flags(file, index, mask) end persist() end local function cannon_index(course) return course - 1 end local function cannon_is_open(course) return save_file_is_cannon_unlocked(file_index(), cannon_index(course)) ~= 0 end local function set_cannon_open(course, enabled) local index = cannon_index(course) local file = file_index() if enabled then save_file_set_star_flags(file, index, 0x80) else save_file_remove_star_flags(file, index, 0x80) end end local function toggle_flag_at(rowIndex) local row = FLAG_ROWS[rowIndex] if row.cannon ~= nil then set_cannon_open(row.cannon, not cannon_is_open(row.cannon)) else local flags = save_file_get_flags() if (flags & row.flag) ~= 0 then save_file_clear_flags(row.flag) else save_file_set_flags(row.flag) end end persist() end local function set_all_flags(enabled) for _, row in ipairs(FLAG_ROWS) do if row.cannon ~= nil then set_cannon_open(row.cannon, enabled) elseif enabled then save_file_set_flags(row.flag) elseif row.flag ~= SAVE_FLAG_FILE_EXISTS then save_file_clear_flags(row.flag) end end persist() end local function set_all_stars(enabled) for _, row in ipairs(COURSE_ROWS) do local file = file_index() local index = course_index(row) save_file_remove_star_flags(file, index, 0x7F) if enabled then save_file_set_star_flags(file, index, (1 << row.stars) - 1) end end persist() end local function set_course_stars(rowIndex, enabled) local row = COURSE_ROWS[rowIndex] local file = file_index() local index = course_index(row) save_file_remove_star_flags(file, index, 0x7F) if enabled then save_file_set_star_flags(file, index, (1 << row.stars) - 1) end persist() end local COIN_ACTIONS local MISC_ACTIONS local sMarioFrozen = mod_storage_load_bool("freeze_mario", false) local sCamLock = nil local AXIS_WIDTH_MIN = 1 local AXIS_WIDTH_MAX = 12 local LINE_OPACITY_MIN = 10 local LINE_OPACITY_MAX = 100 local AXIS_COLORS = { x = { pos = { 255, 0, 0 }, neg = { 240, 155, 155 } }, y = { pos = { 0, 255, 0 }, neg = { 155, 240, 155 } }, z = { pos = { 0, 0, 255 }, neg = { 155, 155, 240 } }, } local function axis_line_width(level) if level <= 3 then return 0.4 * level / 3 end return 0.4 * (level - 2) end local function load_axis_width() local value = mod_storage_load_integer("axis_level_v2", 0) if value == nil or value < 1 then local old = mod_storage_load_integer("axis_level", 0) if old ~= nil and old >= 1 and old ~= 5 then value = old + 2 else value = 5 end end if value < AXIS_WIDTH_MIN then value = 5 end if value > AXIS_WIDTH_MAX then value = AXIS_WIDTH_MAX end return value end local sShowAxes = mod_storage_load_bool("show_axes", false) local sAxisWidth = load_axis_width() local sAxisOpacity = load_ranged_integer("axis_opacity", 100, LINE_OPACITY_MIN, LINE_OPACITY_MAX) local function set_show_axes(enabled) sShowAxes = enabled and true or false mod_storage_save_bool("show_axes", sShowAxes) end local function set_axis_width(value) value = math.max(AXIS_WIDTH_MIN, math.min(AXIS_WIDTH_MAX, math.floor(value))) if value == sAxisWidth then return end sAxisWidth = value mod_storage_save_integer("axis_level_v2", sAxisWidth) end local function snap_tens(value, minValue, maxValue) value = math.max(minValue, math.min(maxValue, math.floor(value))) local snapped = math.floor((value + 5) / 10) * 10 if snapped < minValue then snapped = minValue end if snapped > maxValue then snapped = maxValue end return snapped end local function set_axis_opacity(value) value = snap_tens(value, LINE_OPACITY_MIN, LINE_OPACITY_MAX) if value == sAxisOpacity then return end sAxisOpacity = value mod_storage_save_integer("axis_opacity", sAxisOpacity) end local function set_keep_open(enabled) sKeepOpen = enabled and true or false mod_storage_save_bool("keep_open", sKeepOpen) end local function change_coin_score(row, amount, absolute) local file = file_index() local index = row.course - 1 local value = absolute if value == nil then value = save_file_get_course_coin_score(file, index) + amount end if (save_file_get_star_flags(file, index) & 0x40) ~= 0 then value = math.max(100, value) end value = math.max(0, math.min(255, value)) save_file_set_course_coin_score(file, index, value) persist() end local max_rows local function set_page_size(value) value = math.max(PAGE_MIN, math.min(PAGE_MAX, math.floor(value))) if value == sPageSize then return end sPageSize = value mod_storage_save_integer("page_size", sPageSize) local maxScroll = math.max(0, max_rows() - sPageSize) sScrollTarget = math.min(sScrollTarget, maxScroll) sScroll = math.min(sScroll, maxScroll) end local function set_ui_scale(value) value = snap_ui_scale(value) if value == sUiScale then return end sUiScale = value mod_storage_save_integer("ui_scale", sUiScale) end sUiOpacity = snap_tens(sUiOpacity, UI_OPACITY_MIN, UI_OPACITY_MAX) sAxisOpacity = snap_tens(sAxisOpacity, LINE_OPACITY_MIN, LINE_OPACITY_MAX) local function set_ui_opacity(value) value = snap_tens(value, UI_OPACITY_MIN, UI_OPACITY_MAX) if value == sUiOpacity then return end sUiOpacity = value mod_storage_save_integer("ui_opacity", sUiOpacity) end local function set_lives(value) local m = gMarioStates[0] if m == nil then return end value = math.max(0, math.min(100, math.floor(value))) m.numLives = value if gHudDisplay ~= nil then gHudDisplay.lives = value end end local function set_current_coins(value) local m = gMarioStates[0] if m == nil then return end value = math.max(0, math.min(999, math.floor(value))) m.numCoins = value if gHudDisplay ~= nil then gHudDisplay.coins = value end end local POS_MIN = -32768 local POS_MAX = 32767 local sFrozenPos = nil local function remember_frozen_pos(m) if m == nil or m.pos == nil then return end sFrozenPos = { x = m.pos.x, y = m.pos.y, z = m.pos.z } end local function set_mario_frozen(enabled) sMarioFrozen = enabled and true or false mod_storage_save_bool("freeze_mario", sMarioFrozen) if not sMarioFrozen then sFrozenPos = nil return end local m = gMarioStates[0] if m == nil then return end m.vel.x = 0 m.vel.y = 0 m.vel.z = 0 m.forwardVel = 0 m.slideVelX = 0 m.slideVelZ = 0 remember_frozen_pos(m) end local DISPLAY_SCALES = { 25, 50, 75, 100, 125, 150, 175, 200, 250, 300 } local DEBUG_DECIMALS = { 0, 1, 2, 3 } local DISPLAY_POSITIONS = { "Left Top", "Left Middle", "Left Bottom", "Right Top", "Right Middle", "Right Bottom", } local sDisplayFlags = { show_display = mod_storage_load_bool("show_display", false), show_coords = mod_storage_load_bool("show_coords", true), show_health = mod_storage_load_bool("show_health", true), show_speed = mod_storage_load_bool("show_speed", true), show_speeds = mod_storage_load_bool("show_speeds", true), show_action = mod_storage_load_bool("show_action", true), show_shadow = mod_storage_load_bool("show_shadow", true), } local sDebugDecimals = mod_storage_load_integer("debug_decimals", 1) if sDebugDecimals == nil or sDebugDecimals < 0 or sDebugDecimals > 3 then sDebugDecimals = 1 end local function snap_display_scale(value) local best = 100 local bestDist = 9999 for _, option in ipairs(DISPLAY_SCALES) do local dist = math.abs(option - value) if dist < bestDist then best = option bestDist = dist end end return best end local function load_display_scale() local value = mod_storage_load_integer("display_scale", 75) if value == nil then value = 75 end return snap_display_scale(value) end local function load_display_position() local value = mod_storage_load("display_pos") local renamed = { ["Top Left"] = "Left Top", ["Middle Left"] = "Left Middle", ["Bottom Left"] = "Left Bottom", ["Top Right"] = "Right Top", ["Middle Right"] = "Right Middle", ["Bottom Right"] = "Right Bottom", } if renamed[value] ~= nil then value = renamed[value] end if value == nil then return "Left Top" end for _, name in ipairs(DISPLAY_POSITIONS) do if name == value then return value end end return "Left Top" end local sDisplayScale = load_display_scale() local sDisplayPosition = load_display_position() local function set_debug_decimals(value) value = math.max(0, math.min(3, math.floor(value))) if value == sDebugDecimals then return end sDebugDecimals = value mod_storage_save_integer("debug_decimals", sDebugDecimals) end local function set_display_scale(value) value = snap_display_scale(value) if value == sDisplayScale then return end sDisplayScale = value mod_storage_save_integer("display_scale", sDisplayScale) end local function set_display_position(value) if value == sDisplayPosition then return end sDisplayPosition = value mod_storage_save("display_pos", value) end local function set_display_flag(key, enabled) sDisplayFlags[key] = enabled and true or false mod_storage_save_bool(key, sDisplayFlags[key]) end local function reset_settings() set_page_size(12) set_ui_scale(100) set_ui_opacity(100) set_axis_width(5) set_axis_opacity(100) set_show_axes(false) set_mario_frozen(false) set_display_scale(75) set_display_position("Left Top") set_display_flag("show_display", false) set_display_flag("show_coords", true) set_display_flag("show_health", true) set_display_flag("show_speed", true) set_display_flag("show_speeds", true) set_display_flag("show_action", true) set_display_flag("show_shadow", true) set_debug_decimals(1) end local function set_position(axis, value) local m = gMarioStates[0] if m == nil or m.pos == nil then return end value = math.max(POS_MIN, math.min(POS_MAX, math.floor(value))) m.pos[axis] = value if axis == "y" then m.peakHeight = value end if sMarioFrozen then if sFrozenPos == nil then remember_frozen_pos(m) end if sFrozenPos ~= nil then sFrozenPos[axis] = value end elseif axis == "y" and m.floorHeight ~= nil and value > m.floorHeight + 1 then m.vel.y = 0 set_mario_action(m, ACT_FREEFALL, 0) end local obj = m.marioObj if obj == nil then return end if axis == "x" then obj.oPosX = value elseif axis == "y" then obj.oPosY = value else obj.oPosZ = value end end local function read_position(axis) local m = gMarioStates[0] if m == nil or m.pos == nil then return 0 end return math.floor(m.pos[axis]) end local function wrap_deg(value) value = math.floor(value) % 360 if value < 0 then value = value + 360 end return value end local function deg_to_s16(deg) local value = math.floor(deg * 65536 / 360) % 65536 if value < 0 then value = value + 65536 end return value end local function s16_to_deg(value) if value >= 32768 then value = value - 65536 end return math.floor(value * 360 / 65536 + 0.5) end local function read_live_camera() local lakitu = gLakituState if lakitu == nil or lakitu.focus == nil or lakitu.pos == nil then return nil end local dist, pitch, yaw = vec3f_get_dist_and_angle(lakitu.focus, lakitu.pos) return { dist = dist, pitch = pitch, yaw = yaw, roll = lakitu.roll or 0 } end local function apply_camera_lock() if sCamLock == nil or gLakituState == nil or gLakituState.focus == nil or gLakituState.pos == nil then return end vec3f_set_dist_and_angle(gLakituState.focus, gLakituState.pos, sCamLock.dist, sCamLock.pitch, sCamLock.yaw) if gLakituState.goalFocus ~= nil and gLakituState.goalPos ~= nil then vec3f_set_dist_and_angle(gLakituState.goalFocus, gLakituState.goalPos, sCamLock.dist, sCamLock.pitch, sCamLock.yaw) end gLakituState.yaw = sCamLock.yaw gLakituState.nextYaw = sCamLock.yaw gLakituState.roll = sCamLock.roll local m = gMarioStates[0] local cam = m ~= nil and m.area ~= nil and m.area.camera or nil if cam ~= nil then cam.yaw = sCamLock.yaw cam.nextYaw = sCamLock.yaw if cam.focus ~= nil and cam.pos ~= nil then vec3f_set_dist_and_angle(cam.focus, cam.pos, sCamLock.dist, sCamLock.pitch, sCamLock.yaw) end end end local function edit_camera(field, value) if sCamLock == nil then sCamLock = read_live_camera() if sCamLock == nil then return end end sCamLock[field] = value apply_camera_lock() end local HEALTH_MAX = 0x880 local function set_health(value) local m = gMarioStates[0] if m == nil then return end value = math.max(0, math.min(HEALTH_MAX, math.floor(value))) m.health = value m.healCounter = 0 m.hurtCounter = 0 end local SETTINGS_ROWS = { { name = "UI Visible Rows", choices = VISIBLE_ROW_CHOICES, get = function() return sPageSize end, set = set_page_size, }, { name = "UI Scale (%)", choices = UI_SCALE_CHOICES, get = function() return sUiScale end, set = set_ui_scale, }, { name = "UI Opacity (%)", choices = OPACITY_CHOICES, get = function() return sUiOpacity end, set = set_ui_opacity, separator = true, }, { name = "Show Axes Lines", toggle = true, axisLines = true, }, { name = "Axes Line Thickness", choices = THICKNESS_CHOICES, get = function() return sAxisWidth end, set = set_axis_width, }, { name = "Axes Line Opacity (%)", choices = OPACITY_CHOICES, get = function() return sAxisOpacity end, set = set_axis_opacity, separator = true, }, { name = "Show Debug Infos", toggle = true, get = function() return sDisplayFlags.show_display end, set = function(value) set_display_flag("show_display", value) end, }, { name = "Debug Position", choices = DISPLAY_POSITIONS, get = function() return sDisplayPosition end, set = set_display_position, }, { name = "Debug Scale (%)", choices = DISPLAY_SCALES, get = function() return sDisplayScale end, set = set_display_scale, }, { name = "Debug Decimals", choices = DEBUG_DECIMALS, get = function() return sDebugDecimals end, set = set_debug_decimals, }, { name = "Debug Text Shadow", toggle = true, get = function() return sDisplayFlags.show_shadow end, set = function(value) set_display_flag("show_shadow", value) end, }, { name = "Display Coordinates", toggle = true, get = function() return sDisplayFlags.show_coords end, set = function(value) set_display_flag("show_coords", value) end, }, { name = "Display Health", toggle = true, get = function() return sDisplayFlags.show_health end, set = function(value) set_display_flag("show_health", value) end, }, { name = "Display H Speed", toggle = true, get = function() return sDisplayFlags.show_speed end, set = function(value) set_display_flag("show_speed", value) end, }, { name = "Display Velocities", toggle = true, get = function() return sDisplayFlags.show_speeds end, set = function(value) set_display_flag("show_speeds", value) end, }, { name = "Display Action", toggle = true, get = function() return sDisplayFlags.show_action end, set = function(value) set_display_flag("show_action", value) end, }, } local MISC_ROWS = { { name = "Current Lives", min = 0, max = 100, get = function() local m = gMarioStates[0] return m ~= nil and m.numLives or 0 end, set = set_lives, }, { name = "Current Health", min = 0, max = HEALTH_MAX, get = function() local m = gMarioStates[0] return m ~= nil and m.health or 0 end, set = set_health, }, { name = "Current Coins", min = 0, max = 999, get = function() local m = gMarioStates[0] return m ~= nil and m.numCoins or 0 end, set = set_current_coins, }, { name = "Camera Pitch", min = -80, max = 80, get = function() local cam = sCamLock or read_live_camera() return cam ~= nil and s16_to_deg(cam.pitch) or 0 end, set = function(value) value = math.max(-80, math.min(80, math.floor(value))) edit_camera("pitch", deg_to_s16(value)) end, }, { name = "Camera Yaw", min = 0, max = 359, get = function() local cam = sCamLock or read_live_camera() return cam ~= nil and wrap_deg(s16_to_deg(cam.yaw)) or 0 end, set = function(value) edit_camera("yaw", deg_to_s16(wrap_deg(value))) end, }, { name = "Camera Roll", min = -180, max = 180, get = function() local cam = sCamLock or read_live_camera() return cam ~= nil and s16_to_deg(cam.roll) or 0 end, set = function(value) value = math.max(-180, math.min(180, math.floor(value))) edit_camera("roll", deg_to_s16(value)) end, }, { name = "Camera Distance", min = 100, max = 8000, get = function() local cam = sCamLock or read_live_camera() return cam ~= nil and math.floor(cam.dist) or 0 end, set = function(value) value = math.max(100, math.min(8000, math.floor(value))) edit_camera("dist", value) end, }, { name = "Position X", axis = "x", color = AXIS_COLORS.x.pos, min = POS_MIN, max = POS_MAX, get = function() return read_position("x") end, set = function(value) set_position("x", value) end, }, { name = "Position Y", axis = "y", color = AXIS_COLORS.y.pos, min = POS_MIN, max = POS_MAX, get = function() return read_position("y") end, set = function(value) set_position("y", value) end, }, { name = "Position Z", axis = "z", color = AXIS_COLORS.z.pos, min = POS_MIN, max = POS_MAX, get = function() return read_position("z") end, set = function(value) set_position("z", value) end, }, { name = "Freeze Mario", toggle = true, }, } local function option_rows() if sTab == 6 then return SETTINGS_ROWS end return MISC_ROWS end local function row_button_count(row) if row == nil or row.toggle then return 1 end if row.choices ~= nil then return #row.choices end return #MISC_ACTIONS end local function step_settings_column(delta) local count = row_button_count(option_rows()[sSelection]) if count < 1 then return end sColumn = sColumn + delta if sColumn < 1 then sColumn = count elseif sColumn > count then sColumn = 1 end end max_rows = function() if sTab == 1 then return #COURSE_ROWS end if sTab == 2 then return #FLAG_ROWS end if sTab == 3 then return #COIN_ROWS end if sTab == 4 then return #WARP_ROWS end if sTab == 6 then return #SETTINGS_ROWS end return #MISC_ROWS end local function clamp_scroll() local maxScroll = math.max(0, max_rows() - sPageSize) sScrollTarget = math.max(0, math.min(maxScroll, sScrollTarget)) end local function change_page(delta) clamp_scroll() sScrollTarget = sScrollTarget + delta clamp_scroll() local first = math.floor(sScrollTarget) + 1 local last = math.min(max_rows(), math.floor(sScrollTarget) + sPageSize) if last < 1 then last = 1 end if sSelection > last then sSelection = last end if sSelection < first then sSelection = first end if sTab == 4 then local row = WARP_ROWS[sSelection] if row == nil then sSelection = 1 row = WARP_ROWS[1] end if row ~= nil and not warp_column_enabled(row, sColumn) then sColumn = row.acts or 1 end end end local function set_tab(tab) sTab = tab sPage = 1 sScroll = 0 sScrollTarget = 0 sSelection = 1 sColumn = 1 if sFocus == "footer" then sChrome = math.min(sChrome, footer_count()) end end sWarpUndo = {} sWarpRedo = {} local sWarpTraveling = false local sReloadPos = nil local sReloadFrames = 0 local WARP_HISTORY_MAX = 100 local close_editor local warp_into local warp_front local undo_warp local redo_warp local reload_area local push_warp_history local function activate_chrome() if sFocus == "header" then if sChrome == 1 then set_tab(1) elseif sChrome == 2 then set_tab(2) elseif sChrome == 3 then set_tab(3) elseif sChrome == 4 then set_tab(4) elseif sChrome == 5 then set_tab(5) elseif sChrome == 6 then set_tab(6) else close_editor() end elseif sFocus == "footer" then if sChrome == 1 then change_page(-1) elseif sChrome == 2 then change_page(1) elseif sChrome == 3 and sTab == 1 then set_all_stars(true) elseif sChrome == 4 and sTab == 1 then set_all_stars(false) elseif sChrome == 3 and sTab == 2 then set_all_flags(true) elseif sChrome == 4 and sTab == 2 then set_all_flags(false) elseif sChrome == 3 and sTab == 4 then undo_warp() elseif sChrome == 4 and sTab == 4 then redo_warp() elseif sChrome == 5 and sTab == 4 then reload_area(false) elseif sChrome == 6 and sTab == 4 then reload_area(true) elseif sChrome == 7 and sTab == 4 then set_keep_open(not sKeepOpen) elseif sChrome == 3 and sTab == 6 then reset_settings() end end end local function open_command() sOpen = not sOpen if sOpen then sWasMouseLocked = djui_hud_is_mouse_locked() djui_hud_set_mouse_locked(false) sMouseWasDown = false sPreviousButtons = 0 sRepeatButton = 0 sRepeatTimer = 0 sRepeatSteps = 0 else djui_hud_set_mouse_locked(sWasMouseLocked) end return true end local function block_mario_input(m) m.controller.buttonPressed = 0 m.controller.buttonDown = 0 m.controller.buttonReleased = 0 m.controller.stickX = 0 m.controller.stickY = 0 m.controller.stickMag = 0 m.controller.rawStickX = 0 m.controller.rawStickY = 0 m.controller.extStickX = 0 m.controller.extStickY = 0 m.input = 0 m.intendedMag = 0 end local function update(m) if not sOpen or m.playerIndex ~= 0 then return end local down = m.controller.buttonDown local stickX, stickY = m.controller.stickX, m.controller.stickY local stickDir = 0 if stickY > 48 then stickDir = stickDir | U_JPAD elseif stickY < -48 then stickDir = stickDir | D_JPAD end if stickX > 48 then stickDir = stickDir | R_JPAD elseif stickX < -48 then stickDir = stickDir | L_JPAD end down = down | stickDir local pressed = down & ~sPreviousButtons sPreviousButtons = down local navigated = false local directions = U_JPAD | D_JPAD | L_JPAD | R_JPAD -- A mouse click is also exposed as a controller button by some backends. -- Never process it a second time as a gamepad action. if (djui_hud_get_mouse_buttons_down() & 1) ~= 0 then pressed = 0 end -- Drop the real buttons before any action runs. A later error must not -- leave A or B for Mario, or Start for the pause menu. block_mario_input(m) local newDirection = pressed & directions local heldDirection = down & directions if newDirection ~= 0 then sRepeatButton = newDirection sRepeatTimer = REPEAT_DELAY sRepeatSteps = 0 elseif heldDirection == 0 or (heldDirection & sRepeatButton) == 0 then sRepeatButton = 0 sRepeatTimer = 0 sRepeatSteps = 0 elseif sRepeatButton ~= 0 then sRepeatTimer = sRepeatTimer - 1 if sRepeatTimer <= 0 then pressed = pressed | sRepeatButton sRepeatSteps = sRepeatSteps + 1 local rate = REPEAT_RATE if sRepeatSteps >= 8 then rate = 1 elseif sRepeatSteps >= 4 then rate = 2 end sRepeatTimer = rate end end local scrolling = (down & U_CBUTTONS) ~= 0 or (down & D_CBUTTONS) ~= 0 or sScrollMouseDir ~= 0 or (sFocus == "footer" and (down & A_BUTTON) ~= 0 and (sChrome == 1 or sChrome == 2)) if not scrolling then sScrollHold = 0 end if sCloseFromStart or (pressed & START_BUTTON) ~= 0 then sCloseFromStart = false arm_close_block() close_editor() elseif (pressed & B_BUTTON) ~= 0 then if sFocus == "header" then arm_close_block() close_editor() else sFocus = "header" sChrome = sTab end elseif sFocus == "footer" and (pressed & L_JPAD) ~= 0 then step_focus_slot(-1) elseif sFocus == "footer" and (pressed & R_JPAD) ~= 0 then step_focus_slot(1) elseif scrolling then local dir = 0 if (down & U_CBUTTONS) ~= 0 or sScrollMouseDir < 0 or (sFocus == "footer" and sChrome == 1 and (down & A_BUTTON) ~= 0) then dir = -1 else dir = 1 end local edge = (pressed & (dir < 0 and U_CBUTTONS or D_CBUTTONS)) ~= 0 local buttonEdge = (pressed & A_BUTTON) ~= 0 and sFocus == "footer" and (sChrome == 1 or sChrome == 2) if sScrollHold ~= dir then sScrollHold = dir sScrollHoldTimer = REPEAT_DELAY sScrollHoldSteps = 0 if edge or buttonEdge or sScrollMouseDir ~= 0 then change_page(dir) end else sScrollHoldTimer = sScrollHoldTimer - 1 if sScrollHoldTimer <= 0 then sScrollHoldSteps = sScrollHoldSteps + 1 local rate = REPEAT_RATE if sScrollHoldSteps >= 8 then rate = 1 elseif sScrollHoldSteps >= 4 then rate = 2 end sScrollHoldTimer = rate change_page(dir) end end elseif (pressed & L_TRIG) ~= 0 then set_tab(sTab == 1 and TAB_COUNT or sTab - 1) elseif (pressed & R_TRIG) ~= 0 then set_tab(sTab == TAB_COUNT and 1 or sTab + 1) elseif (pressed & U_JPAD) ~= 0 then if sFocus == "header" then sFocus = "footer" sChrome = 1 elseif sFocus == "footer" then sFocus = "list" elseif sFocus == "list" and sSelection == 1 then sFocus = "header" sChrome = sTab elseif sFocus == "list" then local prevRow = sTab == 4 and WARP_ROWS[sSelection] or nil local prevColumn = sColumn sSelection = sSelection - 1 navigated = true if prevRow ~= nil then snap_warp_column(prevRow, prevColumn) end end elseif (pressed & D_JPAD) ~= 0 then if sFocus == "footer" then sFocus = "header" sChrome = sTab elseif sFocus == "header" then sFocus = "list" elseif sFocus == "list" and sSelection == max_rows() then sFocus = "footer" sChrome = 1 elseif sFocus == "list" then local prevRow = sTab == 4 and WARP_ROWS[sSelection] or nil local prevColumn = sColumn sSelection = sSelection + 1 navigated = true if prevRow ~= nil then snap_warp_column(prevRow, prevColumn) end end elseif (pressed & L_JPAD) ~= 0 then if sFocus == "list" then if sTab == 1 and sColumn == 1 then sColumn = COL_NONE elseif sTab == 1 and sColumn == COL_NONE then sColumn = COL_ALL elseif sTab == 1 and sColumn == COL_ALL then sColumn = COURSE_ROWS[sSelection].stars elseif sTab == 3 and sColumn == 1 then sColumn = #COIN_ACTIONS elseif sTab == 4 then step_warp_column(-1) elseif sTab == 5 or sTab == 6 then step_settings_column(-1) else sColumn = math.max(1, sColumn - 1) end else step_focus_slot(-1) end elseif (pressed & R_JPAD) ~= 0 then if sFocus == "list" then if sTab == 1 then local stars = COURSE_ROWS[sSelection].stars if sColumn == COL_NONE then sColumn = 1 elseif sColumn < stars then sColumn = sColumn + 1 elseif sColumn < COL_ALL then sColumn = COL_ALL else sColumn = COL_NONE end elseif sTab == 3 then sColumn = sColumn >= #COIN_ACTIONS and 1 or sColumn + 1 elseif sTab == 4 then step_warp_column(1) elseif sTab == 5 or sTab == 6 then step_settings_column(1) end else step_focus_slot(1) end elseif (pressed & A_BUTTON) ~= 0 then if sFocus ~= "list" then activate_chrome() elseif sTab == 1 then if sColumn == COL_ALL then set_course_stars(sSelection, true) elseif sColumn == COL_NONE then set_course_stars(sSelection, false) else sColumn = math.min(sColumn, COURSE_ROWS[sSelection].stars) toggle_star_at(sSelection, sColumn) end elseif sTab == 2 then toggle_flag_at(sSelection) elseif sTab == 3 then local action = COIN_ACTIONS[sColumn] if action ~= nil then change_coin_score(COIN_ROWS[sSelection], action.amount, action.absolute) end elseif sTab == 4 then local row = WARP_ROWS[sSelection] local acts = row.acts or 1 if sColumn <= acts then warp_into(row, acts > 1 and sColumn or 0) elseif row.front or row.death ~= nil then warp_front(row) end elseif sTab == 5 or sTab == 6 then local row = option_rows()[sSelection] if row ~= nil and row.choices ~= nil then local choice = row.choices[sColumn] if choice ~= nil then row.set(choice) end elseif row ~= nil and row.toggle then if row.get ~= nil and row.set ~= nil then row.set(not row.get()) elseif row.axisLines then set_show_axes(not sShowAxes) else set_mario_frozen(not sMarioFrozen) end else local action = MISC_ACTIONS[sColumn] if row ~= nil and action ~= nil then row.set(row.get() + action.amount) end end end elseif (pressed & X_BUTTON) ~= 0 then if sTab == 1 then set_course_stars(sSelection, true) end if sTab == 2 then set_all_flags(true) end elseif (pressed & Y_BUTTON) ~= 0 then if sTab == 1 then set_course_stars(sSelection, false) end if sTab == 2 then set_all_flags(false) end end if navigated then local first = math.floor(sScrollTarget) + 1 local last = math.min(max_rows(), math.floor(sScrollTarget) + sPageSize) if sSelection < first then sScrollTarget = sSelection - 1 elseif sSelection > last then sScrollTarget = sSelection - sPageSize end clamp_scroll() end if sFocus == "list" and sTab == 1 and sColumn < COL_ALL then sColumn = math.min(sColumn, COURSE_ROWS[sSelection].stars) elseif sFocus == "list" and sTab == 4 then local row = WARP_ROWS[sSelection] if not warp_column_enabled(row, sColumn) then sColumn = row.acts or 1 end elseif sFocus == "list" and (sTab == 5 or sTab == 6) then local row = option_rows()[sSelection] local count = row_button_count(row) sColumn = math.max(1, math.min(sColumn, math.max(1, count))) end block_mario_input(m) end local CELL = 8 local function fade(alpha) return math.floor(alpha * sUiOpacity / 100) end local function draw_text(text, x, y, scale, r, g, b, a) djui_hud_set_color(r, g, b, fade(a)) djui_hud_print_text(text, x, y, scale) end local function draw_centered_text(text, x, y, scale, r, g, b, a) local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_text_alignment(0.5, 0.5) draw_text(text, x, y - CELL / 8, scale, r, g, b, a) djui_hud_set_text_alignment(oldH, oldV) end close_editor = function() sOpen = false sCamLock = nil djui_hud_set_mouse_locked(sWasMouseLocked) end local function finish_warp() if is_game_paused() then game_unpause() end if sKeepOpen then return end close_editor() arm_close_block() end warp_into = function(row, act) push_warp_history() if warp_to_level(row.level, row.area, act) then finish_warp() else table.remove(sWarpUndo) djui_chat_message_create("\\#ff8080\\That warp is not available.") end end warp_front = function(row) local ok = false push_warp_history() if row.death ~= nil then local dest = row.death ok = warp_to_warpnode(dest[1], dest[2], 0, dest[3]) if not ok then djui_chat_message_create("\\#ff8080\\That respawn warp is not available.") end elseif row.front and warp_to_castle(row.level) then ok = true else djui_chat_message_create("\\#ff8080\\There is no entrance in front of that level.") end if ok then finish_warp() else table.remove(sWarpUndo) end end local function current_place() local np = gNetworkPlayers[0] if np == nil then return nil end return { level = np.currLevelNum, area = np.currAreaIndex, act = np.currActNum } end push_warp_history = function() if sWarpTraveling then return end local here = current_place() if here == nil then return end sWarpRedo = {} sWarpUndo[#sWarpUndo + 1] = here while #sWarpUndo > WARP_HISTORY_MAX do table.remove(sWarpUndo, 1) end end local function warp_to_place(place) if place == nil then return false end sWarpTraveling = true local ok = warp_to_level(place.level, place.area, place.act) sWarpTraveling = false if ok then finish_warp() end return ok end undo_warp = function() if #sWarpUndo == 0 then return end local here = current_place() local dest = table.remove(sWarpUndo) if here ~= nil then sWarpRedo[#sWarpRedo + 1] = here while #sWarpRedo > WARP_HISTORY_MAX do table.remove(sWarpRedo, 1) end end if not warp_to_place(dest) and here ~= nil then sWarpUndo[#sWarpUndo + 1] = dest table.remove(sWarpRedo) djui_chat_message_create("\\#ff8080\\Could not undo that warp.") end end redo_warp = function() if #sWarpRedo == 0 then return end local here = current_place() local dest = table.remove(sWarpRedo) if here ~= nil then sWarpUndo[#sWarpUndo + 1] = here while #sWarpUndo > WARP_HISTORY_MAX do table.remove(sWarpUndo, 1) end end if not warp_to_place(dest) and here ~= nil then sWarpRedo[#sWarpRedo + 1] = dest table.remove(sWarpUndo) djui_chat_message_create("\\#ff8080\\Could not redo that warp.") end end reload_area = function(teleport) local here = current_place() local m = gMarioStates[0] if here == nil or m == nil then return end if teleport then sReloadPos = { x = m.pos.x, y = m.pos.y, z = m.pos.z, yaw = m.faceAngle.y } sReloadFrames = 8 else sReloadPos = nil sReloadFrames = 0 end sWarpTraveling = true local ok = warp_to_level(here.level, here.area, here.act) sWarpTraveling = false if ok then finish_warp() else sReloadPos = nil sReloadFrames = 0 djui_chat_message_create("\\#ff8080\\Could not reload this area.") end end local function apply_reload_pos(m) if sReloadPos == nil or sReloadFrames <= 0 or m == nil then return end m.pos.x = sReloadPos.x m.pos.y = sReloadPos.y m.pos.z = sReloadPos.z m.faceAngle.y = sReloadPos.yaw if m.marioObj ~= nil then m.marioObj.oPosX = sReloadPos.x m.marioObj.oPosY = sReloadPos.y m.marioObj.oPosZ = sReloadPos.z m.marioObj.oFaceAngleYaw = sReloadPos.yaw end sReloadFrames = sReloadFrames - 1 if sReloadFrames <= 0 then sReloadPos = nil end end local function is_hovered(x, y, w, h) return sMouseX >= x and sMouseX <= x + w and sMouseY >= y and sMouseY <= y + h end local sPendingClick = nil local function button(x, y, w, h, label, active, onClick, textScale, enabled, bgR, bgG, bgB, textR, textG, textB) if enabled == nil then enabled = true end local hover = enabled and is_hovered(x, y, w, h) if bgR ~= nil then if hover then djui_hud_set_color(math.min(255, bgR + 30), math.min(255, bgG + 30), math.min(255, bgB + 30), fade(255)) else djui_hud_set_color(bgR, bgG, bgB, fade(255)) end elseif not enabled then djui_hud_set_color(24, 28, 38, fade(255)) elseif active then djui_hud_set_color(45, 125, 220, fade(255)) elseif hover then djui_hud_set_color(65, 90, 125, fade(255)) else djui_hud_set_color(35, 48, 68, fade(255)) end djui_hud_render_rect(x, y, w, h) local scale = textScale or 0.24 if enabled then draw_centered_text(label, x + w * 0.5, y + h * 0.5, scale, textR or 245, textG or 245, textB or 245, 255) else draw_centered_text(label, x + w * 0.5, y + h * 0.5, scale, 80, 95, 120, 255) end if hover and sMouseClicked and onClick ~= nil then sPendingClick = onClick end end local function render_texture_centered(texture, x, y, w, h, scale) local tw = texture.width * scale local th = texture.height * scale djui_hud_render_texture(texture, x + (w - tw) * 0.5, y + (h - th) * 0.5, scale, scale) end local function render_texture_fit(texture, x, y, w, h, fill) if texture == nil or texture.width == nil or texture.height == nil or texture.width <= 0 or texture.height <= 0 then return end local scale = math.min((w * fill) / texture.width, (h * fill) / texture.height) render_texture_centered(texture, x, y, w, h, scale) end local function fitted_icon(texture, boxH) if texture == nil or texture.height == nil or texture.width == nil or texture.height <= 0 or texture.width <= 0 then return 0, 0 end local iconH = boxH * 0.75 local scale = iconH / texture.height return scale, texture.width * scale end local function icon_button(x, y, w, h, label, texture, active, onClick, textScale, tintR, tintG, tintB, iconSize) button(x, y, w, h, "", active, onClick) local midY = y + h * 0.5 local scale, iconW = 0, 0 if texture ~= nil and texture.height ~= nil and texture.height > 0 then if iconSize then scale = iconSize / texture.height iconW = texture.width * scale else scale, iconW = fitted_icon(texture, h) end end if scale > 0 then local drawnH = texture.height * scale local pad = h * 0.25 djui_hud_set_color(tintR or 255, tintG or 255, tintB or 255, fade(255)) djui_hud_render_texture(texture, x + pad, midY - drawnH * 0.5, scale, scale) end local textX = x + h * 0.25 + iconW + h * 0.125 local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_text_alignment(0, 0.5) draw_text(label, textX, midY - CELL / 8, textScale, 245, 245, 245, 255) djui_hud_set_text_alignment(oldH, oldV) end local function selection_outline(x, y, w, h) local t = 0.5 * sUiScale / 100 djui_hud_set_color(255, 220, 35, fade(255)) djui_hud_render_rect(x, y, w, t) djui_hud_render_rect(x, y + h - t, w, t) djui_hud_render_rect(x, y, t, h) djui_hud_render_rect(x + w - t, y, t, h) end local function visible_range(total) local maxScroll = math.max(0, total - sPageSize) sScrollTarget = math.max(0, math.min(maxScroll, sScrollTarget)) sScroll = math.max(0, math.min(maxScroll, sScroll)) local diff = sScrollTarget - sScroll if math.abs(diff) < 0.08 then sScroll = sScrollTarget else sScroll = sScroll + diff * 0.72 end local first = math.max(1, math.floor(sScroll) + 1) local last = math.min(total, math.floor(sScroll) + sPageSize) if sScroll > math.floor(sScroll) then last = math.min(total, last + 1) end return first, last end local PITCH = 9 local function separator_span() local extra = 0 local gap = CELL * 0.7 for _, listed in ipairs(SETTINGS_ROWS) do if listed.separator then extra = extra + gap end end return extra end local function row_y(listY, index) local pitch = PITCH if sTab ~= 6 then pitch = PITCH + separator_span() / sPageSize end return listY + (index - 1 - sScroll) * pitch end local function install_ui() local function row_visible(listY, lineY, extra) local rel = lineY - listY local window = sPageSize * PITCH + math.max(extra or 0, separator_span()) return rel >= -1 and rel + CELL <= window + 1 end local NAME_SCALE = 0.2 local function hud_mark_scale(size) local texture = gTextures.coin local native = 16 if texture ~= nil and texture.height ~= nil and texture.height > 0 then native = texture.height end return size / native end local HUD_COUNT_SIZE = 5 local function hud_text_w(text, size) djui_hud_set_font(FONT_HUD) local width = djui_hud_measure_text(tostring(text)) * hud_mark_scale(size) djui_hud_set_font(FONT_SPECIAL) return width end local function draw_hud_number(digit, centerX, centerY, size, r, g, b) local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_font(FONT_HUD) djui_hud_set_text_alignment(0, 0) djui_hud_set_color(r, g, b, fade(255)) local text = tostring(digit) local scale = hud_mark_scale(size) local width = djui_hud_measure_text(text) * scale djui_hud_print_text(text, centerX - width * 0.5 - 0.5, centerY - size * 0.5, scale) djui_hud_set_text_alignment(oldH, oldV) djui_hud_set_font(FONT_SPECIAL) end local function draw_hud_count(text, x, centerY, size, r, g, b) local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_font(FONT_HUD) djui_hud_set_text_alignment(0, 0) djui_hud_set_color(r, g, b, fade(255)) djui_hud_print_text(tostring(text), x, centerY - size * 0.5, hud_mark_scale(size)) djui_hud_set_text_alignment(oldH, oldV) djui_hud_set_font(FONT_SPECIAL) end local function draw_hud_coin(centerX, centerY, size, r, g, b) local texture = gTextures.coin if texture == nil or texture.height == nil or texture.height <= 0 then return end local scale = hud_mark_scale(size) local width = texture.width * scale djui_hud_set_color(r, g, b, fade(255)) djui_hud_render_texture(texture, centerX - width * 0.5, centerY - size * 0.5, scale, scale) end local function text_w(text, scale) return djui_hud_measure_text(text) * scale end local function widest_name(rows) local width = 0 for _, row in ipairs(rows) do width = math.max(width, text_w(row.name, NAME_SCALE)) end return width end local function label_button_w(label, scale, pad) return math.ceil(text_w(label, scale) + (pad or CELL)) end local function render_stars(x, y, nameW, allW, noneW, contentW) local first, last = visible_range(#COURSE_ROWS) local file = file_index() local origin = x + nameW + 4 local noneX = x + contentW - noneW local allX = noneX - 2 - allW for index = first, last do local row = COURSE_ROWS[index] local lineY = row_y(y, index) if row_visible(y, lineY) then draw_text(row.name, x, lineY + CELL / 8, NAME_SCALE, 235, 235, 240, 255) local flags = save_file_get_star_flags(file, course_index(row)) for star = 1, row.stars do local starIndex = star local rowIndex = index local bx = origin + (star - 1) * PITCH local centerX = bx + CELL * 0.5 local collected = (flags & (1 << (star - 1))) ~= 0 button(bx, lineY, CELL, CELL, "", collected, function() sFocus = "list" sSelection = rowIndex sColumn = starIndex toggle_star_at(rowIndex, starIndex) end) local markR = collected and 255 or 80 local markG = collected and 255 or 95 local markB = collected and 255 or 120 djui_hud_set_color(markR, markG, markB, fade(255)) render_texture_fit(gTextures.star, bx, lineY, CELL, CELL, 0.82) djui_hud_set_color(0, 0, 0, fade(110)) djui_hud_render_rect(bx, lineY, CELL, CELL) if row.numbered then local markSize = CELL * 0.62 local markY = lineY + CELL * 0.5 if star == 7 then draw_hud_coin(centerX, markY, markSize, markR, markG, markB) else draw_hud_number(star, centerX, markY, markSize, markR, markG, markB) end end if sFocus == "list" and index == sSelection and star == sColumn then selection_outline(bx, lineY, CELL, CELL) end end local rowIndex = index icon_button(allX, lineY, allW, CELL, "All", gTextures.star, false, function() sFocus = "list" sSelection = rowIndex sColumn = COL_ALL set_course_stars(rowIndex, true) end, NAME_SCALE) if sFocus == "list" and index == sSelection and sColumn == COL_ALL then selection_outline(allX, lineY, allW, CELL) end icon_button(noneX, lineY, noneW, CELL, "None", gTextures.star, false, function() sFocus = "list" sSelection = rowIndex sColumn = COL_NONE set_course_stars(rowIndex, false) end, NAME_SCALE, 80, 95, 120) if sFocus == "list" and index == sSelection and sColumn == COL_NONE then selection_outline(noneX, lineY, noneW, CELL) end end end return noneX + noneW - x end local function flag_badge(row) if row.cannon ~= nil then return "CANNON" end if string.find(row.name, "Key") ~= nil or string.find(row.name, "key") ~= nil then return "KEY" end if string.find(row.name, "door") ~= nil then return "DOOR" end if string.find(row.name, "Cap") ~= nil then return "CAP" end return "FLAG" end local function render_flags(x, y, rowW) local first, last = visible_range(#FLAG_ROWS) local flags = save_file_get_flags() for index = first, last do local row = FLAG_ROWS[index] local lineY = row_y(y, index) if row_visible(y, lineY) then local enabled = false if row.cannon ~= nil then enabled = cannon_is_open(row.cannon) else enabled = (flags & row.flag) ~= 0 end local rowIndex = index button(x, lineY, rowW, CELL, "", enabled, function() sFocus = "list" sSelection = rowIndex toggle_flag_at(rowIndex) end) if sFocus == "list" and index == sSelection then selection_outline(x, lineY, rowW, CELL) end local midY = lineY + CELL * 0.5 local oldH, oldV = djui_hud_get_text_alignment() local badge = flag_badge(row) local nameX = x + 4 + text_w("CANNON", NAME_SCALE) + 6 djui_hud_set_text_alignment(0, 0.5) draw_text(badge, x + 4, midY - CELL / 8, NAME_SCALE, enabled and 255 or 145, enabled and 220 or 145, enabled and 80 or 145, 255) draw_text(row.name, nameX, midY - CELL / 8, NAME_SCALE, 245, 245, 245, 255) djui_hud_set_text_alignment(1, 0.5) draw_text(enabled and "ON" or "OFF", x + rowW - 4, midY - CELL / 8, NAME_SCALE, enabled and 90 or 210, enabled and 255 or 120, enabled and 130 or 120, 255) djui_hud_set_text_alignment(oldH, oldV) end end end COIN_ACTIONS = { { label = "-50", amount = -50 }, { label = "-10", amount = -10 }, { label = "-5", amount = -5 }, { label = "-1", amount = -1 }, { label = "+1", amount = 1 }, { label = "+5", amount = 5 }, { label = "+10", amount = 10 }, { label = "+50", amount = 50 }, } MISC_ACTIONS = { { label = "-500", amount = -500 }, { label = "-100", amount = -100 }, { label = "-50", amount = -50 }, { label = "-10", amount = -10 }, { label = "-5", amount = -5 }, { label = "-1", amount = -1 }, { label = "+1", amount = 1 }, { label = "+5", amount = 5 }, { label = "+10", amount = 10 }, { label = "+50", amount = 50 }, { label = "+100", amount = 100 }, { label = "+500", amount = 500 }, } local function render_coins(x, y, contentW, buttonWs) local first, last = visible_range(#COIN_ROWS) local file = file_index() local buttonsW = 0 for i, width in ipairs(buttonWs) do buttonsW = buttonsW + width if i < #buttonWs then buttonsW = buttonsW + 2 end end local buttonsX = x + contentW - buttonsW local nameX = x + CELL + 2 local coinX = nameX + widest_name(COIN_ROWS) + 3 local valueX = coinX + CELL + 1 for index = first, last do local row = COIN_ROWS[index] local lineY = row_y(y, index) if row_visible(y, lineY) then local courseIndex = row.course - 1 local value = save_file_get_course_coin_score(file, courseIndex) local hasCoinStar = (save_file_get_star_flags(file, courseIndex) & 0x40) ~= 0 local midY = lineY + CELL * 0.5 local oldH, oldV = djui_hud_get_text_alignment() if hasCoinStar then djui_hud_set_color(255, 255, 255, fade(255)) else djui_hud_set_color(80, 95, 120, fade(255)) end render_texture_fit(gTextures.star, x, lineY, CELL, CELL, 0.82) djui_hud_set_text_alignment(0, 0.5) draw_text(row.name, nameX, midY - CELL / 8, NAME_SCALE, 235, 235, 240, 255) djui_hud_set_color(255, 220, 40, fade(255)) render_texture_fit(gTextures.coin, coinX, lineY, CELL, CELL, 0.7) draw_hud_count(value, valueX, midY, HUD_COUNT_SIZE, 255, 245, 150) djui_hud_set_text_alignment(oldH, oldV) local rowIndex = index local coinRow = row local bx = buttonsX for column, action in ipairs(COIN_ACTIONS) do local width = buttonWs[column] local captured = column local capturedAction = action button(bx, lineY, width, CELL, action.label, sFocus == "list" and index == sSelection and sColumn == column, function() sFocus = "list" sSelection = rowIndex sColumn = captured change_coin_score(coinRow, capturedAction.amount, capturedAction.absolute) end, NAME_SCALE) if sFocus == "list" and index == sSelection and sColumn == column then selection_outline(bx, lineY, width, CELL) end bx = bx + width + 2 end end end end local function render_warps(x, y, contentW, warpW, frontW) local first, last = visible_range(#WARP_ROWS) for index = first, last do local row = WARP_ROWS[index] local lineY = row_y(y, index) if row_visible(y, lineY) then local acts = row.acts or 1 local groupW = (acts > 1 and (acts - 1) * PITCH + CELL or warpW) + 2 + frontW local origin = x + contentW - groupW local midY = lineY + CELL * 0.5 local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_text_alignment(0, 0.5) draw_text(row.name, x, midY - CELL / 8, NAME_SCALE, 245, 245, 245, 255) djui_hud_set_text_alignment(oldH, oldV) local rowIndex = index if acts > 1 then for act = 1, acts do local bx = origin + (act - 1) * PITCH local captured = act button(bx, lineY, CELL, CELL, tostring(act), false, function() sFocus = "list" sSelection = rowIndex sColumn = captured warp_into(row, captured) end, NAME_SCALE) if sFocus == "list" and index == sSelection and sColumn == act then selection_outline(bx, lineY, CELL, CELL) end end else button(origin, lineY, warpW, CELL, "Enter", false, function() sFocus = "list" sSelection = rowIndex sColumn = 1 warp_into(row, 0) end, NAME_SCALE) if sFocus == "list" and index == sSelection and sColumn == 1 then selection_outline(origin, lineY, warpW, CELL) end end local frontX = x + contentW - frontW local frontCol = acts + 1 local canFront = front_available(row) button(frontX, lineY, frontW, CELL, "In Front", false, function() sFocus = "list" sSelection = rowIndex sColumn = frontCol if canFront then warp_front(row) end end, NAME_SCALE, canFront) if sFocus == "list" and index == sSelection and sColumn == frontCol then selection_outline(frontX, lineY, frontW, CELL) end end end end local function bind_toggle(selected, isAxes, getFn, setFn) return function() sFocus = "list" sSelection = selected sColumn = 1 if getFn ~= nil and setFn ~= nil then setFn(not getFn()) elseif isAxes then set_show_axes(not sShowAxes) else set_mario_frozen(not sMarioFrozen) end end end local function bind_choice(selected, choiceColumn, choiceValue, rowSet) return function() sFocus = "list" sSelection = selected sColumn = choiceColumn if rowSet ~= nil then rowSet(choiceValue) end end end local function render_settings(rows, x, y, contentW, buttonWs) local first, last = visible_range(#rows) local buttonsW = 0 for i, width in ipairs(buttonWs) do buttonsW = buttonsW + width if i < #buttonWs then buttonsW = buttonsW + 2 end end local buttonsX = x + contentW - buttonsW local valueW = hud_text_w("-32768", HUD_COUNT_SIZE) local valueX = buttonsX - 6 - valueW local ruleGap = CELL * 0.7 local separatorExtra = 0 for _, listed in ipairs(rows) do if listed.separator then separatorExtra = separatorExtra + ruleGap end end local function separator_shift(index) local extra = 0 for i = 1, index - 1 do if rows[i] ~= nil and rows[i].separator then extra = extra + ruleGap end end return extra end local topShift = separator_shift(first) for index = first, last do local row = rows[index] local lineY = row_y(y, index) + separator_shift(index) - topShift if row_visible(y, lineY, separatorExtra) then local midY = lineY + CELL * 0.5 local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_text_alignment(0, 0.5) local nameR, nameG, nameB = 245, 245, 245 if row.color ~= nil then nameR, nameG, nameB = row.color[1], row.color[2], row.color[3] end draw_text(row.name, x, midY - CELL / 8, NAME_SCALE, nameR, nameG, nameB, 255) djui_hud_set_text_alignment(oldH, oldV) local rowIndex = index local nameCol = 0 if rows == SETTINGS_ROWS then for _, named in ipairs(rows) do nameCol = math.max(nameCol, text_w(named.name, NAME_SCALE)) end nameCol = nameCol + 8 end local stretch = nameCol > 0 local areaX = x + nameCol local areaW = contentW - nameCol if row.toggle then local axisLines = row.axisLines local on = false if row.get ~= nil then on = row.get() elseif axisLines then on = sShowAxes else on = sMarioFrozen end local bgR, bgG, bgB = 140, 36, 44 if on then bgR, bgG, bgB = 32, 120, 52 end local toggleX = buttonsX local toggleW = buttonsW if stretch and areaW > toggleW then toggleX = areaX toggleW = areaW end button(toggleX, lineY, toggleW, CELL, on and "ON" or "OFF", false, bind_toggle(rowIndex, axisLines, row.get, row.set), NAME_SCALE, true, bgR, bgG, bgB) if sFocus == "list" and index == sSelection then selection_outline(toggleX, lineY, toggleW, CELL) end elseif row.choices ~= nil then local widths = {} local gaps = math.max(0, #row.choices - 1) * 2 if stretch and #row.choices > 0 then local each = math.max(1, (areaW - gaps) / #row.choices) for i = 1, #row.choices do widths[i] = each end else for i, choice in ipairs(row.choices) do widths[i] = label_button_w(tostring(choice), NAME_SCALE, CELL * 0.5) end end local bx = stretch and areaX or x + contentW if not stretch then local total = gaps for _, width in ipairs(widths) do total = total + width end bx = x + contentW - total end local current = row.get() local rowSet = row.set local selected = rowIndex for column, choice in ipairs(row.choices) do local width = widths[column] local picked = current == choice local bgR, bgG, bgB, textR, textG, textB if picked then bgR, bgG, bgB = 214, 164, 36 textR, textG, textB = 40, 28, 8 end local choiceColumn = column local choiceValue = choice local label = tostring(choiceValue) local labelScale = NAME_SCALE local labelW = text_w(label, NAME_SCALE) local maxText = math.max(1, width - 2) if labelW > maxText then labelScale = NAME_SCALE * maxText / labelW end button(bx, lineY, width, CELL, label, false, bind_choice(selected, choiceColumn, choiceValue, rowSet), labelScale, true, bgR, bgG, bgB, textR, textG, textB) if sFocus == "list" and index == sSelection and sColumn == column then selection_outline(bx, lineY, width, CELL) end bx = bx + width + 2 end else draw_hud_count(row.get(), valueX + valueW - hud_text_w(row.get(), HUD_COUNT_SIZE), midY, HUD_COUNT_SIZE, 255, 245, 150) local bx = buttonsX for column, action in ipairs(MISC_ACTIONS) do local width = buttonWs[column] local captured = column local amount = action.amount local bgR, bgG, bgB, textR, textG, textB if row.axis ~= nil then local colors = amount < 0 and AXIS_COLORS[row.axis].neg or AXIS_COLORS[row.axis].pos bgR, bgG, bgB = colors[1], colors[2], colors[3] if amount < 0 then textR, textG, textB = 36, 36, 48 end end button(bx, lineY, width, CELL, action.label, sFocus == "list" and index == sSelection and sColumn == column, function() sFocus = "list" sSelection = rowIndex sColumn = captured row.set(row.get() + amount) end, NAME_SCALE, true, bgR, bgG, bgB, textR, textG, textB) if sFocus == "list" and index == sSelection and sColumn == column then selection_outline(bx, lineY, width, CELL) end bx = bx + width + 2 end end if row.separator then local ruleY = lineY + CELL + ruleGap * 0.5 djui_hud_set_color(255, 255, 255, fade(255)) djui_hud_render_rect(x, ruleY, contentW, math.max(1, CELL * 0.08)) end end end end local function render() if not sOpen then return end sPendingClick = nil -- Mouse coordinates are always in DJUI pixels, even when drawing in N64 -- resolution. Convert them before hit-testing the N64-sized controls. djui_hud_set_resolution(RESOLUTION_DJUI) local djuiWidth = djui_hud_get_screen_width() local djuiHeight = djui_hud_get_screen_height() local rawMouseX = djui_hud_get_mouse_x() local rawMouseY = djui_hud_get_mouse_y() djui_hud_set_resolution(RESOLUTION_N64) djui_hud_set_font(FONT_SPECIAL) local n64Width = djui_hud_get_screen_width() local n64Height = djui_hud_get_screen_height() sMouseX = rawMouseX * n64Width / math.max(1, djuiWidth) sMouseY = rawMouseY * n64Height / math.max(1, djuiHeight) local scale = sUiScale / 100 PITCH = 9 * scale CELL = 8 * scale NAME_SCALE = 0.2 * scale HUD_COUNT_SIZE = 5 * scale local mouseDown = (djui_hud_get_mouse_buttons_down() & 1) ~= 0 sMouseClicked = mouseDown and not sMouseWasDown local scroll = djui_hud_get_mouse_scroll_y() local frame = get_global_timer() if scroll ~= 0 and frame - sLastScrollFrame >= 1 then change_page(scroll > 0 and -4 or 4) sLastScrollFrame = frame end local width = djui_hud_get_screen_width() local height = djui_hud_get_screen_height() local pad = 4 * scale local tabScale = NAME_SCALE local footerScale = NAME_SCALE local titleScale = 0.28 * scale local title = "DEBUG TOOLS" local saveLabel = "(Save " .. tostring(get_current_save_file_num()) .. ")" local titleTrack = 0.55 * scale local function tracked_width(text, scale) local widthSum = 0 for i = 1, #text do widthSum = widthSum + text_w(text:sub(i, i), scale) if i < #text then widthSum = widthSum + titleTrack end end return widthSum end local titleW = tracked_width(title, titleScale) + 3 + tracked_width(saveLabel, titleScale) local chromeH = 8 * scale local function icon_tab_w(label, scale, texture, iconSize) local iconW = 0 local iconH = chromeH * 0.75 if texture ~= nil and texture.height ~= nil and texture.height > 0 then local iconScale = iconSize and (iconSize / texture.height) or (iconH / texture.height) iconW = texture.width * iconScale end return math.ceil(chromeH * 0.75 + iconW + text_w(label, scale)) end local starsTabW = icon_tab_w("STARS", tabScale, gTextures.star) local flagsTabW = icon_tab_w("FLAGS", tabScale, sFlagTexture) local coinsTabW = icon_tab_w("COINS", tabScale, gTextures.coin) local warpTabW = icon_tab_w("WARP", tabScale, sHand) local miscTabW = icon_tab_w("PLAYER", tabScale, gTextures.mario_head) local settingsTabW = icon_tab_w("SETTINGS", tabScale, sGear) local closeW = chromeH local tabsW = starsTabW + 2 + flagsTabW + 2 + coinsTabW + 2 + warpTabW + 2 + miscTabW + 2 + settingsTabW + 2 + closeW local starNameW = widest_name(COURSE_ROWS) local allW = icon_tab_w("All", NAME_SCALE, gTextures.star) local noneW = icon_tab_w("None", NAME_SCALE, gTextures.star) local starsInner = starNameW + 4 + (6 * PITCH + CELL) + 3 + allW + 2 + noneW local flagInner = 4 + text_w("CANNON", NAME_SCALE) + 6 + widest_name(FLAG_ROWS) + 8 + text_w("OFF", NAME_SCALE) + 4 local coinBtnWs = {} local coinBtnsW = 0 for i, action in ipairs(COIN_ACTIONS) do coinBtnWs[i] = label_button_w(action.label, NAME_SCALE, CELL * 0.5) coinBtnsW = coinBtnsW + coinBtnWs[i] if i < #COIN_ACTIONS then coinBtnsW = coinBtnsW + 2 end end local miscBtnWs = {} local miscBtnsW = 0 for i, action in ipairs(MISC_ACTIONS) do miscBtnWs[i] = label_button_w(action.label, NAME_SCALE, CELL * 0.5) miscBtnsW = miscBtnsW + miscBtnWs[i] if i < #MISC_ACTIONS then miscBtnsW = miscBtnsW + 2 end end local coinInner = CELL + 2 + widest_name(COIN_ROWS) + 3 + CELL + 1 + hud_text_w("255", HUD_COUNT_SIZE) + 8 + coinBtnsW local warpW = PITCH + CELL local frontW = label_button_w("In Front", NAME_SCALE) local undoW = label_button_w("Undo", footerScale) local redoW = label_button_w("Redo", footerScale) local reloadW = label_button_w("Reload", footerScale) local teleportW = label_button_w("Reload & TP", footerScale) local keepW = label_button_w("Keep UI Open", footerScale) local resetW = label_button_w("Reset to Default", footerScale) local warpInner = widest_name(WARP_ROWS) + 4 + (5 * PITCH + CELL) + 2 + frontW local settingsInner = text_w("Camera Distance", NAME_SCALE) + 8 + hud_text_w("-32768", HUD_COUNT_SIZE) + 6 + miscBtnsW local function choices_strip(list) local width = 0 for i, choice in ipairs(list) do width = width + label_button_w(tostring(choice), NAME_SCALE, CELL * 0.5) if i > 1 then width = width + 2 end end return widest_name(SETTINGS_ROWS) + 8 + width end local choiceLists = { VISIBLE_ROW_CHOICES, UI_SCALE_CHOICES, OPACITY_CHOICES, THICKNESS_CHOICES } local choiceInner = 0 for _, list in ipairs(choiceLists) do choiceInner = math.max(choiceInner, choices_strip(list)) end local arrowSize = 4 * scale local upW = icon_tab_w("Scroll Up", footerScale, gTextures.arrow_up, arrowSize) local downW = icon_tab_w("Scroll Down", footerScale, gTextures.arrow_down, arrowSize) local enableW = icon_tab_w("Enable All", footerScale, sRedStar) local disableW = icon_tab_w("Disable All", footerScale, sWhiteStar) local footerInner = upW + 3 + downW + 8 + enableW + 3 + disableW local warpFooter = upW + 3 + downW + 8 + undoW + 2 + redoW + 2 + reloadW + 2 + teleportW + 2 + keepW local settingsFooter = upW + 3 + downW + 8 + resetW local inner = math.max(starsInner, flagInner, coinInner, warpInner, settingsInner, choiceInner, footerInner, warpFooter, settingsFooter, titleW + 8 + tabsW) local panelW = math.ceil(inner + pad * 2) local footerH = chromeH local hintH = 8 * scale local headerH = 14 * scale local listTop = headerH + pad local bottomPad = 1 local separatorExtra = separator_span() local panelH = listTop + sPageSize * PITCH + separatorExtra + pad + footerH + hintH + bottomPad local x = (width - panelW) * 0.5 local y = (height - panelH) * 0.5 local contentX = x + pad local contentW = panelW - pad * 2 local bodyY = y + headerH local bodyH = panelH - headerH local bodySteps = math.min(360, math.max(1, math.floor(bodyH * 2))) local bodyStrip = bodyH / bodySteps for row = 0, bodySteps - 1 do local t = row / math.max(1, bodySteps - 1) local alpha if t < 0.3 then alpha = 220 + (242 - 220) * (t / 0.3) else alpha = 242 + (250 - 242) * ((t - 0.3) / 0.7) end local blue = t < 0.3 and (1 - t / 0.3) or 0 djui_hud_set_color(14 + 70 * t - 10 * blue, 6 + 28 * t - 4 * blue, 24 + 96 * t + 22 * blue, math.floor(alpha * sUiOpacity / 100)) djui_hud_render_rect(x, bodyY + row * bodyStrip, panelW, bodyStrip) end local steps = math.min(80, math.max(1, math.floor(headerH * 2))) local headerStrip = headerH / steps for row = 0, steps - 1 do local t = row / math.max(1, steps - 1) local shade = 1 - t djui_hud_set_color(8 + 34 * shade, 22 + 68 * shade, 48 + 102 * shade, math.floor(255 * sUiOpacity / 100)) djui_hud_render_rect(x, y + row * headerStrip, panelW, headerStrip) end local closeX = x + panelW - pad - closeW local settingsX = closeX - 2 - settingsTabW local miscX = settingsX - 2 - miscTabW local warpX = miscX - 2 - warpTabW local coinsX = warpX - 2 - coinsTabW local flagsX = coinsX - 2 - flagsTabW local starsX = flagsX - 2 - starsTabW local tabH = chromeH local tabY = y + (headerH - tabH) * 0.5 local titleY = tabY + tabH * 0.5 - 1 local function draw_shadowed_text(text, textX, r, g, b) local oldH, oldV = djui_hud_get_text_alignment() djui_hud_set_text_alignment(0, 0.5) local cursor = textX for i = 1, #text do local ch = text:sub(i, i) djui_hud_set_color(0, 0, 0, fade(160)) djui_hud_print_text(ch, cursor + 0.5, titleY + 0.5, titleScale) djui_hud_set_color(r, g, b, fade(255)) djui_hud_print_text(ch, cursor, titleY, titleScale) djui_hud_print_text(ch, cursor + 0.4, titleY, titleScale) cursor = cursor + text_w(ch, titleScale) + titleTrack end djui_hud_set_text_alignment(oldH, oldV) end draw_shadowed_text(title, x + pad, 255, 255, 255) draw_shadowed_text(saveLabel, x + pad + tracked_width(title, titleScale) + 3, 210, 230, 255) icon_button(starsX, tabY, starsTabW, tabH, "STARS", gTextures.star, sTab == 1, function() sFocus = "header" sChrome = 1 set_tab(1) end, tabScale) icon_button(flagsX, tabY, flagsTabW, tabH, "FLAGS", sFlagTexture, sTab == 2, function() sFocus = "header" sChrome = 2 set_tab(2) end, tabScale) icon_button(coinsX, tabY, coinsTabW, tabH, "COINS", gTextures.coin, sTab == 3, function() sFocus = "header" sChrome = 3 set_tab(3) end, tabScale) icon_button(warpX, tabY, warpTabW, tabH, "WARP", sHand, sTab == 4, function() sFocus = "header" sChrome = 4 set_tab(4) end, tabScale) icon_button(miscX, tabY, miscTabW, tabH, "PLAYER", gTextures.mario_head, sTab == 5, function() sFocus = "header" sChrome = 5 set_tab(5) end, tabScale) icon_button(settingsX, tabY, settingsTabW, tabH, "SETTINGS", sGear, sTab == 6, function() sFocus = "header" sChrome = 6 set_tab(6) end, tabScale) button(closeX, tabY, closeW, tabH, "", false, function() sFocus = "header" sChrome = 7 close_editor() end) if gTextures.no_camera ~= nil and gTextures.no_camera.width ~= nil then local iconBox = closeW - 2 local tex = gTextures.no_camera local iconScale = math.min(iconBox / tex.width, iconBox / tex.height) local iconW = tex.width * iconScale local iconH = tex.height * iconScale djui_hud_set_color(255, 255, 255, fade(255)) djui_hud_render_texture(tex, closeX + (closeW - iconW) * 0.5, tabY + (tabH - iconH) * 0.5, iconScale, iconScale) end if sFocus == "header" then local marks = { { starsX, starsTabW }, { flagsX, flagsTabW }, { coinsX, coinsTabW }, { warpX, warpTabW }, { miscX, miscTabW }, { settingsX, settingsTabW }, { closeX, closeW }, } selection_outline(marks[sChrome][1], tabY, marks[sChrome][2], tabH) end if sTab == 1 then render_stars(contentX, y + listTop, starNameW, allW, noneW, contentW) elseif sTab == 2 then render_flags(contentX, y + listTop, contentW) elseif sTab == 3 then render_coins(contentX, y + listTop, contentW, coinBtnWs) elseif sTab == 4 then render_warps(contentX, y + listTop, contentW, warpW, frontW) elseif sTab == 5 then render_settings(MISC_ROWS, contentX, y + listTop, contentW, miscBtnWs) else render_settings(SETTINGS_ROWS, contentX, y + listTop, contentW, miscBtnWs) end local controlsY = y + panelH - bottomPad - hintH - footerH local scrollUpX = x + pad local scrollDownX = scrollUpX + upW + 3 if mouseDown and is_hovered(scrollUpX, controlsY, upW, footerH) then sScrollMouseDir = -1 elseif mouseDown and is_hovered(scrollDownX, controlsY, downW, footerH) then sScrollMouseDir = 1 else sScrollMouseDir = 0 end icon_button(scrollUpX, controlsY, upW, footerH, "Scroll Up", gTextures.arrow_up, false, function() sFocus = "footer" sChrome = 1 if sScrollHold ~= -1 then sScrollHold = -1 sScrollHoldTimer = REPEAT_DELAY sScrollHoldSteps = 0 change_page(-1) end end, footerScale, nil, nil, nil, arrowSize) icon_button(scrollDownX, controlsY, downW, footerH, "Scroll Down", gTextures.arrow_down, false, function() sFocus = "footer" sChrome = 2 if sScrollHold ~= 1 then sScrollHold = 1 sScrollHoldTimer = REPEAT_DELAY sScrollHoldSteps = 0 change_page(1) end end, footerScale, nil, nil, nil, arrowSize) local enableX, disableX if sTab == 1 or sTab == 2 then disableX = x + panelW - pad - disableW enableX = disableX - 3 - enableW local enable = sTab == 1 and function() set_all_stars(true) end or function() set_all_flags(true) end local disable = sTab == 1 and function() set_all_stars(false) end or function() set_all_flags(false) end icon_button(enableX, controlsY, enableW, footerH, "Enable All", sRedStar, false, function() sFocus = "footer" sChrome = 3 enable() end, footerScale) icon_button(disableX, controlsY, disableW, footerH, "Disable All", sWhiteStar, false, function() sFocus = "footer" sChrome = 4 disable() end, footerScale) elseif sTab == 4 then local keepX = x + panelW - pad - keepW local teleportX = keepX - 2 - teleportW local reloadX = teleportX - 2 - reloadW local redoX = reloadX - 2 - redoW local undoX = redoX - 2 - undoW button(undoX, controlsY, undoW, footerH, "Undo", false, function() sFocus = "footer" sChrome = 3 undo_warp() end, footerScale, #sWarpUndo > 0) button(redoX, controlsY, redoW, footerH, "Redo", false, function() sFocus = "footer" sChrome = 4 redo_warp() end, footerScale, #sWarpRedo > 0) button(reloadX, controlsY, reloadW, footerH, "Reload", false, function() sFocus = "footer" sChrome = 5 reload_area(false) end, footerScale) button(teleportX, controlsY, teleportW, footerH, "Reload & TP", false, function() sFocus = "footer" sChrome = 6 reload_area(true) end, footerScale) local keepR, keepG, keepB = 110, 28, 32 if sKeepOpen then keepR, keepG, keepB = 28, 96, 42 end button(keepX, controlsY, keepW, footerH, "Keep UI Open", false, function() sFocus = "footer" sChrome = 7 sKeepOpen = not sKeepOpen set_keep_open(sKeepOpen) end, footerScale, true, keepR, keepG, keepB) elseif sTab == 6 then local resetX = x + panelW - pad - resetW button(resetX, controlsY, resetW, footerH, "Reset to Default", false, function() sFocus = "footer" sChrome = 3 reset_settings() end, footerScale) end if sFocus == "footer" then local marks = { { scrollUpX, upW }, { scrollDownX, downW }, } if sTab == 1 or sTab == 2 then marks[3] = { enableX, enableW } marks[4] = { disableX, disableW } elseif sTab == 4 then local keepX = x + panelW - pad - keepW local teleportX = keepX - 2 - teleportW local reloadX = teleportX - 2 - reloadW local redoX = reloadX - 2 - redoW local undoX = redoX - 2 - undoW marks[3] = { undoX, undoW } marks[4] = { redoX, redoW } marks[5] = { reloadX, reloadW } marks[6] = { teleportX, teleportW } marks[7] = { keepX, keepW } elseif sTab == 6 then marks[3] = { x + panelW - pad - resetW, resetW } end local mark = marks[sChrome] if mark ~= nil then selection_outline(mark[1], controlsY, mark[2], footerH) end end local hint = "Stick/D-Pad: Navigate A: Toggle Selected B: Back L/R: Switch Tabs C-Up/C-Down: Scroll Start: Close" local hintScale = NAME_SCALE local hintWidth = djui_hud_measure_text(hint) * hintScale local hintMax = panelW - 8 if hintWidth > hintMax then hintScale = hintScale * hintMax / hintWidth end local hintDrop = 5 * scale draw_centered_text(hint, x + panelW * 0.5 + 0.5 * scale, controlsY + footerH + hintDrop + 0.5 * scale, hintScale, 0, 0, 0, 160) draw_centered_text(hint, x + panelW * 0.5, controlsY + footerH + hintDrop, hintScale, 145, 150, 160, 255) local rows = max_rows() if rows > sPageSize then local trackX = x + panelW - 2 local trackY = y + listTop local trackH = sPageSize * PITCH + separatorExtra local thumbH = math.max(6, trackH * sPageSize / rows) local maxScroll = rows - sPageSize local thumbY = trackY + (trackH - thumbH) * (sScroll / maxScroll) djui_hud_set_color(20, 10, 32, math.floor(180 * sUiOpacity / 100)) djui_hud_render_rect(trackX, trackY, 2, trackH) djui_hud_set_color(190, 170, 220, math.floor(230 * sUiOpacity / 100)) djui_hud_render_rect(trackX, thumbY, 2, thumbH) end sMouseWasDown = mouseDown djui_hud_set_font(FONT_NORMAL) if sPendingClick ~= nil then local click = sPendingClick sPendingClick = nil click() end end hook_chat_command("debugtool", "open or close Debug Tool", open_command) hook_chat_command("dt", "shortcut for /debugtool", open_command) hook_chat_command("saveeditor", "shortcut for /debugtool", open_command) hook_chat_command("se", "shortcut for /debugtool", open_command) local function open_from_mod_menu() if not sOpen then open_command() end if is_game_paused() then game_unpause() end return true end hook_mod_menu_button("Open Debug Tool", open_from_mod_menu) hook_mod_menu_text("\\#8080ff\\...or open it faster with") hook_mod_menu_text("\\#80ffff\\/debugtool\\#8080ff\\ or \\#80ffff\\/dt") hook_mod_menu_text("\\#ffff80\\The button above leaves the mod menu open.") hook_mod_menu_text("\\#ffff80\\Press \\#ff8080\\ESC\\#ffff80\\ several times to close it.") hook_mod_menu_button("Reset Settings to Default", function() reset_settings() return true end) hook_mod_menu_text("\\#80ff80\\Mod developed by \\#00ff00\\iZePlayz") local sInputHandled = false local function restore_pause_lock() if sPauseLock == nil or is_game_paused() == sPauseLock then return end if sPauseLock then game_pause() else game_unpause() end end hook_event(HOOK_ON_LEVEL_INIT, function() if sReloadPos ~= nil then sReloadFrames = 8 end end) hook_event(HOOK_UPDATE, function() if sOpen then apply_camera_lock() end sInputHandled = false sFramePause = sPausedLastFrame local m = gMarioStates[0] apply_reload_pos(m) if sOpen and m ~= nil and m.controller ~= nil then local c = m.controller if (c.buttonDown & START_BUTTON) ~= 0 or (c.buttonPressed & START_BUTTON) ~= 0 then sCloseFromStart = true end if is_game_paused() then update(m) sInputHandled = true end end if sBlockFrames > 0 and m ~= nil and m.controller ~= nil then block_mario_input(m) restore_pause_lock() sBlockFrames = sBlockFrames - 1 if sBlockFrames <= 0 then sPauseLock = nil end end sPausedLastFrame = is_game_paused() end) hook_event(HOOK_BEFORE_MARIO_UPDATE, function(m) if m.playerIndex ~= 0 then return end apply_reload_pos(m) if not sOpen and sBlockFrames <= 0 then return end if sInputHandled or is_game_paused() or not sOpen then block_mario_input(m) restore_pause_lock() return end update(m) if sBlockFrames > 0 then block_mario_input(m) restore_pause_lock() end end) hook_event(HOOK_MARIO_UPDATE, function(m) if not sMarioFrozen or m == nil or m.playerIndex ~= 0 then return end if sFrozenPos == nil then remember_frozen_pos(m) end if sFrozenPos == nil then return end m.pos.x = sFrozenPos.x m.pos.y = sFrozenPos.y m.pos.z = sFrozenPos.z m.vel.x = 0 m.vel.y = 0 m.vel.z = 0 m.forwardVel = 0 end) hook_event(HOOK_BEFORE_PHYS_STEP, function(m) if not sMarioFrozen or m == nil or m.playerIndex ~= 0 then return end m.vel.x = 0 m.vel.y = 0 m.vel.z = 0 m.forwardVel = 0 m.slideVelX = 0 m.slideVelZ = 0 return true end) local function render_axis_lines() if not sShowAxes then return end local m = gMarioStates[0] if m == nil or m.pos == nil then return end djui_hud_set_resolution(RESOLUTION_N64) local origin = { x = m.pos.x, y = m.pos.y, z = m.pos.z } local tip = { x = 0, y = 0, z = 0 } local screenO = { x = 0, y = 0, z = 0 } local screenT = { x = 0, y = 0, z = 0 } local screenW = djui_hud_get_screen_width() local screenH = djui_hud_get_screen_height() local step = 100 local axes = { { x = step, y = 0, z = 0, pos = AXIS_COLORS.x.pos, neg = AXIS_COLORS.x.neg }, { x = 0, y = step, z = 0, pos = AXIS_COLORS.y.pos, neg = AXIS_COLORS.y.neg }, { x = 0, y = 0, z = step, pos = AXIS_COLORS.z.pos, neg = AXIS_COLORS.z.neg }, } if not djui_hud_world_pos_to_screen_pos(origin, screenO) then djui_hud_set_rotation(0, 0, 0) return end local span = screenW + screenH for _, axis in ipairs(axes) do tip.x = origin.x + axis.x tip.y = origin.y + axis.y tip.z = origin.z + axis.z if djui_hud_world_pos_to_screen_pos(tip, screenT) then local dx = screenT.x - screenO.x local dy = screenT.y - screenO.y local len = math.sqrt(dx * dx + dy * dy) if len > 0.5 then dx = dx / len dy = dy / len local x1 = screenO.x - dx * span local y1 = screenO.y - dy * span local x2 = screenO.x + dx * span local y2 = screenO.y + dy * span local lineAlpha = math.floor(sAxisOpacity * 255 / 100) djui_hud_set_color(axis.neg[1], axis.neg[2], axis.neg[3], lineAlpha) djui_hud_render_line(x1, y1, screenO.x, screenO.y, axis_line_width(sAxisWidth)) djui_hud_set_color(axis.pos[1], axis.pos[2], axis.pos[3], lineAlpha) djui_hud_render_line(screenO.x, screenO.y, x2, y2, axis_line_width(sAxisWidth)) end end end djui_hud_set_rotation(0, 0, 0) end local ACTION_LABELS = {} local function remember_action(action, label) if action ~= nil then ACTION_LABELS[action] = label end end remember_action(ACT_IDLE, "Idle") remember_action(ACT_WALKING, "Walking") remember_action(ACT_DECELERATING, "Decelerating") remember_action(ACT_BRAKING, "Braking") remember_action(ACT_BRAKING_STOP, "Braking Stop") remember_action(ACT_JUMP, "Jump") remember_action(ACT_DOUBLE_JUMP, "Double Jump") remember_action(ACT_TRIPLE_JUMP, "Triple Jump") remember_action(ACT_LONG_JUMP, "Long Jump") remember_action(ACT_BACKFLIP, "Backflip") remember_action(ACT_SIDE_FLIP, "Side Flip") remember_action(ACT_WALL_KICK_AIR, "Wall Kick") remember_action(ACT_FREEFALL, "Freefall") remember_action(ACT_FREEFALL_LAND, "Freefall Land") remember_action(ACT_FREEFALL_LAND_STOP, "Freefall Land Stop") remember_action(ACT_JUMP_LAND, "Jump Land") remember_action(ACT_DOUBLE_JUMP_LAND, "Double Jump Land") remember_action(ACT_TRIPLE_JUMP_LAND, "Triple Jump Land") remember_action(ACT_LONG_JUMP_LAND, "Long Jump Land") remember_action(ACT_BACKFLIP_LAND, "Backflip Land") remember_action(ACT_SIDE_FLIP_LAND, "Side Flip Land") remember_action(ACT_HOLD_JUMP, "Hold Jump") remember_action(ACT_HOLD_FREEFALL, "Hold Freefall") remember_action(ACT_GROUND_POUND, "Ground Pound") remember_action(ACT_GROUND_POUND_LAND, "Ground Pound Land") remember_action(ACT_CROUCHING, "Crouching") remember_action(ACT_CRAWLING, "Crawling") remember_action(ACT_CROUCH_SLIDE, "Crouch Slide") remember_action(ACT_PUNCHING, "Punching") remember_action(ACT_MOVE_PUNCHING, "Move Punching") remember_action(ACT_JUMP_KICK, "Jump Kick") remember_action(ACT_DIVE, "Dive") remember_action(ACT_DIVE_SLIDE, "Dive Slide") remember_action(ACT_SLIDE_KICK, "Slide Kick") remember_action(ACT_SLIDE_KICK_SLIDE, "Slide Kick Slide") remember_action(ACT_STOMACH_SLIDE, "Stomach Slide") remember_action(ACT_BUTT_SLIDE, "Butt Slide") remember_action(ACT_WATER_IDLE, "Water Idle") remember_action(ACT_WATER_ACTION_END, "Water Action End") remember_action(ACT_BREASTSTROKE, "Breaststroke") remember_action(ACT_FLUTTER_KICK, "Flutter Kick") remember_action(ACT_WATER_PLUNGE, "Water Plunge") remember_action(ACT_WATER_JUMP, "Water Jump") remember_action(ACT_SWIMMING_END, "Swimming End") remember_action(ACT_LAVA_BOOST, "Lava Boost") remember_action(ACT_BURNING_JUMP, "Burning Jump") remember_action(ACT_BURNING_FALL, "Burning Fall") remember_action(ACT_BURNING_GROUND, "Burning Ground") remember_action(ACT_FLYING, "Flying") remember_action(ACT_SHOT_FROM_CANNON, "Shot From Cannon") remember_action(ACT_IN_CANNON, "In Cannon") remember_action(ACT_HOLDING_POLE, "Holding Pole") remember_action(ACT_CLIMBING_POLE, "Climbing Pole") remember_action(ACT_TOP_OF_POLE, "Top Of Pole") remember_action(ACT_TOP_OF_POLE_JUMP, "Top Of Pole Jump") remember_action(ACT_START_HANGING, "Start Hanging") remember_action(ACT_HANGING, "Hanging") remember_action(ACT_HANG_MOVING, "Hang Moving") remember_action(ACT_LEDGE_GRAB, "Ledge Grab") remember_action(ACT_LEDGE_CLIMB_SLOW_1, "Ledge Climb") remember_action(ACT_LEDGE_CLIMB_SLOW_2, "Ledge Climb") remember_action(ACT_LEDGE_CLIMB_FAST, "Ledge Climb Fast") remember_action(ACT_HOLD_IDLE, "Hold Idle") remember_action(ACT_HOLD_WALKING, "Hold Walking") remember_action(ACT_HOLD_HEAVY_IDLE, "Hold Heavy Idle") remember_action(ACT_HOLD_HEAVY_WALKING, "Hold Heavy Walking") remember_action(ACT_PICKING_UP, "Picking Up") remember_action(ACT_THROWING, "Throwing") remember_action(ACT_HEAVY_THROW, "Heavy Throw") remember_action(ACT_AIR_THROW, "Air Throw") remember_action(ACT_SLEEPING, "Sleeping") remember_action(ACT_START_SLEEPING, "Start Sleeping") remember_action(ACT_WAKING_UP, "Waking Up") remember_action(ACT_SHIVERING, "Shivering") remember_action(ACT_STANDING_DEATH, "Standing Death") remember_action(ACT_DEATH_ON_STOMACH, "Death On Stomach") remember_action(ACT_DEATH_ON_BACK, "Death On Back") remember_action(ACT_QUICKSAND_DEATH, "Quicksand Death") remember_action(ACT_ELECTROCUTION, "Electrocution") remember_action(ACT_SHOCKED, "Shocked") remember_action(ACT_SQUISHED, "Squished") remember_action(ACT_STAR_DANCE_EXIT, "Star Dance") remember_action(ACT_STAR_DANCE_NO_EXIT, "Star Dance") remember_action(ACT_STAR_DANCE_WATER, "Star Dance Water") remember_action(ACT_FALL_AFTER_STAR_GRAB, "Fall After Star Grab") remember_action(ACT_READING_SIGN, "Reading Sign") remember_action(ACT_READING_NPC_DIALOG, "Reading Dialog") remember_action(ACT_WAITING_FOR_DIALOG, "Waiting For Dialog") remember_action(ACT_SOFT_BONK, "Soft Bonk") remember_action(ACT_GROUND_BONK, "Ground Bonk") remember_action(ACT_FORWARD_ROLLOUT, "Forward Rollout") remember_action(ACT_BACKWARD_ROLLOUT, "Backward Rollout") remember_action(ACT_RIDING_SHELL_GROUND, "Riding Shell") remember_action(ACT_RIDING_SHELL_JUMP, "Shell Jump") remember_action(ACT_RIDING_SHELL_FALL, "Shell Fall") remember_action(ACT_RIDING_HOOT, "Riding Hoot") remember_action(ACT_DEBUG_FREE_MOVE, "Debug Free Move") local function action_label(action) local name = ACTION_LABELS[action] if name ~= nil then return name end return string.format("0x%08X", action or 0) end local function cap_hud_visible(m) if m == nil or m.flags == nil then return false end local mask = 0 if MARIO_WING_CAP ~= nil then mask = mask | MARIO_WING_CAP end if MARIO_METAL_CAP ~= nil then mask = mask | MARIO_METAL_CAP end if MARIO_VANISH_CAP ~= nil then mask = mask | MARIO_VANISH_CAP end return mask ~= 0 and (m.flags & mask) ~= 0 end local function timer_visible() if gHudDisplay == nil or gHudDisplay.flags == nil then return false end local flag = HUD_DISPLAY_FLAG_TIMER or 0x0040 return (gHudDisplay.flags & flag) ~= 0 end local function readout_anchor(position, blockH, screenW, screenH, m) local hudBottom = screenH - 209 local leftX = 22 local rightEdge = screenW - 22 local underLives = hudBottom + 2 local underCap = hudBottom + 22 local aboveCamera = 205 - 8 - blockH local midY = (screenH - blockH) * 0.5 local bottomY = screenH - 8 - blockH local x, y, alignRight if position == "Left Top" then x, y, alignRight = leftX, cap_hud_visible(m) and underCap or underLives, false elseif position == "Right Top" then x, y, alignRight = rightEdge, underLives, true elseif position == "Left Middle" then x, y, alignRight = leftX, midY, false elseif position == "Right Middle" then x, y, alignRight = rightEdge, midY, true elseif position == "Left Bottom" then x, y, alignRight = leftX, bottomY, false else x, y, alignRight = rightEdge, aboveCamera, true end if alignRight and position ~= "Right Bottom" and timer_visible() then local timerBottom = screenH - 185 + 4 if y < timerBottom and y + blockH > screenH - 185 - 16 then y = timerBottom end end return x, y, alignRight end local function format_decimal(value) value = value or 0 local places = sDebugDecimals or 0 if places <= 0 then local bias = value < 0 and -0.5 or 0.5 return tostring(math.floor(value + bias)) end local text = string.format("%." .. tostring(places) .. "f", value) text = text:gsub("0+$", ""):gsub("%.$", "") return text end local function render_player_readout() if not sDisplayFlags.show_display then return end if not sDisplayFlags.show_coords and not sDisplayFlags.show_health and not sDisplayFlags.show_speed and not sDisplayFlags.show_speeds and not sDisplayFlags.show_action then return end local m = gMarioStates[0] if m == nil or m.pos == nil then return end djui_hud_set_resolution(RESOLUTION_N64) djui_hud_set_font(FONT_SPECIAL) local scale = 0.35 * sDisplayScale / 100 local lineH = 10 * sDisplayScale / 100 local lines = {} local function add_line(label, value, r, g, b) lines[#lines + 1] = { label = label, value = value, r = r, g = g, b = b } end if sDisplayFlags.show_coords then add_line("X", format_decimal(m.pos.x), 255, 80, 80) add_line("Y", format_decimal(m.pos.y), 80, 220, 90) add_line("Z", format_decimal(m.pos.z), 80, 150, 255) end if sDisplayFlags.show_health then add_line("Health", tostring(math.floor(m.health or 0)), 255, 120, 0) end if sDisplayFlags.show_speed then add_line("H Speed", format_decimal(m.forwardVel or 0), 255, 220, 80) end if sDisplayFlags.show_speeds and m.vel ~= nil then add_line("VX", format_decimal(m.vel.x), 255, 160, 120) add_line("VY", format_decimal(m.vel.y), 160, 255, 160) add_line("VZ", format_decimal(m.vel.z), 140, 180, 255) end if sDisplayFlags.show_action then add_line(action_label(m.action), nil, 245, 245, 245) end if #lines == 0 then djui_hud_set_font(FONT_NORMAL) return end local blockH = #lines * lineH local screenW = djui_hud_get_screen_width() local screenH = djui_hud_get_screen_height() local edgeX, y, alignRight = readout_anchor(sDisplayPosition, blockH, screenW, screenH, m) for i, line in ipairs(lines) do local text if line.value == nil then text = line.label elseif alignRight then text = line.value .. " " .. line.label else text = line.label .. " " .. line.value end local lineX = edgeX if alignRight then lineX = edgeX - text_w(text, scale) end local lineY = y + (i - 1) * lineH if sDisplayFlags.show_shadow then local drop = 0.5 * sDisplayScale / 100 djui_hud_set_color(0, 0, 0, 200) djui_hud_print_text(text, lineX + drop, lineY + drop, scale) end djui_hud_set_color(line.r, line.g, line.b, 255) djui_hud_print_text(text, lineX, lineY, scale) end djui_hud_set_font(FONT_NORMAL) end hook_event(HOOK_ON_HUD_RENDER, function() render_player_readout() render_axis_lines() render() end) end install_ui()