-- 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("core/MythosArea", function(require, _LOADED, __bundle_register, __bundle_modules) local guidReferenceApi = require("core/GUIDReferenceApi") local playAreaApi = require("core/PlayAreaApi") local tokenArrangerApi = require("accessories/TokenArrangerApi") local tokenChecker = require("core/token/TokenChecker") local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") local ENCOUNTER_DECK_AREA = { upperLeft = { x = 0.9, z = 0.42 }, lowerRight = { x = 0.86, z = 0.38 }, } local ENCOUNTER_DISCARD_AREA = { upperLeft = { x = 1.62, z = 0.42 }, lowerRight = { x = 1.58, z = 0.38 }, } -- global position of encounter deck and discard pile local ENCOUNTER_DECK_POS = { x = -3.93, y = 1, z = 5.76 } local ENCOUNTER_DISCARD_POSITION = { x = -3.85, y = 1, z = 10.38 } local isReshuffling = false -- scenario metadata local currentScenario, useFrontData, tokenData -- object references local TRASH, DATA_HELPER -- we use this to turn off collision handling until onLoad() is complete local collisionEnabled = false function onLoad(saveState) if saveState ~= nil then local loadedState = JSON.decode(saveState) or {} currentScenario = loadedState.currentScenario or "" useFrontData = loadedState.useFrontData or true tokenData = loadedState.tokenData or {} end TRASH = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash") DATA_HELPER = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper") collisionEnabled = true end function onSave() return JSON.encode({ currentScenario = currentScenario, useFrontData = useFrontData, tokenData = tokenData }) end -- TTS event handler. Handles scenario name event triggering and encounter card token resets. function onCollisionEnter(collisionInfo) if not collisionEnabled then return end local object = collisionInfo.collision_object if object.getName() == "Scenario" then local description = object.getDescription() -- detect if a new scenario card is placed down if currentScenario ~= description then currentScenario = description fireScenarioChangedEvent() end local metadata = JSON.decode(object.getGMNotes()) or {} if not metadata["tokens"] then tokenData = {} return end -- detect orientation of scenario card (for difficulty) useFrontData = not object.is_face_down tokenData = metadata["tokens"][(useFrontData and "front" or "back")] fireTokenDataChangedEvent() end local localPos = self.positionToLocal(object.getPosition()) if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then tokenSpawnTrackerApi.resetTokensSpawned(object.getGUID()) removeTokensFromObject(object) end end -- TTS event handler. Handles scenario name event triggering function onCollisionExit(collisionInfo) if not collisionEnabled then return end local object = collisionInfo.collision_object -- reset token metadata if scenario reference card is removed if object.getName() == "Scenario" then tokenData = {} useFrontData = nil fireTokenDataChangedEvent() end end -- Listens for cards entering the encounter deck or encounter discard, and resets the spawn state -- for the cards when they do. function onObjectEnterContainer(container, object) local localPos = self.positionToLocal(container.getPosition()) if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then tokenSpawnTrackerApi.resetTokensSpawned(object.getGUID()) end end -- fires if the scenario title changes function fireScenarioChangedEvent() Wait.frames(function() Global.call('titleSplash', currentScenario) end, 20) playAreaApi.onScenarioChanged(currentScenario) end -- fires if the scenario title or the difficulty changes function fireTokenDataChangedEvent() local fullData = returnTokenData() tokenArrangerApi.onTokenDataChanged(fullData) end -- returns the chaos token metadata (if provided) function returnTokenData() return { tokenData = tokenData, currentScenario = currentScenario, useFrontData = useFrontData } end --------------------------------------------------------- -- encounter card drawing --------------------------------------------------------- -- gets the encounter deck (for internal functions and Api calls) function getEncounterDeck() local search = searchArea(ENCOUNTER_DECK_POS, { 3, 1, 4 }, isCardOrDeck) for _, v in ipairs(search) do local obj = v.hit_object if obj.type == 'Deck' then return obj end end -- if no deck was found, return the first hit (a card) if #search > 0 then return search[1].hit_object end end -- 'params' contains the position, rotation and a boolean to force a faceup draw function drawEncounterCard(params) local card local deck = getEncounterDeck() if deck then if deck.type == "Deck" then card = deck.takeObject() else card = deck end actualEncounterCardDraw(card, params) else -- nothing here, time to reshuffle reshuffleEncounterDeck(params) end end function actualEncounterCardDraw(card, params) local faceUpRotation = 0 if not params.alwaysFaceUp then local metadata = JSON.decode(card.getGMNotes()) or {} if metadata.hidden or DATA_HELPER.call('checkHiddenCard', card.getName()) then faceUpRotation = 180 end end card.setPositionSmooth(params.pos, false, false) card.setRotationSmooth({ 0, params.rotY, faceUpRotation }, false, false) end function reshuffleEncounterDeck(params) -- flag to avoid multiple calls if isReshuffling then return end isReshuffling = true -- shuffle and flip deck, draw card after completion local discarded = searchArea(ENCOUNTER_DISCARD_POSITION, { 3, 1, 4 }, isDeck) if #discarded > 0 then local deck = discarded[1].hit_object if not deck.is_face_down then deck.flip() end deck.shuffle() deck.setPositionSmooth(Vector(ENCOUNTER_DECK_POS) + Vector(0, 2, 0), false, true) Wait.time(function() actualEncounterCardDraw(deck.takeObject({ index = 0 }), params) end, 0.5) else printToAll("Couldn't find encounter discard pile to reshuffle.", { 1, 0, 0 }) end -- disable flag Wait.time(function() isReshuffling = false end, 1) end --------------------------------------------------------- -- helper functions --------------------------------------------------------- -- Simple method to check if the given point is in a specified area. Local use only, ---@param point Vector. Point to check, only x and z values are relevant ---@param bounds Table. Defined area to see if the point is within. See MAIN_PLAY_AREA for sample -- bounds definition. ---@return Boolean. True if the point is in the area defined by bounds function inArea(point, bounds) return (point.x < bounds.upperLeft.x and point.x > bounds.lowerRight.x and point.z < bounds.upperLeft.z and point.z > bounds.lowerRight.z) end -- removes tokens from the provided card/deck function removeTokensFromObject(object) for _, v in ipairs(searchArea(object.getPosition(), { 3, 1, 4 })) do local obj = v.hit_object if obj.getGUID() ~= "4ee1f2" and -- table obj ~= self and obj.type ~= "Deck" and obj.type ~= "Card" and obj.memo ~= nil and obj.getLock() == false and not tokenChecker.isChaosToken(obj) then TRASH.putObject(obj) end end end -- searches an area and optionally filters the result function searchArea(origin, size, filter) local objList = Physics.cast({ origin = origin, direction = { 0, 1, 0 }, orientation = self.getRotation(), type = 3, size = size, max_distance = 1 }) if filter then local filteredList = {} for _, obj in ipairs(objList) do if filter(obj.hit_object) then table.insert(filteredList, obj) end end return filteredList else return objList end end -- filter functions for searchArea function isDeck(x) return x.tag == 'Deck' end function isCardOrDeck(x) return x.tag == 'Card' or x.tag == 'Deck' 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 Variant 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 tokenData 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("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local GUIDReferenceApi = {} local function getGuidHandler() return getObjectFromGUID("123456") end -- returns all matching objects as a table with references ---@param owner String Parent object for this search ---@param type String Type of object to search for 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 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 GUIDReferenceApi.getObjectsByOwner = function(owner) return getGuidHandler().call("getObjectsByOwner", owner) end return GUIDReferenceApi end end) __bundle_register("core/PlayAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local PlayAreaApi = {} local guidReferenceApi = require("core/GUIDReferenceApi") local function getPlayArea() return guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayArea") end local function getInvestigatorCounter() return guidReferenceApi.getObjectByOwnerAndType("Mythos", "InvestigatorCounter") end -- Returns the current value of the investigator counter from the playmat ---@return Integer. Number of investigators currently set on the counter PlayAreaApi.getInvestigatorCount = function() return getInvestigatorCounter().getVar("val") end -- Updates the current value of the investigator counter from the playmat ---@param count Number of investigators to set on the counter PlayAreaApi.setInvestigatorCount = function(count) getInvestigatorCounter().call("updateVal", count) end -- Move all contents on the play area (cards, tokens, etc) one slot in the given direction. Certain -- fixed objects will be ignored, as will anything the player has tagged with 'displacement_excluded' ---@param playerColor Color Color of the player requesting the shift for messages PlayAreaApi.shiftContentsUp = function(playerColor) return getPlayArea().call("shiftContentsUp", playerColor) end PlayAreaApi.shiftContentsDown = function(playerColor) return getPlayArea().call("shiftContentsDown", playerColor) end PlayAreaApi.shiftContentsLeft = function(playerColor) return getPlayArea().call("shiftContentsLeft", playerColor) end PlayAreaApi.shiftContentsRight = function(playerColor) return getPlayArea().call("shiftContentsRight", playerColor) end -- Reset the play area's tracking of which cards have had tokens spawned. PlayAreaApi.resetSpawnedCards = function() return getPlayArea().call("resetSpawnedCards") end -- Event to be called when the current scenario has changed. ---@param scenarioName Name of the new scenario PlayAreaApi.onScenarioChanged = function(scenarioName) getPlayArea().call("onScenarioChanged", scenarioName) end -- Sets this playmat's snap points to limit snapping to locations or not. -- If matchTypes is false, snap points will be reset to snap all cards. ---@param matchTypes Boolean Whether snap points should only snap for the matching card types. PlayAreaApi.setLimitSnapsByType = function(matchCardTypes) getPlayArea().call("setLimitSnapsByType", matchCardTypes) end -- Receiver for the Global tryObjectEnterContainer event. Used to clear vector lines from dragged -- cards before they're destroyed by entering the container PlayAreaApi.tryObjectEnterContainer = function(container, object) getPlayArea().call("tryObjectEnterContainer", { container = container, object = object }) end -- counts the VP on locations in the play area PlayAreaApi.countVP = function() return getPlayArea().call("countVP") end -- highlights all locations in the play area without metadata ---@param state Boolean True if highlighting should be enabled PlayAreaApi.highlightMissingData = function(state) return getPlayArea().call("highlightMissingData", state) end -- highlights all locations in the play area with VP ---@param state Boolean True if highlighting should be enabled PlayAreaApi.highlightCountedVP = function(state) return getPlayArea().call("countVP", state) end -- Checks if an object is in the play area (returns true or false) PlayAreaApi.isInPlayArea = function(object) return getPlayArea().call("isInPlayArea", object) end PlayAreaApi.getSurface = function() return getPlayArea().getCustomObject().image end PlayAreaApi.updateSurface = function(url) return getPlayArea().call("updateSurface", url) end -- Called by Custom Data Helpers to push their location data into the Data Helper. This adds the -- data to the local token manager instance. ---@param args Table Single-value array holding the GUID of the Custom Data Helper making the call PlayAreaApi.updateLocations = function(args) getPlayArea().call("updateLocations", args) end PlayAreaApi.getCustomDataHelper = function() return getPlayArea().getVar("customDataHelper") end return PlayAreaApi end end) __bundle_register("core/token/TokenChecker", function(require, _LOADED, __bundle_register, __bundle_modules) do local CHAOS_TOKEN_NAMES = { ["Elder Sign"] = true, ["+1"] = true, ["0"] = true, ["-1"] = true, ["-2"] = true, ["-3"] = true, ["-4"] = true, ["-5"] = true, ["-6"] = true, ["-7"] = true, ["-8"] = true, ["Skull"] = true, ["Cultist"] = true, ["Tablet"] = true, ["Elder Thing"] = true, ["Auto-fail"] = true, ["Bless"] = true, ["Curse"] = true, ["Frost"] = true } local TokenChecker = {} -- returns true if the passed object is a chaos token (by name) TokenChecker.isChaosToken = function(obj) if CHAOS_TOKEN_NAMES[obj.getName()] then return true else return false end end return TokenChecker end end) __bundle_register("core/token/TokenSpawnTrackerApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local TokenSpawnTracker = {} local guidReferenceApi = require("core/GUIDReferenceApi") local function getSpawnTracker() return guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSpawnTracker") end TokenSpawnTracker.hasSpawnedTokens = function(cardGuid) return getSpawnTracker().call("hasSpawnedTokens", cardGuid) end TokenSpawnTracker.markTokensSpawned = function(cardGuid) return getSpawnTracker().call("markTokensSpawned", cardGuid) end TokenSpawnTracker.resetTokensSpawned = function(cardGuid) return getSpawnTracker().call("resetTokensSpawned", cardGuid) end TokenSpawnTracker.resetAllAssetAndEvents = function() return getSpawnTracker().call("resetAllAssetAndEvents") end TokenSpawnTracker.resetAllLocations = function() return getSpawnTracker().call("resetAllLocations") end TokenSpawnTracker.resetAll = function() return getSpawnTracker().call("resetAll") end return TokenSpawnTracker end end) __bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules) require("core/MythosArea") end) return __bundle_require("__root")