234 lines
7.3 KiB
Lua
234 lines
7.3 KiB
Lua
--[[
|
||
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-1,Python *255 还原)
|
||
-- G: 耐久度 (0-1)
|
||
-- B: 死亡状态 (0=存活, 0.5=死尸, 1.0=灵魂)
|
||
pixels[6]:SetColorTexture(free / 255, repair, dead)
|
||
|
||
-- [7][8] 地图坐标与朝向(C_Map 现代 API,0~1 比例)
|
||
local mapX, mapY = GetPlayerCoords()
|
||
local facing = GetPlayerFacing() or 0
|
||
-- [7] X:R=整数部分(×100),G=小数部分,B=朝向/2π
|
||
pixels[7]:SetColorTexture(
|
||
math.floor(mapX * 100) / 255,
|
||
math.floor((mapX * 10000) % 100) / 255,
|
||
facing / 6.28318
|
||
)
|
||
-- [8] Y:R=整数部分(×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 查看命令。")
|