-- name: ToeTagger 1.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_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 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 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 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 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 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 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 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 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 type(key) == "number" 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() end if ps and ps.toeStars ~= nil then return ps.toeStars end return m.numStars or 0 end get_local_personal_star_total = function() if _G.PersonalStarCounter and _G.PersonalStarCounter.get_star_counter then return _G.PersonalStarCounter.get_star_counter() or toe_star_counter_value end return toe_star_counter_value end local function current_star_counter_storage_key() local saveSlot = get_current_save_file_num and (get_current_save_file_num() or 1) or 1 if gGlobalSyncTable and gGlobalSyncTable.nuzSessionActive == true then local sessionId = tostring(gGlobalSyncTable.nuzSessionId or "0") return "toe_stars_session_" .. tostring(saveSlot) .. "_" .. sessionId end return "toe_stars_save_" .. tostring(saveSlot) 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_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() local ps = gPlayerSyncTable[local_sync_index()] if ps then if ps.afkerActive == nil then ps.afkerActive = false end if ps.afkerSpectateGlobal == nil then ps.afkerSpectateGlobal = -1 end end if table_mode == 1 then return true end local m = gMarioStates[0] if table_mode == 2 and ps and (ps.afkerActive == true or (ps.afkerSpectateGlobal ~= nil and ps.afkerSpectateGlobal ~= -1) or (m and m.action == ACT_DISAPPEARED)) then return true end return false end local function get_sidebar_icon(localIndex) if 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) local icon = get_sidebar_icon(localIndex) if not icon then return end if type(icon) == "string" 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 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 tokens[#tokens + 1] = { label = longForm and "STARS:" or "STARS:", value = tostring(get_sidebar_stars_value(m, ps)), lr = 255, lg = 190, lb = 60 } 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(255, 255, 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 local function current_romhack_name() if gGlobalSyncTable and gGlobalSyncTable.romhackFile and gGlobalSyncTable.romhackFile ~= "" and gGlobalSyncTable.romhackFile ~= "vanilla" then return tostring(gGlobalSyncTable.romhackFile) end if gActiveMods then for _, mod in ipairs(gActiveMods) do local incompatible = mod.incompatible or "" if mod.category == "romhack" or incompatible:find("romhack") then local name = mod.name or "Super Mario 64" name = name:gsub("\\#%x%x%x%x%x%x\\", "") name = name:gsub("\\\\", "") if name == "vanilla" then name = "Super Mario 64" end return name end end 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 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 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 get_sidebar_pool_keys(ps) 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 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) local keys = get_sidebar_pool_keys(ps) 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 type(icon) == "string" 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 type(icon) == "string" 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_name_tag(playerIndex, np, x, y, scale, alpha) local name = strip_color_codes(np.name) local textScale = clamp(scale, 0.32, 0.95) local width = djui_hud_measure_text(name) * textScale local textX = x - width * 0.5 local r, g, b = get_name_color(playerIndex) djui_hud_set_font(FONT_SPECIAL) 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, 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, np, out.x, y, farScale, alpha) 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(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 == get_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 = show_table_pool_icons and sessionActive 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 romhackName = show_header_romhack and current_romhack_name() or nil local headerExtraLines = romhackName and 1 or 0 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 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, iconWidth + maxStatsWidth + 8) or math.max(iconWidth + maxNameWidth, barXOffset + barWidth + (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, iconWidth + maxNameWidth + panelPadX * 2) local extraPoolLine = showPoolIconLine and (show_table_two_line and 34 or 24) or 0 local headerHeight = 22 + headerExtraLines * 14 + (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 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) render_sidebar_icon(i, x, y + 2, iconScale) djui_hud_set_color(r, g, b, 255) djui_hud_print_text(name, nameX, y, nameScale) render_segmented_bar(x + barXOffset, y + 5, barScale, get_health_wedges(m), 220, r, g, b) 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, nameX, y + 27, statScale, true) else render_sidebar_stats(m, ps, x + barXOffset + barWidth + statsGap, 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 - (nameX - (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, nameX, infoY, courseScale) local starX = nameX + djui_hud_measure_text(courseText) * courseScale + sepWidth djui_hud_print_text(sep, nameX + 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, nameX, infoY, courseScale) elseif starText then djui_hud_set_color(255, 210, 120, 210) djui_hud_print_text(starText, nameX, 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, nameX, poolIconsY, 18) 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" then table_mode = 1 elseif arg == "afk" then table_mode = 2 elseif arg == "off" then table_mode = 0 else table_mode = (table_mode + 1) % 3 end mod_storage_save("table_mode", tostring(table_mode)) if table_mode == 0 then djui_popup_create("ToeTagger player table disabled.", 2) elseif table_mode == 1 then djui_popup_create("ToeTagger player table enabled.", 2) else djui_popup_create("ToeTagger player table set to AFK-only mode.", 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("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 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 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("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("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("Player Course Name", 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("Player Star Name", 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 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("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("Pool Icons (Nuzlocker)", show_table_pool_icons, function(_, value) show_table_pool_icons = value mod_storage_save("show_table_pool_icons", show_table_pool_icons and "true" or "false") end) hook_mod_menu_checkbox("Player Table", table_mode ~= 0, function(_, value) if value then if table_mode == 0 then table_mode = 1 end else table_mode = 0 end mod_storage_save("table_mode", tostring(table_mode)) end) hook_mod_menu_checkbox("Player Table AFK Only", table_mode == 2, function(_, value) if value then table_mode = 2 elseif table_mode == 2 then table_mode = 1 end mod_storage_save("table_mode", tostring(table_mode)) end) end hook_event(HOOK_ON_HUD_RENDER, function() render_player_health_tags() render_sidebar_table() 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 local starKey = current_star_counter_storage_key() if toe_star_counter_loaded_key ~= starKey then toe_star_counter_loaded_key = starKey toe_star_counter_value = tonumber(mod_storage_load(starKey)) or 0 end if _G.PersonalStarCounter and _G.PersonalStarCounter.get_star_counter then local sessionStars = _G.PersonalStarCounter.get_star_counter() or toe_star_counter_value if sessionStars ~= toe_star_counter_value then toe_star_counter_value = sessionStars mod_storage_save(starKey, tostring(toe_star_counter_value)) end end update_sidebar_extra_y_offset() ps.toeLives = m.numLives or 0 ps.toeSpeed = math.floor(math.abs(m.forwardVel or 0) + 0.5) ps.toeStars = get_local_personal_star_total() 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 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_event(HOOK_ON_INTERACT, function(m, obj, intType, interacted) if _G.PersonalStarCounter and _G.PersonalStarCounter.get_star_counter then return end if m.playerIndex ~= 0 then return end if intType == INTERACT_STAR_OR_KEY and (m.numStars or 0) ~= toe_prev_num_stars then local starKey = current_star_counter_storage_key() if toe_star_counter_loaded_key ~= starKey then toe_star_counter_loaded_key = starKey toe_star_counter_value = tonumber(mod_storage_load(starKey)) or 0 end toe_star_counter_value = toe_star_counter_value + 1 mod_storage_save(starKey, tostring(toe_star_counter_value)) end end) hook_chat_command("toetagger-scale", "[tag|default] toggle ToeTagger nametag-linked scaling", toetagger_scale_command) hook_chat_command("toetagger-table", "[off|on|afk] toggle ToeTagger player table", toetagger_table_command) register_mod_menu()