diff --git a/config.json b/config.json index 086c0edf..d53587e0 100644 --- a/config.json +++ b/config.json @@ -199,6 +199,7 @@ "GameKeyHandler.fce69c", "3DText.d628cc", "NavigationOverlayHandler.797ede", + "CampaignImporterExporter.334ee3", "Bloodborne-CityoftheUnseen.1e7a0b", "TheColorOutofOz.806d9a" ], diff --git a/modsettings/ComponentTags.json b/modsettings/ComponentTags.json index b8cf0ac5..83a638b1 100644 --- a/modsettings/ComponentTags.json +++ b/modsettings/ComponentTags.json @@ -76,6 +76,10 @@ "displayed": "SoundCube", "normalized": "soundcube" }, + { + "displayed": "CampaignBox", + "normalized": "campaignbox" + }, { "displayed": "CameraZoom_ignore", "normalized": "camerazoom_ignore" diff --git a/objects/CampaignImporterExporter.334ee3.json b/objects/CampaignImporterExporter.334ee3.json new file mode 100644 index 00000000..094530cc --- /dev/null +++ b/objects/CampaignImporterExporter.334ee3.json @@ -0,0 +1,57 @@ +{ + "AltLookAngle": { + "x": 0, + "y": 0, + "z": 0 + }, + "Autoraise": true, + "ColorDiffuse": { + "b": 1, + "g": 1, + "r": 1 + }, + "CustomImage": { + "CustomTile": { + "Stackable": false, + "Stretch": true, + "Thickness": 0.2, + "Type": 0 + }, + "ImageScalar": 1, + "ImageSecondaryURL": "", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/254843371583173230/BECDC34EB4D2C8C5F9F9933C97085F82A2F21AE3/", + "WidthScale": 0 + }, + "Description": "Saves the state of the table to enable loading the campaign into a new save and keep all current progress.\n\nThis tool will track which campaign you're playing, the entire contents of your campaign log, the contents of your chaos bag, update your health/sanity according to trauma, your ArkhamDB deck IDs, the number of investigators, the page of your campaign guide, and any options you have selected in the options panel.\n\nFor saving trauma values to correct seats, ensure investigators in the campaign log are in the following order: White, Orange, Green, Red\n\n(For custom campaigns, ensure: 1) Campaign Box, Campaign Log, and Campaign Guide each have the corresponding tag, 2)The Campaign Box is on the table when you import or export.", + "DragSelectable": true, + "GMNotes": "", + "GUID": "334ee3", + "Grid": true, + "GridProjection": false, + "Hands": false, + "HideWhenFaceDown": false, + "IgnoreFoW": false, + "LayoutGroupSortIndex": 0, + "Locked": true, + "LuaScript": "require(\"accessories/CampaignImporterExporter\")", + "LuaScriptState": "", + "MeasureMovement": false, + "Name": "Custom_Tile", + "Nickname": "Campaign Importer/Exporter", + "Snap": true, + "Sticky": true, + "Tooltip": true, + "Transform": { + "posX": -11.74, + "posY": 1.481, + "posZ": 55.65, + "rotX": 0, + "rotY": 270, + "rotZ": 0, + "scaleX": 3.38, + "scaleY": 1, + "scaleZ": 3.38 + }, + "Value": 0, + "XmlUI": "" +} diff --git a/objects/CoreNightoftheZealot.64a613.json b/objects/CoreNightoftheZealot.64a613.json index d6a21d61..8014a7a1 100644 --- a/objects/CoreNightoftheZealot.64a613.json +++ b/objects/CoreNightoftheZealot.64a613.json @@ -49,6 +49,9 @@ "Nickname": "Core/Night of the Zealot", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 65, diff --git a/objects/EdgeoftheEarth.895eaa.json b/objects/EdgeoftheEarth.895eaa.json index c2cd0d03..0abd9a26 100644 --- a/objects/EdgeoftheEarth.895eaa.json +++ b/objects/EdgeoftheEarth.895eaa.json @@ -49,6 +49,9 @@ "Nickname": "Edge of the Earth", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 39, diff --git a/objects/ReturntoTheCircleUndone.757324.json b/objects/ReturntoTheCircleUndone.757324.json index a597ceb0..a851cdd1 100644 --- a/objects/ReturntoTheCircleUndone.757324.json +++ b/objects/ReturntoTheCircleUndone.757324.json @@ -49,6 +49,9 @@ "Nickname": "Return to The Circle Undone", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 13, diff --git a/objects/ReturntoTheDunwichLegacy.ce9130.json b/objects/ReturntoTheDunwichLegacy.ce9130.json index ab1b17bc..a7b19102 100644 --- a/objects/ReturntoTheDunwichLegacy.ce9130.json +++ b/objects/ReturntoTheDunwichLegacy.ce9130.json @@ -49,6 +49,9 @@ "Nickname": "Return to The Dunwich Legacy", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 52, diff --git a/objects/ReturntoTheForgottenAge.479ff3.json b/objects/ReturntoTheForgottenAge.479ff3.json index a07eb08a..2a8144c0 100644 --- a/objects/ReturntoTheForgottenAge.479ff3.json +++ b/objects/ReturntoTheForgottenAge.479ff3.json @@ -49,6 +49,9 @@ "Nickname": "Return to The Forgotten Age", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 26, diff --git a/objects/ReturntoThePathtoCarcosa.e9889a.json b/objects/ReturntoThePathtoCarcosa.e9889a.json index 3da08e1a..d01aa6e3 100644 --- a/objects/ReturntoThePathtoCarcosa.e9889a.json +++ b/objects/ReturntoThePathtoCarcosa.e9889a.json @@ -49,6 +49,9 @@ "Nickname": "Return to The Path to Carcosa", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 39, diff --git a/objects/ReturntotheNightoftheZealot.56270d.json b/objects/ReturntotheNightoftheZealot.56270d.json index 7c01b47f..99fbed35 100644 --- a/objects/ReturntotheNightoftheZealot.56270d.json +++ b/objects/ReturntotheNightoftheZealot.56270d.json @@ -49,6 +49,9 @@ "Nickname": "Return to the Night of the Zealot", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 65, diff --git a/objects/TheCircleUndone.63e097.json b/objects/TheCircleUndone.63e097.json index f74813d0..12666a66 100644 --- a/objects/TheCircleUndone.63e097.json +++ b/objects/TheCircleUndone.63e097.json @@ -49,6 +49,9 @@ "Nickname": "The Circle Undone", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 13, diff --git a/objects/TheDream-Eaters.a16a1a.json b/objects/TheDream-Eaters.a16a1a.json index 67459503..5a7f596e 100644 --- a/objects/TheDream-Eaters.a16a1a.json +++ b/objects/TheDream-Eaters.a16a1a.json @@ -49,6 +49,9 @@ "Nickname": "The Dream-Eaters", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 65, diff --git a/objects/TheDunwichLegacy.2898f6.json b/objects/TheDunwichLegacy.2898f6.json index d713bb41..c96eb36f 100644 --- a/objects/TheDunwichLegacy.2898f6.json +++ b/objects/TheDunwichLegacy.2898f6.json @@ -49,6 +49,9 @@ "Nickname": "The Dunwich Legacy", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 52, diff --git a/objects/TheForgottenAge.0bcf19.json b/objects/TheForgottenAge.0bcf19.json index 76fe3144..eb6ffc60 100644 --- a/objects/TheForgottenAge.0bcf19.json +++ b/objects/TheForgottenAge.0bcf19.json @@ -49,6 +49,9 @@ "Nickname": "The Forgotten Age", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 26, diff --git a/objects/TheInnsmouthConspiracy.465aab.json b/objects/TheInnsmouthConspiracy.465aab.json index e76f46e9..20f313db 100644 --- a/objects/TheInnsmouthConspiracy.465aab.json +++ b/objects/TheInnsmouthConspiracy.465aab.json @@ -49,6 +49,9 @@ "Nickname": "The Innsmouth Conspiracy", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 52, diff --git a/objects/ThePathtoCarcosa.aca04c.json b/objects/ThePathtoCarcosa.aca04c.json index 9acb3a0a..dd39ca10 100644 --- a/objects/ThePathtoCarcosa.aca04c.json +++ b/objects/ThePathtoCarcosa.aca04c.json @@ -49,6 +49,9 @@ "Nickname": "The Path to Carcosa", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 39, diff --git a/objects/TheScarletKeys.300fcc.json b/objects/TheScarletKeys.300fcc.json index 7287ed9d..c5c5fc87 100644 --- a/objects/TheScarletKeys.300fcc.json +++ b/objects/TheScarletKeys.300fcc.json @@ -49,6 +49,9 @@ "Nickname": "The Scarlet Keys", "Snap": true, "Sticky": true, + "Tags": [ + "CampaignBox" + ], "Tooltip": true, "Transform": { "posX": 26, diff --git a/src/accessories/CampaignImporterExporter.ttslua b/src/accessories/CampaignImporterExporter.ttslua new file mode 100644 index 00000000..fe7834a7 --- /dev/null +++ b/src/accessories/CampaignImporterExporter.ttslua @@ -0,0 +1,298 @@ +local campaignTokenData = { + GUID = "51b1c9", + Name = "Custom_Model", + Transform = { + posX = -21.25, + posY = 1.68, + posZ = 55.59, + rotX = 0, + rotY = 270, + rotZ = 0, + scaleX = 2, + scaleY = 2, + scaleZ = 2 + }, + Nickname = "Arkham Coin", + Description = "SCED Importer Token", + GMNotes = "", + Tags = { + "ImporterToken" + }, + CustomMesh = { + MeshURL = "http://cloud-3.steamusercontent.com/ugc/943949966265929204/A38BB5D72419E6298385556D931877C0A1A55C17/", + DiffuseURL = "http://cloud-3.steamusercontent.com/ugc/254843371583188147/920981125E37B5CEB6C400E3FD353A2C428DA969/", + NormalURL = "", + ColliderURL = "http://cloud-3.steamusercontent.com/ugc/943949966265929204/A38BB5D72419E6298385556D931877C0A1A55C17/", + Convex = true, + MaterialIndex = 2, + TypeIndex = 0, + CustomShader = { + SpecularColor = { + r = 0.7222887, + g = 0.507659256, + b = 0.339915335 + }, + SpecularIntensity = 0.4, + SpecularSharpness = 7.0, + FresnelStrength = 0.0 + }, + CastShadows = true + } +} + +-- counter GUIDS (4x damage and 4x horror) +local DAMAGE_HORROR_GUIDS = { + "eb08d6"; "e64eec"; "1f5a0a"; "591a45"; + "468e88"; "0257d9"; "7b5729"; "beb964"; + } + +local chaosBagApi = require("chaosbag/ChaosBagApi") +local playAreaApi = require("core/PlayAreaApi") +local deckImporterApi = require("arkhamdb/DeckImporterApi") +local optionPanelApi = require("core/OptionPanelApi") +local blessCurseApi = require("chaosbag/BlessCurseManagerApi") + +local TOUR_GUID = "0e5aa8" + +local campaignBoxGUID + +function onLoad(save_state) + campaignBoxGUID = "" + + self.createButton({ + click_function = "findCampaignFromToken", + function_owner = self, + label = "Import", + tooltip = "Load in a campaign save from a token!\n\n(Token can be anywhere on the table, but ensure there is only 1!)", + position = {x=-1, y=0.2, z=0}, + font_size = 400, + width = 1400, + height = 600, + scale = {0.5, 1, 0.5}, + }) + self.createButton({ + click_function = "createCampaignToken", + function_owner = self, + label = "Export", + tooltip = "Create a campaign save token!\n\n(Ensure all chaos tokens have been unsealed!)", + position = {x=1, y=0.2, z=0}, + font_size = 400, + width = 1400, + height = 600, + scale = {0.5, 1, 0.5}, + }) +end + +-- The main import functions. Due to timing concerns, has to be split up into several separate methods to allow for Wait conditions + +-- Identifies import token, determines campaign box and downloads it (if needed) +function findCampaignFromToken(_, _, _) + local coin = nil + local coinObjects = getObjectsWithTag("ImporterToken") + if #coinObjects == 0 then + broadcastToAll("Could not find importer token", Color.Red) + elseif #coinObjects > 1 then + broadcastToAll("More than 1 importer token found. Please delete all but 1 importer token", Color.Yellow) + else + coin = coinObjects[1] + local importData = JSON.decode(coin.getGMNotes()) + campaignBoxGUID = importData["box"] + local campaignBox = getObjectFromGUID(campaignBoxGUID) + if campaignBox.type == "Generic" then + campaignBox.call("buttonClick_download") + end + Wait.condition( + function() + if #campaignBox.getObjects() > 0 then + placeCampaignFromToken(importData) + else + createCampaignFromToken(importData) + end + end, + function() + local obj = getObjectFromGUID(campaignBoxGUID) + if obj == nil then + return false + else + return obj.type == "Bag" and obj.getLuaScript() ~= "" + end + end, + 2, + function() broadcastToAll("Error loading campaign box") end + ) + end +end + +-- After box has been downloaded, places content on table +function placeCampaignFromToken(importData) + getObjectFromGUID(campaignBoxGUID).call("buttonClick_place") + Wait.condition( + function() createCampaignFromToken(importData) end, + function() return findCampaignLog() ~= nil end, + 2, + function() broadcastToAll("Error placing campaign box") end + ) +end + +-- After content is placed on table, conducts all the other import operations +function createCampaignFromToken(importData) + findCampaignLog().destruct() + --create campaign log + spawnObjectData({data = importData["log"]}) + --create chaos bag + chaosBagApi.setChaosBagState(importData["bag"]) + --populate trauma values + if importData["trauma"] then + updateCounters(importData["trauma"]) + end + --populate ArkhamDB deck IDs + if importData["decks"] then + deckImporterApi.setUiState(importData["decks"]) + end + --set investigator count + playAreaApi.setInvestigatorCount(importData["clueCount"]) + --set campaign guide page + local guide = findCampaignGuide() + if guide then + Wait.condition( + -- Called after the condition function returns true + function() + log("Campaign Guide import successful!") + end, + -- Condition function that is called continiously until returs true or timeout is reached + function() + guide.Book.setPage(importData["guide"]) + return guide.Book.getPage() == importData["guide"] + end, + -- Amount of time in seconds until the Wait times out + 1, + -- Called if the Wait times out + function() + log("Campaign Guide import failed!") + end + ) + end + Wait.time( + function() optionPanelApi.loadSettings(importData["options"]) end, + 0.5 + ) + getObjectFromGUID(TOUR_GUID).destruct() + playAreaApi.updateSurface(importData["playmat"]) + broadcastToAll("Campaign successfully imported!", Color.Green) +end + + +-- Creates a campaign token with save data encoded into GM Notes based on the current state of the table +function createCampaignToken(_, playerColor, _) + -- clean up chaos tokens + blessCurseApi.removeAll(playerColor) + chaosBagApi.releaseAllSealedTokens(playerColor) + + local campaignBoxGUID = "" + -- find active campaign + for _, obj in ipairs(getObjectsWithTag("CampaignBox")) do + if obj.type == "Bag" and #obj.getObjects() == 0 then + if campaignBoxGUID ~= "" then + broadcastToAll("Multiple empty campaign box detected; delete all but one.", Color.Red) + return + end + campaignBoxGUID = obj.getGUID() + end + end + if campaignBoxGUID == "" then + broadcastToAll("Campaign box with all placed objects not found!", Color.Red) + return + end + local campaignLog = findCampaignLog() + if campaignLog == nil then + broadcastToAll("Campaign log not found!", Color.Red) + return + end + local traumaValues = nil + local counterData = campaignLog.getVar("ref_buttonData") + if counterData ~= nil then + traumaValues = {} + printToAll("Trauma values found in campaign log!", "Green") + for i = 1, 10, 3 do + traumaValues[1 + (i - 1) / 3] = counterData.counter[i].value + traumaValues[5 + (i - 1) / 3] = counterData.counter[i + 1].value + end + else + printToAll("Trauma values could not be found in campaign log!", "Yellow") + printToAll("Default values for health and sanity loaded.", "Yellow") + end + local campaignGuide = findCampaignGuide() + if campaignGuide == nil then + broadcastToAll("Campaign guide not found!", Color.Red) + return + end + local campaignGuidePage = campaignGuide.Book.getPage() + local campaignData = { + box = campaignBoxGUID, + log = campaignLog.getData(), + bag = chaosBagApi.getChaosBagState(), + trauma = traumaValues, + decks = deckImporterApi.getUiState(), + clueCount = playAreaApi.getInvestigatorCount(), + guide = campaignGuidePage, + options = optionPanelApi.getOptions(), + playmat = playAreaApi.getSurface() + } + campaignTokenData.GMNotes = JSON.encode(campaignData) + campaignTokenData.Nickname = os.date("%b %d ") .. getObjectFromGUID(campaignBoxGUID).getName() .. " Save" + spawnObjectData({ + data = campaignTokenData, + position = {-21.25, 1.68, 55.59} + }) + broadcastToAll("Campaign successfully exported! Save coin object to import on a fresh save", Color.Green) +end + + +-- helper functions + +function findCampaignLog() + local campaignLog = getObjectsWithTag("CampaignLog") + if campaignLog then + if #campaignLog == 1 then + return campaignLog[1] + else + broadcastToAll("More than 1 campaign log detected; delete all but one.", Color.Red) + return nil + end + else + return nil + end +end + +function findCampaignGuide() + local campaignGuide = getObjectsWithTag("CampaignGuide") + if campaignGuide then + if #campaignGuide == 1 then + return campaignGuide[1] + else + broadcastToAll("More than 1 campaign guide detected; delete all but one.", Color.Red) + return nil + end + else + return nil + end +end + +function updateCounters(tableOfNewValues) + if tonumber(tableOfNewValues) then + local value = tableOfNewValues + tableOfNewValues = {} + for i = 1, #DAMAGE_HORROR_GUIDS do + table.insert(tableOfNewValues, value) + end + end + + for i, guid in ipairs(DAMAGE_HORROR_GUIDS) do + local TOKEN = getObjectFromGUID(guid) + if TOKEN ~= nil then + TOKEN.call("updateVal", tableOfNewValues[i]) + else + printToAll(": No. " .. i .. " could not be found.", "Yellow") + end + end +end diff --git a/src/accessories/CleanUpHelper.ttslua b/src/accessories/CleanUpHelper.ttslua index d719d488..031c79f6 100644 --- a/src/accessories/CleanUpHelper.ttslua +++ b/src/accessories/CleanUpHelper.ttslua @@ -7,6 +7,7 @@ local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") local soundCubeApi = require("core/SoundCubeApi") local playmatApi = require("playermat/PlaymatApi") local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi") +local chaosBagApi = require("chaosbag/ChaosBagApi") -- these objects will be ignored local IGNORE_GUIDS = { @@ -173,7 +174,7 @@ function cleanUp(_, color) removeLines() discardHands() tokenSpawnTrackerApi.resetAll() - Global.call("releaseAllSealedTokens", color) + chaosBagApi.releaseAllSealedTokens(color) printToAll("Tidying main play area...", "White") startLuaCoroutine(self, "tidyPlayareaCoroutine") diff --git a/src/arkhamdb/DeckImporterApi.ttslua b/src/arkhamdb/DeckImporterApi.ttslua new file mode 100644 index 00000000..73fc556f --- /dev/null +++ b/src/arkhamdb/DeckImporterApi.ttslua @@ -0,0 +1,39 @@ +do + local DeckImporterApi = {} + local DECK_IMPORTER_GUID = "a28140" + + + -- Returns a table with the full state of the UI, including options and deck IDs. + -- This can be used to persist via onSave(), or provide values for a load operation + -- Table values: + -- redDeck: Deck ID to load for the red player + -- orangeDeck: Deck ID to load for the orange player + -- whiteDeck: Deck ID to load for the white player + -- greenDeck: Deck ID to load for the green player + -- private: True to load a private deck, false to load a public deck + -- loadNewest: True if the most upgraded version of the deck should be loaded + -- investigators: True if investigator cards should be spawned + DeckImporterApi.getUiState = function() + local passthroughTable = {} + for k,v in pairs(getObjectFromGUID(DECK_IMPORTER_GUID).call("getUiState")) do + passthroughTable[k] = v + end + return passthroughTable + end + + -- Updates the state of the UI based on the provided table. Any values not provided will be left the same. + -- @param uiStateTable Table of values to update on importer + -- Table values: + -- redDeck: Deck ID to load for the red player + -- orangeDeck: Deck ID to load for the orange player + -- whiteDeck: Deck ID to load for the white player + -- greenDeck: Deck ID to load for the green player + -- private: True to load a private deck, false to load a public deck + -- loadNewest: True if the most upgraded version of the deck should be loaded + -- investigators: True if investigator cards should be spawned + DeckImporterApi.setUiState = function(uiStateTable) + return getObjectFromGUID(DECK_IMPORTER_GUID).call("setUiState", uiStateTable) + end + + return DeckImporterApi +end \ No newline at end of file diff --git a/src/arkhamdb/DeckImporterUi.ttslua b/src/arkhamdb/DeckImporterUi.ttslua index 3ea446a4..e227588b 100644 --- a/src/arkhamdb/DeckImporterUi.ttslua +++ b/src/arkhamdb/DeckImporterUi.ttslua @@ -45,6 +45,49 @@ function getUiState() } end +-- Updates the state of the UI based on the provided table. Any values not provided will be left the same. +-- @param uiStateTable Table of values to update on importer +-- Table values: +-- redDeck: Deck ID to load for the red player +-- orangeDeck: Deck ID to load for the orange player +-- whiteDeck: Deck ID to load for the white player +-- greenDeck: Deck ID to load for the green player +-- private: True to load a private deck, false to load a public deck +-- loadNewest: True if the most upgraded version of the deck should be loaded +-- investigators: True if investigator cards should be spawned +function setUiState(uiStateTable) + -- Callback functions aren't triggered when editing buttons/inputs so values must be set manually + + if uiStateTable["greenDeck"] then + greenDeckId = uiStateTable["greenDeck"] + self.editInput({index=0, value=greenDeckId}) + end + if uiStateTable["redDeck"] then + redDeckId = uiStateTable["redDeck"] + self.editInput({index=1, value=redDeckId}) + end + if uiStateTable["whiteDeck"] then + whiteDeckId = uiStateTable["whiteDeck"] + self.editInput({index=2, value=whiteDeckId}) + end + if uiStateTable["orangeDeck"]then + orangeDeckId = uiStateTable["orangeDeck"] + self.editInput({index=3, value=orangeDeckId}) + end + if uiStateTable["private"] then + privateDeck = uiStateTable["private"] + self.editButton { index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] } + end + if uiStateTable["loadNewest"] then + loadNewestDeck = uiStateTable["loadNewest"] + self.editButton { index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] } + end + if uiStateTable["investigators"] then + loadInvestigators = uiStateTable["investigators"] + self.editButton { index = 2, label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] } + end +end + -- Sets up the UI for the deck loader, populating fields from the given save state table decoded from onLoad() function initializeUi(savedUiState) if savedUiState ~= nil then diff --git a/src/chaosbag/ChaosBagApi.ttslua b/src/chaosbag/ChaosBagApi.ttslua new file mode 100644 index 00000000..e2d0a1ae --- /dev/null +++ b/src/chaosbag/ChaosBagApi.ttslua @@ -0,0 +1,32 @@ +do + local ChaosBagApi = {} + + -- respawns the chaos bag with a new state of tokens + ---@param tokenList Table List of chaos token ids + ChaosBagApi.setChaosBagState = function(tokenList) + return Global.call("setChaosBagState", tokenList) + end + + -- returns a Table List of chaos token ids in the current chaos bag + -- requires copying the data into a new table because TTS is weird about handling table return values in Global + ChaosBagApi.getChaosBagState = function() + local chaosBagContentsCatcher = Global.call("getChaosBagState") + local chaosBagContents = {} + for _, v in ipairs(chaosBagContentsCatcher) do + table.insert(chaosBagContents, v) + end + return chaosBagContents + end + + -- checks scripting zone for chaos bag (also called by a lot of objects!) + ChaosBagApi.findChaosBag = function() + return Global.call("findChaosBag") + end + + -- returns all sealed tokens on cards to the chaos bag + ChaosBagApi.releaseAllSealedTokens = function(playerColor) + return Global.call("releaseAllSealedTokens", playerColor) + end + + return ChaosBagApi +end \ No newline at end of file diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua index 15a85c87..5246ed61 100644 --- a/src/core/Global.ttslua +++ b/src/core/Global.ttslua @@ -548,8 +548,37 @@ function getDataValue(storage, key) end end +function createChaosTokenNameLookupTable() + local namesToIds = {} + for k, v in pairs(ID_URL_MAP) do + namesToIds[v.name] = k + end + return namesToIds +end + +-- returns a Table List of chaos token ids in the current chaos bag +---@api chaosbag/ChaosBagApi +function getChaosBagState() + local tokens = {} + local invertedTable = createChaosTokenNameLookupTable() + local chaosbag = findChaosBag() + + for _, v in ipairs(chaosbag.getObjects()) do + local id = invertedTable[v.name] + if id then + table.insert(tokens, id) + else + printToAll(v.name .. " token not recognized. Will not be recorded.", "Yellow") + end + end + + return tokens + +end + -- respawns the chaos bag with a new state of tokens ---@param tokenList Table List of chaos token ids +---@api chaosbag/ChaosBagApi function setChaosBagState(tokenList) if not canTouchChaosTokens() then return end @@ -975,7 +1004,9 @@ end ---@param rotation Vector Rotation of the object for spawning (default: {0, 270, 0}) ---@return. GUID of the spawnedObj (or nil if object was removed) function spawnOrRemoveHelper(state, name, position, rotation) - if state then + if (type(state) == "table" and #state == 0) then + return removeHelperObject(name) + elseif state then Player.getPlayers()[1].pingTable(position) return spawnHelperObject(name, position, rotation).getGUID() else @@ -1045,6 +1076,15 @@ function removeHelperObject(name) end end +-- loads saved options +function loadSettings(newOptions) + optionPanel = newOptions + updateOptionPanelState() + for id, state in pairs(optionPanel) do + applyOptionPanelChange(id, state) + end +end + -- loads the default options function onClick_defaultSettings() for id, _ in pairs(optionPanel) do diff --git a/src/core/MythosAreaApi.ttslua b/src/core/MythosAreaApi.ttslua index cf26f065..a3fcbcf2 100644 --- a/src/core/MythosAreaApi.ttslua +++ b/src/core/MythosAreaApi.ttslua @@ -4,7 +4,7 @@ do -- returns the chaos token metadata (if provided through scenario reference card) MythosAreaApi.returnTokenData = function() - return getObjectFromGUID("9f334f").call("returnTokenData") + return getObjectFromGUID(MYTHOS_AREA_GUID).call("returnTokenData") end return MythosAreaApi diff --git a/src/core/OptionPanelApi.ttslua b/src/core/OptionPanelApi.ttslua new file mode 100644 index 00000000..df678dff --- /dev/null +++ b/src/core/OptionPanelApi.ttslua @@ -0,0 +1,16 @@ +do + local OptionPanelApi = {} + + -- loads saved options + ---@param options Table New options table + OptionPanelApi.loadSettings = function(options) + return Global.call("loadSettings", options) + end + + -- returns option panel table + OptionPanelApi.getOptions = function() + return Global.getTable("optionPanel") + end + + return OptionPanelApi +end \ No newline at end of file diff --git a/src/core/PlayArea.ttslua b/src/core/PlayArea.ttslua index e8507e61..11934eb3 100644 --- a/src/core/PlayArea.ttslua +++ b/src/core/PlayArea.ttslua @@ -511,6 +511,13 @@ function getInvestigatorCount() return investigatorCounter.getVar("val") end +-- Updates the current value of the investigator counter from the playmat +---@param count Number of investigators to set on the counter +function setInvestigatorCount(count) + local investigatorCounter = getObjectFromGUID("f182ee") + return investigatorCounter.call("updateVal", count) +end + -- Check to see if the given object is within the bounds of the play area, based solely on the X and -- Z coordinates, ignoring height ---@param object Object Object to check diff --git a/src/core/PlayAreaApi.ttslua b/src/core/PlayAreaApi.ttslua index e7b4bf7e..c141c61a 100644 --- a/src/core/PlayAreaApi.ttslua +++ b/src/core/PlayAreaApi.ttslua @@ -3,12 +3,20 @@ do local PLAY_AREA_GUID = "721ba2" + local IMAGE_SWAPPER = "b7b45b" + -- Returns the current value of the investigator counter from the playmat ---@return Integer. Number of investigators currently set on the counter PlayAreaApi.getInvestigatorCount = function() return getObjectFromGUID(PLAY_AREA_GUID).call("getInvestigatorCount") end + -- Updates the current value of the investigator counter from the playmat + ---@param count Number of investigators to set on the counter + PlayAreaApi.setInvestigatorCount = function(count) + return getObjectFromGUID(PLAY_AREA_GUID).call("setInvestigatorCount", count) + end + -- Move all contents on the play area (cards, tokens, etc) one slot in the given direction. Certain -- fixed objects will be ignored, as will anything the player has tagged with -- 'displacement_excluded' @@ -77,5 +85,13 @@ do return getObjectFromGUID(PLAY_AREA_GUID).call("isInPlayArea", object) end + PlayAreaApi.getSurface = function() + return getObjectFromGUID(PLAY_AREA_GUID).getCustomObject().image + end + + PlayAreaApi.updateSurface = function(url) + return getObjectFromGUID(IMAGE_SWAPPER).call("updateSurface", url) + end + return PlayAreaApi end