-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Info -- function widget:GetInfo() return { name = "Display Options", desc = "In-game options menu", author = "quantum", date = "Aug 8, 2007", license = "GNU GPL, v2 or later", layer = 0, enabled = false -- loaded by default? } end -- See LuaUI/Widgets/Rooms/documentation.txt for documentation. -- FIXME /viewtaflip -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- External Access -- local rooms = widget WG.rooms = rooms rooms.Window = {} rooms.Tab = {} -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Speedups -- local fontHandler = fontHandler local gl, GL = gl, GL local GetGameSeconds = Spring.GetGameSeconds local Window = rooms.Window local Tab = rooms.Tab local Delist = rooms.Delist -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Constants -- ROOM_DIRNAME = "LuaUI/Widgets/Rooms" FONT_DIRNAME = LUAUI_DIRNAME.."/Fonts/" VFSMODE = VFS.RAW_FIRST -- Color codes: embed in a string to select text color. white = "\255\255\255\255" black = "\255\001\001\001" grey = "\255\128\128\128" red = "\255\255\001\001" pink = "\255\255\064\064" green = "\255\001\255\001" blue = "\255\001\001\255" cyan = "\255\001\255\255" yellow = "\255\255\255\001" magenta = "\255\255\001\255" -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Locals -- local realSeconds = 0 local viewSizeX, viewSizeY = widgetHandler:GetViewSizes() local objectsToDrawSet = {} -- FIXME: replace completely with zStack -- Tables of objects that are to be notified when the specified -- event takes place. local onMousePressQueue = {} local onMouseReleaseQueue = {} local onIsAboveQueue = {} local onMouseMoveSet = {} local onUnitFinishedSet = {} local onUnitCreatedSet = {} local getConfigDataSet = {} local onUpdateSet = {} -- Array of display lists to destroy. local cleanUp = {} local zStack = {} -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Globals -- groups = {} -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Utility -- function WordWrap(str, maxLineWidth) -- Width in pixels. -- TODO: Make it respect newlines. local insert = table.insert local concat = table.concat local wordArray = {} for word in string.gmatch(str, "[^%s]+") do insert(wordArray, word) end local firstWordInPiece = 1 local lastWordInPiece = 1 local lineArray = {} repeat local linePiece = concat(wordArray, " ", firstWordInPiece, lastWordInPiece) local strippedLinePiece = fontHandler.StripColors(linePiece) local pieceWidth = fontHandler.GetTextWidth(strippedLinePiece) if (pieceWidth >= maxLineWidth) then local line = concat(wordArray, " ", firstWordInPiece, lastWordInPiece-1) insert(lineArray, line) firstWordInPiece = lastWordInPiece end lastWordInPiece = lastWordInPiece + 1 until (lastWordInPiece == #wordArray) -- Insert the last words in the lineArray local line if (#wordArray > 1) then line = concat(wordArray, " ", firstWordInPiece, lastWordInPiece) local strippedLine = fontHandler.StripColors(line) local lineWidth = fontHandler.GetTextWidth(strippedLine) if (lineWidth < maxLineWidth and line ~= "") then insert(lineArray, line) else -- There can be only one extra word, see the repeat/until loop. line = (concat(wordArray, " ", firstWordInPiece, lastWordInPiece-1)) insert(lineArray, line) insert(lineArray, wordArray[#wordArray]) end else lineArray = wordArray end return lineArray end function Copy(original) -- Warning: circular table references lead to local copy = {} -- an infinite loop. for k, v in pairs(original) do if (type(v) == "table") then copy[k] = Copy(v) else copy[k] = v end end return copy end function ToSI(n) -- Adapted from IceUI. Thanks, Meltrax! if (not n) then return "nil" end local resultString local exponent local suffix local siSuffixes = {"y", "z", "a", "f", "p", "n", "ยต", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" } if (math.abs(n) < 10^27 and math.abs(n) > 10^-27) then n = math.abs(n) exponent = n == 0 and 0 or math.log(n)/math.log(10) suffix = n == 0 and 0 or math.floor(exponent/3) n = (n + (0.5*10^(math.floor(exponent)-2))) / 10^(suffix*3) n = string.sub(n, 1, tonumber(n) > 100 and 3 or 4) resultString = n..siSuffixes[suffix+9] else resultString = "0" end return resultString end function MakeCounter() local count = 0 return function() count = count + 1 return count end end function MakeTimer() local startTime = realSeconds return function() return realSeconds - startTime end end function Delist(array, object) -- Finds and removes a value from an array. local objectPos for index, key in ipairs(array) do if (key == object) then objectPos = index break end end table.remove(array, objectPos) end function MakeLineArray(text) -- Separates text in lines. local array = {} for word in string.gmatch(text, "[^\n]+") do table.insert(array, word) end return array end function RunOnce(func, ...) local first = true return function (...) if (first) then first = nil func(...) end end end function Empty() return Empty end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Obsolete Utility -- --[[--------------- Might be useful again if gl.Font is used. ------------------ local function WordWrapMono(str, columns) -- Accurate with monospace fonts only. local li = 1 local len = #str while (len-li > columns) do local j local k local i = li + columns while (i > li and string.sub(str, i, i) ~= " ") do i = i-1 end j = i while (j > li and string.sub(str, j, j) == " ") do j = j-1 end str = string.sub(str, 1, j).."\n"..string.sub(str, i+1, -1) k = 1+j-i li = j+k len = len+k end return str end local function MakeLineArray(str, width) local Lines = string.gmatch(WordWrap(str, width), "[^\n]+") local lineArray = {} for line in Lines do table.insert(lineArray, line) end return lineArray end local function GetWidth(lineArray) local width = 0 for _, line in pairs(lineArray) do local s = fontHandler.StripColorCodes(s) local l = #s if (l > width) then width = l end end return width -- In pixels. end local function GetParagraphSize(lineArray, lineSpacing) -- Line spacing in local lineSpacing = lineSpacing or defaultLineSpacing -- in pixels. local w = GetWidth(lineArray) local width = fontHandler.GetTextWidth(string.rep("a", w)) local fontHeight = fontHandler.GetFontSize() local height = #lineArray * (fontHeight + lineSpacing) return width, height -- In pixels. end ]]------------------------------------------------------------------------------ -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Tab Prototype -- function Tab:Create(newTab, parent) newTab = newTab or {} setmetatable(newTab, self) self.__index = self -- Inherit values from the "Tab" prototype. newTab.parent = parent newTab.font = newTab.font or "FreeSansBold_16" fontHandler.UseFont(FONT_DIRNAME..newTab.font) newTab:ProcessArguments() newTab.x1, newTab.y1, newTab.x2, newTab.y2 = newTab:GetCoordinates() fontHandler.UseFont(FONT_DIRNAME..parent.font) return newTab end function Tab:ProcessArguments() local parent = self.parent self.self = self self.title = self.title or white.."This is a tab" self.offset = self.offset or parent.tabOffset self.leftMargin = self.leftMargin or parent.leftMargin self.rightMargin = self.rightMargin or parent.rightMargin self.topMargin = self.topMargin or parent.topMargin self.bottomMargin = self.bottomMargin or parent.bottomMargin self.noBorder = self.noBorder or parent.border -- Border offsets determine how much the border lines protrude. self.borderOffset1 = self.borderOffset1 or parent.borderOffset1 self.borderOffset2 = self.borderOffset2 or parent.borderOffset2 self.position = self.position or "top" self.borderColor = self.borderColor or self.borderColor -- Background colors when the mouse cursor is within the tab. self.isAboveColors = self.isAboveColors or Copy(parent.isAboveColors) self.topLeftColor = self.topLeftColor or Copy(parent.topLeftColor) self.topRightColor = self.topRightColor or Copy(parent.topRightColor) self.bottomLeftColor = self.bottomLeftColor or Copy(parent.bottomLeftColor) self.bottomRightColor = self.bottomRightColor or Copy(parent.bottomRightColor) self.gradient = self.bottomLeftColor or parent.gradient self.backgroundColor = self.backgroundColor or parent.backgroundColor self.borderColor = self.borderColor or parent.borderColor self.useOnIsAbove = true -- FIXME if (not self.gradient) then self.topRightColor = Copy(self.backgroundColor) self.topLeftColor = Copy(self.backgroundColor) self.bottomLeftColor = Copy(self.backgroundColor) self.bottomRightColor = Copy(self.backgroundColor) end self.OnMousePressAction = self.OnMousePressAction or function() return true end if (self.Click) then self.OnMouseReleaseAction = self.Click end if (self.preset) then self.preset(self) end end function Tab:GetCoordinates() local parent = self.parent local x1, y1, y2, x2 local textWidth = fontHandler.GetTextWidth(self.title) local fontSize = parent.fontSize local width = self.leftMargin + textWidth + self.rightMargin local height = self.topMargin + fontSize + self.bottomMargin local offset = self.offset if (self.position == "top") then local oldTabOffset = parent.tabOffsets.top -- Place the new tab next to x1 = parent.x1 + offset + oldTabOffset -- the previous one. y1 = parent.y2 x2 = x1 + width y2 = parent.y2 + height local newTabOffset = x2 - parent.x1 if (oldTabOffset < newTabOffset ) then parent.tabOffsets.top = newTabOffset end elseif (self.position == "right") then local oldTabOffset = parent.tabOffsets.right x1 = parent.x2 y1 = parent.y2 - offset - height - oldTabOffset x2 = parent.x2 + width y2 = parent.y2 - offset - oldTabOffset local newTabOffset = parent.y2 - y1 if (oldTabOffset < newTabOffset ) then parent.tabOffsets.right = newTabOffset end elseif (self.position == "left") then local oldTabOffset = parent.tabOffsets.left x1 = parent.x1 - width + 1 y1 = parent.y2 - offset - height - oldTabOffset x2 = parent.x1 + 1 y2 = parent.y2 - offset - oldTabOffset local newTabOffset = parent.y2 - y1 if (oldTabOffset < newTabOffset ) then parent.tabOffsets.left = newTabOffset end elseif (self.position == "bottom") then local oldTabOffset = parent.tabOffsets.bottom x1 = parent.x1 + offset + oldTabOffset y1 = parent.y1 - height + 1 x2 = x1 + width y2 = parent.y1 local newTabOffset = x2 - parent.x1 if (oldTabOffset < newTabOffset ) then parent.tabOffsets.bottom = newTabOffset end end return x1, y1, x2, y2 end function Tab:UpdateCoordinates() self.x1, self.y1, self.x2, self.y2 = self:GetCoordinates() end function Tab:Draw() local x1 = self.x1 local y1 = self.y1 local x2 = self.x2 local y2 = self.y2 local a = self.borderOffset1 local b = self.borderOffset2 if (self.backgroundColor[4] ~= 0) then gl.BeginEnd(GL.QUADS, function() gl.Color(unpack(self.bottomLeftColor)) gl.Vertex(x1, y1) gl.Color(unpack(self.topLeftColor)) gl.Vertex(x1, y2) gl.Color(unpack(self.topRightColor)) gl.Vertex(x2, y2) gl.Color(unpack(self.bottomRightColor)) gl.Vertex(x2, y1) end) end if (not self.noBorder) then self:DrawBorder() end end function Tab:DrawTitle() local x = self.x1 + (self.x2-self.x1)*0.5 local y = self.y1 + self.bottomMargin fontHandler.UseFont(FONT_DIRNAME..self.font) fontHandler.DrawCentered(self.title, x, y) fontHandler.UseFont(FONT_DIRNAME..self.parent.font) -- FIXME TEMP end function Tab:DrawBorder() local x1 = self.x1 local y1 = self.y1 local x2 = self.x2 local y2 = self.y2 local a = self.borderOffset1 local b = self.borderOffset2 gl.BeginEnd(GL.LINES, function() gl.Color(unpack(self.borderColor)) if (self.position == "top") then gl.Vertex(x1-b, y1+b) gl.Vertex(x1-b, y2+a) gl.Vertex(x1-a, y2+b) gl.Vertex(x2+a, y2+b) gl.Vertex(x2+b, y2+a) gl.Vertex(x2+b, y1+b) elseif (self.position == "right") then gl.Vertex(x1+b, y2+b) gl.Vertex(x2+a, y2+b) gl.Vertex(x2+b, y2+a) gl.Vertex(x2+b, y1-a) gl.Vertex(x2+a, y1-b) gl.Vertex(x1+b, y1-b) elseif (self.position == "left") then gl.Vertex(x1-b, y1-a) gl.Vertex(x1-b, y2+a) gl.Vertex(x1-a, y2+b) gl.Vertex(x2-3*b, y2+b) gl.Vertex(x2-3*b, y1-b) gl.Vertex(x1-a, y1-b) elseif (self.position == "bottom") then gl.Vertex(x1-b, y1-a) gl.Vertex(x1-b, y2-b) gl.Vertex(x2+b, y2-b) gl.Vertex(x2+b, y1-a) gl.Vertex(x2+a, y1-b) gl.Vertex(x1-a, y1-b) end end) end function Tab:OnMousePress(x, y, button) local capture if (not self.parent.closed) then if (x > self.x1 + self.parent.dx and x < self.x2 + self.parent.dx and y > self.y1 + self.parent.dy and y < self.y2 + self.parent.dy) then self.pressCaught = true self.parent:BringToFront() if (self.OnMousePressAction) then capture = self.OnMousePressAction(button) end end end return capture end function Tab:UpdateParentDisplayList() self.parent:UpdateDisplayList() end function Tab:OnIsAbove(x, y) local iac = self.isAboveColors if (x > self.x1 + self.parent.dx and x < self.x2 + self.parent.dx and y > self.y1 + self.parent.dy and y < self.y2 + self.parent.dy) then if (not self.isAbove) then self.isAbove = true iac.bottomLeft, self.bottomLeftColor = self.bottomLeftColor, iac.bottomLeft iac.topLeft, self.topLeftColor = self.topLeftColor, iac.topLeft iac.topRight, self.topRightColor = self.topRightColor, iac.topRight iac.bottomRight, self.bottomRightColor = self.bottomRightColor, iac.bottomRight self:UpdateParentDisplayList() end elseif (self.isAbove) then self.isAbove = nil self.bottomLeftColor, iac.bottomLeft = iac.bottomLeft, self.bottomLeftColor self.topLeftColor, iac.topLeft = iac.topLeft, self.topLeftColor self.topRightColor, iac.topRight = iac.topRight, self.topRightColor self.bottomRightColor, iac.bottomRight = iac.bottomRight, self.bottomRightColor self:UpdateParentDisplayList() end end function Tab:OnMouseRelease(x, y, button) local capture if (not self.parent.closed) then if (x > self.x1 + self.parent.dx and x < self.x2 + self.parent.dx and y > self.y1 + self.parent.dy and y < self.y2 + self.parent.dy) then if (self.OnMouseReleaseAction and self.pressCaught) then capture = self.OnMouseReleaseAction(button) end end end self.pressCaught = nil return capture end -- Preset Functions ------------------------------------------------------------ function Tab:Close() return function(tab) tab.title = "Close" tab.position = "right" tab.OnMouseReleaseAction = function() tab.parent:Close() -- mainButton:Open() end end end function Tab:Goto(args) return function(tab) tab.title = args.title tab.position = "left" tab.OnMouseReleaseAction = function() tab.parent:Close() local f = loadstring(args.destination..":Open()") -- FIXME: there must be a setfenv(f, getfenv(1)) -- better way. f() end end end function Tab:Back(args) return function(tab) tab.title = "Back" tab.position = "right" tab.OnMouseReleaseAction = function() tab.parent:Close() local f = loadstring(args.previousWindow..":Open()") setfenv(f, getfenv(1)) f() end end end function Tab:SetConfigInt(args) return function(tab) tab.title = args.title tab.position = "left" tab.OnMouseReleaseAction = function() Spring.SetConfigInt(args.config, args.int) tab.parent.lineArray[1] = args.text or "Current setting is: "..Spring.GetConfigInt(args.config).."." end end end function Tab:ConfigEnable(args) return function(tab) tab.title = "Enable" tab.position = "left" tab.OnMousePressAction = function() Spring.SetConfigInt(args.config, 1) tab.parent.lineArray[1] = args.text or "Current setting is: enabled." end end end function Tab:ConfigDisable(args) return function(tab) tab.title = "Disable" tab.position = "left" tab.OnMousePressAction = function() Spring.SetConfigInt(args.config, 0) tab.parent.lineArray[1] = args.text or "Current setting is: disabled." end end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Window Prototype -- function Window:Create(newWindow) newWindow = newWindow or {} setmetatable(newWindow, self) self.__index = self -- Inherit fields from the "Window" prototype. newWindow:ProcessArguments() -- If the single line string is smaller than the specified text width, -- reduce the window width to match the string width. if (not newWindow.noAutoShrink and newWindow.text) then newWindow.textWidth = newWindow:GetRealTextWidth() end if (newWindow.text) then newWindow.lineArray = WordWrap(newWindow.text, newWindow.textWidth) else -- If a line array is fed directly to the window object, make the window -- as wide as the widest line. newWindow.textWidth = newWindow:GetMaxLineWidth() end local x2, y2 = newWindow:MakeSize() newWindow.x2 = newWindow.x2 or x2 newWindow.y2 = newWindow.y2 or y2 newWindow.dx = 0 newWindow.dy = 0 newWindow:MakeTabs() newWindow:ExpandToMakeTabsFit() if (not newWindow.closed) then newWindow:Open() end return newWindow end function Window:ExpandToMakeTabsFit() local width = self.x2 - self.x1 local redraw if (self.tabOffsets.top > width) then self.x2 = math.floor(self.x1 + self.tabOffsets.top + self.tabOffset) redraw = true end local width = self.x2 - self.x1 if (self.tabOffsets.bottom > width) then self.x2 = math.floor(self.x1 + self.tabOffsets.bottom + self.tabOffset) redraw = true end self.textWidth = self.x2 - self.x1 - self.leftMargin - self.rightMargin if (redraw) then -- FIXME: Wasteful, lazy hack. self:MakeTabs() if (self.text) then self.lineArray = WordWrap(self.text, self.textWidth) end local _ _, self.y2 = self:MakeSize() self.y2 = math.floor(self.y2) redraw = false end local height = self.y2 - self.y1 if (self.tabOffsets.right > height) then self.y2 = math.floor(self.y1 + self.tabOffsets.right + self.tabOffset) end height = self.y2 - self.y1 if (self.tabOffsets.left > height) then self.y2 = math.floor(self.y1 + self.tabOffsets.left + self.tabOffset) end self:MakeTabs() end function Window:MakeTabs() -- Tab offsets are used to stop tabs from overlapping. self.tabOffsets = {top = 0, bottom = 0, left = 0, right = 0} if (self.tabs) then self.tabData = {} for tab, tabArgs in ipairs(self.tabs) do self.tabData[tab] = Tab:Create(tabArgs, self) end end return self.tabOffsets end function Window:MakeSize() local textHeight = #self.lineArray * (self.fontSize + self.lineSpacing) local x2 = self.x1 + self.textWidth + self.leftMargin + self.rightMargin local y2 = self.y1 + textHeight + self.topMargin + self.bottomMargin if (self.x2) then x2 = self.x2 end if (self.y2) then y2 = self.y2 end return x2, y2 end function Window:GetRealTextWidth() local width local oldWidth = self.textWidth local textNoColorCodes = fontHandler.StripColors(self.text) local newWidth = fontHandler.GetTextWidth(textNoColorCodes) if (newWidth < self.textWidth) then width = newWidth else width = oldWidth end return width end function Window:GetMaxLineWidth() -- FIXME: math.max local lineWidth local maxWidth maxWidth = 0 for _, line in ipairs(self.lineArray) do lineWidth = fontHandler.GetTextWidth(line) if (lineWidth > maxWidth) then maxWidth = lineWidth end end return maxWidth end function Window:ProcessArguments() self.self = self self.font = self.font or "FreeSansBold_16" fontHandler.UseFont(FONT_DIRNAME..self.font) -- Text as an array of strings. Use "self.text" for automatic word wrapping. self.lineArray = self.lineArray or {} self.x = self.x or 400 self.y = self.y or 200 self.x1 = self.x1 or self.x self.y1 = self.y1 or self.y self.textWidth = self.textWidth or 300 self.lineSpacing = self.lineSpacing or 3 self.topMargin = self.topMargin or self.lineSpacing self.bottomMargin = self.bottomMargin or self.topMargin*2 self.leftMargin = self.leftMargin or self.topMargin*2 self.rightMargin = self.rightMargin or self.topMargin -- Border offsets determine how much the border lines protrude. self.borderOffset1 = self.borderOffset1 or 0.5 self.borderOffset2 = self.borderOffset2 or 0.5 self.tabOffset = self.tabOffset or 10 local isAboveColorsDefault = { bottomLeft = { 0, 0, 0, 1}, topLeft = {0.3, 0.3, 0.3, 1}, topRight = {0.3, 0.3, 0.3, 1}, bottomRight = {0.5, 0.5, 0.5, 1}, } self.borderColor = self.borderColor or { 1, 1, 1, 1} -- Background color when the mouse cursor is within the window. self.isAboveColors = self.isAboveColors or isAboveColorsDefault self.bottomLeftColor = self.bottomLeftColor or { 0, 0, 0, 0.5} self.topLeftColor = self.topLeftColor or { 0, 0, 0, 0.8} self.topRightColor = self.topRightColor or { 0, 0, 0, 0.5} self.bottomRightColor = self.bottomRightColor or {0.4, 0.4, 0.4, 0.5} self.gradient = self.IsAboveColors or true -- FIXME? self.backgroundColor = self.backgroundColor or { 0, 0, 0, 0.5} if (not self.gradient) then self.topRightColor = self.backgroundColor self.topLeftColor = self.backgroundColor self.bottomLeftColor = self.backgroundColor self.bottomRightColor = self.backgroundColor end self.fontSize = fontHandler:GetFontSize() if (self.unitPicByName) then self.unitPicByID = UnitDefNames[self.unitPicByName].id end if (self.unitPicByID) then self.backGroundTextureString = "#"..self.unitPicByID end if (self.x1 < 1) then self.x1 = math.floor(self.x1*viewSizeX) end if (self.y1 < 1) then self.y1 = math.floor(self.y1*viewSizeY) end if (self.textWidth < 1) then self.textWidth = math.floor(self.textWidth*viewSizeX) end if (self.x2 and self.x2 < 1) then self.x2 = math.floor(self.x2*viewSizeX) end if (self.y2 and self.y2 < 1) then self.y2 = math.floor(self.y2*viewSizeY) end if (self.group) then if (groups[self.group]) then table.insert(groups[self.group], self) else groups[self.group] = {self} end end -- self.Counter = MakeCounter() self.Timer = MakeTimer() if (self.OnUnitFinished) then onUnitFinishedSet[self] = true end if (self.OnUnitCreated) then onUnitCreatedSet[self] = true end if (self.GetConfigData) then getConfigDataSet[self] = true end if (self.OnUpdate) then onUpdateSet[self] = true end if (self.Click) then self.OnMouseReleaseAction = self.Click end end function Window:UpdateDisplayList() if (self.displayList) then gl.DeleteList(self.displayList) end self.displayList = gl.CreateList( function() self:DrawBackground() if (not self.noBorder) then self:DrawBorder() end if (self.tabs) then for _, tab in pairs(self.tabData) do tab:Draw() end end end) end function Window:Animate() local x1 = self.x1 local x2 = self.x2 local y1 = self.y1 local y2 = self.y2 local t = self.Timer() if (t < 3) then if (self.closed) then -- TODO: give more transition choices local scaleFactor = 1/(1+(0.66/t)^-15) gl.Translate(0, 0, -1) gl.Translate(x1+(x2-x1)/2, y1+(y2-y1)/2, 0) gl.Scale(scaleFactor, scaleFactor^12, 1) gl.Translate(-x1-(x2-x1)/2, -y1-(y2-y1)/2, 0) else local scaleFactor = 1/(1+(1.5*t+0.7)^-15) gl.Translate(x1+(x2-x1)/2, y1+(y2-y1)/2, 0) gl.Scale(scaleFactor, scaleFactor^12, 1) gl.Translate(-x1-(x2-x1)/2, -y1-(y2-y1)/2, 0) end elseif (self.closed) then objectsToDrawSet[self] = nil Delist(zStack, self) return true end end function Window:Draw() gl.PushMatrix() if (not self.noAnimation) then if self:Animate() then return end end local x = self.x1 + self.leftMargin local a = self.fontSize + self.lineSpacing local b = self.y2 - self.topMargin + self.lineSpacing gl.Translate(self.dx, self.dy, 0) gl.CallList(self.displayList) for line, text in ipairs(self.lineArray) do local y = b - line * (a) fontHandler.UseFont(FONT_DIRNAME..self.font) fontHandler.Draw(text, x, y) end if (self.tabs) then for _, tab in pairs(self.tabData) do tab:DrawTitle() end end gl.PopMatrix() end function Window:DeleteList() gl.DeleteList(self.displayList) end function Window:DrawBorder() local x1 = self.x1 local y1 = self.y1 local x2 = self.x2 local y2 = self.y2 local a = self.borderOffset1 local b = self.borderOffset2 gl.BeginEnd(GL.LINES, function() gl.Color(unpack(self.borderColor)) gl.Vertex(x1-b, y1-a) gl.Vertex(x1-b, y2+a) gl.Vertex(x1-a, y2+b) gl.Vertex(x2+a, y2+b) gl.Vertex(x2+b, y2+a) gl.Vertex(x2+b, y1-a) gl.Vertex(x2+a, y1-b) gl.Vertex(x1-a, y1-b) end) end function Window:DrawBackground() local x1 = self.x1 local y1 = self.y1 local x2 = self.x2 local y2 = self.y2 if (self.backgroundColor[4] ~= 0) then -- If background alpha is not 0... gl.BeginEnd(GL.QUADS, function() gl.Color(unpack(self.bottomLeftColor)) gl.Vertex(x1, y1) gl.Color(unpack(self.topLeftColor)) gl.Vertex(x1, y2) gl.Color(unpack(self.topRightColor)) gl.Vertex(x2, y2) gl.Color(unpack(self.bottomRightColor)) gl.Vertex(x2, y1) end) end -- Draw a unitpic as background. if (self.backGroundTextureString) then gl.Color(1, 1, 1, 1) gl.Texture(self.backGroundTextureString) gl.TexRect(x1, y1, x2, y2) gl.Texture(false) end end function Window:OnMousePress(x, y, button) local capture if (not self.closed) then if (x > self.x1 + self.dx and x < self.x2 + self.dx and y > self.y1 + self.dy and y < self.y2 + self.dy) then self:BringToFront() if (self.OnMousePressAction) then self.OnMousePressAction(button) end if (not self.noMove) then onMouseMoveSet[self] = true end self.pressCaught = true capture = true end end return capture end function Window:OnIsAbove(x, y) local siac = self.isAboveColors if (x > self.x1 + self.dx and x < self.x2 + self.dx and y > self.y1 + self.dy and y < self.y2 + self.dy) then if (not self.isAbove) then self.isAbove = true siac.bottomLeft, self.bottomLeftColor = self.bottomLeftColor, siac.bottomLeft siac.topLeft, self.topLeftColor = self.topLeftColor, siac.topLeft siac.topRight, self.topRightColor = self.topRightColor, siac.topRight siac.bottomRight, self.bottomRightColor = self.bottomRightColor, siac.bottomRight self:UpdateDisplayList() end elseif (self.isAbove) then self.isAbove = nil self.bottomLeftColor, siac.bottomLeft = siac.bottomLeft, self.bottomLeftColor self.topLeftColor, siac.topLeft = siac.topLeft, self.topLeftColor self.topRightColor, siac.topRight = siac.topRight, self.topRightColor self.bottomRightColor, siac.bottomRight = siac.bottomRight, self.bottomRightColor self:UpdateDisplayList() end end function Window:OnMouseRelease(x, y, button) local capture if (not self.closed) then if (x > self.x1 + self.dx and x < self.x2 + self.dx and y > self.y1 + self.dy and y < self.y2 + self.dy) then if (self.OnMouseReleaseAction) then self.OnMouseReleaseAction(button) end capture = true end onMouseMoveSet[self] = nil end self.pressCaught = nil return capture end function Window:OnMouseMove(x, y, dx, dy, button) if (self.group) then for _, object in pairs(groups[self.group]) do object.dx = object.dx + dx object.dy = object.dy + dy end else self.dx = self.dx + dx self.dy = self.dy + dy end end function Window:AvailableForNotifications() table.insert(onMousePressQueue, 1, self) if (self.useOnIsAbove) then table.insert(onIsAboveQueue, 1, self) end table.insert(onMouseReleaseQueue, 1, self) if (self.tabs) then for _, tab in pairs(self.tabData) do table.insert(onMousePressQueue, 1, tab) table.insert(onIsAboveQueue, 1, tab) table.insert(onMouseReleaseQueue, 1, tab) end end end function Window:UnavailableForNotifications() Delist(onMousePressQueue, self) Delist(onIsAboveQueue, self) Delist(onMouseReleaseQueue, self) if (self.tabs) then for _, tab in pairs(self.tabData) do Delist(onMousePressQueue, tab) Delist(onIsAboveQueue, tab) Delist(onMouseReleaseQueue, tab) end end end function Window:Open() -- FIXME: ignore if already opened self:AvailableForNotifications() self.closed = nil self.Timer = MakeTimer() objectsToDrawSet[self] = true table.insert(zStack, self) self:UpdateDisplayList() end function Window:Close() self:UnavailableForNotifications() if (not self.noAnimation) then self.Timer = MakeTimer() self.closed = true else objectsToDrawSet[self] = nil Delist(zStack, self) end end function Window:BringToFront() Delist(zStack, self) table.insert(zStack, self) end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Group Functions -- function CloseGroup(group, openMainButton) for _, object in pairs(groups[group]) do object:Close() end if (mainButton.closed and openMainButton ~= false) then mainButton:Open() end end function OpenGroup(group) for _, object in pairs(groups[group]) do object:Open() end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Rooms -- -- local roomList = VFS.DirList(ROOM_DIRNAME, "room_*.lua", VFSMODE) -- for _, file in ipairs(roomList) do -- local s = VFS.LoadFile(file, VFSMODE) -- chunk = loadstring(s, file) -- setfenv(chunk, rooms) -- local success, e = pcall(chunk(), file) -- end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Call-ins -- function widget:DrawScreen() for _, object in ipairs(zStack) do object:Draw() end end function widget:Update(dt) realSeconds = realSeconds + dt for object in pairs(onUpdateSet) do object.OnUpdate(dt, realSeconds) end end function widget:MousePress(x, y, button) local capture for _, object in ipairs(onMousePressQueue) do capture = object:OnMousePress(x, y, button) if (capture) then return true end end end function widget:MouseRelease(x, y, button) local capture for _, object in ipairs(onMouseReleaseQueue) do capture = object:OnMouseRelease(x, y, button) if (capture) then return true end end end function widget:IsAbove(x, y) local capture for _, object in pairs(onIsAboveQueue) do capture = object:OnIsAbove(x, y) if (capture) then return true end end end function widget:ViewResize(vsx, vsy) viewSizeX, viewSizeY = vsx, vsy end function widget:MouseMove(x, y, dx, dy, button) for object in pairs(onMouseMoveSet) do object:OnMouseMove(x, y, dx, dy, button) end end function widget:UnitFinished(unitID, unitDefID, unitTeam) for object in pairs(onUnitFinishedSet) do object.OnUnitFinished(unitID, unitDefID, unitTeam) end end function widget:UnitCreated(unitID, unitDefID, unitTeam) for object in pairs(onUnitCreatedSet) do object.OnUnitCreated(unitID, unitDefID, unitTeam) end end function widget:GetConfigData() -- Save local save = {} for object in pairs(getConfigDataSet) do local k, v = object.GetConfigData() save[k] = v end return save end function widget:SetConfigData(data) -- Load for object in pairs(getConfigDataSet) do object.SetConfigData(data) end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Notes -- --[[ todo: easy way to delete objects MouseWheel(up, value) UnitCreated(unitID, unitDefID, unitTeam) UnitFinished(unitID, unitDefID, unitTeam) UnitDestroyed(unitID, unitDefID, unitTeam) UnitTaken(unitID, unitDefID, unitTeam, newTeam) UnitIdle(unitID, unitDefID, unitTeam) UnitLoaded(unitID, unitDefID, unitTeam, transportID, transportTeam) print("-- Mouse Press Begin -------------------") local count = 0 for i, v in pairs(onMousePressSet) do count = count + 1 if i.title then print(i, "tab", i.title) end if i.lineArray then print(i, "window", i.lineArray[1]) end end print("objects notified: ", count) print("-- Mouse Press End----------------------") print("-- Mouse Release Begin -----------------") local count = 0 for i, v in pairs(onMouseReleaseSet) do count = count + 1 if i.title then print("window", i.parent, "tab", i, i.title) end if i.lineArray then print("window", i, "body", i.lineArray[1]) end end print("objects notified: ", count) print("-- Mouse Release End -------------------") table.foreach(getfenv(1), print) --]] --[[ ]] -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Room Info -- -- In-game options menu. -- -- -- -- -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Speed-ups -- local GetConfigInt = Spring.GetConfigInt local GetConfigString = Spring.GetConfigString local SetConfigInt = Spring.SetConfigInt local SetConfigString = Spring.SetConfigString local Window = Window local Tab = Tab -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Constants -- -- local testString = red..[[ -- Click on the window and drag to move it.]]..cyan..[[ Lets see word wrapping and -- colored text.]]..magenta..[[ If you close this window, type "/luaui reload" to -- open it again.]]..white..[[ Lua is an extension programming language designed to -- support general procedural programming with data description facilities. -- ]]..green..[[ It also offers good support for object-oriented programming, -- functional programming, and data-driven programming.]]..red..[[ Lua is intended -- to be used as a powerful, light-weight scripting language for any program that -- needs one.]]..yellow..[[ Lua is implemented as a library, written in clean C -- (that is, in the common subset of ANSI C and C++). -- ]] -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Main Menu -- -- local function MakeLogo() -- local r = math.random(1, 5) -- return ROOM_DIRNAME.."/Logos/"..r..".png" -- end -- mainMenu = Window:Create{ -- group = "ingameMenu", -- x1 = 300, -- x2 = 600, -- y1 = 300, -- y2 = 600, -- closed = true, -- backGroundTextureString = MakeLogo(), -- tabs = { -- {title = white.."Complete Annihilation Main Menu"}, -- {preset = Tab:Close()}, -- {preset = Tab:Goto{title="Display Options", destination="displayMenu",}}, -- {title = "Unit Information", -- position = "left", -- OnMouseReleaseAction = function() -- MakeInfoWindow() -- mainMenu:Close() -- end}, -- {preset = Tab:Goto{title="Storyline", destination="storyLine1",}}, -- {preset = Tab:Goto{title="Debug", destination="debugWindow"}}, -- {preset = Tab:Goto{title="Help Options", destination="helpMessageOptions"}}, -- } -- } -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Display Menu and Sub-menus -- -- Display Menu ---------------------------------------------------------------- displayMenu = Window:Create{ group = "ingameMenu", closed = false, lineArray = {"Todo:", "- LOD scales", "- Show ally commands",}, tabs = { {title = white.."Display Options"}, -- {preset = Tab:Close()}, -- {preset = Tab:Back{previousWindow = "mainMenu"}}, {preset = Tab:Goto{title="GUI Lines Anti-Aliasing", destination="smoothLines"}}, {preset = Tab:Goto{title="GUI Points Anti-Aliasing", destination="smoothPoints"}}, {preset = Tab:Goto{title="Team Nanospray Color", destination="teamNanospray"}}, {preset = Tab:Goto{title="Advanced Unit Shading", destination="advUnitShading"}}, {preset = Tab:Goto{title="Water Detail", destination="waterDetail"}}, {preset = Tab:Goto{title="Shadow Detail", destination="shadowDetail"}}, {preset = Tab:Goto{title="Maximum Particles", destination="maxParticles"}}, {preset = Tab:Goto{title="Frame Rate Manager", destination="managerOptions"}}, } } -- GUI Lines Anti-Aliansing Options Dialog ------------------------------------- smoothLines = Window:Create{ group = "ingameMenu", closed = true, lineArray = { "Current setting is: "..GetConfigInt("smoothLines")..".", red.."Requires a restart to take effect."..white, }, tabs = { {title = "GUI Lines Anti-Aliansing Options"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {preset = Tab:SetConfigInt{title = "Set to 0 (off)", config = "smoothLines", int = 0}}, {preset = Tab:SetConfigInt{title = "Set to 1 (fastest)", config = "smoothLines", int = 1}}, {preset = Tab:SetConfigInt{title = "Set to 2 (don't care)", config = "smoothLines", int = 2}}, {preset = Tab:SetConfigInt{title = "Set to 3 (nicest)", config = "smoothLines", int = 3}}, } } -- GUI Points Anti-Aliansing Options Dialog ------------------------------------ smoothPoints = Window:Create{ group = "ingameMenu", closed = true, lineArray = { "Current setting is: "..GetConfigInt("smoothPoints")..".", red.."Requires a restart to take effect."..white, "With 0, points appear as squares.", "With 1 to 3, points appear as discs.", }, tabs = { {title = "GUI Points Anti-Aliansing Options"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {preset = Tab:SetConfigInt{title = "Set to 0 (off)", config = "smoothPoints", int = 0}}, {preset = Tab:SetConfigInt{title = "Set to 1 (fastest)", config = "smoothPoints", int = 1}}, {preset = Tab:SetConfigInt{title = "Set to 2 (don't care)", config = "smoothPoints", int = 2}}, {preset = Tab:SetConfigInt{title = "Set to 3 (nicest)", config = "smoothPoints", int = 3}}, } } -- Team Nanospray Color Dialog ------------------------------------------------- local function teamNanosprayText() local status local s = GetConfigInt("TeamNanospray") if (s < 1) then status = "disabled" else status = "enabled" end return "Team nanospray color is "..status.."." end teamNanospray = Window:Create{ group = "ingameMenu", lineArray = {teamNanosprayText(), red.."Requires a restart to take effect."..white}, closed = true, tabs = { {title = white.."Team Nanospray Color Dialog"}, {preset = Tab:ConfigEnable{config = "teamNanospray"}}, {preset = Tab:ConfigDisable{config = "teamNanospray"}}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, } } -- 3D Trees Dialog ------------------------------------------------------------- --[[ -- Redundant: already in settings.exe. local function threeDTreesText() local status local s = GetConfigInt("teamNanospray") if (s < 1) then status = "disabled" else status = "enabled" end return "3D trees are "..status.."." end threeDTrees = Window:Create{ group = "ingameMenu", lineArray = {threeDTreesText(), red.."Restart required to take effect."}, closed = true, tabs = { {title = white.."3D Trees Dialog"}, {preset = Tab:ConfigEnable{config = "3DTrees"}}, {preset = Tab:ConfigDisable{config = "3DTrees"}}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, } } -- High detail sky Dialog ------------------------------------------------------ local function advSkyText() local status local s = GetConfigInt("advSky") if (s < 1) then status = "disabled" else status = "enabled" end return "High detail sky is "..status.."." end advSky = Window:Create{ group = "ingameMenu", lineArray = {advSkyText(), red.."Restart required to take effect."}, closed = true, tabs = { {title = white.."High detail sky Dialog"}, {preset = Tab:ConfigEnable{config = "advSky"}}, {preset = Tab:ConfigDisable{config = "advSky"}}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, } } -- Dual Screen Mode ------------------------------------------------------------ local function dualScreenModeText() local status local s = GetConfigInt("dualScreenMode") if (s < 1) then status = "disabled" else status = "enabled" end return "Dual screen mode is "..status.."." end dualScreenMode = Window:Create{ group = "ingameMenu", lineArray = {dualScreenModeText(), red.."Restart required to take effect."}, closed = true, tabs = { {title = white.."Dual Screen Mode Dialog"}, {preset = Tab:ConfigEnable{config = "dualScreenMode"}}, {preset = Tab:ConfigDisable{config = "dualScreenMode"}}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, } } -- Dual Screen Minimap Side ---------------------------------------------------- local function dualScreenSideText() local status local s = GetConfigInt("DualScreenMiniMapOnLeft") if (s < 1) then status = "right" else status = "left" end return "Minimap is on the "..status.." in dual screen mode." end dualScreenSide = Window:Create{ group = "ingameMenu", lineArray = {dualScreenSideText(), red.."Restart required to take effect"}, closed = true, tabs = { {preset = Tab:SetConfigInt{title = white.."Minimap on left", config = "DualScreenMiniMapOnLeft", int = 1, text = "Minimap is on the left in ".. "dual screen mode."}}, {preset = Tab:SetConfigInt{title = "Minimap on right", config = "DualScreenMiniMapOnLeft", int = 0, text = "Minimap is on the right in ".. "dual screen mode."}}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, } } --]] -- Advanced Unit Shading ------------------------------------------------------- local function advUnitShadingText() local status local s = GetConfigInt("AdvUnitShading") if (s < 1) then status = "disabled" else status = "enabled" end return "Advanced unit shading is "..status.."." end advUnitShading = Window:Create{ group = "ingameMenu", closed = true, lineArray = {advUnitShadingText(), 'It is also known as "shiny units"', 'or "reflective units".'}, tabs = { {title = "Advanced Unit Shading"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {title = "Enable", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"advshading 1"} SetConfigInt("advUnitShading", 1) advUnitShading.lineArray[1] = advUnitShadingText() -- lineArray changes end -- take effect }, -- immediately. {title = "Disable", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"advshading 0"} SetConfigInt("advUnitShading", 0) advUnitShading.lineArray[1] = advUnitShadingText() end }, } } -- Water Detail ---------------------------------------------------------------- local function waterText() local status local s = GetConfigInt("ReflectiveWater") if (s < 1) then status = "basic" elseif (s < 2) then status = "reflective" elseif (s <3) then status = "dynamic" else status = "reflective and refractive" end return "The water surface is "..status.."." end waterDetail = Window:Create{ group = "ingameMenu", closed = true, lineArray = {waterText(), red..'On computers with old video cards, dynamic', 'water can severely impair performance.'}, tabs = { {title = white.."Water Detail"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {title = "Basic Water", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"water 0"} SetConfigInt("ReflectiveWater", 0) waterDetail.lineArray[1] = waterText() end }, {title = "Reflective Water", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"water 1"} SetConfigInt("ReflectiveWater", 1) waterDetail.lineArray[1] = waterText() end }, {title = "Reflective and Refractive Water", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"water 3"} SetConfigInt("ReflectiveWater", 3) waterDetail.lineArray[1] = waterText() end }, {title = "Dynamic Water", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"water 2"} SetConfigInt("ReflectiveWater", 2) waterDetail.lineArray[1] = waterText() end }, } } -- Shadow Detail --------------------------------------------------------------- local function shadowDetailText() local status local s = GetConfigInt("Shadows") local t = GetConfigInt("ShadowMapSize") if (s < 1) then status = "Shadows are disabled. " elseif (t < 2048) then status = "Shadow detail is low. " elseif (t < 4096) then status = "Shadow detail is medium." else status = "Shadow detail is high. " end return status end shadowDetail = Window:Create{ group = "ingameMenu", closed = true, text = shadowDetailText(), tabs = { {title = "Shadow Detail"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {title = "Disable Shadows", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"Shadows 0"} SetConfigInt("Shadows", 0) shadowDetail.lineArray[1] = shadowDetailText() end }, {title = "Low Detail Shadows", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"shadows 1 1024"} SetConfigInt("Shadows", 1) SetConfigInt("ShadowMapSize", 1024) shadowDetail.lineArray[1] = shadowDetailText() end }, {title = "Medium Detail Shadows", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"shadows 1 2048"} SetConfigInt("Shadows", 1) SetConfigInt("ShadowMapSize", 2048) shadowDetail.lineArray[1] = shadowDetailText() end }, {title = "High Detail Shadows", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"shadows 1 4096"} SetConfigInt("Shadows", 1) SetConfigInt("ShadowMapSize", 4096) shadowDetail.lineArray[1] = shadowDetailText() end }, } } -- Maximum Particles ----------------------------------------------------------- local function MaxParticlesText() local n = GetConfigInt("MaxParticles") return "At most, "..n.." particles are displayed on-screen." end maxParticles = Window:Create{ group = "ingameMenu", closed = true, lineArray = {MaxParticlesText()}, tabs = { {title = "Maximum Particles"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {title = "1000", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"maxparticles 1000"} SetConfigInt("MaxParticles", 1000) maxParticles.lineArray[1] = MaxParticlesText() maxParticles:UpdateDisplayList() end }, {title = "5000", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"maxparticles 5000"} SetConfigInt("MaxParticles", 5000) maxParticles.lineArray[1] = MaxParticlesText() maxParticles:UpdateDisplayList() end }, {title = "10000", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"maxparticles 10000"} SetConfigInt("MaxParticles", 10000) maxParticles.lineArray[1] = MaxParticlesText() maxParticles:UpdateDisplayList() end }, {title = "20000", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"maxparticles 20000"} SetConfigInt("MaxParticles", 20000) maxParticles.lineArray[1] = MaxParticlesText() maxParticles:UpdateDisplayList() end }, {title = "40000", position = "left", OnMouseReleaseAction = function() Spring.SendCommands{"maxparticles 40000"} SetConfigInt("MaxParticles", 40000) maxParticles.lineArray[1] = MaxParticlesText() maxParticles:UpdateDisplayList() end }, } } -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Main Menu Link Button -- -- mainButton = Window:Create{ -- x1 = 700, -- y1 = 100, -- textWidth = 300, -- lineArray = {"Main Menu"}, -- useOnIsAbove = true, -- OnMousePressAction = function() -- mainButton.timeFromMousePress = MakeTimer() -- return true -- end, -- OnMouseReleaseAction = function() -- if (mainButton.timeFromMousePress() < 0.2) then -- Ignores press/realease -- mainButton:Close() -- couples intended to drag -- mainMenu:Open() -- the window, not click. -- end -- mainButton.timeFromMousePress = nil -- end, -- } -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Room Info: FPS Manager -- -- -- Tries to keep the framerate around 35 by adjusting the detail level. -- In case of problems, pester quantum until he fixes all the bugs :P -- See LuaUI/Widgets/gui_doors.lua for documentation. -- -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Speed-ups -- local Window = Window local Tab = Tab local SendCommands = Spring.SendCommands local GetFPS = Spring.GetFPS -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Constants -- local tolerance = 30 local defaultLevelTable = { {"advshading 1", "advshading 0"}, -- 1 {"luaui disablewidget BlobShadow", "luaui enablewidget BlobShadow"}, -- 2 {"shadows 1 1024", "shadows 0"}, -- 3 {"maxparticles 10000", "maxparticles 5000"}, -- 4 {"water 1", "water 0"}, -- 5 {"shadows 1 2048", "shadows 1 1024"}, -- 6 {"water 3", "water 1"}, -- 7 {"shadows 4096", "shadows 1 2048"}, -- 8 {"maxparticles 15000", "maxparticles 10000"}, -- 9 {"maxparticles 20000", "maxparticles 15000"}, -- 10 {"dynamicsky 1", "dynamicsky 0"}, -- 11 {"water 2", "water 3"}, -- 12 } -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Locals -- local counter = 0 local detailLevel = 1 local fpsTable = {} local raisePoints = 0 local lowerPoints = 0 minFps = 30 maxFps = 40 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- FPS Management -- function MakeManagerText() local s if (enabled) then s = "enabled." else s = "disabled." end local lines = { "The Frame Rate Manager tries to keep the framerate ", string.format("around %d by adjusting the detail level. ", (minFps+maxFps)/2), " ", "It is "..s, } return lines end local function Remove(n) table.remove(levelTable, n) end local function MakeLevelTable() levelTable = Copy(defaultLevelTable) local st = settingsTable if (st.noWater3 or st.noWater) then Remove(12) end if (st.noDynSky) then Remove(11) end if (st.noMaxParticles) then Remove(10) Remove(9) end if (st.noShadows) then Remove(8) end if (st.noWater) then Remove(7) end if (st.noShadows) then Remove(6) end if (st.noWater) then Remove(5) end if (st.noMaxParticles) then Remove(4) end if (st.noShadows) then Remove(3) end if (st.noBlobs) then Remove(2) end if (st.noAdvShad) then Remove(1) end end local function RaiseDetail() raisePoints = raisePoints + 1 if ((detailLevel <= #levelTable) and (raisePoints > tolerance)) then SendCommands({levelTable[detailLevel][1]}) detailLevel = detailLevel + 1 raisePoints = 0 end end local function LowerDetail() lowerPoints = lowerPoints + 1 if ((detailLevel > 1) and (lowerPoints > tolerance)) then SendCommands({levelTable[detailLevel - 1][2]}) detailLevel = detailLevel - 1 lowerPoints = 0 end end local function OnUpdate(t) if (enabled) then counter = counter + 1 table.insert(fpsTable, GetFPS()) if (#fpsTable > 30) then table.remove(fpsTable, 1) end if ((counter+23) % 30 < 0.1) then counter = 0 sum = 0 for _, v in ipairs(fpsTable) do sum = sum + v end average = (sum / #fpsTable) if (average > maxFps) then RaiseDetail() else raisePoints = 0 end if (average < minFps) then LowerDetail() else lowerPoints = 0 end end end end local function GetConfigData() -- Save room_FPSManager = { enabled = enabled, settingsTable = settingsTable, minFps = minFps, maxFps = maxFps, } return "room_FPSManager", room_FPSManager end local function SetConfigData(data) -- Load if data.room_FPSManager then enabled = data.room_FPSManager.enabled settingsTable = data.room_FPSManager.settingsTable or {} maxFps = data.room_FPSManager.maxFps or 40 minFps = data.room_FPSManager.minFps or 30 else data.room_FPSManager = {} enabled = false settingsTable = {} maxFps = 40 minFps = 30 end if (enabled) then levelTable = Copy(defaultLevelTable) detailLevel = #levelTable for i=1, tolerance*15 do LowerDetail() end end detailLevel = 1 MakeLevelTable() managerOptions.lineArray = MakeManagerText() moreOptions.lineArray = MakeOptionsText() end local function MakeOptionsButton(label, setting) local tab = { title = label, position = "left", OnMouseReleaseAction = function() if (settingsTable[setting]) then settingsTable[setting] = nil else settingsTable[setting] = true end MakeLevelTable() moreOptions.lineArray = MakeOptionsText() end, } return tab end managerOptions = Window:Create{ lineArray = MakeManagerText(), group = "FPSManager", closed = true, OnUpdate = OnUpdate, GetConfigData = GetConfigData, SetConfigData = SetConfigData, tabs = { {title = "Frame Rate Manager"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "displayMenu"}}, {title = "Enable", position = "left", OnMouseReleaseAction = function() enabled = true managerOptions.lineArray = MakeManagerText() end, }, {title = "Disable", position = "left", OnMouseReleaseAction = function() enabled = nil managerOptions.lineArray = MakeManagerText() end }, {title = "Options", position = "left", OnMouseReleaseAction = function() managerOptions:Close() moreOptions:Open() end }, }, } local function Check(setting, o) if (settingsTable[setting] and not o) then return "disabled." elseif (not o) then return "enabled." elseif (settingsTable[setting] and o) then return "disallowed." else return "allowed." end end function MakeOptionsText() local st = settingsTable local lines = { string.format("The target frame rate is %d (%d - %d)", (minFps+maxFps)/2, minFps, maxFps), "Sky management is "..Check("noDynSky"), "Dynamic water is "..Check("noWater3", true), "Shadow management is "..Check("noShadows"), "Water management is "..Check("noWater"), "Particle management is "..Check("noMaxParticles"), "Blob aircraft shadows are "..Check("noBlobs", true), "Unit shading management is "..Check("noAdvShad"), " ", "Blob shadows require the BlobShadows widget.", } return lines end moreOptions = Window:Create{ lineArray = {"Blob shadows require the BlobShadows widget."}, group = "FPSManager", closed = true, tabs = { {title = "Frame Rate Manager: Options"}, {preset = Tab:Close()}, {preset = Tab:Back{previousWindow = "managerOptions"}}, MakeOptionsButton("Toggle Sky Management", "noDynSky"), MakeOptionsButton("Allow Dynamic Water", "noWater3"), MakeOptionsButton("Toggle Shadow management", "noShadows"), MakeOptionsButton("Toggle Water Management", "noWater"), MakeOptionsButton("Toggle Particle Management", "noMaxParticles"), MakeOptionsButton("Allow Blob Shadows", "noBlobs"), MakeOptionsButton("Toggle Unit Shading Management", "noAdvShad"), {title = "Decrease Target FPS", position = "bottom", OnMouseReleaseAction = function() maxFps = maxFps * .8 minFps = minFps * .8 moreOptions.lineArray = MakeOptionsText() managerOptions.lineArray = MakeManagerText() end, }, {title = "Increase Target FPS", position = "bottom", OnMouseReleaseAction = function() maxFps = maxFps * 1.2 minFps = minFps * 1.2 moreOptions.lineArray = MakeOptionsText() managerOptions.lineArray = MakeManagerText() end, }, } }