-- Cached versions of the lua library functions ZO_PI = math.pi ZO_TWO_PI = ZO_PI * 2 ZO_HALF_PI = ZO_PI / 2 ZO_INTEGER_53_MAX_BITS = 53 zo_strlower = LocaleAwareToLower zo_strupper = LocaleAwareToUpper string.lowerbybyte = string.lower string.upperbybyte = string.upper string.lower = zo_strlower string.upper = zo_strupper zo_strsub = string.sub zo_strgsub = string.gsub zo_strlen = string.len zo_strmatch = string.match zo_strgmatch = string.gmatch zo_strfind = string.find zo_plainstrfind = PlainStringFind zo_strsplit = SplitString zo_loadstring = LoadString zo_deg = math.deg zo_rad = math.rad zo_floor = math.floor zo_ceil = math.ceil zo_mod = math.fmod zo_decimalsplit = math.modf zo_abs = math.abs zo_max = math.max zo_min = math.min zo_sqrt = math.sqrt zo_pow = math.pow zo_cos = math.cos zo_sin = math.sin zo_tan = math.tan zo_atan2 = math.atan2 zo_randomseed = math.randomseed zo_random = math.random zo_insecureNext = InsecureNext function zo_randomDecimalRange(min, max) return zo_lerp(min, max, zo_random()) end function zo_insecurePairs(t) return zo_insecureNext, t, nil end function zo_sign(value) if value == 0 then return 0 end return value > 0 and 1 or -1 end local function DefaultComparator(left, _, right) return left - right end function zo_binarysearch(searchData, dataList, comparator) comparator = comparator or DefaultComparator local low, high = 1, #dataList local mid = 0 while low <= high do mid = zo_floor( (low + high) / 2) local compareVal = comparator(searchData, dataList[mid], mid) if(compareVal == 0) then return true, mid elseif(compareVal < 0) then high = mid - 1 else low = mid + 1 end end high = zo_max(high, 1) local numEntries = #dataList while(high <= numEntries) do if(comparator(searchData, dataList[high], high) < 0) then return false, high end high = high + 1 end return false, high end function zo_binaryinsert(item, searchData, dataList, comparator) local _, insertPosition = zo_binarysearch(searchData, dataList, comparator) table.insert(dataList, insertPosition, item) end function zo_binaryremove(searchData, dataList, comparator) local found, removePosition = zo_binarysearch(searchData, dataList, comparator) if found then table.remove(dataList, removePosition) end end function zo_clamp(value, minimum, maximum) if(value <= minimum) then return minimum end if(value >= maximum) then return maximum end return value end function zo_saturate(value) return zo_clamp(value, 0.0, 1.0) end function zo_round(value) return (value >= 0) and zo_floor(value + 0.5) or zo_ceil(value - 0.5) end function zo_roundToZero(value, precision) if precision == 0 then return value end precision = precision or 1 local roundFunction = (value > 0) and zo_floor or zo_ceil return roundFunction(value * (1 / precision)) * precision end function zo_roundToEven(value, precision) if precision == 0 then return value end precision = precision or 1 local floorValue = zo_floor(value * (1 / precision)) if floorValue % 2 == 0 then return floorValue * precision else return (floorValue + 1) * precision end end function zo_roundToNearest(value, precision) if precision == 0 then return value end return zo_round(value * (1 / precision)) * precision end function zo_strjoin(separator, ...) return table.concat({...}, separator) end -- Expected range for amount: [0, 1] function zo_lerp(from, to, amount) return from + amount * (to - from) end -- Expected range for amount: [0, 1] function zo_lerpVector(from, to, amount) local numElements = #from local interpolatedElements = {} for index = 1, numElements do interpolatedElements[index] = from[index] + amount * (to[index] - from[index]) end return interpolatedElements end function zo_frameDeltaNormalizedForTargetFramerate() return GetFrameDeltaNormalizedForTargetFramerate() end function zo_deltaNormalizedLerp(from, to, amount) return zo_lerp(from, to, 1 - math.pow(1 - amount, GetFrameDeltaNormalizedForTargetFramerate())) end function zo_percentBetween(startValue, endValue, value) if startValue == endValue then return 0.0 end return (value - startValue) / (endValue - startValue); end function zo_clampedPercentBetween(startValue, endValue, value) return zo_saturate(zo_percentBetween(startValue, endValue, value)) end function zo_floatsAreEqual(a, b, epsilon) epsilon = epsilon or 0.001 return(zo_abs(a - b) <= epsilon) end -- Parses the first unsigned integer from a string or returns s if s is not a string. function zo_parseUnsignedInteger(s) if type(s) == "string" then return tonumber(s:match("(%d+)")) end return s end function zo_iconFormat(path, width, height) return string.format("|t%s:%s:%s|t", tostring(width), tostring(height), path) end function zo_iconFormatInheritColor(path, width, height) return string.format("|t%s:%s:%s:inheritcolor|t", tostring(width), tostring(height), path) end function zo_iconTextFormat(path, width, height, text, inheritColor) local iconFormatter = zo_iconFormat if inheritColor then iconFormatter = zo_iconFormatInheritColor end return string.format("%s %s", iconFormatter(path, width, height), zo_strformat("<<1>>", text)) end function zo_iconTextFormatNoSpace(path, width, height, text, inheritColor) local iconFormatter = zo_iconFormat if inheritColor then iconFormatter = zo_iconFormatInheritColor end return string.format("%s%s", iconFormatter(path, width, height), zo_strformat("<<1>>", text)) end function zo_iconTextFormatNoSpaceAlignedRight(path, width, height, text, inheritColor) local iconFormatter = zo_iconFormat if inheritColor then iconFormatter = zo_iconFormatInheritColor end return string.format("%s%s", zo_strformat("<<1>>", text), iconFormatter(path, width, height)) end function zo_bulletFormat(label, text) local bulletSpacer = GetString(SI_FORMAT_BULLET_SPACING) local bulletSpacingWidth = label:GetStringWidth(bulletSpacer) label:SetNewLineX(bulletSpacingWidth) label:SetText(zo_strformat(SI_FORMAT_BULLET_TEXT, text)) end function zo_strikethroughTextFormat(text) return string.format("|L0:0:0:45%%:8%%:ignore|l%s|l", text) end function zo_callHandler(object, handler, ...) local handlerFunction = object:GetHandler(handler) if handlerFunction then handlerFunction(object, ...) return true end return false end local ZO_CallLaterId = 1 function zo_callLater(func, ms) local id = ZO_CallLaterId local name = "CallLaterFunction"..id ZO_CallLaterId = ZO_CallLaterId + 1 EVENT_MANAGER:RegisterForUpdate(name, ms, function() EVENT_MANAGER:UnregisterForUpdate(name) func(id) end) return id end function zo_removeCallLater(id) EVENT_MANAGER:UnregisterForUpdate("CallLaterFunction"..id) end do local workingTable = {} function zo_replaceInVarArgs(indexToReplace, itemToReplaceWith, ...) for i = 1, select("#", ...) do if i == indexToReplace then workingTable[i] = itemToReplaceWith else workingTable[i] = select(i, ...) end end return unpack(workingTable, 1, select("#", ...)) end end function zo_mixin(object, ...) for i = 1, select("#", ...) do local source = select(i, ...) for k,v in pairs(source) do object[k] = v end end end function zo_forwardArcSize(startAngle, angle) return (angle - startAngle) % ZO_TWO_PI end function zo_backwardArcSize(startAngle, angle) return ZO_TWO_PI - zo_forwardArcSize(startAngle, angle) end function zo_arcSize(startAngle, angle) return zo_min(zo_forwardArcSize(startAngle, angle), zo_backwardArcSize(startAngle, angle)) end -- id64s are stored as lua Number type, and sometimes generate the same hash key for very similar numbers. -- Use this function to get unique hash key for a given id64. function zo_getSafeId64Key(id) return Id64ToString(id) end function zo_distance(x1, y1, x2, y2) local diffX = x1 - x2 local diffY = y1 - y2 return zo_sqrt(diffX * diffX + diffY * diffY) end function zo_distance3D(x1, y1, z1, x2, y2, z2) local diffX = x1 - x2 local diffY = y1 - y2 local diffZ = z1 - z2 return zo_sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) end function zo_normalize(value, min, max) return (value - min) / (max - min) end function zo_clampLength2D(x, y, maxLength) local distance = zo_sqrt(x * x + y * y) if distance == 0 then -- there is no valid angle for a 0-length vector return 0, 0 end local angleRadians = math.atan2(y, x) distance = zo_clamp(distance, 0, maxLength) return math.cos(angleRadians) * distance, math.sin(angleRadians) * distance end -- Rotate 2D coordinates about the origin by the specified angle. function ZO_Rotate2D(angle, x, y) local cosine = zo_cos(angle) local sine = zo_sin(angle) return x * cosine - y * sine, y * cosine + x * sine end function ZO_ScaleAndRotateTextureCoords(control, angle, originX, originY, scaleX, scaleY) -- protect against 1 / 0 if scaleX == 0 then scaleX = 0.0001 end if scaleY == 0 then scaleY = 0.0001 end local scaleCoefficientX, scaleCoefficientY = 1 / scaleX, 1 / scaleY local topLeftX, topLeftY = ZO_Rotate2D(angle, -0.5 * scaleCoefficientX, -0.5 * scaleCoefficientY) local topRightX, topRightY = ZO_Rotate2D(angle, 0.5 * scaleCoefficientX, -0.5 * scaleCoefficientY) local bottomLeftX, bottomLeftY = ZO_Rotate2D(angle, -0.5 * scaleCoefficientX, 0.5 * scaleCoefficientY) local bottomRightX, bottomRightY = ZO_Rotate2D(angle, 0.5 * scaleCoefficientX, 0.5 * scaleCoefficientY) control:SetVertexUV(VERTEX_POINTS_TOPLEFT, originX + topLeftX, originY + topLeftY) control:SetVertexUV(VERTEX_POINTS_TOPRIGHT, originX + topRightX, originY + topRightY) control:SetVertexUV(VERTEX_POINTS_BOTTOMLEFT, originX + bottomLeftX, originY + bottomLeftY) control:SetVertexUV(VERTEX_POINTS_BOTTOMRIGHT, originX + bottomRightX, originY + bottomRightY) end -- Rotates the vertex UV coordinates of a texture control to simulate 3D rotation. -- Returns the normalized x- and y-axis scale pair. function ZO_RotateTexture3D(textureControl, xRotationRadians, yRotationRadians, zRotationRadians, scaleFactor, normalizedOriginX, normalizedOriginY) scaleFactor = scaleFactor or 1 normalizedOriginX, normalizedOriginY = normalizedOriginX or 0.5, normalizedOriginY or 0.5 local scaleX, scaleY = zo_cos(xRotationRadians), zo_cos(yRotationRadians) local absX, absY = zo_abs(scaleX), zo_abs(scaleY) local signX, signY = zo_sign(scaleX), zo_sign(scaleY) -- Apply easing to minimize visual distortion when both the x- and y- rotations -- converge on right angles concurrently. local normalizedScaleX = zo_lerp(0.15, 1, ZO_EaseInOutCubic(absY)) * signY local normalizedScaleY = zo_lerp(0.15, 1, ZO_EaseInOutCubic(absX)) * signX ZO_ScaleAndRotateTextureCoords(textureControl, zRotationRadians, normalizedOriginX, normalizedOriginY, normalizedScaleX * scaleFactor, normalizedScaleY * scaleFactor) return normalizedScaleX, normalizedScaleY end -- Returns the normalized camera facing [-1, 1] given a normalized x- and y-axis scale pair. --[[ Example: local pitch = zo_rad(0) local yaw = zo_rad(45) local roll = zo_rad(60) local scaleX, scaleY = ZO_RotateTexture3D(myTextureControl, pitch, yaw, roll) local cameraFacingDirection = ZO_GetNormalizedCameraFacingDirectionFromNormalizedAxisScales(scaleX, scaleY) ZO_SetTextureLighting(myTextureControl, cameraFacingDirection) ]] function ZO_GetNormalizedCameraFacingDirectionFromNormalizedAxisScales(normalizedScaleX, normalizedScaleY) local normalizedCameraFacing = ZO_EaseInQuadratic(zo_abs(normalizedScaleX) * zo_abs(normalizedScaleY)) return normalizedCameraFacing end -- Applies basic shading to a texture control in proportion to how directly the control is facing the light source. function ZO_SetTextureLighting(textureControl, normalizedLightSourceFacingDirection, minBrightness, maxBrightness) normalizedLightSourceFacingDirection = normalizedLightSourceFacingDirection or textureControl:GetNormalizedCameraFacing() minBrightness, maxBrightness = minBrightness or 0.1, maxBrightness or 1 -- Apply lighting. local brightness = zo_lerp(minBrightness, maxBrightness, normalizedLightSourceFacingDirection) textureControl:SetTextureSampleProcessingWeight(TEX_SAMPLE_PROCESSING_RGB, brightness) end -- Updates the control's texture coordinates to show a single cell of a texture atlas that is composed of uniformly sized cells. -- Note that cellIndex is 0-based. function ZO_SetTextureCell(control, numColumns, numRows, cellIndex) local cellX = cellIndex % numColumns local cellY = zo_floor(cellIndex / numColumns) local x1, x2 = cellX / numColumns, (cellX + 1) / numColumns local y1, y2 = cellY / numRows, (cellY + 1) / numRows control:SetTextureCoords(x1, x2, y1, y2) end -- Updates the control's texture coordinates to show a single cell of a texture atlas that is composed of uniformly sized cells, -- based upon the number of cells, the interval of the animation loop and the number of seconds that have elapsed. function ZO_SetTextureCellAnimation(control, numColumns, numRows, intervalSeconds, elapsedSeconds) local cellInterval = (elapsedSeconds % intervalSeconds) / intervalSeconds local cellIndex = zo_floor(numColumns * numRows * cellInterval) ZO_SetTextureCell(control, numColumns, numRows, cellIndex) end ZO_FlagHelpers = {} -- Iterator returns each sequential flag in the inclusive range defined by iterationBegin and iterationEnd. -- For example: -- -- for bitValue in ZO_FlagHelpers.FlagIterator(1, 4) do -- d(bitValue) -- end -- -- Produces the output: -- 1 -- 2 -- 4 -- -- The same output is also produced by: -- -- for bitValue in ZO_FlagHelpers.FlagIterator(4) do -- d(bitValue) -- end function ZO_FlagHelpers.FlagIterator(iterationBeginOrEnd, iterationEnd) local iter = iterationBeginOrEnd if not iterationEnd then -- If iterationEnd is omitted then we can infer that: -- iterationBeginOrEnd is the desired ending bit value; and, -- iterationBeginOrEnd assumes the implicit value of 1. iterationEnd = iterationBeginOrEnd iter = 1 end assert(iter > 0) assert(iter <= iterationEnd) return function() if iter <= iterationEnd then local ret = iter iter = iter * 2 -- BitLShift(iter, 1) return ret end end end function ZO_FlagHelpers.MaskHasFlag(mask, flag) return BitAnd(mask, flag) == flag end function ZO_FlagHelpers.MaskHasAnyFlag(mask, ...) for i = 1, select("#", ...) do local flag = select(i, ...) if BitAnd(mask, flag) == flag then return true end end return false end function ZO_FlagHelpers.MasksShareAnyFlag(mask1, mask2) return BitAnd(mask1, mask2) ~= 0 end -- Iterator returns each flag set in the specified mask. -- For example: -- -- local mask = 11 -- 1011 in binary -- for flag in ZO_FlagHelpers.MaskHasFlagsIterator(mask) do -- d(flag) -- end -- -- Produces the output: -- 1 -- 2 -- 8 function ZO_FlagHelpers.MaskHasFlagsIterator(mask) local iter = 1 return function() while iter <= mask do local currentFlag = iter iter = iter * 2 -- BitLShift(iter, 1) if BitAnd(mask, currentFlag) == currentFlag then return currentFlag end end return nil end end function ZO_FlagHelpers.ClearMaskFlag(mask, flag) local flagInverse = BitNot(flag, ZO_INTEGER_53_MAX_BITS) return BitAnd(mask, flagInverse) end function ZO_FlagHelpers.ClearMaskFlags(mask, ...) local flags = {...} local flagsToClear = 0 for _, flag in ipairs(flags) do flagsToClear = BitOr(flagsToClear, flag) end return ZO_FlagHelpers.ClearMaskFlag(mask, flagsToClear) end function ZO_FlagHelpers.SetMaskFlag(mask, flag) return BitOr(mask, flag) end function ZO_FlagHelpers.SetMaskFlags(mask, ...) local flags = {...} for _, flag in ipairs(flags) do mask = BitOr(mask, flag) end return mask end -- Returns nil if no flags have changed; otherwise, -- Returns a table whose keys and values are the flags that -- have changed and their new corresponding Boolean values. function ZO_FlagHelpers.CompareMaskFlags(flagsBefore, flagsAfter) if flagsBefore == flagsAfter then -- No flags have changed. return end local changedFlags = nil -- Higher mask value of either the before or after mask. local maxFlagMask = zo_max(flagsBefore, flagsAfter) -- Begin with first bit mask. local currentFlagMask = 1 while currentFlagMask <= maxFlagMask do -- The new ("after") flag bit value. local flagAfter = BitAnd(flagsAfter, currentFlagMask) -- Has this flag bit value changed? if flagAfter ~= BitAnd(flagsBefore, currentFlagMask) then if not changedFlags then -- Deferred table allocation for performance. changedFlags = {} end -- Key: Flag mask (such as 1 or 4 or 64, etc.) -- Value: Flag bit value (true or false) changedFlags[currentFlagMask] = flagAfter ~= 0 end -- Left shift mask to next highest bit. currentFlagMask = currentFlagMask * 2 end return changedFlags end -- Returns an iterator for which the keys are the names of the functions in -- the callstack ordered from the top to the bottom of the callstack. function ZO_GetCallstackFunctionNamesIterator() local stackTrace = debug.traceback() return zo_strgmatch(stackTrace, "in function '(%S*)'") end -- Returns a numerically indexed table containing the names of the functions -- in the callstack ordered from the top to the bottom of the callstack. -- Specify an optional 'numTopmostFunctionsToExclude' to exclude one or more -- function names starting from the top of the callstack (default is 0). function ZO_GetCallstackFunctionNames(numTopmostFunctionsToExclude) local minFunctionIndex = (numTopmostFunctionsToExclude or 0) + 1 local functionNames = {} local functionIndex = 0 for functionName in ZO_GetCallstackFunctionNamesIterator() do if functionName ~= "ZO_GetCallstackFunctionNames" and functionName ~= "ZO_GetCallstackFunctionNamesIterator" then functionIndex = functionIndex + 1 if functionIndex >= minFunctionIndex then table.insert(functionNames, functionName) end end end return functionNames end -- Returns the name of the function at the specified index in the callstack. -- Note that index 1 is the name of the caller function, index 2 is the name -- of the function that called the caller function, and so on. function ZO_GetCallstackFunctionName(callstackIndex) -- Skip this function and ZO_GetCallstackFunctionNamesIterator. local numFunctionsToSkip = callstackIndex + 1 for functionName in ZO_GetCallstackFunctionNamesIterator() do if numFunctionsToSkip > 0 then numFunctionsToSkip = numFunctionsToSkip - 1 else return functionName end end end -- Returns the name of the current function that is executing. function ZO_GetCurrentFunctionName() -- Skip this function. return ZO_GetCallstackFunctionName(1) end -- Returns the name of the function that called the current function that is executing. function ZO_GetCallerFunctionName() -- Skip this function and the caller function. return ZO_GetCallstackFunctionName(2) end function ZO_GetCallbackForwardingFunction(object, receiverFunction) return function(...) receiverFunction(object, ...) end end function ZO_GetEventForwardingFunction(object, receiverFunction) return function(_, ...) receiverFunction(object, ...) end end -- Returns a reference to the "owner" object instance associated -- with the specified control if such an owner exists. function ZO_GetControlOwnerObject(control) local owner = control.owner or control.object or nil if not owner and control:GetType() ~= CT_TOPLEVELCONTROL then local topLevelControl = control:GetOwningWindow() owner = topLevelControl.owner or topLevelControl.object or nil end return owner end