All pastes #2S8esRpR5J Raw Edit

Line to target

public lua v1 · immutable
·published 2026-11-01 15:42 UTC
rendered paste body
-- Monster Target Indicator(仇恨连线)—— 每只大怪用红线连接当前锁定的猎人 / 随从猎人 / 猫狗。-- 数据:snow.enemy.EnemyCharacterBase.getCurrentTargetCharacter()。招式之间目标为空时保留 1.5 秒,换目标立即切线。-- 目标与世界坐标在 PlayerManager.update 前置钩子内读取;re.on_frame 只投影、绘制坐标快照。-- 使用 REFramework 背景绘图列表(旧版回退到 draw),不依赖 d2d,也不修改游戏内的名字。local CONFIG_FILE = "TargetLines.json"local config = { enabled = true }   -- 菜单只有一个开关(2026-10-31 用户:"只需要 On/off 就行,英文版")local hasCfg = falsepcall(function() hasCfg = fs.glob("TargetLines%.json$")[1] ~= nil end)if hasCfg then    pcall(function()        local c = json.load_file(CONFIG_FILE)        if type(c) == "table" then for k, v in pairs(c) do if config[k] ~= nil then config[k] = v end end end    end)endlocal function saveConfig() pcall(json.dump_file, CONFIG_FILE, config) endif not hasCfg then saveConfig() end   -- 第一次启动写一份默认配置(免得日志里每次报"读不到")local HOLD = 1.5local MAX_TARGET_DISTANCE = 50.0local MONSTER_Y = 1.0local PLAYER_Y = 0.9local OTOMO_Y = 0.5-- 从外到内叠加静态荧光;线宽以 1080p 为基准,颜色使用 ABGR。local LINE_LAYERS = {    { width = 7, color = 0x183030FF },    { width = 5, color = 0x383030FF },    { width = 3, color = 0xFF3030FF },    { width = 1, color = 0xFFD0D0FF },}local HEAD_NAMES = { "Head_00", "Head", "Head_01", "head" }local lines = {}        -- 仅保存复制后的世界坐标,不包含游戏对象local bossLast = {}     -- 怪物地址 → { target = 托管对象引用, kind, t }local headCache = {}    -- 怪物地址 → { mesh, joint };joint = false 表示没有找到骨骼local kindCache = {}local lastStatuslocal diag = { hook = false, err = nil }local function clearState()    lines, bossLast, headCache, kindCache = {}, {}, {}, {}    lastStatus = nilendlocal function kindOf(ch)    local addr = ch:get_address()    local kind = kindCache[addr]    if kind == nil then        local name = ch:get_type_definition():get_full_name()        if name:find("^snow%.player%.") then kind = "player"        elseif name:find("^snow%.otomo%.") then kind = "otomo"        else kind = false end        kindCache[addr] = kind    end    return kindendlocal function offsetPosition(pos, height)    if not pos then return nil end    return Vector3f.new(pos.x, pos.y + height, pos.z)endlocal function monsterPosition(enemy, addr, newHeads, rootPos)    -- 头部骨骼的查找或读取失败不影响连线,改用怪物根位置。    local ok, pos = pcall(function()        local mesh = enemy:call("get_RefMesh")        if not mesh then return nil end        local rec = headCache[addr]        if not rec or rec.mesh:get_address() ~= mesh:get_address() then            local go = mesh:call("get_GameObject")            local transform = go and go:call("get_Transform")            local joint            if transform then                for _, name in ipairs(HEAD_NAMES) do                    joint = transform:call("getJointByName", name)                    if joint then break end                end            end            rec = { mesh = mesh, joint = joint or false }        end        newHeads[addr] = rec        if rec.joint and sdk.is_managed_object(rec.joint) then            return rec.joint:call("get_Position")        end    end)    if ok and pos then return offsetPosition(pos, 0) end    return offsetPosition(rootPos, MONSTER_Y)endlocal function collectBoss(enemy, addr, now, newHeads)    if enemy:call("checkDie") == true or enemy:call("get_IsCombatMode") ~= true then return end    local last = bossLast[addr]    local target = enemy:call("getCurrentTargetCharacter")    local hasCurrentTarget = target ~= nil    local kind    if target then        if not sdk.is_managed_object(target) then return end        kind = kindOf(target)        if not kind then return end   -- 明确锁定其他对象时,不保留上一目标    elseif not last or now - last.t >= HOLD then        return    else        target, kind = last.target, last.kind    end    -- 当前目标和缓冲目标每帧都用原始根位置检查距离,超距清理优先于 1.5 秒缓冲。    -- 不返回 last 即同时丢弃该怪物的连线与缓冲;读取抛错由外层逐怪 pcall 隔离。    if not sdk.is_managed_object(target) then return end    local rootPos = enemy:call("get_Pos")    local targetPos = target:call("get_Pos")    if not rootPos or not targetPos then return end    local dx, dy, dz = rootPos.x - targetPos.x, rootPos.y - targetPos.y, rootPos.z - targetPos.z    if dx * dx + dy * dy + dz * dz > MAX_TARGET_DISTANCE * MAX_TARGET_DISTANCE then return end    -- 仅游戏返回的有效目标能建立或刷新缓冲;沿用旧目标时不延长缓冲时间。    if hasCurrentTarget then        last = { target = target, kind = kind, t = now }    end    local to = offsetPosition(targetPos, kind == "otomo" and OTOMO_Y or PLAYER_Y)    local from = monsterPosition(enemy, addr, newHeads, rootPos)    if not from then return end    return last, { from = from, to = to }end-- ===== 每帧采集(PlayerManager.update 前置钩子)=====local function tick()    if not config.enabled then clearState(); return end    local em = sdk.get_managed_singleton("snow.enemy.EnemyManager")    local pm = sdk.get_managed_singleton("snow.player.PlayerManager")    local qm = sdk.get_managed_singleton("snow.QuestManager")    if not em or not pm or not qm then clearState(); return end    local status = qm:get_field("_QuestStatus")    if status ~= lastStatus then        clearState()        lastStatus = status    end    local now = os.clock()    local newLines, newLast, newHeads = {}, {}, {}    for i = 0, (em:call("getBossEnemyCount") or 0) - 1 do        local enemy = em:call("getBossEnemy", i)        if enemy then            local addr = enemy:get_address()            local ok, last, line = pcall(collectBoss, enemy, addr, now, newHeads)            if ok then                newLast[addr] = last                if line then newLines[#newLines + 1] = line end            else                -- 单只怪读取失败时丢弃它的旧状态,其他怪照常更新。                newHeads[addr] = nil                diag.err = tostring(last)            end        end    end    bossLast, headCache, lines = newLast, newHeads, newLinesenddo    local ok, err = pcall(function()        local td = sdk.find_type_definition("snow.player.PlayerManager")        local method = td and td:get_method("update")        if not method then error("PlayerManager.update is unavailable") end        sdk.hook(method, function()            local updated, updateErr = pcall(tick)            if not updated then                clearState()                diag.err = tostring(updateErr)            end        end, function(retval) return retval end)    end)    diag.hook = ok    if not ok then diag.err = tostring(err) endendre.on_script_reset(clearState)-- ===== 内置绘图:只读取世界坐标快照 =====local function backgroundDrawList()    if type(imgui.get_background_draw_list) ~= "function" then return nil end    local ok, list = pcall(function()        local drawList = imgui.get_background_draw_list()        if drawList and type(drawList.add_line) == "function" then return drawList end    end)    if ok then return list endendlocal function drawGlowLine(from, to, drawList, scale)    local dx, dy = to.x - from.x, to.y - from.y    local lengthSquared = dx * dx + dy * dy    if lengthSquared == 0 then return end    if drawList then        for _, layer in ipairs(LINE_LAYERS) do            drawList:add_line(from, to, layer.color, layer.width * scale)        end        return    end    -- 旧版没有背景绘图接口时,以四边形和端点圆形模拟同样的分层粗线。    local length = math.sqrt(lengthSquared)    local nx, ny = -dy / length, dx / length    for _, layer in ipairs(LINE_LAYERS) do        local radius = layer.width * scale * 0.5        local ox, oy = nx * radius, ny * radius        draw.filled_quad(from.x + ox, from.y + oy, from.x - ox, from.y - oy,            to.x - ox, to.y - oy, to.x + ox, to.y + oy, layer.color)        draw.filled_circle(from.x, from.y, radius, layer.color, 16)        draw.filled_circle(to.x, to.y, radius, layer.color, 16)    endendre.on_frame(function()    if not config.enabled then return end    local snapshot = lines    local ok, err = pcall(function()        local display = imgui.get_display_size()        if not display or display.y <= 0 then return end        local scale = display.y / 1080.0        local drawList = backgroundDrawList()        for _, line in ipairs(snapshot) do            local from = draw.world_to_screen(line.from)            local to = draw.world_to_screen(line.to)            if from and to then drawGlowLine(from, to, drawList, scale) end        end    end)    if not ok then        lines = {}        diag.err = tostring(err)    endend)-- ===== 菜单 =====re.on_draw_ui(function()    if not imgui.tree_node("Monster Target Indicator") then return end    local changed, enabled = imgui.checkbox("Enabled", config.enabled)    if changed then        config.enabled = enabled        clearState()        saveConfig()    end    if not diag.hook then imgui.text("Hook failed (different game version?)") end    if diag.err then imgui.text("Error: " .. diag.err) end    imgui.tree_pop()end)