#include "ui/ui_extensions.lua" #include "ui/ui_helpers.lua" #include "buttons.lua" #include "game.lua" #include "game_types.lua" ModViewer = {} ModViewer = Ui.Window { w = 1920, h = 1080, animator = { playTime = 0.25 }, modsType = IsRunningOnPC() and ModsType.PROS or ModsType.LOCAL, Ui.Text { x = 60, y = 40, font = { "bold.ttf", 50 }, text = "loc@UI_MOD_BROWSER" }, Ui.Grid { id = "gridView", x = 60, y = 120, w = 1090, h = 822, columnCount = 3, rowsIndent = 20, columsIndent = 20, state = { background = { w = 14, h = 822, color = {0, 0, 0, 0.9} }, thumb = { w = 10, color = {1, 1, 1, 0.9} } }, isFocused = true, }, Ui.Frame { x = 1180, y = 120, w = 680, h = 822, ignoreNavigation = true, Ui.ImageBox { resizeToParent = true, border = { width = 10, height = 10 }, color = {0, 0, 0, 0.6}, path = "ui/common/box-solid-10.png" }, Ui.Frame { id = "modDetails", resizeToParent = true, Ui.VerticalLayout { margin = { top = 15, bottom = 30, left = 15, right = 20 }, padding = 15, Ui.Text { id = "modTitle", color = 0xFFFF7F, font = { "bold.ttf", 33 }, wrapWidth = 640 }, Ui.Text { id = "modAuthor", color = 0xFFFFFFCC, font = { "bold.ttf", 27 }, wrapWidth = 640 }, Ui.Text { id = "modTags", color = 0xFFFFFFCC, font = { "bold.ttf", 27 }, wrapWidth = 640 }, Ui.ScrollableArea { margin = { top = 20 }, w = 660, h = 570, Ui.Text { id = "modDesc", color = 0xFFFFFF99, font = { "medium.ttf", 25 }, wrapWidth = 650 } } }, Ui.Text { id = "statusNote", margin = { bottom = 30 }, vAlign = Ui.ContentLayout.Vertical.Bottom, hAlign = Ui.ContentLayout.Horizontal.Center, font = { "regular.ttf", 36 }, visible = false }, Ui.HorizontalLayout { id = "buttonsBar", padding = 20, margin = { bottom = 30 }, vAlign = Ui.ContentLayout.Vertical.Bottom, hAlign = Ui.ContentLayout.Horizontal.Center, alignment = Ui.ContentLayout.Vertical.Middle, Teardown.GenericButton { id = "playBtn", w = 250, h = 50, fontSize = 36, text = "[[menu_accept;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_PLAY"), onLanguageChanged = function(self, lang) self.text = "[[menu_accept;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_PLAY") end, onPressed = function(self) self.root.activeMod:play() end }, Teardown.GenericButton { id = "enableBtn", w = 250, h = 50, fontSize = 36, gpHint = "[[menu_accept;iconsize=42,42;color=255,255,255]]", mainTextKey = "UI_BUTTON_DISABLED", text = "[[menu_accept;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_DISABLED"), onPreDraw = function(self) if not self.root.activeMod then return end if self.root.activeMod.enabled then self.mainTextKey = "UI_BUTTON_ENABLED" else self.mainTextKey = "UI_BUTTON_DISABLED" end self.text = self.gpHint .. GetTranslatedStringByKey(self.mainTextKey) end, onPressed = function(self) self.root.activeMod:toggleActive() end }, Teardown.GenericButton { id = "optionsBtn", w = 250, h = 50, fontSize = 36, text = "[[mmb;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_OPTIONS"), onLanguageChanged = function(self, lang) self.text = "[[mmb;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_OPTIONS") end, onPressed = function(self) self.root.activeMod:showOptions() end }, Teardown.GenericButton { id = "downloadBtn", w = 250, h = 50, fontSize = 36, text = "[[menu_accept;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_DOWNLOAD"), onLanguageChanged = function(self, lang) self.text = "[[menu_accept;iconsize=42,42;color=255,255,255]]" .. GetTranslatedStringByKey("UI_DOWNLOAD") end, onPressed = function(self) Command("dlc.openstore", self.root.activeMod.id) end }, Teardown.GenericButton { id = "subscribeBtn", w = 250, h = 50, fontSize = 36, text = "Subscribe", onPressed = function(self) self.root.activeMod:subscribe() end }, Teardown.GenericButton { id = "unsubscribeBtn", w = 250, h = 50, fontSize = 36, text = "Unsubscribe", onPressed = function(self) self.root.activeMod:unsubscribe() end } } } }, Ui.Frame { x = 60, y = 962, w = 1800, h = 60, Ui.Object:new { w = 1800, h = 60, draw = function(self) UiPush() UiColor(1, 1, 1, 1) UiImageBox("common/rect_c#000_o0.60_cr9.png", self.w, self.h, 10, 10) UiPop() end }, Ui.Text { id = "switchType", hAlign = Ui.ContentLayout.Horizontal.Right, vAlign = Ui.ContentLayout.Vertical.Middle, margin = { right = 150 }, colorFilter = {1,1,1,0.7}, font = {"medium.ttf", 27}, text = "[[mmb;iconsize=42,42;color=255,255,255,255]]" .. "Switch mods type", onLanguageChanged = function(self, lang) self.text = "[[mmb;iconsize=42,42;color=255,255,255,255]]" .. "Switch mods type" end, onPreDraw = function(self) if not GetBool("mods.pros_enabled") or IsRunningOnPC() then self:hide() end if ModViewer.modsType == ModsType.LOCAL then self.text = "[[mmb;iconsize=42,42;color=255,255,255,255]]" .. "Show pros mods" else self.text = "[[mmb;iconsize=42,42;color=255,255,255,255]]" .. "Show local mods" end end, }, Ui.Text { hAlign = Ui.ContentLayout.Horizontal.Right, vAlign = Ui.ContentLayout.Vertical.Middle, margin = { right = 20 }, colorFilter = {1,1,1,0.7}, font = {"medium.ttf", 27}, text = "[[menu_cancel;iconsize=42,42;color=255,255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_EXIT"), onLanguageChanged = function(self, lang) self.text = "[[menu_cancel;iconsize=42,42;color=255,255,255,255]]" .. GetTranslatedStringByKey("UI_BUTTON_EXIT") end } }, onInit = function(self) local canvas = UiCanvasSize() self.x = (canvas.w - self.w) / 2 self.y = (canvas.h - self.h) / 2 self.gridView = self:findById("gridView") self.modTitle = self:findById("modTitle") self.modAuthor = self:findById("modAuthor") self.modTags = self:findById("modTags") self.modDesc = self:findById("modDesc") self.playBtn = self:findById("playBtn") self.enableBtn = self:findById("enableBtn") self.optionsBtn = self:findById("optionsBtn") self.subscribeBtn = self:findById("subscribeBtn") self.unsubscribeBtn = self:findById("unsubscribeBtn") self.downloadBtn = self:findById("downloadBtn") self.statusNote = self:findById("statusNote") self.buttonsBar = self:findById("buttonsBar") self.modDetails = self:findById("modDetails") RegisterListenerTo("DlcAdded", "ModViewerOnDlcsAdded") end, onShow = function(self) self:refreshGrid() local modsShown = ListKeys("mods.available") for i = 1, #modsShown do SetBool("mods.shown." .. modsShown[i], true) end end, refreshGrid = function(self) self.gridView:clear() local gridItems = ModViewer.fetchMods() self.gridView:appendMany(gridItems) if self.activeMod ~= nil then local aMod = tableFirst(gridItems, function(it) return it.id == self.activeMod.id end) if aMod then aMod.selected = true self.gridView:selectItem(aMod) end end self.activeMod = nil end, onClose = function(self) for _, mod in ipairs(self.gridView.children) do UiUnloadImage(mod.previewImage.path) end self.activeMod = nil end, onLogicUpdate = function(self) if InputPressed("menu_cancel") then self:hide() end if GetBool("mods.pros_enabled") and InputPressed("mmb") and not IsRunningOnPC() then if ModViewer.modsType == ModsType.LOCAL then ModViewer.modsType = ModsType.PROS else ModViewer.modsType = ModsType.LOCAL end ModViewer:refreshGrid() end local selectedItem = self.gridView.selectedItem self.modDetails.visible = selectedItem ~= nil if selectedItem == nil then return else -- mark new selected item as selected -- and previous selected item as unselected if self.activeMod == nil then selectedItem.selected = true self.activeMod = selectedItem elseif self.activeMod ~= nil and self.activeMod.id ~= selectedItem.id then self.activeMod.selected = false selectedItem.selected = true self.activeMod = selectedItem end end local details = ModViewer.getModDetails(self.activeMod) self:apply(ModViewer.getModDetailsViewState(details)) -- process input actions if InputPressed("menu_accept") then if self.activeMod.playable then self.activeMod:play() elseif self.activeMod.override then self.activeMod:toggleActive() elseif self.activeMod.downloadable then Command("dlc.openstore", self.activeMod.id) end end if InputPressed("mmb") then self.activeMod:showOptions() end end } function ModViewer.ModPreview(obj) local item = Ui.Frame { w = 350, h = 200, id = "", playable = false, enabled = false, override = false, hasOptions = false, Ui.Image { w = 350, h = 200, id = "image", path = "common/mb_item_preview.png" }, Ui.ImageBox { w = 350, h = 200, border = { width = 9, height = 0 }, path = "common/gradient_with_rc_9.png", color = {1, 1, 1, 0.7} }, Ui.Image { id = "highlight", path = "common/mb_item_selection.png", }, Ui.Frame { w = 56, h = 56, id = "modStatus", Ui.Image { vAlign = Ui.ContentLayout.Vertical.Middle, hAlign = Ui.ContentLayout.Horizontal.Center, color = 0x100000099, path = "common/mb_action_btn_bg.png" }, Ui.Image { id = "actionImg", vAlign = Ui.ContentLayout.Vertical.Middle, hAlign = Ui.ContentLayout.Horizontal.Center } }, Ui.Text { vAlign = Ui.ContentLayout.Vertical.Bottom, hAlign = Ui.ContentLayout.Horizontal.Left, margin = { bottom = 10, left = 10}, id = "title", font = { "bold.ttf", 27 }, wrapWidth = 330 }, onCreate = function(self) self.title = self:findById("title") self.actionImg = self:findById("actionImg") self.modStatus = self:findById("modStatus") self.highlight = self:findById("highlight") self.previewImage = self:findById("image") end, onPreDraw = function(self) self.highlight.visible = self.selected self.title.color = self.selected and 0xFFFF7F or 0xFFFFFF -- update mod state self.enabled = GetBool("mods.available.".. self.id ..".active") self.actionImg.visible = true if self.playable then self.actionImg.path = "common/mb_action_btn_play.png" elseif self.override then if self.enabled then self.actionImg.path = "common/mb_action_btn_active.png" else self.actionImg.path = "common/mb_action_btn_subscribe.png" end else self.actionImg.visible = false end end, toggleActive = function(self) if not self.override then return end if self.enabled then self.enabled = false Command("mods.deactivate", self.id) else self.enabled = true Command("mods.activate", self.id) end end, play = function(self) if not self.playable then return end Command("mods.play", self.id) end, showOptions = function(self) if not self.hasOptions then return end Command("mods.options", self.id) end } return item:new(obj) end function ModViewer.ProsModPreview(obj) local item = ModViewer.ModPreview { prosId = "", pros = true, subscribed = false, state = 0, Ui.Image { vAlign = Ui.ContentLayout.Vertical.Middle, hAlign = Ui.ContentLayout.Horizontal.Center, w = 100, h = 100, id = "spinner", path = "common/spinner.png", visible = false, onPreDraw = function(self) UiAlign("middle center") UiTranslate(50, 50) UiRotate(GetTime() * 100 % 360) end }, onPreDraw = function(self) self.highlight.visible = self.selected self.title.color = self.selected and 0xFFFF7F or 0xFFFFFF -- update mod state local modInfo = UiGetModById(self.prosId) self.playable = modInfo.playable self.override = modInfo.override self.enabled = modInfo.enabled self.subscribed = modInfo.subscribed self.hasOptions = modInfo.options self.state = modInfo.state self.actionImg.visible = true if self.playable then self.actionImg.path = "common/mb_action_btn_play.png" elseif self.override then if self.enabled then self.actionImg.path = "common/mb_action_btn_active.png" else self.actionImg.path = "common/mb_action_btn_subscribe.png" end else self.actionImg.visible = false end self.modStatus.visible = self.subscribed local previewReady = UiProsIsPreviewReady(self.previewImage.path) self.previewImage.visible = previewReady self.spinner.visible = not previewReady end, subscribe = function(self) if self.subscribed then return end UiSubscribeToProsMod(self.prosId) end, unsubscribe = function(self) if not self.subscribed then return end UiUnsubscribeFromProsMod(self.prosId) end } item.spinner = item:findById("spinner") return item:new(obj) end function ModViewer.fetchMods() local mods = {} --TODO: add check for showing pros mods only if necessary if ModViewer.modsType == ModsType.LOCAL then tableCombine(mods, ModViewer.fetchBuiltInMods()) elseif ModViewer.modsType == ModsType.PROS then tableCombine(mods, ModViewer.fetchProsMods()) end return mods end function ModViewer.fetchBuiltInMods() local builtInMods = {} local mods = UiGetModsList(ModsType.LOCAL) table.sort( mods, function(m1, m2) local title1 = m1.listname local title2 = m2.listname return title1 < title2 end ) -- show mods pack if it's not installed local DLC_INSTALLED = 3 if GetInt("dlc.available.UserMods1") ~= DLC_INSTALLED then local mod = ModViewer.ModPreview { id = "UserMods1", title = { text = "Mod Pack #1" }, override = false, enabled = false, playable = false, downloadable = true, previewImage = { path = "common/modpack1.png" } } SetString("mods.available." .. mod.id .. ".description", GetTranslatedStringByKey("UI_MOD_PACK_PROMO")) table.insert(builtInMods, mod) end local isBuiltIn = function(m) local isExpansion = tableContains(gExpansions, function(v) return v.level == m end) local isPros = startsWith(m, "pros-") -- hack for situation when usermods appears in 'mods.available.' local isUserMod = startsWith(m, "usermods") return not isExpansion and not isPros and not isUserMod end for modInfo in filter(mods, function(m) return isBuiltIn(m.id) end) do local mod = ModViewer.ModPreview { id = modInfo.id, title = { text = modInfo.listname }, override = modInfo.override and not modInfo.playable, enabled = modInfo.active, playable = modInfo.playable, hasOptions = modInfo.options } -- very special case for Chronos mod -- TODO: may be we should generalize such special behaviour into special fields in info.txt if string.find(mod.id, "chronos") then mod.override = GetInt("savegame.mod."..mod.id..".final") == 1 or GetInt("savegame.mod."..mod.id..".level") == 6 mod.playable = true mod.play = function(self) Command("mods.options", self.id) end mod.showOptions = function(self) self:toggleActive() end end local previewImage = modInfo.previewimage if previewImage ~= "" then mod.previewImage.path = previewImage end table.insert(builtInMods, mod) end return builtInMods end function ModViewer.fetchProsMods() local mods = UiGetModsList(ModsType.PROS) local prosModTransform = function(it) local prosMod = ModViewer.ProsModPreview { id = "pros-" .. it.id, prosId = it.id, title = { text = it.name }, previewImage = { path = it.icon} } return prosMod end local prosMods = tableTransform(mods, prosModTransform) return prosMods end function ModViewer.getModDetails(mod) if mod.pros then local info = UiSelectedProsModInfo() if info.id ~= mod.prosId then UiSelectProsMod(mod.prosId) info = UiSelectedProsModInfo() end -- overwrite some fields cause UiSelectedProsModInfo updates with delay info.pros = true info.title = info.name info.options = mod.hasOptions info.playable = mod.playable info.override = mod.override info.enabled = mod.enabled info.subscribed = mod.subscribed info.state = mod.state return info elseif mod.downloadable then local info = {} info.downloadable = true info.title = mod.title.text info.longDesc = GetString("mods.available." .. mod.id .. ".description") info.author = "" info.tags = "" info.options = false info.playable = false info.override = false info.enabled = false return info else local info = {} local modInfo = UiGetModById(mod.id) info.title = mod.title.text info.author = modInfo.author info.tags = modInfo.tags info.longDesc = modInfo.description info.options = mod.hasOptions info.playable = mod.playable info.override = mod.override -- very special case for Chronos mod -- TODO: may be we should generalize such special behaviour into special fields in info.txt if string.find(mod.id, "chronos") then info.override = GetInt("savegame.mod."..mod.id..".final") == 1 or GetInt("savegame.mod."..mod.id..".level") == 6 info.id = mod.id info.playable = true end info.enabled = mod.enabled return info end end function ModViewer.getModDetailsViewState(modDetails) local state = {} local modReady = modDetails.state == ModSubscribeState.INSTALLED and modDetails.subscribed and modDetails.pros if modReady then state = { buttonsBar = { visible = true }, statusNote = { visible = false }, unsubscribeBtn = { visible = true }, subscribeBtn = { visible = false }, playBtn = { visible = modDetails.playable }, enableBtn = { visible = modDetails.override }, optionsBtn = { visible = modDetails.options } } elseif not modDetails.subscribed and modDetails.pros then state = { buttonsBar = { visible = true }, statusNote = { visible = false }, unsubscribeBtn = { visible = false }, subscribeBtn = { visible = true }, playBtn = { visible = modDetails.playable }, enableBtn = { visible = modDetails.override }, optionsBtn = { visible = modDetails.options } } elseif not modReady and modDetails.pros then state = { buttonsBar = { visible = false }, statusNote = { text = "Unknown state", visible = true } } if state.statusNote then if modDetails.state == ModSubscribeState.DOWNLOADING or modDetails.state == ModSubscribeState.NONE then state.statusNote.text = "Downloading..." elseif modDetails.state == ModSubscribeState.INSUFFICIENT_SPACE then state.statusNote.text = "No space" end end elseif not modDetails.pros then state = { buttonsBar = { visible = true }, statusNote = { visible = false }, unsubscribeBtn = { visible = false }, subscribeBtn = { visible = false }, playBtn = { visible = modDetails.playable }, enableBtn = { visible = modDetails.override }, optionsBtn = { visible = modDetails.options } } end state.modTitle = { text = modDetails.title } local author = modDetails.author if author ~= "" then state.modAuthor = { visible = true, text = author } else state.modAuthor = { visible = false } end local tags = modDetails.tags or "" if tags ~= "" then state.modTags = { visible = true, text = GetTranslatedStringByKey("UI_TEXT_TAGS") .. " " .. tags } else state.modTags = { visible = false } end state.modDesc = { text = modDetails.longDesc } state.downloadBtn = { visible = modDetails.downloadable == true } -- very special case for Chronos mod -- TODO: may be we should generalize such special behaviour into special fields in info.txt if modDetails.id and string.find(modDetails.id, "chronos") then if state.enableBtn and state.optionsBtn then state.enableBtn.gpHint = "[[mmb;iconsize=42,42;color=255,255,255]]" state.optionsBtn.visible = false end else if state.enableBtn then state.enableBtn.gpHint = "[[menu_accept;iconsize=42,42;color=255,255,255]]" end end return state end function ModViewerOnDlcsAdded() ModViewer:refreshGrid() end