Files
wow/LogicBeacon/LogicBeacon.lua
2026-03-18 09:30:49 +08:00

234 lines
7.3 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[
LogicBeacon - 多状态像素编码器
在屏幕角落显示横向像素条,每个像素对应一个游戏状态,
供外部 Python 等脚本截取小区域即可获取完整状态机。
]]
-- 配置8个状态位1个白色锚点 + 7个数据位
local PIXEL_SIZE = 10
local STATUS_COUNT = 8
local ADDON_NAME = "LogicBeacon"
-- 默认配置(可被 SavedVariables 覆盖)
local defaults = {
pixelSize = PIXEL_SIZE,
statusCount = STATUS_COUNT,
point = "TOPLEFT",
x = 10, -- 整体向右偏移 10 像素
y = 0,
scale = 1,
locked = false,
}
-- 确保每角色存储存在
LogicBeaconDB = LogicBeaconDB or {}
for k, v in pairs(defaults) do
if LogicBeaconDB[k] == nil then
LogicBeaconDB[k] = v
end
end
-- 升级:至少 8 个状态位(含目标位+后勤位+坐标朝向)
LogicBeaconDB.statusCount = math.max(LogicBeaconDB.statusCount or 0, 8)
local db = LogicBeaconDB
-- 主框架
local f = CreateFrame("Frame", "LogicBeaconFrame", UIParent)
local totalW = db.pixelSize * db.statusCount
local totalH = db.pixelSize
f:SetSize(totalW, totalH)
f:SetScale(db.scale)
f:SetPoint(db.point, UIParent, db.point, db.x, db.y)
f:SetFrameStrata("TOOLTIP")
f:SetClampedToScreen(true)
-- 背景底色(防止半透明干扰)
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints()
bg:SetColorTexture(0, 0, 0, 1)
-- 像素纹理([1]=锚点 [2~8]=数据位)
local pixels = {}
for i = 1, db.statusCount do
local tex = f:CreateTexture(nil, "OVERLAY")
tex:SetSize(db.pixelSize, db.pixelSize)
tex:SetPoint("LEFT", f, "LEFT", (i - 1) * db.pixelSize, 0)
pixels[i] = tex
end
-- 后勤位:背包空格、装备耐久、死亡/灵魂状态
local function GetLogisticsInfo()
-- 1. 检查背包空格 (0 - 100+)
local freeSlots = 0
for i = 0, 4 do
local numFree = GetContainerNumFreeSlots(i)
freeSlots = freeSlots + numFree
end
-- 2. 检查装备最低耐久度 (0.0 - 1.0)
local minDurability = 1
for i = 1, 18 do
local current, maximum = GetInventoryItemDurability(i)
if current and maximum and maximum > 0 then
minDurability = math.min(minDurability, current / maximum)
end
end
-- 3. 死亡状态 (0:存活, 0.5:死尸, 1.0:灵魂)
local dState = 0
if UnitIsGhost("player") then
dState = 1.0
elseif UnitIsDead("player") then
dState = 0.5
end
return freeSlots, minDurability, dState
end
-- 地图坐标(现代 C_Map API返回 0~1 比例)
local function GetPlayerCoords()
local mapID = C_Map.GetBestMapForUnit("player")
if mapID then
local mapPos = C_Map.GetPlayerMapPosition(mapID, "player")
if mapPos then
return mapPos:GetXY()
end
end
return 0, 0
end
f:SetScript("OnUpdate", function()
-- [1] 锚点:永远纯白 (255, 255, 255)
pixels[1]:SetColorTexture(1, 1, 1)
-- [2] 血量:红色通道
local hp = UnitHealth("player") / math.max(1, UnitHealthMax("player"))
local targetHp = UnitHealth("target") / math.max(1, UnitHealthMax("target"))
pixels[2]:SetColorTexture(hp, targetHp, 0)
-- [3] 法力/能量:蓝色通道
local mp = UnitPower("player") / math.max(1, UnitPowerMax("player"))
pixels[3]:SetColorTexture(0, 0, mp)
-- [4] 战斗状态:绿色通道 (255=战斗中)
if UnitAffectingCombat("player") then
pixels[4]:SetColorTexture(0, 1, 0)
else
pixels[4]:SetColorTexture(0, 0, 0)
end
-- [5] 目标状态:红色通道 (255=有敌对目标)
-- 逻辑:可以攻击 AND 目标不是玩家 AND 不是灰名怪 AND 不是玩家宠物
if UnitCanAttack("player", "target") and not UnitIsPlayer("target") and not UnitIsTapDenied("target") and not UnitPlayerControlled("target") then
-- 只有满足这三个条件,才输出“有效目标”信号(红色)
pixels[5]:SetColorTexture(1, 0, 0)
else
-- 只要其中一个条件不满足(比如怪被抢了,或者是玩家),就输出黑色
pixels[5]:SetColorTexture(0, 0, 0)
end
-- [6] 综合后勤与死亡位
local free, repair, dead = GetLogisticsInfo()
-- R: 空格数 (0-1Python *255 还原)
-- G: 耐久度 (0-1)
-- B: 死亡状态 (0=存活, 0.5=死尸, 1.0=灵魂)
pixels[6]:SetColorTexture(free / 255, repair, dead)
-- [7][8] 地图坐标与朝向C_Map 现代 API0~1 比例)
local mapX, mapY = GetPlayerCoords()
local facing = GetPlayerFacing() or 0
-- [7] XR=整数部分(×100)G=小数部分B=朝向/2π
pixels[7]:SetColorTexture(
math.floor(mapX * 100) / 255,
math.floor((mapX * 10000) % 100) / 255,
facing / 6.28318
)
-- [8] YR=整数部分(×100)G=小数部分B=预留
pixels[8]:SetColorTexture(
math.floor(mapY * 100) / 255,
math.floor((mapY * 10000) % 100) / 255,
0
)
end)
-- 可拖动
f:SetMovable(not db.locked)
f:EnableMouse(not db.locked)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function()
if not db.locked then
f:StartMoving()
end
end)
f:SetScript("OnDragStop", function()
f:StopMovingOrSizing()
local _, _, _, x, y = f:GetPoint(1)
db.x = x
db.y = y
end)
-- 右键菜单:锁定/解锁、重置位置
local menu = CreateFrame("Frame", "LogicBeaconMenu", f, "UIDropDownMenuTemplate")
f:SetScript("OnMouseDown", function(_, button)
if button == "RightButton" then
local menuList = {
{
text = db.locked and "解锁位置" or "锁定位置",
func = function()
db.locked = not db.locked
f:SetMovable(not db.locked)
f:EnableMouse(not db.locked)
end,
},
{
text = "重置到左上角",
func = function()
db.point = "TOPLEFT"
db.x = 10
db.y = 0
f:ClearAllPoints()
f:SetPoint(db.point, UIParent, db.point, db.x, db.y)
end,
},
}
EasyMenu(menuList, menu, "cursor", 0, 0, "MENU")
end
end)
-- 登录完成后确保显示
local event = CreateFrame("Frame")
event:RegisterEvent("PLAYER_LOGIN")
event:SetScript("OnEvent", function()
f:Show()
end)
-- Slash 命令
SlashCmdList["LOGICBEACON"] = function(msg)
msg = msg:lower():gsub("^%s*(.-)%s*$", "%1")
if msg == "lock" or msg == "锁定" then
db.locked = true
f:SetMovable(false)
f:EnableMouse(false)
print("|cff00ff00[LogicBeacon]|r 已锁定")
elseif msg == "unlock" or msg == "解锁" then
db.locked = false
f:SetMovable(true)
f:EnableMouse(true)
print("|cff00ff00[LogicBeacon]|r 已解锁")
elseif msg == "reset" or msg == "重置" then
db.point, db.x, db.y = "TOPLEFT", 10, 0
f:ClearAllPoints()
f:SetPoint(db.point, UIParent, db.point, db.x, db.y)
print("|cff00ff00[LogicBeacon]|r 已重置到左上角")
else
print("|cff00ff00[LogicBeacon]|r 用法: /lb lock|unlock|reset (锁定|解锁|重置位置)")
end
end
SLASH_LOGICBEACON1 = "/lb"
SLASH_LOGICBEACON2 = "/logicbeacon"
print("|cff00ff00[LogicBeacon]|r 已加载。右键像素条可锁定/重置,/lb 查看命令。")