-- name: ToeTagger 2.0 -- author: Codeflex -- description: \#88ccff\codeflex\#ffffff\\nDisplays custom overhead player tags with segmented health bars, plus an optional sidebar showing player health, icons, coins, and lives/remaining characters. local TAG_DIST_DEFAULT = 7000 local BAR_WIDTH = 58 local BAR_HEIGHT = 7 local TAG_WORLD_Y_OFFSET = 118 local STACK_Y_OFFSET = 32 local NAME_TO_BAR_GAP = 26 local BAR_BG_PAD = 1 local SEGMENTS = 8 local NAMETAG_SCALE_MIN = 0.35 local NAMETAG_SCALE_MAX = 1.25 local FAR_SCALE_MULT = 0.72 local FAR_SCALE_MIN = 0.28 local FAR_SCALE_MAX = 0.72 local NAME_OUTLINE_DARKNESS = 0.25 local FAR_TAG_EXTRA_DIST = 3000 local djui_hud_set_resolution = djui_hud_set_resolution local djui_hud_world_pos_to_screen_pos = djui_hud_world_pos_to_screen_pos local djui_hud_get_fov_coeff = djui_hud_get_fov_coeff or function() return 1.13 end local djui_hud_set_color = djui_hud_set_color local djui_hud_render_rect = djui_hud_render_rect local djui_hud_render_line = djui_hud_render_line local djui_hud_set_rotation = djui_hud_set_rotation local djui_hud_get_screen_width = djui_hud_get_screen_width local djui_hud_get_screen_height = djui_hud_get_screen_height local djui_hud_measure_text = djui_hud_measure_text local djui_hud_print_text = djui_hud_print_text local djui_hud_set_font = djui_hud_set_font local djui_hud_render_texture = djui_hud_render_texture local is_player_active = is_player_active local network_player_connected_count = network_player_connected_count local network_get_player_text_color_string = network_get_player_text_color_string local mod_storage_load = mod_storage_load local mod_storage_save = mod_storage_save local count_objects_with_behavior = count_objects_with_behavior local tie_to_nametag_scale = mod_storage_load("tie_to_nametag_scale") == "true" local table_mode = tonumber(mod_storage_load("table_mode")) or 0 local show_table_panel = mod_storage_load("show_table_panel") ~= "false" local show_table_icons = mod_storage_load("show_table_icons") ~= "false" local show_table_coins = mod_storage_load("show_table_coins") == "true" local show_table_lives = mod_storage_load("show_table_lives") == "true" local show_table_speed = mod_storage_load("show_table_speed") == "true" local show_table_stars = mod_storage_load("show_table_stars") == "true" local show_table_course = mod_storage_load("show_table_course") == "true" local show_table_caps = mod_storage_load("show_table_caps") == "true" local show_table_two_line = mod_storage_load("show_table_two_line") == "true" local show_table_pool_icons = mod_storage_load("show_table_pool_icons") == "true" local raycast_mode = tonumber(mod_storage_load("raycast_mode")) if raycast_mode == nil then raycast_mode = (mod_storage_load("show_raycast_lines") == "true") and 1 or 0 end local raycast_always_when_bubbled = mod_storage_load("raycast_always_when_bubbled") == "true" local show_players_in_level = mod_storage_load("show_players_in_level") == "true" local show_learned_entrance_markers = mod_storage_load("show_learned_entrance_markers") == "true" local show_entrance_marker_names = mod_storage_load("show_entrance_marker_names") == "true" local nav_marking_enabled = mod_storage_load("nav_marking_enabled") == "true" local show_table_red_coins = mod_storage_load("show_table_red_coins") == "true" local show_table_secrets = mod_storage_load("show_table_secrets") == "true" local show_player_cap_prefix = mod_storage_load("show_player_cap_prefix") == "true" local show_header_romhack = mod_storage_load("show_header_romhack") == "true" local show_player_course_name = mod_storage_load("show_player_course_name") == "true" local show_player_star_name = mod_storage_load("show_player_star_name") == "true" local lower_panel_for_custom_hud = mod_storage_load("lower_panel_for_custom_hud") == "true" local sidebar_offset_char_num = nil local sidebar_offset_costume = nil local sidebar_offset_target = 0 local sidebar_offset_active = 0 local sidebar_offset_stable_frames = 0 local entrance_tracker = {} local learned_entrances = {} local learned_entrances_dirty = false local last_romhack_key = mod_storage_load("learned_entrances_romhack") or "" local learned_entrances_storage_key = "learned_entrances" local learned_entrances_update_timer = 0 local learned_entrances_save_timer = 0 local nav_reticle_frames = 0 local SIDEBAR_OFFSET_SETTLE_FRAMES = 15 local toe_star_counter_loaded_key = "" local toe_star_counter_value = tonumber(mod_storage_load("toe_stars_default")) or 0 local toe_prev_num_stars = 0 local get_local_personal_star_total local current_romhack_name local CAP_PREFIX_RESERVED_WIDTH = 12 local NUZ_POOL_ICON_LIMIT = 5 local RAYCAST_OFF = 0 local RAYCAST_ON = 1 local RAYCAST_FAR_ONLY = 2 local RAYCAST_DPAD_DOWN = 3 local RAYCAST_AFK = 4 local RAYCAST_BUBBLE_ONLY = 5 local RED_COIN_COUNTER_ICON_W = 12 local RED_COIN_COUNTER_ICON_H = 16 local CUSTOM_HUD_PANEL_OFFSET = 120 local STAR_FLASH_FRAMES = 120 local ENTRANCE_LEARN_MIN_FRAMES = 8 local ENTRANCE_LEARN_MAX_DIST = 900 local ENTRANCE_MARKER_Y_OFFSET = 115 local ENTRANCE_UPDATE_INTERVAL = 10 local ENTRANCE_SAVE_INTERVAL = 90 local NAV_MARKER_Y_OFFSET = 45 if raycast_mode < RAYCAST_OFF or raycast_mode > RAYCAST_BUBBLE_ONLY then raycast_mode = RAYCAST_OFF end gServerSettings.nametags = false local function clamp(v, lo, hi) if v < lo then return lo end if v > hi then return hi end return v end local function split_csv(text) local out = {} if not text or text == "" then return out end for token in string.gmatch(text, "([^,]+)") do out[#out + 1] = token end return out end local function encode_storage_text(text) text = tostring(text or "") text = string.gsub(text, "%%", "%%25") text = string.gsub(text, ";", "%%3B") text = string.gsub(text, ",", "%%2C") return text end local function decode_storage_text(text) text = tostring(text or "") text = string.gsub(text, "%%2C", ",") text = string.gsub(text, "%%3B", ";") text = string.gsub(text, "%%25", "%%") return text end local function learned_entrances_romhack_key() if gGlobalSyncTable and gGlobalSyncTable.romhackFile and gGlobalSyncTable.romhackFile ~= "" then return "romhackFile:" .. tostring(gGlobalSyncTable.romhackFile) end local name = current_romhack_name and current_romhack_name() or nil if name == nil or name == "" then name = "Super Mario 64" end return name end local function hash_storage_key(text) local hash = 5381 text = tostring(text or "") for i = 1, #text do hash = ((hash * 33) + string.byte(text, i)) % 2147483647 end return tostring(hash) end local function learned_entrances_key_for_romhack(romhackKey) return "learned_entrances_v5_" .. hash_storage_key(romhackKey) end local function clear_loaded_learned_entrances() learned_entrances = {} entrance_tracker = {} learned_entrances_dirty = false end local function reset_current_learned_entrances() clear_loaded_learned_entrances() mod_storage_save(learned_entrances_storage_key, "") mod_storage_save("learned_entrances", "") end local function entrance_key(sourceLevel, sourceArea, targetLevel, targetArea) return tostring(sourceLevel or 0) .. ":" .. tostring(sourceArea or 0) .. ":" .. tostring(targetLevel or 0) .. ":" .. tostring(targetArea or 0) end local function load_learned_entrances() local romhackKey = learned_entrances_romhack_key() learned_entrances_storage_key = learned_entrances_key_for_romhack(romhackKey) clear_loaded_learned_entrances() mod_storage_save("learned_entrances_romhack", romhackKey) last_romhack_key = romhackKey local text = mod_storage_load(learned_entrances_storage_key) or "" for entry in string.gmatch(text, "([^;]+)") do local sourceLevel, sourceArea, targetLevel, targetArea, x, y, z, targetCourse, sourceCourse, targetName = string.match(entry, "^(-?%d+):(-?%d+):(-?%d+):(-?%d+),(-?[%d%.]+),(-?[%d%.]+),(-?[%d%.]+),(-?%d+),(-?%d+),?(.*)$") if not sourceLevel then sourceLevel, sourceArea, targetLevel, targetArea, x, y, z, targetCourse, targetName = string.match(entry, "^(-?%d+):(-?%d+):(-?%d+):(-?%d+),(-?[%d%.]+),(-?[%d%.]+),(-?[%d%.]+),(-?%d+),?(.*)$") sourceCourse = 0 end if not sourceLevel then sourceLevel, sourceArea, targetLevel, x, y, z = string.match(entry, "^(-?%d+):(-?%d+):(-?%d+),(-?[%d%.]+),(-?[%d%.]+),(-?[%d%.]+)$") targetArea = 0 targetCourse = 0 sourceCourse = 0 targetName = nil end if sourceLevel and sourceArea and targetLevel and targetArea and x and y and z then learned_entrances[entrance_key(tonumber(sourceLevel), tonumber(sourceArea), tonumber(targetLevel), tonumber(targetArea))] = { x = tonumber(x), y = tonumber(y), z = tonumber(z), targetLevel = tonumber(targetLevel), targetArea = tonumber(targetArea), targetCourse = tonumber(targetCourse) or 0, sourceCourse = tonumber(sourceCourse) or 0, targetName = targetName and targetName ~= "" and decode_storage_text(targetName) or nil, } end end end local function save_learned_entrances() if not learned_entrances_dirty then return end local parts = {} for key, pos in pairs(learned_entrances) do parts[#parts + 1] = key .. "," .. tostring(math.floor((pos.x or 0) + 0.5)) .. "," .. tostring(math.floor((pos.y or 0) + 0.5)) .. "," .. tostring(math.floor((pos.z or 0) + 0.5)) .. "," .. tostring(pos.targetCourse or 0) .. "," .. tostring(pos.sourceCourse or 0) .. "," .. encode_storage_text(pos.targetName or "") end mod_storage_save(learned_entrances_storage_key, table.concat(parts, ";")) learned_entrances_dirty = false end local function local_sync_index() -- Local synced HUD values belong in slot 0; Coop DX mirrors them out. return 0 end local function active_player(m) local np = gNetworkPlayers[m.playerIndex] if m.playerIndex == 0 then return 1 end if not np.connected then return 0 end if np.currCourseNum ~= gNetworkPlayers[0].currCourseNum then return 0 end if np.currActNum ~= gNetworkPlayers[0].currActNum then return 0 end if np.currLevelNum ~= gNetworkPlayers[0].currLevelNum then return 0 end if np.currAreaIndex ~= gNetworkPlayers[0].currAreaIndex then return 0 end return is_player_active(m) end local function player_shares_local_area(np) local localNp = gNetworkPlayers[0] return np and np.connected and np.currCourseNum == localNp.currCourseNum and np.currActNum == localNp.currActNum and np.currLevelNum == localNp.currLevelNum and np.currAreaIndex == localNp.currAreaIndex end local function raycast_mode_label() if raycast_mode == RAYCAST_ON then return "ON" elseif raycast_mode == RAYCAST_FAR_ONLY then return "FAR ONLY" elseif raycast_mode == RAYCAST_DPAD_DOWN then return "DPAD DOWN" elseif raycast_mode == RAYCAST_AFK then return "AFK" elseif raycast_mode == RAYCAST_BUBBLE_ONLY then return "BUBBLE ONLY" end return "OFF" end local function get_tag_distance() if gGlobalSyncTable and gGlobalSyncTable.tagDist ~= nil then return gGlobalSyncTable.tagDist end return TAG_DIST_DEFAULT end local function get_health_wedges(m) return clamp(m.health >> 8, 0, SEGMENTS) end local function get_health_color(wedges, nameR, nameG, nameB) if wedges >= 7 then return nameR, nameG, nameB elseif wedges >= 5 then return 255, 210, 70 elseif wedges >= 3 then return 255, 140, 60 end return 220, 70, 70 end local function convert_color(text) if text == nil or text:sub(2, 2) ~= "#" then return 255, 255, 255, 255 end text = text:sub(3, -2) local rstring, gstring, bstring = "", "", "" if text:len() ~= 3 and text:len() ~= 6 then return 255, 255, 255, 255 end if text:len() == 6 then rstring = text:sub(1, 2) or "ff" gstring = text:sub(3, 4) or "ff" bstring = text:sub(5, 6) or "ff" else rstring = text:sub(1, 1) .. text:sub(1, 1) gstring = text:sub(2, 2) .. text:sub(2, 2) bstring = text:sub(3, 3) .. text:sub(3, 3) end return tonumber("0x" .. rstring) or 255, tonumber("0x" .. gstring) or 255, tonumber("0x" .. bstring) or 255, 255 end local function strip_color_codes(text) local s = "" local inSlash = false for i = 1, #text do local c = text:sub(i, i) if c == "\\" then inSlash = not inSlash elseif not inSlash then s = s .. c end end return s end local function get_stack_scale(scale) if tie_to_nametag_scale then return clamp(scale, NAMETAG_SCALE_MIN, NAMETAG_SCALE_MAX) end return clamp(scale, 0.45, 0.9) end local function get_name_color(playerIndex) return convert_color(network_get_player_text_color_string(playerIndex)) end local function get_cap_prefix(m) if not show_player_cap_prefix or not m then return nil end if (m.flags & MARIO_WING_CAP) ~= 0 then return 255, 80, 80, m.capTimer or 0 end if (m.flags & MARIO_VANISH_CAP) ~= 0 then return 100, 180, 255, m.capTimer or 0 end if (m.flags & MARIO_METAL_CAP) ~= 0 then return 100, 255, 120, m.capTimer or 0 end return nil end local function cap_prefix_alpha(timer) if timer == nil or timer <= 0 then return 255 end if timer > 90 then return 255 end if (math.floor(timer / 5) % 2) == 0 then return 235 end return 60 end local function toe_global_timer() if get_global_timer then return get_global_timer() end return gGlobalTimer or 0 end local function get_cap_shimmer_color(m) if not m then return nil end local colors = {} if (m.flags & MARIO_WING_CAP) ~= 0 then colors[#colors + 1] = { 255, 80, 80 } end if (m.flags & MARIO_VANISH_CAP) ~= 0 then colors[#colors + 1] = { 100, 180, 255 } end if (m.flags & MARIO_METAL_CAP) ~= 0 then colors[#colors + 1] = { 100, 255, 120 } end if #colors == 0 then return nil end local cr, cg, cb = 0, 0, 0 for i = 1, #colors do cr = cr + colors[i][1] cg = cg + colors[i][2] cb = cb + colors[i][3] end cr = cr / #colors cg = cg / #colors cb = cb / #colors local lr = cr + (255 - cr) * 0.52 local lg = cg + (255 - cg) * 0.52 local lb = cb + (255 - cb) * 0.52 local dr = cr * 0.42 local dg = cg * 0.42 local db = cb * 0.42 local phase = (toe_global_timer() % 120) / 120 local tr, tg, tb if phase < 0.25 then local t = phase / 0.25 tr = 255 + (lr - 255) * t tg = 255 + (lg - 255) * t tb = 255 + (lb - 255) * t elseif phase < 0.5 then local t = (phase - 0.25) / 0.25 tr = lr + (cr - lr) * t tg = lg + (cg - lg) * t tb = lb + (cb - lb) * t elseif phase < 0.75 then local t = (phase - 0.5) / 0.25 tr = cr + (dr - cr) * t tg = cg + (dg - cg) * t tb = cb + (db - cb) * t else local t = (phase - 0.75) / 0.25 tr = dr + (255 - dr) * t tg = dg + (255 - dg) * t tb = db + (255 - db) * t end return math.floor(tr), math.floor(tg), math.floor(tb) end local function resolve_character_entry(charNum, costume) if not _G.charSelect or not _G.charSelect.character_get_full_table then return nil, nil, costume or 1 end local fullTable = _G.charSelect.character_get_full_table() local charTable = fullTable and fullTable[charNum] if not charTable then return nil, nil, costume or 1 end local costumeNum = costume or 1 if charTable[costumeNum] == nil then local fallback = nil for key, value in pairs(charTable) do if tonumber(key) == key and value ~= nil and (fallback == nil or key < fallback) then fallback = key end end costumeNum = fallback or costumeNum end return charTable[costumeNum], charTable, costumeNum end local function get_character_ui_color(playerIndex) if _G.charSelect and _G.charSelect.character_get_full_table and _G.charSelect.character_get_current_number then local charNum = _G.charSelect.character_get_current_number(playerIndex) local costume = _G.charSelect.character_get_current_costume and (_G.charSelect.character_get_current_costume(playerIndex) or 1) or 1 local entry = charNum and resolve_character_entry(charNum, costume) or nil if entry and entry.color and entry.color.r and entry.color.g and entry.color.b then return entry.color.r, entry.color.g, entry.color.b, 255 end end return get_name_color(playerIndex) end local function get_sidebar_lives_value(m, ps) if gGlobalSyncTable and gGlobalSyncTable.nuzSessionActive == true then if gGlobalSyncTable.nuzGlobalPool == true and gGlobalSyncTable.nuzGlobalRemaining ~= nil then return gGlobalSyncTable.nuzGlobalRemaining end if ps and ps.nuzRemaining ~= nil then return ps.nuzRemaining end end if ps and ps.toeLives ~= nil then return ps.toeLives end return m.numLives or 0 end local function get_sidebar_coins_value(m, ps) if ps and ps.toeCoins ~= nil then return ps.toeCoins end return m.numCoins or 0 end local function get_sidebar_speed_value(m, ps) if ps and ps.toeSpeed ~= nil then return ps.toeSpeed end return math.floor(math.abs(m.forwardVel or 0) + 0.5) end local function get_sidebar_stars_value(m, ps) if ps and ps.psc ~= nil then return ps.psc end if _G.PersonalStarCounter and _G.PersonalStarCounter.get_star_counter then return get_local_personal_star_total(m) end if ps and ps.toeStars ~= nil then return ps.toeStars end return m.numStars or 0 end local function get_star_flash_color(playerIndex) local ps = gPlayerSyncTable[playerIndex] if not ps or (ps.toeStarFlashTimer or 0) <= 0 then return nil end local phase = (toe_global_timer() * 10) % 360 local segment = math.floor(phase / 60) local t = (phase % 60) / 60 local colors = { { 255, 80, 80 }, { 255, 220, 70 }, { 80, 255, 120 }, { 80, 220, 255 }, { 140, 100, 255 }, { 255, 90, 220 }, } local a = colors[segment + 1] local b = colors[(segment + 1) % #colors + 1] return math.floor(a[1] + (b[1] - a[1]) * t), math.floor(a[2] + (b[2] - a[2]) * t), math.floor(a[3] + (b[3] - a[3]) * t) end get_local_personal_star_total = function(m) if _G.PersonalStarCounter and _G.PersonalStarCounter.get_star_counter then return _G.PersonalStarCounter.get_star_counter() or toe_star_counter_value end if m and m.numStars ~= nil then return m.numStars end return toe_star_counter_value end local function get_sidebar_course_value(m, ps) if ps and ps.toeCourseNum ~= nil then return ps.toeCourseNum end local np = gNetworkPlayers[m.playerIndex] return (np and np.currCourseNum) or 0 end local function get_local_red_coin_count() local m = gMarioStates[0] local total = m and m.area and m.area.numRedCoins or 0 if total == nil or total <= 0 or not count_objects_with_behavior or not get_behavior_from_id or id_bhvRedCoin == nil then return 0 end local remaining = count_objects_with_behavior(get_behavior_from_id(id_bhvRedCoin)) or 0 return clamp(total - remaining, 0, total) end local function get_local_secret_count() local m = gMarioStates[0] local total = m and m.area and m.area.numSecrets or 0 if total == nil or total <= 0 or not count_objects_with_behavior or not get_behavior_from_id or id_bhvHiddenStarTrigger == nil then return 0 end local remaining = count_objects_with_behavior(get_behavior_from_id(id_bhvHiddenStarTrigger)) or 0 return clamp(total - remaining, 0, total) end local function get_sidebar_save_flags(ps) if ps and ps.toeSaveFlags ~= nil then return ps.toeSaveFlags end if save_file_get_flags then return save_file_get_flags() or 0 end return 0 end local function get_far_name_scale(scale) return clamp(scale * FAR_SCALE_MULT, FAR_SCALE_MIN, FAR_SCALE_MAX) end local function table_mode_enabled() return table_mode ~= 0 end local function local_afker_active() local ps = gPlayerSyncTable[local_sync_index()] local m = gMarioStates[0] return ps and (ps.afkerActive == true or (ps.afkerSpectateGlobal ~= nil and ps.afkerSpectateGlobal ~= -1) or (m and m.action == ACT_DISAPPEARED)) end local function is_bubbled(m) return m and ACT_BUBBLED ~= nil and m.action == ACT_BUBBLED end local function get_sidebar_icon(localIndex, force) if (not force and not show_table_icons) or not _G.charSelect or not _G.charSelect.character_get_full_table then return nil end local charNum = _G.charSelect.character_get_current_number and _G.charSelect.character_get_current_number(localIndex) or nil local costume = _G.charSelect.character_get_current_costume and (_G.charSelect.character_get_current_costume(localIndex) or 1) or 1 if not charNum then return nil end local entry = resolve_character_entry(charNum, costume) return entry and entry.lifeIcon or nil end local function get_sidebar_icon_width(iconScale) if not show_table_icons then return 0 end return iconScale + 6 end local function render_sidebar_icon(localIndex, x, y, scale, force) local icon = get_sidebar_icon(localIndex, force) if not icon then return end if icon.width == nil and icon.height == nil then local r, g, b = get_character_ui_color(localIndex) local startFont = djui_hud_get_font() local textScale = math.max(0.5, scale / 22) djui_hud_set_font(FONT_RECOLOR_HUD) djui_hud_set_color(r, g, b, 255) djui_hud_print_text(icon, x, y - 1, textScale) djui_hud_set_font(startFont) return end djui_hud_set_color(255, 255, 255, 255) if icon.width and icon.height and icon.width > 0 and icon.height > 0 then djui_hud_render_texture(icon, x, y, scale / icon.width, scale / icon.height) end end local function render_nav_player_icon(localIndex, x, y, scale) if djui_hud_set_rotation then djui_hud_set_rotation(0, 0, 0) end render_sidebar_icon(localIndex, x, y, scale, true) if djui_hud_set_rotation then djui_hud_set_rotation(0, 0, 0) end end local function get_sidebar_stats_tokens(m, ps, longForm) local tokens = {} if show_table_coins then tokens[#tokens + 1] = { label = longForm and "COINS:" or "C:", value = tostring(get_sidebar_coins_value(m, ps)), lr = 255, lg = 220, lb = 70 } end if show_table_lives then tokens[#tokens + 1] = { label = longForm and "LIVES:" or "L:", value = tostring(get_sidebar_lives_value(m, ps)), lr = 110, lg = 255, lb = 120 } end if show_table_speed then tokens[#tokens + 1] = { label = longForm and "SPEED:" or "SP:", value = tostring(get_sidebar_speed_value(m, ps)), lr = 90, lg = 220, lb = 255 } end if show_table_stars then local sr, sg, sb = get_star_flash_color(m.playerIndex) tokens[#tokens + 1] = { label = longForm and "STARS:" or "STARS:", value = tostring(get_sidebar_stars_value(m, ps)), lr = sr or 255, lg = sg or 190, lb = sb or 60, vr = sr, vg = sg, vb = sb } end if show_table_course then tokens[#tokens + 1] = { label = "COURSE", value = tostring(get_sidebar_course_value(m, ps)), lr = 220, lg = 220, lb = 255 } end return tokens end local function get_sidebar_stats_width(m, ps, scale, longForm) local width = 0 local tokens = get_sidebar_stats_tokens(m, ps, longForm) for i = 1, #tokens do local token = tokens[i] width = width + (djui_hud_measure_text(token.label) + djui_hud_measure_text(token.value)) * scale + 6 if i < #tokens then width = width + 4 end end return width end local function render_sidebar_stats(m, ps, x, y, scale, longForm) local cursorX = x local tokens = get_sidebar_stats_tokens(m, ps, longForm) for i = 1, #tokens do local token = tokens[i] djui_hud_set_color(token.lr, token.lg, token.lb, 230) djui_hud_print_text(token.label, cursorX, y, scale) cursorX = cursorX + djui_hud_measure_text(token.label) * scale + 2 djui_hud_set_color(token.vr or 255, token.vg or 255, token.vb or 255, 230) djui_hud_print_text(token.value, cursorX, y, scale) cursorX = cursorX + djui_hud_measure_text(token.value) * scale + 6 if i < #tokens then cursorX = cursorX + 4 end end end local function get_special_tag_y_offset(playerIndex) if not _G.charSelect then return 0 end local charNum = _G.charSelect.character_get_current_number and _G.charSelect.character_get_current_number(playerIndex) or nil local costume = _G.charSelect.character_get_current_costume and (_G.charSelect.character_get_current_costume(playerIndex) or 1) or 1 if not charNum then return 0 end local entry = resolve_character_entry(charNum, costume) local name = entry and entry.name and string.upper(entry.name) or "" if string.find(name, "ZERO", 1, true) or string.find(name, "GAEA", 1, true) or string.find(name, "SHADOW", 1, true) then return 32 end if string.find(name, "VILE", 1, true) then return 26 end return 0 end current_romhack_name = function() if gGlobalSyncTable and gGlobalSyncTable.romhackFile and gGlobalSyncTable.romhackFile ~= "" then if gGlobalSyncTable.romhackFile == "vanilla" then return "Super Mario 64" end return tostring(gGlobalSyncTable.romhackFile) end if _G.ROMHACK and _G.ROMHACK.name then return tostring(_G.ROMHACK.name) end return "Super Mario 64" end local function current_star_name_for_player(playerIndex) local np = gNetworkPlayers[playerIndex] if not np or not get_star_name then return nil end local course = np.currCourseNum or 0 local act = np.currActNum or 0 if course == 0 or act <= 0 or act == 99 then return nil end return get_star_name(course, act) end local function current_star_name() return current_star_name_for_player(0) end local function current_course_name_for_player(playerIndex) local np = gNetworkPlayers[playerIndex] if not np or not get_level_name then return nil end if (np.currCourseNum or 0) == 0 then return nil end return get_level_name(np.currCourseNum or 0, np.currLevelNum or 0, np.currAreaIndex or 0) end local function get_sidebar_extra_y_offset() return sidebar_offset_active end local function update_sidebar_extra_y_offset() if lower_panel_for_custom_hud then sidebar_offset_char_num = nil sidebar_offset_costume = nil sidebar_offset_target = CUSTOM_HUD_PANEL_OFFSET sidebar_offset_active = CUSTOM_HUD_PANEL_OFFSET sidebar_offset_stable_frames = SIDEBAR_OFFSET_SETTLE_FRAMES return end if not _G.charSelect then sidebar_offset_char_num = nil sidebar_offset_costume = nil sidebar_offset_target = 0 sidebar_offset_active = 0 sidebar_offset_stable_frames = 0 return end local charNum = _G.charSelect.character_get_current_number and _G.charSelect.character_get_current_number(0) or nil local costume = _G.charSelect.character_get_current_costume and (_G.charSelect.character_get_current_costume(0) or 1) or 1 local offset = get_special_tag_y_offset(0) local target = 0 if offset >= 32 then target = 120 elseif offset >= 26 then target = 96 end if charNum ~= sidebar_offset_char_num or costume ~= sidebar_offset_costume then sidebar_offset_char_num = charNum sidebar_offset_costume = costume sidebar_offset_target = target sidebar_offset_stable_frames = 0 return end if sidebar_offset_active ~= target then sidebar_offset_stable_frames = sidebar_offset_stable_frames + 1 if sidebar_offset_stable_frames >= SIDEBAR_OFFSET_SETTLE_FRAMES then sidebar_offset_active = target end else sidebar_offset_target = target sidebar_offset_stable_frames = SIDEBAR_OFFSET_SETTLE_FRAMES end end local function render_caps_indicator(x, y, scale, flags) local items = { { text = "WC", enabled = (flags & SAVE_FLAG_HAVE_WING_CAP) ~= 0, r = 255, g = 80, b = 80 }, { text = "VC", enabled = (flags & SAVE_FLAG_HAVE_VANISH_CAP) ~= 0, r = 100, g = 180, b = 255 }, { text = "MC", enabled = (flags & SAVE_FLAG_HAVE_METAL_CAP) ~= 0, r = 100, g = 255, b = 120 }, } local cursorX = x for i = 1, #items do local item = items[i] if item.enabled then djui_hud_set_color(item.r, item.g, item.b, 220) else djui_hud_set_color(120, 120, 120, 220) end djui_hud_print_text(item.text, cursorX, y, scale) cursorX = cursorX + djui_hud_measure_text(item.text) * scale + 8 end end local function get_collectible_counter_width(scale) local width = 0 if show_table_red_coins then width = width + RED_COIN_COUNTER_ICON_W + 4 + djui_hud_measure_text("= 8") * scale if show_table_secrets then width = width + 10 end end if show_table_secrets then width = width + djui_hud_measure_text("S = 5") * scale end return width end local function render_level_collectible_counters(x, y, scale) local cursorX = x if show_table_red_coins then local redCoins = get_local_red_coin_count() local coinW = RED_COIN_COUNTER_ICON_W local coinH = RED_COIN_COUNTER_ICON_H local iconY = y - 5 djui_hud_set_color(35, 15, 10, 190) djui_hud_render_rect(cursorX + 1, iconY + 2, coinW, coinH) djui_hud_set_color(255, 70, 55, 235) djui_hud_render_rect(cursorX + 2, iconY + 1, coinW - 2, coinH) djui_hud_set_color(255, 170, 105, 235) djui_hud_render_rect(cursorX + coinW * 0.45, iconY + 2, math.max(1, scale), coinH - 2) cursorX = cursorX + coinW + 4 djui_hud_set_color(255, 255, 255, 225) djui_hud_print_text("= " .. tostring(redCoins), cursorX, y, scale) cursorX = cursorX + djui_hud_measure_text("= " .. tostring(redCoins)) * scale + 10 end if show_table_secrets then local secrets = get_local_secret_count() djui_hud_set_color(255, 75, 135, 245) djui_hud_print_text("S = " .. tostring(secrets), cursorX, y, scale) end end local function parse_pool_key(key) local charNum, costume = string.match(key or "", "^(%d+):?(%d*)$") charNum = tonumber(charNum) costume = tonumber(costume) if charNum == nil then return nil, nil end if costume == nil or costume == 0 then costume = 1 end return charNum, costume end local function pool_key_sort_name(key) local charNum, costume = parse_pool_key(key) if charNum == nil then return tostring(key) end local entry = resolve_character_entry(charNum, costume) return string.lower((entry and entry.name) or tostring(key)) end local function get_sidebar_pool_keys(ps, playerIndex) if not show_table_pool_icons or not ps or gGlobalSyncTable == nil or gGlobalSyncTable.nuzSessionActive == nil then return {} end local draftText = "" local removedText = "" if gGlobalSyncTable and gGlobalSyncTable.nuzSessionActive == true and (gGlobalSyncTable.nuzGlobalPool == true or gGlobalSyncTable.nuzGroupPool == true) then draftText = gGlobalSyncTable.nuzGlobalDraft or "" removedText = gGlobalSyncTable.nuzGlobalRemoved or "" else draftText = ps.nuzDraft or "" removedText = ps.nuzRemoved or "" end local removed = {} for _, token in ipairs(split_csv(removedText)) do removed[token] = true end local keys = {} for _, token in ipairs(split_csv(draftText)) do if token ~= "" and not removed[token] then keys[#keys + 1] = token end end table.sort(keys, function(a, b) local an = pool_key_sort_name(a) local bn = pool_key_sort_name(b) if an == bn then return tostring(a) < tostring(b) end return an < bn end) local currentKey = nil if ps and ps.nuzCurrentChar ~= nil then currentKey = tostring(ps.nuzCurrentChar) .. ":" .. tostring(ps.nuzCurrentCostume or 1) elseif _G.charSelect and _G.charSelect.character_get_current_number and playerIndex ~= nil then local charNum = _G.charSelect.character_get_current_number(playerIndex) local costume = _G.charSelect.character_get_current_costume and (_G.charSelect.character_get_current_costume(playerIndex) or 1) or 1 if charNum ~= nil then currentKey = tostring(charNum) .. ":" .. tostring(costume) end end if currentKey ~= nil and #keys > 1 then local currentPos = nil for i = 1, #keys do if keys[i] == currentKey then currentPos = i break end end if currentPos ~= nil then local rotated = {} for step = 1, #keys - 1 do local pos = ((currentPos - 1 + step) % #keys) + 1 rotated[#rotated + 1] = keys[pos] end keys = rotated end end while #keys > NUZ_POOL_ICON_LIMIT do table.remove(keys) end if #keys == 0 and ps and ps.nuzCurrentChar ~= nil then keys[1] = tostring(ps.nuzCurrentChar) .. ":" .. tostring(ps.nuzCurrentCostume or 1) end return keys end local function render_sidebar_pool_icons(ps, x, y, scale, playerIndex) local keys = get_sidebar_pool_keys(ps, playerIndex) if #keys == 0 and ps and ps.nuzCurrentChar ~= nil then keys[1] = tostring(ps.nuzCurrentChar) .. ":" .. tostring(ps.nuzCurrentCostume or 1) end if #keys == 0 then return 0 end local cursorX = x local drawn = 0 for _, key in ipairs(keys) do local charNum, costume = parse_pool_key(key) if charNum ~= nil and _G.charSelect and _G.charSelect.character_get_full_table then local entry = resolve_character_entry(charNum, costume) if entry == nil then entry = resolve_character_entry(charNum, 1) end local icon = entry and entry.lifeIcon or "?" if icon.width == nil and icon.height == nil then djui_hud_set_font(FONT_RECOLOR_HUD) djui_hud_set_color(255, 255, 255, 220) djui_hud_print_text(icon, cursorX, y, math.max(0.72, scale / 14)) djui_hud_set_font(FONT_NORMAL) elseif icon and icon.width and icon.height and icon.width > 0 and icon.height > 0 then djui_hud_set_color(255, 255, 255, 220) djui_hud_render_texture(icon, cursorX, y, scale / icon.width, scale / icon.height) end cursorX = cursorX + scale + 4 drawn = drawn + 1 end end if drawn == 0 and ps and ps.nuzCurrentChar ~= nil then local entry = resolve_character_entry(ps.nuzCurrentChar, ps.nuzCurrentCostume or 1) local icon = entry and entry.lifeIcon or "?" if icon.width == nil and icon.height == nil then djui_hud_set_font(FONT_RECOLOR_HUD) djui_hud_set_color(255, 255, 255, 220) djui_hud_print_text(icon, x, y, math.max(0.72, scale / 14)) djui_hud_set_font(FONT_NORMAL) elseif icon and icon.width and icon.height and icon.width > 0 and icon.height > 0 then djui_hud_set_color(255, 255, 255, 220) djui_hud_render_texture(icon, x, y, scale / icon.width, scale / icon.height) end return 1 end return drawn end local function render_segmented_bar(x, y, scale, wedges, alpha, nameR, nameG, nameB) local width = BAR_WIDTH * scale local height = BAR_HEIGHT * scale local pad = BAR_BG_PAD * scale local innerW = width - pad * 2 local innerH = height - pad * 2 local gap = math.max(1, scale) local segW = (innerW - (SEGMENTS - 1) * gap) / SEGMENTS local fillR, fillG, fillB = get_health_color(wedges, nameR, nameG, nameB) djui_hud_set_color(0, 0, 0, alpha) djui_hud_render_rect(x - pad, y - pad, width + pad * 2, height + pad * 2) for i = 0, SEGMENTS - 1 do local segX = x + i * (segW + gap) if i < wedges then djui_hud_set_color(fillR, fillG, fillB, alpha) else djui_hud_set_color(55, 55, 55, alpha) end djui_hud_render_rect(segX, y, segW, innerH) end end local function render_outlined_text(text, x, y, scale, alpha, r, g, b) local outlineOffset = math.max(1, scale * 2) local outlineR = math.floor(r * NAME_OUTLINE_DARKNESS) local outlineG = math.floor(g * NAME_OUTLINE_DARKNESS) local outlineB = math.floor(b * NAME_OUTLINE_DARKNESS) djui_hud_set_color(outlineR, outlineG, outlineB, alpha) djui_hud_print_text(text, x - outlineOffset, y, scale) djui_hud_print_text(text, x + outlineOffset, y, scale) djui_hud_print_text(text, x, y - outlineOffset, scale) djui_hud_print_text(text, x, y + outlineOffset, scale) djui_hud_set_color(r, g, b, alpha) djui_hud_print_text(text, x, y, scale) end local function render_cap_prefix(m, x, y, scale, alpha) local r, g, b, timer = get_cap_prefix(m) if r == nil then return end local sr, sg, sb = get_cap_shimmer_color(m) if sr ~= nil then r, g, b = sr, sg, sb end djui_hud_set_color(r, g, b, math.min(alpha, cap_prefix_alpha(timer))) djui_hud_print_text("!", x, y, scale) end local function render_name_tag(playerIndex, m, np, x, y, scale, alpha) local name = strip_color_codes(np.name) local textScale = clamp(scale, 0.32, 0.95) local prefixWidth = show_player_cap_prefix and CAP_PREFIX_RESERVED_WIDTH * textScale or 0 local width = djui_hud_measure_text(name) * textScale + prefixWidth local textX = x - width * 0.5 local r, g, b = get_name_color(playerIndex) djui_hud_set_font(FONT_SPECIAL) if show_player_cap_prefix then render_cap_prefix(m, textX, y, textScale, alpha) textX = textX + prefixWidth end render_outlined_text(name, textX, y, textScale, alpha, r, g, b) end local function render_full_tag(playerIndex, m, np, out, scale, alpha) local stackScale = get_stack_scale(scale) local nameScale = clamp(stackScale, 0.36, 0.95) local barScale = stackScale local scaledBarWidth = BAR_WIDTH * barScale local barX = out.x - scaledBarWidth * 0.5 local barY = out.y + STACK_Y_OFFSET * stackScale local nameY = barY - (NAME_TO_BAR_GAP * stackScale) - (6 * nameScale) local r, g, b = get_name_color(playerIndex) render_name_tag(playerIndex, m, np, out.x, nameY, nameScale, alpha) render_segmented_bar(barX, barY, barScale, get_health_wedges(m), alpha, r, g, b) end local function render_far_name_tag(playerIndex, np, out, scale, alpha) local farScale = get_far_name_scale(scale) local y = out.y - 8 * farScale render_name_tag(playerIndex, gMarioStates[playerIndex], np, out.x, y, farScale, alpha) end local function dist3f(ax, ay, az, bx, by, bz) local dx = bx - ax local dy = by - ay local dz = bz - az return math.sqrt(dx * dx + dy * dy + dz * dz) end local function get_synced_player_world_pos(m) if m == nil then return nil end if m.pos then return { x = m.pos.x or 0, y = (m.pos.y or 0) + 105, z = m.pos.z or 0 } end if m.marioObj then return { x = m.marioObj.oPosX or 0, y = (m.marioObj.oPosY or 0) + 105, z = m.marioObj.oPosZ or 0 } end return nil end local function get_synced_player_ground_pos(m) if m == nil then return nil end if m.pos then return { x = m.pos.x or 0, y = m.pos.y or 0, z = m.pos.z or 0 } end if m.marioObj then return { x = m.marioObj.oPosX or 0, y = m.marioObj.oPosY or 0, z = m.marioObj.oPosZ or 0 } end return nil end local function clamp_screen_point(out, width, height, pad) local x = out.x local y = out.y if out.z > -260 and gLakituState and gLakituState.pos and gMarioStates[0] and gMarioStates[0].pos then local camDist = dist3f(out.worldX or 0, out.worldY or 0, out.worldZ or 0, gLakituState.pos.x, gLakituState.pos.y, gLakituState.pos.z) local playerDist = dist3f(out.worldX or 0, out.worldY or 0, out.worldZ or 0, gMarioStates[0].pos.x, gMarioStates[0].pos.y, gMarioStates[0].pos.z) y = (playerDist < camDist) and pad or (height - pad) end return clamp(x, pad, width - pad), clamp(y, pad, height - pad) end local function local_player_lines_enabled() return raycast_mode ~= RAYCAST_OFF end local function raycast_mode_allows_render() local localM = gMarioStates[0] if raycast_always_when_bubbled and is_bubbled(localM) then return true end if raycast_mode == RAYCAST_OFF then return false elseif raycast_mode == RAYCAST_DPAD_DOWN then return D_JPAD ~= nil and localM and localM.controller and (localM.controller.buttonDown & D_JPAD) ~= 0 elseif raycast_mode == RAYCAST_AFK then return local_afker_active() elseif raycast_mode == RAYCAST_BUBBLE_ONLY then return is_bubbled(localM) end return true end local function is_hub_level(levelNum) return (LEVEL_CASTLE ~= nil and levelNum == LEVEL_CASTLE) or (LEVEL_CASTLE_GROUNDS ~= nil and levelNum == LEVEL_CASTLE_GROUNDS) or (LEVEL_CASTLE_COURTYARD ~= nil and levelNum == LEVEL_CASTLE_COURTYARD) end local function is_overworld_np(np) if not np then return false end return is_hub_level(np.currLevelNum or 0) or (np.currCourseNum or 0) == 0 or (COURSE_NONE ~= nil and np.currCourseNum == COURSE_NONE) end local function learned_entry_matches_route(pos, targetCourse, localCourse) if not pos then return false end targetCourse = targetCourse or 0 if targetCourse ~= 0 and (pos.targetCourse or 0) ~= 0 and (pos.targetCourse or 0) ~= targetCourse then return false end localCourse = localCourse or 0 if localCourse ~= 0 and (pos.sourceCourse or 0) ~= 0 and (pos.sourceCourse or 0) ~= localCourse then return false end return true end local function learned_entrance_pos_for_level(targetLevel, targetArea, localLevel, localArea, targetCourse, localCourse) local pos = learned_entrances[entrance_key(localLevel, localArea, targetLevel, targetArea)] if learned_entry_matches_route(pos, targetCourse, localCourse) then return pos end pos = learned_entrances[entrance_key(localLevel, localArea, targetLevel, 0)] if learned_entry_matches_route(pos, targetCourse, localCourse) then return pos end return nil end local function parse_entrance_key(key) local sourceLevel, sourceArea, targetLevel, targetArea = string.match(key or "", "^(-?%d+):(-?%d+):(-?%d+):(-?%d+)$") if not sourceLevel then return nil end return tonumber(sourceLevel), tonumber(sourceArea), tonumber(targetLevel), tonumber(targetArea) end local function has_learned_path_to_level(sourceLevel, sourceArea, sourceCourse, targetLevel, targetArea, targetCourse, depth, visited) if depth <= 0 then return false end local visitKey = tostring(sourceLevel or 0) .. ":" .. tostring(sourceArea or 0) .. ":" .. tostring(sourceCourse or 0) if visited[visitKey] then return false end visited[visitKey] = true if learned_entrance_pos_for_level(targetLevel, targetArea, sourceLevel, sourceArea, targetCourse, sourceCourse) then return true end for key, pos in pairs(learned_entrances) do local sLevel, sArea, tLevel, tArea = parse_entrance_key(key) if sLevel == sourceLevel and sArea == sourceArea and learned_entry_matches_route(pos, nil, sourceCourse) then if has_learned_path_to_level(tLevel, tArea, pos.targetCourse or 0, targetLevel, targetArea, targetCourse, depth - 1, visited) then return true end end end return false end local function next_entrance_pos_towards_level(targetLevel, targetArea, localLevel, localArea, targetCourse, localCourse) local direct = learned_entrance_pos_for_level(targetLevel, targetArea, localLevel, localArea, targetCourse, localCourse) if direct then return direct end if (targetCourse or 0) == 0 then return nil end if is_hub_level(localLevel) and is_hub_level(targetLevel) then return nil end for key, pos in pairs(learned_entrances) do local sLevel, sArea, tLevel, tArea = parse_entrance_key(key) if sLevel == localLevel and sArea == localArea and learned_entry_matches_route(pos, nil, localCourse) and has_learned_path_to_level(tLevel, tArea, pos.targetCourse or 0, targetLevel, targetArea, targetCourse, 4, {}) then return pos end end return nil end local function learned_target_name(targetCourse, targetLevel, targetArea) if get_level_name then return get_level_name(targetCourse, targetLevel or 0, targetArea or 0) end return nil end local function remember_learned_entrance(sourceLevel, sourceArea, sourceCourse, targetLevel, targetArea, targetCourse, pos) if not sourceLevel or not sourceArea or not targetLevel or not pos or targetLevel == 0 then return end local key = entrance_key(sourceLevel, sourceArea, targetLevel, targetArea) local existing = learned_entrances[key] local rounded = { x = math.floor((pos.x or 0) + 0.5), y = math.floor((pos.y or 0) + 0.5), z = math.floor((pos.z or 0) + 0.5), targetLevel = targetLevel, targetArea = targetArea or 0, targetCourse = targetCourse or 0, sourceCourse = sourceCourse or 0, targetName = learned_target_name(targetCourse or 0, targetLevel, targetArea or 0), } if existing then local targetName = learned_target_name(targetCourse or existing.targetCourse or 0, targetLevel, targetArea or existing.targetArea or 0) if (existing.targetCourse or 0) ~= (targetCourse or 0) or (existing.sourceCourse or 0) ~= (sourceCourse or 0) or existing.targetName ~= targetName then existing.targetCourse = targetCourse or existing.targetCourse or 0 existing.sourceCourse = sourceCourse or existing.sourceCourse or 0 existing.targetName = targetName learned_entrances_dirty = true end return end learned_entrances[key] = rounded learned_entrances_dirty = true end local function is_probably_bad_warp_transition(prev, now) if not prev or not now or not prev.pos or not now.pos then return true end if (prev.frames or 0) < ENTRANCE_LEARN_MIN_FRAMES then return true end if prev.health ~= nil and prev.health <= 0x100 then return true end if prev.action == ACT_DEATH or prev.action == ACT_STAND_DEATH or prev.action == ACT_WATER_DEATH or prev.action == ACT_DROWNING or prev.action == ACT_SUFFOCATION or prev.action == ACT_BURNING_DEATH or prev.action == ACT_QUICKSAND_DEATH or prev.action == ACT_ELECTROCUTION or prev.action == ACT_SHOCKED or prev.action == ACT_DEATH_EXIT or prev.action == ACT_SPECIAL_DEATH_EXIT or prev.action == ACT_FALLING_DEATH_EXIT then return true end if now.level == prev.level and now.area == prev.area then return true end if not prev.isHub and now.isHub then return true end if now.level == prev.level and dist3f(prev.pos.x, prev.pos.y, prev.pos.z, now.pos.x, now.pos.y, now.pos.z) > ENTRANCE_LEARN_MAX_DIST then return true end return false end local function update_learned_entrances() learned_entrances_update_timer = learned_entrances_update_timer + 1 if learned_entrances_update_timer < ENTRANCE_UPDATE_INTERVAL then learned_entrances_save_timer = learned_entrances_save_timer + 1 if learned_entrances_dirty and learned_entrances_save_timer >= ENTRANCE_SAVE_INTERVAL then save_learned_entrances() learned_entrances_save_timer = 0 end return end learned_entrances_update_timer = 0 local romhackKey = learned_entrances_romhack_key() if last_romhack_key ~= "" and last_romhack_key ~= romhackKey then save_learned_entrances() load_learned_entrances() end for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] local m = gMarioStates[i] if np and np.connected and m then local pos = get_synced_player_ground_pos(m) local prev = entrance_tracker[i] local now = { level = np.currLevelNum or 0, area = np.currAreaIndex or 0, course = np.currCourseNum or 0, isHub = is_overworld_np(np), pos = pos, action = m.action, health = m.health, frames = (prev and prev.level == (np.currLevelNum or 0) and prev.area == (np.currAreaIndex or 0)) and ((prev.frames or 0) + 1) or 0, } if prev and prev.pos and now.level ~= 0 and not is_probably_bad_warp_transition(prev, now) then remember_learned_entrance(prev.level, prev.area, prev.course, now.level, now.area, now.course, prev.pos) if prev.isHub and now.isHub and now.pos then remember_learned_entrance(now.level, now.area, now.course, prev.level, prev.area, prev.course, now.pos) end end entrance_tracker[i] = now else entrance_tracker[i] = nil end end learned_entrances_save_timer = learned_entrances_save_timer + ENTRANCE_UPDATE_INTERVAL if learned_entrances_dirty and learned_entrances_save_timer >= ENTRANCE_SAVE_INTERVAL then save_learned_entrances() learned_entrances_save_timer = 0 end end local function render_endpoint_star(x, y, r, g, b, alpha) djui_hud_set_font(FONT_HUD) djui_hud_set_color(0, 0, 0, math.floor(alpha * 0.5)) djui_hud_print_text("*", x - 4, y - 5, 0.6) djui_hud_set_color(255, 235, 75, alpha) djui_hud_print_text("*", x - 5, y - 6, 0.6) djui_hud_set_font(FONT_NORMAL) end local function object_pos(obj) if not obj then return nil end return { x = obj.oPosX or (obj.header and obj.header.gfx and obj.header.gfx.pos and obj.header.gfx.pos.x) or 0, y = obj.oPosY or (obj.header and obj.header.gfx and obj.header.gfx.pos and obj.header.gfx.pos.y) or 0, z = obj.oPosZ or (obj.header and obj.header.gfx and obj.header.gfx.pos and obj.header.gfx.pos.z) or 0, } end local function ray_hit_is_star(hit) if not hit then return false end local obj = hit.object or hit.hitObj or (hit.surface and hit.surface.object) if obj then local behaviorId = obj.behavior if get_id_from_behavior and obj.behavior then behaviorId = get_id_from_behavior(obj.behavior) end if id_bhvStar ~= nil and behaviorId == id_bhvStar then return true end if id_bhvGrandStar ~= nil and behaviorId == id_bhvGrandStar then return true end end if obj_get_first_with_behavior_id and obj_get_next_with_same_behavior_id and hit.hitPos then local behaviorIds = { id_bhvStar, id_bhvGrandStar } for i = 1, #behaviorIds do if behaviorIds[i] ~= nil then local star = obj_get_first_with_behavior_id(behaviorIds[i]) while star do local pos = object_pos(star) if pos and dist3f(hit.hitPos.x, hit.hitPos.y, hit.hitPos.z, pos.x, pos.y, pos.z) < 360 then return true end star = obj_get_next_with_same_behavior_id(star) end end end elseif obj_get_nearest_object_with_behavior_id and hit.hitPos and gMarioStates[0] and gMarioStates[0].marioObj then local nearest = nil if id_bhvStar ~= nil then nearest = obj_get_nearest_object_with_behavior_id(gMarioStates[0].marioObj, id_bhvStar) end if nearest == nil and id_bhvGrandStar ~= nil then nearest = obj_get_nearest_object_with_behavior_id(gMarioStates[0].marioObj, id_bhvGrandStar) end local pos = object_pos(nearest) if pos and dist3f(hit.hitPos.x, hit.hitPos.y, hit.hitPos.z, pos.x, pos.y, pos.z) < 360 then return true end end return false end local function star_pos_on_ray(start, dir, maxDist) if not start or not dir then return nil end local bestPos = nil local bestAlong = maxDist or 16000 local behaviorIds = { id_bhvStar, id_bhvGrandStar } local function consider_star(star) local pos = object_pos(star) if pos then local sx = pos.x - start.x local sy = pos.y - start.y local sz = pos.z - start.z local along = sx * dir.x + sy * dir.y + sz * dir.z if along > 0 and along < bestAlong then local px = start.x + dir.x * along local py = start.y + dir.y * along local pz = start.z + dir.z * along local sideDist = dist3f(pos.x, pos.y, pos.z, px, py, pz) if sideDist < 420 then bestPos = pos bestAlong = along end end end end if obj_get_first_with_behavior_id and obj_get_next_with_same_behavior_id then for i = 1, #behaviorIds do if behaviorIds[i] ~= nil then local star = obj_get_first_with_behavior_id(behaviorIds[i]) while star do consider_star(star) star = obj_get_next_with_same_behavior_id(star) end end end elseif obj_get_nearest_object_with_behavior_id and gMarioStates[0] and gMarioStates[0].marioObj then for i = 1, #behaviorIds do if behaviorIds[i] ~= nil then local star = obj_get_nearest_object_with_behavior_id(gMarioStates[0].marioObj, behaviorIds[i]) consider_star(star) end end end return bestPos end local function star_pos_near_hit(hitPos) if not hitPos then return nil end local behaviorIds = { id_bhvStar, id_bhvGrandStar } local bestPos = nil local bestDist = 520 local function consider_star(star) local pos = object_pos(star) if pos then local d = dist3f(hitPos.x, hitPos.y, hitPos.z, pos.x, pos.y, pos.z) if d < bestDist then bestDist = d bestPos = pos end end end if obj_get_first_with_behavior_id and obj_get_next_with_same_behavior_id then for i = 1, #behaviorIds do if behaviorIds[i] ~= nil then local star = obj_get_first_with_behavior_id(behaviorIds[i]) while star do consider_star(star) star = obj_get_next_with_same_behavior_id(star) end end end elseif obj_get_nearest_object_with_behavior_id and gMarioStates[0] and gMarioStates[0].marioObj then for i = 1, #behaviorIds do if behaviorIds[i] ~= nil then consider_star(obj_get_nearest_object_with_behavior_id(gMarioStates[0].marioObj, behaviorIds[i])) end end end return bestPos end local function nav_button_reset_learned_data() reset_current_learned_entrances() return true end local function update_nav_reticle(m) if not nav_marking_enabled or not m or not m.controller or Z_TRIG == nil then nav_reticle_frames = 0 return end local stationary = math.abs(m.forwardVel or 0) < 1 and math.abs(m.slideVelX or 0) < 1 and math.abs(m.slideVelZ or 0) < 1 if (m.controller.buttonDown & Z_TRIG) ~= 0 and stationary then nav_reticle_frames = nav_reticle_frames + 1 else nav_reticle_frames = 0 end end local function render_nav_reticle() if not nav_marking_enabled or nav_reticle_frames < 30 then return end if djui_hud_set_resolution then djui_hud_set_resolution(RESOLUTION_N64) end if djui_hud_set_font then djui_hud_set_font(FONT_NORMAL) end local x = ((djui_hud_get_screen_width and djui_hud_get_screen_width()) or 320) * 0.5 local y = ((djui_hud_get_screen_height and djui_hud_get_screen_height()) or 240) * 0.5 if not djui_hud_set_color or not djui_hud_print_text then return end djui_hud_set_color(0, 0, 0, 180) djui_hud_print_text(".", x - 1, y - 4, 0.8) djui_hud_set_color(255, 255, 255, 230) djui_hud_print_text(".", x - 2, y - 5, 0.8) end local function place_local_nav_marker() if not nav_marking_enabled or collision_find_surface_on_ray == nil or not gLakituState or not gLakituState.pos or not gLakituState.focus then return end local m = gMarioStates[0] local ps = gPlayerSyncTable[local_sync_index()] if not m or not ps then return end if Z_TRIG == nil or U_JPAD == nil or not m.controller or (m.controller.buttonDown & Z_TRIG) == 0 or (m.controller.buttonPressed & U_JPAD) == 0 then return end if ps.toeNavActive == true then ps.toeNavActive = false return end local start = get_synced_player_world_pos(m) if not start then return end local dx = gLakituState.focus.x - gLakituState.pos.x local dy = gLakituState.focus.y - gLakituState.pos.y local dz = gLakituState.focus.z - gLakituState.pos.z local len = math.sqrt(dx * dx + dy * dy + dz * dz) if len <= 0 then return end local rayLen = 16000 local dir = { x = dx / len, y = dy / len, z = dz / len } local hit = collision_find_surface_on_ray(start.x, start.y, start.z, dir.x * rayLen, dir.y * rayLen, dir.z * rayLen) if hit and hit.hitPos then local wallDist = dist3f(start.x, start.y, start.z, hit.hitPos.x, hit.hitPos.y, hit.hitPos.z) local cameraStart = { x = gLakituState.pos.x, y = gLakituState.pos.y, z = gLakituState.pos.z } local starPos = star_pos_on_ray(cameraStart, dir, rayLen) or star_pos_on_ray(start, dir, wallDist) or star_pos_near_hit(hit.hitPos) local navPos = starPos or hit.hitPos ps.toeNavActive = true ps.toeNavX = navPos.x ps.toeNavY = navPos.y ps.toeNavZ = navPos.z ps.toeNavCourse = gNetworkPlayers[0].currCourseNum or 0 ps.toeNavLevel = gNetworkPlayers[0].currLevelNum or 0 ps.toeNavArea = gNetworkPlayers[0].currAreaIndex or 0 ps.toeNavKind = (starPos ~= nil or ray_hit_is_star(hit)) and 1 or 0 end end local function render_nav_markers() if not nav_marking_enabled then return end local localNp = gNetworkPlayers[0] if not localNp then return end djui_hud_set_resolution(RESOLUTION_N64) djui_hud_set_font(FONT_NORMAL) for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] local ps = gPlayerSyncTable[i] if np and np.connected and ps and ps.toeNavActive == true and ps.toeNavCourse == localNp.currCourseNum and ps.toeNavLevel == localNp.currLevelNum and ps.toeNavArea == localNp.currAreaIndex then local pos = { x = ps.toeNavX or 0, y = (ps.toeNavY or 0) + NAV_MARKER_Y_OFFSET, z = ps.toeNavZ or 0 } local out = { x = 0, y = 0, z = 0 } if djui_hud_world_pos_to_screen_pos(pos, out) then local r, g, b = get_character_ui_color(i) djui_hud_set_font(FONT_HUD) if ps.toeNavKind == 1 then local pulse = (math.sin(toe_global_timer() * 0.28) + 1) * 0.5 local sr = math.floor(255) local sg = math.floor(215 + 40 * pulse) local sb = math.floor(70 + 185 * pulse) djui_hud_set_color(0, 0, 0, 210) djui_hud_print_text("*", out.x - 8, out.y - 8, 1.0) djui_hud_set_color(sr, sg, sb, 245) djui_hud_print_text("*", out.x - 9, out.y - 9, 1.0) else djui_hud_set_color(0, 0, 0, 210) djui_hud_print_text("X", out.x - 8, out.y - 8, 1.0) djui_hud_set_color(r, g, b, 245) djui_hud_print_text("X", out.x - 9, out.y - 9, 1.0) end djui_hud_set_font(FONT_NORMAL) render_nav_player_icon(i, out.x - 3, out.y - 3, 12) end end end djui_hud_set_font(FONT_NORMAL) end local function entrance_marker_label(pos) if not show_entrance_marker_names or not pos then return nil end if pos.targetName and pos.targetName ~= "" then return pos.targetName end return nil end local function render_learned_entrance_markers() if not show_learned_entrance_markers then return end local np = gNetworkPlayers[0] if not np then return end djui_hud_set_resolution(RESOLUTION_N64) local sourcePrefix = tostring(np.currLevelNum or 0) .. ":" .. tostring(np.currAreaIndex or 0) .. ":" for key, pos in pairs(learned_entrances) do if string.sub(key, 1, #sourcePrefix) == sourcePrefix then local markerPos = { x = pos.x, y = (pos.y or 0) + ENTRANCE_MARKER_Y_OFFSET, z = pos.z } local out = { x = 0, y = 0, z = 0 } if djui_hud_world_pos_to_screen_pos(markerPos, out) then render_endpoint_star(out.x, out.y, 255, 235, 75, 220) local label = entrance_marker_label(pos) if label then local scale = 0.42 local width = djui_hud_measure_text(label) * scale djui_hud_set_font(FONT_NORMAL) djui_hud_set_color(0, 0, 0, 180) djui_hud_print_text(label, out.x - width * 0.5 + 1, out.y + 8, scale) djui_hud_set_color(255, 235, 155, 230) djui_hud_print_text(label, out.x - width * 0.5, out.y + 7, scale) end end end end end local function render_raycast_lines() if not djui_hud_render_line then return end if network_player_connected_count() <= 1 then return end if not gNetworkPlayers[0].currAreaSyncValid then return end local localM = gMarioStates[0] local from = get_synced_player_world_pos(localM) if from == nil then return end djui_hud_set_resolution(RESOLUTION_N64) local screenWidth = (djui_hud_get_screen_width and djui_hud_get_screen_width()) or 320 local screenHeight = (djui_hud_get_screen_height and djui_hud_get_screen_height()) or 240 local edgePad = 8 local localNp = gNetworkPlayers[0] local localLevel = localNp and localNp.currLevelNum or 0 local localArea = localNp and localNp.currAreaIndex or 0 local normalLinesAllowed = raycast_mode_allows_render() local allowEntranceLines = show_players_in_level and local_player_lines_enabled() if not normalLinesAllowed and not allowEntranceLines then return end for i = 1, MAX_PLAYERS - 1 do local m = gMarioStates[i] local np = gNetworkPlayers[i] if np and np.connected and m ~= nil and (m.marioBodyState == nil or not m.marioBodyState.mirrorMario) then local sameArea = player_shares_local_area(np) local drawsEntrance = false local to = (sameArea and normalLinesAllowed) and get_synced_player_world_pos(m) or nil if to == nil and allowEntranceLines then to = next_entrance_pos_towards_level(np.currLevelNum or 0, np.currAreaIndex or 0, localLevel, localArea, np.currCourseNum or 0, localNp and localNp.currCourseNum or 0) drawsEntrance = to ~= nil end if to ~= nil then local drawForDistance = drawsEntrance or raycast_mode ~= RAYCAST_FAR_ONLY or dist3f(from.x, from.y, from.z, to.x, to.y, to.z) >= get_tag_distance() if drawForDistance then if drawsEntrance then to = { x = to.x, y = (to.y or 0) + ENTRANCE_MARKER_Y_OFFSET, z = to.z } end local alpha = drawsEntrance and 145 or 190 local outA = { x = 0, y = 0, z = 0 } local outB = { x = 0, y = 0, z = 0, worldX = to.x, worldY = to.y, worldZ = to.z } djui_hud_world_pos_to_screen_pos(to, outB) if djui_hud_world_pos_to_screen_pos(from, outA) then local r, g, b = get_character_ui_color(i) if is_bubbled(m) then r, g, b = 165, 165, 165 alpha = math.min(alpha, 95) end local endX, endY = clamp_screen_point(outB, screenWidth, screenHeight, edgePad) djui_hud_set_color(0, 0, 0, math.floor(alpha * 0.45)) djui_hud_render_line(outA.x + 1, outA.y + 1, endX + 1, endY + 1, 2) djui_hud_set_color(r, g, b, alpha) djui_hud_render_line(outA.x, outA.y, endX, endY, 1) if drawsEntrance then if djui_hud_set_rotation then djui_hud_set_rotation(0, 0, 0) end djui_hud_set_color(0, 0, 0, math.min(alpha, 160)) djui_hud_render_rect(endX - 7, endY - 7, 14, 14) render_nav_player_icon(i, endX - 6, endY - 6, 12) end end end end end end end local function render_player_health_tags() if network_player_connected_count() <= 1 then return end if not gNetworkPlayers[0].currAreaSyncValid then return end if obj_get_first_with_behavior_id and obj_get_first_with_behavior_id(id_bhvActSelector) ~= nil then return end if _G.charSelect and _G.charSelect.is_menu_open and _G.charSelect.is_menu_open() then return end djui_hud_set_resolution(RESOLUTION_N64) local fovCoeff = djui_hud_get_fov_coeff() local tagDist = get_tag_distance() for i = 1, MAX_PLAYERS - 1 do local m = gMarioStates[i] local np = gNetworkPlayers[i] local out = { x = 0, y = 0, z = 0 } local pos = { x = m.marioBodyState.headPos.x, y = m.marioBodyState.headPos.y + TAG_WORLD_Y_OFFSET + get_special_tag_y_offset(i), z = m.marioBodyState.headPos.z } if np.currAreaSyncValid and active_player(m) ~= 0 and not m.marioBodyState.mirrorMario and m.marioBodyState.updateHeadPosTime == toe_global_timer() and djui_hud_world_pos_to_screen_pos(pos, out) then local scale = -300 / out.z * fovCoeff if scale >= 0 then local alpha = math.min(np.fadeOpacity << 3, 255) local nearAlpha = alpha if out.z < -tagDist + 1000 then nearAlpha = clamp((out.z + tagDist) * 0.255, 0, alpha) end if nearAlpha > 0 then render_full_tag(i, m, np, out, scale, nearAlpha) else local farAlpha = alpha if out.z < -(tagDist + FAR_TAG_EXTRA_DIST) + 1000 then farAlpha = clamp((out.z + tagDist + FAR_TAG_EXTRA_DIST) * 0.255, 0, alpha) end if farAlpha > 0 then render_far_name_tag(i, np, out, scale, farAlpha) end end end end end djui_hud_set_color(255, 255, 255, 255) end local function render_sidebar_table() if not table_mode_enabled() then return end djui_hud_set_resolution(RESOLUTION_DJUI) djui_hud_set_font(FONT_NORMAL) local sessionActive = gGlobalSyncTable and gGlobalSyncTable.nuzSessionActive == true local showPoolIconLine = false local showCollectibleLine = show_table_red_coins or show_table_secrets local x = 18 local y = 210 + get_sidebar_extra_y_offset() local rowGap = show_table_two_line and ((show_player_course_name or show_player_star_name) and (showPoolIconLine and 92 or 74) or 56) or 34 local nameScale = 0.82 local barScale = 1.35 local statScale = show_table_two_line and 0.48 or 0.52 local titleScale = 0.88 local titleY = y - 30 local panelPadX = 12 local panelPadTop = 14 local panelPadBottom = 12 local panelAlpha = 96 local title = "ToeTagger" local iconScale = 20 local iconWidth = get_sidebar_icon_width(iconScale) local nameX = x + iconWidth local statsGap = 10 local maxNameWidth = djui_hud_measure_text(title) * titleScale local maxStatsWidth = 0 local rowCount = 0 local capsScale = 0.52 local capsWidth = show_table_caps and ((djui_hud_measure_text("WC") + djui_hud_measure_text("VC") + djui_hud_measure_text("MC")) * capsScale + 16) or 0 local collectibleWidth = 0 if show_table_red_coins then collectibleWidth = collectibleWidth + 8 * capsScale + djui_hud_measure_text("= 8") * capsScale + 10 end if show_table_secrets then collectibleWidth = collectibleWidth + djui_hud_measure_text("S = 5") * capsScale end local romhackName = show_header_romhack and current_romhack_name() or nil local headerExtraLines = (romhackName and 1 or 0) + (showCollectibleLine and 1 or 0) local collectibleLineHeight = show_table_red_coins and 20 or 14 for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] local m = gMarioStates[i] local ps = gPlayerSyncTable[i] if np and np.connected and m then local name = strip_color_codes(np.name) local nameWidth = djui_hud_measure_text(name) * nameScale if nameWidth > maxNameWidth then maxNameWidth = nameWidth end local statsWidth = get_sidebar_stats_width(m, ps, statScale, show_table_two_line) if statsWidth > maxStatsWidth then maxStatsWidth = statsWidth end rowCount = rowCount + 1 end end local barWidth = BAR_WIDTH * barScale local capMarkerWidth = show_player_cap_prefix and (CAP_PREFIX_RESERVED_WIDTH * nameScale + 6) or 0 local minBarXOffset = 112 + iconWidth local barXOffset = math.min(math.max(minBarXOffset, iconWidth + maxNameWidth + 12), minBarXOffset + 64) local contentWidth = show_table_two_line and math.max(iconWidth + maxNameWidth, barXOffset + barWidth + capMarkerWidth, iconWidth + maxStatsWidth + 8) or math.max(iconWidth + maxNameWidth, barXOffset + barWidth + capMarkerWidth + (maxStatsWidth > 0 and (statsGap + maxStatsWidth) or 0)) if romhackName then maxNameWidth = math.max(maxNameWidth, djui_hud_measure_text(romhackName) * 0.48) end local panelWidth = math.max(contentWidth + panelPadX * 2, djui_hud_measure_text(title) * titleScale + capsWidth + panelPadX * 3, collectibleWidth + panelPadX * 2, iconWidth + maxNameWidth + panelPadX * 2) local extraPoolLine = showPoolIconLine and (show_table_two_line and 34 or 24) or 0 local headerHeight = 22 + (romhackName and 14 or 0) + (showCollectibleLine and collectibleLineHeight or 0) + (show_table_caps and 14 or 0) local panelHeight = (y - titleY) + panelPadTop + panelPadBottom + math.max(rowCount - 1, 0) * rowGap + (show_table_two_line and 28 or 0) + extraPoolLine + BAR_HEIGHT * barScale + headerHeight - 6 if show_table_panel then djui_hud_set_color(0, 0, 0, panelAlpha) djui_hud_render_rect(x - panelPadX, titleY - panelPadTop, panelWidth, panelHeight) end djui_hud_set_color(255, 255, 255, 220) djui_hud_print_text(title, x, titleY, titleScale) if romhackName then djui_hud_set_color(210, 210, 255, 220) local romhackScale = 0.48 local romhackWidth = djui_hud_measure_text(romhackName) * romhackScale djui_hud_print_text(romhackName, x - panelPadX + panelWidth - romhackWidth - 6, titleY - 12, romhackScale) end local headerY = titleY + 2 if show_table_caps then local flags = get_sidebar_save_flags(gPlayerSyncTable[local_sync_index()]) local capsX = x - panelPadX + panelWidth - capsWidth - 6 render_caps_indicator(capsX, headerY, capsScale, flags) headerY = headerY + 14 end if showCollectibleLine then local counterWidth = get_collectible_counter_width(capsScale) render_level_collectible_counters(x - panelPadX + panelWidth - counterWidth - 6, headerY, capsScale) headerY = headerY + collectibleLineHeight end y = math.max(y, headerY + 10) for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] local m = gMarioStates[i] local ps = gPlayerSyncTable[i] if np and np.connected and m then local name = strip_color_codes(np.name) local r, g, b = get_character_ui_color(i) local sr, sg, sb = get_cap_shimmer_color(m) if sr ~= nil then r, g, b = sr, sg, sb end render_sidebar_icon(i, x, y + 2, iconScale) local rowNameX = nameX djui_hud_set_color(r, g, b, 255) djui_hud_print_text(name, rowNameX, y, nameScale) render_segmented_bar(x + barXOffset, y + 5, barScale, get_health_wedges(m), 220, r, g, b) if show_player_cap_prefix then render_cap_prefix(m, x + barXOffset + barWidth + 5, y + 1, nameScale, 255) end if show_table_coins or show_table_lives or show_table_speed or show_table_stars or show_table_course then if show_table_two_line then render_sidebar_stats(m, ps, rowNameX, y + 27, statScale, true) else render_sidebar_stats(m, ps, x + barXOffset + barWidth + statsGap + capMarkerWidth, y + 2, statScale, false) end end if show_table_two_line and (show_player_course_name or show_player_star_name) then local infoY = y + 40 local courseText = show_player_course_name and current_course_name_for_player(i) or nil local starText = show_player_star_name and current_star_name_for_player(i) or nil local courseScale = 0.52 local starScale = 0.52 local lineLimit = panelWidth - (rowNameX - (x - panelPadX)) - 10 if courseText and starText then local sep = " - " local courseWidth = djui_hud_measure_text(courseText) * courseScale local sepWidth = djui_hud_measure_text(sep) * courseScale local remainingWidth = math.max(lineLimit - courseWidth - sepWidth, 24) while djui_hud_measure_text(starText) * starScale > remainingWidth and #starText > 4 do starText = string.sub(starText, 1, #starText - 2) end if djui_hud_measure_text(starText) * starScale > remainingWidth and #starText > 1 then starText = string.sub(starText, 1, math.max(#starText - 3, 1)) end if djui_hud_measure_text(starText) * starScale > remainingWidth then starText = string.sub(starText, 1, math.max(math.floor(#starText * 0.65), 1)) end if starText ~= current_star_name_for_player(i) then starText = starText .. "..." end djui_hud_set_color(220, 220, 255, 210) djui_hud_print_text(courseText, rowNameX, infoY, courseScale) local starX = rowNameX + djui_hud_measure_text(courseText) * courseScale + sepWidth djui_hud_print_text(sep, rowNameX + djui_hud_measure_text(courseText) * courseScale, infoY, courseScale) djui_hud_set_color(255, 210, 120, 210) djui_hud_print_text(starText, starX, infoY - 1, starScale) elseif courseText then djui_hud_set_color(220, 220, 255, 210) djui_hud_print_text(courseText, rowNameX, infoY, courseScale) elseif starText then djui_hud_set_color(255, 210, 120, 210) djui_hud_print_text(starText, rowNameX, infoY - 1, starScale) end end if showPoolIconLine then local poolIconsY = y + (show_table_two_line and ((show_player_course_name or show_player_star_name) and 66 or 52) or 24) render_sidebar_pool_icons(ps, rowNameX, poolIconsY, 18, i) end y = y + rowGap end end djui_hud_set_color(255, 255, 255, 255) end local function toetagger_scale_command(msg) local arg = msg and msg:lower() or "" if arg == "tag" or arg == "nametag" or arg == "on" then tie_to_nametag_scale = true elseif arg == "default" or arg == "off" then tie_to_nametag_scale = false else tie_to_nametag_scale = not tie_to_nametag_scale end mod_storage_save("tie_to_nametag_scale", tie_to_nametag_scale and "true" or "false") if tie_to_nametag_scale then djui_popup_create("ToeTagger scale now follows the player nametag more closely.", 2) else djui_popup_create("ToeTagger scale restored to its default sizing.", 2) end return true end local function toetagger_table_command(msg) local arg = msg and msg:lower() or "" if arg == "on" or arg == "afk" then table_mode = 1 elseif arg == "off" then table_mode = 0 else table_mode = table_mode == 0 and 1 or 0 end mod_storage_save("table_mode", tostring(table_mode)) if table_mode == 0 then djui_popup_create("ToeTagger player table disabled.", 2) else djui_popup_create("ToeTagger player table enabled.", 2) end return true end local function register_mod_menu() hook_mod_menu_checkbox("Scale Follows Tag", tie_to_nametag_scale, function(_, value) tie_to_nametag_scale = value mod_storage_save("tie_to_nametag_scale", tie_to_nametag_scale and "true" or "false") end) hook_mod_menu_checkbox("Enable Sidebar", table_mode ~= 0, function(_, value) table_mode = value and 1 or 0 mod_storage_save("table_mode", tostring(table_mode)) end) hook_mod_menu_checkbox("Sidebar Panel", show_table_panel, function(_, value) show_table_panel = value mod_storage_save("show_table_panel", show_table_panel and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Coins", show_table_coins, function(_, value) show_table_coins = value mod_storage_save("show_table_coins", show_table_coins and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Lives", show_table_lives, function(_, value) show_table_lives = value mod_storage_save("show_table_lives", show_table_lives and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Speed", show_table_speed, function(_, value) show_table_speed = value mod_storage_save("show_table_speed", show_table_speed and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Stars", show_table_stars, function(_, value) show_table_stars = value mod_storage_save("show_table_stars", show_table_stars and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Course", show_table_course, function(_, value) show_table_course = value mod_storage_save("show_table_course", show_table_course and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Icons", show_table_icons, function(_, value) show_table_icons = value mod_storage_save("show_table_icons", show_table_icons and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Course Names", show_player_course_name, function(_, value) show_player_course_name = value mod_storage_save("show_player_course_name", show_player_course_name and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Star Names", show_player_star_name, function(_, value) show_player_star_name = value mod_storage_save("show_player_star_name", show_player_star_name and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Romhack Name", show_header_romhack, function(_, value) show_header_romhack = value mod_storage_save("show_header_romhack", show_header_romhack and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Caps Unlocked", show_table_caps, function(_, value) show_table_caps = value mod_storage_save("show_table_caps", show_table_caps and "true" or "false") end) hook_mod_menu_checkbox("Sidebar Red Coins", show_table_red_coins, function(_, value) show_table_red_coins = value mod_storage_save("show_table_red_coins", show_table_red_coins and "true" or "false") end) hook_mod_menu_checkbox("Sidebar 'Secrets' Counter", show_table_secrets, function(_, value) show_table_secrets = value mod_storage_save("show_table_secrets", show_table_secrets and "true" or "false") end) hook_mod_menu_checkbox("Multi-Line Panel", show_table_two_line, function(_, value) show_table_two_line = value mod_storage_save("show_table_two_line", show_table_two_line and "true" or "false") end) hook_mod_menu_checkbox("Lower Sidebar", lower_panel_for_custom_hud, function(_, value) lower_panel_for_custom_hud = value mod_storage_save("lower_panel_for_custom_hud", lower_panel_for_custom_hud and "true" or "false") sidebar_offset_active = value and CUSTOM_HUD_PANEL_OFFSET or 0 sidebar_offset_stable_frames = SIDEBAR_OFFSET_SETTLE_FRAMES end) hook_mod_menu_checkbox("Power-Up Marker !", show_player_cap_prefix, function(_, value) show_player_cap_prefix = value mod_storage_save("show_player_cap_prefix", show_player_cap_prefix and "true" or "false") end) hook_mod_menu_checkbox("Nav Marking Z + DPad Up", nav_marking_enabled, function(_, value) nav_marking_enabled = value mod_storage_save("nav_marking_enabled", nav_marking_enabled and "true" or "false") end) hook_mod_menu_slider("Player Nav Lines: " .. raycast_mode_label(), raycast_mode, RAYCAST_OFF, RAYCAST_BUBBLE_ONLY, function(index, value) raycast_mode = clamp(value, RAYCAST_OFF, RAYCAST_BUBBLE_ONLY) mod_storage_save("raycast_mode", tostring(raycast_mode)) mod_storage_save("show_raycast_lines", raycast_mode ~= RAYCAST_OFF and "true" or "false") if index and update_mod_menu_element_name then update_mod_menu_element_name(index, "Player Nav Lines: " .. raycast_mode_label()) end end) hook_mod_menu_checkbox("Always Show Nav Lines When Bubbled", raycast_always_when_bubbled, function(_, value) raycast_always_when_bubbled = value mod_storage_save("raycast_always_when_bubbled", raycast_always_when_bubbled and "true" or "false") end) hook_mod_menu_checkbox("Mark Learned Entrances", show_learned_entrance_markers, function(_, value) show_learned_entrance_markers = value mod_storage_save("show_learned_entrance_markers", show_learned_entrance_markers and "true" or "false") end) hook_mod_menu_checkbox("Entrance Marker Names", show_entrance_marker_names, function(_, value) show_entrance_marker_names = value mod_storage_save("show_entrance_marker_names", show_entrance_marker_names and "true" or "false") end) hook_mod_menu_checkbox("Show Players In Level", show_players_in_level, function(_, value) show_players_in_level = value mod_storage_save("show_players_in_level", show_players_in_level and "true" or "false") end) if hook_mod_menu_button then hook_mod_menu_button("Reset Learned Nav Data", nav_button_reset_learned_data) else hook_mod_menu_checkbox("Reset Learned Nav Data", false, function(_, value) if value then reset_current_learned_entrances() end end) end end hook_event(HOOK_ON_HUD_RENDER, function() -- Temporarily disabled while isolating the 2.0 Lua error loop. update_learned_entrances() render_learned_entrance_markers() render_nav_reticle() render_nav_markers() -- Temporarily disabled while isolating the 2.0 Lua error loop. -- The repeated lag errors reproduce inside the nav-line render path. render_player_health_tags() render_sidebar_table() render_raycast_lines() end) hook_event(HOOK_MARIO_UPDATE, function(m) if m.playerIndex ~= 0 then return end local ps = gPlayerSyncTable[local_sync_index()] if not ps then return end -- Temporarily disabled while isolating the 2.0 Lua error loop. update_nav_reticle(m) place_local_nav_marker() update_sidebar_extra_y_offset() ps.toeLives = m.numLives or 0 ps.toeSpeed = math.floor(math.abs(m.forwardVel or 0) + 0.5) local localStars = get_local_personal_star_total(m) if ps.toeStars ~= nil and localStars > ps.toeStars then ps.toeStarFlashTimer = STAR_FLASH_FRAMES elseif (ps.toeStarFlashTimer or 0) > 0 then ps.toeStarFlashTimer = ps.toeStarFlashTimer - 1 end ps.toeStars = localStars ps.toeCourseNum = (gNetworkPlayers[0] and gNetworkPlayers[0].currCourseNum) or 0 ps.toeSaveFlags = (save_file_get_flags and (save_file_get_flags() or 0)) or 0 if not obj_get_first_with_behavior_id or obj_get_first_with_behavior_id(id_bhvActSelector) == nil then ps.toeCoins = m.numCoins or 0 end toe_prev_num_stars = m.numStars or toe_prev_num_stars end) hook_chat_command("toetagger-scale", "[tag|default] toggle ToeTagger nametag-linked scaling", toetagger_scale_command) hook_chat_command("toetagger-table", "[off|on] toggle ToeTagger sidebar", toetagger_table_command) load_learned_entrances() register_mod_menu()