Show Ultimate Menu information in companion panel

This commit is contained in:
Debugger
2026-09-14 19:06:36 -04:00
parent a7120f68e7
commit 624c607185
7 changed files with 285 additions and 54 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Ultimate Menu for Nenyoo (GTA V Legacy) # Ultimate Menu for Nenyoo (GTA V Legacy)
This edition runs the YimMenu Legacy script through a Nenyoo API adapter. The bundled `lib/Ultimate_Menu_Legacy.lua` is an unchanged copy of `YimMenu/Ultimate_Menu_Legacy.lua`; the original menu hierarchy and action callbacks remain in one source file. YimMenu tabs, ImGui groups, controls, and popups appear as nested Nenyoo menu lists. Controls that depend on game state are added when their conditions become true. This edition runs the YimMenu Legacy script through a Nenyoo API adapter. The bundled `lib/Ultimate_Menu_Legacy.lua` is an unchanged copy of `YimMenu/Ultimate_Menu_Legacy.lua`; the original menu hierarchy and action callbacks remain in one source file. YimMenu tabs, controls, and popups appear as nested Nenyoo menu lists. Informational text and live status appear in a companion panel only while their submenu is open; hover the panel and use the mouse wheel for longer instructions. Controls that depend on game state are added when their conditions become true.
Copy the contents of this folder to `%LOCALAPPDATA%\Nenyoo\Plus\Scripts\User\UltimateMenu\`, then select `Ultimate_Menu.lua` from Nenyoo's Scripts menu to load its page. Keep the `lib` folder beside the entry script. The script targets GTA V Legacy build 3889 and uses the online menus only after joining GTA Online. Copy the contents of this folder to `%LOCALAPPDATA%\Nenyoo\Plus\Scripts\User\UltimateMenu\`, then select `Ultimate_Menu.lua` from Nenyoo's Scripts menu to load its page. Keep the `lib` folder beside the entry script. The script targets GTA V Legacy build 3889 and uses the online menus only after joining GTA Online.
+2 -2
View File
@@ -4,8 +4,8 @@ Use GTA V **Legacy build 3889** and the Nenyoo DLL built with the `__ny_execute_
## First run ## First run
1. In Story Mode, open the page. Confirm `Ultimate Menu 1.73 By L7NEG` appears with `Story Mode`, `Self Menu`, `Recovery Menu`, `Online Services Menu`, `Collectibles`, `Events Menu`, `Gun Van Menu`, `YimResupplier`, and `Heists Data Editor Menu`. Open and back out of several nested lists. Record any missing row, empty page, freeze, or Lua error. 1. In Story Mode, open the page. Confirm `Ultimate Menu 1.73 By L7NEG` appears with `Story Mode`, `Self Menu`, `Recovery Menu`, `Online Services Menu`, `Collectibles`, `Events Menu`, `Gun Van Menu`, `YimResupplier`, and `Heists Data Editor Menu`. Open and back out of several nested lists. Pages with explanations should show a companion information panel; the panel should close when leaving the page. Hover and scroll the panel to read long text. Record any missing row, empty page, freeze, or Lua error.
2. In `Story Mode`, record the current character's cash, use `Current Character Cash` to set a distinct value, and confirm the game shows it. Restore the original value. Online-only pages should show a single, non-selectable `Unavailable in Single Player.` notice rather than actionable controls. 2. In `Story Mode`, record the current character's cash, use `Current Character Cash` to set a distinct value, and confirm the game shows it. Restore the original value. Online-only pages should show `Unavailable in Single Player.` in the information panel rather than actionable controls.
3. Join a GTA Online session and reopen the menu. Verify changing text, toggle, slider, combo, and button controls update the displayed value or game state. Leave the session and confirm Story Mode controls still work. 3. Join a GTA Online session and reopen the menu. Verify changing text, toggle, slider, combo, and button controls update the displayed value or game state. Leave the session and confirm Story Mode controls still work.
4. In `Gun Van Menu > Weapons > Edit Weapons`, search for `Pistol`, select its row, and use `Set Weapon` on a recorded slot. Check that the displayed slot and the in-game Gun Van show Pistol. Restore the original weapon. Test `Remove Weapon` and `Close`, then reopen the editor. 4. In `Gun Van Menu > Weapons > Edit Weapons`, search for `Pistol`, select its row, and use `Set Weapon` on a recorded slot. Check that the displayed slot and the in-game Gun Van show Pistol. Restore the original weapon. Test `Remove Weapon` and `Close`, then reopen the editor.
5. For a business you own, open `YimResupplier` and verify its controls appear after ownership is detected. Test a teleport to a known property and confirm the landing position. Repeat after changing sessions. 5. For a business you own, open `YimResupplier` and verify its controls appear after ownership is detected. Test a teleport to a known property and confirm the landing position. Repeat after changing sessions.
+1
View File
@@ -36,5 +36,6 @@ end
local compatibility = run(directory .. "lib\\yim_compat.lua") local compatibility = run(directory .. "lib\\yim_compat.lua")
run(directory .. "lib\\Ultimate_Menu_Legacy.lua") run(directory .. "lib\\Ultimate_Menu_Legacy.lua")
run(directory .. "lib\\info_panel.lua")(compatibility)
compatibility.materialize_imgui() compatibility.materialize_imgui()
return compatibility return compatibility
+138
View File
@@ -0,0 +1,138 @@
-- Companion information panel for the Nenyoo edition of Ultimate Menu.
-- The menu adapter publishes text on the script thread. Draw only the
-- published strings here; overlay.on_draw runs on Nenyoo's render thread.
return function(compat)
local scroll_by_page = {}
local last_page
local function clamp(value, low, high)
return math.max(low, math.min(high, value))
end
local function wrap_paragraph(value, max_width, output, heading, color)
local line = ""
for word in value:gmatch("%S+") do
local candidate = line == "" and word or line .. " " .. word
if line ~= "" and text.width(font.small, candidate) > max_width then
output[#output + 1] = {text = line, heading = heading, color = color}
line = word
else
line = candidate
end
end
if line ~= "" then output[#output + 1] = {text = line, heading = heading, color = color} end
end
local function make_lines(rows, max_width)
local lines = {}
for _, row in ipairs(rows) do
local value = tostring(row.text or ""):gsub("\r", "")
if value == "" then
lines[#lines + 1] = {gap = 8}
else
local compact = value:gsub("%s+", " "):match("^%s*(.-)%s*$")
local heading = #compact <= 38 and (compact:sub(-1) == ":" or compact == "Preps")
for paragraph in (value .. "\n"):gmatch("(.-)\n") do
if paragraph:match("%S") then wrap_paragraph(paragraph, max_width, lines, heading, row.color)
else lines[#lines + 1] = {gap = 8} end
end
lines[#lines + 1] = {gap = heading and 7 or 11}
end
if row.tooltip then
wrap_paragraph(row.tooltip:gsub("%s+", " "), max_width, lines, false, nil)
lines[#lines + 1] = {gap = 11}
end
end
while #lines > 0 and lines[#lines].gap do lines[#lines] = nil end
return lines
end
overlay.on_draw("ultimate_menu_information", function()
if not menu.is_visible() then return end
local id = menu.page_id()
local info = compat.info_for_page(id)
if not info or #info.rows == 0 then return end
local sw, sh = ctx.screen_w(), ctx.screen_h()
local bx, by, bw = menu.bounds()
if not bw or bw <= 0 then return end
local gap, pad, header_h = 14, 14, 36
local width = math.min(420, math.max(280, sw * 0.30))
local side = bx + bw + gap + width <= sw - 8 or bx - gap - width >= 8
if not side then width = math.min(600, sw - 16) end
local x
if side and bx + bw + gap + width <= sw - 8 then x = bx + bw + gap
elseif side then x = bx - gap - width
else x = clamp(bx + (bw - width) * 0.5, 8, sw - width - 8) end
local lines = make_lines(info.rows, width - pad * 2)
if #lines == 0 then return end
local line_h = text.height(font.small) + 4
local content_h = 0
for _, line in ipairs(lines) do content_h = content_h + (line.gap or line_h) end
local desired_h = header_h + pad * 2 + content_h
local height = math.min(desired_h, sh - 16, 650)
local y = clamp(by, 8, sh - height - 8)
if not side then
local _, content_y, _, content_height = menu.content_rect()
local below = content_y + content_height + 56
local space_below = sh - below - 8
if content_height > 0 and space_below >= 170 then
y = below
height = math.min(height, space_below)
elseif by >= 180 then
height = math.min(height, by - 16)
y = by - height - 8
end
end
local viewport_h = height - header_h - pad * 2
if viewport_h <= line_h then return end
if last_page ~= id then scroll_by_page[id] = 0; last_page = id end
local max_scroll = math.max(0, content_h - viewport_h)
local scroll = clamp(scroll_by_page[id] or 0, 0, max_scroll)
local mx, my = input.mouse_x(), input.mouse_y()
if mx >= x and mx <= x + width and my >= y and my <= y + height then
scroll = clamp(scroll - input.mouse_wheel() * line_h * 3, 0, max_scroll)
end
scroll_by_page[id] = scroll
local ar, ag, ab = theme.accent()
draw.rect(x, y, x + width, y + height, 12, 15, 22, 232, 7)
draw.rect(x, y, x + width, y + header_h, 24, 32, 44, 245, 7)
draw.rect(x, y + header_h - 2, x + width, y + header_h, ar, ag, ab, 255)
draw.rect_outline(x, y, x + width, y + height, ar, ag, ab, 125, 7)
text.draw_ellipsis(font.item, x + pad, y + (header_h - text.height(font.item)) * 0.5,
240, 245, 250, 255, info.title, width - pad * 2)
local top = y + header_h + pad
draw.push_clip(x + pad, top, x + width - pad, top + viewport_h)
local cursor = top - scroll
for _, line in ipairs(lines) do
if line.gap then cursor = cursor + line.gap
else
if cursor + line_h >= top and cursor <= top + viewport_h then
local r, g, b = 220, 228, 238
if line.heading then r, g, b = ar, ag, ab end
if line.color then
local function channel(value)
value = value or 1
return clamp(math.floor((value <= 1 and value * 255 or value) + 0.5), 0, 255)
end
r, g, b = channel(line.color[1]), channel(line.color[2]), channel(line.color[3])
end
text.draw(font.small, x + pad, cursor, r, g, b, 245, line.text)
end
cursor = cursor + line_h
end
end
draw.pop_clip()
if max_scroll > 0 then
local track_top, track_height = top, viewport_h
local thumb_h = math.max(22, track_height * viewport_h / content_h)
local thumb_y = track_top + (track_height - thumb_h) * scroll / max_scroll
draw.rect(x + width - 5, thumb_y, x + width - 2, thumb_y + thumb_h,
ar, ag, ab, 190, 2)
end
end)
end
+72 -16
View File
@@ -5,7 +5,7 @@ local threads_created = 0
local thread_callbacks = {} local thread_callbacks = {}
local thread_origins = {} local thread_origins = {}
local menu_back_calls = 0 local menu_back_calls = 0
local notices, deleted_notices = {}, {} local list_refs, current_page_id, panel_draw, drawn_text, menu_visible = {}, 0, nil, {}, true
local memory_values = {} local memory_values = {}
local last_pointer local last_pointer
local function ref() local function ref()
@@ -17,7 +17,9 @@ menu = {
list = function(_, label) list = function(_, label)
counters.list = counters.list + 1 counters.list = counters.list + 1
list_names[label] = true list_names[label] = true
return ref() local result = ref()
list_refs[label] = result
return result
end, end,
action = function(_, label, _, _, callback) action = function(_, label, _, _, callback)
counters.action = counters.action + 1 counters.action = counters.action + 1
@@ -34,14 +36,11 @@ menu = {
return ref() return ref()
end, end,
readonly = function() counters.text = counters.text + 1; return ref() end, readonly = function() counters.text = counters.text + 1; return ref() end,
divider = function(_, label) divider = function() return ref() end,
local result = ref() is_visible = function() return menu_visible end,
if label == "Unavailable in Single Player." or label == "Waiting for game..." then page_id = function() return current_page_id end,
notices[result.id] = label bounds = function() return 100, 80, 420 end,
end content_rect = function() return 100, 250, 420, 440 end,
return result
end,
delete = function(value) deleted_notices[value.id] = true end,
get_value = function() return false end, get_value = function() return false end,
set_value = function() end, set_value = function() end,
get_menu_name = function() return "" end, get_menu_name = function() return "" end,
@@ -57,7 +56,10 @@ __smenu = {
node_in_viewport = function() return true end, node_in_viewport = function() return true end,
} }
util = { util = {
joaat = function(name) return #tostring(name) end, joaat = function(name)
local page = tostring(name):match("^Stand#(%d+)$")
return page and (100000 + tonumber(page)) or #tostring(name)
end,
toast = function() end, toast = function() end,
yield = function() yield = function()
local _, is_main = coroutine.running() local _, is_main = coroutine.running()
@@ -70,6 +72,23 @@ util = {
return {} return {}
end, end,
} }
overlay = {on_draw = function(name, callback)
assert(name == "ultimate_menu_information", "Unexpected overlay registration")
panel_draw = callback
end}
font = {small = 1, item = 2}
ctx = {screen_w = function() return 1280 end, screen_h = function() return 900 end}
input = {mouse_x = function() return 0 end, mouse_y = function() return 0 end,
mouse_wheel = function() return 0 end}
theme = {accent = function() return 55, 145, 245 end}
text = {
width = function(_, value) return #tostring(value) * 8 end,
height = function() return 16 end,
draw = function(_, _, _, _, _, _, _, value) drawn_text[#drawn_text + 1] = value end,
draw_ellipsis = function(_, _, _, _, _, _, _, value) drawn_text[#drawn_text + 1] = value end,
}
draw = {rect = function() end, rect_outline = function() end,
push_clip = function() end, pop_clip = function() end}
__ny_execute_as_script = function(_, callback) callback(); return true end __ny_execute_as_script = function(_, callback) callback(); return true end
__ny_request_script_host = function() return true end __ny_request_script_host = function() return true end
memory = { memory = {
@@ -187,22 +206,59 @@ if not os.getenv("UM_START_OFFLINE") then
assert(counters.action >= 940, "Missing initial actions: " .. counters.action) assert(counters.action >= 940, "Missing initial actions: " .. counters.action)
end end
assert(#compat.imgui_sections >= 43, "Missing ImGui sections: " .. #compat.imgui_sections) assert(#compat.imgui_sections >= 43, "Missing ImGui sections: " .. #compat.imgui_sections)
assert(panel_draw, "Information overlay did not register")
if os.getenv("UM_START_OFFLINE") then if os.getenv("UM_START_OFFLINE") then
local viewport_thread = coroutine.create(thread_callbacks[#thread_callbacks]) local viewport_thread = coroutine.create(thread_callbacks[#thread_callbacks])
local ok, message = coroutine.resume(viewport_thread) local ok, message = coroutine.resume(viewport_thread)
assert(ok, message) assert(ok, message)
assert(next(notices), "Single Player warning did not appear as a non-selectable row") local recovery_id = util.joaat("Stand#" .. list_refs["Set Rank menu"].id)
for _, label in pairs(notices) do local offline_info = compat.info_for_page(recovery_id)
assert(not label:find("\n", 1, true), "Single Player warning spans multiple menu rows") local warning = false
for _, row in ipairs(offline_info.rows) do
if row.text:find("Unavailable in Single Player", 1, true) then warning = true end
end end
assert(warning, "Single Player warning did not reach the information panel")
session_started = true session_started = true
ok, message = coroutine.resume(viewport_thread) ok, message = coroutine.resume(viewport_thread)
assert(ok, message) assert(ok, message)
for id in pairs(notices) do for _, row in ipairs(compat.info_for_page(recovery_id).rows) do
assert(deleted_notices[id], "Single Player warning remained after joining online") assert(not row.text:find("Unavailable in Single Player", 1, true),
"Single Player warning remained after joining online")
end end
assert(counters.action >= 940, "Online controls did not appear after joining: " .. counters.action) assert(counters.action >= 940, "Online controls did not appear after joining: " .. counters.action)
end end
local apartment_id = util.joaat("Stand#" .. list_refs["Apartment Heist "].id)
local apartment_info = compat.info_for_page(apartment_id)
assert(apartment_info and #apartment_info.rows >= 5, "Apartment Heist instructions did not reach the panel")
assert(counters.text == 0, "Informational text still occupies selectable menu rows")
local kortz_id = util.joaat("Stand#" .. list_refs["Kortz Center Heist"].id)
local kortz_info = compat.info_for_page(kortz_id)
local colored_status, expanded_help = false, false
for _, row in ipairs(kortz_info.rows) do
if row.color then colored_status = true end
if row.text:find("Select the main painting", 1, true) then expanded_help = true end
end
assert(colored_status, "Colored YimMenu status text lost its color")
assert(expanded_help, "YimMenu help marker was not expanded in the panel")
current_page_id = apartment_id
panel_draw()
local found_instruction = false
local found_wrapped_instruction = false
for _, value in ipairs(drawn_text) do
if value:find("Pay for the preparation", 1, true) then found_instruction = true end
if value:find("change the session", 1, true) then found_wrapped_instruction = true end
end
assert(found_instruction, "Long heist instructions were not drawn in the information panel")
assert(found_wrapped_instruction, "Long heist instructions were cut off instead of wrapped")
local drawn_count = #drawn_text
current_page_id = 0
panel_draw()
assert(#drawn_text == drawn_count, "Information panel appeared outside its submenu")
current_page_id = apartment_id
menu_visible = false
panel_draw()
assert(#drawn_text == drawn_count, "Information panel remained after the menu closed")
menu_visible = true
for _, name in ipairs({"Self Menu", "Story Mode", "Recovery Menu", "Online Services Menu", for _, name in ipairs({"Self Menu", "Story Mode", "Recovery Menu", "Online Services Menu",
"Collectibles", "Events Menu", "YimResupplier", "Heists Data Editor Menu", "Kortz Center Heist"}) do "Collectibles", "Events Menu", "YimResupplier", "Heists Data Editor Menu", "Kortz Center Heist"}) do
assert(list_names[name], "Missing original menu section: " .. name) assert(list_names[name], "Missing original menu section: " .. name)
+70 -34
View File
@@ -254,7 +254,16 @@ end
local tab_methods = {} local tab_methods = {}
local imgui_sections = {} local imgui_sections = {}
local info_pages = {}
local tab_mt = {__index = tab_methods} local tab_mt = {__index = tab_methods}
local function page_id(ref)
return util.joaat("Stand#" .. tostring(type(ref) == "table" and ref.id or ref))
end
local function info_page(ref, title)
local id = page_id(ref)
if not info_pages[id] then info_pages[id] = {title = title or "Information", static = {}} end
return info_pages[id], id
end
local function text_input(parent, label, help, callback, value) local function text_input(parent, label, help, callback, value)
if menu.text_input then if menu.text_input then
return menu.text_input(parent, label, {}, help, callback, value) return menu.text_input(parent, label, {}, help, callback, value)
@@ -262,6 +271,7 @@ local function text_input(parent, label, help, callback, value)
return __smenu.text_input(type(parent) == "table" and parent.id or parent, label, {}, help, callback, value) return __smenu.text_input(type(parent) == "table" and parent.id or parent, label, {}, help, callback, value)
end end
local function tab(ref, path) local function tab(ref, path)
info_page(ref, path:match("([^/]+)$") or path)
return setmetatable({ref = ref, path = path}, tab_mt) return setmetatable({ref = ref, path = path}, tab_mt)
end end
@@ -297,7 +307,10 @@ function tab_methods:add_input_int(label, default_value)
function obj:set_value(value) self.value = value end function obj:set_value(value) self.value = value end
return obj return obj
end end
function tab_methods:add_text(label) return menu.readonly(self.ref, label, "") end function tab_methods:add_text(label)
local page = info_page(self.ref)
page.static[#page.static + 1] = {text = tostring(label or "")}
end
function tab_methods:add_separator() return menu.divider(self.ref, "") end function tab_methods:add_separator() return menu.divider(self.ref, "") end
function tab_methods:add_sameline() end function tab_methods:add_sameline() end
function tab_methods:add_imgui(callback) function tab_methods:add_imgui(callback)
@@ -327,7 +340,7 @@ function script.execute_as_script(name, callback)
return __ny_execute_as_script(name, callback) return __ny_execute_as_script(name, callback)
end end
local current_section, current_event, building, widget_index, widget_occurrences, text_occurrences, separator_occurrences, parent_stack local current_section, current_event, building, widget_index, widget_occurrences, separator_occurrences, parent_stack
local function current_parent() return parent_stack[#parent_stack] end local function current_parent() return parent_stack[#parent_stack] end
local function push_parent(ref) parent_stack[#parent_stack + 1] = ref end local function push_parent(ref) parent_stack[#parent_stack + 1] = ref end
local function pop_parent() if #parent_stack > 1 then parent_stack[#parent_stack] = nil end end local function pop_parent() if #parent_stack > 1 then parent_stack[#parent_stack] = nil end end
@@ -364,13 +377,9 @@ local function label_for_menu(label)
end end
local function replay(section, event) local function replay(section, event)
current_section, current_event = section, event current_section, current_event = section, event
building, widget_index, widget_occurrences, text_occurrences, separator_occurrences, parent_stack = false, 0, {}, {}, {}, {section.parent} building, widget_index, widget_occurrences, separator_occurrences, parent_stack = false, 0, {}, {}, {section.parent}
section.notice_seen = false section.info_rows = {}
section.callback() section.callback()
if section.notice_ref and not section.notice_seen then
menu.delete(section.notice_ref)
section.notice_ref = nil
end
current_section, current_event, parent_stack = nil, nil, nil current_section, current_event, parent_stack = nil, nil, nil
end end
local function event_for(id, value) local function event_for(id, value)
@@ -386,6 +395,7 @@ local function register_action(label, value)
end) end)
end end
section.last_ref = section.widget_refs[id] section.last_ref = section.widget_refs[id]
section.last_info_row = nil
return event_for(id, value) return event_for(id, value)
end end
@@ -402,6 +412,7 @@ function ImGui.Checkbox(label, value)
end, value and true or false) end, value and true or false)
end end
section.last_ref = section.widget_refs[id] section.last_ref = section.widget_refs[id]
section.last_info_row = nil
if current_event and current_event.id == id then return current_event.value, true end if current_event and current_event.id == id then return current_event.value, true end
return value, false return value, false
end end
@@ -419,6 +430,7 @@ local function numeric_input(label, value, as_float)
end, tostring(value or 0)) end, tostring(value or 0))
end end
section.last_ref = section.widget_refs[id] section.last_ref = section.widget_refs[id]
section.last_info_row = nil
if current_event and current_event.id == id then return current_event.value, true end if current_event and current_event.id == id then return current_event.value, true end
return value, false return value, false
end end
@@ -435,6 +447,7 @@ function ImGui.SliderInt(label, value, min_value, max_value)
end) end)
end end
section.last_ref = section.widget_refs[id] section.last_ref = section.widget_refs[id]
section.last_info_row = nil
if current_event and current_event.id == id then return current_event.value, true end if current_event and current_event.id == id then return current_event.value, true end
return value, false return value, false
end end
@@ -448,6 +461,7 @@ function ImGui.InputText(label, value)
end, value or "") end, value or "")
end end
section.last_ref = section.widget_refs[id] section.last_ref = section.widget_refs[id]
section.last_info_row = nil
if current_event and current_event.id == id then return current_event.value, true end if current_event and current_event.id == id then return current_event.value, true end
return value, false return value, false
end end
@@ -469,6 +483,7 @@ function ImGui.Combo(label, selected, choices, count)
end end
end end
section.last_ref = section.containers_by_id[id] section.last_ref = section.containers_by_id[id]
section.last_info_row = nil
local current_choice = choices[(selected or 0) + 1] local current_choice = choices[(selected or 0) + 1]
if current_choice then if current_choice then
local display = menu_label .. ": " .. tostring(current_choice) local display = menu_label .. ": " .. tostring(current_choice)
@@ -477,7 +492,7 @@ function ImGui.Combo(label, selected, choices, count)
if current_event and current_event.id == id then return current_event.value, true end if current_event and current_event.id == id then return current_event.value, true end
return selected, false return selected, false
end end
function ImGui.Text(value) local function record_text(value, color)
if value == nil or value == "" then return end if value == nil or value == "" then return end
local label = tostring(value) local label = tostring(value)
if current_section.tooltip then if current_section.tooltip then
@@ -487,29 +502,21 @@ function ImGui.Text(value)
return return
end end
local compact = label:gsub("%s+", " "):match("^%s*(.-)%s*$") local compact = label:gsub("%s+", " "):match("^%s*(.-)%s*$")
if compact == "Unavailable in Single Player." or compact == "Waiting for game..." then local _, id = info_page(current_parent())
current_section.notice_seen = true local rows = current_section.info_rows[id]
if not current_section.notice_ref then if not rows then rows = {}; current_section.info_rows[id] = rows end
current_section.notice_ref = menu.divider(current_parent(), compact) local row = {text = label, color = color}
elseif menu.get_menu_name(current_section.notice_ref) ~= compact then rows[#rows + 1] = row
menu.set_menu_name(current_section.notice_ref, compact) current_section.last_info_row = row
end current_section.last_ref = nil
return if #compact <= 60 then current_section.last_text = compact:gsub(":$", "") end
end
label = compact
local text_id = source_key(text_occurrences, "text:" .. tostring(#current_section.text_refs + 1))
if not current_section.text_refs[text_id] then
current_section.text_refs[text_id] = menu.readonly(current_parent(), label, "")
else
local ref = current_section.text_refs[text_id]
if menu.get_menu_name(ref) ~= label then menu.set_menu_name(ref, label) end
end
current_section.last_ref = current_section.text_refs[text_id]
if #label <= 60 then current_section.last_text = label:gsub(":$", "") end
end end
ImGui.Text = record_text
ImGui.TextUnformatted = ImGui.Text ImGui.TextUnformatted = ImGui.Text
ImGui.TextWrapped = ImGui.Text ImGui.TextWrapped = ImGui.Text
function ImGui.TextColored(_, _, _, _, value) ImGui.Text(value) end function ImGui.TextColored(r, g, b, a, value)
record_text(value, {r, g, b, a})
end
function ImGui.Separator() function ImGui.Separator()
local id = source_key(separator_occurrences, "separator:" .. tostring(widget_index)) local id = source_key(separator_occurrences, "separator:" .. tostring(widget_index))
if not current_section.separator_refs[id] then if not current_section.separator_refs[id] then
@@ -523,7 +530,13 @@ function ImGui.ProgressBar(fraction, _, _, overlay)
ImGui.Text(overlay or string.format("%d%%", math.floor(math.max(0, math.min(1, fraction or 0)) * 100))) ImGui.Text(overlay or string.format("%d%%", math.floor(math.max(0, math.min(1, fraction or 0)) * 100)))
end end
function ImGui.SetTooltip(message) function ImGui.SetTooltip(message)
if current_section.last_ref then menu.set_help_text(current_section.last_ref, tostring(message)) end if current_section.last_info_row then
local row = current_section.last_info_row
if row.text == "[?]" then row.text = tostring(message)
else row.tooltip = tostring(message) end
elseif current_section.last_ref then
menu.set_help_text(current_section.last_ref, tostring(message))
end
end end
function ImGui.BeginTooltip() current_section.tooltip = true; return true end function ImGui.BeginTooltip() current_section.tooltip = true; return true end
function ImGui.EndTooltip() current_section.tooltip = false end function ImGui.EndTooltip() current_section.tooltip = false end
@@ -550,11 +563,13 @@ function ImGui.BeginTabItem(label)
local ref = current_section.containers_by_id[id] local ref = current_section.containers_by_id[id]
if not ref then if not ref then
ref = menu.list(current_parent(), label, {}, "") ref = menu.list(current_parent(), label, {}, "")
info_page(ref, label)
current_section.containers[#current_section.containers + 1] = ref current_section.containers[#current_section.containers + 1] = ref
current_section.containers_by_id[id] = ref current_section.containers_by_id[id] = ref
end end
if not ref then return false end if not ref then return false end
if current_event and not current_event.ancestors[ref.id] then return false end if current_event and not current_event.ancestors[ref.id] then return false end
current_section.last_info_row = nil
push_parent(ref) push_parent(ref)
return true return true
end end
@@ -577,15 +592,36 @@ ImGuiCol = {Text = 0}
ImGuiTabBarFlags = {None = 0} ImGuiTabBarFlags = {None = 0}
ImGuiWindowFlags = {Modal = 0, NoCollapse = 0, NoMove = 0, NoResize = 0} ImGuiWindowFlags = {Modal = 0, NoCollapse = 0, NoMove = 0, NoResize = 0}
local compat = {imgui_sections = imgui_sections} local compat = {imgui_sections = imgui_sections, info_pages = info_pages}
function compat.info_for_page(id)
local page = info_pages[id]
if not page then return nil end
local rows, last_label = {}, nil
local function append(row)
local label = row.text:gsub("%s+", " "):match("^%s*(.-)%s*$")
if label == "" then
if #rows > 0 and rows[#rows].text ~= "" then rows[#rows + 1] = {text = ""} end
last_label = nil
elseif label ~= last_label then
rows[#rows + 1] = row
last_label = label
end
end
for _, row in ipairs(page.static) do append(row) end
for _, section in ipairs(imgui_sections) do
local live = section.info_rows and section.info_rows[id]
if live then for _, row in ipairs(live) do append(row) end end
end
return {title = page.title, rows = rows}
end
function compat.materialize_imgui() function compat.materialize_imgui()
for _, section in ipairs(imgui_sections) do for _, section in ipairs(imgui_sections) do
section.text_refs, section.widget_refs, section.separator_refs = {}, {}, {} section.widget_refs, section.separator_refs = {}, {}
section.info_rows = {}
section.containers, section.containers_by_id = {}, {} section.containers, section.containers_by_id = {}, {}
section.tooltip, section.last_ref, section.last_text = false, nil, nil section.tooltip, section.last_ref, section.last_text = false, nil, nil
section.notice_ref, section.notice_seen = nil, false
current_section, current_event = section, nil current_section, current_event = section, nil
building, widget_index, widget_occurrences, text_occurrences, separator_occurrences, parent_stack = true, 0, {}, {}, {}, {section.parent} building, widget_index, widget_occurrences, separator_occurrences, parent_stack = true, 0, {}, {}, {section.parent}
scan_context = section.path:find("Story Mode", 1, true) and "story" or "online" scan_context = section.path:find("Story Mode", 1, true) and "story" or "online"
section.callback() section.callback()
scan_context = nil scan_context = nil
+1 -1
View File
@@ -43,7 +43,7 @@
- For **Nenyoo GTA V Legacy**: - For **Nenyoo GTA V Legacy**:
Copy the contents of [`Nenyoo`](Nenyoo) to `%LOCALAPPDATA%\Nenyoo\Plus\Scripts\User\UltimateMenu\`. In Nenyoo, open `Scripts` and select `Ultimate_Menu.lua` to load its menu page. The `lib` folder must stay beside the entry script. Join GTA Online before using online actions. Copy the contents of [`Nenyoo`](Nenyoo) to `%LOCALAPPDATA%\Nenyoo\Plus\Scripts\User\UltimateMenu\`. In Nenyoo, open `Scripts` and select `Ultimate_Menu.lua` to load its menu page. The `lib` folder must stay beside the entry script. Join GTA Online before using online actions.
The Nenyoo edition translates the YimMenu Legacy menu hierarchy and controls. It requires a Nenyoo build with the named-script execution and host-request Lua bridges. GTA V Enhanced has not been validated. The Nenyoo edition translates the YimMenu Legacy menu hierarchy and controls, with instructions and live status in a companion information panel beside the active submenu. It requires a Nenyoo build with the named-script execution and host-request Lua bridges. GTA V Enhanced has not been validated.
</details> </details>
--- ---