- Joined
- May 28, 2020
- Messages
- 689
It looks like a very good feature.
if Debug then Debug.beginFile "QuestText" end
do
--[[
=============================================================================================================================================================
Quest Text
by Antares
Writes a text into a text box, smoothly fading in each character as it is written.
Requires:
TotalInitialization https://www.hiveworkshop.com/threads/total-initialization.317099/
=============================================================================================================================================================
A P I
=============================================================================================================================================================
QuestTextNew(text) Starts writing the specified text into the quest text box.
QuestTextSkipToEnd() Fully displays the current quest text.
QuestTextReachedEnd() Returns whether the quest text has finished writing.
QuestTextFullyVisible() Returns whether all characters have fully faded in.
QuestTextFadeOut(duration) Fades out the current quest text over the specified duration.
QuestTextRemove() Instantly removes the current quest text.
Each function uses an optional player parameter. This will create a quest text that is only visible to that player/affect only that quest text. For a
single-player map, you can omit that parameter. Each player can only see one quest text at a time.
Additional optional parameters for QuestTextNew:
parentFrame Sets the parentFrame of the text. The default, if not specified, is ORIGIN_FRAME_WORLD_FRAME.
onReachingEnd A function that is executed when the text reaches the end. The function is called with (whichPlayer, ...).
... Any additional arguments will be passed into the onReachingEnd function.
=============================================================================================================================================================
C O N F I G
=============================================================================================================================================================
]]
local TEXT_WIDTH = 0.25 ---@type number
--The width of the text box.
local X_LEFT = 0.275 ---@type number
--The x-position of the left edge of the text box.
local Y_TOP = 0.5 ---@type number
--The y-position of the top edge of the text box.
local TEXT_LINE_SPACING = 0.012 ---@type number
--Distance between two lines.
local FONT_SIZE = 12 ---@type number
--Font size.
local CHARACTERS_PER_SECOND = 30 ---@type number
--Writing speed of the quest text.
local FADE_IN_TIME = 0.6 ---@type number
--The time it takes for a character to fully fade in after first appearing.
local COPY_TO_MESSAGE_LOG = true ---@type boolean
--(Only singleplayer) Copies messages to the message log by printing out the message, then clearing all text. Will interfere with default text messages.
--===========================================================================================================================================================
local ALPHA_INCREMENT = ---@type number
255/(CHARACTERS_PER_SECOND*FADE_IN_TIME)
local questTextOfTimer = {} ---@type QuestText
local widthTestFrame = nil ---@type framehandle
local currentQuestText = {} ---@type table
local lastCreatedQuestText = nil ---@type QuestText | nil
---@class QuestText
local QuestText = {
text = nil, ---@type string
words = nil, ---@type string[]
frames = nil, ---@type framehandle[]
alpha = nil, ---@type number[]
length = nil, ---@type integer
pos = nil, ---@type integer
writeTimer = nil, ---@type timer
fadeTimer = nil, ---@type timer
x = nil, ---@type number
y = nil, ---@type number
endReached = nil, ---@type boolean
fullyVisible = nil, ---@type boolean
fadeOutTime = nil, ---@type number
currentColor = nil, ---@type string | nil
player = nil, ---@type player
parentFrame = nil, ---@type framehandle
onReachingEnd = nil, ---@type function | nil
args = nil, ---@type table | nil
}
local function FadeOut()
local self = questTextOfTimer[GetExpiredTimer()]
local fadeOut = false
for i = 1, #self.frames do
if self.alpha[i] > 0 then
fadeOut = true
self.alpha[i] = math.max(0, (self.alpha[i] - 255/(CHARACTERS_PER_SECOND*self.fadeOutTime)))
BlzFrameSetAlpha(self.frames[i], self.alpha[i] // 1)
end
i = i + 1
end
if not fadeOut then
QuestTextRemove()
end
end
local function Fade(self)
self = self or questTextOfTimer[GetExpiredTimer()]
local i = #self.frames
while i >= 1 and self.alpha[i] < 255 do
self.alpha[i] = math.min(255, (self.alpha[i] + ALPHA_INCREMENT))
BlzFrameSetAlpha(self.frames[i], self.alpha[i] // 1)
i = i - 1
end
if i == #self.frames and self.endReached then
self.fullyVisible = true
PauseTimer(self.fadeTimer)
end
end
local function NextChar(self)
self = self or questTextOfTimer[GetExpiredTimer()]
::begin::
self.pos = self.pos + 1
if self.pos > self.length then
self.endReached = true
PauseTimer(self.writeTimer)
if self.onReachingEnd then
self.onReachingEnd(self.player, table.unpack(self.args))
end
return
end
local char = self.text:sub(self.pos, self.pos)
if char == "\n" then
self.x = X_LEFT
self.y = self.y - TEXT_LINE_SPACING
self.currentWord = self.currentWord + 1
goto begin
elseif char == " " then
self.currentWord = self.currentWord + 1
BlzFrameSetText(widthTestFrame, self.words[self.currentWord])
BlzFrameSetSize(widthTestFrame, 0, TEXT_LINE_SPACING)
if self.x + BlzFrameGetWidth(widthTestFrame) > X_LEFT + TEXT_WIDTH then --Next line
self.x = X_LEFT
self.y = self.y - TEXT_LINE_SPACING
goto begin
end
elseif char == "|" then
local nextChar = self.text:sub(self.pos + 1, self.pos + 1):lower()
if nextChar == "c" then
self.currentColor = self.text:sub(self.pos, self.pos + 9)
self.pos = self.pos + 9
goto begin
elseif nextChar == "r" then
self.currentColor = nil
self.pos = self.pos + 1
goto begin
end
end
local newFrame = BlzCreateFrameByType("TEXT", "textFrame", self.parentFrame, "", 0) ---@type framehandle
BlzFrameSetAbsPoint(newFrame, FRAMEPOINT_BOTTOMLEFT, self.x, self.y)
if self.currentColor then
BlzFrameSetText(newFrame, self.currentColor .. char .. "|r")
else
BlzFrameSetText(newFrame, char)
end
if self.player then
BlzFrameSetVisible(newFrame, GetLocalPlayer() == self.player)
end
BlzFrameSetScale(newFrame, FONT_SIZE/10)
BlzFrameSetSize(newFrame, 0, TEXT_LINE_SPACING)
BlzFrameSetAlpha(newFrame, 0)
self.x = self.x + BlzFrameGetWidth(newFrame)
table.insert(self.frames, newFrame)
table.insert(self.alpha, 0)
end
local function Init()
widthTestFrame = BlzCreateFrameByType("TEXT", "textFrame", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 0)
BlzFrameSetVisible(widthTestFrame, false)
end
OnInit.global("QuestText", Init)
--===========================================================================================================================================================
--API
--===========================================================================================================================================================
---@param text string
---@param whichPlayer? player
---@param parentFrame? framehandle
---@param onReachingEnd? function
---@vararg any
function QuestTextNew(text, whichPlayer, parentFrame, onReachingEnd, ...)
local self = {} ---@type QuestText
self.text = text
self.frames = {}
self.alpha = {}
self.words = {}
self.length = text:len()
self.writeTimer = CreateTimer()
self.fadeTimer = CreateTimer()
questTextOfTimer[self.writeTimer] = self
questTextOfTimer[self.fadeTimer] = self
self.x = X_LEFT
self.y = Y_TOP
self.pos = 0
self.currentWord = 1
self.player = whichPlayer
self.parentFrame = parentFrame or BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0)
if onReachingEnd then
self.onReachingEnd = onReachingEnd
self.args = table.pack(...)
end
if bj_isSinglePlayer and COPY_TO_MESSAGE_LOG then
print(text)
ClearTextMessages()
end
if whichPlayer then
if currentQuestText[whichPlayer] then
QuestTextRemove(whichPlayer)
end
currentQuestText[whichPlayer] = self
elseif lastCreatedQuestText then
QuestTextRemove()
for i = 0, 23 do
if currentQuestText[Player(i)] then
QuestTextRemove(Player(i))
end
end
end
lastCreatedQuestText = self
local char
local beginningOfWord = 0
for i = 1, self.length do
char = text:sub(i, i)
if char == " " or char == "\n" then
self.words[#self.words + 1] = text:sub(beginningOfWord, i-1):gsub("\n", "")
beginningOfWord = i
elseif i == self.length then
self.words[#self.words + 1] = text:sub(beginningOfWord, i):gsub("\n", "")
end
end
TimerStart(self.writeTimer, 1/CHARACTERS_PER_SECOND, true, NextChar)
TimerStart(self.fadeTimer, 0.02, true, Fade)
end
---@param whichPlayer? player
function QuestTextSkipToEnd(whichPlayer)
local self = whichPlayer and currentQuestText[whichPlayer] or lastCreatedQuestText
if self == nil then
return
end
while not self.endReached do
NextChar(self)
end
DestroyTimer(self.fadeTimer)
questTextOfTimer[self.fadeTimer] = nil
for i = 1, #self.frames do
self.alpha[i] = 255
BlzFrameSetAlpha(self.frames[i], 255)
end
end
---@param whichPlayer? player
---@return boolean
function QuestTextReachedEnd(whichPlayer)
local self = whichPlayer and currentQuestText[whichPlayer] or lastCreatedQuestText
if self == nil then
return false
end
return currentQuestText.endReached
end
---@param whichPlayer? player
---@return boolean
function QuestTextFullyVisible(whichPlayer)
local self = whichPlayer and currentQuestText[whichPlayer] or lastCreatedQuestText
if self == nil then
return false
end
return currentQuestText.fullyVisible
end
---@param duration number
---@param whichPlayer? player
function QuestTextFadeOut(duration, whichPlayer)
local self = whichPlayer and currentQuestText[whichPlayer] or lastCreatedQuestText
if self == nil then
return
end
if not self.fullyVisible then
PauseTimer(self.writeTimer)
end
self.fadeOutTime = duration
TimerStart(self.fadeTimer, 0.02, true, FadeOut)
end
---@param whichPlayer? player
function QuestTextRemove(whichPlayer)
local self = whichPlayer and currentQuestText[whichPlayer] or lastCreatedQuestText
if self == nil then
return
end
questTextOfTimer[self.writeTimer] = nil
questTextOfTimer[self.fadeTimer] = nil
DestroyTimer(self.writeTimer)
DestroyTimer(self.fadeTimer)
for i = 1, #self.frames do
BlzDestroyFrame(self.frames[i])
self.frames[i] = nil
end
if whichPlayer then
currentQuestText[whichPlayer] = nil
else
lastCreatedQuestText = nil
end
end
end
if Debug then Debug.endFile() end