diff --git a/objects/Fan-MadeAccessories.aa8b38.json b/objects/Fan-MadeAccessories.aa8b38.json index 33b400c4..042ee175 100644 --- a/objects/Fan-MadeAccessories.aa8b38.json +++ b/objects/Fan-MadeAccessories.aa8b38.json @@ -19,7 +19,6 @@ "LuckyPenny.2ab443", "SecretObjectivesUltimatums.b2077d", "UnderworldMarketHelper.3650ea", - "Subject5U-21Helper.1335e8", "Auto-failCounter.a9a321", "ElderSignCounter.e62cb5", "AdditionalVictoryPoints.958bc0" diff --git a/objects/OptionPanelSource.830bd0.json b/objects/OptionPanelSource.830bd0.json index 56ede1b5..743e1af4 100644 --- a/objects/OptionPanelSource.830bd0.json +++ b/objects/OptionPanelSource.830bd0.json @@ -17,6 +17,7 @@ "CYOACampaignGuides.e87ea2", "AttachmentHelper.7f4976", "SearchAssistant.17aed0", + "Subject5U-21Helper.1335e8", "HandHelper.450688", "DisplacementTool.0f1374", "CleanUpHelper.26cf4b" diff --git a/objects/Fan-MadeAccessories.aa8b38/Subject5U-21Helper.1335e8.json b/objects/OptionPanelSource.830bd0/Subject5U-21Helper.1335e8.json similarity index 100% rename from objects/Fan-MadeAccessories.aa8b38/Subject5U-21Helper.1335e8.json rename to objects/OptionPanelSource.830bd0/Subject5U-21Helper.1335e8.json diff --git a/src/core/DataHelper.ttslua b/src/core/DataHelper.ttslua index 707c9add..88c74857 100644 --- a/src/core/DataHelper.ttslua +++ b/src/core/DataHelper.ttslua @@ -1,128 +1,5 @@ -- data for difficulty selector scripts to set up chaos bag modeData = { - -----------------The Forgotten Age - ['The Forgotten Age'] = { - easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm2', 'm3', 'skull', 'skull', 'elder', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', '0', 'm1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'elder', 'red', 'blue' } }, - hard = { token = { 'p1', '0', '0', 'm1', 'm2', 'm3', 'm3', 'm4', 'm6', 'skull', 'skull', 'elder', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm6', 'm8', 'skull', 'skull', 'elder', 'red', 'blue' } } - }, - ['The Doom of Eztli'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['Threads of Fate'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['The Boundary Beyond'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['The City of Archives'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['The Depths of Yoth'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['Heart of the Elders'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['Shattered Aeons'] = { - standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm4', 'm5', 'skull', 'skull', 'elder', 'red', 'blue' } } - }, - - -----------------The Circle Undone - ['The Circle Undone'] = { - easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm2', 'm3', 'skull', 'skull', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'red', 'blue' } }, - hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm5', 'skull', 'skull', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm6', 'm8', 'skull', 'skull', 'red', 'blue' } } - }, - ["At Death's Doorstep"] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } } - }, - ['The Secret Name'] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } } - }, - ['The Wages of Sin'] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['For the Greater Good'] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['Union and Disillusion'] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['In the Clutches of Chaos'] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - ['Before the Black Throne'] = { - standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - - -----------------The Dream-Eaters - ['TDE_A'] = { - easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }, - hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'cultist', 'tablet', 'tablet', 'red', 'blue' } } - }, - ['TDE_B'] = { - easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }, - hard = { token = { '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } } - }, - ['The Search For Kadath'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'tablet', 'red', 'blue' } } - }, - ['A Thousand Shapes of Horror'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } } - }, - ['Dark Side of the Moon'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'tablet', 'red', 'blue' } } - }, - ['Point of No Return'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } } - }, - ['Where the Gods Dwell'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'tablet', 'red', 'blue' } } - }, - ['Weaver of the Cosmos'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } } - }, - - -----------------The Innsmouth Conspiracy - ['The Innsmouth Conspiracy'] = { - easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } }, - hard = { token = { '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } } , - expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } } - }, - ['TIC_Standalone'] = { - standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } } - }, - - -----------------Edge of the Earth - ['Edge of the Earth'] = { - easy = { token = { 'p1', 'p1', 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } }, - hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm7', 'frost', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } } - }, - ['City of the Elder Things'] = { - easy = { token = { 'p1', 'p1', 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }, - hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm7', 'frost', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } } - }, - - -----------------The Scarlet Keys - ['The Scarlet Keys'] = { - easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } }, - normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } }, - hard = { token = { '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } }, - expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } } - }, - -----------------The Side Missions --official ['Curse of the Rougarou'] = { diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua index 07a39e22..de7d627d 100644 --- a/src/core/Global.ttslua +++ b/src/core/Global.ttslua @@ -2525,6 +2525,53 @@ function TokenManager.addUseToCard(params) else return false end + +-- generates the data to spawn an infinite bag of a specific type of resources +function TokenManager.getDataForInfiniteBag(params) + -- re-assign parameters for convenience + local tokenType = params.tokenType + local position = params.position + local rotation = params.rotation + + -- make sure the token templates are initialized + TokenManager.initTokenTemplates() + + -- create a copy of the resource token (to not modify the source) + local template = deepCopy(tokenTemplates["resource"]) + local subTypeStateId = stateTable[tokenType] + local subTypeData = template["States"][subTypeStateId] + + -- add states to data + subTypeData["States"] = template["States"] + + -- add "1" state and remove the current state + subTypeData["States"][1] = template + subTypeData["States"][1]["States"] = nil + subTypeData["States"][subTypeStateId] = nil + + -- update rotation of the main state + subTypeData["Transform"].rotX = 0 + subTypeData["Transform"].rotY = 0 + subTypeData["Transform"].rotZ = 0 + + -- generate and return data for the infinite bag + local properTypeName = tokenType:gsub("^%l", string.upper) + return { + Name = "Infinite_Bag", + Nickname = properTypeName .. " Bag", + ContainedObjects = { subTypeData }, + Transform = { + posX = position.x, + posY = position.y, + posZ = position.z, + rotX = rotation.x, + rotY = rotation.y, + rotZ = rotation.z, + scaleX = 1, + scaleY = 1, + scaleZ = 1 + } + } end --------------------------------------------------------- @@ -2596,3 +2643,17 @@ function getColoredName(playerColor) -- add bb-code return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]" end + +-- creates a deep copy of a table +function deepCopy(data) + if type(data) ~= "table" then return data end + local copiedList = {} + for key, value in pairs(data) do + if type(value) == "table" then + copiedList[key] = deepCopy(value) + else + copiedList[key] = value + end + end + return copiedList +end diff --git a/src/core/GlobalApi.ttslua b/src/core/GlobalApi.ttslua index 0f55e584..1a15d394 100644 --- a/src/core/GlobalApi.ttslua +++ b/src/core/GlobalApi.ttslua @@ -50,7 +50,7 @@ do ---@param playerColor string Color of the player that needs the visibility toggled ---@param handColor string Color of the hand to toggle the visibility for function GlobalApi.handVisibilityToggle(playerColor, handColor) - Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor}) + Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor }) end -- loads saved options diff --git a/src/core/token/TokenManagerApi.ttslua b/src/core/token/TokenManagerApi.ttslua index 1894a09c..ea00f301 100644 --- a/src/core/token/TokenManagerApi.ttslua +++ b/src/core/token/TokenManagerApi.ttslua @@ -91,9 +91,18 @@ do function TokenManagerApi.maybeReplenishCard(card, uses) Global.call("callTable", { { "TokenManager", "maybeReplenishCard" }, + { card = card, uses = uses } + }) + end + + -- Generates the data to spawn an infinite bag of a specific type of resources + function TokenManagerApi.getDataForInfiniteBag(tokenType, position, rotation) + return Global.call("callTable", { + { "TokenManager", "getDataForInfiniteBag" }, { - card = card, - uses = uses + tokenType = tokenType, + position = position, + rotation = rotation } }) end diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index 11e97bf4..f01ae11a 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -268,6 +268,9 @@ function buildSupplementalIndexes() if OFFICIAL_CYCLE_LIST[cycleName] ~= true then otherCardsDetected = true + -- overwrite the cycle for easier handling by the playercard panel + cycleName = "other" + -- maybe add to special investigator / minicard index if card.metadata.type == "Investigator" then writeToNestedTable(customInvestigatorData, "InvestigatorGroup", cardId) diff --git a/src/playercards/CardSearch.ttslua b/src/playercards/CardSearch.ttslua index f5143f2e..2aca432e 100644 --- a/src/playercards/CardSearch.ttslua +++ b/src/playercards/CardSearch.ttslua @@ -1,37 +1,37 @@ require("playercards/PlayerCardSpawner") -local allCardsBagApi = require("playercards/AllCardsBagApi") +local allCardsBagApi = require("playercards/AllCardsBagApi") -local BUTTON_LABELS = {} -BUTTON_LABELS["spawn"] = {} -BUTTON_LABELS["spawn"][true] = "All matching cards" -BUTTON_LABELS["spawn"][false] = "First matching card" -BUTTON_LABELS["search"] = {} -BUTTON_LABELS["search"][true] = "Name equals search term" -BUTTON_LABELS["search"][false] = "Name contains search term" +local BUTTON_LABELS = {} +BUTTON_LABELS["spawn"] = {} +BUTTON_LABELS["spawn"][true] = "All matching cards" +BUTTON_LABELS["spawn"][false] = "First matching card" +BUTTON_LABELS["search"] = {} +BUTTON_LABELS["search"][true] = "Name equals search term" +BUTTON_LABELS["search"][false] = "Name contains search term" -local inputParameters = {} -inputParameters.label = "Click to enter card name" -inputParameters.input_function = "input_func" -inputParameters.function_owner = self -inputParameters.alignment = 2 -inputParameters.position = { x = 0, y = 0.1, z = -0.62 } -inputParameters.width = 3750 -inputParameters.height = 380 -inputParameters.font_size = 350 -inputParameters.scale = { 0.1, 1, 0.1 } -inputParameters.color = { 0.9, 0.7, 0.5 } -inputParameters.font_color = { 0, 0, 0 } +local inputParameters = {} +inputParameters.label = "Click to enter card name" +inputParameters.input_function = "input_func" +inputParameters.function_owner = self +inputParameters.alignment = 2 +inputParameters.position = { x = 0, y = 0.1, z = -0.62 } +inputParameters.width = 3750 +inputParameters.height = 380 +inputParameters.font_size = 350 +inputParameters.scale = { 0.1, 1, 0.1 } +inputParameters.color = { 0.9, 0.7, 0.5 } +inputParameters.font_color = { 0, 0, 0 } function onSave() return JSON.encode({ spawnAll, searchExact, inputParameters.value }) end function onLoad(savedData) - local loadedData = JSON.decode(savedData) - spawnAll = loadedData[1] or false - searchExact = loadedData[2] or false - inputParameters.value = loadedData[3] or "" + local loadedData = JSON.decode(savedData) + spawnAll = loadedData[1] or false + searchExact = loadedData[2] or false + inputParameters.value = loadedData[3] or "" self.createInput(inputParameters) -- shared parameters @@ -103,6 +103,13 @@ function startSearch() return end + -- if the search string is a number, assume it's an ID and spawn the card directly + if tonumber(inputParameters.value) then + local singleCard = allCardsBagApi.getCardById(inputParameters.value) + spawnCardList({ singleCard }) + return + end + -- search all objects in bag local cardList = allCardsBagApi.getCardsByName(inputParameters.value, searchExact) if cardList == nil or #cardList == 0 then @@ -117,6 +124,10 @@ function startSearch() -- sort table by name (reverse for multiple results, because bottom card spawns first) table.sort(cardList, function(k1, k2) return spawnAll == (k1.data.Nickname > k2.data.Nickname) end) + spawnCardList(cardList) +end + +function spawnCardList(cardList) local rot = self.getRotation() local pos = self.positionToWorld(Vector(0, 2, -0.08)) Spawner.spawnCards(cardList, pos, rot, true) diff --git a/src/playermat/Playermat.ttslua b/src/playermat/Playermat.ttslua index c2c0e74e..a955e187 100644 --- a/src/playermat/Playermat.ttslua +++ b/src/playermat/Playermat.ttslua @@ -8,6 +8,7 @@ local searchLib = require("util/SearchLib") local tokenChecker = require("core/token/TokenChecker") local tokenManagerApi = require("core/token/TokenManagerApi") local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") +local zones = require("playermat/Zones") -- option panel data local availableOptions = { @@ -271,9 +272,15 @@ function round(num, numDecimalPlaces) end -- updates the internal "messageColor" which is used for print/broadcast statements if no player is seated ----@param clickedByColor string Colorstring of player who clicked a button +---@param clickedByColor? string Colorstring of player who clicked a button function updateMessageColor(clickedByColor) - messageColor = Player[playerColor].seated and playerColor or clickedByColor + if Player[playerColor].seated then + messageColor = playerColor + elseif clickedByColor and Player[clickedByColor].seated then + messageColor = clickedByColor + else + messageColor = Player.getPlayers()[1].color + end end --------------------------------------------------------- @@ -341,19 +348,19 @@ function doUpkeep(_, clickedByColor, isRightClick) local forcedLearning = false local rot = self.getRotation() for _, obj in ipairs(searchAroundSelf()) do - if obj.hasTag("Temporary") == true then + if obj.hasTag("Temporary") then discardListOfObjects({ obj }) - elseif obj.hasTag("UniversalToken") == true and obj.is_face_down then + elseif obj.hasTag("UniversalToken") and obj.is_face_down then obj.flip() - elseif obj.type == "Card" then - if obj.hasTag("DoInUpkeep") then - obj.call("doInUpkeep") - end - -- do not rotate, replenish, etc. on cards in investigator card area - if inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then - break - end - + end + + -- call the 'doInUpkeep' function for face-up objects with the respective tag + if obj.hasTag("DoInUpkeep") and not obj.is_face_down then + obj.call("doInUpkeep") + end + + if obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then + -- do not continue for cards in investigator card area local cardMetadata = JSON.decode(obj.getGMNotes()) or {} if not (obj.getVar("do_not_ready") or obj.hasTag("DoNotReady")) then @@ -384,8 +391,8 @@ function doUpkeep(_, clickedByColor, isRightClick) if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 and not obj.is_face_down then tokenManagerApi.maybeReplenishCard(obj, cardMetadata.uses, self) end - elseif obj.type == "Deck" and forcedLearning == false then - -- check decks for forced learning + elseif obj.type == "Deck" and not obj.is_face_down and not forcedLearning then + -- check face up decks for forced learning for _, deepObj in ipairs(obj.getObjects()) do local cardMetadata = JSON.decode(deepObj.gm_notes) or {} if cardMetadata.id == "08031" then @@ -1265,36 +1272,34 @@ end -- investigator ID grabbing and skill tracker --------------------------------------------------------- --- updates the internal investigator id and action tokens if an investigator card is detected +-- updates the internal investigator data and performs additional operations if an investigator card is detected ---@param card tts__Object Card that might be an investigator function maybeUpdateActiveInvestigator(card) + -- don't continue if this card is not in the investigator area if not inArea(self.positionToLocal(card.getPosition()), INVESTIGATOR_AREA) then return end - local notes = JSON.decode(card.getGMNotes()) - local extraToken + -- get metadata + local notes = JSON.decode(card.getGMNotes()) or {} - if notes ~= nil and notes.type == "Investigator" and notes.id ~= nil then - if notes.id == activeInvestigatorData.id then return end - activeInvestigatorData.class = notes.class - activeInvestigatorData.id = notes.id - activeInvestigatorData.miniId = getMiniId(notes.id) - extraToken = notes.extraToken - ownedObjects.InvestigatorSkillTracker.call("updateStats", { - notes.willpowerIcons, - notes.intellectIcons, - notes.combatIcons, - notes.agilityIcons - }) - updateTexture() - elseif activeInvestigatorData.id ~= "00000" then - activeInvestigatorData.class = "Neutral" - activeInvestigatorData.id = "00000" - activeInvestigatorData.miniId = "00000-m" - ownedObjects.InvestigatorSkillTracker.call("updateStats", { 1, 1, 1, 1 }) - updateTexture() - else - return - end + -- don't continue for cards without proper metadata + if notes.type ~= "Investigator" or notes.id == nil then return end + + -- don't continue if this is already the active investigator + if notes.id == activeInvestigatorData.id then return end + + -- extract relevant data from the metadata + activeInvestigatorData.class = notes.class + activeInvestigatorData.id = notes.id + activeInvestigatorData.miniId = getMiniId(notes.id) + ownedObjects.InvestigatorSkillTracker.call("updateStats", { + notes.willpowerIcons, + notes.intellectIcons, + notes.combatIcons, + notes.agilityIcons + }) + updateTexture() + + newInvestigatorCallback(notes.id) -- set proper scale for investigators local cardData = card.getData() @@ -1322,10 +1327,10 @@ function maybeUpdateActiveInvestigator(card) end -- spawn additional token (maybe specific for investigator) - if extraToken and extraToken ~= "None" then + if notes.extraToken and notes.extraToken ~= "None" then -- spawn tokens (split string by "|") local count = { action = 0, ability = 0 } - for str in string.gmatch(extraToken, "([^|]+)") do + for str in string.gmatch(notes.extraToken, "([^|]+)") do local type = "action" if str == "FreeTrigger" or str == "Reaction" then type = "ability" @@ -1344,6 +1349,53 @@ function maybeUpdateActiveInvestigator(card) end end +-- does something for specific investigators when they are loaded +function newInvestigatorCallback(newId) + updateMessageColor() + + -- remove existing object that was placed for a specific investigator + local obj = guidReferenceApi.getObjectByOwnerAndType(playerColor, "InvestigatorSpecifics") + if obj ~= nil then + obj.destruct() + guidReferenceApi.editIndex(playerColor, "InvestigatorSpecifics") + end + + if newId == "01005-p" or newId == "01005-pf" then -- parallel Wendy Adams + printToColor("Wendy Adams: There's a Game Key to add sealing options to any card:" .. + " Top menu bar > Options > Game Keys", messageColor) + elseif newId == "06003" then -- Tony Morgan + spawnInfiniteTokenBag("bounty") + printToColor("Tony Morgan: Spawned bounty tokens near your playermat.", messageColor) + elseif newId == "08004" then -- Norman Withers + printToColor("Norman Withers: At the start of the game flip the top card of your deck manually " .. + "and then the mod should keep it flipped throughout the game.", messageColor) + elseif newId == "09015" then -- Darrell Simmons + spawnInfiniteTokenBag("evidence") + printToColor("Darrell Simmons: Spawned evidence tokens near your playermat.", messageColor) + elseif newId == "89001" then -- Subject 5U-21 + local pos = zones.getZonePosition(playerColor, "BelowSetAside") + local rot = self.getRotation() + local sourceBag = guidReferenceApi.getObjectByOwnerAndType("Mythos", "OptionPanelSource") + for _, objData in ipairs(sourceBag.getData().ContainedObjects) do + if objData["Nickname"] == "Subject 5U-21 Helper" then + local spawnedObj = spawnObjectData({ data = objData, position = pos, rotation = rot }) + guidReferenceApi.editIndex(playerColor, "InvestigatorSpecifics", spawnedObj.getGUID()) + break + end + end + printToColor("Subject 5U-21: Spawned a helper to track the classes of devoured cards near your playermat. " .. + "Note that this and 'Ravenous' will work with the Attachment Helper from the option panel.", messageColor) + end +end + +-- spawns an infinite token bag of the specified type near the set aside area +function spawnInfiniteTokenBag(tokenType) + local pos = zones.getZonePosition(playerColor, "AboveSetAside") + local rot = self.getRotation() + local spawnedObj = spawnObjectData({ data = tokenManagerApi.getDataForInfiniteBag(tokenType, pos, rot) }) + guidReferenceApi.editIndex(playerColor, "InvestigatorSpecifics", spawnedObj.getGUID()) +end + -- returns the mini ID for the currently placed investigator function getMiniId(baseId) if #baseId < 16 then diff --git a/src/playermat/Zones.ttslua b/src/playermat/Zones.ttslua index 36f4742b..27d5aac6 100644 --- a/src/playermat/Zones.ttslua +++ b/src/playermat/Zones.ttslua @@ -19,6 +19,9 @@ -- SetAside4: Upgrade sheets for customizable cards -- SetAside5: Hunch Deck for Joe Diamond -- SetAside6: currently unused +-- AboveSetAside: Investigator specific object +-- BelowSetAside: Investigator specific object + do local playermatApi = require("playermat/PlayermatApi") local Zones = { } @@ -78,6 +81,8 @@ do zoneData["White"]["SetAside5"] = { 2.78, 0, 0.042 } zoneData["White"]["SetAside6"] = { 2.78, 0, 0.605 } zoneData["White"]["UnderSetAside6"] = { 2.93, 0, 0.805 } + zoneData["White"]["AboveSetAside"] = { 2.35, 0, -1.069 } + zoneData["White"]["BelowSetAside"] = { 2.85, 0, 1.650 } zoneData["Orange"] = {} zoneData["Orange"]["Investigator"] = commonZones["Investigator"] @@ -110,6 +115,8 @@ do zoneData["Orange"]["SetAside5"] = { -2.78, 0, 0.042 } zoneData["Orange"]["SetAside6"] = { -2.78, 0, 0.605 } zoneData["Orange"]["UnderSetAside6"] = { -2.93, 0, 0.805 } + zoneData["Orange"]["AboveSetAside"] = { -2.35, 0, -1.069 } + zoneData["Orange"]["BelowSetAside"] = { -2.85, 0, 1.650 } -- Green positions are the same as White and Red the same as Orange zoneData["Red"] = zoneData["Orange"] diff --git a/xml/Global/BottomBar.xml b/xml/Global/BottomBar.xml index 5cdc3c87..d680540e 100644 --- a/xml/Global/BottomBar.xml +++ b/xml/Global/BottomBar.xml @@ -6,7 +6,8 @@ color="clear"/> - + + offsetXY="-1 160">