-- Bundled by luabundle {"version":"1.6.0"} local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire) local loadingPlaceholder = {[{}] = true} local register local modules = {} local require local loaded = {} register = function(name, body) if not modules[name] then modules[name] = body end end require = function(name) local loadedModule = loaded[name] if loadedModule then if loadedModule == loadingPlaceholder then return nil end else if not modules[name] then if not superRequire then local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name) error('Tried to require ' .. identifier .. ', but no such module has been registered') else return superRequire(name) end end loaded[name] = loadingPlaceholder loadedModule = modules[name](require, loaded, register, modules) loaded[name] = loadedModule end return loadedModule end return require, loaded, register, modules end)(nil) __bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules) require("chaosbag/BlessCurseManager") end) __bundle_register("chaosbag/BlessCurseManager", function(require, _LOADED, __bundle_register, __bundle_modules) local chaosBagApi = require("chaosbag/ChaosBagApi") local tokenArrangerApi = require("accessories/TokenArrangerApi") -- common button parameters local buttonParamaters = {} buttonParamaters.function_owner = self buttonParamaters.color = { 0, 0, 0, 0 } buttonParamaters.width = 700 buttonParamaters.height = 700 local updating --------------------------------------------------------- -- creating buttons and menus + initializing tables --------------------------------------------------------- function onLoad() -- index: 0 - bless buttonParamaters.click_function = "clickBless" buttonParamaters.position = { -1.03, 0, 0.46 } buttonParamaters.tooltip = "Add / Remove Bless" self.createButton(buttonParamaters) -- index: 1 - curse buttonParamaters.click_function = "clickCurse" buttonParamaters.position[1] = -buttonParamaters.position[1] buttonParamaters.tooltip = "Add / Remove Curse" self.createButton(buttonParamaters) -- index: 2 - bless count buttonParamaters.tooltip = "" buttonParamaters.click_function = "none" buttonParamaters.width = 0 buttonParamaters.height = 0 buttonParamaters.color = { 0.4, 0.4, 0.4 } buttonParamaters.font_color = { 1, 1, 1 } buttonParamaters.font_size = 235 buttonParamaters.position = { -1.03, 0.06, -0.8 } self.createButton(buttonParamaters) -- index: 3 - curse count buttonParamaters.position[1] = -buttonParamaters.position[1] self.createButton(buttonParamaters) -- context menu self.addContextMenuItem("Remove all", doRemove) self.addContextMenuItem("Reset", doReset) -- initializing tables initializeState() broadcastCount("Curse") broadcastCount("Bless") end function resetTables() numInPlay = { Bless = 0, Curse = 0 } tokensTaken = { Bless = {}, Curse = {} } sealedTokens = {} end function initializeState() resetTables() -- count tokens in the bag local chaosBag = chaosBagApi.findChaosBag() for _, v in ipairs(chaosBag.getObjects()) do if v.name == "Bless" then numInPlay.Bless = numInPlay.Bless + 1 elseif v.name == "Curse" then numInPlay.Curse = numInPlay.Curse + 1 end end -- find tokens in the play area for _, obj in ipairs(getObjects()) do local pos = obj.getPosition() if obj.hasTag("tempToken") then -- skip the tokens from the Token Arranger elseif pos.x > -65 and pos.x < 10 and pos.z > -35 and pos.z < 35 and obj.type == "Tile" then if obj.getName() == "Bless" then table.insert(tokensTaken.Bless, obj.getGUID()) numInPlay.Bless = numInPlay.Bless + 1 elseif obj.getName() == "Curse" then table.insert(tokensTaken.Curse, obj.getGUID()) numInPlay.Curse = numInPlay.Curse + 1 end end end updateButtonLabels() end function updateButtonLabels() self.editButton({ index = 2, label = formatTokenCount("Bless", true)}) self.editButton({ index = 3, label = formatTokenCount("Curse", true)}) end function broadcastCount(token) local count = formatTokenCount(token) if count == "(0 + 0)" then return end broadcastToAll(token .. " Tokens " .. count, "White") end function broadcastStatus(color) broadcastToColor("Curse Tokens " .. formatTokenCount("Curse"), color, "White") broadcastToColor("Bless Tokens " .. formatTokenCount("Bless"), color, "White") end --------------------------------------------------------- -- TTS event handling --------------------------------------------------------- -- enable tracking of bag changes for 1 second function onObjectDrop(_, object) if not isBlurseToken(object) then return end -- check if object was dropped in chaos bag area for _, zone in ipairs(object.getZones()) do if zone.getName() == "ChaosBagZone" then trackBagChange = true Wait.time(function() trackBagChange = false end, 1) return end end end -- handle manual returning of bless / curse tokens function onObjectEnterContainer(container, object) if not (trackBagChange and isChaosbag(container) and isBlurseToken(object)) then return end -- small delay to ensure token has entered bag Wait.time(doReset, 0.5) end function isChaosbag(obj) if obj.getDescription() ~= "Chaos Bag" then return end -- early exit for performance return obj == chaosBagApi.findChaosBag() end function isBlurseToken(obj) local name = obj.getName() return name == "Bless" or name == "Curse" end --------------------------------------------------------- -- context menu functions --------------------------------------------------------- function doRemove(color) local chaosBag = chaosBagApi.findChaosBag() -- remove tokens from chaos bag local count = { Bless = 0, Curse = 0 } for _, v in ipairs(chaosBag.getObjects()) do if v.name == "Bless" or v.name == "Curse" then chaosBag.takeObject({ guid = v.guid, position = { 0, 5, 0 }, callback_function = function(obj) obj.destruct() end }) count[v.name] = count[v.name] + 1 end end broadcastToColor("Removed " .. count.Bless .. " Bless and " .. count.Curse .. " Curse tokens from the chaos bag.", color, "White") broadcastToColor("Removed " .. removeTakenTokens("Bless") .. " Bless and " .. removeTakenTokens("Curse") .. " Curse tokens from play.", color, "White") resetTables() updateButtonLabels() tokenArrangerApi.layout() end function doReset() initializeState() broadcastCount("Curse") broadcastCount("Bless") tokenArrangerApi.layout() end --------------------------------------------------------- -- click functions --------------------------------------------------------- function clickBless(_, color, isRightClick) playerColor = color callFunctions("Bless", isRightClick) end function clickCurse(_, color, isRightClick) playerColor = color callFunctions("Curse", isRightClick) end function callFunctions(type, isRightClick) if not chaosBagApi.canTouchChaosTokens() then return end if isRightClick then removeToken(type) else addToken(type) end tokenArrangerApi.layout() end --------------------------------------------------------- -- called functions --------------------------------------------------------- -- returns a formatted string with information about the provided token type (bless / curse) ---@param type string Type of chaos token ("Bless" or "Curse") ---@param omitBrackets? boolean Controls whether the brackets should be omitted from the return ---@return string tokenCount function formatTokenCount(type, omitBrackets) if omitBrackets then return (numInPlay[type] - #tokensTaken[type]) .. " + " .. #tokensTaken[type] else return "(" .. (numInPlay[type] - #tokensTaken[type]) .. " + " .. #tokensTaken[type] .. ")" end end -- seals a token on a card (called by cards that seal bless/curse tokens) ---@param param table This contains the type and guid of the sealed token function sealedToken(param) table.insert(tokensTaken[param.type], param.guid) broadcastCount(param.type) updateButtonLabels() end -- returns a token to the bag (called by cards that seal bless/curse tokens) ---@param param table This contains the type and guid of the released token function releasedToken(param) for i, v in ipairs(tokensTaken[param.type]) do if v == param.guid then table.remove(tokensTaken[param.type], i) break end end if not param.fromBag then updateDisplayAndBroadcast(param.type) end end -- removes a token (called by cards that seal bless/curse tokens) ---@param param table This contains the type and guid of the released token function returnedToken(param) for i, v in ipairs(tokensTaken[param.type]) do if v == param.guid then table.remove(tokensTaken[param.type], i) numInPlay[param.type] = numInPlay[param.type] - 1 break end end updateDisplayAndBroadcast(param.type) end function updateDisplayAndBroadcast(type) if not updating then updating = true Wait.frames(function() broadcastCount(type) updateButtonLabels() updating = false end, 5) end end function getBlessCurseInBag() local numInBag = { Bless = 0, Curse = 0 } local chaosBag = chaosBagApi.findChaosBag() for _, v in ipairs(chaosBag.getObjects()) do if v.name == "Bless" or v.name == "Curse" then numInBag[v.name] = numInBag[v.name] + 1 end end return numInBag end --------------------------------------------------------- -- main functions: add and remove --------------------------------------------------------- function addToken(type) if numInPlay[type] == 10 then printToColor("10 tokens already in play, not adding any.", playerColor) return end numInPlay[type] = numInPlay[type] + 1 printToAll("Adding " .. type .. " token " .. formatTokenCount(type)) updateButtonLabels() return chaosBagApi.spawnChaosToken(type) end function removeToken(type) local chaosBag = chaosBagApi.findChaosBag() local tokens = {} for _, v in ipairs(chaosBag.getObjects()) do if v.name == type then table.insert(tokens, v.guid) end end if #tokens == 0 then printToColor("No " .. type .. " tokens in the chaos bag.", playerColor) return end chaosBag.takeObject({ guid = table.remove(tokens), smooth = false, callback_function = function(obj) numInPlay[type] = numInPlay[type] - 1 printToAll("Removing " .. type .. " token " .. formatTokenCount(type)) updateButtonLabels() obj.destruct() end }) end -- removing tokens that were 'taken' function removeTakenTokens(type) local count = 0 for _, guid in ipairs(tokensTaken[type]) do local token = getObjectFromGUID(guid) if token ~= nil then token.destruct() count = count + 1 end end return count end --------------------------------------------------------- -- Wendy's Menu (context menu for cards on hotkey press) --------------------------------------------------------- function addMenuOptions(parameters) local playerColor = parameters.playerColor local hoveredObject = parameters.hoveredObject if hoveredObject == nil or hoveredObject.type ~= "Card" then broadcastToColor("Right-click seal options can only be added to cards.", playerColor) return elseif hoveredObject.hasTag("CardThatSeals") or hoveredObject.getVar("MENU_ADDED") == true then broadcastToColor("This card already has a sealing context menu.", playerColor) return end hoveredObject.addContextMenuItem("Seal Bless", function(color) sealToken("Bless", color, hoveredObject) tokenArrangerApi.layout() end, true) hoveredObject.addContextMenuItem("Release Bless", function(color) releaseToken("Bless", color, hoveredObject) tokenArrangerApi.layout() end, true) hoveredObject.addContextMenuItem("Seal Curse", function(color) sealToken("Curse", color, hoveredObject) tokenArrangerApi.layout() end, true) hoveredObject.addContextMenuItem("Release Curse", function(color) releaseToken("Curse", color, hoveredObject) tokenArrangerApi.layout() end, true) broadcastToColor("Right-click seal options added to " .. hoveredObject.getName(), playerColor) hoveredObject.setVar("MENU_ADDED", true) sealedTokens[hoveredObject.getGUID()] = {} end function sealToken(type, playerColor, hoveredObject) local chaosBag = chaosBagApi.findChaosBag() for i, token in ipairs(chaosBag.getObjects()) do if token.name == type then return chaosBag.takeObject({ position = hoveredObject.getPosition() + Vector(0, 1, 0), index = i - 1, smooth = false, callback_function = function(obj) table.insert(sealedTokens[hoveredObject.getGUID()], obj) table.insert(tokensTaken[type], obj.getGUID()) tokenArrangerApi.layout() updateDisplayAndBroadcast(type) end }) end end printToColor(type .. " token not found in bag", playerColor) end function releaseToken(type, playerColor, hoveredObject) local chaosBag = chaosBagApi.findChaosBag() local tokens = sealedTokens[hoveredObject.getGUID()] if tokens == nil or #tokens == 0 then return end for i, token in ipairs(tokens) do if token ~= nil and token.getName() == type then local guid = token.getGUID() chaosBag.putObject(token) for j, v in ipairs(tokensTaken[type]) do if v == guid then table.remove(tokensTaken[type], j) table.remove(tokens, i) tokenArrangerApi.layout() updateDisplayAndBroadcast(type) return end end end end printToColor(type .. " token not sealed on " .. hoveredObject.getName(), playerColor) end function none() end end) __bundle_register("accessories/TokenArrangerApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local TokenArrangerApi = {} local guidReferenceApi = require("core/GUIDReferenceApi") -- local function to call the token arranger, if it is on the table ---@param functionName string Name of the function to cal ---@param argument? table Parameter to pass local function callIfExistent(functionName, argument) local tokenArranger = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenArranger") if tokenArranger ~= nil then tokenArranger.call(functionName, argument) end end -- updates the token modifiers with the provided data ---@param fullData table Contains the chaos token metadata TokenArrangerApi.onTokenDataChanged = function(fullData) callIfExistent("onTokenDataChanged", fullData) end -- deletes already laid out tokens TokenArrangerApi.deleteCopiedTokens = function() callIfExistent("deleteCopiedTokens") end -- updates the laid out tokens TokenArrangerApi.layout = function() Wait.time(function() callIfExistent("layout") end, 0.1) end return TokenArrangerApi end end) __bundle_register("chaosbag/ChaosBagApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local ChaosBagApi = {} -- respawns the chaos bag with a new state of tokens ---@param tokenList table List of chaos token ids ChaosBagApi.setChaosBagState = function(tokenList) Global.call("setChaosBagState", tokenList) end -- returns a Table List of chaos token ids in the current chaos bag -- requires copying the data into a new table because TTS is weird about handling table return values in Global ChaosBagApi.getChaosBagState = function() local chaosBagContentsCatcher = Global.call("getChaosBagState") local chaosBagContents = {} for _, v in ipairs(chaosBagContentsCatcher) do table.insert(chaosBagContents, v) end return chaosBagContents end -- checks scripting zone for chaos bag (also called by a lot of objects!) ChaosBagApi.findChaosBag = function() return Global.call("findChaosBag") end -- returns a table of object references to the tokens in play (does not include sealed tokens!) ChaosBagApi.getTokensInPlay = function() return Global.call("getChaosTokensinPlay") end -- returns all sealed tokens on cards to the chaos bag ---@param playerColor string Color of the player to show the broadcast to ChaosBagApi.releaseAllSealedTokens = function(playerColor) Global.call("releaseAllSealedTokens", playerColor) end -- returns all drawn tokens to the chaos bag ChaosBagApi.returnChaosTokens = function() Global.call("returnChaosTokens") end -- removes the specified chaos token from the chaos bag ---@param id string ID of the chaos token ChaosBagApi.removeChaosToken = function(id) Global.call("removeChaosToken", id) end -- returns a chaos token to the bag and calls all relevant functions ---@param token tts__Object Chaos token to return ---@param fromBag boolean whether or not the token to return was in the middle of being drawn (true) or elsewhere (false) ChaosBagApi.returnChaosTokenToBag = function(token, fromBag) Global.call("returnChaosTokenToBag", { token = token, fromBag = fromBag }) end -- spawns the specified chaos token and puts it into the chaos bag ---@param id string ID of the chaos token ChaosBagApi.spawnChaosToken = function(id) Global.call("spawnChaosToken", id) end -- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens -- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the -- contents of the bag should check this method before doing so. -- This method will broadcast a message to all players if the bag is being searched. ---@return any: True if the bag is manipulated, false if it should be blocked. ChaosBagApi.canTouchChaosTokens = function() return Global.call("canTouchChaosTokens") end -- draws a chaos token to a playermat ---@param mat tts__Object Playermat that triggered this ---@param drawAdditional boolean Controls whether additional tokens should be drawn ---@param tokenType? string Name of token (e.g. "Bless") to be drawn from the bag ---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag ---@param takeParameters? table Position and rotation of the location where the new token should be drawn to, usually to replace a returned token ---@return tts__Object: Object reference to the token that was drawn ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, takeParameters) return Global.call("drawChaosToken", { mat = mat, drawAdditional = drawAdditional, tokenType = tokenType, guidToBeResolved = guidToBeResolved, takeParameters = takeParameters }) end -- returns a Table List of chaos token ids in the current chaos bag -- requires copying the data into a new table because TTS is weird about handling table return values in Global ChaosBagApi.getIdUrlMap = function() return Global.getTable("ID_URL_MAP") end return ChaosBagApi end end) __bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local GUIDReferenceApi = {} local function getGuidHandler() return getObjectFromGUID("123456") end -- Returns the matching object ---@param owner string Parent object for this search ---@param type string Type of object to search for ---@return any: Object reference to the matching object GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type) return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type }) end -- Returns all matching objects as a table with references ---@param type string Type of object to search for ---@return table: List of object references to matching objects GUIDReferenceApi.getObjectsByType = function(type) return getGuidHandler().call("getObjectsByType", type) end -- Returns all matching objects as a table with references ---@param owner string Parent object for this search ---@return table: List of object references to matching objects GUIDReferenceApi.getObjectsByOwner = function(owner) return getGuidHandler().call("getObjectsByOwner", owner) end -- Sends new information to the reference handler to edit the main index ---@param owner string Parent of the object ---@param type string Type of the object ---@param guid string GUID of the object GUIDReferenceApi.editIndex = function(owner, type, guid) return getGuidHandler().call("editIndex", { owner = owner, type = type, guid = guid }) end -- Returns the owner of an object or the object it's located on ---@param object tts__GameObject Object for this search ---@return string: Parent of the object or object it's located on GUIDReferenceApi.getOwnerOfObject = function(object) return getGuidHandler().call("getOwnerOfObject", object) end return GUIDReferenceApi end end) return __bundle_require("__root")