Name | Type | is_array | initial_value |
---@diagnostic disable: undefined-global
if Debug then Debug.beginFile 'TotalInitialization' end
--[[ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Total Initialization version 5.3.1
Created by: Bribe
Contributors: Eikonium, HerlySQR, Tasyen, Luashine, Forsakn
Inspiration: Almia, ScorpioT1000, Troll-Brain
Hosted at: https://github.com/BribeFromTheHive/Lua/blob/master/TotalInitialization.lua
Debug library hosted at: https://www.hiveworkshop.com/threads/debug-utils-ingame-console-etc.330758/
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ]]
---Calls the user's initialization function during the map's loading process. The first argument should either be the init function,
---or it should be the string to give the initializer a name (works similarly to a module name/identically to a vJass library name).
---
---To use requirements, call `Require.strict 'LibraryName'` or `Require.optional 'LibraryName'`. Alternatively, the OnInit callback
---function can take the `Require` table as a single parameter: `OnInit(function(import) import.strict 'ThisIsTheSameAsRequire' end)`.
---
-- - `OnInit.global` or just `OnInit` is called after InitGlobals and is the standard point to initialize.
-- - `OnInit.trig` is called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events.
-- - `OnInit.map` is the last point in initialization before the loading screen is completed.
-- - `OnInit.final` occurs immediately after the loading screen has disappeared, and the game has started.
---@class OnInit
--
--Simple Initialization without declaring a library name:
---@overload async fun(initCallback: Initializer.Callback)
--
--Advanced initialization with a library name and an optional third argument to signal to Eikonium's DebugUtils that the file has ended.
---@overload async fun(libraryName: string, initCallback: Initializer.Callback, debugLineNum?: integer)
--
--A way to yield your library to allow other libraries in the same initialization sequence to load, then resume once they have loaded.
---@overload async fun(customInitializerName: string)
OnInit = {}
---@alias Initializer.Callback fun(require?: Requirement | {[string]: Requirement}):...?
---@alias Requirement async fun(reqName: string, source?: table): unknown
-- `Require` will yield the calling `OnInit` initialization function until the requirement (referenced as a string) exists. It will check the
-- global API (for example, does 'GlobalRemap' exist) and then check for any named OnInit resources which might use that same string as its name.
--
-- Due to the way Sumneko's syntax highlighter works, the return value will only be linted for defined @class objects (and doesn't work for regular
-- globals like `TimerStart`). I tried to request the functionality here: https://github.com/sumneko/lua-language-server/issues/1792 , however it
-- was closed. Presumably, there are other requests asking for it, but I wouldn't count on it.
--
-- To declare a requirement, use: `Require.strict 'SomeLibrary'` or (if you don't care about the missing linting functionality) `Require 'SomeLibrary'`
--
-- To optionally require something, use any other suffix (such as `.optionally` or `.nonstrict`): `Require.optional 'SomeLibrary'`
--
---@class Require: { [string]: Requirement }
---@overload async fun(reqName: string, source?: table): string
Require = {}
do
local library = {} --You can change this to false if you don't use `Require` nor the `OnInit.library` API.
--CONFIGURABLE LEGACY API FUNCTION:
---@param _ENV table
---@param OnInit any
local function assignLegacyAPI(_ENV, OnInit)
OnGlobalInit = OnInit; OnTrigInit = OnInit.trig; OnMapInit = OnInit.map; OnGameStart = OnInit.final --Global Initialization Lite API
--OnMainInit = OnInit.main; OnLibraryInit = OnInit.library; OnGameInit = OnInit.final --short-lived experimental API
--onGlobalInit = OnInit; onTriggerInit = OnInit.trig; onInitialization = OnInit.map; onGameStart = OnInit.final --original Global Initialization API
--OnTriggerInit = OnInit.trig; OnInitialization = OnInit.map --Forsakn's Ordered Indices API
end
--END CONFIGURABLES
local _G, rawget, insert =
_G, rawget, table.insert
local initFuncQueue = {}
---@param name string
---@param continue? function
local function runInitializers(name, continue)
--print('running:', name, tostring(initFuncQueue[name]))
if initFuncQueue[name] then
for _,func in ipairs(initFuncQueue[name]) do
coroutine.wrap(func)(Require)
end
initFuncQueue[name] = nil
end
if library then
library:resume()
end
if continue then
continue()
end
end
local function initEverything()
---@param hookName string
---@param continue? function
local function hook(hookName, continue)
local hookedFunc = rawget(_G, hookName)
if hookedFunc then
rawset(_G, hookName,
function()
hookedFunc()
runInitializers(hookName, continue)
end
)
else
runInitializers(hookName, continue)
end
end
hook(
'InitGlobals',
function()
hook(
'InitCustomTriggers',
function()
hook('RunInitializationTriggers')
end
)
end
)
hook(
'MarkGameStarted',
function()
if library then
for _,func in ipairs(library.queuedInitializerList) do
func(nil, true) --run errors for missing requirements.
end
for _,func in pairs(library.yieldedModuleMatrix) do
func(true) --run errors for modules that aren't required.
end
end
OnInit = nil
Require = nil
end
)
end
---@param initName string
---@param libraryName string | Initializer.Callback
---@param func? Initializer.Callback
---@param debugLineNum? integer
---@param incDebugLevel? boolean
local function addUserFunc(initName, libraryName, func, debugLineNum, incDebugLevel)
if not func then
---@cast libraryName Initializer.Callback
func = libraryName
else
assert(type(libraryName) == 'string')
if debugLineNum and Debug then
Debug.beginFile(libraryName, incDebugLevel and 3 or 2)
Debug.data.sourceMap[#Debug.data.sourceMap].lastLine = debugLineNum
end
if library then
func = library:create(libraryName, func)
end
end
assert(type(func) == 'function')
--print('adding user func: ' , initName , libraryName, debugLineNum, incDebugLevel)
initFuncQueue[initName] = initFuncQueue[initName] or {}
insert(initFuncQueue[initName], func)
if initName == 'root' or initName == 'module' then
runInitializers(initName)
end
end
---@param name string
local function createInit(name)
---@async
---@param libraryName string --Assign your callback a unique name, allowing other OnInit callbacks can use it as a requirement.
---@param userInitFunc Initializer.Callback --Define a function to be called at the chosen point in the initialization process. It can optionally take the `Require` object as a parameter. Its optional return value(s) are passed to a requiring library via the `Require` object (defaults to `true`).
---@param debugLineNum? integer --If the Debug library is present, you can call Debug.getLine() for this parameter (which should coincide with the last line of your script file). This will neatly tie-in with OnInit's built-in Debug library functionality to define a starting line and an ending line for your module.
---@overload async fun(userInitFunc: Initializer.Callback)
return function(libraryName, userInitFunc, debugLineNum)
addUserFunc(name, libraryName, userInitFunc, debugLineNum)
end
end
OnInit.global = createInit 'InitGlobals' -- Called after InitGlobals, and is the standard point to initialize.
OnInit.trig = createInit 'InitCustomTriggers' -- Called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events.
OnInit.map = createInit 'RunInitializationTriggers' -- Called last in the script's loading screen sequence. Runs after the GUI "Map Initialization" events have run.
OnInit.final = createInit 'MarkGameStarted' -- Called immediately after the loading screen has disappeared, and the game has started.
do
---@param self table
---@param libraryNameOrInitFunc function | string
---@param userInitFunc function
---@param debugLineNum number
local function __call(
self,
libraryNameOrInitFunc,
userInitFunc,
debugLineNum
)
if userInitFunc or type(libraryNameOrInitFunc) == 'function' then
addUserFunc(
'InitGlobals', --Calling OnInit directly defaults to OnInit.global (AKA OnGlobalInit)
libraryNameOrInitFunc,
userInitFunc,
debugLineNum,
true
)
elseif library then
library:declare(libraryNameOrInitFunc) --API handler for OnInit "Custom initializer"
else
error(
"Bad OnInit args: "..
tostring(libraryNameOrInitFunc) .. ", " ..
tostring(userInitFunc)
)
end
end
setmetatable(OnInit --[[@as table]], { __call = __call })
end
do --if you don't need the initializers for 'root', 'config' and 'main', you can delete this do...end block.
local gmt = getmetatable(_G) or
getmetatable(setmetatable(_G, {}))
local rawIndex = gmt.__newindex or rawset
local hookMainAndConfig
---@param _G table
---@param key string
---@param fnOrDiscard unknown
function hookMainAndConfig(_G, key, fnOrDiscard)
if key == 'main' or key == 'config' then
---@cast fnOrDiscard function
if key == 'main' then
runInitializers 'root'
end
rawIndex(_G, key, function()
if key == 'config' then
fnOrDiscard()
elseif gmt.__newindex == hookMainAndConfig then
gmt.__newindex = rawIndex --restore the original __newindex if no further hooks on __newindex exist.
end
runInitializers(key)
if key == 'main' then
fnOrDiscard()
end
end)
else
rawIndex(_G, key, fnOrDiscard)
end
end
gmt.__newindex = hookMainAndConfig
OnInit.root = createInit 'root' -- Runs immediately during the Lua root, but is yieldable (allowing requirements) and pcalled.
OnInit.config = createInit 'config' -- Runs when `config` is called. Credit to @Luashine: https://www.hiveworkshop.com/threads/inject-main-config-from-we-trigger-code-like-jasshelper.338201/
OnInit.main = createInit 'main' -- Runs when `main` is called. Idea from @Tasyen: https://www.hiveworkshop.com/threads/global-initialization.317099/post-3374063
end
if library then
library.queuedInitializerList = {}
library.customDeclarationList = {}
library.yieldedModuleMatrix = {}
library.moduleValueMatrix = {}
function library:pack(name, ...)
self.moduleValueMatrix[name] = table.pack(...)
end
function library:resume()
if self.queuedInitializerList[1] then
local continue, tempQueue, forceOptional
::initLibraries::
repeat
continue=false
self.queuedInitializerList, tempQueue =
{}, self.queuedInitializerList
for _,func in ipairs(tempQueue) do
if func(forceOptional) then
continue=true --Something was initialized; therefore further systems might be able to initialize.
else
insert(self.queuedInitializerList, func) --If the queued initializer returns false, that means its requirement wasn't met, so we re-queue it.
end
end
until not continue or not self.queuedInitializerList[1]
if self.customDeclarationList[1] then
self.customDeclarationList, tempQueue =
{}, self.customDeclarationList
for _,func in ipairs(tempQueue) do
func() --unfreeze any custom initializers.
end
elseif not forceOptional then
forceOptional = true
else
return
end
goto initLibraries
end
end
local function declareName(name, initialValue)
assert(type(name) == 'string')
assert(library.moduleValueMatrix[name] == nil)
library.moduleValueMatrix[name] =
initialValue and { true, n = 1 }
end
function library:create(name, userFunc)
assert(type(userFunc) == 'function')
declareName(name, false) --declare itself as a non-loaded library.
return function()
self:pack(name, userFunc(Require)) --pack return values to allow multiple values to be communicated.
if self.moduleValueMatrix[name].n == 0 then
self:pack(name, true) --No values were returned; therefore simply package the value as `true`
end
end
end
---@async
function library:declare(name)
declareName(name, true) --declare itself as a loaded library.
local co = coroutine.running()
insert(
self.customDeclarationList,
function()
coroutine.resume(co)
end
)
coroutine.yield() --yields the calling function until after all currently-queued initializers have run.
end
local processRequirement
---@async
function processRequirement(
optional,
requirement,
explicitSource
)
if type(optional) == 'string' then
optional, requirement, explicitSource =
true, optional, requirement --optional requirement (processed by the __index method)
else
optional = false --strict requirement (processed by the __call method)
end
local source = explicitSource or _G
assert(type(source)=='table')
assert(type(requirement)=='string')
::reindex::
local subSource, subReq =
requirement:match("([\x25w_]+)\x25.(.+)") --Check if user is requiring using "table.property" syntax
if subSource and subReq then
source,
requirement =
processRequirement(subSource, source), --If the container is nil, yield until it is not.
subReq
if type(source)=='table' then
explicitSource = source
goto reindex --check for further nested properties ("table.property.subProperty.anyOthers").
else
return --The source table for the requirement wasn't found, so disregard the rest (this only happens with optional requirements).
end
end
local function loadRequirement(unpack)
local package = rawget(source, requirement) --check if the requirement exists in the host table.
if not package and not explicitSource then
if library.yieldedModuleMatrix[requirement] then
library.yieldedModuleMatrix[requirement]() --load module if it exists
end
package = library.moduleValueMatrix[requirement] --retrieve the return value from the module.
if unpack and type(package)=='table' then
return table.unpack(package, 1, package.n) --using unpack allows any number of values to be returned by the required library.
end
end
return package
end
local co, loaded
local function checkReqs(forceOptional, printErrors)
if not loaded then
loaded = loadRequirement()
loaded = loaded or optional and
(loaded==nil or forceOptional)
if loaded then
if co then coroutine.resume(co) end --resume only if it was yielded in the first place.
return loaded
elseif printErrors then
coroutine.resume(co, true)
end
end
end
if not checkReqs() then --only yield if the requirement doesn't already exist.
co = coroutine.running()
insert(library.queuedInitializerList, checkReqs)
if coroutine.yield() then
error("Missing Requirement: "..requirement) --handle the error within the user's function to get an accurate stack trace via the `try` function.
end
end
return loadRequirement(true)
end
---@type Requirement
function Require.strict(name, explicitSource)
return processRequirement(nil, name, explicitSource)
end
setmetatable(Require --[[@as table]], {
__call = processRequirement,
__index = function()
return processRequirement
end
})
local module = createInit 'module'
--- `OnInit.module` will only call the OnInit function if the module is required by another resource, rather than being called at a pre-
--- specified point in the loading process. It works similarly to Go, in that including modules in your map that are not actually being
--- required will throw an error message.
---@param name string
---@param func fun(require?: Initializer.Callback):any
---@param debugLineNum? integer
OnInit.module = function(name, func, debugLineNum)
if func then
local userFunc = func
func = function(require)
local co = coroutine.running()
library.yieldedModuleMatrix[name] =
function(failure)
library.yieldedModuleMatrix[name] = nil
coroutine.resume(co, failure)
end
if coroutine.yield() then
error("Module declared but not required: "..name)
end
return userFunc(require)
end
end
module(name, func, debugLineNum)
end
end
if assignLegacyAPI then --This block handles legacy code.
---Allows packaging multiple requirements into one table and queues the initialization for later.
---@deprecated
---@param initList string | table
---@param userFunc function
function OnInit.library(initList, userFunc)
local typeOf = type(initList)
assert(typeOf=='table' or typeOf=='string')
assert(type(userFunc) == 'function')
local function caller(use)
if typeOf=='string' then
use(initList)
else
for _,initName in ipairs(initList) do
use(initName)
end
if initList.optional then
for _,initName in ipairs(initList.optional) do
use.lazily(initName)
end
end
end
end
if initList.name then
OnInit(initList.name, caller)
else
OnInit(caller)
end
end
local legacyTable = {}
assignLegacyAPI(legacyTable, OnInit)
for key,func in pairs(legacyTable) do
rawset(_G, key, func)
end
OnInit.final(function()
for key in pairs(legacyTable) do
rawset(_G, key, nil)
end
end)
end
initEverything()
end
if Debug then Debug.endFile() end
do
--[[
===========================================================================================
Better Dialogs
by Antares
Create dialogs with button tooltips and cycle buttons.
How to import:
Requires TotalInitialization
Copy this library into your map. Extract the .fdf files and the .toc file and import them
into your map without a subfolder.
The frame size values in the Config are designed to closely emulate the standard dialog.
Edit them if you want to change the layout. You can also choose between standard tooltip
and EscMenu style tooltips.
You can change the values in the .fdf files, such as the border size (BackdropCornerSize),
the tooltip text size (FrameFont), textbox position (SetPoint) etc. Preset values might
not be appropriate for every race UI.
===========================================================================================
How to create a dialog:
Dialog creation functions expect a table that contains all data of the dialog buttons as an
input argument. The table is a sequence containing:
[1] ButtonTitle 1 (type string or table)
[2] ButtonTooltip 1 (type string or table)
[3] ButtonCallback 1 (type function)
[4] ButtonTitle 2
...
If a string is passed as the argument for the buttonTitle, it is a regular dialog button.
Alternatively, you can pass a table as the argument to change the button into a cycle button.
When clicked, the button changes text to the next string in the sequence. The number of
strings stored in the sequence will determine the amount of states the button cycles through
before returning to the original state.
If a button is defined as a cycle button, the tooltip can either be static (if a string is
passed) or cycling (if a string sequence is passed). If the tooltip is cycling, the table
should have the same number of strings stored as the table of button titles. To omit the
tooltip for a button, pass "".
The button callback is the function that is executed upon clicking the button. It is expected
to be function with three arguments:
function myCallback(whichDialog, buttonName, whichPlayer)
buttonName specifies the title of the button that was clicked. This allows you to use the
same callback function for all buttons and execute the correct code based on which button
was clicked. whichPlayer specifies the player that clicked the button. Alternatively, you can
use a different callback function for each button.
For cycling buttons, buttonName specifies the title that the button just cycled into.
If the clicked button is supposed to close the associated dialog, include
CloseDialog(whichDialog)
in the callback function.
If a dialog is created for multiple players, each player sees a completely different copy of
that dialog.
Examples are given in the test map.
===========================================================================================
API
===========================================================================================
CreateBetterDialogForPlayer(whichPlayer, dialogTitle, buttonData)
CreateBetterDialogForForce(whichForce, dialogTitle, buttonData)
CreateBetterDialogForAll(dialogTitle, buttonData)
CloseDialog(whichDialog)
CloseOpenDialogForPlayer(whichPlayer)
GetOpenDialogOfPlayer(whichPlayer)
SaveAndHideOpenDialogForPlayer(whichPlayer)
ShowDialogToPlayer(whichDialog, whichPlayer)
===========================================================================================
]]
--===========================================================================================
--CONFIG
--===========================================================================================
local DIALOG_X = 0.4 --X-position of dialog center.
local DIALOG_Y = 0.3 --Y-position of dialog center.
local DIALOG_WIDTH = 0.285 --Should be integer multiple of BackdropCornerSize
local DIALOG_BOTTOM_GAP = 0.045 --Gap between bottom of lowest button and bottom of dialog.
local DIALOG_BORDER_TILE_SIZE = 0.0475 --Fixes height of dialog to integer multiple of this value to ensure that texture is tiled smoothly. (same behavior as normal dialog)
local DIALOG_TITLE_Y_OFFSET = -0.07 --Gap between top of dialog and dialog title.
local BUTTON_HEIGHT = 0.035
local BUTTON_WIDTH = 0.225
local BUTTON_SPACING = 0.003 --Gap between buttons.
local BUTTON_Y_OFFSET = -0.07 --Gap between top of dialog and first button.
local TOOLTIP_Y_OFFSET = -0.005 --Difference in y-position of top edge of dialog and tooltip.
local TOOLTIP_WIDTH = 0.244 --Should be integer multiple of BackdropCornerSize
local TOOLTIP_FRAME_HEIGHT_BUFFER = 0.045 --Difference in height between tooltip border and tooltip text box.
local TOOLTIP_STYLE = "EscMenu" --"EscMenu" or "Tooltip"
--===========================================================================================
local BUTTON_X_OFFSET = (DIALOG_WIDTH - BUTTON_WIDTH)/2
local CurrentlyOpenDialog = {}
local ButtonTriggers = {}
local ViewingPlayer = {}
local function PlayButtonClickForPlayer(whichPlayer)
local s = CreateSound("Sound\\Interface\\BigButtonClick.flac", false, false, false, 10, 10, "DefaultEAXON" )
local volume
if GetLocalPlayer() == whichPlayer then
volume = 100
else
volume = 0
end
SetSoundVolumeBJ(s, volume)
StartSound(s)
KillSoundWhenDone(s)
end
local function SetupDialog(whichPlayer, dialogTitle, buttonTitles, buttonTooltips, buttonCallbacks)
local numberOfButtons = #buttonTitles
--===========================================================================================
--Setup Dialog Background
--===========================================================================================
local dialogHeightEstimate = -BUTTON_Y_OFFSET + DIALOG_BOTTOM_GAP + numberOfButtons*BUTTON_HEIGHT + (numberOfButtons-1)*BUTTON_SPACING
local dialogHeight = math.floor(dialogHeightEstimate/DIALOG_BORDER_TILE_SIZE + 0.5)*DIALOG_BORDER_TILE_SIZE
local heightDifference = dialogHeight - dialogHeightEstimate
local dialogParent = BlzCreateFrame("BetterDialog", BlzGetFrameByName("ConsoleUIBackdrop", 0), 0, 0) ---@type framehandle
BlzFrameSetScale(dialogParent, 1)
BlzFrameSetAbsPoint(dialogParent, FRAMEPOINT_TOPLEFT , DIALOG_X - DIALOG_WIDTH/2 , DIALOG_Y + dialogHeight/2 )
BlzFrameSetAbsPoint(dialogParent, FRAMEPOINT_BOTTOMRIGHT , DIALOG_X + DIALOG_WIDTH/2 , DIALOG_Y - dialogHeight/2 )
BlzFrameSetVisible( dialogParent, true )
--===========================================================================================
--Setup Dialog Title
--===========================================================================================
local dialogTitleFrame = BlzCreateFrameByType("TEXT", "dialogTitle", dialogParent, "", 0) ---@type framehandle
BlzFrameSetText(dialogTitleFrame, dialogTitle)
BlzFrameSetScale(dialogTitleFrame, 1.6)
BlzFrameSetPoint(dialogTitleFrame, FRAMEPOINT_TOPLEFT, dialogParent, FRAMEPOINT_TOPLEFT, 0, 0)
BlzFrameSetPoint(dialogTitleFrame, FRAMEPOINT_BOTTOMRIGHT, dialogParent, FRAMEPOINT_TOPRIGHT, 0, DIALOG_TITLE_Y_OFFSET - heightDifference/2)
BlzFrameSetTextAlignment(dialogTitleFrame, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
--===========================================================================================
local tooltipText ---@type framehandle
local buttonFrames = {}
local buttonTriggers = {}
local buttonTooltipFrames = {}
for i = 1, numberOfButtons do
--===========================================================================================
--Setup Dialog Button
--===========================================================================================
local isCycleButton ---@type boolean
local cycleButtonState ---@type integer
if type(buttonTitles[i]) == "table" then
buttonFrames[i] = BlzCreateFrameByType("GLUETEXTBUTTON", buttonTitles[i][1] , dialogParent, "ScriptDialogButton", 0)
isCycleButton = true
BlzFrameSetText(buttonFrames[i], buttonTitles[i][1])
cycleButtonState = 1
elseif type(buttonTitles[i]) == "string" then
buttonFrames[i] = BlzCreateFrameByType("GLUETEXTBUTTON", buttonTitles[i] , dialogParent, "ScriptDialogButton", 0)
isCycleButton = false
BlzFrameSetText(buttonFrames[i], buttonTitles[i])
end
BlzFrameSetPoint( buttonFrames[i], FRAMEPOINT_TOPLEFT, dialogParent, FRAMEPOINT_TOPLEFT, BUTTON_X_OFFSET , BUTTON_Y_OFFSET - (i-1)*(BUTTON_HEIGHT + BUTTON_SPACING) - heightDifference/2 )
BlzFrameSetPoint( buttonFrames[i], FRAMEPOINT_BOTTOMRIGHT, dialogParent, FRAMEPOINT_TOPLEFT, BUTTON_X_OFFSET + BUTTON_WIDTH , BUTTON_Y_OFFSET - BUTTON_HEIGHT - (i-1)*(BUTTON_HEIGHT + BUTTON_SPACING) - heightDifference/2 )
--===========================================================================================
--Setup Button Tooltip
--===========================================================================================
if buttonTooltips[i] ~= "" then
buttonTooltipFrames[i] = BlzCreateFrame("BetterDialogTooltip" .. TOOLTIP_STYLE, BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), 0, 0)
BlzFrameSetAbsPoint( buttonTooltipFrames[i] , FRAMEPOINT_TOPLEFT , DIALOG_X + DIALOG_WIDTH/2 , DIALOG_Y + dialogHeight/2 + TOOLTIP_Y_OFFSET )
BlzFrameSetTooltip( buttonFrames[i] , buttonTooltipFrames[i] )
tooltipText = BlzFrameGetChild(buttonTooltipFrames[i], 0)
if type(buttonTooltips[i]) == "table" then
BlzFrameSetText( tooltipText, buttonTooltips[i][1] )
else
BlzFrameSetText( tooltipText, buttonTooltips[i] )
end
BlzFrameSetSize( tooltipText , TOOLTIP_WIDTH - 0.045 , 0.0 )
BlzFrameSetSize(buttonTooltipFrames[i] , TOOLTIP_WIDTH , BlzFrameGetHeight(tooltipText) + TOOLTIP_FRAME_HEIGHT_BUFFER)
end
--===========================================================================================
--Setup Callback Function
--===========================================================================================
buttonTriggers[i] = CreateTrigger()
BlzTriggerRegisterFrameEvent(buttonTriggers[i], buttonFrames[i], FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(buttonTriggers[i], function()
PlayButtonClickForPlayer(whichPlayer)
if isCycleButton then
cycleButtonState = ModuloInteger(cycleButtonState, #buttonTitles[i]) + 1
BlzFrameSetText(buttonFrames[i], buttonTitles[i][cycleButtonState])
if type(buttonTooltips[i]) == "table" then
tooltipText = BlzFrameGetChild(buttonTooltipFrames[i], 0)
BlzFrameSetText( tooltipText, buttonTooltips[i][cycleButtonState] )
BlzFrameSetSize( tooltipText , TOOLTIP_WIDTH - 0.045 , 0.0 )
BlzFrameSetSize(buttonTooltipFrames[i] , TOOLTIP_WIDTH , BlzFrameGetHeight(tooltipText) + TOOLTIP_FRAME_HEIGHT_BUFFER)
end
buttonCallbacks[i](dialogParent, buttonTitles[i][cycleButtonState], GetTriggerPlayer())
else
buttonCallbacks[i](dialogParent, buttonTitles[i], GetTriggerPlayer())
end
end)
end
--===========================================================================================
ButtonTriggers[dialogParent] = buttonTriggers
CurrentlyOpenDialog[whichPlayer] = dialogParent
ViewingPlayer[dialogParent] = whichPlayer
BlzFrameSetVisible(dialogParent, GetLocalPlayer() == whichPlayer)
end
local function SetupButtons(buttonData)
local buttonTitles = {}
local buttonTooltips = {}
local buttonCallbacks = {}
local j = 1
for i = 1, #buttonData - 2, 3 do
if type(buttonData[i]) == "string" or type(buttonData[i]) == "table" then
buttonTitles[j] = buttonData[i]
else
print(type(buttonData[i]) .. " passed as argument for button title. Expected string or table...")
return
end
if type(buttonData[i+1]) == "string" or type(buttonData[i+1]) == "table" then
buttonTooltips[j] = buttonData[i+1]
else
print(type(buttonData[i+1]) .. " passed as argument for button tooltip. Expected string or table...")
return
end
if type(buttonData[i+2]) == "function" then
buttonCallbacks[j] = buttonData[i+2]
else
print(type(buttonData[i+2]) .. " passed as argument for button callback. Expected function...")
return
end
j = j + 1
end
return buttonTitles, buttonTooltips, buttonCallbacks
end
--===========================================================================================
--API
--===========================================================================================
---@param dialogTitle string
---@param buttonData table
function CreateBetterDialogForAll(dialogTitle, buttonData)
local buttonTitles, buttonTooltips, buttonCallbacks = SetupButtons(buttonData)
for i = 0, 23 do
if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER then
CloseDialog(CurrentlyOpenDialog[Player(i)])
SetupDialog(Player(i), dialogTitle, buttonTitles, buttonTooltips, buttonCallbacks)
end
end
end
---@param whichForce table | force
---@param dialogTitle string
---@param buttonData table
function CreateBetterDialogForForce(whichForce, dialogTitle, buttonData)
local buttonTitles, buttonTooltips, buttonCallbacks = SetupButtons(buttonData)
if type(whichForce) == "table" then
for i = 1, #whichForce do
SetupDialog(whichForce[i], dialogTitle, buttonTitles, buttonTooltips, buttonCallbacks)
end
else
for i = 0, 23 do
if IsPlayerInForce(Player(i), whichForce) then
CloseDialog(CurrentlyOpenDialog[Player(i)])
SetupDialog(Player(i), dialogTitle, buttonTitles, buttonTooltips, buttonCallbacks)
end
end
end
end
---@param whichPlayer player
---@param dialogTitle string
---@param buttonData table
---@return framehandle
function CreateBetterDialogForPlayer(whichPlayer, dialogTitle, buttonData)
local buttonTitles, buttonTooltips, buttonCallbacks = SetupButtons(buttonData)
CloseDialog(CurrentlyOpenDialog[whichPlayer])
SetupDialog(whichPlayer, dialogTitle, buttonTitles, buttonTooltips, buttonCallbacks)
return CurrentlyOpenDialog[whichPlayer]
end
---@param whichDialog framehandle
function CloseDialog(whichDialog)
if whichDialog == nil then
return
end
for j = 1, #ButtonTriggers[whichDialog] do
DestroyTrigger(ButtonTriggers[whichDialog][j])
end
BlzDestroyFrame(whichDialog)
CurrentlyOpenDialog[ViewingPlayer[whichDialog]] = nil
end
---@param whichPlayer player
function CloseOpenDialogForPlayer(whichPlayer)
CloseDialog(CurrentlyOpenDialog[whichPlayer])
end
---@param whichPlayer player
---@return framehandle
function GetOpenDialogOfPlayer(whichPlayer)
return CurrentlyOpenDialog[whichPlayer]
end
---@param whichPlayer player
---@return framehandle
function SaveAndHideOpenDialogForPlayer(whichPlayer)
local whichDialog = CurrentlyOpenDialog[whichPlayer]
if GetLocalPlayer() == whichPlayer then
BlzFrameSetVisible(whichDialog, false)
end
CurrentlyOpenDialog[whichPlayer] = nil
return whichDialog
end
---@param whichDialog framehandle
---@param whichPlayer player
function ShowDialogToPlayer(whichDialog, whichPlayer)
CloseOpenDialogForPlayer(whichPlayer)
if GetLocalPlayer() == whichPlayer then
BlzFrameSetVisible(whichDialog, true)
end
CurrentlyOpenDialog[whichPlayer] = whichDialog
end
OnInit.main(function()
BlzLoadTOCFile("BetterDialogTemplates.toc")
end)
--===========================================================================================
end
do
local gameMode = "Casual" --default state of button
local difficulty = "Easy" --default state of button
function SelectGameMode(whichDialog, buttonName, whichPlayer)
gameMode = buttonName
end
function SelectDifficulty(whichDialog, buttonName, whichPlayer)
difficulty = buttonName
end
function Confirm(whichDialog, buttonName, whichPlayer)
CloseDialog(whichDialog)
print(GetPlayerName(whichPlayer) .. "'s selected settings are:\nGame Mode: " .. gameMode .. "\nDifficulty: " .. difficulty)
end
OnInit.final(function()
CreateBetterDialogForPlayer(Player(0), "Choose Game Settings!",
{
--Button 1
--Button Titles
{"Casual", "Deathmatch", "Survival"}, --Creates a cycling button because a table was passed as button title.
--Tooltip
{[[Select the game mode.
|cffffcc00Casual:
Play a sandbox game without a time limit. Ideal for exploration.|r
|cffaaaaaaDeathmatch:
A game mode focused on fast-paced PvP combat.|r
|cffaaaaaaSurvival:
A co-op game mode where you try to survive waves of enemy monsters.|r]],
[[Select the game mode.
|cffaaaaaaCasual:
Play a sandbox game without a time limit. Ideal for exploration.|r
|cffffcc00Deathmatch:
A game mode focused on fast-paced PvP combat.|r
|cffaaaaaaSurvival:
A co-op game mode where you try to survive waves of enemy monsters.|r]],
[[Select the game mode.
|cffaaaaaaCasual:
Play a sandbox game without a time limit. Ideal for exploration.|r
|cffaaaaaaDeathmatch:
A game mode focused on fast-paced PvP combat.|r
|cffffcc00Survival:
A co-op game mode where you try to survive waves of enemy monsters.|r]]}, --Tooltip will get updated when cycling button is clicked because a table was passed as button tooltip.
--Callback
SelectGameMode,
--==============================================
--Button 2
--Button Titles
{"Easy", "Medium", "Hard", "Extreme"}, --Creates a cycling button because a table was passed as button title.
--Tooltip
"Select difficulty. The difficulty level controls the amount of environmental hazards you will face.", --Tooltip is static because a string was passed as button tooltip.
--Callback
SelectDifficulty,
--==============================================
--Button 3
--Button Title
"Confirm", --Creates a regular button because a string was passed as button title.
--Tooltip
"", --Creates no tooltip for this button because an empty string was passed as button tooltip. Do not pass nil.
--Callback
Confirm --Button click will close dialog because CloseDialog function is called in callback function.
}
)
end)
end