-- 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("accessories/TokenArrangerApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local TokenArrangerApi = {} -- 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 = getObjectsWithTag("TokenArranger")[1] 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/PlayAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local PlayAreaApi = { } local PLAY_AREA_GUID = "721ba2" local INVESTIGATOR_COUNTER_GUID = "f182ee" -- Returns the current value of the investigator counter from the playmat ---@return Integer. Number of investigators currently set on the counter PlayAreaApi.getInvestigatorCount = function() return getObjectFromGUID(INVESTIGATOR_COUNTER_GUID).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) return getObjectFromGUID(INVESTIGATOR_COUNTER_GUID).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 of the player requesting the shift. Used solely to send an error --- message in the unlikely case that the scripting zone has been deleted PlayAreaApi.shiftContentsUp = function(playerColor) return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsUp", playerColor) end PlayAreaApi.shiftContentsDown = function(playerColor) return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsDown", playerColor) end PlayAreaApi.shiftContentsLeft = function(playerColor) return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsLeft", playerColor) end PlayAreaApi.shiftContentsRight = function(playerColor) return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsRight", playerColor) end -- Reset the play area's tracking of which cards have had tokens spawned. PlayAreaApi.resetSpawnedCards = function() return getObjectFromGUID(PLAY_AREA_GUID).call("resetSpawnedCards") end -- Event to be called when the current scenario has changed. ---@param scenarioName Name of the new scenario PlayAreaApi.onScenarioChanged = function(scenarioName) getObjectFromGUID(PLAY_AREA_GUID).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) getObjectFromGUID(PLAY_AREA_GUID).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) getObjectFromGUID(PLAY_AREA_GUID).call("tryObjectEnterContainer", { container = container, object = object }) end -- counts the VP on locations in the play area PlayAreaApi.countVP = function() return getObjectFromGUID(PLAY_AREA_GUID).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 getObjectFromGUID(PLAY_AREA_GUID).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 getObjectFromGUID(PLAY_AREA_GUID).call("highlightCountedVP", state) end -- Checks if an object is in the play area (returns true or false) PlayAreaApi.isInPlayArea = function(object) return getObjectFromGUID(PLAY_AREA_GUID).call("isInPlayArea", object) end PlayAreaApi.getSurface = function() return getObjectFromGUID(PLAY_AREA_GUID).getCustomObject().image end PlayAreaApi.updateSurface = function(url) return getObjectFromGUID(PLAY_AREA_GUID).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) getObjectFromGUID(PLAY_AREA_GUID).call("updateLocations", args) end PlayAreaApi.getCustomDataHelper = function() return getObjectFromGUID(PLAY_AREA_GUID).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 SPAWN_TRACKER_GUID = "e3ffc9" TokenSpawnTracker.hasSpawnedTokens = function(cardGuid) return getObjectFromGUID(SPAWN_TRACKER_GUID).call("hasSpawnedTokens", cardGuid) end TokenSpawnTracker.markTokensSpawned = function(cardGuid) return getObjectFromGUID(SPAWN_TRACKER_GUID).call("markTokensSpawned", cardGuid) end TokenSpawnTracker.resetTokensSpawned = function(cardGuid) return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetTokensSpawned", cardGuid) end TokenSpawnTracker.resetAllAssetAndEvents = function() return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllAssetAndEvents") end TokenSpawnTracker.resetAllLocations = function() return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllLocations") end TokenSpawnTracker.resetAll = function() return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAll") end return TokenSpawnTracker end end) __bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules) require("core/MythosArea") end) __bundle_register("core/MythosArea", function(require, _LOADED, __bundle_register, __bundle_modules) 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 -- GUID of data helper local DATA_HELPER_GUID = "708279" local TRASHCAN local TRASHCAN_GUID = "70b9f6" -- 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 TRASHCAN = getObjectFromGUID(TRASHCAN_GUID) 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 --------------------------------------------------------- -- 'params' contains the position, rotation and a boolean to force a faceup draw function drawEncounterCard(params) local card local items = searchArea(ENCOUNTER_DECK_POS, { 3, 1, 4 }, isCardOrDeck) if #items > 0 then for _, j in ipairs(items) do local v = j.hit_object if v.tag == 'Deck' then card = v.takeObject({ index = 0 }) break end end -- we didn't find the deck so just pull the first thing we did find if card == nil then card = items[1].hit_object 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 getObjectFromGUID(DATA_HELPER_GUID).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 TRASHCAN.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) return __bundle_require("__root")