Advertisement
MrDoubleA

littleDialogue (fixed)

Apr 13th, 2023
193
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 72.75 KB | None | 0 0
  1. --[[
  2.  
  3.     littleDialogue.lua (v1.1)
  4.     Written by MrDoubleA
  5.  
  6.     Documentation: https://docs.google.com/document/d/1oJUQT6FvgtX7UA26r-JAWcG41Ns6lpe9iIP4qPEm6zw/edit?usp=sharing
  7.  
  8.     Yoshi's Island font ripped by Nemica (https://www.spriters-resource.com/snes/yoshiisland/sheet/19542/)
  9.     Font for Superstar Saga-styled box by rixithechao (https://www.supermariobrosx.org/forums/viewtopic.php?f=31&t=26204#p376929)
  10.  
  11. ]]
  12.  
  13. local configFileReader = require("configFileReader")
  14. local textplus = require("textplus")
  15. local tplusUtils = require("textplus/tplusutils")
  16.  
  17. local handycam = require("handycam")
  18.  
  19. local littleDialogue = {}
  20.  
  21.  
  22. local smallScreen
  23. pcall(function() smallScreen = require("smallScreen") end)
  24.  
  25.  
  26. function littleDialogue.onInitAPI()
  27.     registerEvent(littleDialogue,"onTick")
  28.     registerEvent(littleDialogue,"onDraw")
  29.  
  30.     registerEvent(littleDialogue,"onMessageBox")
  31. end
  32.  
  33.  
  34. local function getBoundaries()
  35.     local b = camera.bounds
  36.  
  37.     if smallScreen ~= nil and smallScreen.croppingEnabled then
  38.         local widthDifference  = (camera.width  - smallScreen.width ) * 0.5
  39.         local heightDifference = (camera.height - smallScreen.height) * 0.5
  40.  
  41.         b.left   = b.left   + widthDifference
  42.         b.right  = b.right  - widthDifference
  43.         b.top    = b.top    + heightDifference
  44.         b.bottom = b.bottom - heightDifference
  45.     end
  46.  
  47.     return b
  48. end
  49.  
  50. local function getTextPosFromValue(pos) -- takes a number, layout or formatted text and returns the numbers of characters from it
  51.     if type(pos) == "TextplusLayout" then -- pos is a layout: find how long the layout is.
  52.         local lineCount = #pos
  53.         local characterCount = 0
  54.  
  55.         -- Go through each line
  56.         for lineIndex,line in ipairs(pos) do
  57.             -- Add the length of each segment
  58.             for i = 1,#line,4 do
  59.                 local segment = line[i]
  60.  
  61.                 if segment.img ~= nil then
  62.                     characterCount = characterCount + 1
  63.                 else
  64.                     characterCount = characterCount + (line[i+2] - line[i+1]) + 1
  65.                 end
  66.             end
  67.  
  68.             -- New lines count too
  69.             if lineIndex < lineCount then
  70.                 characterCount = characterCount + 1
  71.             end
  72.         end
  73.        
  74.         return characterCount
  75.     elseif type(pos) == "table" then -- pos is a list of segments: find how long it is
  76.         local characterCount = 0
  77.  
  78.         for _,segment in ipairs(pos) do
  79.             if segment.img ~= nil then
  80.                 characterCount = characterCount + 1
  81.             else
  82.                 characterCount = characterCount + #segment
  83.             end
  84.         end
  85.  
  86.         return characterCount
  87.     elseif type(pos) == "number" then
  88.         return pos
  89.     else
  90.         return error("Invalid value for text position: '".. tostring(pos).. "'")
  91.     end
  92. end
  93.  
  94.  
  95. littleDialogue.boxes = {}
  96.  
  97. local boxInstanceFunctions = {}
  98. local boxMT = {
  99.     __index = boxInstanceFunctions,
  100. }
  101.  
  102.  
  103. local STATE = {
  104.     IN     = 0,
  105.     STAY   = 1,
  106.     SCROLL = 2,
  107.     OUT    = 3,
  108.     SCROLL_ANSWERS = 4,
  109.  
  110.     REMOVE = -1,
  111. }
  112.  
  113.  
  114. local customTags = {}
  115. local selfClosingTags = {}
  116.  
  117. local textEventFuncs = {} -- custom thing, for typewriter effects
  118.  
  119. littleDialogue.customTags = customTags
  120. littleDialogue.selfClosingTags = selfClosingTags
  121.  
  122. littleDialogue.textEventFuncs = textEventFuncs
  123.  
  124.  
  125. -- Custom tags
  126. local currentlyUpdatingBox,currentlyUpdatingPage
  127.  
  128. do
  129.     local function setVoice(box,name)
  130.         -- Look for voice file
  131.         if name == nil or name == "" then
  132.             box.voiceSound = nil
  133.         else
  134.             local path = Misc.resolveSoundFile("littleDialogue/portraits/".. name) or Misc.resolveSoundFile("littleDialogue/".. name) or Misc.resolveSoundFile(name)
  135.    
  136.             if path ~= nil then
  137.                 box.voiceSound = SFX.open(path)
  138.             else
  139.                 box.voiceSound = nil
  140.             end
  141.         end
  142.     end
  143.  
  144.  
  145.  
  146.     local questionsMap = {}
  147.  
  148.     function littleDialogue.registerAnswer(name,answer)
  149.         questionsMap[name] = questionsMap[name] or {}
  150.  
  151.         answer.text = answer.text or answer[1] or ""
  152.         answer.chosenFunction = answer.chosenFunction or answer[2]
  153.         answer.addText = answer.addText or answer[3]
  154.  
  155.         table.insert(questionsMap[name],answer)
  156.     end
  157.  
  158.     function littleDialogue.deregisterQuestion(name)
  159.         questionsMap[name] = nil
  160.     end
  161.  
  162.  
  163.     function customTags.question(fmt,out,args)
  164.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  165.             Misc.warn("Invalid use of question tag.")
  166.             return fmt
  167.         end
  168.  
  169.         local name = args[1]
  170.         local answers = questionsMap[name]
  171.  
  172.         if answers == nil then
  173.             Misc.warn("Invalid question '".. name.. "'.")
  174.             return fmt
  175.         end
  176.  
  177.  
  178.         for _,answer in ipairs(answers) do
  179.             currentlyUpdatingBox:addQuestion(currentlyUpdatingPage,answer)
  180.         end
  181.  
  182.         return fmt
  183.     end
  184.  
  185.     table.insert(selfClosingTags,"question")
  186.  
  187.  
  188.  
  189.     local settingsList
  190.  
  191.     local extraSettingsList = {
  192.         "font","speakerNameFont","boxImage","continueArrowImage","scrollArrowImage","selectorImage","speakerNameBoxImage","lineMarkerImage",
  193.         "openSound","closeSound","scrollSound","typewriterSound","moveSelectionSound","chooseAnswerSound",
  194.         "mainTextShader","speakerNameTextShader",
  195.     }
  196.  
  197.  
  198.     local function loadImage(settings,styleName,name,imageFilename)
  199.         local path = Misc.resolveFile("littleDialogue/".. styleName.. "/".. imageFilename) or Misc.resolveFile("littleDialogue/".. imageFilename)
  200.  
  201.         if path ~= nil then
  202.             settings[name] = Graphics.loadImage(path)
  203.         end
  204.     end
  205.     local function loadSound(settings,styleName,name,soundFilename)
  206.         settings[name] = Misc.resolveSoundFile("littleDialogue/".. styleName.. "/".. soundFilename) or Misc.resolveSoundFile("littleDialogue/".. soundFilename)
  207.     end
  208.  
  209.     local function findFont(styleName,fontFileName)
  210.         local folderPath = "littleDialogue/".. styleName.. "/".. fontFileName
  211.         if Misc.resolveFile(folderPath) ~= nil then
  212.             return folderPath
  213.         end
  214.  
  215.         local mainPath = "littleDialogue/".. fontFileName
  216.         if Misc.resolveFile(mainPath) ~= nil then
  217.             return mainPath
  218.         end
  219.  
  220.         return nil
  221.     end
  222.  
  223.     local function compileShader(styleName,vertName,fragName)
  224.         local vertPath = Misc.resolveFile("littleDialogue/".. styleName.. "/".. vertName) or Misc.resolveFile("littleDialogue/".. vertName)
  225.         local fragPath = Misc.resolveFile("littleDialogue/".. styleName.. "/".. fragName) or Misc.resolveFile("littleDialogue/".. fragName)
  226.  
  227.         if vertPath ~= nil or fragPath ~= nil then
  228.             local obj = Shader()
  229.             obj:compileFromFile(vertPath,fragPath)
  230.  
  231.             return obj
  232.         end
  233.  
  234.         return nil
  235.     end
  236.  
  237.  
  238.     littleDialogue.styles = {}
  239.  
  240.     function boxInstanceFunctions:setStyle(style)
  241.         if self.styleName == style then
  242.             return
  243.         end
  244.  
  245.  
  246.         local styleSettings = littleDialogue.styles[style]
  247.  
  248.         if styleSettings == nil then
  249.             error("Invalid box style '".. style.. "'.")
  250.             return
  251.         end
  252.  
  253.  
  254.         self.styleName = style
  255.  
  256.         self.settings = {}
  257.  
  258.         for _,name in ipairs(settingsList) do
  259.             if self.overwriteSettings[name] ~= nil then
  260.                 self.settings[name] = self.overwriteSettings[name]
  261.             elseif styleSettings[name] ~= nil then
  262.                 self.settings[name] = styleSettings[name]
  263.             else
  264.                 self.settings[name] = littleDialogue.defaultBoxSettings[name]
  265.             end
  266.         end
  267.  
  268.         self.maxWidth = self.settings.textMaxWidth
  269.         self.typewriterFinished = (not self.settings.typewriterEnabled)
  270.         self.priority = self.settings.priority
  271.     end
  272.  
  273.     function littleDialogue.registerStyle(name,settings)
  274.         if settingsList == nil then
  275.             settingsList = table.append(table.unmap(littleDialogue.defaultBoxSettings),extraSettingsList)
  276.         end
  277.  
  278.         -- Find images/sounds
  279.         loadImage(settings,name,"boxImage","box.png")
  280.         loadImage(settings,name,"continueArrowImage","continueArrow.png")
  281.         loadImage(settings,name,"scrollArrowImage","scrollArrow.png")
  282.         loadImage(settings,name,"selectorImage","selector.png")
  283.         loadImage(settings,name,"speakerNameBoxImage","speakerNameBox.png")
  284.         loadImage(settings,name,"lineMarkerImage","lineMarker.png")
  285.  
  286.         loadSound(settings,name,"openSound","open")
  287.         loadSound(settings,name,"closeSound","close")
  288.         loadSound(settings,name,"scrollSound","scroll")
  289.         --loadSound(settings,name,"typewriterSound","typewriter")
  290.         loadSound(settings,name,"moveSelectionSound","scroll")
  291.         loadSound(settings,name,"chooseAnswerSound","choose")
  292.  
  293.         settings.typewriterSound = SFX.open(Misc.resolveSoundFile("littleDialogue/".. name.. "/typewriter") or Misc.resolveSoundFile("littleDialogue/typewriter"))
  294.  
  295.         settings.font = settings.font or textplus.loadFont(findFont(name,"font.ini"))
  296.  
  297.         if settings.speakerNameFont == nil then
  298.             local speakerNameFont = findFont(name,"speakerNameFont.ini")
  299.             if speakerNameFont ~= nil then
  300.                 settings.speakerNameFont = textplus.loadFont(speakerNameFont)
  301.             else
  302.                 settings.speakerNameFont = settings.font
  303.             end
  304.         end
  305.  
  306.         -- Load font shader
  307.         settings.mainTextShader = settings.mainTextShader or compileShader(name,"mainText.vert","mainText.frag")
  308.         settings.speakerNameTextShader = settings.speakerNameTextShader or compileShader(name,"speakerNameText.vert","speakerNameText.frag") or settings.mainTextShader
  309.  
  310.  
  311.         littleDialogue.styles[name] = settings
  312.     end
  313.  
  314.  
  315.     function customTags.boxStyle(fmt,out,args)
  316.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  317.             Misc.warn("Invalid use of boxStyle tag.")
  318.             return fmt
  319.         end
  320.  
  321.         currentlyUpdatingBox._newStyle = args[1] or currentlyUpdatingBox.styleName
  322.  
  323.         return fmt
  324.     end
  325.  
  326.     customTags.boxstyle = customTags.boxStyle
  327.  
  328.     table.insert(selfClosingTags,"boxStyle")
  329.  
  330.  
  331.     local keyCodeNames = {
  332.         [VK_MENU] = "key_alt",[VK_SHIFT] = "key_shift",[VK_CONTROL] = "key_control",[VK_TAB] = "key_tab",
  333.         [VK_BACK] = "key_backspace",[VK_PRIOR] = "key_pageUp",[VK_NEXT] = "key_pageDown",[VK_HOME] = "key_home",
  334.         [VK_END] = "key_end",[VK_DELETE] = "key_delete",[VK_SPACE] = "key_space",[VK_RETURN] = "key_enter",
  335.         [VK_UP] = "button_up",[VK_RIGHT] = "button_right",[VK_DOWN] = "button_down",[VK_LEFT] = "button_left",
  336.     }
  337.  
  338.     function customTags.playerKey(fmt,out,args)
  339.         local keyName = (args[1] or ""):lower()
  340.  
  341.         local imageName
  342.  
  343.         if Player.count() > 1 or Misc.GetSelectedControllerName(1) ~= "Keyboard" then
  344.             imageName = "button_".. keyName
  345.         else
  346.             local keyCode = inputConfig1[keyName]
  347.  
  348.             if keyCode == nil then
  349.                 return fmt
  350.             end
  351.  
  352.             if keyCode >= 65 and keyCode <= 90 then
  353.                 imageName = string.char(keyCode)
  354.             elseif keyCodeNames[keyCode] then
  355.                 imageName = keyCodeNames[keyCode]
  356.             else
  357.                 imageName = "button_".. keyName
  358.             end
  359.         end
  360.  
  361.  
  362.         local imagePath
  363.  
  364.         if currentlyUpdatingBox ~= nil then
  365.             imagePath = imagePath or Misc.resolveGraphicsFile("littleDialogue/".. currentlyUpdatingBox.styleName.. "/keys/".. imageName.. ".png")
  366.         end
  367.  
  368.  
  369.         imagePath = imagePath or Misc.resolveGraphicsFile("littleDialogue/keys/".. imageName.. ".png")
  370.  
  371.  
  372.         if imagePath == nil then
  373.             return fmt
  374.         end
  375.  
  376.  
  377.         -- Find relative path because Misc.resolveFile is picky
  378.         local imageRelativePath = imagePath
  379.         local smbxPath = getSMBXPath()
  380.        
  381.         for i = #smbxPath,1,-1 do
  382.             local imageChar = imageRelativePath[i]
  383.             local smbxChar = smbxPath[i]
  384.  
  385.             if imageChar == smbxChar or (imageChar == "/" and smbxChar == "\\") or (imageChar == "\\" and smbxChar == "/") then
  386.                 imageRelativePath = imageRelativePath:sub(1,i-1).. imageRelativePath:sub(i+1,#imageRelativePath)
  387.             else
  388.                 error("Invalid <playerKey> image path")
  389.             end
  390.         end
  391.  
  392.         -- Create fmt for image
  393.         local image = Graphics.loadImage(imagePath)
  394.  
  395.         local char = "A"
  396.         local charCode = string.byte(char)
  397.  
  398.  
  399.         local imageFmt = table.clone(fmt)
  400.  
  401.         imageFmt.xscale = 1
  402.         imageFmt.yscale = 1
  403.  
  404.         imageFmt.font = tplusFont.Font{
  405.             main = {image = imageRelativePath,rows = 1,cols = 1,spacing = 2},
  406.             [char] = {row = 1,col = 1},
  407.         }
  408.  
  409.         out[#out+1] = {charCode,fmt = imageFmt}
  410.  
  411.         return fmt
  412.     end
  413.  
  414.     customTags.playerkey = customTags.playerKey
  415.  
  416.     table.insert(selfClosingTags,"playerKey")
  417.  
  418.     -- Portraits
  419.     local portraitData = {}
  420.  
  421.     function littleDialogue.getPortraitData(name)
  422.         if portraitData[name] == nil then
  423.             local txtPath = Misc.resolveFile("littleDialogue/portraits/".. name.. ".txt")
  424.             local data
  425.  
  426.             if txtPath ~= nil then
  427.                 data = configFileReader.rawParse(txtPath,false)
  428.             else
  429.                 data = {}
  430.             end
  431.  
  432.             data.name = name
  433.  
  434.             data.idleFrames = data.idleFrames or 1
  435.             data.idleFrameDelay = data.idleFrameDelay or 1
  436.             data.speakingFrames = data.speakingFrames or 0
  437.             data.speakingFrameDelay = data.speakingFrameDelay or 1
  438.  
  439.             data.variations = data.variations or 1
  440.  
  441.  
  442.             local imagePath = Misc.resolveGraphicsFile("littleDialogue/portraits/".. name.. ".png")
  443.  
  444.             if imagePath ~= nil then
  445.                 data.image = Graphics.loadImage(imagePath)
  446.                 data.width = data.image.width / data.variations
  447.                 data.height = data.image.height / (data.idleFrames + data.speakingFrames)
  448.             else
  449.                 data.width = 0
  450.                 data.height = 0
  451.             end
  452.  
  453.  
  454.             portraitData[name] = data
  455.         end
  456.  
  457.         return portraitData[name]
  458.     end
  459.  
  460.     function customTags.portrait(fmt,out,args)
  461.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  462.             Misc.warn("Invalid use of portrait tag.")
  463.             return fmt
  464.         end
  465.  
  466.         currentlyUpdatingBox:addTextEvent(currentlyUpdatingPage,out,"portrait",args)
  467.  
  468.         return fmt
  469.     end
  470.  
  471.     function textEventFuncs.portrait(box,page,pos,args)
  472.         local name = args[1]
  473.         local needsToUpdateLayouts = false
  474.  
  475.         if name == nil or name == "" then
  476.             box._newPortraitData = nil
  477.  
  478.             box._newSpeakerName = ""
  479.             setVoice(box,nil)
  480.         else
  481.             local portraitData = littleDialogue.getPortraitData(name)
  482.  
  483.             box._newPortraitData = portraitData
  484.             box.portraitVariation = (args[2] or 1) - 1
  485.  
  486.             box._newSpeakerName = portraitData.speakerName or box._newSpeakerName
  487.             setVoice(box,portraitData.voice)
  488.         end
  489.     end
  490.  
  491.     table.insert(selfClosingTags,"portrait")
  492.  
  493.  
  494.     -- shake tag
  495.     -- at the time I made this I didn't know tremble existed lol
  496.     --[[function customTags.shake(fmt,out,args)
  497.         fmt = table.clone(fmt)
  498.  
  499.         fmt.shake = args[1] or 0.75
  500.  
  501.         fmt.posFilter = function(x,y, fmt,img, width,height)
  502.             return x + RNG.random(-fmt.shake,fmt.shake),y + RNG.random(-fmt.shake,fmt.shake)
  503.         end
  504.  
  505.         return fmt
  506.     end]]
  507.  
  508.     -- characterName tag
  509.     littleDialogue.characterNames = {
  510.         [1]  = "Mario",
  511.         [2]  = "Luigi",
  512.         [3]  = "Peach",
  513.         [4]  = "Toad",
  514.         [5]  = "Link",
  515.         [6]  = "Megaman",
  516.         [7]  = "Wario",
  517.         [8]  = "Bowser",
  518.         [9]  = "Klonoa",
  519.         [10] = "Ninja Bomberman",
  520.         [11] = "Rosalina",
  521.         [12] = "Snake",
  522.         [13] = "Zelda",
  523.         [14] = "Ultimate Rinka",
  524.         [15] = "Uncle Broadsword",
  525.         [16] = "Samus",
  526.     }
  527.  
  528.     function customTags.characterName(fmt,out,args)
  529.         local text = ""
  530.  
  531.         for index,p in ipairs(Player.get()) do
  532.             text = text.. (littleDialogue.characterNames[p.character] or "Player")
  533.  
  534.             if index < Player.count()-1 then
  535.                 text = text.. ", "
  536.             elseif index < Player.count() then
  537.                 text = text.. " and "
  538.             end
  539.         end
  540.  
  541.         local segment = tplusUtils.strToCodes(text)
  542.         segment.fmt = fmt
  543.  
  544.         out[#out+1] = segment
  545.  
  546.         return fmt
  547.     end
  548.  
  549.     table.insert(selfClosingTags,"characterName")
  550.  
  551.  
  552.     function customTags.speakerName(fmt,out,args)
  553.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  554.             Misc.warn("Invalid use of portrait tag.")
  555.             return fmt
  556.         end
  557.  
  558.         currentlyUpdatingBox:addTextEvent(currentlyUpdatingPage,out,"speakerName",args)
  559.         --currentlyUpdatingBox.speakerName = (args[1] or "")
  560.  
  561.         return fmt
  562.     end
  563.  
  564.     function textEventFuncs.speakerName(box,page,pos,args)
  565.         box._newSpeakerName = args[1] or ""
  566.     end
  567.  
  568.     table.insert(selfClosingTags,"speakerName")
  569.  
  570.  
  571.     -- Delay
  572.     function customTags.delay(fmt,out,args)
  573.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  574.             Misc.warn("Invalid use of delay tag.")
  575.             return fmt
  576.         end
  577.  
  578.         currentlyUpdatingBox:addTextEvent(currentlyUpdatingPage,out,"delay",args)
  579.  
  580.         return fmt
  581.     end
  582.  
  583.     function textEventFuncs.delay(box,page,pos,args)
  584.         if not box.typewriterFinished then
  585.             box.typewriterDelay = args[1] or box.settings.typewriterDelayLong
  586.             box.typewriterLongDelayWaiting = false
  587.         end
  588.     end
  589.  
  590.     table.insert(selfClosingTags,"delay")
  591.  
  592.  
  593.     -- Voice
  594.     function customTags.voice(fmt,out,args)
  595.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  596.             Misc.warn("Invalid use of voice tag.")
  597.             return fmt
  598.         end
  599.  
  600.         currentlyUpdatingBox:addTextEvent(currentlyUpdatingPage,out,"voice",args)
  601.  
  602.         return fmt
  603.     end
  604.  
  605.     function textEventFuncs.voice(box,page,pos,args)
  606.         setVoice(box,args[1])
  607.     end
  608.  
  609.     table.insert(selfClosingTags,"voice")
  610.  
  611.  
  612.     -- setPos
  613.     function customTags.setPos(fmt,out,args)
  614.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  615.             Misc.warn("Invalid use of setPos tag.")
  616.             return fmt
  617.         end
  618.  
  619.         currentlyUpdatingBox:addTextEvent(currentlyUpdatingPage,out,"setPos",args)
  620.  
  621.         return fmt
  622.     end
  623.  
  624.     function textEventFuncs.setPos(box,page,pos,args)
  625.         if args[1] ~= nil and args[2] ~= nil then
  626.             box.forcedPosX = args[1]
  627.             box.forcedPosY = args[2]
  628.             box.forcedPosHorizontalPivot = args[3] or 0.5
  629.             box.forcedPosVerticalPivot = args[4] or 0.5
  630.         else
  631.             box.forcedPosX = nil
  632.             box.forcedPosY = nil
  633.             box.forcedPosHorizontalPivot = nil
  634.             box.forcedPosVerticalPivot = nil
  635.         end
  636.     end
  637.  
  638.     table.insert(selfClosingTags,"setPos")
  639.  
  640.  
  641.     -- break with no marker
  642.     function customTags.brNoMarker(fmt,out,args)
  643.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  644.             Misc.warn("Invalid use of brNoMarker tag.")
  645.             return fmt
  646.         end
  647.  
  648.         out[#out+1] = {string.byte("\n"), fmt = fmt}
  649.  
  650.         local pageData = currentlyUpdatingBox.pageData[currentlyUpdatingPage]
  651.  
  652.         if pageData ~= nil then
  653.             local pos = getTextPosFromValue(out)
  654.            
  655.             pageData._noMarkerIndices[pos] = true
  656.         end
  657.  
  658.         return fmt
  659.     end
  660.  
  661.     table.insert(selfClosingTags,"brNoMarker")
  662.  
  663.     -- no auto delay
  664.     function customTags.noAutoDelay(fmt,out,args)
  665.         if currentlyUpdatingBox == nil or currentlyUpdatingPage == nil then
  666.             Misc.warn("Invalid use of brNoMarker tag.")
  667.             return fmt
  668.         end
  669.  
  670.         local pageData = currentlyUpdatingBox.pageData[currentlyUpdatingPage]
  671.  
  672.         if pageData ~= nil then
  673.             pageData.noAutoDelay = true
  674.         end
  675.  
  676.         return fmt
  677.     end
  678.  
  679.     table.insert(selfClosingTags,"noAutoDelay")
  680. end
  681.  
  682.  
  683.  
  684. littleDialogue.BOX_STATE = STATE
  685.  
  686.  
  687. function littleDialogue.create(args)
  688.     local box = setmetatable({},boxMT)
  689.  
  690.     box.isValid = true
  691.  
  692.     box.text = args.text or ""
  693.     box.speakerObj = args.speakerObj or player
  694.     box.uncontrollable = args.uncontrollable or false
  695.     box.uncloseableByPlayer = args.uncloseableByPlayer or false
  696.     box.silent = args.silent or false
  697.  
  698.     box.pauses = args.pauses
  699.     if box.pauses == nil then
  700.         box.pauses = true
  701.     end
  702.  
  703.     box.keepOnScreen = args.keepOnScreen
  704.     if box.keepOnScreen == nil then
  705.         box.keepOnScreen = true
  706.     end
  707.  
  708.     box.updatesInPause = args.updatesInPause
  709.     if box.updatesInPause == nil then
  710.         box.updatesInPause = box.pauses
  711.     end
  712.  
  713.  
  714.  
  715.     box.openingProgress = 0
  716.     box.state = STATE.IN
  717.  
  718.     box.page = 1
  719.  
  720.     box.answersPageIndex = {}
  721.     box.answersPageTarget = 1
  722.  
  723.     box.selectedAnswer = 1
  724.  
  725.  
  726.     box.mainWidth = 0
  727.     box.mainHeight = 0
  728.  
  729.  
  730.     box.typewriterLimit = 0
  731.     box.typewriterDelay = 0
  732.     box.typewriterLongDelayWaiting = false
  733.     box.typewriterFinished = true
  734.  
  735.    
  736.     box.portraitData = nil
  737.     box.portraitFrame = 0
  738.     box.portraitTimer = 0
  739.     box.portraitVariation = 0
  740.  
  741.     box.speakerName = args.speakerName or ""
  742.     box.speakerNameLayout = nil
  743.  
  744.     box.voiceSound = nil
  745.  
  746.     box.mainOffsetY = 0
  747.  
  748.  
  749.     -- Note: these are for the <setPos> tag/args for creating, not the settings!
  750.     box.forcedPosX = args.forcedPosX
  751.     box.forcedPosY = args.forcedPosY
  752.     box.forcedPosHorizontalPivot = args.forcedPosHorizontalPivot
  753.     box.forcedPosVerticalPivot = args.forcedPosVerticalPivot
  754.  
  755.  
  756.     box.overwriteSettings = args.settings or {}
  757.     box:setStyle(args.style or littleDialogue.defaultStyleName)
  758.  
  759.  
  760.     box:updateLayouts()
  761.    
  762.  
  763.     if box.pauses then
  764.         Misc.pause(true)
  765.     end
  766.  
  767.     if not box.silent and box.settings.openSoundEnabled then
  768.         SFX.play(box.settings.openSound)
  769.     end
  770.  
  771.  
  772.     table.insert(littleDialogue.boxes,box)
  773.  
  774.     return box
  775. end
  776.  
  777.  
  778.  
  779. function boxInstanceFunctions:addQuestion(pageIndex,answer)
  780.     local maxWidth = self.maxWidth
  781.     if self.settings.selectorImage ~= nil and self.settings.selectorImageEnabled then
  782.         maxWidth = maxWidth - self.settings.selectorImage.width
  783.     end
  784.     if self.portraitData ~= nil then
  785.         maxWidth = maxWidth - self.portraitData.width
  786.     end
  787.  
  788.     local layout = textplus.layout(answer.text,maxWidth,{font = self.settings.font,color = self.settings.textColor,xscale = self.settings.textXScale,yscale = self.settings.textYScale},customTags,selfClosingTags)
  789.  
  790.  
  791.     local page = self.pageData[pageIndex]
  792.  
  793.     local answerPageCount = #page.answerPages
  794.     local answerPage = page.answerPages[answerPageCount]
  795.  
  796.     if answerPageCount == 0 or answerPage.height+layout.height+self.settings.answerGap >= self.settings.answerPageMaxHeight then
  797.         -- Create new page of answers
  798.         local firstAnswerIndex = 1
  799.         if answerPageCount > 0 then
  800.             firstAnswerIndex = answerPage.firstAnswerIndex + #answerPage.answers
  801.         end
  802.  
  803.         answerPageCount = answerPageCount + 1
  804.  
  805.         answerPage = {answers = {},width = 0,height = 0,firstAnswerIndex = firstAnswerIndex}
  806.         page.answerPages[answerPageCount] = answerPage
  807.     else
  808.         answerPage.height = answerPage.height + self.settings.answerGap
  809.     end
  810.  
  811.  
  812.  
  813.     local width = layout.width
  814.     if self.settings.selectorImage ~= nil and self.settings.selectorImageEnabled then
  815.         width = width + self.settings.selectorImage.width
  816.     end
  817.  
  818.     answerPage.width = math.max(answerPage.width,width)
  819.     answerPage.height = answerPage.height + layout.height
  820.  
  821.     local answerObj = {
  822.         text = answer.text,layout = layout,
  823.         chosenFunction = answer.chosenFunction,
  824.         addText = answer.addText,
  825.     }
  826.  
  827.  
  828.     table.insert(answerPage.answers,answerObj)
  829.     table.insert(page.plainAnswerList,answerObj)
  830.  
  831.     page.totalAnswersCount = page.totalAnswersCount + 1
  832. end
  833.  
  834.  
  835.  
  836. function boxInstanceFunctions:addTextEvent(pageIndex,addPos,eventName,eventArgs)
  837.     addPos = getTextPosFromValue(addPos)
  838.  
  839.     local page = self.pageData[pageIndex]
  840.     local textEventsList = page.textEvents[addPos]
  841.  
  842.     -- If this character has no events yet, make a list
  843.     if textEventsList == nil then
  844.         textEventsList = {}
  845.         page.textEvents[addPos] = textEventsList
  846.     end
  847.  
  848.     -- Add this event to the list
  849.     table.insert(textEventsList,{eventName,eventArgs})
  850. end
  851.  
  852.  
  853. -- Activates the effects of tags like <portrait>, <speakerName> or <voice> between the characters specified
  854. function boxInstanceFunctions:activateTextEvents(startPos,endPos)
  855.     -- Setup values for the events: these are to let the event funcs say that :updateLayouts needs to be run.
  856.     -- The reason why this isn't done in the events themselves is that that can cause stack overflow and other nonsense
  857.     self._newPortraitData = self.portraitData
  858.     self._newSpeakerName = self.speakerName
  859.  
  860.  
  861.     local pageIndex = math.floor(self.page)
  862.     local page = self.pageData[pageIndex]
  863.  
  864.     for i = (startPos or 0), (endPos or #page.characterList) do
  865.         local events = page.textEvents[i]
  866.  
  867.         if events ~= nil then
  868.             for _,event in ipairs(events) do
  869.                 local func = textEventFuncs[event[1]]
  870.  
  871.                 func(self,pageIndex,i,event[2])
  872.             end
  873.         end
  874.     end
  875.  
  876.  
  877.     -- Check those values
  878.     if self._newPortraitData ~= self.portraitData or self._newSpeakerName ~= self.speakerName then
  879.         self.portraitData = self._newPortraitData
  880.         self.speakerName = self._newSpeakerName
  881.  
  882.         self:updateLayouts()
  883.     end
  884. end
  885.  
  886.  
  887. function boxInstanceFunctions:updateLayouts()
  888.     -- Initial setup for pages (rest comes later)
  889.     self.pageData = {}
  890.     self.pageCount = 0
  891.  
  892.     for index,text in ipairs(string.split(self.text,"<page>",true)) do
  893.         local page = {}
  894.  
  895.         page.index = index
  896.         page.text = text
  897.  
  898.         self.pageCount = self.pageCount + 1
  899.         self.pageData[self.pageCount] = page
  900.     end
  901.  
  902.     self.speakerNameLayout = nil
  903.     self.mainOffsetY = 0
  904.  
  905.  
  906.     currentlyUpdatingBox = self
  907.  
  908.  
  909.     -- _newStyle is for the <boxStyle> tag. If that tag is parsed, it'll set _newStyle and then things can be re-done.
  910.     self._newStyle = self.styleName
  911.  
  912.  
  913.     local totalBorderSize = self.settings.borderSize*2
  914.  
  915.     local mainTextFmt = {font = self.settings.font,color = self.settings.textColor,xscale = self.settings.textXScale,yscale = self.settings.textYScale}
  916.  
  917.     local hasLineMarkers = (self.settings.lineMarkerImage ~= nil and self.settings.lineMarkerEnabled)
  918.  
  919.     for index,page in ipairs(self.pageData) do
  920.         currentlyUpdatingPage = index
  921.  
  922.         page.answerPages = {}
  923.         page.plainAnswerList = {}
  924.         page.totalAnswersCount = 0
  925.         page.answersPageIndex = page.answersPageIndex or 1
  926.  
  927.         page.lineMarkers = {}
  928.         page._noMarkerIndices = {}
  929.  
  930.         page.noAutoDelay = false
  931.  
  932.         page.textEvents = {}
  933.  
  934.  
  935.         page.formattedText = textplus.parse(page.text,mainTextFmt,customTags,selfClosingTags)
  936.  
  937.         if self._newStyle ~= self.styleName then -- change style
  938.             self:setStyle(self._newStyle)
  939.             self:updateLayouts()
  940.             return
  941.         end
  942.  
  943.  
  944.         local arrowWidth = 0
  945.         if index < self.pageCount and page.totalAnswersCount == 0 and not self.uncontrollable
  946.         and self.settings.continueArrowEnabled and self.settings.continueArrowImage ~= nil
  947.         then
  948.             arrowWidth = self.settings.continueArrowImage.width
  949.         end
  950.  
  951.         local maxWidth = self.maxWidth - arrowWidth
  952.         if self.portraitData ~= nil then
  953.             maxWidth = maxWidth - self.portraitData.width - self.settings.portraitGap
  954.         end
  955.  
  956.         if hasLineMarkers then
  957.             maxWidth = maxWidth - self.settings.lineMarkerImage.width
  958.         end
  959.  
  960.  
  961.         page.layout = textplus.layout(page.formattedText,maxWidth)
  962.  
  963.         local width = page.layout.width + arrowWidth
  964.         local height = page.layout.height
  965.  
  966.  
  967.         -- Question stuff
  968.         local widestAnswerPageWidth = 0
  969.  
  970.         page.answersHeight = 0
  971.  
  972.         for answerPageIndex,answerPage in ipairs(page.answerPages) do
  973.             widestAnswerPageWidth = math.max(widestAnswerPageWidth, answerPage.width)
  974.             page.answersHeight = math.max(page.answersHeight, answerPage.height)
  975.         end
  976.  
  977.  
  978.         width = math.max(widestAnswerPageWidth,width)
  979.         height = height + page.answersHeight
  980.  
  981.  
  982.         local answerPageCount = #page.answerPages
  983.  
  984.         if answerPageCount > 0 then
  985.             height = height + self.settings.questionGap
  986.  
  987.             if answerPageCount > 1 and self.settings.scrollArrowEnabled and self.settings.scrollArrowImage ~= nil then
  988.                 height = height + self.settings.scrollArrowImage.height*2
  989.             end
  990.         end
  991.  
  992.  
  993.         if self.portraitData ~= nil then
  994.             width = width + self.portraitData.width + self.settings.portraitGap
  995.             height = math.max(height,self.portraitData.height)
  996.         end
  997.  
  998.  
  999.         self.mainWidth = math.max(self.mainWidth,width)
  1000.         self.mainHeight = math.max(self.mainHeight,height)
  1001.  
  1002.  
  1003.         -- Find where to place line markers
  1004.         local characterCount = 0
  1005.         local isForcedLine = {}
  1006.  
  1007.         for i,segment in ipairs(page.formattedText) do
  1008.             for _,character in ipairs(segment) do
  1009.                 characterCount = characterCount + 1
  1010.  
  1011.                 if character == 10 and not page._noMarkerIndices[characterCount] then
  1012.                     isForcedLine[page.formattedText[i]] = true
  1013.                 end
  1014.             end
  1015.         end
  1016.  
  1017.  
  1018.         -- Simplify the character list and add line markers
  1019.         page.characterList = {}
  1020.  
  1021.         local lineCount = #page.layout
  1022.         local lineMarkerY = 0
  1023.  
  1024.         for lineIndex,line in ipairs(page.layout) do
  1025.             -- Add asterisk
  1026.             lineMarkerY = lineMarkerY + line.ascent
  1027.  
  1028.             if hasLineMarkers and (lineIndex == 1 or isForcedLine[line[1]]) then
  1029.                 local hasFirstCharacterFromSeg = true
  1030.  
  1031.                 if lineIndex > 1 then
  1032.                     -- Find if this is the start of the segment
  1033.                     for i = 1,line[2]-1 do
  1034.                         local character = line[1][i]
  1035.  
  1036.                         if character ~= 10 then
  1037.                             hasFirstCharacterFromSeg = false
  1038.                             break
  1039.                         end
  1040.                     end
  1041.                 end
  1042.  
  1043.                 if hasFirstCharacterFromSeg then
  1044.                     table.insert(page.lineMarkers,{y = lineMarkerY,limit = #page.characterList})
  1045.                 end
  1046.             end
  1047.  
  1048.             lineMarkerY = lineMarkerY + line.descent
  1049.  
  1050.             -- Add characters
  1051.             for i = 1,#line,4 do
  1052.                 local segment = line[i]
  1053.  
  1054.                 if segment.img ~= nil then
  1055.                     table.insert(page.characterList,-2)
  1056.                 else
  1057.                     local startIdx = line[i+1]
  1058.                     local endIdx = line[i+2]
  1059.  
  1060.                     for charIdx = startIdx,endIdx do
  1061.                         table.insert(page.characterList,segment[charIdx])
  1062.                     end
  1063.                 end
  1064.             end
  1065.  
  1066.             if lineIndex < lineCount then
  1067.                 table.insert(page.characterList,-1)
  1068.             end
  1069.         end
  1070.     end
  1071.  
  1072.     currentlyUpdatingBox = nil
  1073.     currentlyUpdatingPage = nil
  1074.  
  1075.  
  1076.     -- Speaker name
  1077.     if self.speakerName ~= "" then
  1078.         local speakerNameFmt = {font = self.settings.speakerNameFont,color = self.settings.speakerNameColor,xscale = self.settings.speakerNameXScale,yscale = self.settings.speakerNameYScale}
  1079.         local speakerNameExtraWidth = math.abs(self.settings.speakerNameOffsetX)*2
  1080.  
  1081.         if self.settings.speakerNameOnTop then
  1082.             speakerNameExtraWidth = speakerNameExtraWidth - totalBorderSize
  1083.  
  1084.             if self.settings.speakerNameBoxImage ~= nil then
  1085.                 speakerNameExtraWidth = speakerNameExtraWidth + self.settings.speakerNameBoxImage.width/3*2
  1086.             end
  1087.         end
  1088.  
  1089.         self.speakerNameLayout = textplus.layout(self.speakerName,self.maxWidth - speakerNameExtraWidth,speakerNameFmt,customTags,selfClosingTags)
  1090.  
  1091.         if not self.settings.speakerNameOnTop then
  1092.             self.mainOffsetY = self.mainOffsetY + self.speakerNameLayout.height + self.settings.speakerNameGap
  1093.         end
  1094.  
  1095.         self.mainWidth = math.max(self.mainWidth,self.speakerNameLayout.width + speakerNameExtraWidth)
  1096.     end
  1097.  
  1098.  
  1099.     -- Make the box a bit bigger if below min size
  1100.     if self.settings.useMaxWidthAsBoxWidth then
  1101.         self.mainWidth = math.max(self.mainWidth,self.settings.textMaxWidth)
  1102.     end
  1103.  
  1104.     self.mainHeight = math.max(self.mainHeight,self.settings.minBoxMainHeight)
  1105.  
  1106.  
  1107.     self.totalWidth  = self.mainWidth  + totalBorderSize
  1108.     self.totalHeight = self.mainHeight + totalBorderSize + self.mainOffsetY
  1109.  
  1110.  
  1111.     if self.settings.typewriterEnabled then
  1112.         self:activateTextEvents(0,0)
  1113.     else
  1114.         self:activateTextEvents()
  1115.     end
  1116. end
  1117.  
  1118.  
  1119. function boxInstanceFunctions:addDialogue(text,deleteFurtherText)
  1120.     if deleteFurtherText == nil or deleteFurtherText then
  1121.         -- Delete any text after this page
  1122.         local searchStart = 1
  1123.         local pageIndex = 1
  1124.  
  1125.         while (true) do
  1126.             local foundStart,foundEnd = self.text:find("<page>",searchStart,true)
  1127.  
  1128.             if foundStart == nil then
  1129.                 break
  1130.             end
  1131.  
  1132.             pageIndex = pageIndex + 1
  1133.  
  1134.             if pageIndex <= math.ceil(self.page) then
  1135.                 searchStart = foundEnd + 1
  1136.             else
  1137.                 self.text = self.text:sub(1,foundStart-1)
  1138.                 break
  1139.             end
  1140.         end
  1141.     end
  1142.  
  1143.  
  1144.     self.maxWidth = self.mainWidth
  1145.  
  1146.  
  1147.     if self.text == "" or self.text:sub(-1) == "<page>" then
  1148.         self.text = self.text.. text
  1149.     else
  1150.         self.text = self.text.. "<page>".. text
  1151.     end
  1152.  
  1153.     self:updateLayouts()
  1154. end
  1155.  
  1156.  
  1157. function boxInstanceFunctions:close()
  1158.     self.state = STATE.OUT
  1159.  
  1160.     if not self.silent and self.settings.closeSoundEnabled and answer == nil then
  1161.         SFX.play(self.settings.closeSound)
  1162.     end
  1163. end
  1164.  
  1165.  
  1166. function boxInstanceFunctions:progress(isFromPlayer)
  1167.     local page = self.pageData[self.page]
  1168.     local answer = page.plainAnswerList[self.selectedAnswer]
  1169.  
  1170.     if answer ~= nil then
  1171.         if answer.addText ~= nil then
  1172.             self:addDialogue(answer.addText,true)
  1173.         end
  1174.  
  1175.         if answer.chosenFunction ~= nil then
  1176.             answer.chosenFunction(self)
  1177.         end
  1178.  
  1179.         if not self.silent and self.settings.chooseAnswerSoundEnabled then
  1180.             SFX.play(self.settings.chooseAnswerSound)
  1181.         end
  1182.     end
  1183.  
  1184.     if self.page < self.pageCount then
  1185.         self.state = STATE.SCROLL
  1186.  
  1187.         self.selectedAnswer = 1
  1188.  
  1189.         self.typewriterLimit = 0
  1190.         self.typewriterFinished = (not self.settings.typewriterEnabled)
  1191.  
  1192.         if not self.silent and self.settings.scrollSoundEnabled and answer == nil then
  1193.             SFX.play(self.settings.scrollSound)
  1194.         end
  1195.     elseif not self.uncloseableByPlayer or not isFromPlayer then
  1196.         self:close()
  1197.  
  1198.         player:mem(0x11E,FIELD_BOOL,false)
  1199.     end
  1200. end
  1201.  
  1202. function boxInstanceFunctions:update()
  1203.     if self.state == STATE.STAY then
  1204.         local page = self.pageData[self.page]
  1205.  
  1206.         local characterCount = #page.characterList
  1207.  
  1208.         if not self.typewriterFinished then
  1209.             self.typewriterDelay = self.typewriterDelay - 1
  1210.            
  1211.             if self.typewriterDelay <= 0 then
  1212.                 self.typewriterLimit = self.typewriterLimit + 1
  1213.  
  1214.                 if self.typewriterLimit < characterCount then
  1215.                     local character = page.characterList[self.typewriterLimit]
  1216.  
  1217.                     if not page.noAutoDelay and (self.settings.typewriterDelayCharacters[character] or self.typewriterLongDelayWaiting) then -- extra delaying character
  1218.                         local nextCharacter = page.characterList[self.typewriterLimit + 1]
  1219.  
  1220.                         if self.settings.typewriterClosingCharacters[nextCharacter] or self.settings.typewriterDelayCharacters[nextCharacter] then
  1221.                             self.typewriterDelay = self.settings.typewriterDelayNormal
  1222.                             self.typewriterLongDelayWaiting = true
  1223.                         else
  1224.                             self.typewriterDelay = self.settings.typewriterDelayLong
  1225.                             self.typewriterLongDelayWaiting = false
  1226.                         end
  1227.                     else
  1228.                         self.typewriterDelay = self.settings.typewriterDelayNormal
  1229.                     end
  1230.                 else
  1231.                     self.typewriterFinished = true
  1232.                     self.typewriterLongDelayWaiting = false
  1233.                     self.portraitTimer = 0
  1234.                 end
  1235.  
  1236.                 self:activateTextEvents(self.typewriterLimit,self.typewriterLimit)
  1237.  
  1238.                 if not self.silent then
  1239.                     if self.voiceSound ~= nil then
  1240.                         SFX.play{sound = self.voiceSound,delay = self.settings.typewriterSoundDelay}
  1241.                     elseif self.settings.typewriterSoundEnabled then
  1242.                         SFX.play{sound = self.settings.typewriterSound,delay = self.settings.typewriterSoundDelay}
  1243.                     end
  1244.                 end
  1245.             end
  1246.         end
  1247.  
  1248.  
  1249.         if not self.uncontrollable then
  1250.             if self.typewriterFinished then
  1251.                 if page.totalAnswersCount > 0 then
  1252.                     local answerPage = page.answerPages[page.answersPageIndex]
  1253.  
  1254.                     if player.rawKeys.up == KEYS_PRESSED and self.selectedAnswer > 1 then
  1255.                         self.selectedAnswer = self.selectedAnswer - 1
  1256.  
  1257.                         if not self.silent and self.settings.moveSelectionSoundEnabled then
  1258.                             SFX.play(self.settings.moveSelectionSound)
  1259.                         end
  1260.                     elseif player.rawKeys.down == KEYS_PRESSED and self.selectedAnswer < page.totalAnswersCount then
  1261.                         self.selectedAnswer = self.selectedAnswer + 1
  1262.                        
  1263.                         if not self.silent and self.settings.moveSelectionSoundEnabled then
  1264.                             SFX.play(self.settings.moveSelectionSound)
  1265.                         end
  1266.                     end
  1267.  
  1268.                     if self.selectedAnswer < answerPage.firstAnswerIndex then
  1269.                         self.state = STATE.SCROLL_ANSWERS
  1270.                         self.answersPageTarget = page.answersPageIndex - 1
  1271.                     elseif self.selectedAnswer > answerPage.firstAnswerIndex+(#answerPage.answers - 1) then
  1272.                         self.state = STATE.SCROLL_ANSWERS
  1273.                         self.answersPageTarget = page.answersPageIndex + 1
  1274.                     end
  1275.                 end
  1276.  
  1277.                 if player.rawKeys.jump == KEYS_PRESSED then
  1278.                     self:progress(true)
  1279.                 end
  1280.             else
  1281.                 if player.rawKeys.jump == KEYS_PRESSED or player.rawKeys.run == KEYS_PRESSED then
  1282.                     self.typewriterFinished = true
  1283.  
  1284.                     self:activateTextEvents(self.typewriterLimit+1,nil)
  1285.  
  1286.                     self.typewriterLimit = characterCount
  1287.                     self.portraitTimer = 0
  1288.                 end
  1289.             end
  1290.         end
  1291.     elseif self.state == STATE.SCROLL then
  1292.         local target = math.floor(self.page)+1
  1293.  
  1294.         self.page = math.min(target,self.page + self.settings.pageScrollSpeed)
  1295.  
  1296.         if self.page == target then
  1297.             self.state = STATE.STAY
  1298.  
  1299.             if self.settings.typewriterEnabled then
  1300.                 self:activateTextEvents(0,0)
  1301.             else
  1302.                 self:activateTextEvents()
  1303.             end
  1304.         end
  1305.     elseif self.state == STATE.SCROLL_ANSWERS then
  1306.         local page = self.pageData[self.page]
  1307.  
  1308.         local current = page.answersPageIndex
  1309.         local target = self.answersPageTarget
  1310.  
  1311.         if current < target then
  1312.             page.answersPageIndex = math.min(target,current + self.settings.answerPageScrollSpeed)
  1313.         elseif current > target then
  1314.             page.answersPageIndex = math.max(target,current - self.settings.answerPageScrollSpeed)
  1315.         else
  1316.             self.state = STATE.STAY
  1317.         end
  1318.     elseif self.state == STATE.IN then
  1319.         self.openingProgress = math.min(1,self.openingProgress + self.settings.openSpeed)
  1320.        
  1321.         if self.openingProgress == 1 then
  1322.             self.state = STATE.STAY
  1323.         end
  1324.     elseif self.state == STATE.OUT then
  1325.         self.openingProgress = math.max(0,self.openingProgress - self.settings.openSpeed)
  1326.  
  1327.         if self.openingProgress == 0 then
  1328.             self.state = STATE.REMOVE
  1329.  
  1330.             if self.pauses then
  1331.                 Misc.unpause()
  1332.             end
  1333.         end
  1334.     end
  1335.  
  1336.     -- Profile animation
  1337.     local portraitData = self.portraitData
  1338.  
  1339.     if portraitData ~= nil then
  1340.         if self.typewriterFinished or self.state ~= STATE.STAY or portraitData.speakingFrames <= 0 then
  1341.             self.portraitFrame = (math.floor(self.portraitTimer / portraitData.idleFrameDelay) % portraitData.idleFrames)
  1342.         else
  1343.             self.portraitFrame = (math.floor(self.portraitTimer / portraitData.speakingFrameDelay) % portraitData.speakingFrames) + portraitData.idleFrames
  1344.         end
  1345.  
  1346.         self.portraitTimer = self.portraitTimer + 1
  1347.     end
  1348. end
  1349.  
  1350.  
  1351. local mainBuffer = Graphics.CaptureBuffer(800,600)
  1352. local fullBuffer = Graphics.CaptureBuffer(800,600)
  1353. local answerBuffer = Graphics.CaptureBuffer(800,600)
  1354.  
  1355. local function drawBufferDebug(buffer,priority,x,y,usedWidth,usedHeight)
  1356.     Graphics.drawBox{x = x,y = y,width = usedWidth,height = usedHeight,priority = priority,color = Color.black}
  1357.  
  1358.     Graphics.drawBox{x = x + usedWidth,y = y,width = buffer.width - usedWidth,height = usedHeight,color = Color.darkred,priority = priority}
  1359.     Graphics.drawBox{x = x,y = y + usedHeight,width = buffer.width,height = buffer.height - usedHeight,color = Color.darkred,priority = priority}
  1360.  
  1361.     Graphics.drawBox{texture = buffer,priority = priority,x = x,y = y}
  1362. end
  1363.  
  1364.  
  1365. local function getFirstFontInLayout(layout)
  1366.     for _,line in ipairs(layout) do
  1367.         for _,seg in ipairs(line) do
  1368.             if seg.fmt ~= nil and seg.fmt.font ~= nil then
  1369.                 return seg.fmt.font
  1370.             end
  1371.         end
  1372.     end
  1373.  
  1374.     return nil
  1375. end
  1376.  
  1377. local function getTextShaderUniforms(layout)
  1378.     local uniforms = {time = lunatime.tick()}
  1379.  
  1380.     -- Uniforms pass some info about the font: however, it's always the first found in the layout
  1381.     local font = getFirstFontInLayout(layout)
  1382.  
  1383.     if font ~= nil then
  1384.         uniforms.imageSize = vector(font.imageWidth,font.imageHeight)
  1385.         uniforms.cellSize = vector(font.cellWidth,font.cellHeight)
  1386.  
  1387.         uniforms.ascent = font.ascent
  1388.         uniforms.descent = font.descent
  1389.     else
  1390.         uniforms.imageSize = vector(1,1)
  1391.         uniforms.cellSize = vector(1,1)
  1392.  
  1393.         uniforms.ascent = 0
  1394.         uniforms.descent = 0
  1395.     end
  1396.  
  1397.     return uniforms
  1398. end
  1399.  
  1400.  
  1401. local function drawAnswers(self,page,textX,mainTextY)
  1402.     answerBuffer:clear(self.priority)
  1403.  
  1404.     for answersPageIndex = math.floor(page.answersPageIndex), math.ceil(page.answersPageIndex) do
  1405.         local answerPage = page.answerPages[answersPageIndex]
  1406.  
  1407.         if answerPage ~= nil then
  1408.             local answerX = textX
  1409.             local answerY = math.floor((-(page.answersPageIndex - 1) + (answersPageIndex - 1)) * (page.answersHeight + self.settings.answerGap))
  1410.  
  1411.             if self.settings.selectorImage ~= nil and self.settings.selectorImageEnabled then
  1412.                 answerX = answerX + self.settings.selectorImage.width
  1413.             end
  1414.  
  1415.             for answerIndex,answer in ipairs(answerPage.answers) do
  1416.                 local totalIndex = (answerPage.firstAnswerIndex + (answerIndex - 1))
  1417.                 local answerColor
  1418.  
  1419.                 if page.index == self.page and totalIndex == self.selectedAnswer and self.typewriterFinished and self.state ~= STATE.SCROLL then
  1420.                     answerColor = self.settings.answerSelectedColor
  1421.  
  1422.                     if self.settings.selectorImage ~= nil and self.settings.selectorImageEnabled then
  1423.                         Graphics.drawBox{
  1424.                             texture = self.settings.selectorImage,target = answerBuffer,priority = self.priority,
  1425.                             x = textX,y = answerY + answer.layout.height*0.5 - self.settings.selectorImage.height*0.5,
  1426.                         }
  1427.                     end
  1428.                 else
  1429.                     answerColor = self.settings.answerUnselectedColor
  1430.                 end
  1431.  
  1432.                 textplus.render{
  1433.                     layout = answer.layout,x = answerX,y = answerY,color = answerColor,priority = self.priority,target = answerBuffer,
  1434.                     shader = self.settings.mainTextShader,uniforms = getTextShaderUniforms(answer.layout),
  1435.                 }
  1436.  
  1437.                 answerY = answerY + answer.layout.height + self.settings.answerGap
  1438.             end
  1439.         end
  1440.     end
  1441.  
  1442.     -- Draw those answers to the text buffer
  1443.     local answersY = mainTextY + page.layout.height + self.settings.questionGap
  1444.  
  1445.     local answerPageCount = #page.answerPages
  1446.     local scrollArrow = self.settings.scrollArrowImage
  1447.  
  1448.     if answerPageCount > 1 and scrollArrow ~= nil and self.settings.scrollArrowEnabled then
  1449.         local offset = (math.floor(lunatime.drawtick()/32)%2)*2
  1450.  
  1451.         local iconHeight = scrollArrow.height
  1452.  
  1453.         local answerPageIndex = page.answersPageIndex
  1454.  
  1455.         if math.floor(self.page) == self.page --[[and math.floor(answerPageIndex) == answerPageIndex]] and self.typewriterFinished then
  1456.             local iconWidth = scrollArrow.width*0.5
  1457.             local iconX = self.mainWidth*0.5 - iconWidth*0.5
  1458.  
  1459.             if self.portraitData ~= nil then
  1460.                 iconX = iconX + (self.portraitData.width + self.settings.portraitGap)*0.5
  1461.             end
  1462.  
  1463.             if answerPageIndex >= 2 then
  1464.                 Graphics.drawBox{
  1465.                     texture = scrollArrow,target = mainBuffer,priority = self.priority,
  1466.                     x = iconX,y = answersY + offset,sourceX = 0,sourceY = 0,
  1467.                     sourceWidth = iconWidth,sourceHeight = iconHeight,
  1468.                 }
  1469.             end
  1470.  
  1471.             if answerPageIndex <= answerPageCount-1 then
  1472.                 Graphics.drawBox{
  1473.                     texture = scrollArrow,target = mainBuffer,priority = self.priority,
  1474.                     x = iconX,y = answersY + iconHeight + page.answersHeight - offset,sourceX = iconWidth,sourceY = 0,
  1475.                     sourceWidth = iconWidth,sourceHeight = iconHeight,
  1476.                 }
  1477.             end
  1478.         end
  1479.  
  1480.         answersY = answersY + iconHeight
  1481.     end
  1482.  
  1483.     Graphics.drawBox{
  1484.         texture = answerBuffer,target = mainBuffer,priority = self.priority,
  1485.         x = 0,y = answersY,width = self.mainWidth,height = page.answersHeight,
  1486.         sourceWidth = self.mainWidth,sourceHeight = page.answersHeight,
  1487.     }
  1488. end
  1489.  
  1490.  
  1491. local function drawSegmentedBox(image,priority,sceneCoords,color,x,y,width,height,cutoffWidth,cutoffHeight)
  1492.     local vertexCoords = {}
  1493.     local textureCoords = {}
  1494.  
  1495.     local vertexCount = 0
  1496.  
  1497.     local segmentWidth = image.width / 3
  1498.     local segmentHeight = image.height / 3
  1499.  
  1500.     local segmentCountX = math.max(2,math.ceil(width / segmentWidth))
  1501.     local segmentCountY = math.max(2,math.ceil(height / segmentHeight))
  1502.  
  1503.     x = math.floor(x)
  1504.     y = math.floor(y)
  1505.  
  1506.     for segmentIndexX = 1, segmentCountX do
  1507.         for segmentIndexY = 1, segmentCountY do
  1508.             local thisX = x
  1509.             local thisY = y
  1510.             local thisWidth = math.min(width * 0.5,segmentWidth)
  1511.             local thisHeight = math.min(height * 0.5,segmentHeight)
  1512.             local thisSourceX = 0
  1513.             local thisSourceY = 0
  1514.  
  1515.             if segmentIndexX == segmentCountX then
  1516.                 thisX = thisX + width - thisWidth
  1517.                 thisSourceX = image.width - thisWidth
  1518.             elseif segmentIndexX > 1 then
  1519.                 thisX = thisX + thisWidth + (segmentIndexX-2)*segmentWidth
  1520.                 thisWidth = math.min(segmentWidth,width - segmentWidth - (thisX - x))
  1521.                 thisSourceX = segmentWidth
  1522.             end
  1523.  
  1524.             if segmentIndexY == segmentCountY then
  1525.                 thisY = thisY + height - thisHeight
  1526.                 thisSourceY = image.height - thisHeight
  1527.             elseif segmentIndexY > 1 then
  1528.                 thisY = thisY + thisHeight + (segmentIndexY-2)*segmentHeight
  1529.                 thisHeight = math.min(segmentHeight,height - segmentHeight - (thisY - y))
  1530.                 thisSourceY = segmentHeight
  1531.             end
  1532.            
  1533.  
  1534.             -- Handle cutoff
  1535.             if cutoffWidth ~= nil and cutoffHeight ~= nil then
  1536.                 local cutoffLeft = x + width*0.5 - cutoffWidth*0.5
  1537.                 local cutoffRight = cutoffLeft + cutoffWidth
  1538.                 local cutoffTop = y + height*0.5 - cutoffHeight*0.5
  1539.                 local cutoffBottom = cutoffTop + cutoffHeight
  1540.  
  1541.                 -- Handle X
  1542.                 local offset = math.max(0,cutoffLeft - thisX)
  1543.  
  1544.                 thisWidth = thisWidth - offset
  1545.                 thisSourceX = thisSourceX + offset
  1546.                 thisX = thisX + offset
  1547.  
  1548.                 thisWidth = math.min(thisWidth,cutoffRight - thisX)
  1549.  
  1550.                 -- Handle Y
  1551.                 local offset = math.max(0,cutoffTop - thisY)
  1552.  
  1553.                 thisHeight = thisHeight - offset
  1554.                 thisSourceY = thisSourceY + offset
  1555.                 thisY = thisY + offset
  1556.  
  1557.                 thisHeight = math.min(thisHeight,cutoffBottom - thisY)
  1558.             end
  1559.  
  1560.             -- Add vertices
  1561.             if thisWidth > 0 and thisHeight > 0 then
  1562.                 local x1 = thisX
  1563.                 local y1 = thisY
  1564.                 local x2 = x1 + thisWidth
  1565.                 local y2 = y1 + thisHeight
  1566.  
  1567.                 vertexCoords[vertexCount+1 ] = x1 -- top left
  1568.                 vertexCoords[vertexCount+2 ] = y1
  1569.                 vertexCoords[vertexCount+3 ] = x1 -- bottom left
  1570.                 vertexCoords[vertexCount+4 ] = y2
  1571.                 vertexCoords[vertexCount+5 ] = x2 -- top right
  1572.                 vertexCoords[vertexCount+6 ] = y1
  1573.                 vertexCoords[vertexCount+7 ] = x1 -- bottom left
  1574.                 vertexCoords[vertexCount+8 ] = y2
  1575.                 vertexCoords[vertexCount+9 ] = x2 -- top right
  1576.                 vertexCoords[vertexCount+10] = y1
  1577.                 vertexCoords[vertexCount+11] = x2 -- bottom right
  1578.                 vertexCoords[vertexCount+12] = y2
  1579.  
  1580.                 local x1 = thisSourceX / image.width
  1581.                 local y1 = thisSourceY / image.height
  1582.                 local x2 = (thisSourceX + thisWidth) / image.width
  1583.                 local y2 = (thisSourceY + thisHeight) / image.height
  1584.  
  1585.                 textureCoords[vertexCount+1 ] = x1 -- top left
  1586.                 textureCoords[vertexCount+2 ] = y1
  1587.                 textureCoords[vertexCount+3 ] = x1 -- bottom left
  1588.                 textureCoords[vertexCount+4 ] = y2
  1589.                 textureCoords[vertexCount+5 ] = x2 -- top right
  1590.                 textureCoords[vertexCount+6 ] = y1
  1591.                 textureCoords[vertexCount+7 ] = x1 -- bottom left
  1592.                 textureCoords[vertexCount+8 ] = y2
  1593.                 textureCoords[vertexCount+9 ] = x2 -- top right
  1594.                 textureCoords[vertexCount+10] = y1
  1595.                 textureCoords[vertexCount+11] = x2 -- bottom right
  1596.                 textureCoords[vertexCount+12] = y2
  1597.  
  1598.                 vertexCount = vertexCount + 12
  1599.             end
  1600.         end
  1601.     end
  1602.  
  1603.     --Text.print(#vertexCoords,32,96)
  1604.  
  1605.     Graphics.glDraw{
  1606.         texture = image,
  1607.         priority = priority,
  1608.         color = color,
  1609.         sceneCoords = sceneCoords,
  1610.         vertexCoords = vertexCoords,
  1611.         textureCoords = textureCoords,
  1612.     }
  1613. end
  1614.  
  1615.  
  1616. function boxInstanceFunctions:getDrawPosition()
  1617.     -- Handle forced pos
  1618.     local forcedPosX = self.forcedPosX
  1619.     local forcedPosY = self.forcedPosY
  1620.     local forcedPosHorizontalPivot = self.forcedPosHorizontalPivot or 0.5
  1621.     local forcedPosVerticalPivot = self.forcedPosVerticalPivot or 0.5
  1622.    
  1623.     if (forcedPosX == nil and forcedPosY == nil) and self.settings.forcedPosEnabled then
  1624.         forcedPosX = self.settings.forcedPosX
  1625.         forcedPosY = self.settings.forcedPosY
  1626.         forcedPosHorizontalPivot = self.settings.forcedPosHorizontalPivot
  1627.         forcedPosVerticalPivot = self.settings.forcedPosVerticalPivot
  1628.     end
  1629.  
  1630.     if forcedPosX ~= nil and forcedPosY ~= nil then
  1631.         return forcedPosX - (forcedPosHorizontalPivot - 0.5)*self.totalWidth,forcedPosY - (forcedPosVerticalPivot - 0.5)*self.totalHeight,false
  1632.     end
  1633.  
  1634.     -- Handle coming from the speaker
  1635.     local obj = self.speakerObj
  1636.  
  1637.     if obj ~= nil and obj.isValid ~= false then
  1638.         local x = obj.x + self.settings.offsetFromSpeakerX
  1639.         local y = obj.y + self.settings.offsetFromSpeakerY - self.totalHeight*0.5
  1640.  
  1641.         if obj.width ~= nil then
  1642.             x = x + obj.width*0.5
  1643.         end
  1644.  
  1645.         return x,y,true
  1646.     end
  1647.  
  1648.     -- No position so
  1649.     return 0,0,false
  1650. end
  1651.  
  1652.  
  1653. function boxInstanceFunctions:draw()
  1654.     local x,y,sceneCoords = self:getDrawPosition()
  1655.  
  1656.     -- Apply handycam zoom
  1657.     if self.priority > 0 and sceneCoords then
  1658.         local handycamObj = rawget(handycam,1)
  1659.  
  1660.         if handycamObj ~= nil then
  1661.             local cameraX = camera.x + camera.width*0.5
  1662.             local cameraY = camera.y + camera.height*0.5
  1663.  
  1664.             x = (x - cameraX)*handycamObj.zoom + cameraX
  1665.             y = (y - cameraY)*handycamObj.zoom + cameraY
  1666.         end
  1667.     end
  1668.  
  1669.     -- Apply bounds to make sure it's not off screen
  1670.     if self.keepOnScreen and sceneCoords then
  1671.         local minDistance = self.settings.minDistanceFromEdge
  1672.         local b = getBoundaries()
  1673.  
  1674.         x = math.clamp(x,b.left + self.totalWidth *0.5 + minDistance,b.right  - self.totalWidth *0.5 - minDistance)
  1675.         y = math.clamp(y,b.top  + self.totalHeight*0.5 + minDistance,b.bottom - self.totalHeight*0.5 - minDistance)
  1676.     end
  1677.  
  1678.  
  1679.     local scaleX  = math.lerp(self.settings.openStartScaleX ,1,self.openingProgress)
  1680.     local scaleY  = math.lerp(self.settings.openStartScaleY ,1,self.openingProgress)
  1681.     local opacity = math.lerp(self.settings.openStartOpacity,1,self.openingProgress)
  1682.  
  1683.     local topNameDarkening = 0
  1684.  
  1685.     local boxWidth = self.totalWidth * scaleX
  1686.     local boxHeight = self.totalHeight * scaleY
  1687.  
  1688.     local boxCutoffWidth
  1689.     local boxCutoffHeight
  1690.  
  1691.  
  1692.     if self.settings.windowingOpeningEffectEnabled and self.openingProgress < 1 then
  1693.         local blackProgress = math.min(1,self.openingProgress*2)
  1694.         local mainProgress = math.max(0,self.openingProgress*2 - 1)
  1695.  
  1696.         scaleX  = math.lerp(self.settings.openStartScaleX ,1,blackProgress)
  1697.         scaleY  = math.lerp(self.settings.openStartScaleY ,1,blackProgress)
  1698.         opacity = math.lerp(self.settings.openStartOpacity,1,blackProgress)
  1699.  
  1700.         boxCutoffWidth  = math.lerp(self.settings.openStartScaleX,1,mainProgress) * self.totalWidth
  1701.         boxCutoffHeight = math.lerp(self.settings.openStartScaleY,1,mainProgress) * self.totalHeight
  1702.  
  1703.         topNameDarkening = math.clamp((1 - mainProgress)*3.5)
  1704.  
  1705.         boxWidth = self.totalWidth
  1706.         boxHeight = self.totalHeight
  1707.  
  1708.         Graphics.drawBox{
  1709.             color = Color.black.. opacity,sceneCoords = sceneCoords,priority = self.priority,centred = true,
  1710.             x = x,y = y,width = self.totalWidth * scaleX,height = self.totalHeight * scaleY,
  1711.         }
  1712.     end
  1713.  
  1714.  
  1715.     if self.settings.boxImage ~= nil then
  1716.         drawSegmentedBox(self.settings.boxImage,self.priority,sceneCoords,Color.white.. opacity,x - boxWidth*0.5,y - boxHeight*0.5,boxWidth,boxHeight,boxCutoffWidth,boxCutoffHeight)
  1717.     end
  1718.  
  1719.  
  1720.     fullBuffer:clear(self.priority)
  1721.     --Graphics.drawBox{color = Color.red.. 0.25,priority = self.priority,target = fullBuffer,x = 0,y = 0,width = fullBuffer.width,height = fullBuffer.height}
  1722.  
  1723.     -- Speaker name
  1724.     if self.speakerNameLayout ~= nil then
  1725.         local nameX,nameY,nameTarget,nameSceneCoords,nameColor
  1726.  
  1727.         if self.settings.speakerNameOnTop then
  1728.             local boxImage = self.settings.speakerNameBoxImage
  1729.             local boxColor = Color.white:lerp(Color.black,topNameDarkening)
  1730.             local boxOpacity = (opacity*scaleX*scaleY)
  1731.  
  1732.             nameX = x + (-self.totalWidth*0.5 + self.settings.speakerNameOffsetX + (self.totalWidth - self.speakerNameLayout.width)*self.settings.speakerNamePivot)*scaleX
  1733.             nameY = y - (self.totalHeight*0.5 + self.speakerNameLayout.height)*scaleY
  1734.             nameSceneCoords = sceneCoords
  1735.  
  1736.             nameColor = boxColor*boxOpacity
  1737.  
  1738.             if boxImage ~= nil then
  1739.                 nameX = nameX - boxImage.width/3*2*self.settings.speakerNamePivot*scaleX
  1740.  
  1741.                 local boxWidth = (self.speakerNameLayout.width + boxImage.width/3*2)*scaleX
  1742.                 local boxHeight = self.speakerNameLayout.height + boxImage.height/3
  1743.                 local boxX = nameX
  1744.                 local boxY = nameY
  1745.  
  1746.                 nameX = nameX + boxWidth*0.5 - self.speakerNameLayout.width*0.5
  1747.  
  1748.                 drawSegmentedBox(boxImage,self.priority,sceneCoords,boxColor.. boxOpacity,boxX,boxY,boxWidth,boxHeight)
  1749.             end
  1750.  
  1751.             nameY = nameY + self.settings.speakerNameOffsetY
  1752.         else
  1753.             nameX = (self.mainWidth - self.speakerNameLayout.width)*self.settings.speakerNamePivot + self.settings.speakerNameOffsetX
  1754.             nameY = self.settings.speakerNameOffsetY
  1755.             nameTarget = fullBuffer
  1756.             nameColor = Color.white*opacity
  1757.         end
  1758.        
  1759.         textplus.render{
  1760.             layout = self.speakerNameLayout,priority = self.priority,color = nameColor,target = nameTarget,sceneCoords = nameSceneCoords,x = nameX,y = nameY,
  1761.             shader = self.settings.speakerNameTextShader,uniforms = getTextShaderUniforms(self.speakerNameLayout),
  1762.         }
  1763.     end
  1764.  
  1765.  
  1766.     if self.openingProgress >= 1 or self.settings.showTextWhileOpening then
  1767.         mainBuffer:clear(self.priority)
  1768.  
  1769.         -- Character portrait
  1770.         local portraitData = self.portraitData
  1771.         local textX = 0
  1772.  
  1773.         if portraitData ~= nil and portraitData.image ~= nil then
  1774.             Graphics.drawBox{
  1775.                 texture = portraitData.image,target = mainBuffer,priority = self.priority,
  1776.  
  1777.                 x = 0,y = math.floor((self.mainHeight - portraitData.height)*self.settings.portraitVerticalPivot + 0.5),
  1778.  
  1779.                 width = portraitData.width,height = portraitData.height,
  1780.                 sourceWidth = portraitData.width,sourceHeight = portraitData.height,
  1781.                 sourceX = self.portraitVariation * portraitData.width,sourceY = self.portraitFrame * portraitData.height,
  1782.             }
  1783.  
  1784.             textX = textX + portraitData.width + self.settings.portraitGap
  1785.         end
  1786.  
  1787.  
  1788.         -- Line markers
  1789.         local lineMarkerImage = self.settings.lineMarkerImage
  1790.         if lineMarkerImage ~= nil and self.settings.lineMarkerEnabled then
  1791.             textX = textX + lineMarkerImage.width
  1792.         end
  1793.  
  1794.  
  1795.         -- Text
  1796.         for index = math.floor(self.page), math.ceil(self.page) do
  1797.             local page = self.pageData[index]
  1798.  
  1799.             local y = math.floor((-(self.page - 1) + (index - 1))*self.mainHeight)
  1800.             local limit
  1801.  
  1802.             if self.settings.typewriterEnabled then
  1803.                 if index > self.page then
  1804.                     limit = 0
  1805.                     --break
  1806.                 elseif index == self.page and self.state ~= STATE.SCROLL then
  1807.                     limit = self.typewriterLimit
  1808.                 end
  1809.             end
  1810.  
  1811.             -- Draw line markers
  1812.             for _,lineMarker in ipairs(page.lineMarkers) do
  1813.                 if limit == nil or limit > lineMarker.limit or lineMarker.limit == 0 then
  1814.                     Graphics.drawBox{
  1815.                         texture = lineMarkerImage,target = mainBuffer,priority = self.priority,
  1816.                         x = textX - lineMarkerImage.width,y = y + lineMarker.y - lineMarkerImage.height,
  1817.                     }
  1818.                 end
  1819.             end
  1820.  
  1821.             textplus.render{
  1822.                 layout = page.layout,limit = limit,x = textX,y = y,priority = self.priority,target = mainBuffer,
  1823.                 shader = self.settings.mainTextShader,uniforms = getTextShaderUniforms(page.layout),
  1824.             }
  1825.  
  1826.             drawAnswers(self,page,textX,y)
  1827.         end
  1828.  
  1829.  
  1830.         -- Continue arrow
  1831.         if math.floor(self.page) == self.page and self.page < self.pageCount and self.pageData[self.page].totalAnswersCount == 0
  1832.         and not self.uncontrollable and self.typewriterFinished
  1833.         and self.settings.continueArrowEnabled and self.settings.continueArrowImage ~= nil
  1834.         then
  1835.             local offset = (math.floor(lunatime.drawtick()/32)%2)*2
  1836.  
  1837.             Graphics.drawBox{
  1838.                 texture = self.settings.continueArrowImage,target = mainBuffer,priority = self.priority,
  1839.                 x = self.mainWidth - self.settings.continueArrowImage.width,y = self.mainHeight - self.settings.continueArrowImage.height + offset,
  1840.             }
  1841.         end
  1842.  
  1843.  
  1844.         local drawWidth  = math.max(0,self.totalWidth *scaleX - self.settings.borderSize*2)
  1845.         local drawHeight = math.max(0,self.totalHeight*scaleY - self.settings.borderSize*2)
  1846.  
  1847.         if boxCutoffWidth ~= nil then
  1848.             drawWidth = math.min(drawWidth,boxCutoffWidth)
  1849.         end
  1850.         if boxCutoffHeight ~= nil then
  1851.             drawHeight = math.min(drawHeight,boxCutoffHeight)
  1852.         end
  1853.  
  1854.         Graphics.drawBox{
  1855.             texture = mainBuffer,target = fullBuffer,priority = self.priority,
  1856.             x = 0,y = self.mainOffsetY,
  1857.         }
  1858.  
  1859.         Graphics.drawBox{
  1860.             texture = fullBuffer,priority = self.priority,
  1861.             sceneCoords = sceneCoords,
  1862.             color = Color.white.. opacity,
  1863.  
  1864.             x = math.floor(x - drawWidth*0.5 + 0.5),y = math.floor(y - drawHeight*0.5 + 0.5),
  1865.             width = drawWidth,height = drawHeight,
  1866.             sourceWidth = drawWidth,sourceHeight = drawHeight,
  1867.             sourceX = self.mainWidth*0.5 - drawWidth*0.5,sourceY = (self.mainHeight + self.mainOffsetY)*0.5 - drawHeight*0.5,
  1868.         }
  1869.  
  1870.  
  1871.         --drawBufferDebug(fullBuffer,self.priority,0,0,self.mainWidth,self.mainHeight + self.mainOffsetY)
  1872.         --drawBufferDebug(answerBuffer,self.priority,mainBuffer.width,0,self.mainWidth,self.answersHeight)
  1873.     end
  1874. end
  1875.  
  1876.  
  1877.  
  1878. function littleDialogue.onTick()
  1879.     for k=#littleDialogue.boxes,1,-1 do
  1880.         local box = littleDialogue.boxes[k]
  1881.  
  1882.         if box.state ~= STATE.REMOVE then
  1883.             if not box.updatesInPause then
  1884.                 box:update()
  1885.             end
  1886.         else
  1887.             table.remove(littleDialogue.boxes,k)
  1888.             box.isValid = false
  1889.         end
  1890.     end
  1891. end
  1892.  
  1893. function littleDialogue.onDraw()
  1894.     for k=#littleDialogue.boxes,1,-1 do
  1895.         local box = littleDialogue.boxes[k]
  1896.  
  1897.         if box.state ~= STATE.REMOVE then
  1898.             if box.updatesInPause then
  1899.                 box:update()
  1900.             end
  1901.  
  1902.             box:draw()
  1903.         else
  1904.             table.remove(littleDialogue.boxes,k)
  1905.             box.isValid = false
  1906.         end
  1907.     end
  1908. end
  1909.  
  1910. function littleDialogue.onMessageBox(eventObj,text,playerObj,npcObj)
  1911.     littleDialogue.create{
  1912.         text = text,
  1913.         speakerObj = npcObj or playerObj or player,
  1914.     }
  1915.  
  1916.     eventObj.cancelled = true
  1917. end
  1918.  
  1919.  
  1920.  
  1921. littleDialogue.defaultBoxSettings = {
  1922.     -- Text formatting related
  1923.     textXScale = 2,          -- X scale of text.
  1924.     textYScale = 2,          -- Y scale of text.
  1925.     textMaxWidth = 384,      -- The maximum text width before it goes to a new line.
  1926.     textColor = Color.white, -- The tint of the text.
  1927.  
  1928.  
  1929.     -- Speaker name related
  1930.     speakerNameXScale = 2.5,        -- X scale of the speaker's name.
  1931.     speakerNameYScale = 2.5,        -- X scale of the speaker's name.
  1932.     speakerNameColor = Color.white, -- The tint of the speaker's name.
  1933.     speakerNameGap = 4,             -- The gap between the speaker's name and the text of the box.
  1934.     speakerNamePivot = 0.5,         -- The pivot for where the speaker's name is.
  1935.     speakerNameOffsetX = 0,
  1936.     speakerNameOffsetY = 0,
  1937.     speakerNameOnTop = false,       -- If true, the speaker's name will go on top of the box with its own 'protrusion'.
  1938.  
  1939.    
  1940.     -- Question related
  1941.     questionGap = 16, -- The gap between each a question and all of its answers.
  1942.     answerGap = 0,    -- The gap between each answer for a question.
  1943.  
  1944.     answerPageMaxHeight = 160, -- The maximum height of an answers list before it splits off into another page.
  1945.  
  1946.     answerUnselectedColor = Color.white,   -- The color of an answer when it's not being hovered over.
  1947.     answerSelectedColor = Color(1,1,0.25), -- The color of an answer when it is being hovered over.
  1948.  
  1949.  
  1950.     -- Typewriter effect related
  1951.     typewriterEnabled = false, -- If the typewriter effect is enabled.
  1952.     typewriterDelayNormal = 2, -- The usual delay between each character.
  1953.     typewriterDelayLong = 16,  -- The extended delay after any of the special delaying characters, listed below.
  1954.     typewriterSoundDelay = 5,  -- How long there must between each sound.
  1955.  
  1956.     -- Characters that, when hit by the typewriter effects, causes a slightly longer delay.
  1957.     typewriterDelayCharacters = table.map{string.byte("."),string.byte(","),string.byte("!"),string.byte("?")},
  1958.     -- Characters that stop the above character from causing a delay until the get printed as well.
  1959.     typewriterClosingCharacters = table.map{string.byte("\""),string.byte("'"),string.byte(")"),string.byte("}"),string.byte("]")},
  1960.  
  1961.  
  1962.     -- Other
  1963.     borderSize = 8, -- How much is added around the text to get the full size (pixels).
  1964.     priority = 6,   -- The render priority of boxes.
  1965.  
  1966.     minDistanceFromEdge = 16, -- The minimum distance away from the borders of the screen that the box can be while 'keepOnScreen' is enabled.
  1967.  
  1968.     useMaxWidthAsBoxWidth = false, -- If true, textMaxWidth gets used as the minimum width for the main part of the box.
  1969.     minBoxMainHeight = 0, -- The minimum height for the box's main section.
  1970.  
  1971.     offsetFromSpeakerX = 0,   -- How horizontally far away the box is from the NPC saying it.
  1972.     offsetFromSpeakerY = -40, -- How vertically far away the box is from the NPC saying it.
  1973.  
  1974.     pageOffset = 0, -- How far away each text page is from each other.
  1975.  
  1976.     openSpeed = 0.05, -- How much the scale increases per frame while opening/closing.
  1977.     pageScrollSpeed = 0.05, -- How fast it scrolls when switching pages.
  1978.     answerPageScrollSpeed = 0.05, -- How fast it scrolls when switching answer pages.
  1979.  
  1980.     portraitGap = 8, -- The space between a portrait's image and the text of the box.
  1981.     portraitVerticalPivot = 0, -- The pivot of the portrait. At 0, it goes at the top, 1 puts it at the bottom, and 0.5 puts it in the middle.
  1982.  
  1983.     windowingOpeningEffectEnabled = false, -- If true, the box will have a black "window" over before opening/closing.
  1984.     showTextWhileOpening = false,          -- If true, the text won't disappear during the box's opening/closing animations.
  1985.  
  1986.     openStartScaleX = 0,  -- The X scale that the box starts at when opening/ends at when closing.
  1987.     openStartScaleY = 0,  -- The Y scale that the box starts at when opening/ends at when closing.
  1988.     openStartOpacity = 1, -- The opacity that the box starts at when opening/ends at when closing.
  1989.  
  1990.     forcedPosEnabled = false,       -- If true, the box will be forced into a certain screen position, rather than floating over the speaker's head.
  1991.     forcedPosX = 400,               -- The X position the box will appear at on screen, if forced positioning is enabled.
  1992.     forcedPosY = 32,                -- The Y position the box will appear at on screen, if forced positioning is enabled.
  1993.     forcedPosHorizontalPivot = 0.5, -- How the box is positioned using its X coordinate. If 0, the X means the left, 1 means right, and 0.5 means the middle.
  1994.     forcedPosVerticalPivot = 0,     -- How the box is positioned using its Y coordinate. If 0, the Y means the top, 1 means bottom, and 0.5 means the middle.
  1995.  
  1996.  
  1997.     -- Sound effect related
  1998.     openSoundEnabled          = true, -- If a sound is played when the box opens.
  1999.     closeSoundEnabled         = true, -- If a sound is played when the box closes.
  2000.     scrollSoundEnabled        = true, -- If a sound is played when the box scrolls between pages.
  2001.     moveSelectionSoundEnabled = true, -- If a sound is played when the option selector moves.
  2002.     chooseAnswerSoundEnabled  = true, -- If a sound is played when an answer to a question is chosen.
  2003.     typewriterSoundEnabled    = true, -- If a sound is played when a letter appears with the typewriter effect.
  2004.  
  2005.     -- Image related
  2006.     continueArrowEnabled = true, -- Whether or not an image shows up in the bottom right to show that there's another page.
  2007.     selectorImageEnabled = true, -- Whether or not an image shows up next to where your chosen answer is.
  2008.     scrollArrowEnabled   = true, -- Whether or not arrows show up to indicate that there's more pages of options.
  2009.     lineMarkerEnabled    = true, -- Whether or not to have a maker on a new line.
  2010. }
  2011.  
  2012.  
  2013.  
  2014. -- Register each style.
  2015.  
  2016. -- SMW styled: uses all the default settings, so nothing is needed.
  2017. littleDialogue.registerStyle("smw",{
  2018.    
  2019. })
  2020.  
  2021. -- Yoshi's Island styled: has a few custom settings.
  2022. littleDialogue.registerStyle("yi",{
  2023.     borderSize = 32,
  2024.  
  2025.     openSpeed = 0.025,
  2026.  
  2027.     windowingOpeningEffectEnabled = true,
  2028.     showTextWhileOpening = true,
  2029.  
  2030.     speakerNameOnTop = true,
  2031.     speakerNameOffsetY = 24,
  2032. })
  2033.  
  2034. -- Mario & Luigi Superstar Saga styled: a bunch of custom settings!
  2035. -- Not meant to be particularly accurate to the game.
  2036. littleDialogue.registerStyle("ml",{
  2037.     textColor = Color.fromHexRGB(0x303030),
  2038.     speakerNameColor = Color.fromHexRGB(0x303030),
  2039.     typewriterEnabled = true,
  2040.     borderSize = 12,
  2041.     showTextWhileOpening = true,
  2042.  
  2043.     openStartScaleX = 0,
  2044.     openStartScaleY = 0,
  2045.     openStartOpacity = 0.5,
  2046.  
  2047.     speakerNameOnTop = true,
  2048.     speakerNameOffsetX = 24,
  2049.     speakerNameOffsetY = 4,
  2050.     speakerNamePivot = 0,
  2051.     speakerNameXScale = 2,
  2052.     speakerNameYScale = 2,
  2053.  
  2054.     openSpeed = 0.06,
  2055.     pageScrollSpeed = 0.075,
  2056. })
  2057.  
  2058. -- Deltarune styled: very different from the rest! Also uses a shader for text to add an extra effect.
  2059. littleDialogue.registerStyle("dr",{
  2060.     textXScale = 1,
  2061.     textYScale = 1,
  2062.     textMaxWidth = 600,
  2063.  
  2064.     speakerNameXScale = 1,
  2065.     speakerNameYScale = 1,
  2066.     speakerNameOffsetX = 0,
  2067.     speakerNameColor = Color.yellow,
  2068.  
  2069.     borderSize = 32,
  2070.  
  2071.     openSpeed = 1,
  2072.     pageScrollSpeed = 1,
  2073.  
  2074.     portraitGap = 32,
  2075.     portraitVerticalPivot = 0.5,
  2076.  
  2077.     useMaxWidthAsBoxWidth = true,
  2078.     minBoxMainHeight = 104,
  2079.  
  2080.     forcedPosEnabled = true,
  2081.     forcedPosX = 400,
  2082.     forcedPosY = 32,
  2083.     forcedPosHorizontalPivot = 0.5,
  2084.     forcedPosVerticalPivot = 0,
  2085.  
  2086.     answerSelectedColor = Color.yellow,
  2087.  
  2088.     typewriterEnabled = true,
  2089.     typewriterSoundDelay = 2,
  2090.  
  2091.     openSoundEnabled = false,
  2092.     closeSoundEnabled = false,
  2093.     scrollSoundEnabled = false,
  2094.     moveSelectionSoundEnabled = false,
  2095.     chooseAnswerSoundEnabled = false,
  2096.  
  2097.     continueArrowEnabled = false,
  2098. })
  2099.  
  2100.  
  2101. -- Small deltarune styled: very different from the rest!
  2102. littleDialogue.registerStyle("drSmall",{
  2103.     textXScale = 1,
  2104.     textYScale = 1,
  2105.     textMaxWidth = 174,
  2106.     textColor = Color.black,
  2107.  
  2108.     speakerNameXScale = 1,
  2109.     speakerNameYScale = 1,
  2110.     speakerNameOffsetX = 0,
  2111.     speakerNameColor = Color.yellow,
  2112.  
  2113.     borderSize = 10,
  2114.  
  2115.     openSpeed = 1,
  2116.     pageScrollSpeed = 1,
  2117.  
  2118.     portraitGap = 32,
  2119.     portraitVerticalPivot = 0.5,
  2120.  
  2121.     answerSelectedColor = Color.yellow,
  2122.  
  2123.     typewriterEnabled = true,
  2124.     typewriterSoundDelay = 2,
  2125.  
  2126.     openSoundEnabled = false,
  2127.     closeSoundEnabled = false,
  2128.     scrollSoundEnabled = false,
  2129.     moveSelectionSoundEnabled = false,
  2130.     chooseAnswerSoundEnabled = false,
  2131.  
  2132.     continueArrowEnabled = false,
  2133. })
  2134.  
  2135.  
  2136. -- Note that you can setup your own styles from luna.lua files.
  2137.  
  2138.  
  2139. -- Default box style.
  2140. littleDialogue.defaultStyleName = "smw"
  2141.  
  2142.  
  2143. return littleDialogue
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement