sm64coopdx/mods/character-select-coop/hud.lua
Agent X 6092488d1c
Some checks failed
Build coop / build-linux (push) Has been cancelled
Build coop / build-steamos (push) Has been cancelled
Build coop / build-windows-opengl (push) Has been cancelled
Build coop / build-windows-directx (push) Has been cancelled
Build coop / build-macos-arm (push) Has been cancelled
Build coop / build-macos-intel (push) Has been cancelled
Update builtin mods
2025-12-31 22:35:17 -05:00

1175 lines
43 KiB
Lua

------------------------------------------------------
-- Custom HUD Rendering by Agent X and xLuigiGamerx --
------------------------------------------------------
if incompatibleClient then return 0 end
local og_hud_get_value = hud_get_value
local og_hud_set_value = hud_set_value
---@param text string
---@return number?, number?, number?, number?
local function convert_color(text)
if text:sub(2, 2) ~= "#" then
return
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
local r = tonumber("0x" .. rstring) or 255
local g = tonumber("0x" .. gstring) or 255
local b = tonumber("0x" .. bstring) or 255
return r, g, b, 255
end
---@param text string
---@param get_color boolean|nil
---@return string, string?, string?
local function remove_color(text, get_color)
local start = text:find("\\")
local next = 1
while (next ~= nil) and (start ~= nil) do
start = text:find("\\")
if start ~= nil then
next = text:find("\\", start + 1)
if next == nil then
next = text:len() + 1
end
if get_color then
local color = text:sub(start, next)
local render = text:sub(1, start - 1)
text = text:sub(next + 1)
return text, color, render
else
text = text:sub(1, start - 1) .. text:sub(next + 1)
end
end
end
return text
end
---@param text string
---@return string
local function generate_rainbow_text(text)
local preResult = {}
local postResult = {}
for match in text:gmatch(string.format(".", "(.)")) do
table.insert(preResult, match)
end
RED = djui_menu_get_rainbow_string_color(0)
GREEN = djui_menu_get_rainbow_string_color(1)
BLUE = djui_menu_get_rainbow_string_color(2)
YELLOW = djui_menu_get_rainbow_string_color(3)
local sRainbowColors = {
[0] = YELLOW,
[1] = RED,
[2] = GREEN,
[3] = BLUE,
}
for i = 1, #preResult do
rainbow = sRainbowColors[i % 4]
table.insert(postResult, rainbow .. preResult[i])
end
local result = table.concat(postResult, "")
return result
end
---@param x integer X Offset
---@param y integer Y Offset
---@param width integer Width
---@param height integer Height
---@param rectColor DjuiColor Rect Color
---@param borderColor DjuiColor Border Color
local function djui_hud_render_djui(x, y, width, height, rectColor, borderColor)
djui_hud_set_color(borderColor.r, borderColor.g, borderColor.b, borderColor.a)
-- Left
djui_hud_render_rect(x, y, 8, height)
-- Right
djui_hud_render_rect(x + width - 8, y, 8, height)
-- Up
djui_hud_render_rect(x + 8, y, width - 16, 8)
-- Down
djui_hud_render_rect(x + 8, y + height - 8, width - 16, 8)
-- Inner Rect
djui_hud_set_color(rectColor.r, rectColor.g, rectColor.b, rectColor.a)
djui_hud_render_rect(x + 8, y + 8, width - 16, height - 16)
end
---@param text string Text
---@param x integer X Offset
---@param y integer Y Offset
---@param scale integer Scale
---@param red integer Default text red value
---@param green integer Default text green value
---@param blue integer Default text blue value
---@param alpha integer Default text alpha value
local function djui_hud_print_text_with_color(text, x, y, scale, red, green, blue, alpha)
djui_hud_set_color(red or 255, green or 255, blue or 255, alpha or 255)
local space = 0
local color = ""
text, color, render = remove_color(text, true)
while render ~= nil do
local r, g, b, a = convert_color(color)
if alpha then a = alpha end
djui_hud_print_text(render, x + space, y, scale)
if r then djui_hud_set_color(r, g, b, a) end
space = space + djui_hud_measure_text(render) * scale
text, color, render = remove_color(text, true)
end
djui_hud_print_text(text, x + space, y, scale)
end
---@param text string Message
---@param headerYOffset integer A y offset for the header
---@param tr integer Text Red
---@param tg integer Text Green
---@param tb integer Text Blue
---@param a integer Text Alpha Value
---@param scale integer Scale
---@param x integer X Offset
---@param y integer Y Offset
---@param width integer Width
---@param height integer Height
---@param rectColor DjuiColor Rect Color
---@param borderColor DjuiColor Border Color
local function djui_hud_render_header_box(text, headerYOffset, tr, tg, tb, a, scale, x, y, width, height, rectColor, borderColor)
local headerFont = djui_menu_get_theme().panels.hudFontHeader and FONT_HUD or FONT_MENU
djui_hud_set_font(headerFont)
local hudFont = headerFont == FONT_HUD
local scaleMultiplier = hudFont and 4 * 0.7 or 1
local headerFontOffset = hudFont and 31.65 or 14.5
local defaultHeaderOffset = y + headerFontOffset + headerYOffset
djui_hud_render_djui(x, y, width, height, rectColor, borderColor)
djui_hud_print_text_with_color(text, x + width / 2 - (djui_hud_measure_text(remove_color(text, false)) * scale * scaleMultiplier) / 2, defaultHeaderOffset, scaleMultiplier, tr, tg, tb, a)
end
---------------------
-- Real HUD Stuffs --
---------------------
-- Updates the Chracter Select hud flags along with the vanilla hud flags
local hiddenList = HUD_DISPLAY_FLAG_LIVES | HUD_DISPLAY_FLAG_STAR_COUNT | HUD_DISPLAY_FLAG_CAMERA | HUD_DISPLAY_FLAG_POWER
local sCharSelectHudDisplayFlags = og_hud_get_value(HUD_DISPLAY_FLAGS) | hiddenList -- Initializes custom hud flags
function flags_update()
sCharSelectHudDisplayFlags = sCharSelectHudDisplayFlags & (og_hud_get_value(HUD_DISPLAY_FLAGS) | hiddenList) -- Updated the custom flags
og_hud_set_value(HUD_DISPLAY_FLAGS, og_hud_get_value(HUD_DISPLAY_FLAGS) & ~(hiddenList)) -- Update the vanilla flags
end
hook_event(HOOK_ON_HUD_RENDER_BEHIND, flags_update)
-- Modified Vanilla Functions --
--[[
These are `_G` on their own to replace vanilla functions
]]
---@param type HudDisplayValue
---@return integer
function _G.hud_get_value(type)
if type == HUD_DISPLAY_FLAGS then
return sCharSelectHudDisplayFlags | og_hud_get_value(HUD_DISPLAY_FLAGS)
else
return og_hud_get_value(type)
end
end
---@param type HudDisplayValue
---@param value integer
--- Sets a HUD display value
function _G.hud_set_value(type, value)
if type == HUD_DISPLAY_FLAGS then
sCharSelectHudDisplayFlags = value
og_hud_set_value(type, value & ~(hiddenList))
else
og_hud_set_value(type, value)
end
end
-- Old CS Hud Functions --
---Hides the specified custom hud element
---@param hudElement HUDDisplayFlag
function hud_hide_element(hudElement)
log_to_console_once("`charSelect.hud_hide_element` function is deprecated, please use 'hud_set_value'", CONSOLE_MESSAGE_WARNING)
hud_set_value(HUD_DISPLAY_FLAGS, hud_get_value(HUD_DISPLAY_FLAGS) & ~hudElement)
return true
end
---Shows the specified custom hud element
---@param hudElement HUDDisplayFlag
function hud_show_element(hudElement)
log_to_console_once("`hud_show_element` function is deprecated, please use 'hud_set_value'", CONSOLE_MESSAGE_WARNING)
hud_set_value(HUD_DISPLAY_FLAGS, hud_get_value(HUD_DISPLAY_FLAGS) | hudElement)
return true
end
---Gets the specified custom hud element's state
---@param hudElement HUDDisplayFlag
---@return boolean
function hud_get_element(hudElement)
log_to_console_once("`charSelect.hud_get_element` function is deprecated, please use 'hud_set_value'", CONSOLE_MESSAGE_WARNING)
return (hud_get_value(HUD_DISPLAY_FLAGS) & hudElement) ~= 0
end
local MATH_DIVIDE_16 = 1/16
local MATH_DIVIDE_32 = 1/32
local MATH_DIVIDE_64 = 1/64
local FONT_USER = FONT_NORMAL
---@param localIndex integer
---@return string
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function name_from_local_index(localIndex)
if localIndex == nil then localIndex = 0 end
local p = gCSPlayers[localIndex]
for i = 0, #characterTable do
if characterTable[i].saveName == p.saveName then
return characterTable[i][(p.currAlt and p.currAlt or 1)].name
end
end
return "???"
end
---@param localIndex integer
---@return table
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function color_from_local_index(localIndex)
if localIndex == nil then localIndex = 0 end
local p = gCSPlayers[localIndex]
for i = 0, #characterTable do
if characterTable[i].saveName == p.saveName then
return characterTable[i][(p.currAlt and p.currAlt or 1)].color
end
end
return {r = 255, g = 255, b = 255}
end
---@param localIndex integer
---@return TextureInfo|string
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them.
--- This function can return nil. if this is the case, render `djui_hud_print_text("?", x, y, 1)`
function life_icon_from_local_index(localIndex)
if localIndex == nil then localIndex = 0 end
local p = gCSPlayers[localIndex]
for i = 0, #characterTable do
local char = characterTable[i]
if char.saveName == p.saveName then
return char[(p.currAlt and p.currAlt or 1)].lifeIcon
end
end
return "?"
end
---@param localIndex integer
---@return TextureInfo
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function star_icon_from_local_index(localIndex)
if localIndex == nil then localIndex = 0 end
local p = gCSPlayers[localIndex]
for i = 0, #characterTable do
local char = characterTable[i]
if char.saveName == p.saveName then
return char[(p.currAlt and p.currAlt or 1)].starIcon
end
end
return gTextures.star
end
local TYPE_STRING = "string"
---@param localIndex integer
---@param x integer
---@param y integer
---@param scale integer
function render_life_icon_from_local_index(localIndex, x, y, scale)
if localIndex == nil then localIndex = 0 end
local lifeIcon = life_icon_from_local_index(localIndex)
local startFont = djui_hud_get_font()
local startColor = djui_hud_get_color()
if type(lifeIcon) == TYPE_STRING then
local color = color_from_local_index(localIndex)
djui_hud_set_font(FONT_RECOLOR_HUD)
djui_hud_set_color(color.r/startColor.r*255, color.g/startColor.g*255, color.b/startColor.b*255, startColor.a)
djui_hud_print_text(lifeIcon, x, y, scale)
-- Reset HUD Modifications
djui_hud_set_font(startFont)
djui_hud_set_color(startColor.r, startColor.g, startColor.b, startColor.a)
else
djui_hud_render_texture(lifeIcon, x, y, scale / (lifeIcon.width * MATH_DIVIDE_16), scale / (lifeIcon.height * MATH_DIVIDE_16))
end
end
---@param localIndex integer
---@param prevX integer
---@param prevY integer
---@param prevScale integer
---@param x integer
---@param y integer
---@param scale integer
function render_life_icon_from_local_index_interpolated(localIndex, prevX, prevY, prevScale, x, y, scale)
if localIndex == nil then localIndex = 0 end
local lifeIcon = life_icon_from_local_index(localIndex)
local startFont = djui_hud_get_font()
local startColor = djui_hud_get_color()
if type(lifeIcon) == TYPE_STRING then
local color = color_from_local_index(localIndex)
djui_hud_set_font(FONT_RECOLOR_HUD)
djui_hud_set_color(color.r/startColor.r*255, color.g/startColor.g*255, color.b/startColor.b*255, startColor.a)
djui_hud_print_text_interpolated(lifeIcon, prevX - prevScale/4, prevY - 10*prevScale - prevScale/4, prevScale, x - scale/4, y - 10*scale - scale/4, scale)
-- Reset HUD Modifications
djui_hud_set_font(startFont)
djui_hud_set_color(startColor.r, startColor.g, startColor.b, startColor.a)
else
djui_hud_render_texture_interpolated(lifeIcon, prevX, prevY, prevScale / (lifeIcon.width * MATH_DIVIDE_16), prevScale / (lifeIcon.height * MATH_DIVIDE_16), x, y, scale / (lifeIcon.width * MATH_DIVIDE_16), scale / (lifeIcon.height * MATH_DIVIDE_16))
end
end
---@param localIndex integer
---@param x integer
---@param y integer
---@param scale integer
function render_star_icon_from_local_index(localIndex, x, y, scale)
if localIndex == nil then localIndex = 0 end
local starIcon = star_icon_from_local_index(localIndex)
djui_hud_render_texture(starIcon, x, y, scale / (starIcon.width * MATH_DIVIDE_16), scale / (starIcon.height * MATH_DIVIDE_16))
end
---@param localIndex integer
---@param x integer
---@param y integer
---@param scale integer
function render_star_icon_from_local_index_interpolated(localIndex, prevX, prevY, prevScale, x, y, scale)
if localIndex == nil then localIndex = 0 end
local starIcon = star_icon_from_local_index(localIndex)
djui_hud_render_texture_interpolated(starIcon, prevX, prevY, prevScale / (starIcon.width * MATH_DIVIDE_16), prevScale / (starIcon.height * MATH_DIVIDE_16), x, y, scale / (starIcon.width * MATH_DIVIDE_16), scale / (starIcon.height * MATH_DIVIDE_16))
end
-- Health Meter --
local TEXT_DEFAULT_METER_PREFIX = "char_select_custom_meter_"
local TEX_DEFAULT_METER_LEFT = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."left")
local TEX_DEFAULT_METER_RIGHT = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."right")
local TEX_DEFAULT_METER_PIE1 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie1")
local TEX_DEFAULT_METER_PIE2 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie2")
local TEX_DEFAULT_METER_PIE3 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie3")
local TEX_DEFAULT_METER_PIE4 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie4")
local TEX_DEFAULT_METER_PIE5 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie5")
local TEX_DEFAULT_METER_PIE6 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie6")
local TEX_DEFAULT_METER_PIE7 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie7")
local TEX_DEFAULT_METER_PIE8 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie8")
defaultMeterInfo = {
label = {
left = TEX_DEFAULT_METER_LEFT,
right = TEX_DEFAULT_METER_RIGHT,
},
pie = {
TEX_DEFAULT_METER_PIE1,
TEX_DEFAULT_METER_PIE2,
TEX_DEFAULT_METER_PIE3,
TEX_DEFAULT_METER_PIE4,
TEX_DEFAULT_METER_PIE5,
TEX_DEFAULT_METER_PIE6,
TEX_DEFAULT_METER_PIE7,
TEX_DEFAULT_METER_PIE8,
}
}
local TEXT_DEFAULT_COURSE_PREFIX = "char_select_custom_course_"
local TEX_DEFAULT_COURSE_TOP = get_texture_info(TEXT_DEFAULT_COURSE_PREFIX.."top")
local TEX_DEFAULT_COURSE_BOTTOM = get_texture_info(TEXT_DEFAULT_COURSE_PREFIX.."bottom")
local defaultCourseInfo = {
top = TEX_DEFAULT_COURSE_TOP,
bottom = TEX_DEFAULT_COURSE_BOTTOM,
}
---@param localIndex integer
---@return table
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function health_meter_from_local_index(localIndex)
localIndex = localIndex or 0
local p = gCSPlayers[localIndex]
for i = 0, #characterTable do
local char = characterTable[i]
local healthMeter = (char[p.currAlt] and char[p.currAlt].healthMeter) or char[1].healthMeter
if char.saveName == p.saveName and healthMeter ~= nil then
return healthMeter
end
end
return defaultMeterInfo
end
---@param localIndex integer
---@param health integer
---@param x integer
---@param y integer
---@param scaleX integer
---@param scaleY integer
function render_health_meter_from_local_index(localIndex, health, x, y, scaleX, scaleY)
local color = djui_hud_get_color()
localIndex = localIndex or 0
local meter = health_meter_from_local_index(localIndex)
if type(meter) == "function" then
meter(localIndex, health, x, y, scaleX, scaleY, x, y, scaleX, scaleY)
else
health = health >> 8
local tex = meter.label.left
djui_hud_render_texture(tex, x, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
tex = meter.label.right
djui_hud_render_texture(tex, x + 31*scaleX*MATH_DIVIDE_64, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
if health > 0 then
tex = meter.pie[health]
djui_hud_render_texture(tex, x + 15*scaleX*MATH_DIVIDE_64, y + 16*scaleY*MATH_DIVIDE_64, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_32) * MATH_DIVIDE_64)
end
end
djui_hud_set_color(color.r, color.g, color.b, color.a)
end
---@param localIndex integer
---@param health integer
---@param prevX integer
---@param prevY integer
---@param prevScaleX integer
---@param prevScaleY integer
---@param x integer
---@param y integer
---@param scaleX integer
---@param scaleY integer
function render_health_meter_from_local_index_interpolated(localIndex, health, prevX, prevY, prevScaleX, prevScaleY, x, y, scaleX, scaleY)
local color = djui_hud_get_color()
localIndex = localIndex or 0
local meter = health_meter_from_local_index(localIndex)
if type(meter) == "function" then
meter(localIndex, health, prevX, prevY, prevScaleX, prevScaleY, x, y, scaleX, scaleY)
else
health = health >> 8
local tex = meter.label.left
djui_hud_render_texture_interpolated(tex, prevX, prevY, prevScaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, prevScaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64, x, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
tex = meter.label.right
djui_hud_render_texture_interpolated(tex, prevX + 31*prevScaleX*MATH_DIVIDE_64, prevY, prevScaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, prevScaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64, x + 31*scaleX*MATH_DIVIDE_64, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
if health > 0 then
tex = meter.pie[health]
djui_hud_render_texture_interpolated(tex, prevX + 15*prevScaleX*MATH_DIVIDE_64, prevY + 16*scaleY*MATH_DIVIDE_64, prevScaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, prevScaleY / (tex.height * MATH_DIVIDE_32) * MATH_DIVIDE_64, x + 15*scaleX*MATH_DIVIDE_64, y + 16*scaleY*MATH_DIVIDE_64, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_32) * MATH_DIVIDE_64)
end
end
djui_hud_set_color(color.r, color.g, color.b, color.a)
end
-- Force Default Health function to render CS' Meter
_G.hud_render_power_meter = function(health, x, y, scaleX, scaleY)
render_health_meter_from_local_index(0, health, x, y, scaleX, scaleY)
end
_G.hud_render_power_meter_interpolated = function(health, prevX, prevY, prevScaleX, prevScaleY, x, y, scaleX, scaleY)
render_health_meter_from_local_index_interpolated(0, health, prevX, prevY, prevScaleX, prevScaleY, x, y, scaleX, scaleY)
end
-- Health Meter Code
local POWER_METER_HIDDEN = 0
local POWER_METER_EMPHASIZED = 1
local POWER_METER_DEEMPHASIZING = 2
local POWER_METER_HIDING = 3
local POWER_METER_VISIBLE = 4
local sPowerMeterHUD = {
animation = POWER_METER_HIDDEN,
x = 140,
y = 166,
unused = 1.0,
};
local sPowerMeterVisibleTimer = 0
local sPowerMeterStoredHealth = 0
local function animate_power_meter_emphasized()
local hudDisplayFlags = hud_get_value(HUD_DISPLAY_FLAGS)
if ((hudDisplayFlags & HUD_DISPLAY_FLAG_EMPHASIZE_POWER) == 0) then
if (sPowerMeterVisibleTimer == 45.0) then
sPowerMeterHUD.animation = POWER_METER_DEEMPHASIZING;
end
else
sPowerMeterVisibleTimer = 0;
end
end
-- Power meter animation called after emphasized mode.
-- Moves power meter y pos speed until it's at 200 to be visible.
local function animate_power_meter_deemphasizing()
local speed = 5;
if (sPowerMeterHUD.y >= 181) then
speed = 3;
end
if (sPowerMeterHUD.y >= 191) then
speed = 2;
end
if (sPowerMeterHUD.y >= 196) then
speed = 1;
end
sPowerMeterHUD.y = sPowerMeterHUD.y + speed;
if (sPowerMeterHUD.y >= 201) then
sPowerMeterHUD.y = 200;
sPowerMeterPrevY = 200;
sPowerMeterHUD.animation = POWER_METER_VISIBLE;
end
end
-- Power meter animation called when there's 8 health segments.
-- Moves power meter y pos quickly until it's at 301 to be hidden.
local function animate_power_meter_hiding()
sPowerMeterHUD.y = sPowerMeterHUD.y + 20;
if (sPowerMeterHUD.y >= 301) then
sPowerMeterHUD.animation = POWER_METER_HIDDEN;
sPowerMeterVisibleTimer = 0;
end
end
-- Handles power meter actions depending of the health segments values.
local function handle_power_meter_actions(numHealthWedges)
local gPlayerCameraState = gMarioStates[0].statusForCamera
-- Show power meter if health is not full, less than 8
if (numHealthWedges < 8 and sPowerMeterStoredHealth == 8 and sPowerMeterHUD.animation == POWER_METER_HIDDEN) then
sPowerMeterHUD.animation = POWER_METER_EMPHASIZED;
sPowerMeterHUD.y = 166;
sPowerMeterPrevY = 166;
end
-- Show power meter if health is full, has 8
if (numHealthWedges == 8 and sPowerMeterStoredHealth == 7) then
sPowerMeterVisibleTimer = 0;
end
-- After health is full, hide power meter
if (numHealthWedges == 8 and sPowerMeterVisibleTimer > 45.0) then
sPowerMeterHUD.animation = POWER_METER_HIDING;
end
-- Update to match health value
sPowerMeterStoredHealth = numHealthWedges;
-- If Mario is swimming, keep power meter visible
if (gPlayerCameraState.action & ACT_FLAG_SWIMMING ~= 0) then
if (sPowerMeterHUD.animation == POWER_METER_HIDDEN
or sPowerMeterHUD.animation == POWER_METER_EMPHASIZED) then
sPowerMeterHUD.animation = POWER_METER_DEEMPHASIZING;
sPowerMeterHUD.y = 166;
sPowerMeterPrevY = 166;
end
sPowerMeterVisibleTimer = 0;
end
end
-- Renders the power meter that shows when Mario is in underwater
-- or has taken damage and has less than 8 health segments.
-- And calls a power meter animation function depending of the value defined.
local function render_hud_power_meter()
local shownHealthWedges = hud_get_value(HUD_DISPLAY_WEDGES);
sPowerMeterHUD.x = djui_hud_get_screen_width()*0.5 - 51
if (sPowerMeterHUD.animation ~= POWER_METER_HIDING) then
handle_power_meter_actions(shownHealthWedges);
end
if (sPowerMeterHUD.animation == POWER_METER_HIDDEN) then
return;
end
local powerMeterPrevY = sPowerMeterHUD.y
local anim = sPowerMeterHUD.animation
if anim == POWER_METER_EMPHASIZED then
animate_power_meter_emphasized();
elseif anim == POWER_METER_DEEMPHASIZING then
animate_power_meter_deemphasizing();
elseif anim == POWER_METER_HIDING then
animate_power_meter_hiding();
end
--render_dl_power_meter(shownHealthWedges);
render_health_meter_from_local_index_interpolated(0, gMarioStates[0].health, sPowerMeterHUD.x, 208 - powerMeterPrevY, 64, 64, sPowerMeterHUD.x, 208 - sPowerMeterHUD.y, 64, 64)
sPowerMeterVisibleTimer = sPowerMeterVisibleTimer + 1;
end
local function render_hud_act_select_course()
if currChar == 1 then
texture_override_reset("texture_menu_course_upper")
texture_override_reset("texture_menu_course_lower")
return
end
local textureTable = characterTable[currChar][characterTable[currChar].currAlt].courseTexture
if textureTable then -- sets health HUD to custom textures
if textureTable.top and textureTable.bottom then -- if left and right label textures exist. BOTH have to exist to be set!
texture_override_set("texture_menu_course_upper", textureTable.top)
texture_override_set("texture_menu_course_lower", textureTable.bottom)
end
else -- resets the course HUD
texture_override_set("texture_menu_course_upper", defaultCourseInfo.top)
texture_override_set("texture_menu_course_lower", defaultCourseInfo.bottom)
end
end
local function render_hud_mario_lives()
if (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_LIVES) == 0 then return end
local x = 22
local y = 15 -- SCREEN_HEIGHT - 209 - 16
render_life_icon_from_local_index(0, x, y, 1)
djui_hud_print_text("@", x + 16, y, 1)
djui_hud_print_text(tostring(hud_get_value(HUD_DISPLAY_LIVES)):gsub("-", "M"), x + 32, y, 1)
end
local function render_hud_stars()
if (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_STAR_COUNT) == 0 then return end
if hud_get_flash ~= nil then
-- prevent star count from flashing outside of castle
if gNetworkPlayers[0].currCourseNum ~= COURSE_NONE then hud_set_flash(0) end
if hud_get_flash() == 1 and (get_global_timer() & 0x08) == 0 then
return
end
end
local x = math.ceil(djui_hud_get_screen_width() - 76)
if x % 2 ~= 0 then
x = x - 1
end
local y = math.ceil(240 - 209 - 16)
local showX = 0
local hudDisplayStars = hud_get_value(HUD_DISPLAY_STARS)
if hudDisplayStars < 100 then showX = 1 end
render_star_icon_from_local_index(0, x, y, 1)
if showX == 1 then
djui_hud_print_text("@", x + 16, y, 1)
end
djui_hud_print_text(tostring(hudDisplayStars):gsub("-", "M"), (showX * 14) + x + 16, y, 1)
end
local function render_hud_camera_status()
if (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_CAMERA) == 0 or (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_CAMERA_AND_POWER) == 0 then return end
local x = djui_hud_get_screen_width() - 54
local y = 205
local cameraHudStatus = hud_get_value(HUD_DISPLAY_CAMERA_STATUS) -- doesn't show the status of freecam because it's currently bugged when we hide the hud camera -- TODO: keep nagging the coopers to "fix `hud_get_value(HUD_DISPLAY_CAMERA_STATUS)` when using freecam and hiding the hud camera" :trollface:
if cameraHudStatus == CAM_STATUS_NONE then return end
djui_hud_render_texture(gTextures.camera, x, y, 1, 1)
switch(cameraHudStatus & CAM_STATUS_MODE_GROUP, {
[CAM_STATUS_MARIO] = function()
render_life_icon_from_local_index(0, x + 16, y, 1)
end,
[CAM_STATUS_LAKITU] = function()
djui_hud_render_texture(gTextures.lakitu, x + 16, y, 1, 1)
end,
[CAM_STATUS_FIXED] = function()
djui_hud_render_texture(gTextures.no_camera, x + 16, y, 1, 1)
end
})
switch(cameraHudStatus & CAM_STATUS_C_MODE_GROUP, {
[CAM_STATUS_C_DOWN] = function()
djui_hud_render_texture(gTextures.arrow_down, x + 4, y + 16, 1, 1)
end,
[CAM_STATUS_C_UP] = function()
djui_hud_render_texture(gTextures.arrow_up, x + 4, y - 8, 1, 1)
end
})
end
-- Act Select Hud --
local STAR_SELECTOR_NOT_SELECTED = 0
local STAR_SELECTOR_SELECTED = 1
local STAR_SELECTOR_100_COINS = 2
local sVisibleStars = 0
local function render_act_select_hud()
local course = gNetworkPlayers[0].currCourseNum
if course == 0 or not obj_get_first_with_behavior_id(id_bhvActSelector) then return end
if sVisibleStars == 0 then
local starObj = obj_get_first_with_behavior_id(id_bhvActSelectorStarType)
while starObj do
if starObj.oStarSelectorType ~= STAR_SELECTOR_100_COINS then
sVisibleStars = sVisibleStars + 1
end
starObj = obj_get_next_with_same_behavior_id(starObj)
end
end
for a = 1, sVisibleStars do
local x = (139 - sVisibleStars * 17 + a * 34) + (djui_hud_get_screen_width() / 2) - 160 + 0.5
for j = 1, MAX_PLAYERS - 1 do -- 0 is not needed, you're never supposed to see yourself in act select
local np = gNetworkPlayers[j]
if np and np.connected and np.currCourseNum == course and np.currActNum == a then
render_life_icon_from_local_index(j, x - 4, 17, 1)
break
end
end
end
local selectedStar = obj_get_first_with_behavior_id(id_bhvActSelectorStarType)
local starsList = {}
while selectedStar do
table.insert(starsList, selectedStar)
selectedStar = obj_get_next_with_same_behavior_id(selectedStar)
end
if (sVisibleStars > 0) then
local playersInAct = 0
local sSelectedActIndex = 0
for i = 1, #starsList do
local curStar = starsList[i]
if curStar.oStarSelectorType == STAR_SELECTOR_SELECTED then
sSelectedActIndex = i - 1
end
end
local gCurrCourseNum = gNetworkPlayers[0].currCourseNum
for j = 1, MAX_PLAYERS - 1 do
local np = gNetworkPlayers[j]
if not (np or np.connected) then goto continue end
if (np.currCourseNum ~= gCurrCourseNum) then goto continue end
if (np.currActNum ~= sSelectedActIndex + 1) then goto continue end
playersInAct = playersInAct + 1
::continue::
end
if (playersInAct > 0) then
local message = ""
if (playersInAct == 1) then
message = message .. " Join "
else
message = message .. string.format("%d Players", playersInAct)
end
djui_hud_set_font(FONT_NORMAL)
djui_hud_set_color(100, 100, 100, 255)
local textScale = .5
local textWidth = djui_hud_measure_text(message) * textScale
local xPos = ((sSelectedActIndex + 1) * 34 - sVisibleStars * 17 + 139 - (textWidth / 2) + 4) + (djui_hud_get_screen_width() / 2) - 160 + 2
local yPos = -1
if message:find("Players") then
message = string.format("%d Player", playersInAct)
end
djui_hud_print_text(message, xPos, yPos, textScale) -- Not fully accurate because the font in act select is stretched in a way unachievable with normal fonts, will revisit in the future
end
end
end
---@param table table
---@return table
function zero_index_to_one_index(table)
local tableOne = {}
for i = 0, #table do
tableOne[i+1] = table[i]
end
return tableOne
end
local activeNonCSMods = {}
local nonCSModPosition = 0
local CSPacks = 0
for i = 0, #gActiveMods do
if gActiveMods[i].name == "Character Select" then
table.insert(activeNonCSMods, tostring(gActiveMods[i].name))
nonCSModPosition = #activeNonCSMods
elseif (remove_color(gActiveMods[i].name):sub(1, 4) ~= "[CS]" and gActiveMods[i].category ~= "cs") then
table.insert(activeNonCSMods, tostring(gActiveMods[i].name))
else
CSPacks = CSPacks + 1
end
end
activeNonCSMods[nonCSModPosition] = "Character Select (+"..CSPacks..")"
function render_playerlist_and_modlist()
-- DjuiTheme Data
local sDjuiTheme = djui_menu_get_theme()
local hudFont = sDjuiTheme.panels.hudFontHeader
local rectColor = sDjuiTheme.threePanels.rectColor
local borderColor = sDjuiTheme.threePanels.borderColor
-- PlayerList
playerListWidth = 710
playerListHeight = (16 * 32) + (16 - 1) * 4 + (32 + 16) + 32 + 32
local x = djui_hud_get_screen_width()/2 - playerListWidth/2
local y = djui_hud_get_screen_height()/2 - playerListHeight/2
listMargins = 16
local playerList = {}
gNetworkPlayersOne = zero_index_to_one_index(gNetworkPlayers)
for index, player in pairs(gNetworkPlayersOne) do
if player.connected then
playerList[#playerList + 1] = player
end
end
local playersString = hudFont and djui_language_get("PLAYER_LIST", "PLAYERS") or generate_rainbow_text(djui_language_get("PLAYER_LIST", "PLAYERS"))
djui_hud_render_header_box(playersString, 0, 0xff, 0xff, 0xff, 0xff, 1, x, y, playerListWidth, playerListHeight, rectColor, borderColor)
djui_hud_set_font(FONT_USER)
for i = 1, #playerList do
np = playerList[i]
local v = (i % 2) == 0 and 16 or 32
djui_hud_set_color(v, v, v, 128)
local entryWidth = playerListWidth - ((8 + listMargins) * 2)
local entryHeight = 32
local entryX = x + 8 + listMargins
local entryY = y + 88 + ((entryHeight + 4) * (i-1))
djui_hud_render_rect(entryX, entryY, entryWidth, entryHeight)
local capColor = network_player_get_override_palette_color(np, CAP)
playerNameColor = {
r = 127 + capColor.r/2,
g = 127 + capColor.g/2,
b = 127 + capColor.b/2
}
djui_hud_set_color(255, 255, 255, 255)
render_life_icon_from_local_index(np.localIndex, entryX, entryY, 2)
djui_hud_print_text_with_color(np.name, entryX + 40, entryY, 1, playerNameColor.r, playerNameColor.g, playerNameColor.b, 255)
local levelName = np.overrideLocation ~= "" and np.overrideLocation or get_level_name(np.currCourseNum, np.currLevelNum, np.currAreaIndex)
if levelName then
djui_hud_print_text_with_color(levelName, ((entryX + entryWidth) - djui_hud_measure_text((string.gsub(levelName, "\\(.-)\\", "")))) - 126, entryY, 1, 0xdc, 0xdc, 0xdc, 255)
end
if np.currActNum then
currActNum = np.currActNum == 99 and "Done" or np.currActNum ~= 0 and "# "..tostring(np.currActNum) or ""
printedcurrActNum = currActNum
djui_hud_print_text_with_color(printedcurrActNum, entryX + entryWidth - djui_hud_measure_text(printedcurrActNum) - 18, entryY, 1, 0xdc, 0xdc, 0xdc, 255)
end
if np.description then
djui_hud_print_text_with_color(np.description, (entryX + 278) - (djui_hud_measure_text((string.gsub(np.description, "\\(.-)\\", "")))/2), entryY, 1, np.descriptionR, np.descriptionG, np.descriptionB, np.descriptionA)
end
end
-- ModList
local modListWidth = 280
local modListHeight = (#activeNonCSMods * 32) + (#activeNonCSMods - 1) * 4 + (32 + 16) + 32 + 32
local mX = djui_hud_get_screen_width()/2 + 363
local mY = djui_hud_get_screen_height()/2 - modListHeight/2
local modsString = hudFont and djui_language_get("MODLIST", "MODS") or generate_rainbow_text(djui_language_get("MODLIST", "MODS"))
djui_hud_render_header_box(modsString, 0, 0xff, 0xff, 0xff, 0xff, 1, mX, mY, modListWidth, modListHeight, rectColor, borderColor)
djui_hud_set_font(FONT_USER)
for i = 0, #activeNonCSMods - 1 do
--local i = i - packFilter
v = (i % 2) ~= 0 and 16 or 32
djui_hud_set_color(v, v, v, 128)
local entryWidth = modListWidth - ((8 + listMargins) * 2)
local entryHeight = 32
local entryX = mX + 8 + listMargins
local entryY = mY + 124 + 0 + ((entryHeight + 4) * (i - 1))
djui_hud_render_rect(entryX, entryY, entryWidth, entryHeight)
local modName = activeNonCSMods[i + 1]
local stringSubCount = 23
local inColor = false
for i = 1, #modName do
if modName:sub(i, i) == "\\" then
inColor = not inColor
end
if inColor then
stringSubCount = stringSubCount + 1
end
end
djui_hud_print_text_with_color(modName:sub(1, stringSubCount), entryX, entryY, 1, 0xdc, 0xdc, 0xdc, 255)
end
end
-- Yes the ending stuffs is hardcoded, no there's not much of a better way to do it
local DIALOG_ENDING_REPLACE_1 = "$CHARNAME!"
local DIALOG_ENDING_REPLACE_2 = "Thank you $CHARNAME!"
local DIALOG_ENDING_REPLACE_3 = "...for $CHARNAME..."
local END_PEACH_CUTSCENE_DIALOG_1 = 6
local END_PEACH_CUTSCENE_DIALOG_2 = 7
local END_PEACH_CUTSCENE_DIALOG_3 = 10
local END_PEACH_CUTSCENE_RUN_TO_CASTLE = 11
local fadeLength = 5
local function render_hud_ending_dialog()
djui_hud_set_font(FONT_TINY)
local m = gMarioStates[0]
if m.action ~= ACT_END_PEACH_CUTSCENE then return end
local width = djui_hud_get_screen_width()
local charName = characterTable[currChar].nickname
local string = ""
local startTime = 0
local endTime = 0
if m.actionArg == END_PEACH_CUTSCENE_DIALOG_1 and m.actionTimer >= 230 and m.actionTimer <= 275 then
string = DIALOG_ENDING_REPLACE_1
startTime = 230
endTime = 275
elseif m.actionArg == END_PEACH_CUTSCENE_DIALOG_2 and m.actionTimer >= 75 and m.actionTimer <= 130 then
string = DIALOG_ENDING_REPLACE_2
startTime = 75
endTime = 130
elseif m.actionArg == END_PEACH_CUTSCENE_DIALOG_3 and m.actionTimer >= 130 and m.actionTimer <= 195 then
string = DIALOG_ENDING_REPLACE_3
startTime = 130
endTime = 195
elseif m.actionArg == END_PEACH_CUTSCENE_RUN_TO_CASTLE and m.actionTimer >= 95 and m.actionTimer <= 150 then
string = DIALOG_ENDING_REPLACE_1
startTime = 95
endTime = 150
end
if string ~= "" then
djui_hud_set_color(0, 0, 0, 255)
djui_hud_render_rect(0, 210, width, 30)
string = string:gsub("$CHARNAME", charName)
local opacity = 255
local startToTimer = m.actionTimer - startTime
local endToTimer = endTime - m.actionTimer
if startToTimer >= 0 then
opacity = math.min(startToTimer, fadeLength)/fadeLength * 255
end
if endToTimer >= 0 and startToTimer >= fadeLength then
opacity = math.min(endToTimer, fadeLength)/fadeLength * 255
end
djui_hud_set_color(255, 255, 255, opacity)
local x = width*0.5 - djui_hud_measure_text(string)*0.5
djui_hud_print_text(string, x, 210, 1)
end
end
-- Nametags Powermeter Hud --
local nametagsActionBlacklist = {
[ACT_START_CROUCHING] = true,
[ACT_CROUCHING] = true,
[ACT_STOP_CROUCHING] = true,
[ACT_START_CRAWLING] = true,
[ACT_CRAWLING] = true,
[ACT_STOP_CRAWLING] = true,
[ACT_IN_CANNON] = true,
[ACT_DISAPPEARED] = true,
}
local FADE_SCALE = 4.0
--- @class StateExtras
--- @field public prevPos Vec3f
--- @field public prevScale number
--- @field public inited boolean
--- @type StateExtras[]
local sStateExtras = {}
for i = 0, MAX_PLAYERS - 1 do
sStateExtras[i] = {}
local _ENV = setmetatable(sStateExtras[i], { __index = _G })
prevPos = gVec3fZero()
prevScale = 0.0
inited = false
end
local function render_nametag_powermeter()
local sGlobalTimer = get_global_timer()
for i = 1, MAX_PLAYERS - 1 do
local m = gMarioStates[i]
if is_player_active(m) == 0 then goto continue end
local np = gNetworkPlayers[i]
if not np.currAreaSyncValid then goto continue end
if nametagsActionBlacklist[m.action] then goto continue end
if (m.marioBodyState.mirrorMario or m.marioBodyState.updateHeadPosTime ~= sGlobalTimer) then goto continue end
local pos = gVec3fZero()
local out = gVec3fZero()
vec3f_copy(pos, m.marioBodyState.headPos)
pos.y = pos.y + 100
if not djui_hud_world_pos_to_screen_pos(pos, out) then
goto continue
end
local scale = -300 / out.z * djui_hud_get_fov_coeff()
out.y = out.y - 16 * scale
local alpha = (math.min(np.fadeOpacity << 3, 255)) * math.clamp(FADE_SCALE - scale, 0.0, 1.0)
local e = sStateExtras[i]
if not e.inited then
vec3f_copy(e.prevPos, out)
e.prevScale = scale
e.inited = true
end
if gNametagsSettings.showHealth then
djui_hud_set_color(255, 255, 255, alpha)
local healthScale = 90 * scale
local prevHealthScale = 90 * e.prevScale
render_health_meter_from_local_index_interpolated(i, m.health,
e.prevPos.x - (prevHealthScale * 0.5), e.prevPos.y - 72 * scale, prevHealthScale, prevHealthScale,
out.x - ( healthScale * 0.5), out.y - 72 * scale, healthScale, healthScale
)
end
vec3f_copy(e.prevPos, out)
e.prevScale = scale
::continue::
end
end
local function nametags_reset()
for i = 0, MAX_PLAYERS - 1 do
sStateExtras[i].inited = false
end
end
local function on_level_init()
nametags_reset()
end
hook_event(HOOK_ON_LEVEL_INIT, on_level_init)
local sServerSettings = gServerSettings
local sNametagsSettings = gNametagsSettings
_G.gServerSettings = {
enablePlayerList = sServerSettings.enablePlayerList,
enablePlayersInLevelDisplay = sServerSettings.enablePlayersInLevelDisplay,
}
_G.gNametagsSettings = {
showHealth = sNametagsSettings.showHealth,
}
local _ServerSettingsMetaTable = {
__index = function (t, k)
return rawget(t, k) or sServerSettings[k]
end,
__newindex = function (_, k, v)
sServerSettings[k] = v
end,
}
local _NametagsSettingsMetaTable = {
__index = function (t, k)
return rawget(t, k) or sNametagsSettings[k]
end,
__newindex = function (_, k, v)
sNametagsSettings[k] = v
end,
}
setmetatable(gServerSettings, _ServerSettingsMetaTable)
setmetatable(gNametagsSettings, _NametagsSettingsMetaTable)
function nametags_settings()
if sNametagsSettings.showHealth then
gNametagsSettings.showHealth = not gNametagsSettings.showHealth
end
sNametagsSettings.showHealth = false
end
hook_event(HOOK_ON_NAMETAGS_RENDER, nametags_settings)
local function on_hud_render_behind()
FONT_USER = djui_menu_get_font()
djui_hud_set_resolution(RESOLUTION_N64)
djui_hud_set_font(FONT_HUD)
render_nametag_powermeter() -- Render before setting the color, it sets its own
djui_hud_set_color(255, 255, 255, 255)
if gNetworkPlayers[0].currActNum == 99 or gMarioStates[0].action == ACT_INTRO_CUTSCENE or hud_is_hidden() then return end
sServerSettings.enablePlayersInLevelDisplay = 0 -- Disables the original playersInLevel Display
local enablePlayersInLevelDisplay = gServerSettings.enablePlayersInLevelDisplay
if not obj_get_first_with_behavior_id(id_bhvActSelector) then
render_hud_mario_lives()
render_hud_stars()
render_hud_camera_status()
render_hud_power_meter()
sVisibleStars = 0
else
if enablePlayersInLevelDisplay then
render_act_select_hud()
end
render_hud_act_select_course()
end
end
local function on_hud_render()
djui_hud_set_resolution(RESOLUTION_N64)
djui_hud_set_font(FONT_HUD)
djui_hud_set_color(255, 255, 255, 255)
if gNetworkPlayers[0].currActNum == 99 then
render_hud_ending_dialog()
end
sServerSettings.enablePlayerList = 0 -- Disables the original playerlist and modlist
local enablePlayerList = gServerSettings.enablePlayerList
djui_hud_set_resolution(RESOLUTION_DJUI)
if djui_attempting_to_open_playerlist() and enablePlayerList then
render_playerlist_and_modlist()
end
end
hook_event(HOOK_ON_HUD_RENDER_BEHIND, on_hud_render_behind)
hook_event(HOOK_ON_HUD_RENDER, on_hud_render)