diff --git a/config.json b/config.json
index 45015cf7..bf3cf147 100644
--- a/config.json
+++ b/config.json
@@ -218,5 +218,5 @@
"Tags": [],
"Turns_path": "Turns.json",
"VersionNumber": "v13.2.2",
- "XmlUI": "\u003cInclude src=\"Global.xml\"/\u003e"
+ "XmlUI": "\u003cInclude src=\"Global/Global.xml\"/\u003e"
}
diff --git a/modsettings/CustomUIAssets.json b/modsettings/CustomUIAssets.json
index 402f57df..3a808204 100644
--- a/modsettings/CustomUIAssets.json
+++ b/modsettings/CustomUIAssets.json
@@ -218,5 +218,20 @@
"Name": "FinnIcon",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2037357792052848566/5DA900C430E97D3DFF2C9B8A3DB1CB2271791FC7/"
+ },
+ {
+ "Name": "box-cover-mask-small",
+ "Type": 0,
+ "URL": "http://cloud-3.steamusercontent.com/ugc/2115061298536631564/F29C2ED9DD8431A1D1E21C7FFAFF1FFBC0AF0BF3/"
+ },
+ {
+ "Name": "box-cover-mask-big",
+ "Type": 0,
+ "URL": "http://cloud-3.steamusercontent.com/ugc/2115061298536631429/D075D2EECE6EE091AD3BEA5800DEF9C7B02B745B/"
+ },
+ {
+ "Name": "box-cover-mask-wide",
+ "Type": 0,
+ "URL": "http://cloud-3.steamusercontent.com/ugc/2115061298538827369/A20C2ECB8ECDC1B0AD8B2B38F68CA1C1F5E07D37/"
}
]
diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua
index 39394941..8b68fbc2 100644
--- a/src/core/Global.ttslua
+++ b/src/core/Global.ttslua
@@ -43,8 +43,22 @@ local hideTitleSplashWaitFunctionId = nil
-- online functionality related variables
local MOD_VERSION = "3.3.0"
local SOURCE_REPO = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main'
-local library, requestObj, modMeta, notificationVisible
+local library, requestObj, modMeta
local acknowledgedUpgradeVersions = {}
+local contentToShow = "campaigns"
+local currentListItem = 1
+local xmlVisibility = {
+ downloadWindow = false,
+ optionPanel = false,
+ updateNotification = false
+}
+local tabIdTable = {
+ tab1 = "campaigns",
+ tab2 = "scenarios",
+ tab3 = "fanmadeCampaigns",
+ tab4 = "fanmadeScenarios",
+ tab5 = "fanmadePlayerCards"
+}
-- optionPanel data
optionPanel = {}
@@ -142,6 +156,11 @@ function onLoad(savedData)
resetChaosTokenStatTracker()
getModVersion()
math.randomseed(os.time())
+
+ -- initialization of loadable objects library (delay to let Navigation Overlay build)
+ Wait.time(function()
+ WebRequest.get(SOURCE_REPO .. '/library.json', libraryDownloadCallback)
+ end, 1)
end
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
@@ -619,176 +638,288 @@ end
-- Content Importing and XML functions
---------------------------------------------------------
-function onClick_refreshList()
- local request = WebRequest.get(SOURCE_REPO .. '/library.json', completed_list_update)
- requestObj = request
- startLuaCoroutine(Global, 'downloadCoroutine')
+-- forwards the requested content type to the update function and sets highlight to clicked tab
+---@param tabId String Id of the clicked tab
+function onClick_tab(_, _, tabId)
+ for listId, listContent in pairs(tabIdTable) do
+ if listId == tabId then
+ UI.setClass(listId, 'downloadTab activeTab')
+ contentToShow = listContent
+ else
+ UI.setClass(listId, 'downloadTab')
+ end
+ end
+ currentListItem = 1
+ updateDownloadItemList()
end
-function onClick_select(player, params)
- params = JSON.decode(urldecode(params))
+-- click function for the items in the download window
+-- updates backgroundcolor for row panel and fontcolor for list item
+function onClick_select(_, _, identificationKey)
+ UI.setAttribute("panel" .. currentListItem, "color", "clear")
+ UI.setAttribute(contentToShow .. "_" .. currentListItem, "color", "white")
+
+ -- parses the identification key (contentToShow_currentListItem)
+ if identificationKey then
+ contentToShow = nil
+ currentListItem = nil
+ for str in string.gmatch(identificationKey, "([^_]+)") do
+ if not contentToShow then
+ -- grab the first part to know the content type
+ contentToShow = str
+ else
+ -- get the index
+ currentListItem = tonumber(str)
+ break
+ end
+ end
+ end
+
+ UI.setAttribute("panel" .. currentListItem, "color", "grey")
+ UI.setAttribute(contentToShow .. "_" .. currentListItem, "color", "black")
+ updatePreviewWindow()
+end
+
+-- click function for the download button in the preview window
+function onClick_download()
+ placeholder_download(library[contentToShow][currentListItem])
+end
+
+-- the download button on the placeholder objects calls this to directly initiate a download
+---@param param Table contains url and guid of replacement object
+function placeholder_download(params)
local url = SOURCE_REPO .. '/' .. params.url
- local request = WebRequest.get(url, function (request) complete_obj_download(request, params) end )
- requestObj = request
+ requestObj = WebRequest.get(url, function (request) contentDownloadCallback(request, params) end)
startLuaCoroutine(Global, 'downloadCoroutine')
end
-function onClick_load()
- UI.show('progress_display')
- UI.hide('load_button')
+function downloadCoroutine()
+ -- show progress bar
+ UI.setAttribute('download_progress', 'active', true)
+
+ -- update progress bar
+ while requestObj do
+ UI.setAttribute('download_progress', 'percentage', requestObj.download_progress * 100)
+ coroutine.yield(0)
+ end
+ UI.setAttribute('download_progress', 'percentage', 100)
+
+ -- wait 30 frames
+ for i = 1, 30 do
+ coroutine.yield(0)
+ end
+
+ -- hide progress bar
+ UI.setAttribute('download_progress', 'active', false)
+
+ -- hide download window
+ if xmlVisibility.downloadWindow then
+ xmlVisibility.downloadWindow = false
+ UI.hide('downloadWindow')
+ end
+ return 1
end
+-- toggles the visibility of the respective UI
+---@param player LuaPlayer Player that triggered this
+---@param title String Name of the UI to toggle
function onClick_toggleUi(player, title)
if title == "Navigation Overlay" then
navigationOverlayApi.cycleVisibility(player.color)
return
end
- UI.hide('optionPanel')
- UI.hide('load_ui')
-
- -- when same button is clicked or close window button is pressed, don't open UI
- if UI.getValue('title') ~= title and title ~= 'Hidden' then
- UI.setValue('title', title)
-
- if title == "Options" then
- UI.show('optionPanel')
- else
- update_window_content(title)
- UI.show('load_ui')
- end
+ if xmlVisibility[title] then
+ -- small delay to allow button click sounds to play
+ Wait.time(function() UI.hide(title) end, 0.1)
else
- UI.setValue('title', "Hidden")
+ UI.show(title)
+ end
+ xmlVisibility[title] = not xmlVisibility[title]
+end
+
+-- updates the preview window
+function updatePreviewWindow()
+ local item = library[contentToShow][currentListItem]
+ local tempImage = "http://cloud-3.steamusercontent.com/ugc/2115061845788345842/2CD6ABC551555CCF58F9D0DDB7620197BA398B06/"
+
+ -- set default image if not defined
+ if item.boxsize == nil or item.boxsize == "" or item.boxart == nil or item.boxart == "" then
+ item.boxsize = "big"
+ item.boxart = "http://cloud-3.steamusercontent.com/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/"
+ end
+
+ UI.setValue("previewTitle", item.name)
+ UI.setValue("previewAuthor", "by " .. (item.author or "- Author not found -"))
+ UI.setValue("previewDescription", item.description or "- Description not found -")
+
+ -- update mask according to size (hardcoded values to align image in mask)
+ local maskData = {}
+ if item.boxsize == "big" then
+ maskData = {
+ image = "box-cover-mask-big",
+ width = "870",
+ height = "435",
+ offsetXY = "154 60"
+ }
+ elseif item.boxsize == "small" then
+ maskData = {
+ image = "box-cover-mask-small",
+ width = "792",
+ height = "594",
+ offsetXY = "135 13"
+ }
+ elseif item.boxsize == "wide" then
+ maskData = {
+ image = "box-cover-mask-wide",
+ width = "756",
+ height = "630",
+ offsetXY = "-190 -70"
+ }
+ end
+
+ -- loading empty image as placeholder until real image is loaded
+ UI.setAttribute("previewArtImage", "image", tempImage)
+
+ -- insert the image itself
+ UI.setAttribute("previewArtImage", "image", item.boxart)
+ UI.setAttributes("previewArtMask", maskData)
+end
+
+-- formats the json response from the webrequest into a key-value lua table
+-- strips the prefix from the community content items
+function formatLibrary(json_response)
+ library = {}
+ library["campaigns"] = json_response.campaigns
+ library["scenarios"] = json_response.scenarios
+ library["extras"] = json_response.extras
+ library["fanmadeCampaigns"] = {}
+ library["fanmadeScenarios"] = {}
+ library["fanmadePlayerCards"] = {}
+
+ for _, item in ipairs(json_response.community) do
+ local identifier = nil
+ for str in string.gmatch(item.name, "([^:]+)") do
+ if not identifier then
+ -- grab the first part to know the content type
+ identifier = str
+ else
+ -- update the name without the content type
+ item.name = str
+ break
+ end
+ end
+
+ if identifier == "Fan Investigators" then
+ table.insert(library["fanmadePlayerCards"], item)
+ elseif identifier == "Fan Campaign" then
+ table.insert(library["fanmadeCampaigns"], item)
+ elseif identifier == "Fan Scenario" then
+ table.insert(library["fanmadeScenarios"], item)
+ end
end
end
-function downloadCoroutine()
- while requestObj do
- UI.setAttribute('download_progress', 'percentage', requestObj.download_progress * 100)
- coroutine.yield(0)
- end
- return 1
-end
+-- updates the window content to the requested content
+function updateDownloadItemList()
+ if not library then return end
-function update_list(objects)
- local ui = UI.getXmlTable()
- local update_height = find_tag_with_id(ui, 'ui_update_height')
- local update_children = find_tag_with_id(update_height.children, 'ui_update_point')
+ -- addition of list items according to library file
+ local globalXml = UI.getXmlTable()
+ local contentList = getXmlTableElementById(globalXml, 'contentList')
- update_children.children = {}
-
- for _, v in ipairs(objects) do
- local s = JSON.encode(v);
- table.insert(update_children.children,
- { tag = 'Text',
- value = v.name,
- attributes = { onClick = 'onClick_select(' .. urlencode(JSON.encode(v)) .. ')', alignment = 'MiddleLeft' }
+ contentList.children = {}
+ for i, v in ipairs(library[contentToShow]) do
+ table.insert(contentList.children,
+ {
+ tag = "Panel",
+ attributes = { id = "panel" .. i },
+ children = {
+ tag = 'Text',
+ value = v.name,
+ attributes = {
+ id = contentToShow .. "_" .. i,
+ onClick = 'onClick_select',
+ alignment = 'MiddleLeft'
+ }
+ }
})
end
- update_height.attributes.height = #(update_children.children) * 24
- UI.setXmlTable(ui)
+ contentList.attributes.height = #contentList.children * 27
+ UI.setXmlTable(globalXml)
+
+ -- select the first item
+ Wait.time(onClick_select, 0.2)
end
-function update_window_content(new_title)
- if not library then return end
+-- called after the webrequest of downloading an item
+-- deletes the placeholder and spawns the downloaded item
+function contentDownloadCallback(request, params)
+ requestObj = nil
- if new_title == 'Campaigns' then
- update_list(library.campaigns)
- elseif new_title == 'Standalone Scenarios' then
- update_list(library.scenarios)
- elseif new_title == 'Investigators' then
- update_list(library.investigators)
- elseif new_title == 'Community Content' then
- update_list(library.community)
- elseif new_title == 'Extras' then
- update_list(library.extras)
- else
- update_list({})
- end
-end
-
-function complete_obj_download(request, params)
- assert(request.is_done)
+ -- error handling
if request.is_error or request.response_code ~= 200 then
- print('error: ' .. request.error)
- else
- if pcall(function()
- local replaced_object
- pcall(function()
- if params.replace then
- replaced_object = getObjectFromGUID(params.replace)
- end
- end)
- local json = request.text
- if replaced_object then
- local pos = replaced_object.getPosition()
- local rot = replaced_object.getRotation()
- destroyObject(replaced_object)
- Wait.frames(function()
- spawnObjectJSON({json = json, position = pos, rotation = rot})
- end, 1)
- else
- spawnObjectJSON({json = json})
- end
- end) then
- print('Object loaded.')
- else
- print('Error loading object.')
+ print('Error: ' .. request.error)
+ return
+ end
+
+ -- initiate content spawning
+ local spawnTable = { json = request.text }
+ if params.replace then
+ local replacedObject = getObjectFromGUID(params.replace)
+ if replacedObject then
+ spawnTable.position = replacedObject.getPosition()
+ spawnTable.rotation = replacedObject.getRotation()
+ spawnTable.scale = replacedObject.getScale()
+ destroyObject(replacedObject)
end
end
- requestObj = nil
- UI.setAttribute('download_progress', 'percentage', 100)
-end
-
--- the download button on the placeholder objects calls this to directly initiate a download
--- params is a table with url and guid of replacement object, which happens to match what onClick_select wants
-function placeholder_download(params)
- onClick_select(nil, JSON.encode(params))
-end
-
-function completed_list_update(request)
- assert(request.is_done)
- if request.is_error or request.response_code ~= 200 then
- print('error: ' .. request.error)
- else
- local json_response = nil
- if pcall(function () json_response = JSON.decode(request.text) end) then
- library = json_response
- update_window_content(UI.getValue('title'))
- else
- print('error parsing downloaded library')
+ -- if spawned from menu, ping the position
+ if params.name then
+ spawnTable["callback_function"] = function(obj)
+ Player.getPlayers()[1].pingTable(obj.getPosition())
end
end
- requestObj = nil
- UI.setAttribute('download_progress', 'percentage', 100)
+ if pcall(function() spawnObjectJSON(spawnTable) end) then
+ print('Object loaded.')
+ else
+ print('Error loading object.')
+ end
end
-function find_tag_with_id(ui, id)
+-- downloading of the library file
+function libraryDownloadCallback(request)
+ if request.is_error or request.response_code ~= 200 then
+ print('error: ' .. request.error)
+ return
+ end
+
+ local json_response = nil
+ if pcall(function () json_response = JSON.decode(request.text) end) then
+ formatLibrary(json_response)
+ updateDownloadItemList()
+ else
+ print('error parsing downloaded library')
+ end
+end
+
+-- loops through an XML table and returns the specified object
+---@param ui Table XmlTable (get this via getXmlTable)
+---@param id String Id of the object to return
+function getXmlTableElementById(ui, id)
for _, obj in ipairs(ui) do
if obj.attributes and obj.attributes.id and obj.attributes.id == id then return obj end
if obj.children then
- local result = find_tag_with_id(obj.children, id)
+ local result = getXmlTableElementById(obj.children, id)
if result then return result end
end
end
return nil
end
-function urlencode(str)
- local str = string.gsub(str, "([^A-Za-z0-9-_.~])",
- function (c) return string.format("%%%02X", string.byte(c)) end)
- return str
-end
-
-function urldecode(str)
- local str = string.gsub(str, "%%(%x%x)",
- function (h) return string.char(tonumber(h, 16)) end)
- return str
-end
-
---------------------------------------------------------
-- Option Panel related functionality
---------------------------------------------------------
@@ -1118,17 +1249,6 @@ function updateNotificationLoading()
UI.setAttribute("updateNotification", "height", 20*#highlights + 125)
end
--- triggered by clicking on the Finn Icon
-function onClick_FinnIcon()
- if notificationVisible then
- UI.hide("updateNotification")
- notificationVisible = false
- else
- UI.show("updateNotification")
- notificationVisible = true
- end
-end
-
-- close / don't show again buttons on the update notification
function onClick_notification(_, parameter)
if parameter == "dontShowAgain" then
@@ -1137,4 +1257,5 @@ function onClick_notification(_, parameter)
end
UI.hide("FinnIcon")
UI.hide("updateNotification")
+ xmlVisibility["updateNotification"] = false
end
diff --git a/xml/Global.xml b/xml/Global.xml
deleted file mode 100644
index 5d9fb47d..00000000
--- a/xml/Global.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Please refresh to see available items.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/xml/Global/BottomBar.xml b/xml/Global/BottomBar.xml
new file mode 100644
index 00000000..55f595a5
--- /dev/null
+++ b/xml/Global/BottomBar.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xml/Global/DownloadWindow.xml b/xml/Global/DownloadWindow.xml
new file mode 100644
index 00000000..8b11460b
--- /dev/null
+++ b/xml/Global/DownloadWindow.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Downloadable Content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreviewTitle
+ by PreviewAuthor
+
+
+
+
+
+
+
+
+
+
+
+
+ PreviewDescription
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xml/Global/Global.xml b/xml/Global/Global.xml
new file mode 100644
index 00000000..6f6f0d4d
--- /dev/null
+++ b/xml/Global/Global.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xml/NavigationOverlay.xml b/xml/Global/NavigationOverlay.xml
similarity index 100%
rename from xml/NavigationOverlay.xml
rename to xml/Global/NavigationOverlay.xml
diff --git a/xml/OptionPanel.xml b/xml/Global/OptionPanel.xml
similarity index 98%
rename from xml/OptionPanel.xml
rename to xml/Global/OptionPanel.xml
index 9cbbf41c..b82bb725 100644
--- a/xml/OptionPanel.xml
+++ b/xml/Global/OptionPanel.xml
@@ -79,9 +79,10 @@
+ offsetXY="-50 80"
+ raycastTarget="true">
@@ -95,7 +96,9 @@
-
+
@@ -356,7 +359,7 @@
+ onClick="onClick_toggleUi(optionPanel)">Close
|
diff --git a/xml/TitleSplash.xml b/xml/Global/TitleSplash.xml
similarity index 100%
rename from xml/TitleSplash.xml
rename to xml/Global/TitleSplash.xml
diff --git a/xml/UpdateNotification.xml b/xml/Global/UpdateNotification.xml
similarity index 94%
rename from xml/UpdateNotification.xml
rename to xml/Global/UpdateNotification.xml
index 21dedd89..6649ea90 100644
--- a/xml/UpdateNotification.xml
+++ b/xml/Global/UpdateNotification.xml
@@ -10,10 +10,11 @@
offsetXY="420 -5"
height="90"
width="90"
- onClick="onClick_FinnIcon"
+ onClick="onClick_toggleUi(updateNotification)"
image="FinnIcon"
tooltip="Update notification"
- tooltipBackgroundColor="rgba(0,0,0,0.8)"/>
+ tooltipPosition="Right"
+ tooltipBackgroundColor="rgba(0,0,0,1)"/>
|