-- 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("playermat/PlaymatApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local PlaymatApi = { } local internal = { } local MAT_IDS = { White = "8b081b", Orange = "bd0ff4", Green = "383d8b", Red = "0840d5" } local CLUE_COUNTER_GUIDS = { White = "37be78", Orange = "1769ed", Green = "032300", Red = "d86b7c" } local CLUE_CLICKER_GUIDS = { White = "db85d6", Orange = "3f22e5", Green = "891403", Red = "4111de" } -- Returns the color of the by position requested playermat as string ---@param startPos Table Position of the search, table get's roughly cut into 4 quarters to assign a playermat PlaymatApi.getMatColorByPosition = function(startPos) if startPos.x < -42 then if startPos.z > 0 then return "White" else return "Orange" end else if startPos.z > 0 then return "Green" else return "Red" end end end -- Returns the color of the player's hand that is seated next to the playermat ---@param matColor String Color of the playermat PlaymatApi.getPlayerColor = function(matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.getVar("playerColor") end -- Returns the color of the playermat that owns the playercolor's hand ---@param handColor String Color of the playermat PlaymatApi.getMatColor = function(handColor) local matColors = {"White", "Orange", "Green", "Red"} for i, mat in ipairs(internal.getMatForColor("All")) do local color = mat.getVar("playerColor") if color == handColor then return matColors[i] end end return "NOT_FOUND" end -- Returns if there is the card "Dream-Enhancing Serum" on the requested playermat ---@param matColor String Color of the playermat PlaymatApi.isDES = function(matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.getVar("isDES") end -- Returns the draw deck of the requested playmat ---@param matColor String Color of the playermat PlaymatApi.getDrawDeck = function(matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) mat.call("getDrawDiscardDecks") return mat.getVar("drawDeck") end -- Returns the position of the discard pile of the requested playmat ---@param matColor String Color of the playermat PlaymatApi.getDiscardPosition = function(matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.call("returnGlobalDiscardPosition") end -- Transforms a local position into a global position ---@param localPos Table Local position to be transformed ---@param matColor String Color of the playermat PlaymatApi.transformLocalPosition = function(localPos, matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.positionToWorld(localPos) end -- Returns the rotation of the requested playmat ---@param matColor String Color of the playermat PlaymatApi.returnRotation = function(matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.getRotation() end -- Triggers the Upkeep for the requested playmat ---@param matColor String Color of the playermat ---@param playerColor String Color of the calling player (for messages) PlaymatApi.doUpkeepFromHotkey = function(matColor, playerColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.call("doUpkeepFromHotkey", playerColor) end -- Returns the active investigator id ---@param matColor String Color of the playermat PlaymatApi.returnInvestigatorId = function(matColor) local mat = getObjectFromGUID(MAT_IDS[matColor]) return mat.getVar("activeInvestigatorId") end -- Sets the requested playermat's snap points to limit snapping to matching card types or not. If -- matchTypes is true, the main card slot snap points will only snap assets, while the -- investigator area point will only snap Investigators. If matchTypes is false, snap points will -- be reset to snap all cards. ---@param matchCardTypes Boolean. Whether snap points should only snap for the matching card -- types. ---@param matColor String for one of the active player colors - White, Orange, Green, Red. Also -- accepts "All" as a special value which will apply the setting to all four mats. PlaymatApi.setLimitSnapsByType = function(matchCardTypes, matColor) for _, mat in ipairs(internal.getMatForColor(matColor)) do mat.call("setLimitSnapsByType", matchCardTypes) end end -- Sets the requested playermat's draw 1 button to visible ---@param isDrawButtonVisible Boolean. Whether the draw 1 button should be visible or not ---@param matColor String for one of the active player colors - White, Orange, Green, Red. Also -- accepts "All" as a special value which will apply the setting to all four mats. PlaymatApi.showDrawButton = function(isDrawButtonVisible, matColor) for _, mat in ipairs(internal.getMatForColor(matColor)) do mat.call("showDrawButton", isDrawButtonVisible) end end -- Shows or hides the clickable clue counter for the requested playermat ---@param showCounter Boolean. Whether the clickable counter should be present or not ---@param matColor String for one of the active player colors - White, Orange, Green, Red. Also -- accepts "All" as a special value which will apply the setting to all four mats. PlaymatApi.clickableClues = function(showCounter, matColor) for _, mat in ipairs(internal.getMatForColor(matColor)) do mat.call("clickableClues", showCounter) end end -- Removes all clues (to the trash for tokens and counters set to 0) for the requested playermat ---@param matColor String for one of the active player colors - White, Orange, Green, Red. Also -- accepts "All" as a special value which will apply the setting to all four mats. PlaymatApi.removeClues = function(matColor) for _, mat in ipairs(internal.getMatForColor(matColor)) do mat.call("removeClues") end end -- Reports the clue count for the requested playermat ---@param useClickableCounters Boolean Controls which type of counter is getting checked PlaymatApi.getClueCount = function(useClickableCounters, matColor) local count = 0 for _, mat in ipairs(internal.getMatForColor(matColor)) do count = count + tonumber(mat.call("getClueCount", useClickableCounters)) end return count end -- Adds the specified amount of resources to the requested playermat's resource counter PlaymatApi.gainResources = function(amount, matColor) for _, mat in ipairs(internal.getMatForColor(matColor)) do mat.call("gainResources", amount) end end -- Discard a non-hidden card from the corresponding player's hand PlaymatApi.doDiscardOne = function(matColor) for _, mat in ipairs(internal.getMatForColor(matColor)) do mat.call("doDiscardOne") end end -- Convenience function to look up a mat's object by color, or get all mats. ---@param matColor String for one of the active player colors - White, Orange, Green, Red. Also -- accepts "All" as a special value which will return all four mats. ---@return: Array of playermat objects. If a single mat is requested, will return a single-element -- array to simplify processing by consumers. internal.getMatForColor = function(matColor) local targetMatGuid = MAT_IDS[matColor] if targetMatGuid != nil then return { getObjectFromGUID(targetMatGuid) } end if matColor == "All" then return { getObjectFromGUID(MAT_IDS.White), getObjectFromGUID(MAT_IDS.Orange), getObjectFromGUID(MAT_IDS.Green), getObjectFromGUID(MAT_IDS.Red), } end end return PlaymatApi end end) __bundle_register("core/PlayAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules) do local PlayAreaApi = { } local PLAY_AREA_GUID = "721ba2" -- 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(PLAY_AREA_GUID).call("getInvestigatorCount") 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 return PlayAreaApi 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("playercards/cards/FamilyInheritance") end) __bundle_register("playercards/cards/FamilyInheritance", function(require, _LOADED, __bundle_register, __bundle_modules) local tokenManager = require("core/token/TokenManager") local playmatApi = require("playermat/PlaymatApi") local clickableResourceCounter = nil local foundTokens = 0 function onLoad() self.addContextMenuItem("Add 4 resources", function(playerColor) add4(playerColor) end) self.addContextMenuItem("Take all resources", function(playerColor) takeAll(playerColor) end) self.addContextMenuItem("Discard all resources", function(playerColor) loseAll(playerColor) end) end function searchSelf() clickableResourceCounter = nil foundTokens = 0 for _, obj in ipairs(searchArea(self.getPosition(), { 2.5, 0.5, 3.5 })) do local obj = obj.hit_object if obj.getCustomObject().image == "http://cloud-3.steamusercontent.com/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/" then foundTokens = foundTokens + math.abs(obj.getQuantity()) obj.destruct() elseif obj.getMemo() == "resourceCounter" then foundTokens = obj.getVar("val") clickableResourceCounter = obj return end end end function add4(playerColor) searchSelf() local newCount = foundTokens + 4 if clickableResourceCounter then clickableResourceCounter.call("updateVal", newCount) else if newCount > 12 then printToColor("Count increased to " .. newCount .. " resources. Spawning clickable counter instead.", playerColor) tokenManager.spawnResourceCounterToken(self, newCount) else tokenManager.spawnTokenGroup(self, "resource", newCount) end end end function takeAll(playerColor) searchSelf() local matColor = playmatApi.getMatColorByPosition(self.getPosition()) playmatApi.gainResources(foundTokens, matColor) if clickableResourceCounter then clickableResourceCounter.call("updateVal", 0) end printToColor("Moved " .. foundTokens .. " resource(s) to " .. matColor .. "'s resource pool.", playerColor) end function loseAll(playerColor) searchSelf() if clickableResourceCounter then clickableResourceCounter.call("updateVal", 0) end printToColor("Discarded " .. foundTokens .. " resource(s).", playerColor) end function searchArea(origin, size) return Physics.cast({ origin = origin, direction = { 0, 1, 0 }, orientation = PLAY_ZONE_ROTATION, type = 3, size = size, max_distance = 1 }) end end) __bundle_register("core/token/TokenManager", function(require, _LOADED, __bundle_register, __bundle_modules) do local tokenSpawnTracker = require("core/token/TokenSpawnTrackerApi") local playArea = require("core/PlayAreaApi") local PLAYER_CARD_TOKEN_OFFSETS = { [1] = { Vector(0, 3, -0.2) }, [2] = { Vector(0.4, 3, -0.2), Vector(-0.4, 3, -0.2) }, [3] = { Vector(0, 3, -0.9), Vector(0.4, 3, -0.2), Vector(-0.4, 3, -0.2) }, [4] = { Vector(0.4, 3, -0.9), Vector(-0.4, 3, -0.9), Vector(0.4, 3, -0.2), Vector(-0.4, 3, -0.2) }, [5] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.4, 3, -0.2), Vector(-0.4, 3, -0.2) }, [6] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2) }, [7] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2), Vector(0, 3, 0.5) }, [8] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2), Vector(-0.35, 3, 0.5), Vector(0.35, 3, 0.5) }, [9] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2), Vector(0.7, 3, 0.5), Vector(0, 3, 0.5), Vector(-0.7, 3, 0.5) }, [10] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2), Vector(0.7, 3, 0.5), Vector(0, 3, 0.5), Vector(-0.7, 3, 0.5), Vector(0, 3, 1.2) }, [11] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2), Vector(0.7, 3, 0.5), Vector(0, 3, 0.5), Vector(-0.7, 3, 0.5), Vector(-0.35, 3, 1.2), Vector(0.35, 3, 1.2) }, [12] = { Vector(0.7, 3, -0.9), Vector(0, 3, -0.9), Vector(-0.7, 3, -0.9), Vector(0.7, 3, -0.2), Vector(0, 3, -0.2), Vector(-0.7, 3, -0.2), Vector(0.7, 3, 0.5), Vector(0, 3, 0.5), Vector(-0.7, 3, 0.5), Vector(0.7, 3, 1.2), Vector(0, 3, 1.2), Vector(-0.7, 3, 1.2) } } -- Source for tokens local TOKEN_SOURCE_GUID = "124381" -- Table of data extracted from the token source bag, keyed by the Memo on each token which -- should match the token type keys ("resource", "clue", etc) local tokenTemplates local DATA_HELPER_GUID = "708279" local playerCardData local locationData local TokenManager = { } local internal = { } -- Spawns tokens for the card. This function is built to just throw a card at it and let it do -- the work once a card has hit an area where it might spawn tokens. It will check to see if -- the card has already spawned, find appropriate data from either the uses metadata or the Data -- Helper, and spawn the tokens. ---@param card Object Card to maybe spawn tokens for ---@param extraUses Table A table of = which will modify the number of tokens --- spawned for that type. e.g. Akachi's playmat should pass "Charge"=1 TokenManager.spawnForCard = function(card, extraUses) if tokenSpawnTracker.hasSpawnedTokens(card.getGUID()) then return end local metadata = JSON.decode(card.getGMNotes()) if metadata ~= nil then internal.spawnTokensFromUses(card, extraUses) else internal.spawnTokensFromDataHelper(card) end end -- Spawns a set of tokens on the given card. ---@param card Object Card to spawn tokens on ---@param tokenType String type of token to spawn, valid values are "damage", "horror", -- "resource", "doom", or "clue" ---@param tokenCount Number How many tokens to spawn. For damage or horror this value will be set to the -- spawned state object rather than spawning multiple tokens ---@param shiftDown Number An offset for the z-value of this group of tokens TokenManager.spawnTokenGroup = function(card, tokenType, tokenCount, shiftDown) local optionPanel = Global.getTable("optionPanel") if tokenType == "damage" or tokenType == "horror" then TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown) elseif tokenType == "resource" and optionPanel["useResourceCounters"] then TokenManager.spawnResourceCounterToken(card, tokenCount) else TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown) end end -- Spawns a single counter token and sets the value to tokenValue. Used for damage and horror -- tokens. ---@param card Object Card to spawn tokens on ---@param tokenType String type of token to spawn, valid values are "damage" and "horror". Other -- types should use spawnMultipleTokens() ---@param tokenValue Number Value to set the damage/horror to TokenManager.spawnCounterToken = function(card, tokenType, tokenValue, shiftDown) if tokenValue < 1 or tokenValue > 50 then return end local pos = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[1][1] + Vector(0, 0, shiftDown)) local rot = card.getRotation() TokenManager.spawnToken(pos, tokenType, rot, function(spawned) spawned.setState(tokenValue) end) end TokenManager.spawnResourceCounterToken = function(card, tokenCount) local pos = card.positionToWorld(card.positionToLocal(card.getPosition()) + Vector(0, 0.2, -0.5)) local rot = card.getRotation() TokenManager.spawnToken(pos, "resourceCounter", rot, function(spawned) spawned.call("updateVal", tokenCount) end) end -- Spawns a number of tokens. ---@param tokenType String type of token to spawn, valid values are resource", "doom", or "clue". -- Other types should use spawnCounterToken() ---@param tokenCount Number How many tokens to spawn ---@param shiftDown Number An offset for the z-value of this group of tokens TokenManager.spawnMultipleTokens = function(card, tokenType, tokenCount, shiftDown) if tokenCount < 1 or tokenCount > 12 then return end local offsets = {} if tokenType == "clue" then offsets = internal.buildClueOffsets(card, tokenCount) else for i = 1, tokenCount do offsets[i] = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[tokenCount][i]) -- Fix the y-position for the spawn, since positionToWorld considers rotation which can -- have bad results for face up/down differences offsets[i].y = card.getPosition().y + 0.15 end end if shiftDown ~= nil then -- Copy the offsets to make sure we don't change the static values local baseOffsets = offsets offsets = { } for i, baseOffset in ipairs(baseOffsets) do offsets[i] = baseOffset offsets[i][3] = offsets[i][3] + shiftDown end end if offsets == nil then error("couldn't find offsets for " .. tokenCount .. ' tokens') return end for i = 1, tokenCount do TokenManager.spawnToken(offsets[i], tokenType, card.getRotation()) end end -- Spawns a single token at the given global position by copying it from the template bag. ---@param position Global position to spawn the token ---@param tokenType String type of token to spawn, valid values are "damage", "horror", -- "resource", "doom", or "clue" ---@param rotation Vector Rotation to be used for the new token. Only the y-value will be used, -- x and z will use the default rotation from the source bag ---@param callback function A callback function triggered after the new token is spawned TokenManager.spawnToken = function(position, tokenType, rotation, callback) internal.initTokenTemplates() local loadTokenType = tokenType if tokenType == "clue" or tokenType == "doom" then loadTokenType = "clueDoom" end if tokenTemplates[loadTokenType] == nil then error("Unknown token type '" .. tokenType .. "'") return end local tokenTemplate = tokenTemplates[loadTokenType] -- Take ONLY the Y-value for rotation, so we don't flip the token coming out of the bag local rot = Vector(tokenTemplate.Transform.rotX, 270, tokenTemplate.Transform.rotZ) if rotation ~= nil then rot.y = rotation.y end if tokenType == "doom" then rot.z = 180 end tokenTemplate.Nickname = "" return spawnObjectData({ data = tokenTemplate, position = position, rotation = rot, callback_function = callback }) end -- Delegate function to the token spawn tracker. Exists to avoid circular dependencies in some -- callers. ---@param card Object Card object to reset the tokens for TokenManager.resetTokensSpawned = function(card) tokenSpawnTracker.resetTokensSpawned(card.getGUID()) end -- Pushes new player card data into the local copy of the Data Helper player data. ---@param dataTable Table Key/Value pairs following the DataHelper style TokenManager.addPlayerCardData = function(dataTable) internal.initDataHelperData() for k, v in pairs(dataTable) do playerCardData[k] = v end end -- Pushes new location data into the local copy of the Data Helper location data. ---@param dataTable Table Key/Value pairs following the DataHelper style TokenManager.addLocationData = function(dataTable) internal.initDataHelperData() for k, v in pairs(dataTable) do locationData[k] = v end end -- Checks to see if the given card has location data in the DataHelper ---@param card Object Card to check for data ---@return Boolean True if this card has data in the helper, false otherwise TokenManager.hasLocationData = function(card) internal.initDataHelperData() return internal.getLocationData(card) ~= nil end internal.initTokenTemplates = function() if tokenTemplates ~= nil then return end tokenTemplates = { } local tokenSource = getObjectFromGUID(TOKEN_SOURCE_GUID) for _, tokenTemplate in ipairs(tokenSource.getData().ContainedObjects) do local tokenName = tokenTemplate.Memo tokenTemplates[tokenName] = tokenTemplate end end -- Copies the data from the DataHelper. Will only happen once. internal.initDataHelperData = function() if playerCardData ~= nil then return end local dataHelper = getObjectFromGUID(DATA_HELPER_GUID) playerCardData = dataHelper.getTable('PLAYER_CARD_DATA') locationData = dataHelper.getTable('LOCATIONS_DATA') end -- Spawn tokens for a card based on the uses metadata. This will consider the face up/down state -- of the card for both locations and standard cards. ---@param card Object Card to maybe spawn tokens for ---@param extraUses Table A table of = which will modify the number of tokens --- spawned for that type. e.g. Akachi's playmat should pass "Charge"=1 internal.spawnTokensFromUses = function(card, extraUses) local uses = internal.getUses(card) if uses == nil then return end local type = nil local token = nil local tokenCount = 0 -- Uses structure underwent a breaking change in 2.4.0, have to check to see if this is -- a single entry or an array. This is ugly and duplicated, but impossible to replicate the -- multi-spawn vs. single spawn otherwise. TODO: Clean this up when 2.4.0 has been out long -- enough that saved decks don't have old data if uses.count != nil then type = cardMetadata.uses.type token = cardMetadata.uses.token tokenCount = cardMetadata.uses.count if extraUses ~= nil and extraUses[type] ~= nil then tokenCount = tokenCount + extraUses[type] end log("Spawning single use tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token) TokenManager.spawnTokenGroup(card, token, tokenCount) else for i, useInfo in ipairs(uses) do type = useInfo.type token = useInfo.token tokenCount = (useInfo.count or 0) + (useInfo.countPerInvestigator or 0) * playArea.getInvestigatorCount() if extraUses ~= nil and extraUses[type] ~= nil then tokenCount = tokenCount + extraUses[type] end log("Spawning use array tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token) -- Shift each spawned group after the first down so they don't pile on each other TokenManager.spawnTokenGroup(card, token, tokenCount, (i - 1) * 0.8) end end tokenSpawnTracker.markTokensSpawned(card.getGUID()) end -- Spawn tokens for a card based on the data helper data. This will consider the face up/down state -- of the card for both locations and standard cards. ---@param card Object Card to maybe spawn tokens for internal.spawnTokensFromDataHelper = function(card) internal.initDataHelperData() local playerData = internal.getPlayerCardData(card) if playerData ~= nil then internal.spawnPlayerCardTokensFromDataHelper(card, playerData) end local locationData = internal.getLocationData(card) if locationData ~= nil then internal.spawnLocationTokensFromDataHelper(card, locationData) end end -- Spawn tokens for a player card using data retrieved from the Data Helper. ---@param card Object Card to maybe spawn tokens for ---@param playerData Table Player card data structure retrieved from the DataHelper. Should be -- the right data for this card. internal.spawnPlayerCardTokensFromDataHelper = function(card, playerData) token = playerData.tokenType tokenCount = playerData.tokenCount log("Spawning data helper tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token) TokenManager.spawnTokenGroup(card, token, tokenCount) tokenSpawnTracker.markTokensSpawned(card.getGUID()) end -- Spawn tokens for a location using data retrieved from the Data Helper. ---@param card Object Card to maybe spawn tokens for ---@param playerData Table Location data structure retrieved from the DataHelper. Should be -- the right data for this card. internal.spawnLocationTokensFromDataHelper = function(card, locationData) local clueCount = internal.getClueCountFromData(card, locationData) if clueCount > 0 then TokenManager.spawnTokenGroup(card, "clue", clueCount) tokenSpawnTracker.markTokensSpawned(card.getGUID()) end end internal.getPlayerCardData = function(card) return playerCardData[card.getName() .. ':' .. card.getDescription()] or playerCardData[card.getName()] end internal.getLocationData = function(card) return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()] end internal.getClueCountFromData = function(card, locationData) -- Return the number of clues to spawn on this location if locationData == nil then error('attempted to get clue for unexpected object: ' .. card.getName()) return 0 end log(card.getName() .. ' : ' .. locationData.type .. ' : ' .. locationData.value .. ' : ' .. locationData.clueSide) if ((card.is_face_down and locationData.clueSide == 'back') or (not card.is_face_down and locationData.clueSide == 'front')) then if locationData.type == 'fixed' then return locationData.value elseif locationData.type == 'perPlayer' then return locationData.value * playArea.getInvestigatorCount() end error('unexpected location type: ' .. locationData.type) end return 0 end -- Gets the right uses structure for this card, based on metadata and face up/down state ---@param card Object Card to pull the uses from internal.getUses = function(card) local metadata = JSON.decode(card.getGMNotes()) or { } if metadata.type == "Location" then if card.is_face_down and metadata.locationBack ~= nil then return metadata.locationBack.uses elseif not card.is_face_down and metadata.locationFront ~= nil then return metadata.locationFront.uses end elseif not card.is_face_down then return metadata.uses end return nil end -- Dynamically create positions for clues on a card. ---@param card Object Card the clues will be placed on ---@param count Integer How many clues? ---@return Table Array of global positions to spawn the clues at internal.buildClueOffsets = function(card, count) local pos = card.getPosition() local cluePositions = { } for i = 1, count do local row = math.floor(1 + (i - 1) / 4) local column = (i - 1) % 4 table.insert(cluePositions, Vector(pos.x + 1.5 - 0.55 * row, pos.y + 0.15, pos.z - 0.825 + 0.55 * column)) end return cluePositions end return TokenManager end end) return __bundle_require("__root")