- Joined
- Jun 30, 2017
- Messages
- 50
Dialog Queue and some dialog extensions.
A Dialog Queueing System to queue up dialogs, rather than forcibly closing one when another one needs to get displayed. MultiOptionDialog library to support cycling dialog buttons, and Voting Dialog. More details and information about these resources is found in their respective spoilers.Need to do more tests on these before putting them on submissions, but in the meantime, suggestions and critiques are welcome!
Lua:
do
--[[
DialogQueue v1.0 by Insanity_AI
------------------------------------------------------------------------------
A system that fixes WC3's default dialog behavior by hijacking Dialog natives:
- Calling DialogDisplay on a new dialog when another dialog is currently being displayed
will no longer hide the currently displayed dialog to show the new dialog, but will wait
for the old dialog to be clicked (or hidden) before showing the new dialog.
- Calling DialogClear on dialogs being currently shown will not just wipe all its content
and keep displaying a blank dialog to player, but will hide the dialog as well.
Additionally:
- Calling DialogDisplay or DialogDestroy on a dialog that is queued up will remove it from the queue, as it is blank.
- Will always execute dialog events first, and then dialog button events.
- Can be used without native override, and with callback functions instead of triggers.
Requires:
SetUtils - https://www.hiveworkshop.com/threads/set-group-datastructure.331886/
Defintive Doubly Linked List - https://www.hiveworkshop.com/threads/definitive-doubly-linked-list.339392/
Optional:
Total Initialization - https://www.hiveworkshop.com/threads/total-initialization.317099/
Installation:
To enable dialog native override (for GUI), either call DialogQueue.NativeOverride on Map Initialization
Or have TotalInitialization and set OVERRIDE_NATIVES to true.]]
local OVERRIDE_NATIVES = true
local OVERRIDE_GET_TRIGGERING_PLAYER = false --works only if OVERRIDE_NATIVES is also true.
--[[ Additionally, call DialogQueue.Init function on Game Start, if TotalInitialization is not present.
Hijacked Natives API:
DialogCreate -> returns a DialogWrapper object
DialogDestroy -> clears DialogWrapper's content and dequeues it from player queues.
DialogClear -> same as DialogDestroy
DialogSetMessage -> sets DialogWrapper's messageText property
DialogAddButton -> adds a new DialogButtonWrapper object to DialogWrapper with text and hotkey.
DialogAddQuitButton -> same as DialogAddButton but with quit functionality.
DialogDisplay -> Enqueues or Dequeues the dialog to/from player's dialog queue (depending on flag argument)
TriggerRegisterDialogEvent -> Adds the trigger to DialogWrapper object to be executed when dialog is clicked on.
TriggerRegisterDialogButtonEvent -> Adds the trigger to DialogButtonWrapper object to be executed when dialog is clicked on.
GetClickedButton -> returns DialogButtonWrapper object that was clicked.
GetClickedDialog -> returns DialogWrapper object that was clicked.
GetTriggerPlayer -> returns the player that did the clicking, if it was dialog related. Otherwise does what GetTriggerPlayer usually does.
DialogWrapper API:
DialogWrapper.create(data?) -> creates a new dialog object to be used for this system.
-> data can be an existing DialogWrapper object to make another copy of.
DialogWrapper.triggers - array of triggers to be executed when dialog is clicked. (not nillable, must be at least empty table.)
DialogWrapper.callback - callback function for when dialog has been clicked.
DialogWrapper.messageText - Title text for dialog. (nillable)
DialogWrapper.buttons - array of DialogButtonWrapper objects (not nillable, must be at least empty table.)
DialogWrapper.quitButton - single DialogQuitButtonWrapper object (nillable)
DialogButtonWrapper API:
DialogButtonWrapper.create(data) -> creates a new DialogButtonWrapper
-> data is an existing DialogButtonWrapper object to make another copy of.
DialogButtonWrapper.text - button's text (nillable)
DialogButtonWrapper.hotkey - button's hotkey (must be either integer or nillable)
DialogButtonWrapper.triggers - array of triggers to be executed when the button is clicked. (not nillable, must be at least empty table)
DialogButtonWrapper.callback - callback function for when dialog button has been clicked.
DialogQuitButtonWrapper API: (extension of DialogButtonWrapper)
DialogQuitButtonWrapper.create(data) -> creates a new DialogQuitButtonWrapper
-> data is an existing DialogQuitButtonWrapper object to make another copy of.
DialogQuitButtonWrapper.doScoreScreen - must be true or false (not nillable)
DialogQueue API:
DialogQueue.Enqueue(dialogWrapper, player) -> queues up a dialog for the player
DialogQueue.EnqueueAfterCurrentDialog(dialogWrapper, player) -> queues up a dialog right after the current dialog that is being displayed right now.
DialogQueue.Dequeue(dialogWrapper, player) -> dequeues a dialog for the player
DialogQueue.SkipCurrentDialog -> skips currently displayed dialog.
DialogQueue.ClearEventResponsesForTrigger - clears trigger's dialog event responses from memory (avoids memory leaks in case of trigger destroy)
DialogQueue.GetTriggerPlayer - gets the last player that clicked the dialog/button for specific trigger.
DialogQueue.GetTriggerDialog - gets the last clicked dialog for that trigger.
DialogQueue.GetTriggerButton - gets the last clicked button for that trigger.
Functionality:
- Creates a single dialog for each player in the game, all registered to the same trigger that processes dialog clicks
and passes them to respective triggers/callback functions. These WC3 dialogs are never removed but rather cleared and
set with different buttons and messages.
- Creates a single queue for each player designated to store DialogWrappers and show them sequentially in the queue order.
- When a player presses a dialog button, the currently displayed dialog is removed from that player's queue and the system
will show the next queued dialog, if there is any.
- Queueing up a dialog when there are no dialogs in the queue will immediately display it to the player.
- If the dialog has no buttons defined, the dialog will be skipped just before being displayed.
]]
-- concerns:
-- Event response getters not working (properly) if:
-- 2 or more consecutive executions of same trigger (probably easily done with TSA or Polled Waits)
-- trigger gets destroyed on execution before all event response getter calls are called.
---@class DialogWrapper
---@field triggers trigger[]
---@field callback fun(dialog: DialogWrapper, player: player)
---@field messageText string
---@field buttons DialogButtonWrapper[]
---@field quitButton DialogQuitButtonWrapper
DialogWrapper = {}
DialogWrapper.__index = DialogWrapper
---@param data? DialogWrapper
---@return DialogWrapper
function DialogWrapper.create(data)
local instance = setmetatable({}, DialogWrapper)
instance.buttons = {}
instance.triggers = {}
if data ~= nil then
for _, button in ipairs(data.buttons) do
table.insert(instance.buttons, DialogButtonWrapper.create(button))
end
instance.triggers = {}
for _, trigger in ipairs(data.triggers) do
table.insert(instance.triggers, trigger)
end
instance.callback = data.callback
instance.messageText = data.messageText
if data.quitButton then
instance.quitButton = DialogQuitButtonWrapper.create(data.quitButton)
end
end
return instance
end
---@class DialogButtonWrapper
---@field text string
---@field hotkey integer|nil --1 character only
---@field triggers trigger[]
---@field callback fun(button: DialogButtonWrapper, dialog: DialogWrapper, player: player)
DialogButtonWrapper = {}
DialogButtonWrapper.__index = DialogButtonWrapper
---@param data? DialogButtonWrapper
---@return DialogButtonWrapper
function DialogButtonWrapper.create(data)
local instance = setmetatable({}, DialogButtonWrapper)
instance.triggers = {}
if data ~= nil then
instance.text = data.text
instance.hotkey = data.hotkey
instance.callback = data.callback
end
return instance
end
---@class DialogQuitButtonWrapper : DialogButtonWrapper
---@field doScoreScreen boolean
DialogQuitButtonWrapper = {}
DialogQuitButtonWrapper.__index = DialogQuitButtonWrapper
setmetatable(DialogQuitButtonWrapper, DialogButtonWrapper)
---@param data? DialogQuitButtonWrapper
---@return DialogQuitButtonWrapper
function DialogQuitButtonWrapper.create(data)
local instance = setmetatable(DialogButtonWrapper(data), DialogQuitButtonWrapper)
if data ~= nil then
instance.doScoreScreen = data.doScoreScreen
end
return instance --[[@as DialogQuitButtonWrapper]]
end
local oldDialogCreate = DialogCreate
-- local oldDialogDestroy = DialogDestroy
local oldDialogClear = DialogClear
local oldDialogSetMessage = DialogSetMessage
local oldDialogAddButton = DialogAddButton
local oldDialogAddQuitButton = DialogAddQuitButton
local oldDialogDisplay = DialogDisplay
local oldTriggerRegisterDialogEvent = TriggerRegisterDialogEvent
-- local oldTriggerRegisterDialogButtonEvent = TriggerRegisterDialogButtonEvent
local oldGetClickedButton = GetClickedButton
-- local oldGetClickedDialog = GetClickedDialog
local oldGetTriggerPlayer = GetTriggerPlayer
DialogQueue = {}
local playerQueueActive = {} ---@type table<player, boolean>
local dialogQueues = {} ---@type table<player, LinkedListHead>
local playerDialog = {} ---@type table<player, dialog>
local currentDialogButtons = {} ---@type table<player, table<button, DialogButtonWrapper>>
---@param player player
local function displayNextDialog(player)
if playerQueueActive[player] ~= nil then
return -- is active
end
local playerQueue = dialogQueues[player]
if playerQueue.n <= 0 then
return -- empty nothing to show.
end
playerQueueActive[player] = true
local dialog = playerDialog[player]
local dialogDefinition = playerQueue.next.value --[[@as DialogWrapper]]
oldDialogDisplay(player, dialog, false)
oldDialogClear(dialog)
if type(dialogDefinition.messageText) == "string" then
oldDialogSetMessage(dialog, dialogDefinition.messageText)
end
currentDialogButtons[player] = {}
if #dialogDefinition.buttons == 0 and dialogDefinition.quitButton == nil then
-- dialog has no buttons, skip and show next dialog.
DialogQueue.SkipCurrentDialog(player)
playerQueueActive[player] = false
displayNextDialog(player)
return
end
for _, buttonDefinition in ipairs(dialogDefinition.buttons) do
local button = oldDialogAddButton(dialog, buttonDefinition.text, buttonDefinition.hotkey)
currentDialogButtons[player][button] = buttonDefinition
end
if dialogDefinition.quitButton ~= nil then
local quitButton = oldDialogAddQuitButton(dialog, dialogDefinition.quitButton.doScoreScreen, dialogDefinition.quitButton.text, dialogDefinition.quitButton.hotkey)
currentDialogButtons[player][quitButton] = dialogDefinition.quitButton
end
oldDialogDisplay(player, dialog, true)
end
---@param dialog DialogWrapper
---@param player player
function DialogQueue.Enqueue(dialog, player)
dialogQueues[player]:insert(dialog)
displayNextDialog(player)
end
---@param dialog DialogWrapper
---@param player player
function DialogQueue.EnqueueAfterCurrentDialog(dialog, player)
dialogQueues[player]:insert(dialog, true)
end
---@param dialog DialogWrapper
---@param player player
function DialogQueue.Dequeue(dialog, player)
if dialogQueues[player].n <= 0 then
return
end
-- is currently displayed dialog being dequeued?
if dialogQueues[player].next.value --[[@as DialogWrapper]] == dialog then
DialogQueue.SkipCurrentDialog(player)
return
end
for thisDialog in dialogQueues[player]:loop() do
if thisDialog.value --[[@as DialogWrapper]] == dialog then
thisDialog --[[@as LinkedListNode]]:remove()
break
end
end
end
---@param player player
function DialogQueue.SkipCurrentDialog(player)
playerQueueActive[player] = nil
dialogQueues[player].next:remove()
displayNextDialog(player)
end
local tableTriggerPlayer = {} ---@type table<trigger, player>
local tableTriggerDialog = {} ---@type table<trigger, DialogWrapper>
local tableTriggerButton = {} ---@type table<trigger, DialogButtonWrapper>
---@param trigger trigger
---@return player
function DialogQueue.GetTriggerPlayer(trigger)
return tableTriggerPlayer[trigger]
end
---@param trigger trigger
---@return DialogWrapper
function DialogQueue.GetTriggerDialog(trigger)
return tableTriggerDialog[trigger]
end
---@param trigger trigger
---@return DialogButtonWrapper
function DialogQueue.GetTriggerButton(trigger)
return tableTriggerButton[trigger]
end
---@param trigger trigger
function DialogQueue.ClearEventResponsesForTrigger(trigger)
tableTriggerPlayer[trigger] = nil
tableTriggerDialog[trigger] = nil
tableTriggerButton[trigger] = nil
end
function DialogQueue.NativeOverride()
-- Event callbacks
-- Decided to add special config option since this one is a bit more generic, and widely used instead of for just dialogs.
if OVERRIDE_GET_TRIGGERING_PLAYER then
GetTriggerPlayer = function()
local triggerPlayer = oldGetTriggerPlayer()
if oldGetTriggerPlayer() == nil then
triggerPlayer = DialogQueue.GetTriggerPlayer(GetTriggeringTrigger())
end
return triggerPlayer
end
end
GetClickedDialog = function()
return DialogQueue.GetTriggerDialog(GetTriggeringTrigger())
end
GetClickedButton = function()
return DialogQueue.GetTriggerButton(GetTriggeringTrigger())
end
-- Event registry
---@param trigger trigger
---@param dialog DialogWrapper
TriggerRegisterDialogEvent = function(trigger, dialog)
table.insert(dialog.triggers, trigger)
end
---@param trigger trigger
---@param button DialogButtonWrapper
TriggerRegisterDialogButtonEvent = function(trigger, button)
table.insert(button.triggers, trigger)
end
-- Dialog API
DialogCreate = DialogWrapper.create
---@param dialog DialogWrapper
DialogDestroy = function(dialog)
for player in SetUtils.getPlayersAll():elements() do
DialogQueue.Dequeue(dialog, player)
end
dialog.buttons = {}
dialog.messageText = nil
dialog.quitButton = nil
dialog.triggers = {}
end
DialogClear = DialogDestroy
---@param dialog DialogWrapper
---@param messageText string
DialogSetMessage = function (dialog, messageText)
dialog.messageText = messageText
end
---@param dialog DialogWrapper
---@param buttonText string
---@param hotkey integer
---@return DialogButtonWrapper
DialogAddButton = function(dialog, buttonText, hotkey)
local button = DialogButtonWrapper.create()
button.text = buttonText
button.hotkey = hotkey
table.insert(dialog.buttons, button)
return button
end
---@param dialog DialogWrapper
---@param doScoreScreen boolean
---@param buttonText string
---@param hotkey integer
---@return DialogButtonWrapper
DialogAddQuitButton = function(dialog, doScoreScreen, buttonText, hotkey)
local button = DialogQuitButtonWrapper.create()
button.text = buttonText
button.hotkey = hotkey
button.doScoreScreen = doScoreScreen
dialog.quitButton = button
return button
end
---@param player player
---@param dialog DialogWrapper
---@param show boolean
DialogDisplay = function (player, dialog, show)
if show then
DialogQueue.Enqueue(dialog, player)
else
DialogQueue.Dequeue(dialog, player)
end
end
end
---@param triggers trigger[]
---@param player player
---@param dialog DialogWrapper
---@param button DialogButtonWrapper
local callTriggers = function(triggers, player, dialog, button)
for _, trigger in ipairs(triggers) do
if trigger ~= nil then
tableTriggerPlayer[trigger] = player
tableTriggerDialog[trigger] = dialog
tableTriggerButton[trigger] = button
if TriggerEvaluate(trigger) then
TriggerExecute(trigger)
end
end
end
end
function DialogQueue.Init()
local trigger = CreateTrigger()
for player in SetUtils.getPlayersAll():elements() do
local dialog = oldDialogCreate() -- dialog per player
playerDialog[player] = dialog
dialogQueues[player] = LinkedList.create()
oldTriggerRegisterDialogEvent(trigger, dialog)
end
TriggerAddAction(trigger, function ()
local triggerPlayer = oldGetTriggerPlayer()
local dialogNode = dialogQueues[triggerPlayer].next --[[@as LinkedListNode]]
local dialog = dialogNode.value --[[@as DialogWrapper]]
local actualButton = oldGetClickedButton()
local button = currentDialogButtons[triggerPlayer][actualButton]
callTriggers(dialog.triggers, triggerPlayer, dialog, button)
callTriggers(button.triggers, triggerPlayer, dialog, button)
if dialog.callback ~= nil then dialog:callback(triggerPlayer) end
if button.callback ~= nil then button:callback(dialog, triggerPlayer) end
local status, err = pcall(dialogNode.remove, dialogNode)
if status ~= true then
print("Failed removing Dialog Node from Linked List. This is caused by the DialogWrapper being Dequeued by the callback function. In the future, please avoid this!")
print("Error: " .. err)
end
playerQueueActive[triggerPlayer] = nil
displayNextDialog(triggerPlayer)
end)
end
if OVERRIDE_NATIVES and OnInit then OnInit.root("DialogQueue", DialogQueue.NativeOverride) end
if OnInit then OnInit.final(function (require)
require "SetUtils"
DialogQueue.Init()
end) end
end
Lua:
do
--[[
MultiOptionDialog v1.0 by Insanity_AI
------------------------------------------------------------------------------
DialogQueue extension to handle dialogs with buttons which cycle options and commit button at the end.
Each option has a previewFunc which can be called when that option is currently selected.
dialog's callback function is called when the player has commited their options.
Requires:
DialogQueue - [INSERT LINK HERE]
SetUtils - https://www.hiveworkshop.com/threads/set-group-datastructure.331886/
Installation:
Just Plug & Play, nothing specific to be done.
MultiOptionDialog API:
MultiOptionDialog.create(data?) -> creates a new MultiOptionDialog.
-> data can be an existing MultiOptionDialog object to make another copy of.
MultiOptionDialog.callback - callback function for when MultiOptionDialog has been commited.
MultiOptionDialog.title - Title text for dialog. (nillable)
MultiOptionDialog.buttons - array of MultiOptionDialogButton objects (not nillable, must be at least empty table.)
MultiOptionDialog.commitButton - DialogButtonWrapper for commit.
MultiOptionDialog:Enqueue(playerSet) -> enqueues MultiOptionDialog for Set of players.
MultiOptionDialog:Dequeue(playerSet) -> dequeues MultiOptionDialog for Set of players.
MultiOptionDialogButton API:
MultiOptionDialogButton.create(data?) -> data is an existing MultiOptionDialogButton object to make another copy of.
MultiOptionDialogButton.messageFormat - format string to merge button prefix and option name.
MultiOptionDialogButton.hotkey - button's hotkey (must be single char or nil)
MultiOptionDialogButton.options - array of MultiOptionDialogButtonOption to cycle through on button click. (not nillable, must be at least empty table)
MultiOptionDialogButton.prefix - button's prefix text (nillable)
MultiOptionDialogButtonOption API:
MultiOptionDialogButtonOption.create(data?) -> data is an existing MultiOptionDialogButtonOption object to make another copy of.
MultiOptionDialogButtonOption.previewCallback - callback function called upon the option being cycled to by a button click.
MultiOptionDialogButtonOption.name - option name to be displayed on button.
]]
---@class MultiOptionDialog
---@field callback fun(dialog: MultiOptionDialog, player: player, buttonChosenOptionPairs: table<MultiOptionDialogButton, MultiOptionDialogButtonOption>)
---@field buttons MultiOptionDialogButton[]
---@field commitButton DialogButtonWrapper
---@field title string
MultiOptionDialog = {}
MultiOptionDialog.__index = MultiOptionDialog
---@param data? MultiOptionDialog
---@return MultiOptionDialog
function MultiOptionDialog.create(data)
local instance = setmetatable({}, MultiOptionDialog)
instance.buttons = {}
if data ~= nil then
for _, button in ipairs(data.buttons) do
table.insert(instance.buttons, MultiOptionDialogButton.create(button))
end
instance.callback = data.callback
instance.title = data.title
instance.commitButton = DialogButtonWrapper.create(data.commitButton)
end
return instance
end
---@class MultiOptionDialogButton
---@field options MultiOptionDialogButtonOption[]
---@field prefix string
---@field messageFormat string
---@field hotkey string
MultiOptionDialogButton = {}
MultiOptionDialogButton.__index = MultiOptionDialogButton
---@param data? MultiOptionDialogButton
---@return MultiOptionDialogButton
function MultiOptionDialogButton.create(data)
local instance = setmetatable({}, MultiOptionDialogButton)
instance.options = {}
if data ~= nil then
for _, option in ipairs(data.options) do
table.insert(instance.options, MultiOptionDialogButtonOption.create(option))
end
instance.messageFormat = data.messageFormat
instance.prefix = data.prefix
instance.hotkey = data.hotkey
else
instance.messageFormat = "[\x25s] \x25s"
end
return instance
end
---@class MultiOptionDialogButtonOption
---@field previewCallback fun(player: player)
---@field name string
MultiOptionDialogButtonOption = {}
MultiOptionDialogButtonOption.__index = MultiOptionDialogButtonOption
---@param data? MultiOptionDialogButtonOption
---@return MultiOptionDialogButtonOption
function MultiOptionDialogButtonOption.create(data)
local instance = setmetatable({}, MultiOptionDialogButtonOption)
if data ~= nil then
instance.previewCallback = data.previewCallback
instance.name = data.name
end
return instance
end
-- Internal Classes
---@class MultiDialogWrapper : DialogWrapper
---@field buttons MultiDialogButtonWrapper[]
---@field _dialogDef MultiOptionDialog
---@class MultiDialogButtonWrapper : DialogButtonWrapper
---@field _buttonDef MultiOptionDialogButton
---@field _option integer
---@class DialogPlayerData
---@field players Set
---@field playerDialogWrappers table<player, MultiDialogWrapper>
---@param button MultiDialogButtonWrapper
---@param dialog MultiDialogWrapper
---@param player player
local function cycleOption(button, dialog, player)
button._option = button._option + 1
local buttonDef = button._buttonDef
if button._option > #buttonDef.options then
button._option = 1
end
local option = buttonDef.options[button._option]
option.previewCallback(player)
button.text = string.format(buttonDef.messageFormat, buttonDef.prefix, option.name)
DialogQueue.EnqueueAfterCurrentDialog(dialog, player)
end
local queuedDialogsForPlayers = {} ---@type table<MultiOptionDialog, DialogPlayerData>
---@param dialog MultiOptionDialog
---@param player player
local function dequeuePlayer(dialog, player)
local playerDialogData = queuedDialogsForPlayers[dialog]
playerDialogData.players:removeSingle(player)
if playerDialogData.players:isEmpty() then
queuedDialogsForPlayers[dialog] = nil
end
end
---@param dialog MultiDialogWrapper
---@param player player
local function commitOptions(_, dialog, player)
local selectedButtonOptions = {} ---@type table<MultiOptionDialogButton, MultiOptionDialogButtonOption>
for _, thisButton in ipairs(dialog.buttons) do
if thisButton._buttonDef then -- last button is not MultiOptionDialogButton, just a normal DialogButtonWrapper
selectedButtonOptions[thisButton._buttonDef] = thisButton._buttonDef.options[thisButton._option]
end
end
dialog._dialogDef.callback(dialog._dialogDef, player, selectedButtonOptions)
dequeuePlayer(dialog._dialogDef, player)
end
---@param dialogDef MultiOptionDialog
---@return DialogWrapper
local function toDialogWrapper(dialogDef)
local dialogWrapper = { ---@type DialogWrapper
triggers = {},
buttons = {},
messageText = dialogDef.title,
}
for _, buttonDef in ipairs(dialogDef.buttons) do
table.insert(dialogWrapper.buttons, {
text = string.format(buttonDef.messageFormat, buttonDef.prefix, buttonDef.options[1].name),
hotkey = string.byte(buttonDef.hotkey) or 0,
triggers = {},
callback = cycleOption,
_buttonDef = buttonDef
})
end
table.insert(dialogWrapper.buttons, {
text = "Done",
hotkey = 0,
triggers = {},
callback = commitOptions
})
return dialogWrapper
end
---@param players Set
---@return boolean success
function MultiOptionDialog:Enqueue(players)
if queuedDialogsForPlayers[self] ~= nil then
return false
end
local dialogWrapper = toDialogWrapper(self)
local playerDialogData = {
players = players,
playerDialogWrappers = {}
}
queuedDialogsForPlayers[self] = playerDialogData
for player in players:elements() do
local playerDialog = DialogWrapper.create(dialogWrapper) --[[@as MultiDialogWrapper]]
playerDialog._dialogDef = self
for index, button in ipairs(playerDialog.buttons) do
button._option = 1
button._buttonDef = self.buttons[index]
end
playerDialogData.playerDialogWrappers[player] = playerDialog
DialogQueue.Enqueue(playerDialog, player)
end
return true
end
---@param players Set
---@return boolean success
function MultiOptionDialog:Dequeue(players)
if queuedDialogsForPlayers[self] ~= nil then
return false
end
local playersToDequeue = Set.intersection(players, queuedDialogsForPlayers[self].players)
queuedDialogsForPlayers[self].players:removeAll(playersToDequeue)
for player --[[@as player]] in playersToDequeue:elements() do
dequeuePlayer(self, player)
DialogQueue.Dequeue(queuedDialogsForPlayers[self].playerDialogWrappers[player], player)
end
return true
end
end
Lua:
do
--[[
VotingDialog v1.0 by Insanity_AI
------------------------------------------------------------------------------
MultiOptionDialog extension to wrap them in a player voting logic.
Requires:
MultiOptionDialog - [INSERT LINK HERE]
SetUtils - https://www.hiveworkshop.com/threads/set-group-datastructure.331886/
Installation:
Just Plug & Play, nothing specific to be done.
MultiOptionDialog API:
VotingDialog.create(data?) -> creates a new VotingDialog.
-> data can be an existing VotingDialog object to make another copy of.
VotingDialog.votingDoneCallback - callback function for when all players have comitted their votes.
VotingDialog.title - Title text for dialog. (nillable)
VotingDialog.buttons - array of MultiOptionDialogButton objects (not nillable, must be at least empty table.)
VotingDialog.commitButton - DialogButtonWrapper for commit.
VotingDialog:Enqueue(playerSet) -> enqueues VotingDialog for Set of players.
VotingDialog:Dequeue(playerSet) -> dequeues VotingDialog for Set of players.
MultiOptionDialog.callback is not to be used by end-user, it is used by VotingDialog to count votes!
]]
---@class VotingDialog : MultiOptionDialog
---@field votingDoneCallback fun(selectedOptions: MultiOptionDialogButtonOption[])
VotingDialog = {}
VotingDialog.__index = VotingDialog
setmetatable(VotingDialog, MultiOptionDialog)
---@param data? VotingDialog
---@return VotingDialog
VotingDialog.create = function(data)
local instance = setmetatable(MultiOptionDialog.create(data), VotingDialog)
if data ~= nil then
instance.votingDoneCallback = data.votingDoneCallback
end
return instance --[[@as VotingDialog]]
end
-- jesus christ this datastructure.
local playersVotes = {} ---@type table<VotingDialog, {players : Set, buttonOptionCounts: table<MultiOptionDialogButton, table<MultiOptionDialogButtonOption, integer>>}>
---@param dialog VotingDialog
local function functionVotingFinish(dialog)
local selectedOptions = {} ---@type MultiOptionDialogButtonOption[]
for _, optionCountPairs in pairs(playersVotes[dialog].buttonOptionCounts) do
local maxOption = nil
local max = nil
for option, count in pairs(optionCountPairs) do
if max == nil or max < count then
maxOption = option
max = count
end
end
table.insert(selectedOptions, maxOption)
end
playersVotes[dialog] = nil
dialog.votingDoneCallback(selectedOptions)
end
---@param dialog VotingDialog
---@param player player
---@param buttonChosenOptionPairs table<MultiOptionDialogButton, MultiOptionDialogButtonOption>
local function processPlayerCommit(dialog, player, buttonChosenOptionPairs)
local dataStruct = playersVotes[dialog]
dataStruct.players:removeSingle(player)
for button, option in pairs(buttonChosenOptionPairs) do
if dataStruct.buttonOptionCounts[button] == nil then
dataStruct.buttonOptionCounts[button] = {}
dataStruct.buttonOptionCounts[button][option] = 1
else
dataStruct.buttonOptionCounts[button][option] = dataStruct.buttonOptionCounts[button][option] + 1
end
end
if dataStruct.players:isEmpty() then
functionVotingFinish(dialog)
end
end
---@param players Set
---@return boolean success
function VotingDialog:Enqueue(players)
if playersVotes[self] ~= nil then
-- Already queued up!
return false
end
local dataStruct = { ---@type {players : Set, buttonOptionCounts: table<MultiOptionDialogButton, table<MultiOptionDialogButtonOption, integer>>}
players = players,
buttonOptionCounts = {}
}
playersVotes[self] = dataStruct
self.callback = processPlayerCommit
MultiOptionDialog.Enqueue(self, players)
return true
end
---@param players Set
---@return boolean success
function VotingDialog:Dequeue(players)
if playersVotes[self] == nil then
-- Not queued up!
return false
end
playersVotes[self].players = playersVotes[self].players:removeAll(players)
if playersVotes[self].players:isEmpty() then
functionVotingFinish(self)
end
MultiOptionDialog:Dequeue(players);
return true
end
end
Attachments
Last edited: