|
|
|
|
|
|
|
|
|
MapVar("g_Badges", {}, weak_keys_meta) |
|
PersistableGlobals.g_Badges = false |
|
|
|
function OnMsg.DoneMap() |
|
if not g_Badges then return end |
|
for i, t in pairs(g_Badges) do |
|
for ii, b in ipairs(t) do |
|
b:CleanOwnedResources() |
|
end |
|
end |
|
end |
|
|
|
DefineClass.BadgeHolderDialog = { |
|
__parents = { "XDrawCacheDialog" }, |
|
ZOrder = 0, |
|
FocusOnOpen = "" |
|
} |
|
|
|
DefineClass.XBadge = { |
|
__parents = { "InitDone" }, |
|
target = false, |
|
targetIsEntity = false, |
|
targetSpot = false, |
|
zoom = false, |
|
arrowUI = false, |
|
worldObj = false, |
|
ui = false, |
|
visible = true, |
|
visible_user = true, |
|
uiHandleMouse = false, |
|
|
|
preset = false, |
|
done = false, |
|
|
|
|
|
|
|
custom_visibility = false |
|
} |
|
|
|
DefineClass.XBadgeArrow = { |
|
__parents = { "XImage" }, |
|
UseClipBox = false, |
|
Clip = false, |
|
HAlign = "left", |
|
VAlign = "top" |
|
} |
|
|
|
DefineClass.XBadgeEntity = { |
|
__parents = { "Object", "CameraFacingObject" }, |
|
entity = false |
|
} |
|
|
|
function XBadgeEntity:Init() |
|
self:SetCameraFacing(true) |
|
end |
|
|
|
|
|
function XBadge:Setup(target, spot, zoom) |
|
self.target = target |
|
self.targetSpot = spot |
|
self.zoom = zoom |
|
self.targetIsEntity = IsValid(self.target) |
|
|
|
local targetBadges = g_Badges[target] |
|
if targetBadges then |
|
targetBadges[#targetBadges + 1] = self |
|
else |
|
g_Badges[target] = {self} |
|
end |
|
end |
|
|
|
function XBadge:GetUIAttachArgs() |
|
local target, targetSpot, zoom = self.target, self.targetSpot, self.zoom |
|
if IsPoint(target) then |
|
return { |
|
id = "attached_ui", |
|
target = target, |
|
zoom = zoom, |
|
} |
|
elseif targetSpot then |
|
if IsPoint(targetSpot) then |
|
return { |
|
id = "attached_ui", |
|
target = targetSpot, |
|
zoom = zoom, |
|
} |
|
elseif not IsValidEntity(target:GetEntity()) then |
|
return { |
|
id = "attached_ui", |
|
target = target, |
|
spot_type = EntitySpots["Origin"], |
|
} |
|
else |
|
if not target:HasSpot(targetSpot) then |
|
|
|
targetSpot = target:GetSpotName(0) |
|
end |
|
return { |
|
id = "attached_ui", |
|
target = target, |
|
spot_type = EntitySpots[targetSpot], |
|
zoom = zoom, |
|
} |
|
end |
|
else |
|
return { |
|
id = "attached_ui", |
|
target = target, |
|
zoom = zoom, |
|
} |
|
end |
|
end |
|
|
|
|
|
|
|
function XBadge:SetupArrow(template, settings) |
|
EnsureDialog("BadgeHolderDialog") |
|
|
|
if type(template) ~= "string" then template = false end |
|
local arrowUI = XTemplateSpawn(template or "XBadgeArrow", GetDialog("BadgeHolderDialog"), settings and settings.context) |
|
|
|
local mode = const.badgeOn |
|
if settings and settings.no_rotate then |
|
mode = const.badgeNoRotate |
|
end |
|
local attachArgs = self:GetUIAttachArgs() |
|
if attachArgs then |
|
attachArgs.faceTargetOffScreen = mode |
|
arrowUI:AddDynamicPosModifier(attachArgs) |
|
end |
|
arrowUI:Open() |
|
self.arrowUI = arrowUI |
|
end |
|
|
|
|
|
function XBadge:SetupEntity(entity, attachOffset) |
|
|
|
local customEntity = g_Classes[entity] and PlaceObject(entity) |
|
local badgeObj = customEntity or PlaceObject("XBadgeEntity") |
|
if not badgeObj:ChangeEntity(entity) then |
|
badgeObj:ChangeEntity(entity) |
|
end |
|
|
|
if customEntity and IsKindOf(badgeObj, "CameraFacingSign") then |
|
self.targetSpot = badgeObj.attach_spot or self.targetSpot |
|
attachOffset = attachOffset or badgeObj.attach_offset |
|
end |
|
|
|
|
|
if self.targetIsEntity then |
|
if self.targetSpot then |
|
if IsPoint(self.targetSpot) then |
|
self.target:Attach(self.targetSpot) |
|
else |
|
self.target:Attach(badgeObj, self.target:GetSpotBeginIndex(self.targetSpot)) |
|
end |
|
else |
|
self.target:Attach(badgeObj) |
|
end |
|
if attachOffset then badgeObj:SetAttachOffset(attachOffset) end |
|
else |
|
local pos = self.target |
|
if attachOffset then pos = pos + attachOffset end |
|
badgeObj:SetPos(pos) |
|
end |
|
badgeObj:SetGameFlags(const.gofNoDepthTest) |
|
self.worldObj = badgeObj |
|
end |
|
|
|
|
|
function XBadge:SetupBadgeUI(uiElement, dlgOverride) |
|
EnsureDialog("BadgeHolderDialog") |
|
self.ui = uiElement |
|
rawset(uiElement, "xbadge-instance", self) |
|
|
|
local attachArgs = self:GetUIAttachArgs() |
|
if attachArgs then uiElement:AddDynamicPosModifier(attachArgs) end |
|
local oldDestroy = uiElement.OnDelete |
|
uiElement.OnDelete = function() |
|
oldDestroy(uiElement) |
|
if not self.done then |
|
self:Done() |
|
end |
|
end |
|
uiElement:SetParent(dlgOverride or GetDialog("BadgeHolderDialog")) |
|
uiElement:Open() |
|
return uiElement |
|
end |
|
|
|
function XBadge:CleanOwnedResources() |
|
self.done = true |
|
if self.arrowUI and self.arrowUI.window_state ~= "destroying" then |
|
self.arrowUI:Close() |
|
self.arrowUI = false |
|
end |
|
if self.ui and self.ui.window_state ~= "destroying" then |
|
self.ui:Close() |
|
self.ui = false |
|
end |
|
if self.worldObj then |
|
DoneObject(self.worldObj) |
|
self.worldObj = false |
|
end |
|
end |
|
|
|
function XBadge:Done() |
|
self:CleanOwnedResources() |
|
|
|
local targetBadges = g_Badges[self.target] |
|
local idx = table.find(targetBadges, self) |
|
if targetBadges and idx then table.remove(targetBadges, idx) end |
|
|
|
self:UpdateVisibilityForMyTarget() |
|
self.target = false |
|
end |
|
|
|
function XBadge:SetVisible(visible) |
|
if self.visible_user == visible then return end |
|
self.visible_user = visible |
|
self:UpdateVisibilityForMyTarget() |
|
end |
|
|
|
|
|
function XBadge:IsBadgeVisibleUserLogic() |
|
return self.visible_user |
|
end |
|
|
|
function XBadge:SetVisibleInternal(visible) |
|
self.visible = visible |
|
if self.arrowUI then self.arrowUI:SetVisible(visible) end |
|
if self.ui then self.ui:SetVisible(visible) end |
|
if self.worldObj then |
|
if visible then |
|
self.worldObj:SetEnumFlags(const.efVisible) |
|
else |
|
self.worldObj:ClearEnumFlags(const.efVisible) |
|
end |
|
end |
|
end |
|
|
|
function XBadge:UpdateVisibilityForMyTarget() |
|
local target = self.target |
|
local badges = g_Badges[target] |
|
if not badges then return end |
|
local foundVisible = false |
|
for i = #badges, 1, -1 do |
|
local current = badges[i] |
|
|
|
if not current.custom_visibility then |
|
if not foundVisible and current:IsBadgeVisibleUserLogic() then |
|
foundVisible = true |
|
current:SetVisibleInternal(true) |
|
else |
|
current:SetVisibleInternal(false) |
|
end |
|
else |
|
current:SetVisibleInternal(current:IsBadgeVisibleUserLogic()) |
|
end |
|
end |
|
|
|
Msg("BadgeVisibilityUpdated") |
|
end |
|
|
|
|
|
function SpawnBadge(badgeClass, targetArgs, hasArrow, arrowSettings) |
|
if not targetArgs then return false end |
|
local badge = _G[badgeClass or "XBadge"]:new() |
|
|
|
if not targetArgs["class"] and type(targetArgs) == "table" then |
|
local target = targetArgs["target"] |
|
local spot = targetArgs["spot"] |
|
local zoom = targetArgs["zoom"] |
|
badge:Setup(target, spot, zoom) |
|
else |
|
badge:Setup(targetArgs) |
|
end |
|
|
|
if hasArrow then badge:SetupArrow(hasArrow, arrowSettings) end |
|
return badge |
|
end |
|
|
|
|
|
function SpawnBadgeUI(badgeClass, targetArgs, hasArrow, uiTemplate, context) |
|
if not targetArgs then return false end |
|
local badge = SpawnBadge(badgeClass, targetArgs, hasArrow) |
|
if uiTemplate then badge:SetupBadgeUI(XTemplateSpawn(uiTemplate, nil, context)) end |
|
badge:UpdateVisibilityForMyTarget() |
|
return badge |
|
end |
|
|
|
|
|
function SpawnBadgeEntity(badgeClass, targetArgs, hasArrow, badgeEntity, attachOffset) |
|
if not targetArgs then return false end |
|
local badge = SpawnBadge(badgeClass, targetArgs, hasArrow) |
|
if badgeEntity then |
|
badge:SetupEntity(badgeEntity, attachOffset) |
|
assert(badge.worldObj) |
|
end |
|
badge:UpdateVisibilityForMyTarget() |
|
return badge |
|
end |
|
|
|
function CreateBadgeFromPreset(presetName, target, uiContext, dlgOverride) |
|
local preset = BadgePresetDefs[presetName] |
|
if not preset then return false end |
|
|
|
local targetArgs = target |
|
if preset.AttachSpotName or preset.ZoomUI then |
|
targetArgs = { target = target, spot = preset.AttachSpotName, zoom = preset.ZoomUI } |
|
end |
|
|
|
local badge = SpawnBadge(false, targetArgs, preset.ArrowTemplate, { no_rotate = preset.noRotate, context = uiContext }) |
|
badge.preset = presetName |
|
if preset.noHide then badge.custom_visibility = true end |
|
local ui |
|
if preset.UITemplate then |
|
ui = badge:SetupBadgeUI(XTemplateSpawn(preset.UITemplate, nil, uiContext), dlgOverride) |
|
if ui and preset.handleMouse then |
|
badge:SetHandleMouse(true) |
|
end |
|
end |
|
if preset.EntityName then |
|
badge:SetupEntity(preset.EntityName, preset.attachOffset) |
|
end |
|
|
|
|
|
table.sort(g_Badges[badge.target] or empty_table, function(a, b) |
|
local presetA = a.preset |
|
local presetB = b.preset |
|
if not presetA or not presetB then return end |
|
presetA = BadgePresetDefs[presetA] |
|
presetB = BadgePresetDefs[presetB] |
|
return (presetA.BadgePriority or 0) < (presetB.BadgePriority or 0) |
|
end) |
|
|
|
badge:UpdateVisibilityForMyTarget() |
|
return badge, ui |
|
end |
|
|
|
function XBadge:SetHandleMouse(on) |
|
local ui = self.ui |
|
self.uiHandleMouse = on |
|
|
|
ui:DeleteThread("badgeMouseThread") |
|
ui.interaction_box = false |
|
ui:SetHandleMouse(on) |
|
if not on then return end |
|
|
|
local attachArgs = self:GetUIAttachArgs() |
|
ui:CreateThread("badgeMouseThread", function(ctrl, uiTarget, uiSpotType, zoom) |
|
local targetIsPos = IsPoint(uiTarget) |
|
local uiSpotIdx = not targetIsPos and uiSpotType and uiTarget:HasSpot(uiSpotType) and uiTarget:GetSpotBeginIndex(uiSpotType) |
|
local full_scale = point(1000, 1000) |
|
local last_x, last_y, last_scale |
|
while ctrl.window_state ~= "destroying" and (targetIsPos or IsValid(uiTarget)) do |
|
if ctrl.visible then |
|
local pos_x, pos_y, pos_z |
|
if targetIsPos then |
|
pos_x = uiTarget |
|
elseif uiTarget:IsValidPos() then |
|
if uiSpotIdx then |
|
pos_x, pos_y, pos_z = uiTarget:GetSpotLocPosXYZ(uiSpotIdx) |
|
else |
|
pos_x, pos_y, pos_z = uiTarget:GetVisualPosXYZ() |
|
end |
|
end |
|
local front, screen_x, screen_y |
|
if pos_x then |
|
front, screen_x, screen_y = GameToScreenXY(pos_x, pos_y, pos_z) |
|
end |
|
if front then |
|
local x, y = screen_x, screen_y |
|
if not ctrl.DontAddBoxToInteractionBox then |
|
x = x + ctrl.box:minx() |
|
y = y + ctrl.box:miny() |
|
end |
|
local scale = full_scale |
|
if zoom then |
|
scale = UIL.GetDynamicPosZoomScale(point(pos_x, pos_y, pos_z)) |
|
scale = point(scale, scale) |
|
end |
|
if x ~= last_x or y ~= last_y or scale ~= last_scale then |
|
ctrl:InvalidateInteractionBox() |
|
ctrl:SetInteractionBox(x, y, scale, true) |
|
last_x, last_y, last_scale = x, y, scale |
|
end |
|
else |
|
ctrl:InvalidateInteractionBox() |
|
ctrl.interaction_box = empty_box |
|
last_x, last_y, last_scale = nil, nil, nil |
|
end |
|
end |
|
Sleep(50) |
|
end |
|
end, ui, attachArgs.target, attachArgs.spot_type, attachArgs.zoom) |
|
end |
|
|
|
|
|
function DeleteBadgesFromTarget(target) |
|
local t = g_Badges[target] |
|
if not t then return end |
|
|
|
for i, b in pairs(t) do |
|
b:CleanOwnedResources() |
|
end |
|
|
|
g_Badges[target] = nil |
|
end |
|
|
|
function TargetHasBadgeOfPreset(preset, target) |
|
local t = g_Badges[target] |
|
if not t then return false end |
|
for i = #t, 1, -1 do |
|
if t[i].preset == preset then |
|
return t[i] |
|
end |
|
end |
|
end |
|
|
|
|
|
function DeleteBadgesFromTargetOfPreset(preset, target) |
|
local t = g_Badges[target] |
|
if not t then return end |
|
for i = #t, 1, -1 do |
|
if t[i].preset == preset then |
|
t[i]:Done() |
|
end |
|
end |
|
end |