diff --git a/LuaMenu/Addons/tablefunctions.lua b/LuaMenu/Addons/tablefunctions.lua index d986bcef7..ac516d71a 100644 --- a/LuaMenu/Addons/tablefunctions.lua +++ b/LuaMenu/Addons/tablefunctions.lua @@ -54,6 +54,22 @@ function Spring.Utilities.TableEqual(table1, table2) return true end +-- Returns whether the first table is equal to a subset of the second +function Spring.Utilities.TableSubsetEquals(table1, table2) + if not table1 then + return true + end + if not table2 then + return false + end + for key, value in pairs(table1) do + if table2[key] ~= value then + return false + end + end + return true +end + function Spring.Utilities.TableToString(data) local str = "" diff --git a/LuaMenu/Addons/timeFunctions.lua b/LuaMenu/Addons/timeFunctions.lua new file mode 100644 index 000000000..a9a943536 --- /dev/null +++ b/LuaMenu/Addons/timeFunctions.lua @@ -0,0 +1,66 @@ + +Spring.Utilities = Spring.Utilities or {} + +------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------- + +function Spring.Utilities.GetTimeToPast(pastTimeString, includeSeconds) + if (not pastTimeString) or (type(pastTimeString) ~= "string") then + return "??" + end + + -- Example: 2016-07-21T14:49:00.4731696Z + local pastTime = { + string.sub(pastTimeString, 18, 19), + string.sub(pastTimeString, 15, 16), + string.sub(pastTimeString, 12, 13), + string.sub(pastTimeString, 9, 10), + --string.sub(pastTimeString, 6, 7), + --string.sub(pastTimeString, 0, 4), + } + + for i = 1, #pastTime do + pastTime[i] = tonumber(pastTime[i]) + if not pastTime[i] then + return "??" + end + end + + local currentTime = { + tonumber(os.date("!%S")), + tonumber(os.date("!%M")), + tonumber(os.date("!%H")), + tonumber(os.date("!%d")), + --tonumber(os.date("!%m")), + --tonumber(os.date("!%Y")), + } + + local pastSeconds = pastTime[1] + 60*(pastTime[2] + 60*pastTime[3]) + local currentSeconds = currentTime[1] + 60*(currentTime[2] + 60*currentTime[3]) + if currentTime[4] ~= pastTime[4] then + -- Always assume that the past time is one day behind. + currentSeconds = currentSeconds + 86400 + end + + local distanceSeconds = currentSeconds - pastSeconds + local hours = math.floor(distanceSeconds/3600) + local minutes = math.floor(distanceSeconds/60)%60 + local seconds = math.floor(distanceSeconds)%60 + + --Spring.Echo("pastTime", pastTime[1], pastTime[2], pastTime[3], pastTime[4], "pastSeconds", pastSeconds) + --Spring.Echo("currentTime", currentTime[1], currentTime[2], currentTime[3], currentTime[4], "currentSeconds", currentSeconds) + --Spring.Echo("distanceSeconds", distanceSeconds) + + local timeText = "" + if hours > 0 then + timeText = timeText .. hours .. "h " + end + if hours > 0 or minutes > 0 or (not includeSeconds) then + timeText = timeText .. minutes .. "m " + end + if includeSeconds then + timeText = timeText .. seconds .. "s " + end + + return timeText +end diff --git a/LuaMenu/configs/gameConfig/zk/settingsMenu.lua b/LuaMenu/configs/gameConfig/zk/settingsMenu.lua index b2b496b94..fd0e11e98 100644 --- a/LuaMenu/configs/gameConfig/zk/settingsMenu.lua +++ b/LuaMenu/configs/gameConfig/zk/settingsMenu.lua @@ -2,75 +2,90 @@ local settings = { { name = "Graphics", presets = { - Minimal = { - WaterType = "Basic", - WaterQuality = "Low", - DeferredRendering = "Off", - Shadows = "None", - ShadowDetail = "Low", - ParticleLimit = "Minimal", - TerrainDetail = "Minimal", - VegetationDetail = "Minimal", - CompatibilityMode = "On", - AntiAliasing = "Off", - ShaderDetail = "Minimal", - FancySky = "Off", + { + name = "Minimal", + settings = { + WaterType = "Basic", + WaterQuality = "Low", + DeferredRendering = "Off", + Shadows = "None", + ShadowDetail = "Low", + ParticleLimit = "Minimal", + TerrainDetail = "Minimal", + VegetationDetail = "Minimal", + CompatibilityMode = "On", + AntiAliasing = "Off", + ShaderDetail = "Minimal", + FancySky = "Off", + } }, - Low = { - WaterType = "Bumpmapped", - WaterQuality = "Low", - DeferredRendering = "Off", - Shadows = "Units Only", - ShadowDetail = "Low", - ParticleLimit = "Low", - TerrainDetail = "Low", - VegetationDetail = "Low", - CompatibilityMode = "Off", - AntiAliasing = "Off", - ShaderDetail = "Low", - FancySky = "Off", + { + name = "Low", + settings = { + WaterType = "Bumpmapped", + WaterQuality = "Low", + DeferredRendering = "Off", + Shadows = "Units Only", + ShadowDetail = "Low", + ParticleLimit = "Low", + TerrainDetail = "Low", + VegetationDetail = "Low", + CompatibilityMode = "Off", + AntiAliasing = "Off", + ShaderDetail = "Low", + FancySky = "Off", + } }, - Medium = { - WaterType = "Bumpmapped", - WaterQuality = "Medium", - DeferredRendering = "On", - Shadows = "Units Only", - ShadowDetail = "Medium", - ParticleLimit = "Medium", - TerrainDetail = "Medium", - VegetationDetail = "Medium", - CompatibilityMode = "Off", - AntiAliasing = "Off", - ShaderDetail = "Medium", - FancySky = "Off", + { + name = "Medium", + settings = { + WaterType = "Bumpmapped", + WaterQuality = "Medium", + DeferredRendering = "On", + Shadows = "Units Only", + ShadowDetail = "Medium", + ParticleLimit = "Medium", + TerrainDetail = "Medium", + VegetationDetail = "Medium", + CompatibilityMode = "Off", + AntiAliasing = "Off", + ShaderDetail = "Medium", + FancySky = "Off", + } }, - High = { - WaterType = "Bumpmapped", - WaterQuality = "High", - DeferredRendering = "On", - Shadows = "Units and Terrain", - ShadowDetail = "Medium", - ParticleLimit = "High", - TerrainDetail = "High", - VegetationDetail = "High", - CompatibilityMode = "Off", - AntiAliasing = "Low", - ShaderDetail = "High", - FancySky = "Off", + { + name = "High", + settings = { + WaterType = "Bumpmapped", + WaterQuality = "High", + DeferredRendering = "On", + Shadows = "Units and Terrain", + ShadowDetail = "Medium", + ParticleLimit = "High", + TerrainDetail = "High", + VegetationDetail = "High", + CompatibilityMode = "Off", + AntiAliasing = "Low", + ShaderDetail = "High", + FancySky = "Off", + } }, - Ultra = { - WaterType = "Bumpmapped", - WaterQuality = "High", - DeferredRendering = "On", - Shadows = "Units and Terrain", - ShadowDetail = "Ultra", - ParticleLimit = "Ultra", - TerrainDetail = "Ultra", - VegetationDetail = "Ultra", - CompatibilityMode = "Off", - AntiAliasing = "High", - ShaderDetail = "Ultra", - FancySky = "On", + { + name = "Ultra", + settings = { + WaterType = "Bumpmapped", + WaterQuality = "High", + DeferredRendering = "On", + Shadows = "Units and Terrain", + ShadowDetail = "Ultra", + ParticleLimit = "Ultra", + TerrainDetail = "Ultra", + VegetationDetail = "Ultra", + CompatibilityMode = "Off", + AntiAliasing = "High", + ShaderDetail = "Ultra", + FancySky = "On", + } }, }, @@ -458,6 +473,66 @@ local settings = { }, }, }, + { + name = "Interface", + presets = { + { + name = "Default", + settings = { + MouseZoomSpeed = 25, + IconDistance = 151, + MiddlePanSpeed = 10, + CameraPanSpeed = 10, + } + }, + }, + settings = { + { + name = "IconDistance", + humanName = "Icon Distance", + isNumberSetting = true, + applyName = "UnitIconDist", + minValue = 0, + maxValue = 10000, + springConversion = function(value) + return value + end, + }, + { + name = "MouseZoomSpeed", + humanName = "Mouse Zoom Speed", + isNumberSetting = true, + applyName = "ScrollWheelSpeed", + minValue = 1, + maxValue = 500, + springConversion = function(value) + return value*-1 + end, + }, + { + name = "MiddlePanSpeed", + humanName = "Middle Click Pan Speed", + isNumberSetting = true, + applyName = "MiddleClickScrollSpeed", + minValue = 0, + maxValue = 1000, + springConversion = function(value) + return value*-1/2000 + end, + }, + { + name = "CameraPanSpeed", + humanName = "Camera Pan Speed", + isNumberSetting = true, + applyName = "OverheadScrollSpeed", + minValue = 0, + maxValue = 1000, + springConversion = function(value) + return value + end, + }, + }, + }, } local settingsDefaults = { @@ -473,6 +548,10 @@ local settingsDefaults = { AntiAliasing = "Low", ShaderDetail = "High", FancySky = "Off", + MouseZoomSpeed = 25, + IconDistance = 151, + MiddlePanSpeed = 10, + CameraPanSpeed = 10, } return settings, settingsDefaults \ No newline at end of file diff --git a/LuaMenu/configs/gameConfig/zk/singleplayerMenu.lua b/LuaMenu/configs/gameConfig/zk/singleplayerMenu.lua index 43d1372ce..53b67b506 100644 --- a/LuaMenu/configs/gameConfig/zk/singleplayerMenu.lua +++ b/LuaMenu/configs/gameConfig/zk/singleplayerMenu.lua @@ -1,3 +1,16 @@ + +-- Only show reasonably complete things in ZK mode +if WG.Chobby.Configuration.singleplayer_mode == 2 then + return { + { + name = "skirmish", + control = WG.BattleRoomWindow.GetSingleplayerControl(), + entryCheck = WG.BattleRoomWindow.SetSingleplayerGame, + }, + } +end + +-- Show everything in ZK Dev mode. return { { name = "quick_start", diff --git a/LuaMenu/configs/gameConfig/zk/taskbarLogo.png b/LuaMenu/configs/gameConfig/zk/taskbarLogo.png new file mode 100644 index 000000000..7973834f9 Binary files /dev/null and b/LuaMenu/configs/gameConfig/zk/taskbarLogo.png differ diff --git a/LuaMenu/widgets/api_internet_browser.lua b/LuaMenu/widgets/api_internet_browser.lua new file mode 100644 index 000000000..26d42233a --- /dev/null +++ b/LuaMenu/widgets/api_internet_browser.lua @@ -0,0 +1,63 @@ +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +function widget:GetInfo() + return { + name = "Internet Browser API", + desc = "Provides the interface for opening URLs and interacting with a browser.", + author = "GoogleFrog", + date = "22 September 2016", + license = "GNU LGPL, v2.1 or later", + layer = -10000, + enabled = true -- loaded by default? + } +end + +local urlPattern = "https?://[%w-_%.%?%.:/%+=&]+" + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- External Functions +local BrowserHandler = {} + +function BrowserHandler.OpenUrl(urlString) + Spring.SetClipboard(urlString) + WG.TooltipHandler.TooltipOverride("URL copied " .. urlString, 1) +end + +function BrowserHandler.AddClickableUrls(chatString, onTextClick) + + local urlStart, urlEnd = string.find(chatString, "http[^%s]*") + --Spring.Echo("URL urlStart, urlEnd", chatString, urlStart, urlEnd) + while urlStart do + -- Cull end puncuation + local endChar = string.sub(chatString, urlEnd, urlEnd) + if string.find(endChar, "%p") then + urlEnd = urlEnd - 1 + end + + local urlString = string.sub(chatString, urlStart, urlEnd) + + onTextClick[#onTextClick + 1] = { + startIndex = urlStart, + endIndex = urlEnd, + OnTextClick = { + function() + BrowserHandler.OpenUrl(urlString) + end + } + } + + urlStart, urlEnd = string.find(chatString, urlPattern, urlEnd) + end + + return onTextClick +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Widget Interface + +function widget:Initialize() + WG.BrowserHandler = BrowserHandler +end diff --git a/LuaMenu/widgets/api_user_handler.lua b/LuaMenu/widgets/api_user_handler.lua index fd6cf4244..ea63177b6 100644 --- a/LuaMenu/widgets/api_user_handler.lua +++ b/LuaMenu/widgets/api_user_handler.lua @@ -123,13 +123,18 @@ local function GetUserComboBoxOptions(userName, isInBattle, userControl) local userBattleInfo = userControl.lobby:GetUserBattleStatus(userName) or {} local myUserName = userControl.lobby:GetMyUserName() local comboOptions = {} + if (not userBattleInfo.aiLib) and userName ~= myUserName then comboOptions[#comboOptions + 1] = "Message" if (not isInBattle) and userInfo.battleID then local battle = lobby:GetBattle(userInfo.battleID) if battle and WG.Chobby.Configuration:IsValidEngineVersion(battle.engineVersion) then - comboOptions[#comboOptions + 1] = "Join Battle" + if not WG.Chobby.Configuration.showMatchMakerBattles and battle.isMatchMaker then + comboOptions[#comboOptions + 1] = "Watch Battle" + else + comboOptions[#comboOptions + 1] = "Join Battle" + end end end @@ -138,7 +143,11 @@ local function GetUserComboBoxOptions(userName, isInBattle, userControl) else comboOptions[#comboOptions + 1] = "Friend" end - comboOptions[#comboOptions + 1] = "Report" + + if userInfo.accountID then + comboOptions[#comboOptions + 1] = "Report" + end + if userInfo.isIgnored then comboOptions[#comboOptions + 1] = "Unignore" else @@ -146,6 +155,10 @@ local function GetUserComboBoxOptions(userName, isInBattle, userControl) end end + if userInfo.accountID then + comboOptions[#comboOptions + 1] = "User Page" + end + -- userControl.lobby:GetMyIsAdmin() -- Let everyone start kick votes. if isInBattle or (userBattleInfo.aiLib and userBattleInfo.owner == myUserName) then @@ -221,6 +234,48 @@ local function GetUserStatus(userName, isInBattle, userControl) end end +local function UpdateUserControlStatus(userName, userControls) + if userControls.imStatusLarge then + local imgFile, status, fontColor = GetUserStatus(userName, isInBattle, userControls) + userControls.tbName.font.color = fontColor + userControls.tbName:Invalidate() + userControls.imStatusLarge.file = imgFile + userControls.imStatusLarge:Invalidate() + userControls.lblStatusLarge.font.color = fontColor + userControls.lblStatusLarge:SetCaption(i18n(status .. "_status")) + return + elseif not userControls.imStatusFirst then + return + end + + local status1, status2 = GetUserStatusImages(userName, userControls.isInBattle, userControls) + userControls.imStatusFirst.file = status1 + userControls.imStatusSecond.file = status2 + userControls.imStatusFirst:Invalidate() + userControls.imStatusSecond:Invalidate() + + if not userControls.maxNameLength then + return + end + local statusFirstPos = userControls.nameStartY + userControls.nameActualLength + 3 + local statusSecondPos = userControls.nameStartY + userControls.nameActualLength + 24 + if status2 and userControls.nameStartY + userControls.nameActualLength + 42 > userControls.maxNameLength then + statusFirstPos = userControls.maxNameLength - 42 + statusSecondPos = userControls.maxNameLength - 21 + elseif status1 and userControls.nameStartY + userControls.nameActualLength + 21 > userControls.maxNameLength then + statusFirstPos = userControls.maxNameLength - 21 + end + + userControls.imStatusFirst:SetPos(statusFirstPos) + userControls.imStatusSecond:SetPos(statusSecondPos) + + local nameSpace = userControls.maxNameLength - userControls.nameStartY - (userControls.maxNameLength - statusFirstPos) + local truncatedName = StringUtilities.TruncateStringIfRequiredAndDotDot(userName, userControls.tbName.font, nameSpace) + if truncatedName then + userControls.tbName:SetText(truncatedName) + end +end + local function UpdateUserActivity(listener, userName) for i = 1, #userListList do local userList = userListList[i] @@ -235,22 +290,7 @@ local function UpdateUserActivity(listener, userName) data.tbName.font.color = GetUserNameColor(userName, data) or WG.Chobby.Configuration:GetUserNameColor() data.tbName:Invalidate() end - - if data.imStatusFirst then - local status1, status2 = GetUserStatusImages(userName, data.isInBattle, data) - data.imStatusFirst.file = status1 - data.imStatusSecond.file = status2 - data.imStatusFirst:Invalidate() - data.imStatusSecond:Invalidate() - elseif data.imStatusLarge then - local imgFile, status, fontColor = GetUserStatus(userName, isInBattle, data) - data.tbName.font.color = fontColor - data.tbName:Invalidate() - data.imStatusLarge.file = imgFile - data.imStatusLarge:Invalidate() - data.lblStatusLarge.font.color = fontColor - data.lblStatusLarge:SetCaption(i18n(status .. "_status")) - end + UpdateUserControlStatus(userName, data) end end end @@ -302,8 +342,9 @@ local function GetUserControls(userName, opts) local offset = opts.offset or 0 local offsetY = opts.offsetY or 0 local height = opts.height or 22 - local showFounder = opts.showFounder + local showFounder = opts.showFounder local showModerator = opts.showModerator + local comboBoxOnly = opts.comboBoxOnly local userControls = reinitialize or {} @@ -331,7 +372,7 @@ local function GetUserControls(userName, opts) end userControls.mainControl = ControlType:New { - name = userName, + name = (not comboBoxOnly) and userName, -- Many can be added to screen0 x = 0, y = 0, right = 0, @@ -352,6 +393,9 @@ local function GetUserControls(userName, opts) OnOpen = { function (obj) obj.tooltip = nil + -- Update hovered tooltip + local x,y = Spring.GetMouseState() + screen0:IsAbove(x,y) end }, OnClose = { @@ -387,8 +431,21 @@ local function GetUserControls(userName, opts) WG.BattleRoomWindow.LeaveBattle() userControls.lobby:JoinBattle(userInfo.battleID) end + elseif selectedName == "Watch Battle" then + local userInfo = userControls.lobby:GetUser(userName) or {} + if userInfo.battleID then + lobby:RejoinBattle(userInfo.battleID) + end + elseif selectedName == "User Page" then + local userInfo = userControls.lobby:GetUser(userName) or {} + if userInfo.accountID then + WG.BrowserHandler.OpenUrl("http://zero-k.info/Users/Detail/" .. userInfo.accountID) + end elseif selectedName == "Report" then - Spring.Echo("TODO - Open the right webpage") + local userInfo = userControls.lobby:GetUser(userName) or {} + if userInfo.accountID then + WG.BrowserHandler.OpenUrl("http://zero-k.info/Users/ReportToAdmin/" .. userInfo.accountID) + end elseif selectedName == "Unignore" then userControls.lobby:Unignore(userName) elseif selectedName == "Ignore" then @@ -398,7 +455,11 @@ local function GetUserControls(userName, opts) } } end - + + if comboBoxOnly then + return userControls + end + if isInBattle and not suppressSync then offset = offset + 1 userControls.imSyncStatus = Image:New { @@ -472,6 +533,9 @@ local function GetUserControls(userName, opts) } local userNameStart = offset local truncatedName = StringUtilities.TruncateStringIfRequiredAndDotDot(userName, userControls.tbName.font, maxNameLength and (maxNameLength - offset)) + userControls.nameStartY = offset + userControls.maxNameLength = maxNameLength + local nameColor = GetUserNameColor(userName, userControls) if nameColor then userControls.tbName.font.color = nameColor @@ -480,7 +544,8 @@ local function GetUserControls(userName, opts) if truncatedName then userControls.tbName:SetText(truncatedName) end - offset = offset + userControls.tbName.font:GetTextWidth(userControls.tbName.text) + userControls.nameActualLength = userControls.tbName.font:GetTextWidth(userControls.tbName.text) + offset = offset + userControls.nameActualLength if not hideStatus then if not large then @@ -510,6 +575,8 @@ local function GetUserControls(userName, opts) file = status2, } offset = offset + 20 + + UpdateUserControlStatus(userName, userControls) else offsetY = offsetY + 35 offset = 5 @@ -566,6 +633,35 @@ local function GetUserControls(userName, opts) return userControls end +local function _GetUserDropdownMenu(userName, isInBattle) + local opts = { + isInBattle = isInBattle, + comboBoxOnly = true + } + local userControls = GetUserControls(userName, opts) + local parentControl = WG.Chobby.interfaceRoot.GetLobbyInterfaceHolder() + + parentControl:AddChild(userControls.mainControl) + userControls.mainControl:BringToFront() + + local x,y = Spring.GetMouseState() + local screenWidth, screenHeight = Spring.GetWindowGeometry() + userControls.mainControl:SetPos(math.max(0, x - 60), screenHeight - y - userControls.mainControl.height + 5, 120) + + local function delayFunc() + -- Must click on the new ComboBox, otherwise an infinite loop may be caused. + screen0:MouseDown(x, y + 10, 1) + end + + WG.Delay(delayFunc, 0.001) + + userControls.mainControl.OnClose = userControls.mainControl.OnClose or {} + userControls.mainControl.OnClose[#userControls.mainControl.OnClose + 1] = + function (obj) + obj:Dispose() + end +end + -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- External Functions @@ -657,6 +753,10 @@ function userHandler.GetNotificationUser(userName) }) end +function userHandler.GetUserDropdownMenu(userName, isInbattle) + _GetUserDropdownMenu(userName, isInbattle) +end + -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Connection diff --git a/LuaMenu/widgets/chobby/components/background.lua b/LuaMenu/widgets/chobby/components/background.lua index 58d363ef8..6da03a0a3 100644 --- a/LuaMenu/widgets/chobby/components/background.lua +++ b/LuaMenu/widgets/chobby/components/background.lua @@ -1,32 +1,41 @@ Background = LCS.class{} -function Background:init() - self:Enable() - - local function onConfigurationChange(listener, key, value) - if key == "singleplayer_mode" then - local file, focus = Configuration:GetBackgroundImage() - self.backgroundFocus = focus - self.backgroundImage.file = file - local texInfo = gl.TextureInfo(file) - self.width, self.height = texInfo.xsize, texInfo.ysize - self.backgroundControl:Invalidate() - self:Resize() +function Background:init(imageOverride, colorOverride, backgroundFocus) + self.colorOverride = colorOverride + if imageOverride then + self.imageOverride = imageOverride + self.backgroundFocus = backgroundFocus + self:Enable() + else + self:Enable() + local function onConfigurationChange(listener, key, value) + if key == "singleplayer_mode" then + local file, focus = Configuration:GetBackgroundImage() + self.backgroundFocus = focus + self.backgroundImage.file = file + local texInfo = gl.TextureInfo(file) + self.width, self.height = texInfo.xsize, texInfo.ysize + self.backgroundControl:Invalidate() + self:Resize() + end end - end - Configuration:AddListener("OnConfigurationChange", onConfigurationChange) + Configuration:AddListener("OnConfigurationChange", onConfigurationChange) + end end function Background:SetAlpha(newAlpha) - self.backgroundImage.color[4] = newAlpha - self.backgroundImage:Invalidate() + if self.backgroundImage then + self.backgroundImage.color[4] = newAlpha + self.backgroundImage:Invalidate() + end end function Background:Resize(backgroundControl) backgroundControl = backgroundControl or self.backgroundControl - if not self.backgroundImage then + if not (self.backgroundImage and self.backgroundFocus) then return end + local width, height = self.width, self.height if not (width and height) then return @@ -69,9 +78,15 @@ end function Background:Enable() if not self.backgroundControl then - local file, focus = Configuration:GetBackgroundImage() - self.backgroundFocus = focus - local texInfo = gl.TextureInfo(file) + local imageFile + if self.imageOverride then + imageFile = self.imageOverride + else + local file, focus = Configuration:GetBackgroundImage() + imageFile = file + self.backgroundFocus = focus + end + local texInfo = gl.TextureInfo(imageFile) self.width, self.height = texInfo.xsize, texInfo.ysize self.backgroundImage = Image:New { @@ -81,12 +96,12 @@ function Background:Enable() bottom = 0, padding = {0,0,0,0}, margin = {0,0,0,0}, + color = self.colorOverride, keepAspect = false, - file = file, + file = imageFile, } self.backgroundControl = Control:New { - name = "backgroundControl", x = 0, y = 0, right = 0, diff --git a/LuaMenu/widgets/chobby/components/battle/battle_list_window.lua b/LuaMenu/widgets/chobby/components/battle/battle_list_window.lua index f7e857144..f6f77c449 100644 --- a/LuaMenu/widgets/chobby/components/battle/battle_list_window.lua +++ b/LuaMenu/widgets/chobby/components/battle/battle_list_window.lua @@ -102,9 +102,14 @@ function BattleListWindow:Update() end function BattleListWindow:AddBattle(battleID, battle) + battle = battle or lobby:GetBattle(battleID) if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then return end + + if not WG.Chobby.Configuration.showMatchMakerBattles and battle and battle.isMatchMaker then + return + end local height = self.itemHeight - 20 local parentButton = Button:New { @@ -125,11 +130,14 @@ function BattleListWindow:AddBattle(battleID, battle) return end if not Configuration.confirmation_battleFromBattle then - local function Success() - self:JoinBattle(battle) + local myBattle = lobby:GetBattle(myBattleID) + if not WG.Chobby.Configuration.showMatchMakerBattles and myBattle and not myBattle.isMatchMaker then + local function Success() + self:JoinBattle(battle) + end + ConfirmationPopup(Success, "Are you sure you want to leave your current battle and join a new one?", "confirmation_battleFromBattle") + return end - ConfirmationPopup(Success, "Are you sure you want to leave your current battle and join a new one?", "confirmation_battleFromBattle") - return end end self:JoinBattle(battle) @@ -279,7 +287,13 @@ function BattleListWindow:UpdateSync(battleID) if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then return end + local items = self:GetRowItems(battleID) + if not items then + self:AddBattle(battleID) + return + end + local imHaveMap = items.battleButton:GetChildByName("imHaveMap") local imHaveGame = items.battleButton:GetChildByName("imHaveGame") @@ -292,7 +306,13 @@ function BattleListWindow:JoinedBattle(battleID) if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then return end + local items = self:GetRowItems(battleID) + if not items then + self:AddBattle(battleID) + return + end + local playersCaption = items.battleButton:GetChildByName("playersCaption") playersCaption:SetCaption((#battle.users - battle.spectatorCount) .. "/" .. battle.maxPlayers) self:RecalculateOrder(battleID) @@ -303,7 +323,13 @@ function BattleListWindow:LeftBattle(battleID) if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then return end + local items = self:GetRowItems(battleID) + if not items then + self:AddBattle(battleID) + return + end + local playersCaption = items.battleButton:GetChildByName("playersCaption") playersCaption:SetCaption((#battle.users - battle.spectatorCount) .. "/" .. battle.maxPlayers) self:RecalculateOrder(battleID) @@ -314,7 +340,12 @@ function BattleListWindow:OnUpdateBattleInfo(battleID) if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then return end + local items = self:GetRowItems(battleID) + if not items then + self:AddBattle(battleID) + return + end local mapCaption = items.battleButton:GetChildByName("mapCaption") local imHaveMap = items.battleButton:GetChildByName("imHaveMap") @@ -348,7 +379,13 @@ function BattleListWindow:OnBattleIngameUpdate(battleID, isRunning) if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then return end + local items = self:GetRowItems(battleID) + if not items then + self:AddBattle(battleID) + return + end + local runningImage = items.battleButton:GetChildByName("minimap"):GetChildByName("runningImage") if isRunning then runningImage.file = BATTLE_RUNNING diff --git a/LuaMenu/widgets/chobby/components/battle/battle_watch_list_window.lua b/LuaMenu/widgets/chobby/components/battle/battle_watch_list_window.lua new file mode 100644 index 000000000..8e746feeb --- /dev/null +++ b/LuaMenu/widgets/chobby/components/battle/battle_watch_list_window.lua @@ -0,0 +1,303 @@ +BattleWatchListWindow = ListWindow:extends{} + +local BATTLE_RUNNING = LUA_DIRNAME .. "images/runningBattle.png" + +local IMG_READY = LUA_DIRNAME .. "images/ready.png" +local IMG_UNREADY = LUA_DIRNAME .. "images/unready.png" + +function BattleWatchListWindow:init(parent) + self:super("init", parent, i18n("spectate_running_games"), true) + + self:SetMinItemWidth(320) + self.columns = 3 + self.itemHeight = 80 + self.itemPadding = 1 + + local update = function() self:Update() end + + self.onBattleOpened = function(listener, battleID) + self:AddBattle(battleID, lobby:GetBattle(battleID)) + end + lobby:AddListener("OnBattleOpened", self.onBattleOpened) + + self.onBattleClosed = function(listener, battleID) + self:RemoveRow(battleID) + end + lobby:AddListener("OnBattleClosed", self.onBattleClosed) + + self.onJoinedBattle = function(listener, battleID) + self:JoinedBattle(battleID) + end + lobby:AddListener("OnJoinedBattle", self.onJoinedBattle) + + self.onLeftBattle = function(listener, battleID) + self:LeftBattle(battleID) + end + lobby:AddListener("OnLeftBattle", self.onLeftBattle) + + self.onUpdateBattleInfo = function(listener, battleID) + self:OnUpdateBattleInfo(battleID) + end + lobby:AddListener("OnUpdateBattleInfo", self.onUpdateBattleInfo) + + self.onBattleIngameUpdate = function(listener, battleID, isRunning) + self:OnBattleIngameUpdate(battleID, isRunning) + end + lobby:AddListener("OnBattleIngameUpdate", self.onBattleIngameUpdate) + + local function onConfigurationChange(listener, key, value) + if key == "displayBadEngines" then + update() + end + end + Configuration:AddListener("OnConfigurationChange", onConfigurationChange) + + local function UpdateTimersDelay() + self:UpdateTimers() + WG.Delay(UpdateTimersDelay, 30) + end + WG.Delay(UpdateTimersDelay, 30) + + update() +end + +function BattleWatchListWindow:RemoveListeners() + lobby:RemoveListener("OnBattleOpened", self.onBattleOpened) + lobby:RemoveListener("OnBattleClosed", self.onBattleClosed) + lobby:RemoveListener("OnJoinedBattle", self.onJoinedBattle) + lobby:RemoveListener("OnLeftBattle", self.onLeftBattle) + lobby:RemoveListener("OnUpdateBattleInfo", self.onUpdateBattleInfo) +end + +function BattleWatchListWindow:Update() + self:Clear() + + local battles = lobby:GetBattles() + Spring.Echo("Number of battles: " .. lobby:GetBattleCount()) + local tmp = {} + for _, battle in pairs(battles) do + table.insert(tmp, battle) + end + battles = tmp + table.sort(battles, + function(a, b) + return #a.users > #b.users + end + ) + + for _, battle in pairs(battles) do + self:AddBattle(battle.battleID, battle) + end +end + +function BattleWatchListWindow:AddBattle(battleID) + local battle = lobby:GetBattle(battleID) + + if (not battle) or battle.passworded or (not battle.isRunning) or (not VFS.HasArchive(battle.mapName)) or (not VFS.HasArchive(battle.gameName)) then + return + end + + if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then + return + end + + local height = self.itemHeight - 20 + local parentButton = Button:New { + name = "battleButton", + x = 0, + right = 0, + y = 0, + height = self.itemHeight, + caption = "", + OnClick = { + function() + lobby:RejoinBattle(battleID) + end + }, + tooltip = "battle_tooltip_" .. battleID, + } + + local lblTitle = Label:New { + name = "title", + x = height + 3, + y = 0, + right = 0, + height = 20, + valign = 'center', + font = Configuration:GetFont(2), + caption = battle.title, + parent = parentButton, + OnResize = { + function (obj, xSize, ySize) + obj:SetCaption(StringUtilities.GetTruncatedStringWithDotDot(battle.title, obj.font, obj.width)) + end + } + } + local minimap = Panel:New { + name = "minimap", + x = 3, + y = 3, + width = height - 6, + height = height - 6, + padding = {1,1,1,1}, + parent = parentButton, + } + local minimapImage = Image:New { + name = "minimapImage", + x = 0, + y = 0, + right = 0, + bottom = 0, + keepAspect = true, + file = Configuration:GetMinimapSmallImage(battle.mapName, battle.gameName), + parent = minimap, + } + local runningImage = Image:New { + name = "runningImage", + x = 0, + y = 0, + right = 0, + bottom = 0, + keepAspect = false, + file = BATTLE_RUNNING, + parent = minimap, + } + runningImage:BringToFront() + + local playerCount = (#battle.users - battle.spectatorCount) + local lblPlayersOnMap = Label:New { + name = "playersOnMapCaption", + x = height + 3, + right = 0, + y = 20, + height = 15, + valign = 'center', + font = Configuration:GetFont(1), + caption = playerCount .. ((playerCount == 1 and " player on " ) or " players on ") .. battle.mapName:gsub("_", " "), + parent = parentButton, + } + + local lblRunningTime = Label:New { + name = "runningTimeCaption", + x = height + 3, + right = 0, + y = 36, + height = 15, + valign = 'center', + font = Configuration:GetFont(1), + caption = "Running for " .. Spring.Utilities.GetTimeToPast(battle.runningSince), + parent = parentButton, + } + + self:AddRow({parentButton}, battle.battleID) +end + +function BattleWatchListWindow:CompareItems(id1, id2) + local battle1, battle2 = lobby:GetBattle(id1), lobby:GetBattle(id2) + if battle1 and battle2 then + return #battle1.users - #battle2.users + else + Spring.Echo("battle1", id1, battle1, battle1 and battle1.users) + Spring.Echo("battle2", id2, battle2, battle2 and battle2.users) + return 0 + end +end + +function BattleWatchListWindow:DownloadFinished(downloadID, bla, moredata, thing) + for battleID,_ in pairs(lobby:GetBattles()) do + self:UpdateSync(battleID) + end +end + +function BattleWatchListWindow:UpdateSync(battleID) + local battle = lobby:GetBattle(battleID) + if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then + return + end + local items = self:GetRowItems(battleID) + + if not items then + self:AddBattle(battleID) + return + end + + if not (VFS.HasArchive(battle.mapName) and VFS.HasArchive(battle.gameName)) then + self:RemoveRow(battleID) + end +end + +function BattleWatchListWindow:UpdateTimers() + for battleID,_ in pairs(self.itemNames) do + local items = self:GetRowItems(battleID) + if not items then + break + end + + local battle = lobby:GetBattle(battleID) + local runningTimeCaption = items.battleButton:GetChildByName("runningTimeCaption") + runningTimeCaption:SetCaption("Running for " .. Spring.Utilities.GetTimeToPast(battle.runningSince)) + end +end + +function BattleWatchListWindow:JoinedBattle(battleID) + local items = self:GetRowItems(battleID) + if not items then + return + end + local battle = lobby:GetBattle(battleID) + local playersOnMapCaption = items.battleButton:GetChildByName("playersOnMapCaption") + local playerCount = (#battle.users - battle.spectatorCount) + playersOnMapCaption:SetCaption((#battle.users - battle.spectatorCount) .. ((playerCount == 1 and " player on " ) or " players on ") .. battle.mapName:gsub("_", " ")) + self:RecalculateOrder(battleID) +end + +function BattleWatchListWindow:LeftBattle(battleID) + local items = self:GetRowItems(battleID) + if not items then + return + end + local battle = lobby:GetBattle(battleID) + local playersOnMapCaption = items.battleButton:GetChildByName("playersOnMapCaption") + local playerCount = (#battle.users - battle.spectatorCount) + playersOnMapCaption:SetCaption((#battle.users - battle.spectatorCount) .. ((playerCount == 1 and " player on " ) or " players on ") .. battle.mapName:gsub("_", " ")) + self:RecalculateOrder(battleID) +end + +function BattleWatchListWindow:OnUpdateBattleInfo(battleID) + local battle = lobby:GetBattle(battleID) + if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then + return + end + local items = self:GetRowItems(battleID) + + if not items then + self:AddBattle(battleID) + return + end + + if not (VFS.HasArchive(battle.mapName) and VFS.HasArchive(battle.gameName)) then + self:RemoveRow(battleID) + end + + local minimapImage = items.battleButton:GetChildByName("minimap"):GetChildByName("minimapImage") + minimapImage.file = Configuration:GetMinimapImage(battle.mapName, battle.gameName) + minimapImage:Invalidate() + + local playersOnMapCaption = items.battleButton:GetChildByName("playersOnMapCaption") + local playerCount = (#battle.users - battle.spectatorCount) + playersOnMapCaption:SetCaption((#battle.users - battle.spectatorCount) .. ((playerCount == 1 and " player on " ) or " players on ") .. battle.mapName:gsub("_", " ")) + + self:RecalculateOrder(battleID) +end + +function BattleWatchListWindow:OnBattleIngameUpdate(battleID, isRunning) + local battle = lobby:GetBattle(battleID) + if not (Configuration.displayBadEngines or Configuration:IsValidEngineVersion(battle.engineVersion)) then + return + end + if isRunning then + self:AddBattle(battleID) + else + self:RemoveRow(battleID) + end +end diff --git a/LuaMenu/widgets/chobby/components/chat_windows.lua b/LuaMenu/widgets/chobby/components/chat_windows.lua index d3fb9f954..fbf9aeb2e 100644 --- a/LuaMenu/widgets/chobby/components/chat_windows.lua +++ b/LuaMenu/widgets/chobby/components/chat_windows.lua @@ -142,7 +142,7 @@ function ChatWindows:init() self.activeUnreadMessages = self.activeUnreadMessages + 1 end privateChatConsole:AddMessage(message, userName, msgDate) - self:_NotifyTab(chanName, userName, "Private", message, "sounds/beep4.wav", 15) + self:_NotifyTab(chanName, userName, "Private", true, message, "sounds/beep4.wav", 15) end end ) @@ -154,7 +154,7 @@ function ChatWindows:init() self.activeUnreadMessages = self.activeUnreadMessages + 1 end privateChatConsole:AddMessage(message, userName, msgDate, Configuration.meColor, true) - self:_NotifyTab(chanName, userName, "Private", message, "sounds/beep4.wav", 15) + self:_NotifyTab(chanName, userName, "Private", true, message, "sounds/beep4.wav", 15) end ) lobby:AddListener("OnRemoveUser", @@ -556,6 +556,7 @@ function ChatWindows:_NotifyTab(tabName, userName, chanName, nameMentioned, mess title = userName .. " in " .. chanName .. ":", body = message, sound = sound, + soundVolume = (WG.Chobby.Configuration.menuNotificationVolume or 1)*0.5, time = popupDuration, }) end @@ -688,7 +689,7 @@ function ChatWindows:GetChannelConsole(chanName) local function Resize(obj) self:UpdateOldChatLinePosition(obj) end - channelConsole = Console(chanName, MessageListener, nil, Resize) + channelConsole = Console(chanName, MessageListener, nil, Resize, false) self.channelConsoles[chanName] = channelConsole Configuration.channels[chanName] = true @@ -771,7 +772,7 @@ function ChatWindows:GetPrivateChatConsole(userName, switchTo) local function Resize(obj) self:UpdateOldChatLinePosition(obj) end - privateChatConsole = Console(chanName, MessageListener, nil, Resize) + privateChatConsole = Console(chanName, MessageListener, nil, Resize, false) self.privateChatConsoles[chanName] = privateChatConsole local caption = "@" .. userName diff --git a/LuaMenu/widgets/chobby/components/configuration.lua b/LuaMenu/widgets/chobby/components/configuration.lua index 83814d508..a30e27bf8 100644 --- a/LuaMenu/widgets/chobby/components/configuration.lua +++ b/LuaMenu/widgets/chobby/components/configuration.lua @@ -6,12 +6,13 @@ VFS.Include("libs/liblobby/lobby/json.lua") function Configuration:init() self.listeners = {} --- self.serverAddress = "localhost" + --self.serverAddress = "localhost" self.serverAddress = WG.Server.serverAddress or "springrts.com" self.serverPort = 8200 - self.chatMaxNameLength = 170 -- Pixels - self.statusMaxNameLength = 230 + self.userListWidth = 205 -- Main user list width. Possibly configurable in the future. + self.chatMaxNameLength = 195 -- Pixels + self.statusMaxNameLength = 210 self.friendMaxNameLength = 230 self.notificationMaxNameLength = 230 @@ -59,7 +60,15 @@ function Configuration:init() doNotAskAgainKey = "confirmation_mainMenuFromBattle", question = "You are in a battle and will leave it if you return to the main menu. Are you sure you want to return to the main menu?", testFunction = function () - return (lobby:GetMyBattleID() and true) or false + local battleID = lobby:GetMyBattleID() + if not battleID then + return false + end + if self.showMatchMakerBattles then + return true + end + local battle = lobby:GetBattle(battleID) + return (battle and not battle.isMatchMaker) or false end } }, @@ -67,8 +76,6 @@ function Configuration:init() } } - self.userListWidth = 220 -- Main user list width. Possibly configurable in the future. - self.shortnameMap = { "chobby", "zk", @@ -81,6 +88,11 @@ function Configuration:init() self.debugMode = false self.onlyShowFeaturedMaps = true self.useSpringRestart = false + self.menuMusicVolume = 0.5 + self.menuNotificationVolume = 0.8 + self.showMatchMakerBattles = false + + self.chatFontSize = 16 self.font = { [0] = {size = 10, shadow = false}, @@ -112,6 +124,8 @@ end function Configuration:GetConfigData() return { + serverAddress = self.serverAddress, + serverPort = self.serverPort, userName = self.userName, password = self.password, autoLogin = self.autoLogin, @@ -131,6 +145,10 @@ function Configuration:GetConfigData() displayBots = self.displayBots, displayBadEngines = self.displayBadEngines, settingsMenuValues = self.settingsMenuValues, + menuMusicVolume = self.menuMusicVolume, + menuNotificationVolume = self.menuNotificationVolume, + showMatchMakerBattles = self.showMatchMakerBattles, + chatFontSize = self.chatFontSize, } end @@ -297,6 +315,15 @@ function Configuration:GetBackgroundImage() return self:GetGameConfigFilePath(false, "skinning/background.jpg", shortname), backgroundFocus end +function Configuration:GetTaskbarIcon() + local shortname = self.shortnameMap[self.singleplayer_mode] + local pngImage = self:GetGameConfigFilePath(false, "taskbarLogo.png", shortname) + if pngImage then + return pngImage + end + return false +end + function Configuration:IsValidEngineVersion(engineVersion) --Spring.Echo("Checking engineVersion", engineVersion, "against", Game.version, "numbers", tonumber(Game.version), string.gsub(Game.version, " develop", "")) if tonumber(Game.version) then diff --git a/LuaMenu/widgets/chobby/components/console.lua b/LuaMenu/widgets/chobby/components/console.lua index 048a0404b..dcc9bf498 100644 --- a/LuaMenu/widgets/chobby/components/console.lua +++ b/LuaMenu/widgets/chobby/components/console.lua @@ -1,6 +1,6 @@ Console = LCS.class{} -function Console:init(channelName, sendMessageListener, noHistoryLoad, onResizeFunc) +function Console:init(channelName, sendMessageListener, noHistoryLoad, onResizeFunc, isBattleChat) self.listener = sendMessageListener self.showDate = true self.dateFormat = "%H:%M" @@ -34,7 +34,7 @@ function Console:init(channelName, sendMessageListener, noHistoryLoad, onResizeF -- maxHeight = 500, bottom = 0, text = "", - fontsize = Configuration:GetFont(1).size, + fontsize = Configuration.chatFontSize, parent = self.spHistory, selectable = true, @@ -60,10 +60,31 @@ function Console:init(channelName, sendMessageListener, noHistoryLoad, onResizeF height = 25, right = 2, text = "", - fontsize = Configuration:GetFont(1).size, + fontsize = Configuration.chatFontSize, --hint = i18n("type_here_to_chat"), } + local function onConfigurationChange(listener, key, value) + if key == "chatFontSize" then + local oldFont = self.ebInputText.font + -- Relevant settings depend on skin + local fontSettings = { + font = oldFont.font, + color = oldFont.color, + outlineColor = oldFont.outlineColor, + outline = oldFont.outline, + shadow = oldFont.shadow, + size = value, + } + self.ebInputText.font = Font:New(fontSettings) + self.ebInputText:UpdateLayout() + + self.tbHistory.font = Font:New(fontSettings) + self.tbHistory:UpdateLayout() + end + end + Configuration:AddListener("OnConfigurationChange", onConfigurationChange) + self.ebInputText.KeyPress = function(something, key, ...) if key == Spring.GetKeyCode("tab") then self:Autocomplete(self.ebInputText.text) @@ -206,23 +227,53 @@ function Console:AddMessage(message, userName, dateOverride, color, thirdPerson) txt = txt .. color end end + + local textTooltip, onTextClick if userName ~= nil then + local userStartIndex, userEndIndex if thirdPerson then + userStartIndex = #txt txt = txt .. userName .. " " + userEndIndex = userStartIndex + #userName else + userStartIndex = #txt + 4 txt = txt .. "\255\50\160\255" .. userName .. ": \255\255\255\255" + userEndIndex = userStartIndex + #userName whiteText = whiteText .. userName .. ": " if color ~= nil then txt = txt .. color end end + + textTooltip = { + { + startIndex = userStartIndex, + endIndex = userEndIndex, + tooltip = "user_chat_s_" .. userName + } + } + onTextClick = { + { + startIndex = userStartIndex, + endIndex = userEndIndex, + OnTextClick = { + function() + WG.UserHandler.GetUserDropdownMenu(userName, isBattleChat) + --Spring.Echo("Clicked on " .. userName .. ". TODO: Spawn popup.") + end + } + } + } end + + onTextClick = WG.BrowserHandler.AddClickableUrls(message, onTextClick or {}) + txt = txt .. message whiteText = whiteText .. message if self.tbHistory.text == "" then - self.tbHistory:SetText(txt) + self.tbHistory:SetText(txt, textTooltip, onTextClick) else - self.tbHistory:AddLine(txt) + self.tbHistory:AddLine(txt, textTooltip, onTextClick) end if self.channelName then diff --git a/LuaMenu/widgets/chobby/components/friend_list_window.lua b/LuaMenu/widgets/chobby/components/friend_list_window.lua index 4df2322c7..cd0cec465 100644 --- a/LuaMenu/widgets/chobby/components/friend_list_window.lua +++ b/LuaMenu/widgets/chobby/components/friend_list_window.lua @@ -22,7 +22,7 @@ function FriendListWindow:OnAddUser(userName) local userInfo = lobby:GetUser(userName) if userInfo.isFriend then local userControl = WG.UserHandler.GetNotificationUser(userName) - userControl:SetPos(20, 40, 250, 20) + userControl:SetPos(30, 30, 250, 20) Chotify:Post({ title = i18n("User got online"), body = userControl, @@ -37,7 +37,7 @@ function FriendListWindow:OnRemoveUser(userName) local userInfo = lobby:GetUser(userName) if userInfo and userInfo.isFriend then local userControl = WG.UserHandler.GetNotificationUser(userName) - userControl:SetPos(20, 40, 250, 20) + userControl:SetPos(30, 30, 250, 20) Chotify:Post({ title = i18n("User went offline"), body = userControl, diff --git a/LuaMenu/widgets/chobby/components/interface_root.lua b/LuaMenu/widgets/chobby/components/interface_root.lua index ab1128456..e0751a9f6 100644 --- a/LuaMenu/widgets/chobby/components/interface_root.lua +++ b/LuaMenu/widgets/chobby/components/interface_root.lua @@ -373,6 +373,8 @@ function GetInterfaceRoot(optionsParent, mainWindowParent, fontFunction) -- Background holder is put here to be at the back ----------------------------------- local backgroundHolder = Background() + local ingameBackgroundHolder = Background(IMAGE_TOP_BACKGROUND, {0, 0, 0, 0.5}) + ingameBackgroundHolder:Disable() ------------------------------------------------------------------- -- In-Window Handlers @@ -396,6 +398,7 @@ function GetInterfaceRoot(optionsParent, mainWindowParent, fontFunction) local queueListWindow = WG.QueueListWindow.GetControl() local battleListWindow = BattleListWindow() + local battleWatchListWindow = BattleWatchListWindow() local submenus = { { @@ -406,7 +409,8 @@ function GetInterfaceRoot(optionsParent, mainWindowParent, fontFunction) name = "multiplayer", entryCheck = WG.MultiplayerEntryPopup, tabs = { - {name = "matchMaking", control = queueListWindow}, + {name = "play", control = queueListWindow}, + {name = "watch", control = battleWatchListWindow.window}, {name = "serverList", control = battleListWindow.window}, }, cleanupFunction = CleanMultiplayerState @@ -427,7 +431,6 @@ function GetInterfaceRoot(optionsParent, mainWindowParent, fontFunction) local matchMakerStatus = WG.QueueStatusPanel.GetControl() holder_matchMaking:AddChild(matchMakerStatus) - matchMakerStatus:Hide() ------------------------------------------------------------------- -- Resizing functions @@ -648,7 +651,9 @@ function GetInterfaceRoot(optionsParent, mainWindowParent, fontFunction) end WG.SetGameInputBlock(newVisible) - backgroundHolder:SetEnabled(newVisible) + backgroundHolder:SetEnabled(newVisible and not showTopBar) + ingameBackgroundHolder:SetEnabled(newVisible and showTopBar) + if newVisible then lobbyInterfaceHolder:Show() ingameInterfaceHolder:Hide() @@ -683,13 +688,13 @@ function GetInterfaceRoot(optionsParent, mainWindowParent, fontFunction) holder_status:SetPos(nil, topOffset) if showTopBar then - backgroundHolder:SetAlpha(0.85) --buttonsHolder_image.color[4] = 0.1 --buttonsHolder_image:Invalidate() --holder_topImage.color[4] = 0.25 --holder_topImage:Invalidate() else - backgroundHolder:SetAlpha(1) + backgroundHolder:SetEnabled(true) + ingameBackgroundHolder:SetEnabled(false) --buttonsHolder_image.color[4] = 0.1 --buttonsHolder_image:Invalidate() --holder_topImage.color[4] = 0.25 diff --git a/LuaMenu/widgets/chobby/components/login_window.lua b/LuaMenu/widgets/chobby/components/login_window.lua index a395b736d..b937b0db3 100644 --- a/LuaMenu/widgets/chobby/components/login_window.lua +++ b/LuaMenu/widgets/chobby/components/login_window.lua @@ -72,35 +72,11 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) caption = i18n("connect_to_spring_server"), font = Configuration:GetFont(3), } - self.lblServerAddress = Label:New { - x = 15, - width = 170, - y = 90, - height = 35, - caption = i18n("server") .. ":", - font = Configuration:GetFont(3), - } - self.ebServerAddress = EditBox:New { - x = 135, - width = 125, - y = 85, - height = 35, - text = Configuration.serverAddress, - font = Configuration:GetFont(3), - } - self.ebServerPort = EditBox:New { - x = 265, - width = 70, - y = 85, - height = 35, - text = tostring(Configuration.serverPort), - font = Configuration:GetFont(3), - } self.lblUsername = Label:New { x = 15, width = 170, - y = 130, + y = 95, height = 35, caption = i18n("username") .. ":", font = Configuration:GetFont(3), @@ -108,7 +84,7 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) self.ebUsername = EditBox:New { x = 135, width = 200, - y = 125, + y = 90, height = 35, text = Configuration.userName, font = Configuration:GetFont(3), @@ -117,7 +93,7 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) self.lblPassword = Label:New { x = 15, width = 170, - y = 175, + y = 135, height = 35, caption = i18n("password") .. ":", font = Configuration:GetFont(3), @@ -125,7 +101,7 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) self.ebPassword = EditBox:New { x = 135, width = 200, - y = 170, + y = 130, height = 35, text = Configuration.password, passwordInput = true, @@ -140,19 +116,19 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) }, } - self.lblError = Label:New { + self.txtError = TextBox:New { x = 15, right = 15, - y = 265, + y = 215, height = 90, caption = "", - font = Configuration:GetFont(4), + fontsize = Configuration:GetFont(4).size, } self.cbAutoLogin = Checkbox:New { x = 15, width = 190, - y = 220, + y = 175, height = 35, boxalign = "right", boxsize = 15, @@ -210,7 +186,7 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) } local ww, wh = Spring.GetWindowGeometry() - local w, h = 430, 420 + local w, h = 430, 380 self.window = Window:New { x = (ww - w) / 2, y = (wh - h) / 2, @@ -222,14 +198,11 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) classname = windowClassname, children = { self.lblInstructions, - self.lblServerAddress, self.lblUsername, self.lblPassword, - self.ebServerAddress, - self.ebServerPort, self.ebUsername, self.ebPassword, - self.lblError, + self.txtError, self.cbAutoLogin, self.btnLogin, self.btnRegister, @@ -250,7 +223,7 @@ function LoginWindow:init(failFunction, cancelText, windowClassname) self.window:BringToFront() - createTabGroup({self.ebServerAddress, self.ebServerPort, self.ebUsername, self.ebPassword}) + createTabGroup({self.ebUsername, self.ebPassword}) screen0:FocusControl(self.ebUsername) -- FIXME: this should probably be moved to the lobby wrapper self.loginAttempts = 0 @@ -284,15 +257,13 @@ function LoginWindow:RemoveListeners() end function LoginWindow:tryLogin() - self.lblError:SetCaption("") + self.txtError:SetText("") username = self.ebUsername.text password = self.ebPassword.text if username == '' or password == '' then return end - Configuration.serverAddress = self.ebServerAddress.text - Configuration.serverPort = self.ebServerPort.text Configuration.userName = username Configuration.password = password @@ -308,11 +279,11 @@ function LoginWindow:tryLogin() self.onDisconnected = function(listener) lobby:RemoveListener("OnDisconnected", self.onDisconnected) - self.lblError:SetCaption(Configuration:GetErrorColor() .. "Cannot reach server:\n" .. tostring(Configuration:GetServerAddress()) .. ":" .. tostring(Configuration:GetServerPort())) + self.txtError:SetText(Configuration:GetErrorColor() .. "Cannot reach server:\n" .. tostring(Configuration:GetServerAddress()) .. ":" .. tostring(Configuration:GetServerPort())) end lobby:AddListener("OnDisconnected", self.onDisconnected) - lobby:Connect(Configuration:GetServerAddress(), Configuration:GetServerPort()) + lobby:Connect(Configuration:GetServerAddress(), Configuration:GetServerPort(), username, password, 3, nil, GetLobbyName()) else lobby:Login(username, password, 3, nil, GetLobbyName()) end @@ -321,15 +292,13 @@ function LoginWindow:tryLogin() end function LoginWindow:tryRegister() - self.lblError:SetCaption("") + self.txtError:SetText("") username = self.ebUsername.text password = self.ebPassword.text if username == '' or password == '' then return end - Configuration.serverAddress = self.ebServerAddress.text - Configuration.serverPort = self.ebServerPort.text if not lobby.connected or self.loginAttempts >= 3 then self.loginAttempts = 0 @@ -341,7 +310,7 @@ function LoginWindow:tryRegister() end lobby:AddListener("OnConnect", self.onConnectRegister) - lobby:Connect(Configuration:GetServerAddress(), Configuration:GetServerPort()) + lobby:Connect(Configuration:GetServerAddress(), Configuration:GetServerPort(), username, password, 3, nil, GetLobbyName()) else lobby:Register(username, password, "name@email.com") end @@ -352,11 +321,11 @@ end function LoginWindow:OnRegister() lobby:Register(username, password, "name@email.com") lobby:AddListener("OnRegistrationAccepted", function(listener) - self.lblError:SetCaption(Configuration:GetSuccessColor() .. "Registered!") + self.txtError:SetText(Configuration:GetSuccessColor() .. "Registered!") --lobby:RemoveListener("OnRegistrationAccepted", listener) end) lobby:AddListener("OnRegistrationDenied", function(listener, err) - self.lblError:SetCaption(Configuration:GetErrorColor() .. (err or "Unknown Error")) + self.txtError:SetText(Configuration:GetErrorColor() .. (err or "Unknown Error")) --lobby:RemoveListener("OnRegistrationDenied", listener) end) @@ -364,10 +333,10 @@ end function LoginWindow:OnConnected() - self.lblError:SetCaption(Configuration:GetPartialColor() .. i18n("connecting")) + self.txtError:SetText(Configuration:GetPartialColor() .. i18n("connecting")) self.onDenied = function(listener, reason) - self.lblError:SetCaption(Configuration:GetErrorColor() .. (reason or "Denied, unknown reason")) + self.txtError:SetText(Configuration:GetErrorColor() .. (reason or "Denied, unknown reason")) end self.onAccepted = function(listener) @@ -404,8 +373,6 @@ function LoginWindow:OnConnected() lobby:RemoveListener("OnAgreement", self.onAgreement) end lobby:AddListener("OnAgreementEnd", self.onAgreementEnd) - - lobby:Login(username, password, 3, nil, GetLobbyName()) end function LoginWindow:createAgreementWindow() diff --git a/LuaMenu/widgets/chobby/core.lua b/LuaMenu/widgets/chobby/core.lua index 6afb1ddb5..5c0e3cc43 100644 --- a/LuaMenu/widgets/chobby/core.lua +++ b/LuaMenu/widgets/chobby/core.lua @@ -20,6 +20,7 @@ local includes = { -- battle "components/battle/battle_list_window.lua", + "components/battle/battle_watch_list_window.lua", -- queue "components/queue/queue_list_window.lua", "components/queue/queue_window.lua", @@ -62,6 +63,10 @@ function Chobby:_Initialize() WG.Delay(function() lobby:AddListener("OnJoinBattle", function(listener, battleID) + local battle = lobby:GetBattle(battleID) + if not WG.Chobby.Configuration.showMatchMakerBattles and battle and battle.isMatchMaker then + return + end Spring.Echo("Showing battle with ID", battleID) WG.BattleRoomWindow.ShowMultiplayerBattleRoom(battleID) end @@ -80,6 +85,19 @@ function Chobby:_Initialize() ) end, 0.001) end) + self:WrapCall(function() + WG.Delay(function() + lobby:AddListener("OnConnect", + function(listener, _, engineName) + if engineName and not WG.Chobby.Configuration:IsValidEngineVersion(engineName) then + WG.Chobby.InformationPopup("Wrong Spring engine version. The required version is '" .. engineName .. "', your version is '" .. Game.version .. "'.", 420, 260) + end + end + ) + end, 0.001) + end) + + end function Chobby:GetRegisteredComponents() diff --git a/LuaMenu/widgets/chobby/i18n/chililobby.lua b/LuaMenu/widgets/chobby/i18n/chililobby.lua index 13aca15c9..f5111e5b9 100644 --- a/LuaMenu/widgets/chobby/i18n/chililobby.lua +++ b/LuaMenu/widgets/chobby/i18n/chililobby.lua @@ -15,7 +15,7 @@ return { -- console type_here_to_chat = "Type here to chat. Press enter to send.", -- login_window - connect_to_spring_server = 'Connect to the Spring lobby server', + connect_to_spring_server = 'Connect to the lobby server', username = 'Username', password = 'Password', login_noun = 'Login', @@ -67,6 +67,7 @@ return { play_custom_multiplayer_game = "Play a custom multiplayer game", queues = "Queues", custom_games = "Custom games", + spectate_running_games = "Select a game to watch", download = "Download", downloads = "Downloads", friend_list = "Friend list", @@ -83,6 +84,7 @@ return { spectate = "Spectate", spectating = "Spectating", play = "Play", + watch = "Watch", playing = "Playing", pick_map = "Change Map", add_team = "Add Team", diff --git a/LuaMenu/widgets/gui_battle_room_window.lua b/LuaMenu/widgets/gui_battle_room_window.lua index 07b5bc49c..81cb554c2 100644 --- a/LuaMenu/widgets/gui_battle_room_window.lua +++ b/LuaMenu/widgets/gui_battle_room_window.lua @@ -18,27 +18,12 @@ end -- Local variables -- Chili controls -local window -local imHaveMap, imHaveGame -local lblHaveMap, lblHaveGame - --- Listeners, needed here so they can be deregistered -local onBattleClosed -local onLeftBattle_counter -local onJoinedBattle -local onSaidBattle -local onSaidBattleEx -local onUpdateUserTeamStatus -local onUpdateUserTeamStatusSelf -local onLeftBattle -local onRemoveAi -local onVoteUpdate -local onVoteEnd +local mainWindow -- Globals local battleLobby local wrapperControl -local parentTabPanel +local mainWindowFunctions local singleplayerWrapper local multiplayerWrapper @@ -69,26 +54,10 @@ local function UpdateArchiveStatus(updateSync) local haveGame = VFS.HasArchive(battle.gameName) local haveMap = VFS.HasArchive(battle.mapName) - if imHaveGame then - if haveGame then - imHaveGame.file = IMG_READY - lblHaveGame:SetCaption(i18n("have_game")) - else - imHaveGame.file = IMG_UNREADY - lblHaveGame:SetCaption(i18n("dont_have_game")) - end - imHaveGame:Invalidate() - end - - if imHaveMap then - if haveMap then - imHaveMap.file = IMG_READY - lblHaveMap:SetCaption(i18n("have_map")) - else - imHaveMap.file = IMG_UNREADY - lblHaveMap:SetCaption(i18n("dont_have_map")) - end - imHaveMap:Invalidate() + if mainWindowFunctions and mainWindowFunctions.GetInfoHandler() then + local infoHandler = mainWindowFunctions.GetInfoHandler() + infoHandler.SetHaveGame(haveGame) + infoHandler.SetHaveMap(haveMap) end haveMapAndGame = (haveGame and haveMap) @@ -317,7 +286,6 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs leftOffset = leftOffset + 38 WG.ModoptionsPanel.LoadModotpions(battle.gameName, battleLobby) - local btnModoptions = Button:New { x = 5, y = leftOffset, @@ -334,21 +302,7 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs } leftOffset = leftOffset + 38 - --local lblNumberOfPlayers - --if battleLobby.name ~= "singleplayer" then - -- lblNumberOfPlayers = Label:New { - -- x = 8, - -- y = leftOffset, - -- width = 200, - -- height = 30, - -- caption = "", - -- font = WG.Chobby.Configuration:GetFont(1), - -- parent = leftInfo, - -- } - -- leftOffset = leftOffset + 25 - --end - - imHaveGame = Image:New { + local imHaveGame = Image:New { x = 8, y = leftOffset, width = 15, @@ -356,7 +310,7 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs file = IMG_READY, parent = leftInfo, } - lblHaveGame = Label:New { + local lblHaveGame = Label:New { x = 28, y = leftOffset, caption = "", @@ -365,7 +319,7 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs } leftOffset = leftOffset + 25 - imHaveMap = Image:New { + local imHaveMap = Image:New { x = 8, y = leftOffset, width = 15, @@ -373,7 +327,7 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs file = IMG_READY, parent = leftInfo, } - lblHaveMap = Label:New { + local lblHaveMap = Label:New { x = 28, y = leftOffset, caption = "", @@ -411,26 +365,38 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs modoptionsHolder.children[1]:Hide() end - --downloader.lblDownload.OnHide = downloader.lblDownload.OnHide or {} - --downloader.lblDownload.OnHide[#downloader.lblDownload.OnHide + 1] = function () - -- if not modoptionsHolder.visible then - -- modoptionsHolder:Show() - -- end - --end - -- - --downloader.lblDownload.OnShow = downloader.lblDownload.OnShow or {} - --downloader.lblDownload.OnShow[#downloader.lblDownload.OnShow + 1] = function () - -- if modoptionsHolder.visible then - -- modoptionsHolder:Hide() - -- end - --end - leftOffset = leftOffset + 120 + -- Example downloads --MaybeDownloadArchive("Titan-v2", "map") --MaybeDownloadArchive("tinyskirmishredux1.1", "map") - onUpdateUserTeamStatusSelf = function(listener, userName, allyNumber, isSpectator) + local externalFunctions = {} + + function externalFunctions.SetHaveGame(newHaveGame) + if newHaveGame then + imHaveGame.file = IMG_READY + lblHaveGame:SetCaption(i18n("have_game")) + else + imHaveGame.file = IMG_UNREADY + lblHaveGame:SetCaption(i18n("dont_have_game")) + end + imHaveGame:Invalidate() + end + + function externalFunctions.SetHaveMap(newHaveMap) + if newHaveMap then + imHaveMap.file = IMG_READY + lblHaveMap:SetCaption(i18n("have_map")) + else + imHaveMap.file = IMG_UNREADY + lblHaveMap:SetCaption(i18n("dont_have_map")) + end + imHaveMap:Invalidate() + end + + -- Lobby interface + function externalFunctions.UpdateUserTeamStatus(userName, allyNumber, isSpectator) if userName == myUserName then if isSpectator then ButtonUtilities.SetButtonDeselected(btnPlay) @@ -445,9 +411,8 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs end end end - battleLobby:AddListener("OnUpdateUserTeamStatus", onUpdateUserTeamStatusSelf) - onBattleIngameUpdate = function(listener, updatedBattleID, isRunning) + function externalFunctions.BattleIngameUpdate(updatedBattleID, isRunning) if battleID == updatedBattleID then if isRunning then btnStartBattle:SetCaption(i18n("rejoin")) @@ -456,11 +421,10 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs end end end - battleLobby:AddListener("OnBattleIngameUpdate", onBattleIngameUpdate) - onBattleIngameUpdate(nil, battleID, battle.isRunning) + externalFunctions.BattleIngameUpdate(battleID, battle.isRunning) - onUpdateBattleInfo = function(listener, updatedBattleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode) + function externalFunctions.UpdateBattleInfo(updatedBattleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode) if battleID ~= updatedBattleID then return end @@ -485,48 +449,37 @@ local function SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, myUs }) end end - battleLobby:AddListener("OnUpdateBattleInfo", onUpdateBattleInfo) - --local UpdatePlayers = function(battleID) - -- if lblNumberOfPlayers then - -- lblNumberOfPlayers:SetCaption(i18n("players") .. ": " .. tostring(#battle.users - battle.spectatorCount) .. "/" .. tostring(battle.maxPlayers)) - -- end - --end - - onLeftBattle_counter = function(listener, leftBattleID, userName) + function externalFunctions.LeftBattle(leftBattleID, userName) if battleID ~= leftBattleID then return end if battleLobby:GetMyUserName() == userName then - window:Dispose() - window = nil + mainWindow:Dispose() + mainWindow = nil if wrapperControl and wrapperControl.visible and wrapperControl.parent then wrapperControl:Hide() end - else - --UpdatePlayers(battleID) end end - battleLobby:AddListener("OnLeftBattle", onLeftBattle_counter) - onJoinedBattle = function(listener, joinedBattleId, userName) + function externalFunctions.JoinedBattle(joinedBattleId, userName) if battleID ~= joinedBattleId then return end - --UpdatePlayers(battleID) end - battleLobby:AddListener("OnJoinedBattle", onJoinedBattle) - - --UpdatePlayers(battleID) MaybeDownloadGame(battle) MaybeDownloadMap(battle) UpdateArchiveStatus(true) + + return externalFunctions end local function AddTeamButtons(parent, offX, joinFunc, aiFunc, unjoinable, disallowBots) if not disallowBots then local addAiButton = Button:New { + name = "addAiButton", x = offX, y = 4, height = 24, @@ -556,6 +509,8 @@ end local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) local SPACING = 22 + local disallowCustomTeams = battle.disallowCustomTeams + local disallowBots = battle.disallowBots local mainScrollPanel = ScrollPanel:New { x = 0, @@ -665,7 +620,7 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) parentStack = spectatorStackPanel parentScroll = spectatorScrollPanel else - if battle.disallowCustomTeams then + if disallowCustomTeams then if teamIndex == 0 then humanName = "Players" else @@ -711,8 +666,8 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) function() WG.Chobby.AiListWindow(battleLobby, battle.gameName, teamIndex) end, - battle.disallowCustomTeams and teamIndex ~= 0, - (battle.disallowBots or battle.disallowCustomTeams) and teamIndex == 0 + disallowCustomTeams and teamIndex ~= 0, + (disallowBots or disallowCustomTeams) and teamIndex ~= 1 ) end local teamStack = Control:New { @@ -731,6 +686,50 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) end local teamData = {} + + function teamData.UpdateBattleMode() + local addAiButton = teamHolder:GetChildByName("addAiButton") + if addAiButton then + teamHolder:RemoveChild(addAiButton) + addAiButton:Dispose() + end + local joinTeamButton = teamHolder:GetChildByName("joinTeamButton") + if joinTeamButton then + teamHolder:RemoveChild(joinTeamButton) + joinTeamButton:Dispose() + end + + if teamIndex ~= -1 then + AddTeamButtons( + teamHolder, + 88, + function() + battleLobby:SetBattleStatus({ + allyNumber = teamIndex, + isSpectator = false, + }) + end, + function() + WG.Chobby.AiListWindow(battleLobby, battle.gameName, teamIndex) + end, + disallowCustomTeams and teamIndex ~= 0, + (disallowBots or disallowCustomTeams) and teamIndex ~= 1 + ) + + if disallowCustomTeams then + if teamIndex == 0 then + humanName = "Players" + elseif teamIndex == 1 then + humanName = "Bots" + else + humanName = "Invalid" + end + else + humanName = "Team " .. (teamIndex + 1) + end + end + label:SetCaption(humanName) + end function teamData.AddPlayer(name) local playerData = GetPlayerData(name) @@ -755,7 +754,44 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) teamHolder:Invalidate() end end + + function teamData.RemoveTeam() + if teamIndex < emptyTeamIndex then + emptyTeamIndex = teamIndex + end + team[teamIndex] = nil + parentStack:RemoveChild(parentStack:GetChildByName(teamIndex)) + teamHolder:Dispose() + end + + function teamData.CheckRemoval() + if teamStack:IsEmpty() and teamIndex ~= -1 then + local removeHolder = false + + if disallowCustomTeams then + if teamIndex > 1 then + teamData.RemoveTeam() + return true + elseif disallowBots and teamIndex > 0 then + teamData.RemoveTeam() + return true + end + else + if teamIndex > 1 then + local maxTeam = 0 + for teamID,_ in pairs(team) do + maxTeam = math.max(teamID, maxTeam) + end + if teamIndex == maxTeam then + teamData.RemoveTeam() + return true + end + end + end + end + end + function teamData.RemovePlayer(name) local playerData = GetPlayerData(name) if playerData.team ~= teamIndex then @@ -784,27 +820,11 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) end end - if teamStack:IsEmpty() and teamIndex ~= -1 then - local teamCount = 0 - for _,_ in pairs(team) do - teamCount = teamCount + 1 - end - -- Don't leave us with less than two teams (spectator is a team too) - if teamCount > 3 then - if teamIndex < emptyTeamIndex then - emptyTeamIndex = teamIndex - end - - team[teamIndex] = nil - parentStack:RemoveChild(parentStack:GetChildByName(teamIndex)) - teamHolder:Dispose() - else - teamHolder:Invalidate() - end - else + + if not teamData.CheckRemoval() then teamHolder:Invalidate() + PositionChildren(parentStack, parentScroll.height) end - PositionChildren(parentStack, parentScroll.height) end team[teamIndex] = teamData @@ -828,7 +848,7 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) GetTeam(-1) -- Make Spectator heading appear GetTeam(0) -- Always show two teams in custom battles - if not (battle.disallowCustomTeams and battle.disallowBots) then + if not (disallowCustomTeams and disallowBots) then GetTeam(1) end @@ -848,7 +868,23 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) end } - onUpdateUserTeamStatus = function(listener, userName, allyNumber, isSpectator) + local externalFunctions = {} + + function externalFunctions.UpdateBattleMode(newDisallowCustomTeams, newDisallowBots) + disallowCustomTeams = newDisallowCustomTeams + disallowBots = newDisallowBots + + if not (disallowCustomTeams and disallowBots) then + GetTeam(1) + end + for teamIndex, teamData in pairs(team) do + if not teamData.CheckRemoval() then + teamData.UpdateBattleMode() + end + end + end + + function externalFunctions.UpdateUserTeamStatus(userName, allyNumber, isSpectator) if isSpectator then allyNumber = -1 end @@ -859,19 +895,18 @@ local function SetupPlayerPanel(playerParent, spectatorParent, battle, battleID) RemovePlayerFromTeam(userName) AddPlayerToTeam(allyNumber, userName) end - battleLobby:AddListener("OnUpdateUserTeamStatus", onUpdateUserTeamStatus) - onLeftBattle = function(listener, leftBattleID, userName) + function externalFunctions.LeftBattle(leftBattleID, userName) if leftBattleID == battleID then RemovePlayerFromTeam(userName) end end - battleLobby:AddListener("OnLeftBattle", onLeftBattle) - onRemoveAi = function(listener, botName) + function externalFunctions.RemoveAi(botName) RemovePlayerFromTeam(botName) end - battleLobby:AddListener("OnRemoveAi", onRemoveAi) + + return externalFunctions end local function SetupVotePanel(votePanel, battle, battleID) @@ -1000,8 +1035,10 @@ local function SetupVotePanel(votePanel, battle, battleID) voteResultLabel:Hide() end end + + local externalFunctions = {} - onVoteUpdate = function(listener, message, yesVotes, noVotes, votesNeeded) + function externalFunctions.VoteUpdate(message, yesVotes, noVotes, votesNeeded) voteName:SetCaption(message) voteCountLabel:SetCaption(yesVotes .. "/" .. votesNeeded) voteProgress:SetValue(100 * yesVotes / votesNeeded) @@ -1010,9 +1047,8 @@ local function SetupVotePanel(votePanel, battle, battleID) end HideVoteResult() end - battleLobby:AddListener("OnVoteUpdate", onVoteUpdate) - onVoteEnd = function(listener, message, success) + function externalFunctions.VoteEnd(message, success) if activePanel.visible then activePanel:Hide() end @@ -1027,7 +1063,8 @@ local function SetupVotePanel(votePanel, battle, battleID) WG.Delay(HideVoteResult, 5) end - battleLobby:AddListener("OnVoteEnd", onVoteEnd) + + return externalFunctions end local function InitializeControls(battleID, oldLobby, topPoportion) @@ -1038,37 +1075,23 @@ local function InitializeControls(battleID, oldLobby, topPoportion) return false end + if not WG.Chobby.Configuration.showMatchMakerBattles and battle.isMatchMaker then + return + end + local EXTERNAL_PAD_VERT = 10 local EXTERNAL_PAD_HOR = 15 local INTERNAL_PAD = 2 local BOTTOM_SPACING = 50 - window = Control:New { + mainWindow = Control:New { x = 0, y = 0, width = "100%", height = "100%", resizable = false, padding = {0, 0, 0, 0}, - OnDispose = { - function() - emptyTeamIndex = 0 - - oldLobby:RemoveListener("OnBattleClosed", onBattleClosed) - oldLobby:RemoveListener("OnLeftBattle", onLeftBattle_counter) - oldLobby:RemoveListener("OnJoinedBattle", onJoinedBattle) - oldLobby:RemoveListener("OnSaidBattle", onSaidBattle) - oldLobby:RemoveListener("OnSaidBattleEx", onSaidBattleEx) - oldLobby:RemoveListener("OnUpdateUserTeamStatus", onUpdateUserTeamStatus) - oldLobby:RemoveListener("OnUpdateUserTeamStatus", onUpdateUserTeamStatusSelf) - oldLobby:RemoveListener("OnLeftBattle", onLeftBattle) - oldLobby:RemoveListener("OnRemoveAi", onRemoveAi) - oldLobby:RemoveListener("OnBattleIngameUpdate", onBattleIngameUpdate) - oldLobby:RemoveListener("OnVoteUpdate", onVoteUpdate) - oldLobby:RemoveListener("OnVoteEnd", onVoteEnd) - end - }, } local subPanel = Control:New { @@ -1077,7 +1100,7 @@ local function InitializeControls(battleID, oldLobby, topPoportion) right = 0, bottom = 0, padding = {0, 0, 0, 0}, - parent = window, + parent = mainWindow, } local topPanel = Control:New { @@ -1117,7 +1140,7 @@ local function InitializeControls(battleID, oldLobby, topPoportion) parent = bottomPanel, } - SetupPlayerPanel(playerPanel, spectatorPanel, battle, battleID) + local playerHandler = SetupPlayerPanel(playerPanel, spectatorPanel, battle, battleID) local votePanel = Control:New { x = 0, @@ -1128,8 +1151,8 @@ local function InitializeControls(battleID, oldLobby, topPoportion) parent = topPanel, } - SetupVotePanel(votePanel) - + local votePanel = SetupVotePanel(votePanel) + local leftInfo = Control:New { x = "48%", y = 0, @@ -1148,7 +1171,7 @@ local function InitializeControls(battleID, oldLobby, topPoportion) parent = topPanel, } - SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, battleLobby:GetMyUserName()) + local infoHandler = SetupInfoButtonsPanel(leftInfo, rightInfo, battle, battleID, battleLobby:GetMyUserName()) local btnQuitBattle = Button:New { right = 7, @@ -1163,12 +1186,13 @@ local function InitializeControls(battleID, oldLobby, topPoportion) battleLobby:LeaveBattle() end }, - parent = window, + parent = mainWindow, } - local battleTitle = i18n("battle") .. ": " .. tostring(battle.title) + local rawBattleTitle = tostring(battle.title) + local battleTitle = i18n("battle") .. ": " .. rawBattleTitle if battle.battleMode then - battleTitle = i18n(WG.Chobby.Configuration.battleTypeToName[battle.battleMode]) .. " " .. battleTitle + battleTitle = i18n(WG.Chobby.Configuration.battleTypeToName[battle.battleMode]) .. ": " .. battleTitle end local lblBattleTitle = Label:New { @@ -1178,7 +1202,7 @@ local function InitializeControls(battleID, oldLobby, topPoportion) height = 30, font = WG.Chobby.Configuration:GetFont(3), caption = "", - parent = window, + parent = mainWindow, OnResize = { function (obj, xSize, ySize) obj:SetCaption(StringUtilities.GetTruncatedStringWithDotDot(battleTitle, obj.font, obj.width)) @@ -1193,7 +1217,7 @@ local function InitializeControls(battleID, oldLobby, topPoportion) battleLobby:SayBattle(message) end end - local battleRoomConsole = WG.Chobby.Console("Battleroom Chat", MessageListener, true) + local battleRoomConsole = WG.Chobby.Console("Battleroom Chat", MessageListener, true, nil, true) local chatPanel = Control:New { x = 0, @@ -1215,33 +1239,113 @@ local function InitializeControls(battleID, oldLobby, topPoportion) local CHAT_MENTION = "\255\255\0\0" local CHAT_ME = WG.Chobby.Configuration.meColor + + -- External Functions + local externalFunctions = {} + + function externalFunctions.OnBattleClosed(listener, closedBattleID) + if battleID == closedBattleID then + mainWindow:Dispose() + mainWindow = nil + if wrapperControl and wrapperControl.visible and wrapperControl.parent then + wrapperControl:Hide() + end + end + end + + function externalFunctions.GetInfoHandler() + return infoHandler + end + + -- Lobby interface + local function OnUpdateUserTeamStatus(listener, userName, allyNumber, isSpectator) + infoHandler.UpdateUserTeamStatus(userName, allyNumber, isSpectator) + playerHandler.UpdateUserTeamStatus(userName, allyNumber, isSpectator) + end + + local function OnBattleIngameUpdate(listener, updatedBattleID, isRunning) + infoHandler.BattleIngameUpdate(updatedBattleID, isRunning) + end - local onSaidBattle = function(listener, userName, message) - local iAmMentioned = (string.find(message, WG.LibLobby.lobby:GetMyUserName()) and userName ~= WG.LibLobby.lobby:GetMyUserName()) + local function OnUpdateBattleInfo(listener, updatedBattleID, spectatorCount, locked, mapHash, mapName, + engineVersion, runningSince, gameName, battleMode, disallowCustomTeams, disallowBots, isMatchMaker) + if battleMode then + battleTitle = i18n(WG.Chobby.Configuration.battleTypeToName[battleMode]) .. ": " .. rawBattleTitle + lblBattleTitle:SetCaption(battleTitle) + + playerHandler.UpdateBattleMode(disallowCustomTeams, disallowBots) + end + + infoHandler.UpdateBattleInfo(updatedBattleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode) + end + + local function OnLeftBattle(listener, leftBattleID, userName) + infoHandler.LeftBattle(leftBattleID, userName) + playerHandler.LeftBattle(leftBattleID, userName) + end + + local function OnJoinedBattle(listener, joinedBattleId, userName) + infoHandler.JoinedBattle(joinedBattleId, userName) + end + + local function OnRemoveAi(listener, botName) + playerHandler.RemoveAi(botName) + end + + local function OnVoteUpdate(listener, message, yesVotes, noVotes, votesNeeded) + votePanel.VoteUpdate(message, yesVotes, noVotes, votesNeeded) + end + + local function OnVoteEnd(listener, message, success) + votePanel.VoteEnd(message, success) + end + + local function OnSaidBattle(listener, userName, message) + local myUserName = battleLobby:GetMyUserName() + local iAmMentioned = myUserName and userName ~= myUserName and string.find(message, myUserName) local chatColour = (iAmMentioned and CHAT_MENTION) or nil battleRoomConsole:AddMessage(message, userName, false, chatColour, false) end - battleLobby:AddListener("OnSaidBattle", onSaidBattle) - local onSaidBattleEx = function(listener, userName, message) - local iAmMentioned = (string.find(message, WG.LibLobby.lobby:GetMyUserName()) and userName ~= WG.LibLobby.lobby:GetMyUserName()) + local function OnSaidBattleEx(listener, userName, message) + local myUserName = battleLobby:GetMyUserName() + local iAmMentioned = myUserName and userName ~= myUserName and string.find(message, myUserName) local chatColour = (iAmMentioned and CHAT_MENTION) or CHAT_ME battleRoomConsole:AddMessage(message, userName, false, chatColour, true) end - battleLobby:AddListener("OnSaidBattleEx", onSaidBattleEx) - onBattleClosed = function(listener, closedBattleID, ... ) - if battleID == closedBattleID then - window:Dispose() - window = nil - if wrapperControl and wrapperControl.visible and wrapperControl.parent then - wrapperControl:Hide() - end - end + battleLobby:AddListener("OnUpdateUserTeamStatus", OnUpdateUserTeamStatus) + battleLobby:AddListener("OnBattleIngameUpdate", OnBattleIngameUpdate) + battleLobby:AddListener("OnUpdateBattleInfo", OnUpdateBattleInfo) + battleLobby:AddListener("OnLeftBattle", OnLeftBattle) + battleLobby:AddListener("OnJoinedBattle", OnJoinedBattle) + battleLobby:AddListener("OnRemoveAi", OnRemoveAi) + battleLobby:AddListener("OnVoteUpdate", OnVoteUpdate) + battleLobby:AddListener("OnVoteEnd", OnVoteEnd) + battleLobby:AddListener("OnSaidBattle", OnSaidBattle) + battleLobby:AddListener("OnSaidBattleEx", OnSaidBattleEx) + battleLobby:AddListener("OnBattleClosed", externalFunctions.OnBattleClosed) + + local function OnDisposeFunction() + emptyTeamIndex = 0 + + oldLobby:RemoveListener("OnUpdateUserTeamStatus", OnUpdateUserTeamStatus) + oldLobby:RemoveListener("OnBattleIngameUpdate", OnBattleIngameUpdate) + oldLobby:RemoveListener("OnUpdateBattleInfo", OnUpdateBattleInfo) + oldLobby:RemoveListener("OnLeftBattle", OnLeftBattle) + oldLobby:RemoveListener("OnJoinedBattle", OnJoinedBattle) + oldLobby:RemoveListener("OnRemoveAi", OnRemoveAi) + oldLobby:RemoveListener("OnVoteUpdate", OnVoteUpdate) + oldLobby:RemoveListener("OnVoteEnd", OnVoteEnd) + oldLobby:RemoveListener("OnSaidBattle", OnSaidBattle) + oldLobby:RemoveListener("OnSaidBattleEx", OnSaidBattleEx) + oldLobby:RemoveListener("OnBattleClosed", externalFunctions.OnBattleClosed) end - battleLobby:AddListener("OnBattleClosed", onBattleClosed) - - return window + + mainWindow.OnDispose = mainWindow.OnDispose or {} + mainWindow.OnDispose[#mainWindow.OnDispose + 1] = OnDisposeFunction + + return mainWindow, externalFunctions end -------------------------------------------------------------------------------- @@ -1251,18 +1355,25 @@ end local BattleRoomWindow = {} function BattleRoomWindow.ShowMultiplayerBattleRoom(battleID) - if window then - window:Dispose() - window = nil + + if mainWindow then + mainWindow:Dispose() + mainWindow = nil end + + local tabPanel = WG.Chobby.interfaceRoot.GetBattleStatusWindowHandler() + if multiplayerWrapper then + tabPanel.RemoveTab("myBattle", true) + WG.Chobby.interfaceRoot.UpdateMatchMakingHolderPosition() + multiplayerWrapper:Dispose() + multiplayerWrapper = nil + end + if singleplayerWrapper then singleplayerWrapper = nil end - local tabPanel = WG.Chobby.interfaceRoot.GetBattleStatusWindowHandler() - parentTabPanel = tabPanel - battleLobby = WG.LibLobby.lobby multiplayerWrapper = Control:New { @@ -1278,7 +1389,8 @@ function BattleRoomWindow.ShowMultiplayerBattleRoom(battleID) if obj:IsEmpty() then wrapperControl = obj - local battleWindow = InitializeControls(battleID, battleLobby, 55) + local battleWindow, functions = InitializeControls(battleID, battleLobby, 55) + mainWindowFunctions = functions if battleWindow then obj:AddChild(battleWindow) end @@ -1324,13 +1436,13 @@ function BattleRoomWindow.GetSingleplayerControl() tabPanel.RemoveTab("myBattle", true) WG.Chobby.interfaceRoot.UpdateMatchMakingHolderPosition() - if window then - window:Dispose() - window = nil + if mainWindow then + mainWindow:Dispose() + mainWindow = nil end WG.LibLobby.lobby:LeaveBattle() multiplayerWrapper = nil - elseif window then + elseif mainWindow then return end @@ -1341,14 +1453,13 @@ function BattleRoomWindow.GetSingleplayerControl() defaultMap = singleplayerDefault.map end - parentTabPanel = nil - battleLobby = WG.LibLobby.localLobby battleLobby:SetBattleState(lobby:GetMyUserName() or "Player", singleplayerGame, defaultMap, "Skirmish Battle") wrapperControl = obj - local battleWindow = InitializeControls(1, battleLobby, 70) + local battleWindow, functions = InitializeControls(1, battleLobby, 70) + mainWindowFunctions = functions if not battleWindow then return end @@ -1388,7 +1499,7 @@ function BattleRoomWindow.SetSingleplayerGame(ToggleShowFunc, battleroomObj, tab if config.singleplayer_mode == 1 then WG.Chobby.GameListWindow(SetGameFail, SetGameSucess) elseif config.singleplayer_mode == 2 then - singleplayerGame = "Zero-K v1.4.9.2" + singleplayerGame = "Zero-K v1.4.9.3" ToggleShowFunc(battleroomObj, tabData) elseif config.singleplayer_mode == 3 then singleplayerGame = "Zero-K $VERSION" @@ -1406,16 +1517,18 @@ function BattleRoomWindow.LeaveBattle(onlyMultiplayer, onlySingleplayer) end if onlySingleplayer and battleLobby.name == "singleplayer" then - if window then - window:Dispose() - window = nil + if mainWindow then + mainWindow:Dispose() + mainWindow = nil end return end battleLobby:LeaveBattle() - onBattleClosed(_, battleLobby:GetMyBattleID()) - + if mainWindowFunctions then + mainWindowFunctions.OnBattleClosed(_, battleLobby:GetMyBattleID()) + end + local tabPanel = WG.Chobby.interfaceRoot.GetBattleStatusWindowHandler() tabPanel.RemoveTab("myBattle", true) WG.Chobby.interfaceRoot.UpdateMatchMakingHolderPosition() diff --git a/LuaMenu/widgets/gui_chili_lobby.lua b/LuaMenu/widgets/gui_chili_lobby.lua index c0990779d..e4e2d99cb 100644 --- a/LuaMenu/widgets/gui_chili_lobby.lua +++ b/LuaMenu/widgets/gui_chili_lobby.lua @@ -43,11 +43,6 @@ function widget:Update() end end -function widget:GamePreload() - interfaceRoot.SetIngame(Spring.GetGameName() ~= "") - lobby:SetIngameStatus(true) -end - local ignoreFirstCall = true function widget:ActivateMenu() if ignoreFirstCall then @@ -70,8 +65,6 @@ function widget:Initialize() return end - Spring.SetWMCaption("Ingame Lobby", "IngameLobby") - Chobby = VFS.Include(CHOBBY_DIR .. "core.lua", nil) WG.Chobby = Chobby @@ -82,6 +75,29 @@ function widget:Initialize() lobbyInterfaceHolder = interfaceRoot.GetLobbyInterfaceHolder() Chobby.lobbyInterfaceHolder = lobbyInterfaceHolder Chobby.interfaceRoot = interfaceRoot + + Spring.SetWMCaption("Ingame Lobby", "IngameLobby") + local taskbarIcon = Chobby.Configuration:GetTaskbarIcon() + if taskbarIcon then + Spring.SetWMIcon(taskbarIcon) + end + + local function OnBattleAboutToStart() + interfaceRoot.SetIngame(true) + lobby:SetIngameStatus(true) + end + WG.LibLobby.localLobby:AddListener("OnBattleAboutToStart", OnBattleAboutToStart) + WG.LibLobby.lobby:AddListener("OnBattleAboutToStart", OnBattleAboutToStart) + + local function onConfigurationChange(listener, key, value) + if key == "singleplayer_mode" then + local taskbarIcon = Chobby.Configuration:GetTaskbarIcon() + if taskbarIcon then + Spring.SetWMIcon(taskbarIcon) + end + end + end + Chobby.Configuration:AddListener("OnConfigurationChange", onConfigurationChange) end function widget:KeyPress(key, mods, isRepeat, label, unicode) @@ -91,6 +107,7 @@ function widget:KeyPress(key, mods, isRepeat, label, unicode) end function widget:Shutdown() + Spring.Echo("Chobby Shutdown") WG.Chobby = nil end diff --git a/LuaMenu/widgets/gui_queue_list_window.lua b/LuaMenu/widgets/gui_queue_list_window.lua index f241613c1..553b501cf 100644 --- a/LuaMenu/widgets/gui_queue_list_window.lua +++ b/LuaMenu/widgets/gui_queue_list_window.lua @@ -17,8 +17,8 @@ end -------------------------------------------------------------------------------- -- Variables -local requiredMaps = {} -local requiredMapCount = 0 +local requiredResources = {} +local requiredResourceCount = 0 local panelInterface @@ -26,7 +26,7 @@ local panelInterface -------------------------------------------------------------------------------- -- Initialization -local function MakeQueueControl(parentControl, queueName, queueDescription) +local function MakeQueueControl(parentControl, queueName, queueDescription, players, waiting) local Configuration = WG.Chobby.Configuration local btnLeave, btnJoin @@ -41,8 +41,8 @@ local function MakeQueueControl(parentControl, queueName, queueDescription) classname = "option_button", OnClick = { function(obj) - if requiredMapCount ~= 0 then - WG.Chobby.InformationPopup("Map downloads must complete before you are able to join matchmaking.") + if requiredResourceCount ~= 0 then + WG.Chobby.InformationPopup("All required maps and games must be downloaded before you can join matchmaking.") return end @@ -75,9 +75,9 @@ local function MakeQueueControl(parentControl, queueName, queueDescription) local lblTitle = TextBox:New { x = 90, - y = 10, + y = 15, width = 120, - bottom = 0, + height = 33, fontsize = Configuration:GetFont(3).size, text = queueName, parent = parentControl @@ -85,16 +85,40 @@ local function MakeQueueControl(parentControl, queueName, queueDescription) local lblDescription = TextBox:New { x = 180, - y = 15, + y = 8, width = 120, + height = 22, right = 5, align = "bottom", - bottom = 0, fontsize = Configuration:GetFont(1).size, text = queueDescription, parent = parentControl } + local lblPlayers = TextBox:New { + x = 180, + y = 30, + width = 120, + height = 22, + right = 5, + align = "bottom", + fontsize = Configuration:GetFont(1).size, + text = "Playing: " .. players, + parent = parentControl + } + + local lblWaiting = TextBox:New { + x = 280, + y = 30, + width = 120, + height = 22, + right = 5, + align = "bottom", + fontsize = Configuration:GetFont(1).size, + text = "Waiting: " .. waiting, + parent = parentControl + } + local externalFunctionsAndData = {} externalFunctionsAndData.inQueue = false @@ -104,9 +128,19 @@ local function MakeQueueControl(parentControl, queueName, queueDescription) btnLeave:SetVisibility(inQueue) end - function externalFunctionsAndData.UpdateQueueInformation(newName, newDescription) - lblTitle:SetText(newName) - lblDescription:SetText(newDescription) + function externalFunctionsAndData.UpdateQueueInformation(newName, newDescription, newPlayers, newWaiting) + if newName then + lblTitle:SetText(newName) + end + if newDescription then + lblDescription:SetText(newDescription) + end + if newPlayers then + lblPlayers:SetText("Playing: " .. newPlayers) + end + if newWaiting then + lblWaiting:SetText("Waiting: " .. newWaiting) + end end return externalFunctionsAndData @@ -116,6 +150,9 @@ local function InitializeControls(window) local Configuration = WG.Chobby.Configuration local lobby = WG.LibLobby.lobby + local banStart + local banDuration + local lblTitle = Label:New { x = 20, right = 5, @@ -175,23 +212,24 @@ local function InitializeControls(window) local queues = 0 local queueHolders = {} local function AddQueue(_, queueName, queueDescription, mapNames) + local queueData = lobby:GetQueue(queueName) or {} if queueHolders[queueName] then - queueHolders[queueName].UpdateQueueInformation(queueName, queueDescription) + queueHolders[queueName].UpdateQueueInformation(queueName, queueDescription, queueData.playersIngame or "?", queueData.playersWaiting or "?") return end local queueHolder = Control:New { x = 10, - y = queues*50 + 20, + y = queues*55 + 15, right = 0, - height = 35, + height = 45, caption = "", -- Status Window parent = listPanel, resizable = false, draggable = false, padding = {0, 0, 0, 0}, } - queueHolders[queueName] = MakeQueueControl(queueHolder, queueName, queueDescription) + queueHolders[queueName] = MakeQueueControl(queueHolder, queueName, queueDescription, queueData.playersIngame or "?", queueData.playersWaiting or "?") queues = queues + 1 end @@ -200,37 +238,62 @@ local function InitializeControls(window) AddQueue(_, data.name, data.description, data.mapNames) end - local function UpdateQueueStatus(listener, inMatchMaking, joinedQueueList, queueCounts, currentEloWidth, joinedTime, bannedTime) - local peopleInCommonQueues = 0 - for i = 1, #joinedQueueList do - local queueName = joinedQueueList[i] - peopleInCommonQueues = peopleInCommonQueues + ((queueCounts and queueCounts[queueName]) or 0) - if queueHolders[queueName] then - queueHolders[queueName].inQueue = true + local function UpdateQueueStatus(listener, inMatchMaking, joinedQueueList, queueCounts, ingameCounts, _, _, _, bannedTime) + if joinedQueueList then + for i = 1, #joinedQueueList do + local queueName = joinedQueueList[i] + if queueHolders[queueName] then + queueHolders[queueName].inQueue = true + end end end + for queueName, waitingCount in pairs(queueCounts) do + queueHolders[queueName].UpdateQueueInformation(nil, nil, ingameCounts and ingameCounts[queueName], waitingCount) + end + for name, queueHolder in pairs(queueHolders) do queueHolder.SetInQueue(queueHolder.inQueue) queueHolder.inQueue = false end if bannedTime then - statusText:SetText("You are banned from matchmaking for " .. bannedTime) + statusText:SetText("You are banned from matchmaking for " .. bannedTime .. " seconds") + banStart = Spring.GetTimer() + banDuration = bannedTime end end + if lobby.matchMakerBannedTime then + statusText:SetText("You are banned from matchmaking for " .. lobby.matchMakerBannedTime .. " seconds") + banStart = Spring.GetTimer() + banDuration = lobby.matchMakerBannedTime + end + lobby:AddListener("OnQueueOpened", AddQueue) lobby:AddListener("OnMatchMakerStatus", UpdateQueueStatus) local externalFunctions = {} + + function externalFunctions.UpdateBanTimer() + if not banStart then + return + end + local timeRemaining = banDuration - math.ceil(Spring.DiffTimers(Spring.GetTimer(), banStart)) + if timeRemaining < 0 then + banStart = false + statusText:SetText("") + return + end + statusText:SetText("You are banned from matchmaking for " .. timeRemaining .. " seconds") + end function externalFunctions.UpdateRequirementText() local newText = "" local firstEntry = true - for name,_ in pairs(requiredMaps) do + for name,_ in pairs(requiredResources) do if firstEntry then - newText = "Required maps: " + newText = "Required resources: " else newText = newText .. ", " end @@ -251,6 +314,10 @@ end local QueueListWindow = {} +function QueueListWindow.HaveMatchMakerResources() + return requiredResourceCount == 0 +end + function QueueListWindow.GetControl() local window = Control:New { @@ -274,12 +341,18 @@ end -------------------------------------------------------------------------------- -- Widget Interface +function widget:Update() + if panelInterface then + panelInterface.UpdateBanTimer() + end +end + function widget:DownloadFinished() - for mapName,_ in pairs(requiredMaps) do - local haveMap = VFS.HasArchive(mapName) - if haveMap then - requiredMaps[mapName] = nil - requiredMapCount = requiredMapCount - 1 + for resourceName,_ in pairs(requiredResources) do + local haveResource = VFS.HasArchive(resourceName) + if haveResource then + requiredResources[resourceName] = nil + requiredResourceCount = requiredResourceCount - 1 end end @@ -292,20 +365,33 @@ function widget:Initialize() CHOBBY_DIR = LUA_DIRNAME .. "widgets/chobby/" VFS.Include(LUA_DIRNAME .. "widgets/chobby/headers/exports.lua", nil, VFS.RAW_FIRST) - local function AddQueue(_, queueName, queueDescription, mapNames) + local function AddQueue(_, queueName, queueDescription, mapNames, maxPartSize, gameNames) for i = 1, #mapNames do local mapName = mapNames[i] - if not requiredMaps[mapName] then + if not requiredResources[mapName] then local haveMap = VFS.HasArchive(mapName) if not haveMap then - requiredMaps[mapName] = true - requiredMapCount = requiredMapCount + 1 + requiredResources[mapName] = true + requiredResourceCount = requiredResourceCount + 1 VFS.DownloadArchive(mapName, "map") end end end + for i = 1, #gameNames do + local gameName = gameNames[i] + if not requiredResources[gameName] then + local haveGame = VFS.HasArchive(gameName) + if not haveGame then + requiredResources[gameName] = true + requiredResourceCount = requiredResourceCount + 1 + + VFS.DownloadArchive(gameName, "game") + end + end + end + if panelInterface then panelInterface.UpdateRequirementText() end diff --git a/LuaMenu/widgets/gui_queue_status_panel.lua b/LuaMenu/widgets/gui_queue_status_panel.lua index 5b9bcfc1e..50c8596e1 100644 --- a/LuaMenu/widgets/gui_queue_status_panel.lua +++ b/LuaMenu/widgets/gui_queue_status_panel.lua @@ -21,6 +21,11 @@ local queueStatusIngame local readyCheckPopup local findingMatch = false +local instantStartQueuePriority = { + ["Teams"] = 2, + ["1v1"] = 1, +} + -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Utilities @@ -37,9 +42,23 @@ end -------------------------------------------------------------------------------- -- Initialization -local function InitializeQueueStatusHandler(parentControl) +local function InitializeQueueStatusHandler(parentControl, ControlType) local lobby = WG.LibLobby.lobby + ControlType = ControlType or Panel + + local queuePanel = ControlType:New { + x = 0, + y = 0, + right = 0, + bottom = 0, + padding = {0,0,0,0}, + caption = "", + resizable = false, + draggable = false, + parent = parentControl, + } + local button = Button:New { name = "cancel", x = "68%", @@ -55,7 +74,7 @@ local function InitializeQueueStatusHandler(parentControl) lobby:LeaveMatchMakingAll() end }, - parent = parentControl, + parent = queuePanel, } local rightBound = "33%" @@ -75,7 +94,7 @@ local function InitializeQueueStatusHandler(parentControl) bottom = bottomBound, fontsize = WG.Chobby.Configuration:GetFont(2).size, text = "", - parent = parentControl + parent = queuePanel } local externalFunctions = {} @@ -132,11 +151,119 @@ local function InitializeQueueStatusHandler(parentControl) UpdateQueueText() end + function externalFunctions.SetVisibility(newVisibility) + queuePanel:SetVisibility(newVisibility) + end + + return externalFunctions +end + +local function InitializeInstantQueueHandler(parentControl) + local lobby = WG.LibLobby.lobby + local queueName + + local queuePanel = Panel:New { + x = 0, + y = 0, + right = 0, + bottom = 0, + padding = {0,0,0,0}, + caption = "", + resizable = false, + draggable = false, + parent = parentControl, + } + + local button = Button:New { + name = "join", + x = "50%", + right = 4, + y = 4, + bottom = 4, + padding = {0,0,0,0}, + caption = "Join", + font = WG.Chobby.Configuration:GetFont(3), + classname = "action_button", + OnClick = { + function() + lobby:JoinMatchMaking(queueName) + end + }, + parent = queuePanel, + } + + local rightBound = "50%" + local bottomBound = 12 + local bigMode = true + + local queueStatusText = TextBox:New { + x = 12, + y = 18, + right = rightBound, + bottom = bottomBound, + fontsize = WG.Chobby.Configuration:GetFont(3).size, + text = "", + parent = queuePanel + } + + local externalFunctions = {} + + local function UpdateQueueText() + if queueName then + queueStatusText:SetText(queueName .. " Availible\nClick to Join") + end + end + + function externalFunctions.UpdateQueueName(newQueueName) + queueName = newQueueName + UpdateQueueText() + end + + function externalFunctions.Resize(xSize, ySize) + queueStatusText._relativeBounds.right = rightBound + queueStatusText._relativeBounds.bottom = bottomBound + queueStatusText:UpdateClientArea() + if ySize < 60 then + queueStatusText:SetPos(xSize/4 - 52, 4) + queueStatusText.font.size = WG.Chobby.Configuration:GetFont(2).size + queueStatusText:Invalidate() + bigMode = false + else + queueStatusText:SetPos(xSize/4 - 62, 18) + queueStatusText.font.size = WG.Chobby.Configuration:GetFont(3).size + queueStatusText:Invalidate() + bigMode = true + end + UpdateQueueText() + end + + function externalFunctions.SetVisibility(newVisibility) + queuePanel:SetVisibility(newVisibility) + end + + function externalFunctions.ProcessInstantStartQueue(instantStartQueues) + if instantStartQueues and #instantStartQueues > 0 then + local instantQueueName + local bestPriority = -1 + for i = 1, #instantStartQueues do + local queueName = instantStartQueues[i] + if (instantStartQueuePriority[queueName] or 0) > bestPriority then + instantQueueName = queueName + bestPriority = (instantStartQueuePriority[queueName] or 0) + end + end + if instantQueueName then + externalFunctions.UpdateQueueName(instantQueueName) + return true + end + end + end + return externalFunctions end local function InitializeIngameStatus(parentControl) - local queuePanel = Window:New { + local fakeQueuePanel = Control:New { right = 2, y = 52, height = 78, @@ -145,18 +272,10 @@ local function InitializeIngameStatus(parentControl) caption = "", resizable = false, draggable = false, - --classname = "overlay_window", parent = parentControl, - --OnResize = { - -- function (obj, xSize, ySize) - -- if queueStatusHandler then - -- queueStatusHandler.Resize(xSize, ySize) - -- end - -- end - --} } - return queuePanel, InitializeQueueStatusHandler(queuePanel) + return fakeQueuePanel, InitializeQueueStatusHandler(fakeQueuePanel, Window) end -------------------------------------------------------------------------------- @@ -166,6 +285,10 @@ end local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) local Configuration = WG.Chobby.Configuration + if Configuration.menuNotificationVolume ~= 0 then + Spring.PlaySoundFile("sounds/matchFound.wav", Configuration.menuNotificationVolume or 1) + end + local readyCheckWindow = Window:New { caption = "", name = "readyCheckWindow", @@ -187,20 +310,20 @@ local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) parent = readyCheckWindow, } - local statusLabel = Label:New { - x = 30, - width = 200, - y = 75, + local statusLabel = TextBox:New { + x = 15, + width = 250, + y = 80, height = 35, - caption = "", - font = Configuration:GetFont(3), + text = "", + fontsize = Configuration:GetFont(3).size, parent = readyCheckWindow, } local playersAcceptedLabel = Label:New { - x = 30, - width = 200, - y = 120, + x = 15, + width = 250, + y = 130, height = 35, caption = "Players accepted: 0", font = Configuration:GetFont(3), @@ -208,6 +331,7 @@ local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) } local acceptRegistered = false + local rejectedMatch = false local displayTimer = true local startTimer = Spring.GetTimer() local timeRemaining = secondsRemaining @@ -222,7 +346,8 @@ local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) local function CancelFunc() lobby:RejectMatchMakingMatch() - statusLabel:SetCaption(Configuration:GetErrorColor() .. "Rejected match") + statusLabel:SetText(Configuration:GetErrorColor() .. "Rejected match") + rejectedMatch = true displayTimer = false WG.Delay(DoDispose, 1) end @@ -279,7 +404,7 @@ local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) return end timeRemaining = newTimeRemaining - statusLabel:SetCaption(((acceptRegistered and "Waiting for players ") or "Accept in ") .. SecondsToMinutes(timeWaiting)) + statusLabel:SetText(((acceptRegistered and "Waiting for players ") or "Accept in ") .. SecondsToMinutes(timeRemaining)) end function externalFunctions.UpdatePlayerCount(readyPlayers) @@ -292,7 +417,7 @@ local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) return end acceptRegistered = true - statusLabel:SetCaption("Waiting for players " .. (timeRemaining or "time error") .. "s") + statusLabel:SetText("Waiting for players " .. (timeRemaining or "time error") .. "s") buttonAccept:Hide() @@ -304,12 +429,14 @@ local function CreateReadyCheckWindow(secondsRemaining, DestroyFunc) function externalFunctions.MatchMakingComplete(success) if success then - statusLabel:SetCaption(Configuration:GetSuccessColor() .. "Battle starting") - else - statusLabel:SetCaption(Configuration:GetErrorColor() .. "Match rejected") + statusLabel:SetText(Configuration:GetSuccessColor() .. "Battle starting") + elseif (not rejectedMatch) then + -- If we rejected the match then this message is not useful. + statusLabel:SetText(Configuration:GetWarningColor() .. "Match rejected by another player") end + Spring.Echo("MatchMakingComplete", success) displayTimer = false - WG.Delay(DoDispose, 1) + WG.Delay(DoDispose, 3) end return externalFunctions @@ -325,7 +452,7 @@ function QueueStatusPanel.GetControl() local lobby = WG.LibLobby.lobby local queueStatusIngamePanel - local queuePanel = Panel:New { + local fakeQueuePanel = Control:New { x = 0, y = 0, right = 0, @@ -338,32 +465,49 @@ function QueueStatusPanel.GetControl() function (obj, xSize, ySize) if queueStatusHandler then queueStatusHandler.Resize(xSize, ySize) + instantQueueHandler.Resize(xSize, ySize) end end } } - queueStatusHandler = InitializeQueueStatusHandler(queuePanel) + queueStatusHandler = InitializeQueueStatusHandler(fakeQueuePanel) + instantQueueHandler = InitializeInstantQueueHandler(fakeQueuePanel) + + queueStatusHandler.SetVisibility(false) + instantQueueHandler.SetVisibility(false) + + local previouslyInMatchMaking = false lobby:AddListener("OnMatchMakerStatus", - function(listener, inMatchMaking, joinedQueueList, queueCounts, currentEloWidth, joinedTime, bannedTime) + function(listener, inMatchMaking, joinedQueueList, queueCounts, ingameCounts, instantStartQueues, currentEloWidth, joinedTime, bannedTime) findingMatch = inMatchMaking if not queueStatusIngame then queueStatusIngamePanel, queueStatusIngame = InitializeIngameStatus(WG.Chobby.interfaceRoot.GetIngameInterfaceHolder()) end + Spring.Utilities.TableEcho(instantStartQueues, "instantStartQueues") if inMatchMaking then - - if not queuePanel.visible then + if not previouslyInMatchMaking then queueStatusHandler.ResetTimer() queueStatusIngame.ResetTimer() end queueStatusHandler.UpdateMatches(joinedQueueList, queueCounts, currentEloWidth, joinedTime) queueStatusIngame.UpdateMatches(joinedQueueList, queueCounts, currentEloWidth, joinedTime) + + instantQueueHandler.SetVisibility(false) + else + if (not bannedTime) and WG.QueueListWindow.HaveMatchMakerResources() and instantQueueHandler.ProcessInstantStartQueue(instantStartQueues) then + instantQueueHandler.SetVisibility(true) + else + instantQueueHandler.SetVisibility(false) + end end - queuePanel:SetVisibility(inMatchMaking) + previouslyInMatchMaking = inMatchMaking + + queueStatusHandler.SetVisibility(inMatchMaking) queueStatusIngamePanel:SetVisibility(inMatchMaking) end ) @@ -391,6 +535,7 @@ function QueueStatusPanel.GetControl() end local function OnMatchMakerReadyResult(_, isBattleStarting, areYouBanned) + Spring.Echo("OnMatchMakerReadyResult", isBattleStarting, areYouBanned) if not readyCheckPopup then return end @@ -410,7 +555,7 @@ function QueueStatusPanel.GetControl() lobby:AddListener("OnMatchMakerReadyResult", OnMatchMakerReadyResult) lobby:AddListener("OnBattleAboutToStart", OnBattleAboutToStart) - return queuePanel + return fakeQueuePanel end -------------------------------------------------------------------------------- diff --git a/LuaMenu/widgets/gui_settings_window.lua b/LuaMenu/widgets/gui_settings_window.lua index a4eacaeb6..0072862ab 100644 --- a/LuaMenu/widgets/gui_settings_window.lua +++ b/LuaMenu/widgets/gui_settings_window.lua @@ -31,6 +31,7 @@ local ITEM_OFFSET = 38 local COMBO_X = 230 local COMBO_WIDTH = 235 local CHECK_WIDTH = 230 +local TEXT_OFFSET = 6 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- @@ -113,7 +114,7 @@ local function GetLobbyTabControls() offset = offset + ITEM_OFFSET children[#children + 1] = Label:New { x = 20, - y = offset, + y = offset + TEXT_OFFSET, width = 90, height = 30, valign = "top", @@ -149,13 +150,13 @@ local function GetLobbyTabControls() offset = offset + ITEM_OFFSET children[#children + 1] = Label:New { x = 20, - y = offset, + y = offset + TEXT_OFFSET, width = 90, height = 40, valign = "top", align = "left", font = Configuration:GetFont(2), - caption = "Panels", + caption = "Panel mode", } children[#children + 1] = ComboBox:New { x = COMBO_X, @@ -176,6 +177,96 @@ local function GetLobbyTabControls() }, } + offset = offset + ITEM_OFFSET + children[#children + 1] = Label:New { + x = 20, + y = offset + TEXT_OFFSET, + width = 90, + height = 40, + valign = "top", + align = "left", + font = Configuration:GetFont(2), + caption = "Menu Music Volume", + } + children[#children + 1] = Trackbar:New { + x = COMBO_X, + y = offset, + width = COMBO_WIDTH, + height = 30, + value = Configuration.menuMusicVolume or 0.5, + min = 0, + max = 1, + step = 0.02, + OnChange = { + function(obj, value) + if freezeSettings then + return + end + Configuration:SetConfigValue("menuMusicVolume", value) + end + } + } + + offset = offset + ITEM_OFFSET + children[#children + 1] = Label:New { + x = 20, + y = offset + TEXT_OFFSET, + width = 90, + height = 40, + valign = "top", + align = "left", + font = Configuration:GetFont(2), + caption = "Notification Volume", + } + children[#children + 1] = Trackbar:New { + x = COMBO_X, + y = offset, + width = COMBO_WIDTH, + height = 30, + value = Configuration.menuNotificationVolume or 0.5, + min = 0, + max = 1, + step = 0.02, + OnChange = { + function(obj, value) + if freezeSettings then + return + end + Configuration:SetConfigValue("menuNotificationVolume", value) + end + } + } + + offset = offset + ITEM_OFFSET + children[#children + 1] = Label:New { + x = 20, + y = offset + TEXT_OFFSET, + width = 90, + height = 40, + valign = "top", + align = "left", + font = Configuration:GetFont(2), + caption = "Chat Font Size", + } + children[#children + 1] = Trackbar:New { + x = COMBO_X, + y = offset, + width = COMBO_WIDTH, + height = 30, + value = Configuration.chatFontSize or 16, + min = 12, + max = 20, + step = 1, + OnChange = { + function(obj, value) + if freezeSettings then + return + end + Configuration:SetConfigValue("chatFontSize", value) + end + } + } + offset = offset + ITEM_OFFSET local autoLogin = Checkbox:New { x = 20, @@ -292,10 +383,93 @@ local function GetLobbyTabControls() end}, } + offset = offset + ITEM_OFFSET + children[#children + 1] = Checkbox:New { + x = 20, + width = CHECK_WIDTH, + y = offset, + height = 30, + boxalign = "right", + boxsize = 20, + caption = "Debug for MatchMaker", + checked = Configuration.showMatchMakerBattles or false, + font = Configuration:GetFont(2), + OnChange = {function (obj, newState) + Configuration:SetConfigValue("showMatchMakerBattles", newState) + end}, + } + offset = offset + ITEM_OFFSET children[#children + 1] = Label:New { x = 20, + y = offset + TEXT_OFFSET, + width = 90, + height = 40, + valign = "top", + align = "left", + font = Configuration:GetFont(2), + caption = "Server Address", + } + children[#children + 1] = EditBox:New { + x = COMBO_X, y = offset, + width = COMBO_WIDTH, + height = 30, + text = Configuration.serverAddress, + font = Configuration:GetFont(2), + OnFocusUpdate = { + function (obj) + if obj.focused then + return + end + + Configuration.serverAddress = obj.text + obj:SetText(Configuration.serverAddress) + end + } + } + + offset = offset + ITEM_OFFSET + children[#children + 1] = Label:New { + x = 20, + y = offset + TEXT_OFFSET, + width = 90, + height = 40, + valign = "top", + align = "left", + font = Configuration:GetFont(2), + caption = "Server Port", + } + children[#children + 1] = EditBox:New { + x = COMBO_X, + y = offset, + width = COMBO_WIDTH, + height = 30, + text = tostring(Configuration.serverPort), + font = Configuration:GetFont(2), + OnFocusUpdate = { + function (obj) + if obj.focused then + return + end + + local newValue = tonumber(obj.text) + + if not newValue then + obj:SetText(tostring(Configuration.serverPort)) + return + end + + Configuration.serverPort = math.floor(0.5 + math.max(0, newValue)) + obj:SetText(tostring(Configuration.serverPort)) + end + } + } + + offset = offset + ITEM_OFFSET + children[#children + 1] = Label:New { + x = 20, + y = offset + TEXT_OFFSET, width = 90, height = 30, valign = "top", @@ -355,6 +529,7 @@ end -- Game Settings local settingsComboBoxes = {} +local settingsUpdateFunction = {} local function MakePresetsControl(settingPresets, offset) local Configuration = WG.Chobby.Configuration @@ -362,14 +537,14 @@ local function MakePresetsControl(settingPresets, offset) local presetLabel = Label:New { name = "presetLabel", x = 20, - y = offset + 10, + y = offset + TEXT_OFFSET, width = 90, height = 40, valign = "top", align = "left", parent = window, font = Configuration:GetFont(2), - caption = "Presets", + caption = "Preset:", } local useCustomSettings = true @@ -379,9 +554,9 @@ local function MakePresetsControl(settingPresets, offset) local button = Button:New { name = caption, x = 80*x, - y = 45*y, + y = ITEM_OFFSET*y, width = 75, - height = 40, + height = 30, caption = caption, font = Configuration:GetFont(2), customSettings = not settings, @@ -393,6 +568,10 @@ local function MakePresetsControl(settingPresets, offset) if comboBox then comboBox:Select(value) end + local updateFunction = settingsUpdateFunction[key] + if updateFunction then + updateFunction(value) + end end end @@ -409,7 +588,7 @@ local function MakePresetsControl(settingPresets, offset) settingsPresetControls[#settingsPresetControls + 1] = button if settings then - if Spring.Utilities.TableEqual(settings, Configuration.settingsMenuValues) then + if Spring.Utilities.TableSubsetEquals(settings, Configuration.settingsMenuValues) then useCustomSettings = false ButtonUtilities.SetButtonSelected(button) end @@ -419,24 +598,30 @@ local function MakePresetsControl(settingPresets, offset) return button end + local x = 0 + local y = 0 + local settingsButtons = {} + for i = 1, #settingPresets do + settingsButtons[#settingsButtons + 1] = SettingsButton(x, y, settingPresets[i].name, settingPresets[i].settings) + x = x + 1 + if x > 2 then + x = 0 + y = y + 1 + end + end + + local customSettingsButton = SettingsButton(x, y, "Custom") + settingsButtons[#settingsButtons + 1] = customSettingsButton + local settingsHolder = Control:New { name = "settingsHolder", x = COMBO_X, y = offset, width = COMBO_WIDTH, - height = 120, + height = ITEM_OFFSET*(y + 1), padding = {0, 0, 0, 0}, - children = { - SettingsButton(0, 0, "Minimal", settingPresets.Minimal), - SettingsButton(1, 0, "Low", settingPresets.Low), - SettingsButton(2, 0, "Medium", settingPresets.Medium), - SettingsButton(0, 1, "High", settingPresets.High), - SettingsButton(1, 1, "Ultra", settingPresets.Ultra), - } + children = settingsButtons } - - local customSettingsButton = SettingsButton(2, 1, "Custom") - settingsHolder:AddChild(customSettingsButton) local function EnableCustomSettings() ButtonUtilities.SetButtonSelected(customSettingsButton) @@ -448,7 +633,7 @@ local function MakePresetsControl(settingPresets, offset) end end - return presetLabel, settingsHolder, EnableCustomSettings, offset + ITEM_OFFSET*2 + 20 + return presetLabel, settingsHolder, EnableCustomSettings, offset + ITEM_OFFSET*(y + 1) end local function ProcessScreenSizeOption(data, offset) @@ -459,7 +644,7 @@ local function ProcessScreenSizeOption(data, offset) local label = Label:New { name = data.name .. "_label", x = 20, - y = offset, + y = offset + TEXT_OFFSET, width = 350, height = 30, valign = "top", @@ -499,13 +684,13 @@ local function ProcessScreenSizeOption(data, offset) return label, list, offset + ITEM_OFFSET end -local function ProcessSettingsOption(data, offset, customSettingsSwitch) +local function ProcessSettingsOption(data, offset, defaults, customSettingsSwitch) local Configuration = WG.Chobby.Configuration local label = Label:New { name = data.name .. "_label", x = 20, - y = offset, + y = offset + TEXT_OFFSET, width = 350, height = 30, valign = "top", @@ -516,7 +701,7 @@ local function ProcessSettingsOption(data, offset, customSettingsSwitch) } local defaultItem = 1 - local defaultName = Configuration.settingsMenuValues[data.name] + local defaultName = Configuration.settingsMenuValues[data.name] or defaults[data.name] local items = {} for i = 1, #data.options do @@ -527,6 +712,8 @@ local function ProcessSettingsOption(data, offset, customSettingsSwitch) end end + local freezeSettings = true + settingsComboBoxes[data.name] = ComboBox:New { name = data.name .. "_combo", x = COMBO_X, @@ -539,6 +726,10 @@ local function ProcessSettingsOption(data, offset, customSettingsSwitch) selected = defaultItem, OnSelect = { function (obj, num) + if freezeSettings then + return freezeSettings + end + local selectedData = data.options[num] Configuration.settingsMenuValues[data.name] = selectedData.name @@ -562,12 +753,79 @@ local function ProcessSettingsOption(data, offset, customSettingsSwitch) } } + freezeSettings = false + return label, settingsComboBoxes[data.name], offset + ITEM_OFFSET end -local function PopulateTab(settingPresets, settingOptions) +local function ProcessSettingsNumber(data, offset, defaults, customSettingsSwitch) + local Configuration = WG.Chobby.Configuration + + local label = Label:New { + name = data.name .. "_label", + x = 20, + y = offset + TEXT_OFFSET, + width = 350, + height = 30, + valign = "top", + align = "left", + caption = data.humanName, + font = Configuration:GetFont(2), + tooltip = data.desc, + } + + local function SetEditboxValue(obj, newValue) + newValue = tonumber(newValue) + + if not newValue then + obj:SetText(tostring(Configuration.settingsMenuValues[data.name])) + return + end + + if customSettingsSwitch then + customSettingsSwitch() + end + + local newValue = math.floor(0.5 + math.max(data.minValue, math.min(data.maxValue, newValue))) + local springValue = data.springConversion(newValue) + Configuration.settingsMenuValues[data.name] = newValue + Configuration.game_settings[data.applyName] = springValue + SetSpringsettingsValue(data.applyName, springValue) + + obj:SetText(tostring(newValue)) + end + + local freezeSettings = true + + local numberInput = EditBox:New { + x = COMBO_X, + y = offset, + width = COMBO_WIDTH, + height = 30, + text = tostring(Configuration.settingsMenuValues[data.name] or defaults[data.name]), + font = Configuration:GetFont(2), + OnFocusUpdate = { + function (obj) + if obj.focused or freezeSettings then + return + end + SetEditboxValue(obj, obj.text) + end + } + } + + freezeSettings = false + + settingsUpdateFunction[data.name] = function (newValue) + SetEditboxValue(numberInput, newValue) + end + + return label, numberInput, offset + ITEM_OFFSET +end + +local function PopulateTab(settingPresets, settingOptions, settingsDefaults) local children = {} - local offset = 20 + local offset = ITEM_OFFSET local customSettingsSwitch local label, list @@ -581,8 +839,10 @@ local function PopulateTab(settingPresets, settingOptions) local data = settingOptions[i] if data.displayModeToggle then label, list, offset = ProcessScreenSizeOption(data, offset) + elseif data.isNumberSetting then + label, list, offset = ProcessSettingsNumber(data, offset, settingsDefaults, customSettingsSwitch) else - label, list, offset = ProcessSettingsOption(data, offset, customSettingsSwitch) + label, list, offset = ProcessSettingsOption(data, offset, settingsDefaults, customSettingsSwitch) end children[#children + 1] = label children[#children + 1] = list @@ -615,7 +875,7 @@ local function InitializeControls(window) name = data.name, caption = data.name, font = WG.Chobby.Configuration:GetFont(3), - children = PopulateTab(data.presets, data.settings) + children = PopulateTab(data.presets, data.settings, settingsDefaults) } end @@ -646,64 +906,6 @@ local function InitializeControls(window) tabPanel.tabBar } } - - --offset = offset + 60 - --Label:New { - -- x = 40, - -- y = offset, - -- width = COMBO_WIDTH, - -- height = 30, - -- parent = window, - -- font = Configuration:GetFont(4), - -- caption = "Game", - --} - --offset = offset + 20 - - - -- - --offset = offset + ITEM_OFFSET - --Label:New { - -- x = 60, - -- y = offset, - -- width = 90, - -- height = 25, - -- valign = "top", - -- align = "left", - -- parent = window, - -- font = Configuration:GetFont(2), - -- caption = "Display", - --} - --ComboBox:New { - -- x = COMBO_X, - -- y = offset + 2, - -- width = COMBO_WIDTH, - -- height = 26, - -- parent = window, - -- items = {"Fullscreen Window", "Windowed", "Fullscreen"}, - -- font = Configuration:GetFont(2), - -- itemFontSize = Configuration:GetFont(2).size, - -- selected = Configuration.game_fullscreen or battleStartDisplay, - -- OnSelect = { - -- function (obj) - -- if freezeSettings then - -- return - -- end - -- - -- if Spring.GetGameName() ~= "" then - -- SetLobbyFullscreenMode(obj.selected) - -- end - -- - -- battleStartDisplay = obj.selected - -- Configuration.game_fullscreen = obj.selected - -- end - -- }, - --} - - - - -- - --freezeSettings = false - end -------------------------------------------------------------------------------- diff --git a/LuaMenu/widgets/gui_tooltip.lua b/LuaMenu/widgets/gui_tooltip.lua index 1e5a97045..627d9ca24 100644 --- a/LuaMenu/widgets/gui_tooltip.lua +++ b/LuaMenu/widgets/gui_tooltip.lua @@ -10,8 +10,6 @@ function widget:GetInfo() } end -local mousePosX, mousePosY -local tipWindow, tipTextDisplay local spGetGameFrame = Spring.GetGameFrame local spGetMouseState = Spring.GetMouseState @@ -37,6 +35,14 @@ local BATTLE_NOT_RUNNING = LUA_DIRNAME .. "images/nothing.png" local PASSWORD_EXPLAINATION = "Battle requires a password to join." +-------------------------------------------------------------------------- +-------------------------------------------------------------------------- +-- Variables + +local mousePosX, mousePosY +local tipWindow, tipTextDisplay +local tooltipOverride = nil + -------------------------------------------------------------------------- -------------------------------------------------------------------------- -- Initialization @@ -89,56 +95,6 @@ end -------------------------------------------------------------------------- -- Specific tooltip type utilities -local function GetTimeToPast(pastTimeString) - -- Example: 2016-07-21T14:49:00.4731696Z - local pastTime = { - string.sub(pastTimeString, 18, 19), - string.sub(pastTimeString, 15, 16), - string.sub(pastTimeString, 12, 13), - string.sub(pastTimeString, 9, 10), - --string.sub(pastTimeString, 6, 7), - --string.sub(pastTimeString, 0, 4), - } - - for i = 1, #pastTime do - pastTime[i] = tonumber(pastTime[i]) - if not pastTime[i] then - return - end - end - - local currentTime = { - tonumber(os.date("!%S")), - tonumber(os.date("!%M")), - tonumber(os.date("!%H")), - tonumber(os.date("!%d")), - --tonumber(os.date("!%m")), - --tonumber(os.date("!%Y")), - } - - local pastSeconds = pastTime[1] + 60*(pastTime[2] + 24*pastTime[3]) - local currentSeconds = currentTime[1] + 60*(currentTime[2] + 24*currentTime[3]) - if currentTime[4] ~= pastTime[4] then - -- Always assume that the past time is one day behind. - currentSeconds = currentSeconds + 86400 - end - - local distanceSeconds = currentSeconds - pastSeconds - local hours = math.floor(distanceSeconds/3600) - local minutes = math.floor(distanceSeconds/60)%60 - local seconds = math.floor(distanceSeconds)%60 - - local timeText = "" - if hours > 0 then - timeText = timeText .. hours .. "h " - end - if hours > 0 or minutes > 0 then - timeText = timeText .. minutes .. "m " - end - - return timeText .. seconds .. "s" -end - local function GetTooltipLine(parent, hasImage, fontSize, xOffset) local textDisplay, imageDisplay @@ -472,7 +428,7 @@ local function GetBattleTooltip(battleID, battle) end battleTooltip.inGameSince.Update( offset, - "Running for " .. GetTimeToPast(battle.runningSince), + "Running for " .. Spring.Utilities.GetTimeToPast(battle.runningSince, true), IMAGE_INGAME ) offset = offset + 20 @@ -685,7 +641,7 @@ local function GetUserTooltip(userName, userInfo, userBattleInfo, inBattleroom) end userTooltip.inGameSince.Update( offset, - "In game for " .. GetTimeToPast(userInfo.inGameSince), + "In game for " .. Spring.Utilities.GetTimeToPast(userInfo.inGameSince, true), IMAGE_INGAME ) offset = offset + 20 @@ -700,7 +656,7 @@ local function GetUserTooltip(userName, userInfo, userBattleInfo, inBattleroom) end userTooltip.awaySince.Update( offset, - "Idle for " .. GetTimeToPast(userInfo.awaySince), + "Idle for " .. Spring.Utilities.GetTimeToPast(userInfo.awaySince, true), IMAGE_AFK ) offset = offset + 20 @@ -776,6 +732,9 @@ end -- Tooltip maintence local function GetTooltip() + if tooltipOverride then + return tooltipOverride + end if screen0.currentTooltip then -- this gives chili absolute priority, otherwise TraceSreenRay() would ignore the fact ChiliUI is underneath the mouse return screen0.currentTooltip end @@ -808,7 +767,7 @@ local function SetTooltipPos() y = screenHeight - y -- Spring y is from the bottom, chili is from the top -- Making sure the tooltip is within the boundaries of the screen - y = (y + height > screenHeight) and (y - height) or (y + 20) + y = ((y + height > screenHeight) and (y > height) and (y - height)) or (y + 20) x = (x + width > screenWidth) and (screenWidth - width) or x tipWindow:SetPos(x, y, width, height) @@ -857,19 +816,12 @@ local function UpdateTooltip(inputText) end end --------------------------------------------------------------------------- --------------------------------------------------------------------------- --- Widget callins local currentTooltipText = false - -function widget:Update() - EvilHax() - - local text = GetTooltip() - if text then - if currentTooltipText ~= text then - currentTooltipText = text - UpdateTooltip(text) +local function CheckTooltipUpdate(newText) + if newText then + if currentTooltipText ~= newText then + currentTooltipText = newText + UpdateTooltip(newText) end SetTooltipPos() else @@ -880,11 +832,40 @@ function widget:Update() end end +-------------------------------------------------------------------------- +-------------------------------------------------------------------------- +-- External Functions + +local TooltipHandler = {} + +function TooltipHandler.TooltipOverrideClear() + tooltipOverride = nil + CheckTooltipUpdate(GetTooltip()) +end + +function TooltipHandler.TooltipOverride(newText, overrideTime) + tooltipOverride = newText + CheckTooltipUpdate(tooltipOverride) + if overrideTime then + WG.Delay(TooltipHandler.TooltipOverrideClear, overrideTime) + end +end + +-------------------------------------------------------------------------- +-------------------------------------------------------------------------- +-- Widget callins + +function widget:Update() + EvilHax() + CheckTooltipUpdate(GetTooltip()) +end + function widget:Initialize() CHOBBY_DIR = LUA_DIRNAME .. "widgets/chobby/" VFS.Include(LUA_DIRNAME .. "widgets/chobby/headers/exports.lua", nil, VFS.RAW_FIRST) InitWindow() + WG.TooltipHandler = TooltipHandler end function widget:Shutdown() diff --git a/LuaMenu/widgets/snd_music_lite.lua b/LuaMenu/widgets/snd_music_lite.lua index 7e68b6cb4..8e7300f3a 100644 --- a/LuaMenu/widgets/snd_music_lite.lua +++ b/LuaMenu/widgets/snd_music_lite.lua @@ -1,24 +1,14 @@ -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- --- --- file: gui_music.lua --- brief: yay music --- author: cake --- --- Copyright (C) 2007. --- Licensed under the terms of the GNU GPL, v2 or later. --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- function widget:GetInfo() return { name = "Music Player Lite", desc = "Plays music for ingame lobby client", - author = "cake, trepan, Smoth, Licho, xponen", - date = "Mar 01, 2008, Aug 20 2009, Nov 23 2011", + author = "GoogleFrog and KingRaptor", + date = "25 September 2016", license = "GNU GPL, v2 or later", - layer = 0, + layer = 2000, enabled = true -- loaded by default? } end @@ -26,222 +16,143 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -options_path = 'Settings/Audio' -options = { - useIncludedTracks = { - name = "Use Included Tracks", - type = 'bool', - value = true, - desc = 'Use the tracks included with Zero-K', - noHotkey = true, - }, -} - -local windows = {} - -local warThreshold = 5000 -local peaceThreshold = 1000 -local PLAYLIST_FILE = 'sounds/music/playlist.lua' -local LOOP_BUFFER = 0.015 -- if looping track is this close to the end, go ahead and loop -local UPDATE_PERIOD = 1 - -local musicType = 'lobby' -local timeframetimer = 0 -local timeframetimer_short = 0 -local loopTrack = '' -local previousTrack = '' -local previousTrackType = '' -local newTrackWait = 1000 -local fadeVol -local curTrack = "no name" -local songText = "no name" -local haltMusic = false -local looping = false -local paused = false -local lastTrackTime = -1 - -local warTracks, peaceTracks, briefingTracks, victoryTracks, defeatTracks - -local firstTime = false -local wasPaused = false -local firstFade = true -local initSeed = 0 -local initialized = false - -local timer = Spring.GetTimer() --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- -local function GetMusicType() - return musicType -end +local playingTrack -local function StartLoopingTrack(trackInit, trackLoop) - if not (VFS.FileExists(trackInit) and VFS.FileExists(trackLoop)) then - Spring.Log(widget:GetInfo().name, LOG.ERROR, "Missing one or both tracks for looping") +local function StartTrack(trackName, volume) + volume = volume or WG.Chobby.Configuration.menuMusicVolume + Spring.Echo("Starting Track", trackName, volume) + if volume == 0 then + return end - haltMusic = true Spring.StopSoundStream() - musicType = 'custom' - - curTrack = trackInit - loopTrack = trackLoop - Spring.PlaySoundStream(trackInit, WG.music_volume or 0.5) - looping = 0.5 + Spring.PlaySoundStream(trackName, volume) + playingTrack = true end -local function StartTrack(track) - haltMusic = false - looping = false +local function StopTrack() Spring.StopSoundStream() - - local newTrack = previousTrack - if track then - newTrack = track -- play specified track - musicType = 'custom' - else - local tries = 0 - repeat - if (#lobbyTracks == 0) then return end - newTrack = lobbyTracks[math.random(1, #lobbyTracks)] - tries = tries + 1 - until newTrack ~= previousTrack or tries >= 10 - musicType = 'lobby' + playingTrack = false +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +local randomTrackList = { + "sounds/lobbyMusic/A Magnificent Journey (Alternative Version).ogg", + "sounds/lobbyMusic/Dream Infinity.ogg", + "sounds/lobbyMusic/Interstellar.ogg", + "sounds/lobbyMusic/Tomorrow Landscape.ogg", +} + +local function GetRandomTrack(previousTrack) + local trackCount = #randomTrackList + local previousTrackIndex + if previousTrack then + for i = 1, #randomTrackList do + if randomTrackList[i] == previousTrack then + trackCount = trackCount - 1 + previousTrackIndex = i + break + end + end end - -- for key, val in pairs(oggInfo) do - -- Spring.Echo(key, val) - -- end - firstFade = false - previousTrack = newTrack - - -- if (oggInfo.comments.TITLE and oggInfo.comments.TITLE) then - -- Spring.Echo("Song changed to: " .. oggInfo.comments.TITLE .. " By: " .. oggInfo.comments.ARTIST) - -- else - -- Spring.Echo("Song changed but unable to get the artist and title info") - -- end - curTrack = newTrack - Spring.PlaySoundStream(newTrack,WG.music_volume or 0.5) - WG.music_start_volume = WG.music_volume + local randomTrack = math.ceil(math.random()*trackCount) + if randomTrack == previousTrackIndex then + randomTrack = trackCount + 1 + end + return randomTrackList[randomTrack] end -local function StopTrack(noContinue) - looping = false - Spring.StopSoundStream() - if noContinue then - haltMusic = true - else - haltMusic = false - StartTrack() +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +local previousTrack + +local function SetTrackVolume(volume) + if volume == 0 then + StopTrack() + return + end + if playingTrack then + Spring.SetSoundStreamVolume(volume) + return end + StartTrack(GetRandomTrack(), volume) + previousTrack = nil end +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +local firstActivation = true +local ingame = false +local OPEN_TRACK_NAME = 'sounds/lobbyMusic/The Secret of Ayers Rock.ogg' + function widget:Update() - if gameOver then + if ingame or (WG.Chobby.Configuration.menuMusicVolume == 0 )then return end - local currentTime = Spring.GetTimer() - local dt = Spring.DiffTimers(currentTime, timer) - - if not initialized then - math.randomseed(os.clock()* 100) - initialized=true - -- these are here to give epicmenu time to set the values properly - -- (else it's always default at startup) - if VFS.FileExists(PLAYLIST_FILE, VFS.RAW_FIRST) then - local tracks = VFS.Include(PLAYLIST_FILE, nil, VFS.RAW_FIRST) - lobbyTracks = tracks.lobby - end - - local vfsMode = (options.useIncludedTracks.value and VFS.RAW_FIRST) or VFS.RAW - lobbyTracks = warTracks or VFS.DirList('sounds/music/lobby/', '*.ogg', vfsMode) - end - - timeframetimer_short = timeframetimer_short + dt - if timeframetimer_short > 0.03 then - local playedTime, totalTime = Spring.GetSoundStreamTime() - playedTime = tonumber( ("%.2f"):format(playedTime) ) - paused = (playedTime == lastTrackTime) - lastTrackTime = playedTime - if looping then - if looping == 0.5 then - looping = 1 - elseif playedTime >= totalTime - LOOP_BUFFER then - Spring.StopSoundStream() - Spring.PlaySoundStream(loopTrack,WG.music_volume or 0.5) - end - end - timeframetimer_short = 0 - end - - timeframetimer = timeframetimer + dt - if (timeframetimer > UPDATE_PERIOD) then -- every second - timeframetimer = 0 - newTrackWait = newTrackWait + 1 - - if (not firstTime) then - StartTrack() - firstTime = true -- pop this cherry - end - - local playedTime, totalTime = Spring.GetSoundStreamTime() - playedTime = math.floor(playedTime) - totalTime = math.floor(totalTime) - --Spring.Echo(playedTime, totalTime) - - --Spring.Echo(playedTime, totalTime, newTrackWait) - - --if((totalTime - playedTime) <= 6 and (totalTime >= 1) ) then - --Spring.Echo("time left:", (totalTime - playedTime)) - --Spring.Echo("volume:", (totalTime - playedTime)/6) - --if ((totalTime - playedTime)/6 >= 0) then - -- Spring.SetSoundStreamVolume((totalTime - playedTime)/6) - --else - -- Spring.SetSoundStreamVolume(0.1) - --end - --elseif(playedTime <= 5 )then--and not firstFade - --Spring.Echo("time playing:", playedTime) - --Spring.Echo("volume:", playedTime/5) - --Spring.SetSoundStreamVolume( playedTime/5) - --end - --Spring.Echo(previousTrackType, musicType) - if (playedTime >= totalTime) -- both zero means track stopped - and not(haltMusic or looping) then - previousTrackType = musicType - StartTrack() - - --Spring.Echo("Track: " .. newTrack) - newTrackWait = 0 - end + local playedTime, totalTime = Spring.GetSoundStreamTime() + playedTime = math.floor(playedTime) + totalTime = math.floor(totalTime) + + if (playedTime >= totalTime) then + local newTrack = GetRandomTrack(previousTrack) + StartTrack(newTrack) + previousTrack = newTrack end - - timer = currentTime end -function widget:Initialize() - WG.Music = WG.Music or {} - WG.Music.StartTrack = StartTrack - WG.Music.StartLoopingTrack = StartLoopingTrack - WG.Music.StopTrack = StopTrack - WG.Music.GetMusicType = GetMusicType - - -- Spring.Echo(math.random(), math.random()) - -- Spring.Echo(os.clock()) - - -- for TrackName,TrackDef in pairs(peaceTracks) do - -- Spring.Echo("Track: " .. TrackDef) - -- end - --math.randomseed(os.clock()* 101.01)--lurker wants you to burn in hell rgn - -- for i=1,20 do Spring.Echo(math.random()) end +local MusicHandler = { + StartTrack = StartTrack, +} + +-- Called just before the game loads +-- This could be used to implement music in the loadscreen +--function widget:GamePreload() +-- -- Ingame, no longer any of our business +-- if Spring.GetGameName() ~= "" then +-- ingame = true +-- StopTrack() +-- end +--end + +-- called when returning to menu from a game +function widget:ActivateMenu() + ingame = false + if firstActivation then + StartTrack(OPEN_TRACK_NAME) + firstActivation = false + return + end + -- start playing music again + local newTrack = GetRandomTrack(previousTrack) + StartTrack(newTrack) + previousTrack = newTrack end -function widget:Shutdown() - Spring.StopSoundStream() - WG.Music = nil +function widget:Initialize() + math.randomseed(os.clock() * 100) - for i=1,#windows do - (windows[i]):Dispose() + local Configuration = WG.Chobby.Configuration + + local function onConfigurationChange(listener, key, value) + if key == "menuMusicVolume" then + SetTrackVolume(value) + end end + Configuration:AddListener("OnConfigurationChange", onConfigurationChange) + + local function OnBattleAboutToStart() + ingame = true + StopTrack() + end + WG.LibLobby.localLobby:AddListener("OnBattleAboutToStart", OnBattleAboutToStart) + WG.LibLobby.lobby:AddListener("OnBattleAboutToStart", OnBattleAboutToStart) + + WG.MusicHandler = MusicHandler end -------------------------------------------------------------------------------- diff --git a/LuaMenu/widgets/zk_campaign_handler.lua b/LuaMenu/widgets/zk_campaign_handler.lua index f001320f9..7fd11a20b 100644 --- a/LuaMenu/widgets/zk_campaign_handler.lua +++ b/LuaMenu/widgets/zk_campaign_handler.lua @@ -17,6 +17,19 @@ end -------------------------------------------------------------------------------- local CAMPAIGN_DIR = "campaign/" +local STARMAP_WINDOW_WIDTH = 1280 +local STARMAP_WINDOW_HEIGHT = 768 +local PLANET_IMAGE_SIZE = 259 +local PLANET_BACKGROUND_SIZE = 1280 +local CAMPAIGN_SELECTOR_BUTTON_HEIGHT = 96 +local SAVEGAME_BUTTON_HEIGHT = 128 +local OUTLINE_COLOR = {0.54,0.72,1,0.3} +local CODEX_BUTTON_FONT_SIZE = 14 +local SAVE_DIR = "saves/campaign/" +local MAX_SAVES = 999 +local AUTOSAVE_ID = "auto" +local AUTOSAVE_FILENAME = "autosave" +local RESULTS_FILE_PATH = "cache/mission_results.lua" -------------------------------------------------------------------------------- -- Chili elements -------------------------------------------------------------------------------- @@ -28,6 +41,7 @@ local ScrollPanel local Label local Button +-- chili elements local window local screens = {} -- main, newGame, save, load, intermission, codex local currentScreenID = "main" @@ -47,27 +61,16 @@ local starmapWindow, starmapBackgroundHolder, starmapBackground, starmapBackgrou local timer = Spring.GetTimer() local starmapAnimation = nil +-- mission launcher variables local runningMission = false local missionCompletionFunc = nil -- function local missionArchive = nil local requiredMap = nil -local STARMAP_WINDOW_WIDTH = 1280 -local STARMAP_WINDOW_HEIGHT = 768 -local PLANET_IMAGE_SIZE = 259 -local PLANET_BACKGROUND_SIZE = 1280 -local CAMPAIGN_SELECTOR_BUTTON_HEIGHT = 96 -local SAVEGAME_BUTTON_HEIGHT = 128 -local OUTLINE_COLOR = {0.54,0.72,1,0.3} -local CODEX_BUTTON_FONT_SIZE = 14 -local SAVE_DIR = "saves/campaign/" -local MAX_SAVES = 999 -local AUTOSAVE_ID = "auto" -local AUTOSAVE_FILENAME = "autosave" -local RESULTS_FILE_PATH = "cache/mission_results.lua" -------------------------------------------------------------------------------- -- data -------------------------------------------------------------------------------- +-- this stores anything that goes into a save file local gamedata = { chapterTitle = "", --[[ @@ -88,6 +91,7 @@ local campaignDefs = {} -- {name, author, image, definition, starting function c local campaignDefsByID = {} local currentCampaignID = nil +-- loaded when campaign is loaded local planetDefs = {} local planetDefsByID = {} local missionDefs = {} @@ -98,6 +102,7 @@ local savesOrdered = {} -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- +-- Makes a control grey for disabled, or whitish for enabled local function SetControlGreyout(control, state) if state then control.backgroundColor = {0.4, 0.4, 0.4, 1} @@ -141,6 +146,8 @@ local function IsCodexEntryVisible(id) return gamedata.codexUnlocked[id] or codexEntries[id].alwaysUnlocked end +-- Makes the codex button in intermission screen have a blue textshadow if any entries are unread +-- removes the shadow if not local function UpdateCodexButtonState(unread) if unread == nil then unread = false @@ -157,6 +164,8 @@ local function UpdateCodexButtonState(unread) intermissionButtonCodex:Invalidate() end +-- This happens when a button for a codex entry is clicked +-- It removes the "unread" blue shadow from the text, and writes the entry contents to the text box local function UpdateCodexEntry(entryID) if not gamedata.codexRead[entryID] then if codexTreeControls[entryID] then @@ -262,7 +271,7 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- - +-- Called on Initialize local function LoadCampaignDefs() local success, err = pcall(function() local subdirs = VFS.SubDirs(CAMPAIGN_DIR) @@ -282,6 +291,7 @@ local function LoadCampaignDefs() end end +-- This is called when a new game is started or a save is loaded local function LoadCampaign(campaignID) local def = campaignDefsByID[campaignID] local success, err = pcall(function() @@ -304,10 +314,12 @@ local function LoadCampaign(campaignID) end end +-- Sets the next Visual Novel script to be played on clicking the Next Episode button in intermission local function SetNextMissionScript(script) gamedata.nextMissionScript = script end +-- Tells Visual Novel widget to load a story local function SetVNStory(story, dir) if (not story) or story == "" then return @@ -317,6 +329,7 @@ local function SetVNStory(story, dir) WG.VisualNovel.LoadStory(story, dir) end +-- chapter titles are visible on save file local function SetChapterTitle(title) gamedata.chapterTitle = title end @@ -342,6 +355,7 @@ local function GetMapNameFromStartscript(startscript) return string.match(startscript, "MapName%s?=%s?(.-);") end +-- Checks if we have the map specified in the mission's startscript, if not download it local function GetMapForMissionIfNeeded(missionID, startscriptStr) if not startscriptStr then startscriptStr = GetMissionStartscriptString(missionID) @@ -383,8 +397,9 @@ local function LaunchMission(missionID, func) end -------------------------------------------------------------------------------- +-- Savegame utlity functions -------------------------------------------------------------------------------- - +-- FIXME: currently unused as it doesn't seem to give the correct order local function SortSavesByDate(a, b) if a == nil or b == nil then return false @@ -404,6 +419,7 @@ local function SortSavesByDate(a, b) return false end +-- Returns the data stored in a save file local function GetSave(filename) local ret = nil local success, err = pcall(function() @@ -420,6 +436,7 @@ local function GetSave(filename) end end +-- Loads the list of save files and their contents local function GetSaves() Spring.CreateDir(SAVE_DIR) saves = {} @@ -445,6 +462,7 @@ local function GetSaves() --table.sort(savesOrdered, SortSavesByDate) end +-- e.g. if save slots 1, 2, 5, and 7 are used, return 3 local function FindFirstEmptySaveSlot() local start = #saves if start == 0 then start = 1 end @@ -501,6 +519,8 @@ local function SetSaveLoadButtonStates() end ]] +-- Called when player clicks a campaign selection button in new game screen +-- Writes details of the campaign to the details panel local function UpdateCampaignDetailsPanel(campaignID) local def = campaignDefsByID[campaignID] if not def then @@ -516,9 +536,11 @@ local function UpdateCampaignDetailsPanel(campaignID) EnableStartButton() end +-- Called when player clicks a campaign selection button in new game screen local function SelectCampaign(campaignID) local def = campaignDefsByID[campaignID] currentCampaignID = def.id + -- grey out the buttons of the unselected campaigns, highlight the selected one for id,button in pairs(newGameCampaignButtons) do if id == campaignID then button.backgroundColor = {1,1,1,1} @@ -531,17 +553,21 @@ local function SelectCampaign(campaignID) end -------------------------------------------------------------------------------- +-- star map stuff -------------------------------------------------------------------------------- local function IsMissionUnlocked(missionID) local mission = missionDefs[missionID] if not mission then return false end - if gamedata.completedMissions[requiredMissionID] then + -- any completed missions are considered unlocked + if gamedata.completedMissions[missionID] then return true + -- no missions required to lock this elseif #(mission.requiredMissions or {}) == 0 then return true end + -- check if we have completed any set of required missions for j, requiredMissionSet in ipairs(mission.requiredMissions) do for k, requiredMissionID in ipairs(requiredMissionSet) do if gamedata.completedMissions[requiredMissionID] then @@ -552,12 +578,11 @@ local function IsMissionUnlocked(missionID) return false end +-- returns true if any missions on the planet have been completed or unlocked local function IsPlanetVisible(planetDef) for i=1,#planetDef.missions do local missionID = planetDef.missions[i] - if gamedata.completedMissions[missionID] then - return true - elseif IsMissionUnlocked(missionID) then + if IsMissionUnlocked(missionID) then return true end end @@ -571,6 +596,7 @@ local function CloseStarMap() end end +-- Called when clicking back button on planet detail panel local function BackToStarmap() starmapAnimation = "out" --starmapBackground2.file = nil @@ -581,6 +607,7 @@ local function BackToStarmap() starmapClose:SetLayer(1) end +-- Called when clicking a planet button on the galaxy map local function SelectPlanet(planetID) local planetDef = planetDefsByID[planetID] starmapAnimation = "in" @@ -653,6 +680,7 @@ local function SelectPlanet(planetID) } } } + -- list of missions on this planet local missionsStack = StackPanel:New { parent = starmapInfoPanel, orientation = "vertical", @@ -781,6 +809,7 @@ local function MakeStarMap() backgroundColor = {0, 0, 0, 0}, padding = {0,0,0,0} } + -- this is the "local" star background that gets drawn in planet detail screen starmapBackground2 = Image:New{ name = "chobby_campaign_starmapBackground2", parent = starmapBackgroundHolder, @@ -798,6 +827,7 @@ local function MakeStarMap() --starmapBackground2.width = 0 --starmapBackground2.height = 0 + -- Planet image in detail screen starmapPlanetImage = Image:New{ name = "chobby_campaign_starmapPlanetImage", parent = starmapBackground2, @@ -808,7 +838,7 @@ local function MakeStarMap() file = "", color = {1,1,1,0} } - + -- this background is for the galaxy overview starmapBackground = Image:New{ name = "chobby_campaign_starmapBackground", parent = starmapBackgroundHolder, @@ -944,8 +974,9 @@ local function AdvanceCampaign(completedMissionID, nextScript, chapterTitle) SaveGame(AUTOSAVE_ID) end -------------------------------------------------------------------------------- +-- Save/Load UI -------------------------------------------------------------------------------- - +-- Generates a button for each available campaign, in the New Game screen local function AddCampaignSelectorButtons() for i=1,#campaignDefs do local def = campaignDefs[i] @@ -1005,6 +1036,7 @@ local function RemoveSaveEntryButtons() saveLoadControls = {} end +-- Makes a button for a save game on the save/load screen local function AddSaveEntryButton(saveFile, allowSave, count) local id = saveFile and saveFile.id or #savesOrdered + 1 local controlsEntry = {id = id} @@ -1093,6 +1125,7 @@ local function AddSaveEntryButton(saveFile, allowSave, count) end +-- Generates the buttons for the savegames on the save/load screen local function AddSaveEntryButtons(allowSave) local startIndex = #savesOrdered local count = 0 @@ -1120,6 +1153,9 @@ local function OpenSaveOrLoadMenu(save) end end +-------------------------------------------------------------------------------- +-- Make Chili controls +-------------------------------------------------------------------------------- local function InitializeMainControls() -- main menu screens.main = Panel:New { @@ -1386,8 +1422,8 @@ local function InitializeIntermissionControls() CleanupAfterMission() SwitchToScreen("main") ResetGamedata() - if WG.Music then - WG.Music.StartTrack() + if WG.MusicHandler then + WG.MusicHandler.StartTrack() end end }) end, @@ -1607,7 +1643,7 @@ local function InitializeControls() Button = Chili.Button -- window to hold things - window = Window:New { + window = Panel:New { name = 'chobby_campaign_window', x = 0, y = 0, @@ -1615,7 +1651,8 @@ local function InitializeControls() height = "100%", resizable = false, draggable = false, - --padding = {0, 0, 0, 0}, + padding = {0, 0, 0, 0}, + backgroundColor = {0, 0, 0, 0} } InitializeMainControls() @@ -1637,6 +1674,8 @@ function widget:Update() local dt = Spring.DiffTimers(currentTime, timer) timer = currentTime + -- animation in: expands the planet image and star background image when opening a planet detail screen + -- animation out: shrinks and disappears those images when returning to galaxy map if starmapAnimation then animElapsed = animElapsed + dt local stage = animElapsed/ANIMATION_TIME @@ -1677,7 +1716,7 @@ function widget:ActivateMenu() end function widget:DownloadFinished() - if VFS.HasArchive(requiredMap) then + if requiredMap and VFS.HasArchive(requiredMap) then requiredMap = nil end end diff --git a/README.md b/README.md index 449de0cb4..a7f614d49 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,38 @@ Chobby, the ingame lobby. Installation ============ -Chobby is on rapid, under the "chobby" tag. The development version can be obtained directly from here. + +There are multiple ways you can install Chobby. + +#### 1. Chobby.exe wrapper (recommended) #### + +Download `chobby.exe` from http://zero-k.info/lobby/chobby.exe and run it. It will download the newest version of Chobby as well as the required engine, and automatically start it. Should work under Windows and Linux. + +#### 2. Rapid (pr-downloader) #### + +Chobby is on rapid, under the "chobby" tag. To download with pr-downloader use this command line: - pr-downloader chobby:test "Blank v1" + pr-downloader chobby:test + +You will need to obtain the appropriate engine and configure it manually. + +#### 3. Development version from Github #### + +The development version of Chobby can be obtained directly from here, and should be placed in your games subdirectory. +Example (execute in your games folder): + + git clone https://github.com/Spring-Chobby/Chobby.git Chobby.sdd Launching ========= -You can launch Chobby from spring start menu like any other game. +If you're using the wrapper, you don't need to read this. Just go ahead and run the executable. + +Chobby is a Lua Menu and requires the newest engine. For exact version, see the engine tag in `modinfo.lua` (https://github.com/Spring-Chobby/Chobby/blob/master/modinfo.lua#L19). -It is suggested to select "Player Only" script and it's a good idea to use [Blank v1](http://api.springfiles.com/?springname=Blank%20v1&category=map) as your map. Other small maps can also work pretty well. +For development version of Chobby (obtained from Github), you can launch Chobby by running `spring --menu 'Chobby $VERSION'` or by setting `DefaultLuaMenu = Chobby $VERSION` in your springsettings.cfg (https://springrts.com/wiki/Springsettings.cfg). Versions obtained from Github should follow the same procedure, but set the appropriate fixed version instead, e.g. `Chobby test-1145-2d0ef36`, instead of `Chobby $VERSION`. [![Join the chat at https://gitter.im/Spring-Chobby/Chobby](https://badges.gitter.im/Spring-Chobby/Chobby.svg)](https://gitter.im/Spring-Chobby/Chobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/Sounds/lobbyMusic/A Magnificent Journey (Alternative Version).ogg b/Sounds/lobbyMusic/A Magnificent Journey (Alternative Version).ogg new file mode 100644 index 000000000..1e98719d3 Binary files /dev/null and b/Sounds/lobbyMusic/A Magnificent Journey (Alternative Version).ogg differ diff --git a/Sounds/lobbyMusic/Dream Infinity.ogg b/Sounds/lobbyMusic/Dream Infinity.ogg new file mode 100644 index 000000000..515a9c0af Binary files /dev/null and b/Sounds/lobbyMusic/Dream Infinity.ogg differ diff --git a/Sounds/lobbyMusic/Interstellar.ogg b/Sounds/lobbyMusic/Interstellar.ogg new file mode 100644 index 000000000..b86ac2248 Binary files /dev/null and b/Sounds/lobbyMusic/Interstellar.ogg differ diff --git a/Sounds/lobbyMusic/The Secret of Ayers Rock.ogg b/Sounds/lobbyMusic/The Secret of Ayers Rock.ogg new file mode 100644 index 000000000..9d52c0150 Binary files /dev/null and b/Sounds/lobbyMusic/The Secret of Ayers Rock.ogg differ diff --git a/Sounds/lobbyMusic/Tomorrow Landscape.ogg b/Sounds/lobbyMusic/Tomorrow Landscape.ogg new file mode 100644 index 000000000..db691d0a0 Binary files /dev/null and b/Sounds/lobbyMusic/Tomorrow Landscape.ogg differ diff --git a/Sounds/lobbyMusic/licenses.txt b/Sounds/lobbyMusic/licenses.txt new file mode 100644 index 000000000..b92c92d14 --- /dev/null +++ b/Sounds/lobbyMusic/licenses.txt @@ -0,0 +1,13 @@ +== Denny Schneidemesser == +CC-BY-NC-ND +see http://www.soundclick.com/bands/page_licensing.cfm?bandID=604509 + A Magnificent Journey (Alternative Version) + + +== Grégoire Lourme == +CC-BY-ND or CC-BY-SA (website download says ND, but original mp3s' comments say SA) + Tomorrow Landscape: https://www.jamendo.com/track/1103298/tomorrow-landscape + Dream Infinity: https://www.jamendo.com/track/1103300/dream-infinity + Interstellar: https://www.jamendo.com/track/1103295/interstellar + + diff --git a/Sounds/matchFound.wav b/Sounds/matchFound.wav new file mode 100644 index 000000000..7f9a018db Binary files /dev/null and b/Sounds/matchFound.wav differ diff --git a/Sounds/origin.txt b/Sounds/origin.txt new file mode 100644 index 000000000..0a29a1a2a --- /dev/null +++ b/Sounds/origin.txt @@ -0,0 +1 @@ +matchFound.wav - Public domain sound from Golgotha diff --git a/libs/chiliui/luamenu/chili/chili/controls/editbox.lua b/libs/chiliui/luamenu/chili/chili/controls/editbox.lua index 8f4a7c420..e1a72ddf0 100644 --- a/libs/chiliui/luamenu/chili/chili/controls/editbox.lua +++ b/libs/chiliui/luamenu/chili/chili/controls/editbox.lua @@ -263,10 +263,12 @@ function EditBox:_GeneratePhysicalLines(logicalLineID) end -- will automatically wrap into multiple lines if too long -function EditBox:AddLine(text) +function EditBox:AddLine(text, tooltips, OnTextClick) -- add logical line local line = { text = text, + tooltips = tooltips, + OnTextClick = OnTextClick, pls = {}, -- indexes of physical lines } table.insert(self.lines, line) @@ -391,13 +393,22 @@ function EditBox:Update(...) end end -function EditBox:_SetCursorByMousePos(x, y) +function EditBox:_GetCursorByMousePos(x, y) + local retVal = { + offset = self.offset, + cursor = self.cursor, + cursorY = self.cursorY, + physicalCursor = self.physicalCursor, + physicalCursorY = self.physicalCursorY + } + local clientX, clientY = self.clientArea[1], self.clientArea[2] if not self.multiline and x - clientX < 0 then - self.offset = self.offset - 1 - self.offset = math.max(0, self.offset) - self.cursor = self.offset + 1 - self.cursorY = 1 + retVal.offset = retVal.offset - 1 + retVal.offset = math.max(0, retVal.offset) + retVal.cursor = retVal.offset + 1 + retVal.cursorY = 1 + return retVal else local text = self.text -- properly accounts for passworded text where characters are represented as "*" @@ -405,37 +416,37 @@ function EditBox:_SetCursorByMousePos(x, y) if #text > 0 and self.passwordInput then text = string.rep("*", #text) end - self.cursorY = #self.physicalLines + retVal.cursorY = #self.physicalLines for i, line in pairs(self.physicalLines) do if line.y > y - clientY then - self.cursorY = math.max(1, i-1) + retVal.cursorY = math.max(1, i-1) break end end - local selLine = self.physicalLines[self.cursorY] - if not selLine then return end + local selLine = self.physicalLines[retVal.cursorY] + if not selLine then return retVal end selLine = selLine.text if not self.multiline then selLine = text end - self.cursor = #selLine + 1 + retVal.cursor = #selLine + 1 for i = 1, #selLine do - local tmp = selLine:sub(1 + self.offset, i) + local tmp = selLine:sub(1 + retVal.offset, i) if self.font:GetTextWidth(tmp) > (x - clientX) then - self.cursor = i + retVal.cursor = i break end end -- convert back to logical line - self.physicalCursorY = self.cursorY - self.physicalCursor = self.cursor + retVal.physicalCursorY = retVal.cursorY + retVal.physicalCursor = retVal.cursor - local physicalLine = self.physicalLines[self.physicalCursorY] - self.cursorY = physicalLine.lineID + local physicalLine = self.physicalLines[retVal.physicalCursorY] + retVal.cursorY = physicalLine.lineID - local logicalLine = self.lines[self.cursorY] + local logicalLine = self.lines[retVal.cursorY] for i, plID in pairs(logicalLine.pls) do -- FIXME when less tired -- if i > 1 or #physicalLine.text + 1 == self.physicalCursor then @@ -444,10 +455,10 @@ function EditBox:_SetCursorByMousePos(x, y) -- if i > 1 then -- self.cursor = self.cursor - 1 -- end - if plID == self.physicalCursorY then + if plID == retVal.physicalCursorY then break end - self.cursor = self.cursor + #self.physicalLines[plID].text + retVal.cursor = retVal.cursor + #self.physicalLines[plID].text end -- if logicalLine.pls[#logicalLine.pls] ~= self.physicalCursorY and then -- self.cursor = self.cursor - 1 @@ -469,10 +480,40 @@ function EditBox:_SetCursorByMousePos(x, y) -- break -- end -- end + return retVal end end +function EditBox:_SetCursorByMousePos(x, y) + local retVal = self:_GetCursorByMousePos(x, y) + self.offset = retVal.offset + self.cursor = retVal.cursor + self.cursorY = retVal.cursorY + self.physicalCursor = retVal.physicalCursor + self.physicalCursorY = retVal.physicalCursorY +end + + function EditBox:MouseDown(x, y, ...) + -- FIXME: didn't feel like reimplementing Screen:MouseDown to capture MouseClick correctly, so clicking on text items is triggered in MouseDown + -- handle clicking on text items + local retVal = self:_GetCursorByMousePos(x, y) + local line = self.lines[retVal.cursorY] + if line and line.OnTextClick then + local cx, cy = self:ScreenToLocal(x, y) + for _, OnTextClick in pairs(line.OnTextClick) do + if OnTextClick.startIndex <= retVal.cursor and OnTextClick.endIndex >= retVal.cursor then + for _, f in pairs(OnTextClick.OnTextClick) do + f(self, cx, cy, ...) + end + self._interactedTime = Spring.GetTimer() + inherited.MouseDown(self, x, y, ...) + self:Invalidate() + return self + end + end + end + if not self.selectable then return false end @@ -498,6 +539,18 @@ function EditBox:MouseDown(x, y, ...) end function EditBox:MouseMove(x, y, dx, dy, button) + if button == nil then -- handle tooltips + local retVal = self:_GetCursorByMousePos(x, y) + local line = self.lines[retVal.cursorY] + if line and line.tooltips then + for _, tooltip in pairs(line.tooltips) do + if tooltip.startIndex <= retVal.cursor and tooltip.endIndex >= retVal.cursor then + Screen0.currentTooltip = tooltip.tooltip + end + end + end + end + if button ~= 1 then return inherited.MouseMove(self, x, y, dx, dy, button) end diff --git a/libs/chilivn/luamenu/Widgets/campaign_chili_vn.lua b/libs/chilivn/luamenu/Widgets/campaign_chili_vn.lua index 6fe703ff3..e94dfbeb9 100644 --- a/libs/chilivn/luamenu/Widgets/campaign_chili_vn.lua +++ b/libs/chilivn/luamenu/Widgets/campaign_chili_vn.lua @@ -278,10 +278,10 @@ local function PlayScriptLine(line) -- automatically hide text box while in wait mode, show otherwise if not data.nvlMode then if (action == 'AddText' or (not waitTime)) and textPanel.hidden then - textPanel:Show() + textPanel:SetVisibility(true) ResetMainLayers() elseif not textPanel.hidden and (type(waitTime) == 'number' and (waitTime > 0)) then - textPanel:Hide() + textPanel:SetVisibility(false) end end end @@ -296,7 +296,7 @@ end local function StartScript(scriptName) if mainWindow.hidden then - mainWindow:Show() + mainWindow:SetVisibility(true) end ResetMainLayers() data.scriptCallstack = {{scriptName, 1}} @@ -310,8 +310,8 @@ local function ResizeNVLEntryPanel(textControl, nvlControlsEntry) local height = textControl.height + panel.padding[2] + panel.padding[4] if (panel.height < height) then panel:Resize(nil, height, true, true) - panel:Hide() -- force refresh - panel:Show() + panel:SetVisibility(false) -- force refresh + panel:SetVisibility(true) --Spring.Echo("force resizing", textControl.height, panel.height) end end @@ -860,7 +860,7 @@ local function Cleanup() image:Dispose() end if nvlPanel.visible and nvlPanel.parent then - nvlPanel:Hide() + nvlPanel:SetVisibility(false) end scriptFunctions.ClearNVL() animations = {} @@ -904,7 +904,7 @@ local function CloseStory() images = {} } if (not mainWindow.hidden) then - mainWindow:Hide() + mainWindow:SetVisibility(false) end end @@ -974,7 +974,7 @@ scriptFunctions = { end, Exit = function() - mainWindow:Hide() + mainWindow:SetVisibility(false) if WG.Music then --WG.Music.StartTrack() end @@ -1078,11 +1078,11 @@ scriptFunctions = { data.nvlMode = bool if not uiHidden then if (bool) then - textPanel:Hide() - nvlPanel:Show() + textPanel:SetVisibility(false) + nvlPanel:SetVisibility(true) else - textPanel:Show() - nvlPanel:Hide() + textPanel:SetVisibility(true) + nvlPanel:SetVisibility(false) end ResetMainLayers() end @@ -1126,9 +1126,9 @@ scriptFunctions = { -- Show/hide the menu buttons local function ToggleMenu() if menuVisible then - menuStack:Hide() + menuStack:SetVisibility(false) else - menuStack:Show() + menuStack:SetVisibility(true) end ResetMainLayers() menuVisible = not menuVisible @@ -1137,29 +1137,29 @@ end local function ToggleUI() if uiHidden then if data.nvlMode then - nvlPanel:Show() + nvlPanel:SetVisibility(true) else - textPanel:Show() + textPanel:SetVisibility(true) end - menuButton:Show() + menuButton:SetVisibility(true) if menuVisible then - menuStack:Show() + menuStack:SetVisibility(true) end if panelChoiceDialog ~= nil then - panelChoiceDialog:Show() + panelChoiceDialog:SetVisibility(true) end else if data.nvlMode then - nvlPanel:Hide() + nvlPanel:SetVisibility(false) else - textPanel:Hide() + textPanel:SetVisibility(false) end - menuButton:Hide() + menuButton:SetVisibility(false) if menuVisible then - menuStack:Hide() + menuStack:SetVisibility(false) end if panelChoiceDialog ~= nil then - panelChoiceDialog:Hide() + panelChoiceDialog:SetVisibility(false) end end ResetMainLayers(true) @@ -1609,7 +1609,7 @@ function widget:Initialize() caption = "QUIT", width = MENU_BUTTON_WIDTH, height = MENU_BUTTON_HEIGHT, - OnClick = {function() Cleanup(); mainWindow:Hide() + OnClick = {function() Cleanup(); mainWindow:SetVisibility(false) if WG.Music then --WG.Music.StartTrack() end @@ -1635,7 +1635,7 @@ function widget:Initialize() padding = {0, 0, 0, 0}, children = menuChildren, } - menuStack:Hide() + menuStack:SetVisibility(false) textPanel = Panel:New { parent = mainWindow, @@ -1766,8 +1766,8 @@ function widget:Initialize() keepAspect = false, } - nvlPanel:Hide() - mainWindow:Hide() + nvlPanel:SetVisibility(false) + mainWindow:SetVisibility(false) WG.VisualNovel = { GetDefs = GetDefs, diff --git a/libs/chotify/chotify/chotify.lua b/libs/chotify/chotify/chotify.lua index 5c934b2d6..bd13e4768 100644 --- a/libs/chotify/chotify/chotify.lua +++ b/libs/chotify/chotify/chotify.lua @@ -44,7 +44,7 @@ function Chotify:Post(obj) local time = obj.time or 5 if obj.sound then - Spring.PlaySoundFile(obj.sound, 1.0) + Spring.PlaySoundFile(obj.sound, obj.soundVolume or 1) end local id = self._idCounter @@ -61,14 +61,12 @@ function Chotify:Post(obj) resizable = false, } if type(body) == "string" then - Chili.Label:New { - x = 0, - right = 20, - y = 0, - bottom = 10, - valign = 'center', - align = 'center', - caption = body, + Chili.TextBox:New { + x = 5, + right = 5, + y = 15, + bottom = 5, + text = body, parent = window, } else diff --git a/libs/liblobby/lobby/interface_shared.lua b/libs/liblobby/lobby/interface_shared.lua index b9eb6c3c3..906916eb0 100644 --- a/libs/liblobby/lobby/interface_shared.lua +++ b/libs/liblobby/lobby/interface_shared.lua @@ -26,10 +26,13 @@ function Interface:init() self:super("init") end -function Interface:Connect(host, port) +function Interface:Connect(host, port, user, password, cpu, localIP, lobbyVersion) self:super("Connect", host, port) self.client = socket.tcp() self.client:settimeout(0) + + self.loginData = {user, password, cpu, localIP, lobbyVersion} + self._startedConnectingTime = os.clock() local res, err = self.client:connect(host, port) if res == nil and err == "host not found" then diff --git a/libs/liblobby/lobby/interface_skirmish.lua b/libs/liblobby/lobby/interface_skirmish.lua index 9b642d469..b3e3c34f1 100644 --- a/libs/liblobby/lobby/interface_skirmish.lua +++ b/libs/liblobby/lobby/interface_skirmish.lua @@ -40,8 +40,6 @@ function InterfaceSkirmish:_StartScript(gameName, mapName, playerName) local ais = {} local aiCount = 0 - local allyTeamMap = {} - for userName, data in pairs(self.userBattleStatus) do if data.allyNumber then if data.aiLib then @@ -73,17 +71,27 @@ function InterfaceSkirmish:_StartScript(gameName, mapName, playerName) end end end - + for i, teamData in pairs(teams) do - if not allyTeamMap[teamData.AllyTeam] then - allyTeamMap[teamData.AllyTeam] = allyTeamCount - allyTeams[allyTeamCount] = { + if not allyTeams[teamData.AllyTeam] then + allyTeams[teamData.AllyTeam] = { numallies = 0, } - allyTeamCount = allyTeamCount + 1 end - teamData.AllyTeam = allyTeamMap[teamData.AllyTeam] end + + -- This kind of thing would prevent holes in allyTeams + --local allyTeamMap = {} + --for i, teamData in pairs(teams) do + -- if not allyTeamMap[teamData.AllyTeam] then + -- allyTeamMap[teamData.AllyTeam] = allyTeamCount + -- allyTeams[allyTeamCount] = { + -- numallies = 0, + -- } + -- allyTeamCount = allyTeamCount + 1 + -- end + -- teamData.AllyTeam = allyTeamMap[teamData.AllyTeam] + --end local script = { gametype = gameName, @@ -96,6 +104,7 @@ function InterfaceSkirmish:_StartScript(gameName, mapName, playerName) numplayers = playerCount, numusers = playerCount + aiCount, startpostype = 2, + modoptions = self.modoptions, } for i, ai in pairs(ais) do diff --git a/libs/liblobby/lobby/interface_zerok.lua b/libs/liblobby/lobby/interface_zerok.lua index eada97506..9a2d70b77 100644 --- a/libs/liblobby/lobby/interface_zerok.lua +++ b/libs/liblobby/lobby/interface_zerok.lua @@ -698,7 +698,8 @@ function Interface:_BattleAdded(data) header.IsRunning, header.RunningSince, header.Mode, header.Mode ~= 0, -- Is Custom - (header.Mode ~= 5 and header.Mode ~= 0) -- Is Bots + (header.Mode ~= 5 and header.Mode ~= 0), -- Is Bots + header.IsMatchMaker ) end Interface.jsonCommands["BattleAdded"] = Interface._BattleAdded @@ -728,12 +729,26 @@ function Interface:_BattleUpdate(data) Spring.Log(LOG_SECTION, LOG.ERROR, "Interface:_BattleUpdate no such battle with ID: " .. tostring(header.BattleID)) return end + + self:_OnUpdateBattleInfo( + header.BattleID, + header.SpectatorCount, + header.Locked, + 0, + header.Map, + header.Engine, + header.RunningSince, + header.Game, + header.Mode, + header.Mode ~= 0, -- Is Custom + (header.Mode ~= 5 and header.Mode ~= 0), -- Is Bots + header.IsMatchMaker + ) + if header.IsRunning ~= nil then + -- battle.RunningSince should be set by this point. self:_OnBattleIngameUpdate(header.BattleID, header.IsRunning) end - - Spring.Echo("header.Gameheader.Game", header.Game) - self:_OnUpdateBattleInfo(header.BattleID, header.SpectatorCount, header.Locked, 0, header.Map, header.Engine, header.RunningSince, header.Game, header.Mode) end Interface.jsonCommands["BattleUpdate"] = Interface._BattleUpdate @@ -820,7 +835,7 @@ local function FindLastOccurence(mainString, subString) end function Interface:ProcessVote(data, battle, duplicateMessageTime) - if (not battle) and battle.founder == data.User then + if (not battle) or battle.founder == data.User then return false end local message = data.Text @@ -946,13 +961,19 @@ function Interface:_MatchMakerSetup(data) self.queues = {} for i = 1, #queues do local queue = queues[i] - self:_OnQueueOpened(queue.Name, queue.Description, queue.Maps, queue.MaxPartySize, {"Zero-K v1.4.9.1"}) + + -- Remove when server updates + if queue.Game == "zk:stable" then + queue.Game = "Zero-K v1.4.9.3" + end + + self:_OnQueueOpened(queue.Name, queue.Description, queue.Maps, queue.MaxPartySize, {queue.Game}) end end Interface.jsonCommands["MatchMakerSetup"] = Interface._MatchMakerSetup function Interface:_MatchMakerStatus(data) - self:_OnMatchMakerStatus(data.MatchMakerEnabled, data.JoinedQueues, data.QueueCounts, data.CurrentEloWidth, data.JoinedTime, data.BannedSeconds) + self:_OnMatchMakerStatus(data.MatchMakerEnabled, data.JoinedQueues, data.QueueCounts, data.IngameCounts, data.InstantStartQueues, data.CurrentEloWidth, data.JoinedTime, data.BannedSeconds) end Interface.jsonCommands["MatchMakerStatus"] = Interface._MatchMakerStatus @@ -974,8 +995,7 @@ Interface.jsonCommands["AreYouReadyUpdate"] = Interface._AreYouReadyUpdate function Interface:_AreYouReadyResult(data) self:_OnMatchMakerReadyResult(data.IsBattleStarting, data.AreYouBanned) end -Interface.jsonCommands["AreYouReadyResult"] = Interface.AreYouReadyResult - +Interface.jsonCommands["AreYouReadyResult"] = Interface._AreYouReadyResult ------------------- -- Unimplemented -- diff --git a/libs/liblobby/lobby/lobby.lua b/libs/liblobby/lobby/lobby.lua index 6860139d6..1ba51cc57 100644 --- a/libs/liblobby/lobby/lobby.lua +++ b/libs/liblobby/lobby/lobby.lua @@ -338,10 +338,7 @@ end ------------------------ function Lobby:_OnConnect(protocolVersion, springVersion, udpPort, serverMode) - if self.status == "disconnected" and self.disconnectTime ~= nil then -- in the process of reconnecting - self.disconnectTime = nil - self:Login(unpack(self._oldData.loginData)) - end + self:Login(unpack(self.loginData)) self:_CallListeners("OnConnect", protocolVersion, springVersion, udpPort, serverMode) end @@ -568,18 +565,18 @@ end function Lobby:_OnBattleOpened(battleID, type, natType, founder, ip, port, maxPlayers, passworded, rank, mapHash, other, engineVersion, mapName, title, gameName, spectatorCount, isRunning, runningSince, - battleMode, disallowCustomTeams, disallowBots) + battleMode, disallowCustomTeams, disallowBots, isMatchMaker) self.battles[battleID] = { battleID = battleID, type = type, natType = natType, founder = founder, ip = ip, port = port, maxPlayers = maxPlayers, passworded = passworded, rank = rank, mapHash = mapHash, spectatorCount = spectatorCount or 0, engineName = engineName, engineVersion = engineVersion, mapName = mapName, title = title, gameName = gameName, users = {}, isRunning = isRunning, runningSince = runningSince, - battleMode = battleMode, disallowCustomTeams = disallowCustomTeams, disallowBots = disallowBots + battleMode = battleMode, disallowCustomTeams = disallowCustomTeams, disallowBots = disallowBots, isMatchMaker = isMatchMaker } self.battleCount = self.battleCount + 1 self:_CallListeners("OnBattleOpened", battleID, type, natType, founder, ip, port, maxPlayers, passworded, rank, mapHash, - engineName, engineVersion, map, title, gameName, spectatorCount, isRunning, runningSince, battleMode) + engineName, engineVersion, map, title, gameName, spectatorCount, isRunning, runningSince, battleMode, isMatchMaker) end function Lobby:_OnBattleClosed(battleID) @@ -596,8 +593,18 @@ function Lobby:_OnJoinBattle(battleID, hashCode) end function Lobby:_OnJoinedBattle(battleID, userName, scriptPassword) - table.insert(self.battles[battleID].users, userName) - + local found = false + local users = self.battles[battleID].users + for i = 1, #users do + if users[i] == userName then + found = true + break + end + end + if not found then + table.insert(self.battles[battleID].users, userName) + end + self.users[userName].battleID = battleID self:_CallListeners("OnUpdateUserStatus", userName, {battleID = battleID}) @@ -631,7 +638,7 @@ function Lobby:_OnLeftBattle(battleID, userName) self:_CallListeners("OnLeftBattle", battleID, userName) end -function Lobby:_OnUpdateBattleInfo(battleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode) +function Lobby:_OnUpdateBattleInfo(battleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode, disallowCustomTeams, disallowBots, isMatchMaker) local battle = self.battles[battleID] battle.spectatorCount = spectatorCount or battle.spectatorCount battle.locked = locked or battle.locked @@ -641,8 +648,12 @@ function Lobby:_OnUpdateBattleInfo(battleID, spectatorCount, locked, mapHash, ma battle.runningSince = runningSince or battle.runningSince battle.gameName = gameName or battle.gameName battle.battleMode = battleMode or battle.battleMode - Spring.Echo("_OnUpdateBattleInfo_OnUpdateBattleInfo", gameName) - self:_CallListeners("OnUpdateBattleInfo", battleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode) + battle.disallowCustomTeams = disallowCustomTeams or battle.disallowCustomTeams + battle.disallowBots = disallowBots or battle.disallowBots + battle.isMatchMaker = isMatchMaker or battle.isMatchMaker + + Spring.Echo("_OnUpdateBattleInfo_OnUpdateBattleInfo", gameName) + self:_CallListeners("OnUpdateBattleInfo", battleID, spectatorCount, locked, mapHash, mapName, engineVersion, runningSince, gameName, battleMode, disallowCustomTeams, disallowBots, isMatchMaker) end -- Updates the specified status keys @@ -871,7 +882,9 @@ function Lobby:_OnQueueOpened(name, description, mapNames, maxPartSize, gameName description = description, mapNames = mapNames, maxPartSize = maxPartSize, - gameNames = gameNames + gameNames = gameNames, + playersIngame = 0, + playersWaiting = 0, } self.queueCount = self.queueCount + 1 @@ -887,7 +900,7 @@ function Lobby:_OnQueueClosed(name) self:_CallListeners("OnQueueClosed", name) end -function Lobby:_OnMatchMakerStatus(inMatchMaking, joinedQueueList, queueCounts, currentEloWidth, joinedTime, bannedTime) +function Lobby:_OnMatchMakerStatus(inMatchMaking, joinedQueueList, queueCounts, ingameCounts, instantStartQueues, currentEloWidth, joinedTime, bannedTime) if inMatchMaking then self.joinedQueueList = joinedQueueList self.joinedQueues = {} @@ -899,7 +912,16 @@ function Lobby:_OnMatchMakerStatus(inMatchMaking, joinedQueueList, queueCounts, self.joinedQueueList = nil end - self:_CallListeners("OnMatchMakerStatus", inMatchMaking, joinedQueueList, queueCounts, currentEloWidth, joinedTime, bannedTime) + self.matchMakerBannedTime = bannedTime + + if queueCounts or ingameCounts then + for name, queueData in pairs(self.queues) do + queueData.playersIngame = (ingameCounts and ingameCounts[name]) or queueData.playersIngame + queueData.playersWaiting = (queueCounts and queueCounts[name]) or queueData.playersWaiting + end + end + + self:_CallListeners("OnMatchMakerStatus", inMatchMaking, joinedQueueList, queueCounts, ingameCounts, instantStartQueues, currentEloWidth, joinedTime, bannedTime) end function Lobby:_OnMatchMakerReadyCheck(secondsRemaining) @@ -982,7 +1004,7 @@ end function Lobby:Reconnect() self.lastReconnectionAttempt = Spring.GetTimer() - self:Connect(self._oldData.host, self._oldData.port) + self:Connect(self._oldData.host, self._oldData.port, self._oldData.loginData[1], self._oldData.loginData[2], self._oldData.loginData[3], self._oldData.loginData[4]) end function Lobby:SafeUpdate(...) diff --git a/libs/liblobby/lobby/observable.lua b/libs/liblobby/lobby/observable.lua index 6ef4d6901..87c20b33c 100644 --- a/libs/liblobby/lobby/observable.lua +++ b/libs/liblobby/lobby/observable.lua @@ -46,5 +46,10 @@ function Lobby:_CallListeners(event, ...) end function Lobby:_PrintError(err) + Spring.Echo("_PrintError") + Spring.Echo(LOG_SECTION) + Spring.Echo(LOG.ERROR) + Spring.Echo(err) + Spring.Echo(debug.traceback(err)) Spring.Log(LOG_SECTION, LOG.ERROR, debug.traceback(err)) end diff --git a/modinfo.lua b/modinfo.lua index 8446f8277..e21a26310 100644 --- a/modinfo.lua +++ b/modinfo.lua @@ -16,7 +16,7 @@ local modinfo = { "Spring content v1", }, onlyLocal = true, - engine = "103.0.1-129-g98e3979", + engine = "103.0.1-131-g5a68792", } return modinfo