evolve
商店/共享库

screen_presets

Screen presets for what evolve draws: "minimum", "usual" and "everything" over the groups the menu lists under the "screen" category (sections over heroes, map, timers, panels; the camera section is l

hunnyc0 个脚本在用
支持源码公开
在脚本中使用
pack.json
"requires": { "screen_presets": "^1.0" }
你的 lua
local screen_presets = require("lib/screen_presets")

玩家无需安装:脚本会自动带上库,取范围内的任一版本。发布前可在开发会话中修改 scripts/lib/screen_presets/ 来试验。

API
screen_presets.add_group(key: string, sec?: string|nil, opts?: table|nil)#

Put a group of your own under the presets ("tab.group"). opts.keep = true keeps it on in "minimum".

参数
key string
sec string|nil over|map|timers|panels (default panels)
opts table|nil keep
screen_presets.groups(): table[]#

Every group a preset covers, in menu order: the groups the menu lists under "screen" (their

返回
table[] { key, sec, keep }

own ui.group_meta category, or their script's), without the camera section.

screen_presets.switches(): table[]#

The switches as they are now; a group whose script is off gives none.

返回
table[] { key, ctl, group, sec, keep }
screen_presets.changes(name: string, list?: table[]|nil): integer, table[]#

What applying a preset would write.

参数
name string minimum|usual|all
list table[]|nil switches(), when the caller has them
返回
integer
table[]
screen_presets.current(list?: table[]|nil): string|nil#

The preset the switches match now, nil for the player's own mix.

参数
list table[]|nil
返回
string|nil
screen_presets.encode_undo(list?: table[]|nil): string#

The undo as text, one "1<tab>key" line per value.

参数
list table[]|nil
返回
string
screen_presets.load_undo(text: string): integer#

Replace the undo with one read back from encode_undo's text.

参数
text string
返回
integer held
screen_presets.can_undo(): integer#

How many values the undo would put back.

返回
integer
screen_presets.apply(name: string): integer#

Apply a preset. A preset that changes nothing keeps the last undo.

参数
name string minimum|usual|all
返回
integer written
screen_presets.undo(): integer#

Put back the values the last apply overwrote.

返回
integer restored
screen_presets.catalog(lang?: string|nil, opts?: table|nil): table[]#

Every group a player can open, A-Я then A-Z, grouped by first letter. Read from the menu's own

参数
lang string|nil ru|en (default: the menu's)
opts table|nil dev, other
返回
table[] { letter, items = { key, tab, group, caption, cat, cat_caption, sec } }

category lists (ui.menu_layout, captions as the column prints them, ui.categories titles); on a menu without them, from the category map with captions in lang. opts.dev also lists the developer groups (map only: the menu already shows them in a developer session); opts.other = false drops the groups with no category.

screen_presets.install(opts?: table|nil): table#

The menu rows in opts.group (default "Misc.screen presets"): segments showing the matching

参数
opts table|nil group, lang
返回
table with update(), counts, seg, undo_btn, store

preset ("custom" when none) that apply one on a click, an undo button, the counts in the hint. Call at file scope, then handle:update() from on_update: it notices a click on the next call and recounts once a second.

介绍

lib/screen_presets

Screen presets for what evolve draws: "minimum", "usual" and "everything" over the groups the menu lists under the "screen" category (sections over heroes, map, timers, panels; the camera section is left alone). One call applies a preset, one call undoes it, and changes tells how many values each would write. The same library computes the A-Я catalogue of every menu group by category, which the R2 home draws (#535).

Which groups: the menu's own category lists, ui.menu_layout() views @<category> (#535 R2 step 2), so a third-party group that sets category = "screen" in ui.group_meta joins the presets by itself. The sections come from data/categories.lua; a group the map does not section counts as "other" and is covered. The live layout lists a page's sub-tabs as entries of their own ("Combo.Alerts.Rune spawns", "Оповещения на карте / Появление рун"); they fold into the page. On a menu without category views (an older dll, the offline mock) the map gives the groups too.

local P = require("lib/screen_presets")
print(P.changes("minimum"))    -- 18 on a fresh install
P.apply("minimum")             -- writes them, remembers the old values
P.undo()                       -- puts all 18 back
print(P.current())             -- "usual"

what a preset writes

A switch is a group's main checkbox ("Enable", "enabled", "0. Enabled {{utea}}"); a group without one gives its own top-level checkboxes (Visualization - Panels: show abilities, items, self, allies, enemies). Only those checkboxes are written; sliders, colours and the rows under a section stay as the player set them.

preset switches
minimum on: HP bars, map alerts, minimap marks, wards, rune timers, Roshan tracker (M.KEEP); everything else off
usual every switch back to its default (c:reset, the value the scripts declared)
all every switch on

From a fresh install: minimum changes 18 values, usual 0, all 2 (SEEN and the stats panel).

A script adds its own group with P.add_group("Visuals.my overlay", "over"); it is off in "minimum" unless { keep = true }.

undo

apply remembers the previous value of every switch it wrote; undo puts them back. An apply that changes nothing keeps the last undo. install keeps the undo in a hidden textbox of its group, so it survives a reload and a restart.

the menu rows

install(opts) makes three rows in opts.group (default Misc.screen presets):

  • Screen preset: segments Minimum / Usual / Everything / Custom. It shows the preset the switches match (Custom when none); a click on the first three applies that preset, a click on Custom does nothing. The hint under it carries the counts.
  • Undo preset: shown while an undo is held, caption "Undo preset (restore N)".
  • undo: the hidden textbox with the undo.

handle:update() from on_update applies a click on the next call and recounts once a second. A click is told from a config load by on_change: the menu's widgets write the control directly and fire none (src/ui/gs/ui2_rows.cpp), a config load and c:set do, so a value that moved with an on_change is followed, one that moved without it is applied. utea.lua hosts the rows, because utea ships enabled and a new top-level script starts disabled for existing players.

catalogue

catalog(lang, opts) returns { { letter, items = { { key, tab, group, caption, cat, cat_caption, sec } } } }: Cyrillic letters first (Ё after Е), then Latin. With category views the items are the menu's own lists: captions as the column prints them, cat_caption from ui.categories(), developer groups only in a developer session, hero pages left out, the settings category left out. Without them: data/categories.lua (generated from tools/menu535/categories.json by tools/menu535/gen_presets_data.py), groups with live controls only, merged groups as one entry, developer groups only with opts.dev, unknown live groups under other. opts.other == false drops other in both.

Example (offline menu, en captions):

A: Aim (hero), Armlet Abuse (fight), Autopilot (lane), Auto Interrupt (fight), Auto Pick (draft)
B: Behavior (humanizer), Blink Range (screen)

test

tools\lua\lua.exe scripts\lib\screen_presets\selftest.lua

Loads every script under the fake runtime the way tools/menu_dump.lua does, then checks on that menu: each preset writes exactly what changes names and nothing outside the screen groups, undo returns every value in the menu, the undo survives a reload through the textbox, a click on a segment applies it, the catalogue order and contents (from the map and from the menu's category views as menu_dump mirrors them, the two must agree), data/categories.lua against the json, and menu_dump --keys with and without the presets: no key gone, the new ones only in Misc.screen presets.

源码

1.0.2 版本的代码,即客户端拿到的内容。包内字节码由这些文件编译而来。

init.lua591 行
---@diagnostic disable: undefined-global
--- Screen presets: "minimum / usual / everything" for what evolve draws on screen, one call to
--- apply, one to undo, and the count of values each would change. Plus the A-Z catalogue of every
--- menu group by category. Public `ui.*` only: a preset writes the same switches a player clicks.
---
--- A switch is a group's main checkbox ("Enable", "0. Enabled {{utea}}"); a group without one
--- gives its own top-level checkboxes. "usual" puts every switch back to its default, "all"
--- turns every switch on, "minimum" keeps only `KEEP` on. No saved key moves; the only new keys
--- are the rows `install` makes in its own group.
---
--- ```lua
--- local P = require("lib/screen_presets")
--- local n = P.changes("minimum")   -- values it would write
--- P.apply("minimum")               -- writes them, remembers the old ones
--- P.undo()                         -- puts them back
--- for _, L in ipairs(P.catalog("ru")) do print(L.letter, #L.items) end
--- ```
-- #535 R2 step 5. the groups and captions: data/categories.lua, generated from
-- tools/menu535/categories.json by tools/menu535/gen_presets_data.py

local DATA = require("lib/screen_presets/data/categories")

local M = {}

M.PRESETS = { "minimum", "usual", "all" }

-- the sections of "на экране" a preset covers (categories.json cat "screen"); the camera
-- section is not drawn info, a preset leaves it alone
M.SECTIONS = {
    { id = "over",   ru = "Над героями", en = "Over heroes" },
    { id = "map",    ru = "Карта",       en = "Map" },
    { id = "timers", ru = "Таймеры",     en = "Timers" },
    { id = "panels", ru = "Панели",      en = "Panels" },
}
local SEC_ID = {}
for _, s in ipairs(M.SECTIONS) do SEC_ID[s.ru] = s.id end
local CAMERA = "Камера"

-- what "minimum" keeps on: hp bars (basic readability), map alerts, minimap marks, wards,
-- rune and roshan timers. everything else drawn goes off
M.KEEP = {
    ["Visuals.Visualization - HP bar"] = true,
    ["Combo.Alerts"] = true,
    ["Combo.Minimap"] = true,
    ["Combo.Ward"] = true,
    ["Combo.Rune Timers"] = true,
    ["Combo.Roshan Tracker"] = true,
}

-- the groups install() made: the preset's own rows, not a feature for the catalogue
local own = {}

local CAT_EN = {
    home = "Home", hero = "My hero", fight = "Fight and items", lane = "Lane and farm",
    screen = "Screen and map", draft = "Draft and players", humanizer = "Humanizer",
    keys = "Keys", store = "Store", settings = "Settings", other = "Other",
}
local CAT_RU = { other = "Другое" }
for _, r in ipairs(DATA.rail) do CAT_RU[r.id] = r.ru end

local function split(key)
    local dot = key:find(".", 1, true)
    if not dot then return key, "general" end
    return key:sub(1, dot - 1), key:sub(dot + 1)
end
M.split = split

local function lang_of(lang)
    if lang then return lang end
    local ok, l = pcall(function() return ui.menu_lang() end)
    return ok and l == "ru" and "ru" or "en"
end

-- ------------------------------------------------------------------ groups --
-- groups a third-party script put under the presets: { key =, sec =, keep = }
local added = {}

--- Put a group of your own under the presets ("tab.group"). opts.keep = true keeps it on in "minimum".
---@param key string
---@param sec string|nil over|map|timers|panels (default panels)
---@param opts table|nil keep
function M.add_group(key, sec, opts)
    assert(type(key) == "string" and key:find(".", 1, true), "screen_presets.add_group: key is \"tab.group\"")
    for _, g in ipairs(added) do
        if g.key == key then g.sec = sec or g.sec; g.keep = opts and opts.keep or false; return end
    end
    added[#added + 1] = { key = key, sec = sec or "panels", keep = opts and opts.keep or false }
end

local DATA_BY = {}
for _, g in ipairs(DATA.groups) do DATA_BY[g.key] = g end

-- the rail as the menu lists it (#535 R2 step 2): { [category id] = { { key = "tab.group",
-- caption = }, ... } }, "other" for "@*". nil on a menu without category views (an older dll,
-- the offline mock), and the category map stands in
local function live_views()
    local ok, lay = pcall(function() return ui.menu_layout() end)
    if not ok or type(lay) ~= "table" then return nil end
    local out, any = {}, false
    for _, v in ipairs(lay) do
        local name = type(v) == "table" and v.name
        if type(name) == "string" and name:sub(1, 1) == "@" and type(v.groups) == "table" then
            local id = name:sub(2)
            if id == "*" then id = "other" end
            local list = out[id] or {}
            for _, g in ipairs(v.groups) do
                local t, gr = tostring(g.key):match("^([^\31]*)\31(.*)$")
                if t then list[#list + 1] = { key = t .. "." .. gr, caption = g.caption } end
            end
            out[id] = list
            any = true
        end
    end
    if not any then return nil end
    -- the live layout lists a page's sub-tabs as entries of their own ("Combo.Alerts.Rune spawns",
    -- caption "Оповещения на карте / Появление рун"): fold them into the page they extend
    for id, list in pairs(out) do
        local keys = {}
        for _, g in ipairs(list) do keys[g.key] = g end
        local folded = {}
        for _, g in ipairs(list) do
            local p, head = g.key:match("^(.*)%.[^.]*$"), nil
            while p do
                if keys[p] then head = keys[p] end
                p = p:match("^(.*)%.[^.]*$")
            end
            if head then head.subs = true else folded[#folded + 1] = g end
        end
        for _, g in ipairs(folded) do
            if g.subs and type(g.caption) == "string" then g.caption = g.caption:match("^(.-) / [^/]*$") or g.caption end
            g.subs = nil
        end
        out[id] = folded
    end
    return out
end
M._live_views = live_views

--- Every group a preset covers, in menu order: the groups the menu lists under "screen" (their
--- own ui.group_meta category, or their script's), without the camera section.
---@return table[] { key, sec, keep }
function M.groups()
    local out, seen = {}, {}
    local views = live_views()
    if views then
        for _, g in ipairs(views.screen or {}) do
            local d = DATA_BY[g.key]
            -- a group the map does not section (a third-party script's) still counts
            local sec
            if not (d and d.sec == CAMERA) then sec = d and SEC_ID[d.sec] or "other" end
            if sec and not own[g.key] and not seen[g.key] then
                out[#out + 1] = { key = g.key, sec = sec, keep = M.KEEP[g.key] == true }
                seen[g.key] = true
            end
        end
    else
        for _, g in ipairs(DATA.groups) do
            -- a merged group's parts hang under its head's switch
            if g.cat == "screen" and SEC_ID[g.sec] and (not g.merge or g.merge == g.key) then
                out[#out + 1] = { key = g.key, sec = SEC_ID[g.sec], keep = M.KEEP[g.key] == true }
                seen[g.key] = true
            end
        end
    end
    for _, g in ipairs(added) do
        if not seen[g.key] then out[#out + 1] = { key = g.key, sec = g.sec, keep = g.keep } end
    end
    return out
end

-- ---------------------------------------------------------------- switches --
local function is_master(label)
    local l = tostring(label):lower():gsub("%s*{{%w+}}%s*$", ""):gsub("^%d+%.%s*", "")
    return l == "enable" or l == "enabled"
end
M.is_master = is_master

local function on(c) local v = c:get(); return v == true or v == 1 end

--- The switches as they are now; a group whose script is off gives none.
---@return table[] { key, ctl, group, sec, keep }
function M.switches()
    local out = {}
    for _, g in ipairs(M.groups()) do
        local tab, group = split(g.key)
        local ok, ctls = pcall(ui.group_controls, tab, group)
        if ok and type(ctls) == "table" then
            local boxes, master = {}, nil
            for _, c in ipairs(ctls) do
                if c:type() == "checkbox" then
                    boxes[#boxes + 1] = c
                    if not master and is_master(c:label()) then master = c end
                end
            end
            for _, c in ipairs(master and { master } or boxes) do
                out[#out + 1] = { key = g.key .. "." .. c:label(), ctl = c, group = g.key,
                                  sec = g.sec, keep = g.keep }
            end
        end
    end
    return out
end

-- what preset `name` wants from a switch: true/false, or nil = its default
local function wants(name, s)
    if name == "minimum" then return s.keep end
    if name == "all" then return true end
    if name == "usual" then return nil end
    error("screen_presets: unknown preset " .. tostring(name), 3)
end

--- What applying a preset would write.
---@param name string minimum|usual|all
---@param list table[]|nil switches(), when the caller has them
---@return integer n
---@return table[] switches
function M.changes(name, list)
    list = list or M.switches()
    local out = {}
    for _, s in ipairs(list) do
        local w = wants(name, s)
        if w == nil then
            if not s.ctl:is_default() then out[#out + 1] = s end
        elseif on(s.ctl) ~= w then
            out[#out + 1] = s
        end
    end
    return #out, out
end

--- The preset the switches match now, nil for the player's own mix.
---@param list table[]|nil
---@return string|nil
function M.current(list)
    list = list or M.switches()
    if #list == 0 then return nil end
    for _, p in ipairs({ "usual", "minimum", "all" }) do
        if M.changes(p, list) == 0 then return p end
    end
    return nil
end

-- ------------------------------------------------------------------- undo --
-- the values the last apply overwrote: { { key =, value = bool }, ... }. on_undo(text) is
-- install's hook that keeps them in its hidden textbox, so an undo survives a restart
local undo_list, on_undo = {}, nil

--- The undo as text, one "1<tab>key" line per value.
---@param list table[]|nil
---@return string
function M.encode_undo(list)
    local t = {}
    for _, u in ipairs(list or undo_list) do t[#t + 1] = (u.value and "1" or "0") .. "\t" .. u.key end
    return table.concat(t, "\n")
end

--- Replace the undo with one read back from encode_undo's text.
---@param text string
---@return integer values held
function M.load_undo(text)
    undo_list = {}
    for line in tostring(text or ""):gmatch("[^\n]+") do
        local v, k = line:match("^([01])\t(.+)$")
        if k then undo_list[#undo_list + 1] = { key = k, value = v == "1" } end
    end
    return #undo_list
end

--- How many values the undo would put back.
---@return integer
function M.can_undo() return #undo_list end

local function remembered(list)
    undo_list = list
    if on_undo then pcall(on_undo, M.encode_undo()) end
end

--- Apply a preset. A preset that changes nothing keeps the last undo.
---@param name string minimum|usual|all
---@return integer values written
function M.apply(name)
    local list = M.switches()
    local n, ch = M.changes(name, list)
    if n == 0 then return 0 end         -- nothing to write: the last undo stays
    local prev = {}
    for _, s in ipairs(ch) do
        prev[#prev + 1] = { key = s.key, value = on(s.ctl) }
        local w = wants(name, s)
        if w == nil then s.ctl:reset() else s.ctl:set(w) end
    end
    remembered(prev)
    return n
end

--- Put back the values the last apply overwrote.
---@return integer values restored
function M.undo()
    if #undo_list == 0 then return 0 end
    local by = {}
    for _, s in ipairs(M.switches()) do by[s.key] = s end
    local n = 0
    for _, u in ipairs(undo_list) do
        local s = by[u.key]
        -- a switch whose script went away since: nothing to put back
        if s then
            if on(s.ctl) ~= u.value then s.ctl:set(u.value) end
            n = n + 1
        end
    end
    remembered({})
    return n
end

-- --------------------------------------------------------------- catalogue --
local function cp_upper(cp)
    if cp >= 0x430 and cp <= 0x44F then return cp - 0x20 end
    if cp == 0x451 then return 0x401 end
    if cp >= 0x61 and cp <= 0x7A then return cp - 0x20 end
    return cp
end

-- cyrillic first (ё right after е), then latin, then digits, then the rest
local function rank(cp)
    cp = cp_upper(cp)
    if cp == 0x401 then return 1000 + (0x415 - 0x410) * 2 + 1 end
    if cp >= 0x410 and cp <= 0x42F then return 1000 + (cp - 0x410) * 2 end
    if cp >= 0x41 and cp <= 0x5A then return 2000 + cp end
    if cp >= 0x30 and cp <= 0x39 then return 3000 + cp end
    return 4000 + cp
end

-- the dll's lua has no utf8 library (live, 2026-11-03): decode by hand. a broken byte counts as itself
local function codepoints(s)
    local out, i, n = {}, 1, #s
    while i <= n do
        local c = s:byte(i)
        local len = c >= 0xF0 and 4 or c >= 0xE0 and 3 or c >= 0xC0 and 2 or 1
        local cp = len == 1 and c or (c & (0x3F >> (len - 1)))
        for j = 1, len - 1 do cp = (cp << 6) | ((s:byte(i + j) or 0x80) & 0x3F) end
        out[#out + 1] = cp
        i = i + len
    end
    return out
end

local function utf8_char(cp)
    if cp < 0x80 then return string.char(cp) end
    if cp < 0x800 then return string.char(0xC0 | (cp >> 6), 0x80 | (cp & 0x3F)) end
    if cp < 0x10000 then return string.char(0xE0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3F), 0x80 | (cp & 0x3F)) end
    return string.char(0xF0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3F), 0x80 | ((cp >> 6) & 0x3F), 0x80 | (cp & 0x3F))
end

local function sort_key(s)
    local k = {}
    for _, cp in ipairs(codepoints(s)) do k[#k + 1] = rank(cp) end
    return k
end

local function before(a, b)
    for i = 1, math.max(#a, #b) do
        local x, y = a[i], b[i]
        if x == nil then return true end
        if y == nil then return false end
        if x ~= y then return x < y end
    end
    return false
end

local function first_letter(s)
    local cp = codepoints(s)[1]
    return cp and utf8_char(cp_upper(cp)) or "#"
end

local function pretty(group)
    local leaf = group:gsub("^[Vv]isuali[sz]ation %- ", ""):gsub("^visuals %- ", "")
    return (leaf:gsub("^%l", string.upper))
end

-- the captions the column prints now, by "tab.group" (ui.menu_layout keys are "tab\31group")
local function layout_captions()
    local out = {}
    local ok, lay = pcall(function() return ui.menu_layout() end)
    if not ok or type(lay) ~= "table" then return out end
    for _, view in ipairs(lay) do
        for _, g in ipairs(type(view) == "table" and type(view.groups) == "table" and view.groups or {}) do
            if type(g.key) == "string" and type(g.caption) == "string" and g.caption ~= "" then
                local t, gr = g.key:match("^([^\31]*)\31(.*)$")
                if t then out[t .. "." .. gr] = g.caption end
            end
        end
    end
    return out
end

local function present(key)
    local tab, group = split(key)
    local ok, ctls = pcall(ui.group_controls, tab, group, true)
    return ok and type(ctls) == "table" and #ctls > 0
end

--- Every group a player can open, A-Я then A-Z, grouped by first letter. Read from the menu's own
--- category lists (ui.menu_layout, captions as the column prints them, ui.categories titles); on a
--- menu without them, from the category map with captions in `lang`. opts.dev also lists the
--- developer groups (map only: the menu already shows them in a developer session); opts.other =
--- false drops the groups with no category.
---@param lang string|nil ru|en (default: the menu's)
---@param opts table|nil dev, other
---@return table[] { letter, items = { key, tab, group, caption, cat, cat_caption, sec } }
function M.catalog(lang, opts)
    lang, opts = lang_of(lang), opts or {}
    local caps, items, seen, known = layout_captions(), {}, {}, {}
    local by = {}
    for _, g in ipairs(DATA.groups) do by[g.key] = g; known[g.key] = true end
    local function caption(g, key)
        if lang == "ru" and g and g.caption then return g.caption end
        if lang == "ru" and caps[key] then return caps[key] end
        local _, group = split(key)
        return pretty(group)
    end
    local function add(key, g, cat)
        local tab, group = split(key)
        items[#items + 1] = {
            key = key, tab = tab, group = group, caption = caption(g, key), cat = cat,
            cat_caption = (lang == "ru" and CAT_RU or CAT_EN)[cat] or cat, sec = g and g.sec or "",
        }
    end
    local views = live_views()
    if views then
        -- the menu's own lists: captions in its language, developer groups already left out of a
        -- player's session, a group under the category its script gave it
        local titles = {}
        pcall(function() for _, c in ipairs(ui.categories()) do titles[c.id] = c.title end end)
        for cat, list in pairs(views) do
            if cat ~= "settings" and (opts.other ~= false or cat ~= "other") then
                for _, g in ipairs(list) do
                    local tab, group = split(g.key)
                    local hero_page = group:match("^Heroes%.") and not by[g.key]
                    if not own[g.key] and not seen[g.key] and not hero_page then
                        seen[g.key] = true
                        local d = by[g.key]
                        items[#items + 1] = {
                            key = g.key, tab = tab, group = group,
                            caption = (type(g.caption) == "string" and g.caption ~= "") and g.caption or caption(d, g.key),
                            cat = cat, cat_caption = titles[cat] or (lang == "ru" and CAT_RU or CAT_EN)[cat] or cat,
                            sec = d and d.sec or "",
                        }
                    end
                end
            end
        end
    end
    for _, g in ipairs(views and {} or DATA.groups) do
        local head = g.merge or g.key
        if not seen[head] and g.cat ~= "settings" and (opts.dev or not g.dev) and present(head) then
            seen[head] = true
            add(head, by[head] or g, g.cat)
        end
    end
    if not views and opts.other ~= false and ui.all_controls then
        local ok, all = pcall(ui.all_controls)
        for _, c in ipairs(ok and type(all) == "table" and all or {}) do
            local tab, group = c:tab(), c:group()
            local low = tab:lower()
            local key = tab .. "." .. group:match("^[^.]*")
            -- a group the map files already (its own key or one it extends), the hero list,
            -- the built-in settings tab
            local covered = low == "config" or low == "settings" or group:match("^Heroes%.")
                or group == "Heroes"
            if not covered then
                local p = tab .. "." .. group
                while p do
                    if known[p] then covered = true; break end
                    p = p:match("^(.*)%.[^.]*$")
                end
            end
            if not covered and not seen[key] and not own[key] then
                seen[key] = true
                add(key, nil, "other")
            end
        end
    end
    for _, it in ipairs(items) do it._k = sort_key(it.caption) end
    table.sort(items, function(a, b)
        if before(a._k, b._k) then return true end
        if before(b._k, a._k) then return false end
        return a.key < b.key
    end)
    local out, last = {}, nil
    for _, it in ipairs(items) do
        it._k = nil
        local l = first_letter(it.caption)
        if not last or last.letter ~= l then last = { letter = l, items = {} }; out[#out + 1] = last end
        last.items[#last.items + 1] = it
    end
    return out
end

-- -------------------------------------------------------------- menu rows --
local TEXT = {
    ru = { title = "Пресет экрана", names = { "Минимум", "Обычный", "Всё", "Свой" },
           hint = "Изменит значений: минимум %d, обычный %d, всё %d",
           undo = "Отменить пресет (вернуть %d)",
           tip = "Минимум: над героями только полоски HP, на карте оповещения, миникарта и варды, "
              .. "таймеры рун и Рошана. Обычный: как после установки. Всё: включено всё, что рисует evolve." },
    en = { title = "Screen preset", names = { "Minimum", "Usual", "Everything", "Custom" },
           hint = "Values it changes: minimum %d, usual %d, everything %d",
           undo = "Undo preset (restore %d)",
           tip = "Minimum: only hp bars over heroes; map alerts, minimap marks and wards; rune and "
              .. "Roshan timers. Usual: as installed. Everything: all evolve draws is on." },
}
local CUSTOM = 3

--- The menu rows in opts.group (default "Misc.screen presets"): segments showing the matching
--- preset ("custom" when none) that apply one on a click, an undo button, the counts in the hint.
--- Call at file scope, then handle:update() from on_update: it notices a click on the next call and
--- recounts once a second.
---@param opts table|nil group, lang
---@return table handle with update(), counts, seg, undo_btn, store
function M.install(opts)
    opts = opts or {}
    local G = opts.group or "Misc.screen presets"
    local tab, group = split(G)
    local L = TEXT[lang_of(opts.lang)]
    local H = { group = G }
    own[G] = true
    local seg = ui.slider_int(G, "Screen preset", 0, CUSTOM, 1)
    local undo_btn, store
    pcall(function() seg:names(L.names) end)
    pcall(function() seg:caption(L.title); seg:tooltip(L.tip) end)
    store = ui.textbox(G, "undo", "")
    pcall(function() store:visible(false) end)
    undo_btn = ui.button(G, "Undo preset", function() M.undo(); H.next = 0; H:update() end)
    pcall(function() undo_btn:visible(false) end)
    -- home_head: the R2 home draws these rows in the head of the screen column, not as a tile
    pcall(ui.group_meta, tab, group, { caption = L.title, icon = "layers", category = "screen", home_head = true })
    H.seg, H.undo_btn, H.store = seg, undo_btn, store

    on_undo = function(text) store:set(text) end
    if M.can_undo() == 0 then M.load_undo(store:get()) end

    -- the segment value as last written by lua or a config load. the menu's widgets write the
    -- control directly and fire no on_change (ui2_rows.cpp, render thread), a config load and
    -- c:set do: so on_change is state to follow, and a value that moved without one is a click
    local shown
    seg:on_change(function() shown = seg:get() end)

    -- every frame: a click on a segment applies at once. the recount runs once a second
    function H:update(now)
        now = now or (globals and globals.realtime and globals.realtime()) or 0
        local v = seg:get()
        if shown ~= nil and v ~= shown then
            shown = v
            local name = M.PRESETS[(tonumber(v) or -1) + 1]
            if name then M.apply(name) end
            self.next = 0
        end
        local okm, open = pcall(ui.menu_open)
        local closed = okm and open == false
        if self.closed and not closed then self.next = 0 end
        self.closed = closed
        if self.next and now < self.next then return end
        self.next = now + 1.0
        -- a reload before the first update: the textbox holds the undo, not the module
        if shown == nil and M.can_undo() == 0 then M.load_undo(store:get()) end
        -- menu_layout + group_controls is a full menu pass, 4.75 ms (#624). closed menu: the list
        -- is kept until the control count moves or 30 s pass, the segment still follows the switches
        -- (a stale one was written on the next open, the api gate saw it as a call's write)
        local nc = 0
        pcall(function() nc = ui.control_count and ui.control_count() or 0 end)
        if not closed or not self.list or self.list_n ~= nc or now >= (self.list_due or 0) then
            self.list, self.list_n, self.list_due = M.switches(), nc, now + 30
        end
        local list = self.list
        local cur = M.current(list)
        v = CUSTOM
        for i, p in ipairs(M.PRESETS) do if p == cur then v = i - 1 end end
        if seg:get() ~= v then seg:set(v) end
        shown = v
        if store:get() ~= M.encode_undo() then store:set(M.encode_undo()) end
        if closed then return end
        local n1, n2, n3 = M.changes("minimum", list), M.changes("usual", list), M.changes("all", list)
        self.counts = { minimum = n1, usual = n2, all = n3 }
        pcall(function() seg:hint(L.hint:format(n1, n2, n3)) end)
        local u = M.can_undo()
        pcall(function() undo_btn:visible(u > 0); undo_btn:caption(L.undo:format(u)) end)
    end
    return H
end

return M
pack.json2 行
{ "kind": "lib", "version": "1.0.2" }
data/categories.lua86 行
-- generated by tools/menu535/gen_presets_data.py from tools/menu535/categories.json.
-- do not edit: change the json and rerun. order = the json's.
return {
    rail = {
        { id = "home", ru = "Главная", builtin = true },
        { id = "hero", ru = "Мой герой" },
        { id = "fight", ru = "Бой и предметы" },
        { id = "lane", ru = "Линия и фарм" },
        { id = "screen", ru = "Экран и карта" },
        { id = "draft", ru = "Драфт и игроки" },
        { id = "humanizer", ru = "Хуманайзер" },
        { id = "keys", ru = "Клавиши", builtin = true },
        { id = "store", ru = "Магазин", builtin = true },
        { id = "settings", ru = "Настройки", builtin = true },
    },
    groups = {
        { key = "Combo.uTEA", cat = "hero", sec = "", caption = "Для всех героев" },
        { key = "Combo.Misc", cat = "hero", sec = "", caption = "Аим" },
        { key = "Combo.Heroes.Custom games", cat = "hero", sec = "", caption = "Комбо в кастомках" },
        { key = "Combo.Item Manager", cat = "fight", sec = "Предметы" },
        { key = "Combo.Item Advisor", cat = "fight", sec = "Предметы" },
        { key = "Combo.Armlet Abuse", cat = "fight", sec = "Предметы" },
        { key = "Combo.Dispel", cat = "fight", sec = "Предметы", caption = "Снятие дебаффов" },
        { key = "Combo.Power Treads", cat = "fight", sec = "Предметы", caption = "Power Treads" },
        { key = "Combo.Orbwalker", cat = "fight", sec = "Бой" },
        { key = "Combo.Smart Dodge", cat = "fight", sec = "Бой" },
        { key = "Combo.Auto Interrupt", cat = "fight", sec = "Бой", caption = "Авто прерывание" },
        { key = "Combo.Kill Steal", cat = "fight", sec = "Бой", caption = "Добить спеллом" },
        { key = "Combo.Defend", cat = "fight", sec = "Бой" },
        { key = "Combo.Illusion", cat = "fight", sec = "Бой", caption = "Управление иллюзиями" },
        { key = "Combo.Last Hit", cat = "lane", sec = "" },
        { key = "Combo.Creep Blocker", cat = "lane", sec = "" },
        { key = "Combo.Lane Control", cat = "lane", sec = "" },
        { key = "Combo.Jungle", cat = "lane", sec = "" },
        { key = "Combo.Camp Stacker", cat = "lane", sec = "" },
        { key = "Combo.Dominion", cat = "lane", sec = "" },
        { key = "Combo.Autopilot", cat = "lane", sec = "" },
        { key = "Combo.Snatcher", cat = "lane", sec = "", caption = "Подбор рун" },
        { key = "Visuals.Visualization - HP bar", cat = "screen", sec = "Над героями", caption = "Полоски HP" },
        { key = "Visuals.Visualization - Status effects", cat = "screen", sec = "Над героями" },
        { key = "Visuals.Visualization - Panels", cat = "screen", sec = "Над героями", caption = "Способности, предметы" },
        { key = "Visuals.visuals - esp", cat = "screen", sec = "Над героями", caption = "ESP: рамки и имена" },
        { key = "Visuals.Visualization - Offscreen ESP", cat = "screen", sec = "Над героями" },
        { key = "Combo.Illusion Marker", cat = "screen", sec = "Над героями" },
        { key = "Combo.Blink Range", cat = "screen", sec = "Над героями" },
        { key = "Combo.uVBE", cat = "screen", sec = "Над героями", caption = "SEEN: тебя видят враги" },
        { key = "Combo.Alerts", cat = "screen", sec = "Карта", caption = "Оповещения на карте" },
        { key = "Combo.Minimap", cat = "screen", sec = "Карта" },
        { key = "Combo.Awareness", cat = "screen", sec = "Карта", caption = "Способности врагов" },
        { key = "Combo.Ward", cat = "screen", sec = "Карта", caption = "Варды" },
        { key = "Combo.Ward Spots", cat = "screen", sec = "Карта" },
        { key = "Combo.Enemy Couriers", cat = "screen", sec = "Карта" },
        { key = "Combo.Rune Timers", cat = "screen", sec = "Таймеры" },
        { key = "Combo.Roshan Tracker", cat = "screen", sec = "Таймеры" },
        { key = "Combo.Lotus Pool Timer", cat = "screen", sec = "Таймеры" },
        { key = "Visuals.Visualization - Top bars", cat = "screen", sec = "Панели" },
        { key = "Visuals.Visualization - Stats panel", cat = "screen", sec = "Панели" },
        { key = "Combo.Net Worth", cat = "screen", sec = "Панели", caption = "Нетворс" },
        { key = "Combo.HUD", cat = "screen", sec = "Панели", caption = "Строка статуса uTEA" },
        { key = "Misc.advisor", cat = "screen", sec = "Панели", caption = "Советник по игре", merge = "Misc.advisor", sub = "Основное" },
        { key = "Misc.advisor.minimap", cat = "screen", sec = "Панели", merge = "Misc.advisor", sub = "Миникарта" },
        { key = "Misc.advisor.allies", cat = "screen", sec = "Панели", merge = "Misc.advisor", sub = "Союзники" },
        { key = "Misc.advisor.coach", cat = "screen", sec = "Панели", merge = "Misc.advisor", sub = "Тренер" },
        { key = "Misc.advisor.classes", cat = "screen", sec = "Панели", merge = "Misc.advisor", sub = "Какие советы" },
        { key = "Misc.advisor.phrases", cat = "screen", sec = "Панели", merge = "Misc.advisor", sub = "Фразы в чат" },
        { key = "Misc.Camera", cat = "screen", sec = "Камера", caption = "Камера" },
        { key = "Visuals.Visualization - Weather", cat = "screen", sec = "Камера" },
        { key = "Visuals.Visualization", cat = "screen", sec = "Камера", caption = "Визуализация (весь скрипт)" },
        { key = "Misc.draft advisor", cat = "draft", sec = "", caption = "Подсказки на драфте" },
        { key = "Combo.Auto Pick", cat = "draft", sec = "" },
        { key = "Misc.Draft ranks", cat = "draft", sec = "", caption = "Ранги на драфте" },
        { key = "Misc.player notes", cat = "draft", sec = "", caption = "Заметки об игроках" },
        { key = "Misc.session", cat = "draft", sec = "", caption = "Итоги матча" },
        { key = "Misc.Chat translate", cat = "draft", sec = "", caption = "Перевод чата" },
        { key = "Misc.New account", cat = "draft", sec = "", caption = "Новый аккаунт" },
        { key = "Humanizer.General", cat = "humanizer", sec = "", caption = "Основное" },
        { key = "Humanizer.Behavior", cat = "humanizer", sec = "" },
        { key = "Humanizer.Part B", cat = "humanizer", sec = "", dev = true },
        { key = "Humanizer.Debug", cat = "humanizer", sec = "", dev = true },
        { key = "Misc.Theme", cat = "settings", sec = "", merge = "appearance" },
        { key = "Misc.general", cat = "settings", sec = "", merge = "appearance" },
        { key = "Misc.fps mode", cat = "settings", sec = "", merge = "appearance" },
        { key = "Misc..BotTrainer", cat = "settings", sec = "", caption = "Тренер ботов", dev = true },
    },
}
更新日志

这个作品没有版本说明。

使用它的脚本

还没有已发布的脚本需要它。

该作者的其他作品

该作者的其他作品

escape

作者 hunnyc

How many seconds a unit dropped at a point stays out of reach of what saves him: his towers, his fountain, his heroes walking at him, and his heroes in the fog from where `lib/fog_seen` last put them

pack.json
"requires": { "escape": "^1.0" }
你的 lua
local escape = require("lib/escape")
0 个脚本在用还没有已发布的版本

menu_form

作者 hunnyc

Splits a top group into its main rows and an "Advanced" list.

pack.json
"requires": { "menu_form": "^1.0" }
你的 lua
local menu_form = require("lib/menu_form")
0 个脚本在用还没有已发布的版本

config_defaults

作者 hunnyc

Moves a saved setting to a new default once, if it still holds the old one.

pack.json
"requires": { "config_defaults": "^1.0" }
你的 lua
local config_defaults = require("lib/config_defaults")
0 个脚本在用还没有已发布的版本