diff --git a/downloadable/campaign/the_drowned_city_preview.json b/downloadable/campaign/the_drowned_city_preview.json index 505b81c9..8c95d3a1 100644 --- a/downloadable/campaign/the_drowned_city_preview.json +++ b/downloadable/campaign/the_drowned_city_preview.json @@ -2,12 +2,12 @@ "GUID": "197580", "Name": "Bag", "Transform": { - "posX": 59.9175758, - "posY": 1.29646468, - "posZ": 38.9555931, - "rotX": -8.42260135E-07, - "rotY": 270.0001, - "rotZ": 5.404044E-07, + "posX": 59.102, + "posY": 1.29646444, + "posZ": 38.051, + "rotX": -1.739717E-06, + "rotY": 270.000061, + "rotZ": -3.73265721E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 @@ -3006,67 +3006,6 @@ "LuaScriptState": "", "XmlUI": "" }, - { - "GUID": "tdc010", - "Name": "CardCustom", - "Transform": { - "posX": 58.99, - "posY": 3.573, - "posZ": 37.392, - "rotX": 0.0, - "rotY": 270.0, - "rotZ": 0.0, - "scaleX": 1.0, - "scaleY": 1.0, - "scaleZ": 1.0 - }, - "Nickname": "Glimpse the Void", - "Description": "", - "GMNotes": "{\n \"id\": \"11010\",\n \"type\": \"Treachery\",\n \"class\": \"Neutral\",\n \"traits\": \"Blunder. Insight.\",\n \"weakness\": true\n}", - "AltLookAngle": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "ColorDiffuse": { - "r": 0.71324, - "g": 0.71324, - "b": 0.71324 - }, - "Tags": [ - "PlayerCard" - ], - "LayoutGroupSortIndex": 0, - "Value": 0, - "Locked": false, - "Grid": true, - "Snap": true, - "IgnoreFoW": false, - "MeasureMovement": false, - "DragSelectable": true, - "Autoraise": true, - "Sticky": true, - "Tooltip": true, - "GridProjection": false, - "HideWhenFaceDown": true, - "Hands": true, - "CardID": 6601000, - "SidewaysCard": false, - "CustomDeck": { - "66010": { - "FaceURL": "https://steamusercontent-a.akamaihd.net/ugc/62585168909601053/28F4662906415997500F10213E06B216F6146419/", - "BackURL": "https://steamusercontent-a.akamaihd.net/ugc/2342503777940352139/A2D42E7E5C43D045D72CE5CFC907E4F886C8C690/", - "NumWidth": 1, - "NumHeight": 1, - "BackIsHidden": true, - "UniqueBack": false, - "Type": 0 - } - }, - "LuaScript": "", - "LuaScriptState": "", - "XmlUI": "" - }, { "GUID": "tdc016", "Name": "CardCustom", @@ -3677,7 +3616,7 @@ "Type": 0 } }, - "LuaScript": "-- Bundled by luabundle {\"version\":\"1.6.0\"}\nlocal __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)\n\tlocal loadingPlaceholder = {[{}] = true}\n\n\tlocal register\n\tlocal modules = {}\n\n\tlocal require\n\tlocal loaded = {}\n\n\tregister = function(name, body)\n\t\tif not modules[name] then\n\t\t\tmodules[name] = body\n\t\tend\n\tend\n\n\trequire = function(name)\n\t\tlocal loadedModule = loaded[name]\n\n\t\tif loadedModule then\n\t\t\tif loadedModule == loadingPlaceholder then\n\t\t\t\treturn nil\n\t\t\tend\n\t\telse\n\t\t\tif not modules[name] then\n\t\t\t\tif not superRequire then\n\t\t\t\t\tlocal identifier = type(name) == 'string' and '\\\"' .. name .. '\\\"' or tostring(name)\n\t\t\t\t\terror('Tried to require ' .. identifier .. ', but no such module has been registered')\n\t\t\t\telse\n\t\t\t\t\treturn superRequire(name)\n\t\t\t\tend\n\t\t\tend\n\n\t\t\tloaded[name] = loadingPlaceholder\n\t\t\tloadedModule = modules[name](require, loaded, register, modules)\n\t\t\tloaded[name] = loadedModule\n\t\tend\n\n\t\treturn loadedModule\n\tend\n\n\treturn require, loaded, register, modules\nend)(nil)\n__bundle_register(\"__root\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"playercards/cards/OculaObscura\")\nend)\n__bundle_register(\"chaosbag/BlessCurseManagerApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local BlessCurseManagerApi = {}\n local guidReferenceApi = require(\"core/GUIDReferenceApi\")\n\n local function getManager()\n return guidReferenceApi.getObjectByOwnerAndType(\"Mythos\", \"BlessCurseManager\")\n end\n\n -- removes all taken tokens and resets the counts\n BlessCurseManagerApi.removeTakenTokensAndReset = function()\n local BlessCurseManager = getManager()\n Wait.time(function() BlessCurseManager.call(\"removeTakenTokens\", \"Bless\") end, 0.05)\n Wait.time(function() BlessCurseManager.call(\"removeTakenTokens\", \"Curse\") end, 0.10)\n Wait.time(function() BlessCurseManager.call(\"doReset\", \"White\") end, 0.15)\n end\n\n -- updates the internal count (called by cards that seal bless/curse tokens)\n ---@param type string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param guid string GUID of the token\n BlessCurseManagerApi.sealedToken = function(type, guid)\n getManager().call(\"sealedToken\", { type = type, guid = guid })\n end\n\n -- updates the internal count (called by cards that seal bless/curse tokens)\n ---@param type string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param guid string GUID of the token\n ---@param fromBag? boolean Whether or not token was just drawn from the chaos bag\n BlessCurseManagerApi.releasedToken = function(type, guid, fromBag)\n getManager().call(\"releasedToken\", { type = type, guid = guid, fromBag = fromBag })\n end\n\n -- updates the internal count (called by cards that seal bless/curse tokens)\n ---@param type string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param guid string GUID of the token\n BlessCurseManagerApi.returnedToken = function(type, guid)\n getManager().call(\"returnedToken\", { type = type, guid = guid })\n end\n\n -- broadcasts the current status for bless/curse tokens\n ---@param playerColor string Color of the player to show the broadcast to\n BlessCurseManagerApi.broadcastStatus = function(playerColor)\n getManager().call(\"broadcastStatus\", playerColor)\n end\n\n -- removes all bless / curse tokens from the chaos bag and play\n ---@param playerColor string Color of the player to show the broadcast to\n BlessCurseManagerApi.removeAll = function(playerColor)\n getManager().call(\"doRemove\", playerColor)\n end\n\n -- adds bless / curse sealing to the hovered card\n ---@param playerColor string Color of the player to show the broadcast to\n ---@param hoveredObject tts__Object Hovered object\n ---@param noCurse? boolean True if just Bless sealing should be added (Parallel Mateo)\n BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject, noCurse)\n getManager().call(\"addMenuOptions\", { playerColor = playerColor, hoveredObject = hoveredObject, noCurse = noCurse })\n end\n\n -- adds bless / curse to the chaos bag\n ---@param tokenType string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param playerColor? string Color of the triggering player\n BlessCurseManagerApi.addToken = function(tokenType, playerColor)\n getManager().call(\"callFunctionFromApi\", { tokenType = tokenType, playerColor = playerColor, remove = false })\n end\n\n -- removes bless / curse from the chaos bag\n ---@param tokenType string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param playerColor? string Color of the triggering player\n BlessCurseManagerApi.removeToken = function(tokenType, playerColor)\n getManager().call(\"callFunctionFromApi\", { tokenType = tokenType, playerColor = playerColor, remove = true })\n end\n\n BlessCurseManagerApi.getBlessCurseInBag = function()\n return getManager().call(\"getBlessCurseInBag\", {})\n end\n\n return BlessCurseManagerApi\nend\nend)\n__bundle_register(\"chaosbag/ChaosBagApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local ChaosBagApi = {}\n\n -- respawns the chaos bag with a new state of tokens\n ---@param tokenList table List of chaos token ids\n ChaosBagApi.setChaosBagState = function(tokenList)\n Global.call(\"setChaosBagState\", tokenList)\n end\n\n -- returns a Table List of chaos token ids in the current chaos bag\n -- requires copying the data into a new table because TTS is weird about handling table return values in Global\n ChaosBagApi.getChaosBagState = function()\n local chaosBagContentsCatcher = Global.call(\"getChaosBagState\")\n local chaosBagContents = {}\n for _, v in ipairs(chaosBagContentsCatcher) do\n table.insert(chaosBagContents, v)\n end\n return chaosBagContents\n end\n\n -- checks scripting zone for chaos bag (also called by a lot of objects!)\n ChaosBagApi.findChaosBag = function()\n return Global.call(\"findChaosBag\")\n end\n\n -- returns a table of object references to the tokens in play (does not include sealed tokens!)\n ChaosBagApi.getTokensInPlay = function()\n return Global.call(\"getChaosTokensinPlay\")\n end\n\n -- returns all sealed tokens on cards to the chaos bag\n ---@param playerColor string Color of the player to show the broadcast to\n ChaosBagApi.releaseAllSealedTokens = function(playerColor)\n Global.call(\"releaseAllSealedTokens\", playerColor)\n end\n\n -- returns all drawn tokens to the chaos bag\n ChaosBagApi.returnChaosTokens = function()\n Global.call(\"returnChaosTokens\")\n end\n\n -- removes the specified chaos token from the chaos bag\n ---@param id string ID of the chaos token\n ChaosBagApi.removeChaosToken = function(id)\n Global.call(\"removeChaosToken\", id)\n end\n\n -- returns a chaos token to the bag and calls all relevant functions\n ---@param token tts__Object Chaos token to return\n ---@param fromBag boolean whether or not the token to return was in the middle of being drawn (true) or elsewhere (false)\n ChaosBagApi.returnChaosTokenToBag = function(token, fromBag)\n Global.call(\"returnChaosTokenToBag\", { token = token, fromBag = fromBag })\n end\n\n -- spawns the specified chaos token and puts it into the chaos bag\n ---@param id string ID of the chaos token\n ChaosBagApi.spawnChaosToken = function(id)\n Global.call(\"spawnChaosToken\", id)\n end\n\n -- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens\n -- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the\n -- contents of the bag should check this method before doing so.\n -- This method will broadcast a message to all players if the bag is being searched.\n ---@return any: True if the bag is manipulated, false if it should be blocked.\n ChaosBagApi.canTouchChaosTokens = function()\n return Global.call(\"canTouchChaosTokens\")\n end\n\n ChaosBagApi.activeRedrawEffect = function(validTokens, invalidTokens, returnToPool, drawSpecificToken)\n Global.call(\"activeRedrawEffect\", {\n validTokens = validTokens,\n invalidTokens = invalidTokens,\n returnToPool = returnToPool,\n drawSpecificToken = drawSpecificToken\n })\n end\n\n ChaosBagApi.getReadableTokenName = function(tokenName)\n return Global.call(\"getReadableTokenName\", tokenName)\n end\n\n ChaosBagApi.getChaosTokenName = function(chosenToken)\n return Global.call(\"getChaosTokenName\", chosenToken)\n end\n\n -- draws a chaos token to a playermat\n ---@param mat tts__Object Playermat that triggered this\n ---@param drawAdditional boolean Controls whether additional tokens should be drawn\n ---@param tokenType? string Name of token (e.g. \"Bless\") to be drawn from the bag\n ---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag\n ---@param takeParameters? table Position and rotation of the location where the new token should be drawn to, usually to replace a returned token\n ---@return tts__Object: Object reference to the token that was drawn\n ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, takeParameters)\n return Global.call(\"drawChaosToken\", {\n mat = mat,\n drawAdditional = drawAdditional,\n tokenType = tokenType,\n guidToBeResolved = guidToBeResolved,\n takeParameters = takeParameters\n })\n end\n\n -- returns a Table List of chaos token ids in the current chaos bag\n -- requires copying the data into a new table because TTS is weird about handling table return values in Global\n ChaosBagApi.getIdUrlMap = function()\n return Global.getTable(\"ID_URL_MAP\")\n end\n\n return ChaosBagApi\nend\nend)\n__bundle_register(\"core/GUIDReferenceApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local GUIDReferenceApi = {}\n\n local function getGuidHandler()\n return getObjectFromGUID(\"123456\")\n end\n\n -- Returns the matching object\n ---@param owner string Parent object for this search\n ---@param type string Type of object to search for\n ---@return any: Object reference to the matching object\n GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)\n return getGuidHandler().call(\"getObjectByOwnerAndType\", { owner = owner, type = type })\n end\n\n -- Returns all matching objects as a table with references\n ---@param type string Type of object to search for\n ---@return table: List of object references to matching objects\n GUIDReferenceApi.getObjectsByType = function(type)\n return getGuidHandler().call(\"getObjectsByType\", type)\n end\n\n -- Returns all matching objects as a table with references\n ---@param owner string Parent object for this search\n ---@return table: List of object references to matching objects\n GUIDReferenceApi.getObjectsByOwner = function(owner)\n return getGuidHandler().call(\"getObjectsByOwner\", owner)\n end\n\n -- Sends new information to the reference handler to edit the main index\n ---@param owner string Parent of the object\n ---@param type string Type of the object\n ---@param guid string GUID of the object\n GUIDReferenceApi.editIndex = function(owner, type, guid)\n return getGuidHandler().call(\"editIndex\", {\n owner = owner,\n type = type,\n guid = guid\n })\n end\n\n -- Returns the owner of an object or the object it's located on\n ---@param object tts__GameObject Object for this search\n ---@return string: Parent of the object or object it's located on\n GUIDReferenceApi.getOwnerOfObject = function(object)\n return getGuidHandler().call(\"getOwnerOfObject\", object)\n end\n\n return GUIDReferenceApi\nend\nend)\n__bundle_register(\"playercards/CardsThatSealTokens\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Library for cards that seal tokens\nThis file is used to add sealing option to cards' context menu.\nNOTE: all cards are allowed to release a single token to enable Hallow and A Watchful Peace,\nand to release all sealed tokens to allow for cards that might leave play with sealed tokens on them.\nValid options (set before requiring this file):\n\nMAX_SEALED --@type: number (maximum number of tokens allowable by the card to be sealed)\n - required for all cards\n - if MAX_SEALED is more than 1, then an XML label is created for the topmost token indicating the number of sealed tokens\n - gives an error if user tries to seal additional tokens on the card\n - example usage: \"The Chthonian Stone\"\n > MAX_SEALED = 1\n\nUPDATE_ON_HOVER --@type: boolean\n - automatically updates the context menu options when the card is hovered\n - the \"Read Bag\" function reads the content of the chaos bag to update the context menu\n - example usage: \"Unrelenting\" (to only display valid tokens)\n\nKEEP_OPEN --@type: boolean\n- meant for cards that seal single tokens multiple times (one by one)\n- makes the context menu stay open after selecting an option\n- example usage: \"Unrelenting\"\n\nSHOW_MULTI_RELEASE --@type: number (maximum amount of tokens to release at once)\n - enables an entry in the context menu\n - this entry allows releasing of multiple tokens at once, to the maximum number\n - does not fail if there are fewer than the maximum sealed\n - example usage: \"Nephthys\" (to release up to 3 bless tokens at once)\n\nSHOW_MULTI_RETURN --@type: number (amount of tokens to return to pool at once)\n - enables an entry in the context menu\n - this entry allows returning tokens to the token pool\n - fails if not enough tokens are sealed\n - example usage: \"Nephthys\" (to return 3 bless tokens at once)\n\nSHOW_RETURN_ALL --@boolean:\n - enables an entry in the context menu\n - this entry allows returning all sealed tokens to the token pool\n - example usage: \"Radiant Smite\" (to return whatever number of bless tokens that are sealed at once)\n\nSHOW_MULTI_SEAL --@type: number (amount of tokens to seal at once)\n - enables an entry in the context menu\n - this entry allows sealing of multiple tokens at once\n - example usage: \"Holy Spear\" (to seal two bless tokens at once)\n\nVALID_TOKENS --@type: table ([tokenName] = true)\n - this table defines which tokens should be abled to be sealed\n - needs to be defined for each card -> even if empty\n - example usage: \"The Chthonian Stone\"\n > VALID_TOKENS = {\n > [\"Skull\"] = true,\n > [\"Cultist\"] = true,\n > [\"Tablet\"] = true,\n > [\"Elder Thing\"] = true,\n > }\n\nINVALID_TOKENS --@type: table ([tokenName] = true)\n - this table defines which tokens are invalid for sealing\n - only needs to be defined if needed\n - usually combined with empty \"VALID_TOKENS\" table\n - example usage: \"Protective Incantation\" (not allowed to seal Auto-fail)\n\n----------------------------------------------------------\nExample 1: Crystalline Elder Sign\nThis card can only seal the \"+1\" or \"Elder Sign\" token,\nit does not need specific options for multi-sealing or releasing.\nThus it should be implemented like this:\n > VALID_TOKENS = {\n > [\"+1\"] = true,\n > [\"Elder Sign\"] = true\n > }\n > MAX_SEALED = 1\n > require...\n----------------------------------------------------------\nExample 2: Holy Spear\nThis card features the following abilities (just listing the relevant parts):\n- releasing a single bless token\n- sealing two bless tokens\nThus it should be implemented like this:\n > VALID_TOKENS = {\n > [\"Bless\"] = true\n > }\n > SHOW_MULTI_SEAL = 2\n > MAX_SEALED = 10\n > require...\n----------------------------------------------------------]]\n\nlocal blessCurseManagerApi = require(\"chaosbag/BlessCurseManagerApi\")\nlocal chaosBagApi = require(\"chaosbag/ChaosBagApi\")\nlocal guidReferenceApi = require(\"core/GUIDReferenceApi\")\nlocal playermatApi = require(\"playermat/PlayermatApi\")\nlocal tokenArrangerApi = require(\"tokens/TokenArrangerApi\")\n\nlocal sealedTokens = {}\nlocal ID_URL_MAP = {}\nlocal tokensInBag = {}\n\n-- XML background color for each token for label when stacked\nlocal tokenColor = {\n [\"Skull\"] = \"#4A0400E6\",\n [\"Cultist\"] = \"#173B0BE6\",\n [\"Tablet\"] = \"#1D2238E6\",\n [\"Elder Thing\"] = \"#4D2331E6\",\n [\"Auto-fail\"] = \"#9B0004E6\",\n [\"Bless\"] = \"#9D702CE6\",\n [\"Curse\"] = \"#633A84E6\",\n [\"Frost\"] = \"#404450E6\",\n [\"Elder Sign\"] = \"#50A8CEE6\",\n [\"\"] = \"#77674DE6\"\n}\n\nfunction updateSave()\n self.script_state = JSON.encode(sealedTokens)\nend\n\nfunction onLoad(savedData)\n -- verify sealed tokens\n for _, guid in ipairs(JSON.decode(savedData) or {}) do\n local token = getObjectFromGUID(guid)\n if token ~= nil then\n table.insert(sealedTokens, guid)\n end\n end\n\n ID_URL_MAP = chaosBagApi.getIdUrlMap()\n generateContextMenu()\n updateStackSize()\n self.addTag(\"CardThatSeals\")\nend\n\n-- builds the context menu\nfunction generateContextMenu()\n self.addContextMenuItem(\"Release one token\", releaseOneToken)\n\n -- conditional release options\n if MAX_SEALED > 1 then\n self.addContextMenuItem(\"Release all tokens\", releaseAllTokens)\n end\n\n if SHOW_MULTI_RELEASE then\n self.addContextMenuItem(\"Release \" .. SHOW_MULTI_RELEASE .. \" token(s)\", releaseMultipleTokens)\n end\n\n if RESOLVE_TOKEN then\n local label\n for tokenType, val in pairs(VALID_TOKENS) do\n if label and label ~= tokenType then\n label = \"sealed token\"\n break\n else\n label = tokenType\n end\n end\n self.addContextMenuItem(\"Resolve \" .. label, resolveSealed)\n end\n\n if SHOW_MULTI_RETURN then\n self.addContextMenuItem(\"Return \" .. SHOW_MULTI_RETURN .. \" token(s)\", returnMultipleTokens)\n end\n\n if SHOW_RETURN_ALL then\n self.addContextMenuItem(\"Return all tokens\", returnAllTokens)\n end\n\n -- main context menu options to seal tokens\n for _, map in pairs(ID_URL_MAP) do\n if (VALID_TOKENS[map.name] ~= nil) or (UPDATE_ON_HOVER and tokensInBag[map.name] and INVALID_TOKENS and not INVALID_TOKENS[map.name]) then\n if not SHOW_MULTI_SEAL then\n self.addContextMenuItem(\"Seal \" .. map.name, function(playerColor)\n sealToken(map.name, playerColor)\n end, KEEP_OPEN)\n else\n self.addContextMenuItem(\"Seal \" .. SHOW_MULTI_SEAL .. \" \" .. map.name, function(playerColor)\n readBag()\n local allowed = true\n local notFound\n\n for name, _ in pairs(VALID_TOKENS) do\n if (tokensInBag[name] or 0) < SHOW_MULTI_SEAL then\n allowed = false\n notFound = name\n end\n end\n\n if allowed then\n for i = SHOW_MULTI_SEAL, 1, -1 do\n sealToken(map.name, playerColor)\n end\n else\n printToColor(\"Not enough \" .. notFound .. \" tokens in the chaos bag.\", playerColor)\n end\n end)\n end\n end\n end\nend\n\n-- generates a list of chaos tokens that is in the chaos bag\nfunction readBag()\n local chaosbag = chaosBagApi.findChaosBag()\n tokensInBag = {}\n\n for _, token in ipairs(chaosbag.getObjects()) do\n tokensInBag[token.name] = (tokensInBag[token.name] or 0) + 1\n end\nend\n\nfunction resetSealedTokens()\n sealedTokens = {}\n updateSave()\nend\n\n-- native event from TTS - used to update the context menu for cards like \"Unrelenting\"\nfunction onHover()\n if UPDATE_ON_HOVER then\n readBag()\n self.clearContextMenu()\n generateContextMenu()\n end\nend\n\n-- seals the named token on this card\nfunction sealToken(name, playerColor)\n if #sealedTokens >= MAX_SEALED then\n printToColor(\"Cannot seal any more tokens on this card\", playerColor, \"Red\")\n return\n end\n if not chaosBagApi.canTouchChaosTokens() then return end\n local chaosbag = chaosBagApi.findChaosBag()\n for i, obj in ipairs(chaosbag.getObjects()) do\n if obj.name == name then\n chaosbag.takeObject({\n position = self.getPosition() + Vector(0, 0.5 + 0.1 * #sealedTokens, 0),\n rotation = self.getRotation(),\n index = i - 1,\n smooth = false,\n callback_function = function(token)\n local guid = token.getGUID()\n table.insert(sealedTokens, guid)\n tokenArrangerApi.layout()\n if name == \"Bless\" or name == \"Curse\" then\n blessCurseManagerApi.sealedToken(name, guid)\n end\n -- destroy XML on just covered token\n if #sealedTokens > 1 then\n local coveredToken = getObjectFromGUID(sealedTokens[#sealedTokens - 1])\n if coveredToken ~= nil then\n coveredToken.UI.setXml(\"\")\n else\n table.remove(sealedTokens, #sealedTokens - 1)\n end\n end\n updateStackSize()\n updateSave()\n end\n })\n return\n end\n end\n printToColor(name .. \" token not found in chaos bag\", playerColor)\nend\n\n-- release the last sealed token\nfunction releaseOneToken(playerColor)\n if not chaosBagApi.canTouchChaosTokens() then return end\n if #sealedTokens == 0 then\n printToColor(\"No sealed token(s) found\", playerColor)\n else\n printToColor(\"Releasing token\", playerColor)\n putTokenAway(table.remove(sealedTokens))\n updateSave()\n end\n Player[playerColor].clearSelectedObjects()\nend\n\n-- release up to multiple tokens at once with no minimum\nfunction releaseMultipleTokens(playerColor)\n if #sealedTokens == 0 then\n printToColor(\"Not enough tokens sealed.\", playerColor)\n return\n end\n\n local numRemoved = SHOW_MULTI_RELEASE\n if #sealedTokens < SHOW_MULTI_RELEASE then\n numRemoved = #sealedTokens\n end\n\n for i = 1, numRemoved do\n putTokenAway(table.remove(sealedTokens))\n end\n updateSave()\n printToColor(\"Releasing \" .. numRemoved .. \" tokens\", playerColor)\n Player[playerColor].clearSelectedObjects()\nend\n\n-- releases all sealed tokens\nfunction releaseAllTokens(playerColor)\n if not chaosBagApi.canTouchChaosTokens() then return end\n if #sealedTokens == 0 then\n printToColor(\"No sealed token(s) found\", playerColor)\n else\n printToColor(\"Releasing token(s)\", playerColor)\n for _, guid in ipairs(sealedTokens) do\n putTokenAway(guid)\n end\n sealedTokens = {}\n updateSave()\n end\n Player[playerColor].clearSelectedObjects()\nend\n\n-- returns multiple tokens at once to the token pool (with minimum)\nfunction returnMultipleTokens(playerColor)\n if SHOW_MULTI_RETURN <= #sealedTokens then\n for i = 1, SHOW_MULTI_RETURN do\n returnToken(table.remove(sealedTokens))\n end\n updateSave()\n printToColor(\"Returning \" .. SHOW_MULTI_RETURN .. \" tokens to the token pool\", playerColor)\n else\n printToColor(\"Not enough tokens sealed.\", playerColor)\n end\n Player[playerColor].clearSelectedObjects()\nend\n\n-- returns all sealed tokens to the token pool\nfunction returnAllTokens(playerColor)\n printToColor(\"Returning \" .. #sealedTokens .. \" tokens to the token pool\", playerColor)\n for i = 1, #sealedTokens do\n returnToken(table.remove(sealedTokens))\n end\n updateSave()\n Player[playerColor].clearSelectedObjects()\nend\n\n-- returns the token (referenced by GUID) to the chaos bag\nfunction putTokenAway(guid)\n local token = getObjectFromGUID(guid)\n if not token then return end\n\n local name = token.getName()\n local chaosbag = chaosBagApi.findChaosBag()\n chaosbag.putObject(token)\n tokenArrangerApi.layout()\n if name == \"Bless\" or name == \"Curse\" then\n blessCurseManagerApi.releasedToken(name, guid)\n end\n updateStackSize()\nend\n\n-- returns the token to the pool (== removes it)\nfunction returnToken(guid)\n local token = getObjectFromGUID(guid)\n if not token then return end\n\n local name = token.getName()\n token.destruct()\n if name == \"Bless\" or name == \"Curse\" then\n blessCurseManagerApi.returnedToken(name, guid)\n end\n updateStackSize()\nend\n\n-- resolves sealed token as if it came from the chaos bag\nfunction resolveSealed(playerColor)\n if #sealedTokens == 0 then\n broadcastToAll(\"No tokens sealed.\", \"Red\")\n return\n end\n\n local closestMatColor = playermatApi.getMatColorByPosition(self.getPosition())\n local mat = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, \"Playermat\")\n local guidToBeResolved = table.remove(sealedTokens)\n local resolvedToken = getObjectFromGUID(guidToBeResolved)\n if resolvedToken ~= nil then\n resolvedToken.UI.setXml(\"\")\n end\n\n updateStackSize()\n updateSave()\n chaosBagApi.drawChaosToken(mat, true, _, guidToBeResolved)\n Player[playerColor].clearSelectedObjects()\nend\n\nfunction updateStackSize()\n if MAX_SEALED == 1 then return end\n if #sealedTokens == 0 then return end\n\n -- get topmost sealed token\n local topToken = getObjectFromGUID(sealedTokens[#sealedTokens])\n if topToken == nil then return end\n\n -- handling for two-digit numbers\n local fontsize = 380\n if #sealedTokens > 9 then\n fontsize = 360\n end\n\n topToken.UI.setXmlTable({\n {\n tag = \"Panel\",\n attributes = {\n height = 380,\n width = 380,\n rotation = \"0 0 180\",\n scale = \"0.2 0.2 1\",\n position = \"0 0 -12\",\n color = tokenColor[topToken.getName()] or \"#77674DE6\"\n },\n children = {\n tag = \"Text\",\n attributes = {\n fontSize = fontsize,\n font = \"font_teutonic-arkham\",\n color = \"#ffffff\",\n outline = \"#000000\",\n outlineSize = \"8 -8\",\n text = \"x\" .. #sealedTokens\n }\n }\n }\n })\nend\nend)\n__bundle_register(\"playercards/cards/OculaObscura\", function(require, _LOADED, __bundle_register, __bundle_modules)\nVALID_TOKENS = {\n [\"+1\"] = true,\n [\"0\"] = true,\n [\"-1\"] = true,\n [\"-2\"] = true,\n [\"-3\"] = true,\n [\"-4\"] = true,\n [\"-5\"] = true,\n [\"-6\"] = true,\n [\"-7\"] = true,\n [\"-8\"] = true\n}\n\nMAX_SEALED = 1\n\nrequire(\"playercards/CardsThatSealTokens\")\nend)\n__bundle_register(\"playermat/PlayermatApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local PlayermatApi = {}\n local guidReferenceApi = require(\"core/GUIDReferenceApi\")\n local searchLib = require(\"util/SearchLib\")\n local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }\n\n -- Convenience function to look up a mat's object by color, or get all mats.\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@return table: Single-element if only single playermat is requested\n local function getMatForColor(matColor)\n if matColor == \"All\" then\n return guidReferenceApi.getObjectsByType(\"Playermat\")\n else\n return { matColor = guidReferenceApi.getObjectByOwnerAndType(matColor, \"Playermat\") }\n end\n end\n\n -- Returns the color of the closest playermat\n ---@param startPos table Starting position to get the closest mat from\n PlayermatApi.getMatColorByPosition = function(startPos)\n local result, smallestDistance\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local distance = Vector.between(startPos, mat.getPosition()):magnitude()\n if smallestDistance == nil or distance < smallestDistance then\n smallestDistance = distance\n result = matColor\n end\n end\n return result\n end\n\n -- Returns the color of the player's hand that is seated next to the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getPlayerColor = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.getVar(\"playerColor\")\n end\n end\n\n -- Returns the color of the playermat that owns the playercolor's hand\n ---@param handColor string Color of the playermat\n PlayermatApi.getMatColor = function(handColor)\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local playerColor = mat.getVar(\"playerColor\")\n if playerColor == handColor then\n return matColor\n end\n end\n end\n\n -- gets the slot data for the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getSlotData = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.getTable(\"slotData\")\n end\n end\n\n -- sets the slot data for the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param newSlotData table New slot data for the playermat\n PlayermatApi.loadSlotData = function(matColor, newSlotData)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.setTable(\"slotData\", newSlotData)\n mat.call(\"updateSave\")\n mat.call(\"updateSlotSymbols\")\n return\n end\n end\n\n -- Performs a search of the deck area of the requested playermat and returns the result as table\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getDeckAreaObjects = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"getDeckAreaObjects\")\n end\n end\n\n -- Flips the top card of the deck (useful after deck manipulation for Norman Withers)\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.flipTopCardFromDeck = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"flipTopCardFromDeck\")\n end\n end\n\n -- Returns the position of the discard pile of the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getDiscardPosition = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"returnGlobalDiscardPosition\")\n end\n end\n\n -- Returns the position of the draw pile of the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getDrawPosition = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"returnGlobalDrawPosition\")\n end\n end\n\n -- Transforms a local position into a global position\n ---@param localPos table Local position to be transformed\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.transformLocalPosition = function(localPos, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.positionToWorld(localPos)\n end\n end\n\n -- Returns the rotation of the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.returnRotation = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.getRotation()\n end\n end\n\n -- Returns a table with spawn data (position and rotation) for a helper object\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param helperName string Name of the helper object\n PlayermatApi.getHelperSpawnData = function(matColor, helperName)\n local resultTable = {}\n local localPositionTable = {\n [\"Hand Helper\"] = Vector(-0.055, 0, -1.132),\n [\"Search Assistant\"] = Vector(-0.34, 0, -1.132)\n }\n\n for color, mat in pairs(getMatForColor(matColor)) do\n resultTable[color] = {\n position = mat.positionToWorld(localPositionTable[helperName]),\n rotation = mat.getRotation()\n }\n end\n return resultTable\n end\n\n\n -- Triggers the Upkeep for the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param playerColor string Color of the calling player (for messages)\n PlayermatApi.doUpkeepFromHotkey = function(matColor, playerColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"doUpkeepFromHotkey\", playerColor)\n end\n end\n\n -- Handles discarding for the requested playermat for the provided list of objects\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param objList table List of objects to discard\n PlayermatApi.discardListOfObjects = function(matColor, objList)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"discardListOfObjects\", objList)\n end\n end\n\n -- Gets data about the active investigator\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getActiveInvestigatorData = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"getActiveInvestigatorData\")\n end\n end\n\n -- Gets data about the active investigator\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param newData table New active investigator data (class and id)\n PlayermatApi.setActiveInvestigatorData = function(matColor, newData)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"setActiveInvestigatorData\", newData)\n end\n end\n\n -- Returns the position for encounter card drawing\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param stack boolean If true, returns the leftmost position instead of the first empty from the right\n PlayermatApi.getEncounterCardDrawPosition = function(matColor, stack)\n for _, mat in pairs(getMatForColor(matColor)) do\n return Vector(mat.call(\"getEncounterCardDrawPosition\", stack))\n end\n end\n\n -- Sets the requested playermat's snap points to limit snapping to matching card types or not. If\n -- matchTypes is true, the main card slot snap points will only snap assets, while the\n -- investigator area point will only snap Investigators. If matchTypes is false, snap points will\n -- be reset to snap all cards.\n ---@param matchCardTypes boolean Whether snap points should only snap for the matching card types\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.setLimitSnapsByType = function(matchCardTypes, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"setLimitSnapsByType\", matchCardTypes)\n end\n end\n\n -- Sets the requested playermat's draw 1 button to visible\n ---@param isDrawButtonVisible boolean Whether the draw 1 button should be visible or not\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.showDrawButton = function(isDrawButtonVisible, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"showDrawButton\", isDrawButtonVisible)\n end\n end\n\n -- Shows or hides the clickable clue counter for the requested playermat\n ---@param showCounter boolean Whether the clickable counter should be present or not\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.clickableClues = function(showCounter, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"clickableClues\", showCounter)\n end\n end\n\n -- Toggles the use of class textures for the requested playermat\n ---@param state boolean Whether the class texture should be used or not\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.useClassTexture = function(state, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"useClassTexture\", state)\n end\n end\n\n -- updates the texture of the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param overrideName? string Force a specific texture\n PlayermatApi.updateTexture = function(matColor, overrideName)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"updateTexture\", overrideName)\n end\n end\n\n -- Removes all clues (to the trash for tokens and counters set to 0) for the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.removeClues = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"removeClues\")\n end\n end\n\n -- Reports the clue count for the requested playermat\n ---@param useClickableCounters boolean Controls which type of counter is getting checked\n PlayermatApi.getClueCount = function(useClickableCounters, matColor)\n local count = 0\n for _, mat in pairs(getMatForColor(matColor)) do\n count = count + mat.call(\"getClueCount\", useClickableCounters)\n end\n return count\n end\n\n -- Updates the specified owned counter\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param type string Counter to target\n ---@param newValue number Value to set the counter to\n ---@param modifier number If newValue is not provided, the existing value will be adjusted by this modifier\n PlayermatApi.updateCounter = function(matColor, type, newValue, modifier)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"updateCounter\", { type = type, newValue = newValue, modifier = modifier })\n end\n end\n\n -- Triggers the draw function for the specified playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param number number Amount of cards to draw\n PlayermatApi.drawCardsWithReshuffle = function(matColor, number)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"drawCardsWithReshuffle\", number)\n end\n end\n\n -- Returns the resource counter amount\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param type string Counter to target\n PlayermatApi.getCounterValue = function(matColor, type)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"getCounterValue\", type)\n end\n end\n\n -- Returns a list of mat colors that have an investigator placed\n PlayermatApi.getUsedMatColors = function()\n local usedColors = {}\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local searchPos = mat.positionToWorld(localInvestigatorPosition)\n local searchResult = searchLib.atPosition(searchPos, \"isCardOrDeck\")\n if #searchResult > 0 then\n table.insert(usedColors, matColor)\n end\n end\n return usedColors\n end\n\n -- Returns a list of investigator card objects\n PlayermatApi.getUsedInvestigatorCards = function()\n local usedCards = {}\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local searchPos = mat.positionToWorld(localInvestigatorPosition)\n local searchResult = searchLib.atPosition(searchPos, \"isCardOrDeck\")\n if #searchResult > 0 then\n usedCards[matColor] = searchResult[1]\n end\n end\n return usedCards\n end\n\n -- Returns investigator name\n ---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getInvestigatorName = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n local searchPos = mat.positionToWorld(localInvestigatorPosition)\n local searchResult = searchLib.atPosition(searchPos, \"isCardOrDeck\")\n if #searchResult == 1 then\n return searchResult[1].getName()\n end\n end\n return \"\"\n end\n\n -- Resets the specified skill tracker to \"1, 1, 1, 1\"\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.resetSkillTracker = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"resetSkillTracker\")\n end\n end\n\n -- Updates the XML for the slot symbols based on the slotData table\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.updateSlotSymbols = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"updateSlotSymbols\")\n end\n end\n\n -- Finds all objects on the playermat and associated set aside zone and returns a table\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param filter string Name of the filte function (see util/SearchLib)\n PlayermatApi.searchAroundPlayermat = function(matColor, filter)\n local objList = {}\n for _, mat in pairs(getMatForColor(matColor)) do\n for _, obj in ipairs(mat.call(\"searchAroundSelf\", filter)) do\n table.insert(objList, obj)\n end\n end\n return objList\n end\n\n -- Discard a non-hidden card from the corresponding player's hand\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.doDiscardOne = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"doDiscardOne\")\n end\n end\n\n -- Spawns the regular action tokens\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.spawnActionTokens = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"spawnActionTokens\")\n end\n end\n\n -- Triggers the metadata sync for all playermats\n PlayermatApi.syncAllCustomizableCards = function()\n for _, mat in pairs(getMatForColor(\"All\")) do\n mat.call(\"syncAllCustomizableCards\")\n end\n end\n\n -- moves + rotates a playermat (and related objects)\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param position table New position for the playermat\n ---@param rotationY number New y-rotation for the playermat (X and Z will be 0)\n PlayermatApi.moveAndRotate = function(matColor, position, rotationY)\n -- get mat and related objects\n local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, \"Playermat\")\n local matObjects = guidReferenceApi.getObjectsByOwner(matColor)\n\n if not mat then return end\n\n -- use current value if undefined\n position = position or mat.getPosition()\n rotationY = rotationY or mat.getRotation().y\n\n -- store relative positions\n local storedPositions = {}\n for _, obj in pairs(matObjects) do\n if obj ~= mat then\n storedPositions[obj.getGUID()] = mat.positionToLocal(obj.getPosition())\n end\n end\n\n -- also get objects on the mat\n local objectsOnMat = searchLib.onObject(mat)\n for _, obj in ipairs(objectsOnMat) do\n if obj ~= mat and storedPositions[obj.getGUID()] == nil and obj.interactable ~= false then\n storedPositions[obj.getGUID()] = mat.positionToLocal(obj.getPosition())\n end\n end\n\n -- move main mat\n mat.setPosition(position)\n mat.setRotation({ 0, rotationY, 0 })\n\n -- set new position + rotation (preserve object X / Z rotation)\n for guid, pos in pairs(storedPositions) do\n local obj = getObjectFromGUID(guid)\n obj.setPosition(mat.positionToWorld(pos))\n\n -- offset the rotation by 180 degrees if the guid matches the player hand zones\n local finalRotationY = rotationY\n if obj.type == \"Hand\" then\n finalRotationY = rotationY + 180\n end\n\n obj.setRotation(obj.getRotation():setAt(\"y\", finalRotationY))\n end\n end\n\n return PlayermatApi\nend\nend)\n__bundle_register(\"tokens/TokenArrangerApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local TokenArrangerApi = {}\n local guidReferenceApi = require(\"core/GUIDReferenceApi\")\n\n -- internal function to create a copy of the table to avoid operating on variables owned by different objects\n local function deepCopy(data)\n if type(data) ~= \"table\" then return data end\n local copiedList = {}\n for key, value in pairs(data) do\n if type(value) == \"table\" then\n copiedList[key] = deepCopy(value)\n else\n copiedList[key] = value\n end\n end\n return copiedList\n end\n\n -- local function to call the token arranger, if it is on the table\n ---@param functionName string Name of the function to cal\n ---@param argument? table Parameter to pass\n local function callIfExistent(functionName, argument)\n local tokenArranger = guidReferenceApi.getObjectByOwnerAndType(\"Mythos\", \"TokenArranger\")\n if tokenArranger ~= nil then\n return tokenArranger.call(functionName, argument)\n end\n end\n\n -- updates the token modifiers with the provided data\n ---@param fullData table Contains the chaos token metadata\n TokenArrangerApi.onTokenDataChanged = function(fullData)\n callIfExistent(\"onTokenDataChanged\", fullData)\n end\n\n -- deletes already laid out tokens\n TokenArrangerApi.deleteCopiedTokens = function()\n callIfExistent(\"deleteCopiedTokens\")\n end\n\n -- updates the laid out tokens\n TokenArrangerApi.layout = function()\n Wait.time(function() callIfExistent(\"layout\") end, 0.1)\n end\n\n TokenArrangerApi.getSaveData = function()\n return deepCopy(callIfExistent(\"getSaveData\"))\n end\n\n TokenArrangerApi.loadData = function(loadedData)\n callIfExistent(\"loadData\", loadedData)\n end\n\n return TokenArrangerApi\nend\nend)\n__bundle_register(\"util/SearchLib\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local SearchLib = {}\n local filterFunctions = {\n isCard = function(x) return x.type == \"Card\" end,\n isDeck = function(x) return x.type == \"Deck\" end,\n isCardOrDeck = function(x) return x.type == \"Card\" or x.type == \"Deck\" end,\n isClue = function(x) return x.memo == \"clueDoom\" and x.is_face_down == false end,\n isDoom = function(x) return x.memo == \"clueDoom\" and x.is_face_down == true end,\n isInteractable = function(x) return x.interactable end,\n isTileOrToken = function(x) return not x.Book and (x.type == \"Tile\" or x.type == \"Generic\") end,\n isUniversalToken = function(x) return x.getMemo() == \"universalActionAbility\" end,\n }\n\n -- performs the actual search and returns a filtered list of object references\n ---@param pos tts__Vector Global position\n ---@param rot? tts__Vector Global rotation\n ---@param size table Size\n ---@param filter? string Name of the filter function\n ---@param direction? table Direction (positive is up)\n ---@param maxDistance? number Distance for the cast\n local function returnSearchResult(pos, rot, size, filter, direction, maxDistance)\n local filterFunc = filter and filterFunctions[filter]\n local searchResult = Physics.cast({\n origin = pos,\n direction = direction or { 0, 1, 0 },\n orientation = rot or { 0, 0, 0 },\n type = 3,\n size = size,\n max_distance = maxDistance or 0\n })\n\n -- filter the result for matching objects\n local objList = {}\n for _, v in ipairs(searchResult) do\n if (not filter or filterFunc(v.hit_object)) then\n table.insert(objList, v.hit_object)\n end\n end\n return objList\n end\n\n -- searches the specified area\n function SearchLib.inArea(pos, rot, size, filter)\n return returnSearchResult(pos, rot, size, filter)\n end\n\n -- searches the area on an object\n function SearchLib.onObject(obj, filter, scale)\n scale = scale or 1\n local pos = obj.getPosition() + Vector(0, 1, 0) -- offset by half the cast's height\n local size = obj.getBounds().size:scale(scale):setAt(\"y\", 2)\n return returnSearchResult(pos, _, size, filter)\n end\n\n -- searches the specified position (a single point)\n function SearchLib.atPosition(pos, filter)\n local size = { 0.1, 2, 0.1 }\n return returnSearchResult(pos, _, size, filter)\n end\n\n -- searches below the specified position (downwards until y = 0)\n function SearchLib.belowPosition(pos, filter)\n local size = { 0.1, 2, 0.1 }\n local direction = { 0, -1, 0 }\n local maxDistance = pos.y\n return returnSearchResult(pos, _, size, filter, direction, maxDistance)\n end\n\n return SearchLib\nend\nend)\nreturn __bundle_require(\"__root\")", + "LuaScript": "-- Bundled by luabundle {\"version\":\"1.6.0\"}\nlocal __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)\n\tlocal loadingPlaceholder = {[{}] = true}\n\n\tlocal register\n\tlocal modules = {}\n\n\tlocal require\n\tlocal loaded = {}\n\n\tregister = function(name, body)\n\t\tif not modules[name] then\n\t\t\tmodules[name] = body\n\t\tend\n\tend\n\n\trequire = function(name)\n\t\tlocal loadedModule = loaded[name]\n\n\t\tif loadedModule then\n\t\t\tif loadedModule == loadingPlaceholder then\n\t\t\t\treturn nil\n\t\t\tend\n\t\telse\n\t\t\tif not modules[name] then\n\t\t\t\tif not superRequire then\n\t\t\t\t\tlocal identifier = type(name) == 'string' and '\\\"' .. name .. '\\\"' or tostring(name)\n\t\t\t\t\terror('Tried to require ' .. identifier .. ', but no such module has been registered')\n\t\t\t\telse\n\t\t\t\t\treturn superRequire(name)\n\t\t\t\tend\n\t\t\tend\n\n\t\t\tloaded[name] = loadingPlaceholder\n\t\t\tloadedModule = modules[name](require, loaded, register, modules)\n\t\t\tloaded[name] = loadedModule\n\t\tend\n\n\t\treturn loadedModule\n\tend\n\n\treturn require, loaded, register, modules\nend)(nil)\n__bundle_register(\"__root\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"playercards/cards/OculaObscura\")\nend)\n__bundle_register(\"chaosbag/BlessCurseManagerApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local BlessCurseManagerApi = {}\n local guidReferenceApi = require(\"core/GUIDReferenceApi\")\n\n local function getManager()\n return guidReferenceApi.getObjectByOwnerAndType(\"Mythos\", \"BlessCurseManager\")\n end\n\n -- removes all taken tokens and resets the counts\n BlessCurseManagerApi.removeTakenTokensAndReset = function()\n local BlessCurseManager = getManager()\n Wait.time(function() BlessCurseManager.call(\"removeTakenTokens\", \"Bless\") end, 0.05)\n Wait.time(function() BlessCurseManager.call(\"removeTakenTokens\", \"Curse\") end, 0.10)\n Wait.time(function() BlessCurseManager.call(\"doReset\", \"White\") end, 0.15)\n end\n\n -- updates the internal count (called by cards that seal bless/curse tokens)\n ---@param type string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param guid string GUID of the token\n BlessCurseManagerApi.sealedToken = function(type, guid)\n getManager().call(\"sealedToken\", { type = type, guid = guid })\n end\n\n -- updates the internal count (called by cards that seal bless/curse tokens)\n ---@param type string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param guid string GUID of the token\n ---@param fromBag? boolean Whether or not token was just drawn from the chaos bag\n BlessCurseManagerApi.releasedToken = function(type, guid, fromBag)\n getManager().call(\"releasedToken\", { type = type, guid = guid, fromBag = fromBag })\n end\n\n -- updates the internal count (called by cards that seal bless/curse tokens)\n ---@param type string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param guid string GUID of the token\n BlessCurseManagerApi.returnedToken = function(type, guid)\n getManager().call(\"returnedToken\", { type = type, guid = guid })\n end\n\n -- broadcasts the current status for bless/curse tokens\n ---@param playerColor string Color of the player to show the broadcast to\n BlessCurseManagerApi.broadcastStatus = function(playerColor)\n getManager().call(\"broadcastStatus\", playerColor)\n end\n\n -- removes all bless / curse tokens from the chaos bag and play\n ---@param playerColor string Color of the player to show the broadcast to\n BlessCurseManagerApi.removeAll = function(playerColor)\n getManager().call(\"doRemove\", playerColor)\n end\n\n -- adds bless / curse sealing to the hovered card\n ---@param playerColor string Color of the player to show the broadcast to\n ---@param hoveredObject tts__Object Hovered object\n ---@param noCurse? boolean True if just Bless sealing should be added (Parallel Mateo)\n BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject, noCurse)\n getManager().call(\"addMenuOptions\", { playerColor = playerColor, hoveredObject = hoveredObject, noCurse = noCurse })\n end\n\n -- adds bless / curse to the chaos bag\n ---@param tokenType string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param playerColor? string Color of the triggering player\n BlessCurseManagerApi.addToken = function(tokenType, playerColor)\n getManager().call(\"callFunctionFromApi\", { tokenType = tokenType, playerColor = playerColor, remove = false })\n end\n\n -- removes bless / curse from the chaos bag\n ---@param tokenType string Type of chaos token (\"Bless\" or \"Curse\")\n ---@param playerColor? string Color of the triggering player\n BlessCurseManagerApi.removeToken = function(tokenType, playerColor)\n getManager().call(\"callFunctionFromApi\", { tokenType = tokenType, playerColor = playerColor, remove = true })\n end\n\n BlessCurseManagerApi.getBlessCurseInBag = function()\n return getManager().call(\"getBlessCurseInBag\", {})\n end\n\n return BlessCurseManagerApi\nend\nend)\n__bundle_register(\"chaosbag/ChaosBagApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local ChaosBagApi = {}\n\n -- respawns the chaos bag with a new state of tokens\n ---@param tokenList table List of chaos token ids\n ChaosBagApi.setChaosBagState = function(tokenList)\n Global.call(\"setChaosBagState\", tokenList)\n end\n\n -- returns a Table List of chaos token ids in the current chaos bag\n -- requires copying the data into a new table because TTS is weird about handling table return values in Global\n ChaosBagApi.getChaosBagState = function()\n local chaosBagContentsCatcher = Global.call(\"getChaosBagState\")\n local chaosBagContents = {}\n for _, v in ipairs(chaosBagContentsCatcher) do\n table.insert(chaosBagContents, v)\n end\n return chaosBagContents\n end\n\n -- checks scripting zone for chaos bag (also called by a lot of objects!)\n ChaosBagApi.findChaosBag = function()\n return Global.call(\"findChaosBag\")\n end\n\n -- returns a table of object references to the tokens in play (does not include sealed tokens!)\n ChaosBagApi.getTokensInPlay = function()\n return Global.call(\"getChaosTokensinPlay\")\n end\n\n -- returns all sealed tokens on cards to the chaos bag\n ---@param playerColor string Color of the player to show the broadcast to\n ChaosBagApi.releaseAllSealedTokens = function(playerColor)\n Global.call(\"releaseAllSealedTokens\", playerColor)\n end\n\n -- returns all drawn tokens to the chaos bag\n ChaosBagApi.returnChaosTokens = function()\n Global.call(\"returnChaosTokens\")\n end\n\n -- removes the specified chaos token from the chaos bag\n ---@param id string ID of the chaos token\n ChaosBagApi.removeChaosToken = function(id)\n Global.call(\"removeChaosToken\", id)\n end\n\n -- returns a chaos token to the bag and calls all relevant functions\n ---@param token tts__Object Chaos token to return\n ---@param fromBag boolean whether or not the token to return was in the middle of being drawn (true) or elsewhere (false)\n ChaosBagApi.returnChaosTokenToBag = function(token, fromBag)\n Global.call(\"returnChaosTokenToBag\", { token = token, fromBag = fromBag })\n end\n\n -- spawns the specified chaos token and puts it into the chaos bag\n ---@param id string ID of the chaos token\n ChaosBagApi.spawnChaosToken = function(id)\n Global.call(\"spawnChaosToken\", id)\n end\n\n -- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens\n -- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the\n -- contents of the bag should check this method before doing so.\n -- This method will broadcast a message to all players if the bag is being searched.\n ---@return any: True if the bag is manipulated, false if it should be blocked.\n ChaosBagApi.canTouchChaosTokens = function()\n return Global.call(\"canTouchChaosTokens\")\n end\n\n ChaosBagApi.activeRedrawEffect = function(validTokens, invalidTokens, returnToPool, drawSpecificToken)\n Global.call(\"activeRedrawEffect\", {\n validTokens = validTokens,\n invalidTokens = invalidTokens,\n returnToPool = returnToPool,\n drawSpecificToken = drawSpecificToken\n })\n end\n\n ChaosBagApi.getReadableTokenName = function(tokenName)\n return Global.call(\"getReadableTokenName\", tokenName)\n end\n\n ChaosBagApi.getChaosTokenName = function(chosenToken)\n return Global.call(\"getChaosTokenName\", chosenToken)\n end\n\n -- draws a chaos token to a playermat\n ---@param mat tts__Object Playermat that triggered this\n ---@param drawAdditional boolean Controls whether additional tokens should be drawn\n ---@param tokenType? string Name of token (e.g. \"Bless\") to be drawn from the bag\n ---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag\n ---@param takeParameters? table Position and rotation of the location where the new token should be drawn to, usually to replace a returned token\n ---@return tts__Object: Object reference to the token that was drawn\n ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, takeParameters)\n return Global.call(\"drawChaosToken\", {\n mat = mat,\n drawAdditional = drawAdditional,\n tokenType = tokenType,\n guidToBeResolved = guidToBeResolved,\n takeParameters = takeParameters\n })\n end\n\n -- returns a Table List of chaos token ids in the current chaos bag\n -- requires copying the data into a new table because TTS is weird about handling table return values in Global\n ChaosBagApi.getIdUrlMap = function()\n return Global.getTable(\"ID_URL_MAP\")\n end\n\n return ChaosBagApi\nend\nend)\n__bundle_register(\"core/GUIDReferenceApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local GUIDReferenceApi = {}\n\n local function getGuidHandler()\n return getObjectFromGUID(\"123456\")\n end\n\n -- Returns the matching object\n ---@param owner string Parent object for this search\n ---@param type string Type of object to search for\n ---@return any: Object reference to the matching object\n GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)\n return getGuidHandler().call(\"getObjectByOwnerAndType\", { owner = owner, type = type })\n end\n\n -- Returns all matching objects as a table with references\n ---@param type string Type of object to search for\n ---@return table: List of object references to matching objects\n GUIDReferenceApi.getObjectsByType = function(type)\n return getGuidHandler().call(\"getObjectsByType\", type)\n end\n\n -- Returns all matching objects as a table with references\n ---@param owner string Parent object for this search\n ---@return table: List of object references to matching objects\n GUIDReferenceApi.getObjectsByOwner = function(owner)\n return getGuidHandler().call(\"getObjectsByOwner\", owner)\n end\n\n -- Sends new information to the reference handler to edit the main index (if type/guid are omitted, entry will be removed)\n ---@param owner string Parent of the object\n ---@param type? string Type of the object\n ---@param guid? string GUID of the object\n GUIDReferenceApi.editIndex = function(owner, type, guid)\n return getGuidHandler().call(\"editIndex\", {\n owner = owner,\n type = type,\n guid = guid\n })\n end\n\n -- Returns the owner of an object or the object it's located on\n ---@param object tts__GameObject Object for this search\n ---@return string: Parent of the object or object it's located on\n GUIDReferenceApi.getOwnerOfObject = function(object)\n return getGuidHandler().call(\"getOwnerOfObject\", object)\n end\n\n return GUIDReferenceApi\nend\nend)\n__bundle_register(\"playercards/CardsThatSealTokens\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Library for cards that seal tokens\nThis file is used to add sealing option to cards' context menu.\nNOTE: all cards are allowed to release a single token to enable Hallow and A Watchful Peace,\nand to release all sealed tokens to allow for cards that might leave play with sealed tokens on them.\nValid options (set before requiring this file):\n\nMAX_SEALED --@type: number (maximum number of tokens allowable by the card to be sealed)\n - required for all cards\n - if MAX_SEALED is more than 1, then an XML label is created for the topmost token indicating the number of sealed tokens\n - gives an error if user tries to seal additional tokens on the card\n - example usage: \"The Chthonian Stone\"\n > MAX_SEALED = 1\n\nUPDATE_ON_HOVER --@type: boolean\n - automatically updates the context menu options when the card is hovered\n - the \"Read Bag\" function reads the content of the chaos bag to update the context menu\n - example usage: \"Unrelenting\" (to only display valid tokens)\n\nKEEP_OPEN --@type: boolean\n- meant for cards that seal single tokens multiple times (one by one)\n- makes the context menu stay open after selecting an option\n- example usage: \"Unrelenting\"\n\nSHOW_MULTI_RELEASE --@type: number (maximum amount of tokens to release at once)\n - enables an entry in the context menu\n - this entry allows releasing of multiple tokens at once, to the maximum number\n - does not fail if there are fewer than the maximum sealed\n - example usage: \"Nephthys\" (to release up to 3 bless tokens at once)\n\nSHOW_MULTI_RETURN --@type: number (amount of tokens to return to pool at once)\n - enables an entry in the context menu\n - this entry allows returning tokens to the token pool\n - fails if not enough tokens are sealed\n - example usage: \"Nephthys\" (to return 3 bless tokens at once)\n\nSHOW_RETURN_ALL --@boolean:\n - enables an entry in the context menu\n - this entry allows returning all sealed tokens to the token pool\n - example usage: \"Radiant Smite\" (to return whatever number of bless tokens that are sealed at once)\n\nSHOW_MULTI_SEAL --@type: number (amount of tokens to seal at once)\n - enables an entry in the context menu\n - this entry allows sealing of multiple tokens at once\n - example usage: \"Holy Spear\" (to seal two bless tokens at once)\n\nVALID_TOKENS --@type: table ([tokenName] = true)\n - this table defines which tokens should be abled to be sealed\n - needs to be defined for each card -> even if empty\n - example usage: \"The Chthonian Stone\"\n > VALID_TOKENS = {\n > [\"Skull\"] = true,\n > [\"Cultist\"] = true,\n > [\"Tablet\"] = true,\n > [\"Elder Thing\"] = true,\n > }\n\nINVALID_TOKENS --@type: table ([tokenName] = true)\n - this table defines which tokens are invalid for sealing\n - only needs to be defined if needed\n - usually combined with empty \"VALID_TOKENS\" table\n - example usage: \"Protective Incantation\" (not allowed to seal Auto-fail)\n\n----------------------------------------------------------\nExample 1: Crystalline Elder Sign\nThis card can only seal the \"+1\" or \"Elder Sign\" token,\nit does not need specific options for multi-sealing or releasing.\nThus it should be implemented like this:\n > VALID_TOKENS = {\n > [\"+1\"] = true,\n > [\"Elder Sign\"] = true\n > }\n > MAX_SEALED = 1\n > require...\n----------------------------------------------------------\nExample 2: Holy Spear\nThis card features the following abilities (just listing the relevant parts):\n- releasing a single bless token\n- sealing two bless tokens\nThus it should be implemented like this:\n > VALID_TOKENS = {\n > [\"Bless\"] = true\n > }\n > SHOW_MULTI_SEAL = 2\n > MAX_SEALED = 10\n > require...\n----------------------------------------------------------]]\n\nlocal blessCurseManagerApi = require(\"chaosbag/BlessCurseManagerApi\")\nlocal chaosBagApi = require(\"chaosbag/ChaosBagApi\")\nlocal guidReferenceApi = require(\"core/GUIDReferenceApi\")\nlocal playermatApi = require(\"playermat/PlayermatApi\")\nlocal tokenArrangerApi = require(\"tokens/TokenArrangerApi\")\n\nlocal sealedTokens = {}\nlocal ID_URL_MAP = {}\nlocal tokensInBag = {}\n\n-- XML background color for each token for label when stacked\nlocal tokenColor = {\n [\"Skull\"] = \"#4A0400E6\",\n [\"Cultist\"] = \"#173B0BE6\",\n [\"Tablet\"] = \"#1D2238E6\",\n [\"Elder Thing\"] = \"#4D2331E6\",\n [\"Auto-fail\"] = \"#9B0004E6\",\n [\"Bless\"] = \"#9D702CE6\",\n [\"Curse\"] = \"#633A84E6\",\n [\"Frost\"] = \"#404450E6\",\n [\"Elder Sign\"] = \"#50A8CEE6\",\n [\"\"] = \"#77674DE6\"\n}\n\nfunction updateSave()\n self.script_state = JSON.encode(sealedTokens)\nend\n\nfunction onLoad(savedData)\n -- verify sealed tokens\n for _, guid in ipairs(JSON.decode(savedData) or {}) do\n local token = getObjectFromGUID(guid)\n if token ~= nil then\n table.insert(sealedTokens, guid)\n end\n end\n\n ID_URL_MAP = chaosBagApi.getIdUrlMap()\n generateContextMenu()\n updateStackSize()\n self.addTag(\"CardThatSeals\")\nend\n\n-- builds the context menu\nfunction generateContextMenu()\n self.addContextMenuItem(\"Release one token\", releaseOneToken)\n\n -- conditional release options\n if MAX_SEALED > 1 then\n self.addContextMenuItem(\"Release all tokens\", releaseAllTokens)\n end\n\n if SHOW_MULTI_RELEASE then\n self.addContextMenuItem(\"Release \" .. SHOW_MULTI_RELEASE .. \" token(s)\", releaseMultipleTokens)\n end\n\n if RESOLVE_TOKEN then\n local label\n for tokenType, val in pairs(VALID_TOKENS) do\n if label and label ~= tokenType then\n label = \"sealed token\"\n break\n else\n label = tokenType\n end\n end\n self.addContextMenuItem(\"Resolve \" .. label, resolveSealed)\n end\n\n if SHOW_MULTI_RETURN then\n self.addContextMenuItem(\"Return \" .. SHOW_MULTI_RETURN .. \" token(s)\", returnMultipleTokens)\n end\n\n if SHOW_RETURN_ALL then\n self.addContextMenuItem(\"Return all tokens\", returnAllTokens)\n end\n\n -- main context menu options to seal tokens\n for _, map in pairs(ID_URL_MAP) do\n if (VALID_TOKENS[map.name] ~= nil) or (UPDATE_ON_HOVER and tokensInBag[map.name] and INVALID_TOKENS and not INVALID_TOKENS[map.name]) then\n if not SHOW_MULTI_SEAL then\n self.addContextMenuItem(\"Seal \" .. map.name, function(playerColor)\n sealToken(map.name, playerColor)\n end, KEEP_OPEN)\n else\n self.addContextMenuItem(\"Seal \" .. SHOW_MULTI_SEAL .. \" \" .. map.name, function(playerColor)\n readBag()\n local allowed = true\n local notFound\n\n for name, _ in pairs(VALID_TOKENS) do\n if (tokensInBag[name] or 0) < SHOW_MULTI_SEAL then\n allowed = false\n notFound = name\n end\n end\n\n if allowed then\n for i = SHOW_MULTI_SEAL, 1, -1 do\n sealToken(map.name, playerColor)\n end\n else\n printToColor(\"Not enough \" .. notFound .. \" tokens in the chaos bag.\", playerColor)\n end\n end)\n end\n end\n end\nend\n\n-- generates a list of chaos tokens that is in the chaos bag\nfunction readBag()\n local chaosbag = chaosBagApi.findChaosBag()\n tokensInBag = {}\n\n for _, token in ipairs(chaosbag.getObjects()) do\n tokensInBag[token.name] = (tokensInBag[token.name] or 0) + 1\n end\nend\n\nfunction resetSealedTokens()\n sealedTokens = {}\n updateSave()\nend\n\n-- native event from TTS - used to update the context menu for cards like \"Unrelenting\"\nfunction onHover()\n if UPDATE_ON_HOVER then\n readBag()\n self.clearContextMenu()\n generateContextMenu()\n end\nend\n\n-- seals the named token on this card\nfunction sealToken(name, playerColor)\n if #sealedTokens >= MAX_SEALED then\n printToColor(\"Cannot seal any more tokens on this card\", playerColor, \"Red\")\n return\n end\n if not chaosBagApi.canTouchChaosTokens() then return end\n local chaosbag = chaosBagApi.findChaosBag()\n for i, obj in ipairs(chaosbag.getObjects()) do\n if obj.name == name then\n chaosbag.takeObject({\n position = self.getPosition() + Vector(0, 0.5 + 0.1 * #sealedTokens, 0),\n rotation = self.getRotation(),\n index = i - 1,\n smooth = false,\n callback_function = function(token)\n local guid = token.getGUID()\n table.insert(sealedTokens, guid)\n tokenArrangerApi.layout()\n if name == \"Bless\" or name == \"Curse\" then\n blessCurseManagerApi.sealedToken(name, guid)\n end\n -- destroy XML on just covered token\n if #sealedTokens > 1 then\n local coveredToken = getObjectFromGUID(sealedTokens[#sealedTokens - 1])\n if coveredToken ~= nil then\n coveredToken.UI.setXml(\"\")\n else\n table.remove(sealedTokens, #sealedTokens - 1)\n end\n end\n updateStackSize()\n updateSave()\n end\n })\n return\n end\n end\n printToColor(name .. \" token not found in chaos bag\", playerColor)\nend\n\n-- release the last sealed token\nfunction releaseOneToken(playerColor)\n if not chaosBagApi.canTouchChaosTokens() then return end\n if #sealedTokens == 0 then\n printToColor(\"No sealed token(s) found\", playerColor)\n else\n printToColor(\"Releasing token\", playerColor)\n putTokenAway(table.remove(sealedTokens))\n updateSave()\n end\n Player[playerColor].clearSelectedObjects()\nend\n\n-- release up to multiple tokens at once with no minimum\nfunction releaseMultipleTokens(playerColor)\n if #sealedTokens == 0 then\n printToColor(\"Not enough tokens sealed.\", playerColor)\n return\n end\n\n local numRemoved = SHOW_MULTI_RELEASE\n if #sealedTokens < SHOW_MULTI_RELEASE then\n numRemoved = #sealedTokens\n end\n\n for i = 1, numRemoved do\n putTokenAway(table.remove(sealedTokens))\n end\n updateSave()\n printToColor(\"Releasing \" .. numRemoved .. \" tokens\", playerColor)\n Player[playerColor].clearSelectedObjects()\nend\n\n-- releases all sealed tokens\nfunction releaseAllTokens(playerColor)\n if not chaosBagApi.canTouchChaosTokens() then return end\n if #sealedTokens == 0 then\n printToColor(\"No sealed token(s) found\", playerColor)\n else\n printToColor(\"Releasing token(s)\", playerColor)\n for _, guid in ipairs(sealedTokens) do\n putTokenAway(guid)\n end\n sealedTokens = {}\n updateSave()\n end\n Player[playerColor].clearSelectedObjects()\nend\n\n-- returns multiple tokens at once to the token pool (with minimum)\nfunction returnMultipleTokens(playerColor)\n if SHOW_MULTI_RETURN <= #sealedTokens then\n for i = 1, SHOW_MULTI_RETURN do\n returnToken(table.remove(sealedTokens))\n end\n updateSave()\n printToColor(\"Returning \" .. SHOW_MULTI_RETURN .. \" tokens to the token pool\", playerColor)\n else\n printToColor(\"Not enough tokens sealed.\", playerColor)\n end\n Player[playerColor].clearSelectedObjects()\nend\n\n-- returns all sealed tokens to the token pool\nfunction returnAllTokens(playerColor)\n printToColor(\"Returning \" .. #sealedTokens .. \" tokens to the token pool\", playerColor)\n for i = 1, #sealedTokens do\n returnToken(table.remove(sealedTokens))\n end\n updateSave()\n Player[playerColor].clearSelectedObjects()\nend\n\n-- returns the token (referenced by GUID) to the chaos bag\nfunction putTokenAway(guid)\n local token = getObjectFromGUID(guid)\n if not token then return end\n\n local name = token.getName()\n local chaosbag = chaosBagApi.findChaosBag()\n chaosbag.putObject(token)\n tokenArrangerApi.layout()\n if name == \"Bless\" or name == \"Curse\" then\n blessCurseManagerApi.releasedToken(name, guid)\n end\n updateStackSize()\nend\n\n-- returns the token to the pool (== removes it)\nfunction returnToken(guid)\n local token = getObjectFromGUID(guid)\n if not token then return end\n\n local name = token.getName()\n token.destruct()\n if name == \"Bless\" or name == \"Curse\" then\n blessCurseManagerApi.returnedToken(name, guid)\n end\n updateStackSize()\nend\n\n-- resolves sealed token as if it came from the chaos bag\nfunction resolveSealed(playerColor)\n if #sealedTokens == 0 then\n broadcastToAll(\"No tokens sealed.\", \"Red\")\n return\n end\n\n local closestMatColor = playermatApi.getMatColorByPosition(self.getPosition())\n local mat = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, \"Playermat\")\n local guidToBeResolved = table.remove(sealedTokens)\n local resolvedToken = getObjectFromGUID(guidToBeResolved)\n if resolvedToken ~= nil then\n resolvedToken.UI.setXml(\"\")\n end\n\n updateStackSize()\n updateSave()\n chaosBagApi.drawChaosToken(mat, true, _, guidToBeResolved)\n Player[playerColor].clearSelectedObjects()\nend\n\nfunction updateStackSize()\n if MAX_SEALED == 1 then return end\n if #sealedTokens == 0 then return end\n\n -- get topmost sealed token\n local topToken = getObjectFromGUID(sealedTokens[#sealedTokens])\n if topToken == nil then return end\n\n -- handling for two-digit numbers\n local fontsize = 380\n if #sealedTokens > 9 then\n fontsize = 360\n end\n\n topToken.UI.setXmlTable({\n {\n tag = \"Panel\",\n attributes = {\n height = 380,\n width = 380,\n rotation = \"0 0 180\",\n scale = \"0.2 0.2 1\",\n position = \"0 0 -12\",\n color = tokenColor[topToken.getName()] or \"#77674DE6\"\n },\n children = {\n tag = \"Text\",\n attributes = {\n fontSize = fontsize,\n font = \"font_teutonic-arkham\",\n color = \"#ffffff\",\n outline = \"#000000\",\n outlineSize = \"8 -8\",\n text = \"x\" .. #sealedTokens\n }\n }\n }\n })\nend\nend)\n__bundle_register(\"playercards/cards/OculaObscura\", function(require, _LOADED, __bundle_register, __bundle_modules)\nVALID_TOKENS = {\n [\"+1\"] = true,\n [\"0\"] = true,\n [\"-1\"] = true,\n [\"-2\"] = true,\n [\"-3\"] = true,\n [\"-4\"] = true,\n [\"-5\"] = true,\n [\"-6\"] = true,\n [\"-7\"] = true,\n [\"-8\"] = true\n}\n\nMAX_SEALED = 1\n\nrequire(\"playercards/CardsThatSealTokens\")\nend)\n__bundle_register(\"playermat/PlayermatApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local PlayermatApi = {}\n local guidReferenceApi = require(\"core/GUIDReferenceApi\")\n local searchLib = require(\"util/SearchLib\")\n local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }\n\n -- Convenience function to look up a mat's object by color, or get all mats.\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@return table: Single-element if only single playermat is requested\n local function getMatForColor(matColor)\n if matColor == \"All\" then\n return guidReferenceApi.getObjectsByType(\"Playermat\")\n else\n return { matColor = guidReferenceApi.getObjectByOwnerAndType(matColor, \"Playermat\") }\n end\n end\n\n -- Returns the color of the closest playermat\n ---@param startPos table Starting position to get the closest mat from\n PlayermatApi.getMatColorByPosition = function(startPos)\n local result, smallestDistance\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local distance = Vector.between(startPos, mat.getPosition()):magnitude()\n if smallestDistance == nil or distance < smallestDistance then\n smallestDistance = distance\n result = matColor\n end\n end\n return result\n end\n\n -- Returns the color of the player's hand that is seated next to the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getPlayerColor = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.getVar(\"playerColor\")\n end\n end\n\n -- Returns the color of the playermat that owns the playercolor's hand\n ---@param handColor string Color of the playermat\n PlayermatApi.getMatColor = function(handColor)\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local playerColor = mat.getVar(\"playerColor\")\n if playerColor == handColor then\n return matColor\n end\n end\n return nil\n end\n\n -- gets the slot data for the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getSlotData = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.getTable(\"slotData\")\n end\n end\n\n -- sets the slot data for the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param newSlotData table New slot data for the playermat\n PlayermatApi.loadSlotData = function(matColor, newSlotData)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.setTable(\"slotData\", newSlotData)\n mat.call(\"updateSave\")\n mat.call(\"updateSlotSymbols\")\n return\n end\n end\n\n -- Performs a search of the deck area of the requested playermat and returns the result as table\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getDeckAreaObjects = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"getDeckAreaObjects\")\n end\n end\n\n -- Flips the top card of the deck (useful after deck manipulation for Norman Withers)\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.flipTopCardFromDeck = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"flipTopCardFromDeck\")\n end\n end\n\n -- Returns the position of the discard pile of the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getDiscardPosition = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"returnGlobalDiscardPosition\")\n end\n end\n\n -- Returns the position of the draw pile of the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getDrawPosition = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"returnGlobalDrawPosition\")\n end\n end\n\n -- Transforms a local position into a global position\n ---@param localPos table Local position to be transformed\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.transformLocalPosition = function(localPos, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.positionToWorld(localPos)\n end\n end\n\n -- Returns the rotation of the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.returnRotation = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.getRotation()\n end\n end\n\n -- Returns a table with spawn data (position and rotation) for a helper object\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param helperName string Name of the helper object\n PlayermatApi.getHelperSpawnData = function(matColor, helperName)\n local resultTable = {}\n local localPositionTable = {\n [\"Hand Helper\"] = Vector(-0.055, 0, -1.132),\n [\"Search Assistant\"] = Vector(-0.34, 0, -1.132)\n }\n\n for color, mat in pairs(getMatForColor(matColor)) do\n resultTable[color] = {\n position = mat.positionToWorld(localPositionTable[helperName]),\n rotation = mat.getRotation()\n }\n end\n return resultTable\n end\n\n\n -- Triggers the Upkeep for the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param playerColor string Color of the calling player (for messages)\n PlayermatApi.doUpkeepFromHotkey = function(matColor, playerColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"doUpkeepFromHotkey\", playerColor)\n end\n end\n\n -- Handles discarding for the requested playermat for the provided list of objects\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param objList table List of objects to discard\n PlayermatApi.discardListOfObjects = function(matColor, objList)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"discardListOfObjects\", objList)\n end\n end\n\n -- Gets data about the active investigator\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getActiveInvestigatorData = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"getActiveInvestigatorData\")\n end\n end\n\n -- Gets data about the active investigator\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param newData table New active investigator data (class and id)\n PlayermatApi.setActiveInvestigatorData = function(matColor, newData)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"setActiveInvestigatorData\", newData)\n end\n end\n\n -- Returns the position for encounter card drawing\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param stack boolean If true, returns the leftmost position instead of the first empty from the right\n PlayermatApi.getEncounterCardDrawPosition = function(matColor, stack)\n for _, mat in pairs(getMatForColor(matColor)) do\n return Vector(mat.call(\"getEncounterCardDrawPosition\", stack))\n end\n end\n\n -- Sets the requested playermat's snap points to limit snapping to matching card types or not. If\n -- matchTypes is true, the main card slot snap points will only snap assets, while the\n -- investigator area point will only snap Investigators. If matchTypes is false, snap points will\n -- be reset to snap all cards.\n ---@param matchCardTypes boolean Whether snap points should only snap for the matching card types\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.setLimitSnapsByType = function(matchCardTypes, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"setLimitSnapsByType\", matchCardTypes)\n end\n end\n\n -- Sets the requested playermat's draw 1 button to visible\n ---@param isDrawButtonVisible boolean Whether the draw 1 button should be visible or not\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.showDrawButton = function(isDrawButtonVisible, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"showDrawButton\", isDrawButtonVisible)\n end\n end\n\n -- Shows or hides the clickable clue counter for the requested playermat\n ---@param showCounter boolean Whether the clickable counter should be present or not\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.clickableClues = function(showCounter, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"clickableClues\", showCounter)\n end\n end\n\n -- Toggles the use of class textures for the requested playermat\n ---@param state boolean Whether the class texture should be used or not\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.useClassTexture = function(state, matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"useClassTexture\", state)\n end\n end\n\n -- updates the texture of the playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param overrideName? string Force a specific texture\n PlayermatApi.updateTexture = function(matColor, overrideName)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"updateTexture\", overrideName)\n end\n end\n\n -- Removes all clues (to the trash for tokens and counters set to 0) for the requested playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.removeClues = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"removeClues\")\n end\n end\n\n -- Reports the clue count for the requested playermat\n ---@param useClickableCounters boolean Controls which type of counter is getting checked\n PlayermatApi.getClueCount = function(useClickableCounters, matColor)\n local count = 0\n for _, mat in pairs(getMatForColor(matColor)) do\n count = count + mat.call(\"getClueCount\", useClickableCounters)\n end\n return count\n end\n\n -- Updates the specified owned counter\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param type string Counter to target\n ---@param newValue number Value to set the counter to\n ---@param modifier number If newValue is not provided, the existing value will be adjusted by this modifier\n PlayermatApi.updateCounter = function(matColor, type, newValue, modifier)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"updateCounter\", { type = type, newValue = newValue, modifier = modifier })\n end\n end\n\n -- Triggers the draw function for the specified playermat\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param number number Amount of cards to draw\n PlayermatApi.drawCardsWithReshuffle = function(matColor, number)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"drawCardsWithReshuffle\", number)\n end\n end\n\n -- Returns the resource counter amount\n ---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support \"All\")\n ---@param type string Counter to target\n PlayermatApi.getCounterValue = function(matColor, type)\n for _, mat in pairs(getMatForColor(matColor)) do\n return mat.call(\"getCounterValue\", type)\n end\n end\n\n -- Returns a list of mat colors that have an investigator placed\n PlayermatApi.getUsedMatColors = function()\n local usedColors = {}\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local searchPos = mat.positionToWorld(localInvestigatorPosition)\n local searchResult = searchLib.atPosition(searchPos, \"isCardOrDeck\")\n if #searchResult > 0 then\n table.insert(usedColors, matColor)\n end\n end\n return usedColors\n end\n\n -- Returns a list of investigator card objects\n PlayermatApi.getUsedInvestigatorCards = function()\n local usedCards = {}\n for matColor, mat in pairs(getMatForColor(\"All\")) do\n local searchPos = mat.positionToWorld(localInvestigatorPosition)\n local searchResult = searchLib.atPosition(searchPos, \"isCardOrDeck\")\n if #searchResult > 0 then\n usedCards[matColor] = searchResult[1]\n end\n end\n return usedCards\n end\n\n -- Returns investigator name\n ---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support \"All\")\n PlayermatApi.getInvestigatorName = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n local searchPos = mat.positionToWorld(localInvestigatorPosition)\n local searchResult = searchLib.atPosition(searchPos, \"isCardOrDeck\")\n if #searchResult == 1 then\n return searchResult[1].getName()\n end\n end\n return \"\"\n end\n\n -- Resets the specified skill tracker to \"1, 1, 1, 1\"\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.resetSkillTracker = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"resetSkillTracker\")\n end\n end\n\n -- Updates the XML for the slot symbols based on the slotData table\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.updateSlotSymbols = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"updateSlotSymbols\")\n end\n end\n\n -- Finds all objects on the playermat and associated set aside zone and returns a table\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param filter string Name of the filte function (see util/SearchLib)\n PlayermatApi.searchAroundPlayermat = function(matColor, filter)\n local objList = {}\n for _, mat in pairs(getMatForColor(matColor)) do\n for _, obj in ipairs(mat.call(\"searchAroundSelf\", filter)) do\n table.insert(objList, obj)\n end\n end\n return objList\n end\n\n -- Discard a non-hidden card from the corresponding player's hand\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.doDiscardOne = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"doDiscardOne\")\n end\n end\n\n -- Spawns the regular action tokens\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n PlayermatApi.spawnActionTokens = function(matColor)\n for _, mat in pairs(getMatForColor(matColor)) do\n mat.call(\"spawnActionTokens\")\n end\n end\n\n -- Triggers the metadata sync for all playermats\n PlayermatApi.syncAllCustomizableCards = function()\n for _, mat in pairs(getMatForColor(\"All\")) do\n mat.call(\"syncAllCustomizableCards\")\n end\n end\n\n -- moves + rotates a playermat (and related objects)\n ---@param matColor string Color of the playermat - White, Orange, Green, Red or All\n ---@param position table New position for the playermat\n ---@param rotationY number New y-rotation for the playermat (X and Z will be 0)\n PlayermatApi.moveAndRotate = function(matColor, position, rotationY)\n -- get mat and related objects\n local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, \"Playermat\")\n local matObjects = guidReferenceApi.getObjectsByOwner(matColor)\n\n if not mat then return end\n\n -- use current value if undefined\n position = position or mat.getPosition()\n rotationY = rotationY or mat.getRotation().y\n\n -- store relative positions\n local storedPositions = {}\n for _, obj in pairs(matObjects) do\n if obj ~= mat then\n storedPositions[obj.getGUID()] = mat.positionToLocal(obj.getPosition())\n end\n end\n\n -- also get objects on the mat\n local objectsOnMat = searchLib.onObject(mat)\n for _, obj in ipairs(objectsOnMat) do\n if obj ~= mat and storedPositions[obj.getGUID()] == nil and obj.interactable ~= false then\n storedPositions[obj.getGUID()] = mat.positionToLocal(obj.getPosition())\n end\n end\n\n -- move main mat\n mat.setPosition(position)\n mat.setRotation({ 0, rotationY, 0 })\n\n -- set new position + rotation (preserve object X / Z rotation)\n for guid, pos in pairs(storedPositions) do\n local obj = getObjectFromGUID(guid)\n obj.setPosition(mat.positionToWorld(pos))\n\n -- offset the rotation by 180 degrees if the guid matches the player hand zones\n local finalRotationY = rotationY\n if obj.type == \"Hand\" then\n finalRotationY = rotationY + 180\n end\n\n obj.setRotation(obj.getRotation():setAt(\"y\", finalRotationY))\n end\n end\n\n return PlayermatApi\nend\nend)\n__bundle_register(\"tokens/TokenArrangerApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local TokenArrangerApi = {}\n local guidReferenceApi = require(\"core/GUIDReferenceApi\")\n\n -- internal function to create a copy of the table to avoid operating on variables owned by different objects\n local function deepCopy(data)\n if type(data) ~= \"table\" then return data end\n local copiedList = {}\n for key, value in pairs(data) do\n if type(value) == \"table\" then\n copiedList[key] = deepCopy(value)\n else\n copiedList[key] = value\n end\n end\n return copiedList\n end\n\n -- local function to call the token arranger, if it is on the table\n ---@param functionName string Name of the function to cal\n ---@param argument? table Parameter to pass\n local function callIfExistent(functionName, argument)\n local tokenArranger = guidReferenceApi.getObjectByOwnerAndType(\"Mythos\", \"TokenArranger\")\n if tokenArranger ~= nil then\n return tokenArranger.call(functionName, argument)\n end\n end\n\n -- updates the token modifiers with the provided data\n ---@param fullData table Contains the chaos token metadata\n TokenArrangerApi.onTokenDataChanged = function(fullData)\n callIfExistent(\"onTokenDataChanged\", fullData)\n end\n\n -- deletes already laid out tokens\n TokenArrangerApi.deleteCopiedTokens = function()\n callIfExistent(\"deleteCopiedTokens\")\n end\n\n -- updates the laid out tokens\n TokenArrangerApi.layout = function()\n Wait.time(function() callIfExistent(\"layout\") end, 0.1)\n end\n\n TokenArrangerApi.getSaveData = function()\n return deepCopy(callIfExistent(\"getSaveData\"))\n end\n\n TokenArrangerApi.loadData = function(loadedData)\n callIfExistent(\"loadData\", loadedData)\n end\n\n return TokenArrangerApi\nend\nend)\n__bundle_register(\"util/SearchLib\", function(require, _LOADED, __bundle_register, __bundle_modules)\ndo\n local SearchLib = {}\n local filterFunctions = {\n isCard = function(x) return x.type == \"Card\" end,\n isDeck = function(x) return x.type == \"Deck\" end,\n isCardOrDeck = function(x) return x.type == \"Card\" or x.type == \"Deck\" end,\n isClue = function(x) return x.memo == \"clueDoom\" and x.is_face_down == false end,\n isDoom = function(x) return x.memo == \"clueDoom\" and x.is_face_down == true end,\n isInteractable = function(x) return x.interactable end,\n isTileOrToken = function(x) return not x.Book and (x.type == \"Tile\" or x.type == \"Generic\") end,\n isUniversalToken = function(x) return x.getMemo() == \"universalActionAbility\" end,\n }\n\n -- performs the actual search and returns a filtered list of object references\n ---@param pos tts__Vector Global position\n ---@param rot? tts__Vector Global rotation\n ---@param size table Size\n ---@param filter? string Name of the filter function\n ---@param direction? table Direction (positive is up)\n ---@param maxDistance? number Distance for the cast\n local function returnSearchResult(pos, rot, size, filter, direction, maxDistance)\n local filterFunc = filter and filterFunctions[filter]\n local searchResult = Physics.cast({\n origin = pos,\n direction = direction or { 0, 1, 0 },\n orientation = rot or { 0, 0, 0 },\n type = 3,\n size = size,\n max_distance = maxDistance or 0\n })\n\n -- filter the result for matching objects\n local objList = {}\n for _, v in ipairs(searchResult) do\n if (not filter or filterFunc(v.hit_object)) then\n table.insert(objList, v.hit_object)\n end\n end\n return objList\n end\n\n -- searches the specified area\n function SearchLib.inArea(pos, rot, size, filter)\n return returnSearchResult(pos, rot, size, filter)\n end\n\n -- searches the area on an object\n function SearchLib.onObject(obj, filter, scale)\n scale = scale or 1\n local pos = obj.getPosition() + Vector(0, 1, 0) -- offset by half the cast's height\n local size = obj.getBounds().size:scale(scale):setAt(\"y\", 2)\n return returnSearchResult(pos, _, size, filter)\n end\n\n -- searches the specified position (a single point)\n function SearchLib.atPosition(pos, filter)\n local size = { 0.1, 2, 0.1 }\n return returnSearchResult(pos, _, size, filter)\n end\n\n -- searches below the specified position (downwards until y = 0)\n function SearchLib.belowPosition(pos, filter)\n local size = { 0.1, 2, 0.1 }\n local direction = { 0, -1, 0 }\n local maxDistance = pos.y\n return returnSearchResult(pos, _, size, filter, direction, maxDistance)\n end\n\n return SearchLib\nend\nend)\nreturn __bundle_require(\"__root\")", "LuaScriptState": "", "XmlUI": "" },