token manager including

This commit is contained in:
Chr1Z93 2024-08-03 23:19:52 +02:00
parent 5eb6873c45
commit 96f251183f
8 changed files with 636 additions and 557 deletions

View File

@ -1,24 +1,24 @@
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi") local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi") local guidReferenceApi = require("core/GUIDReferenceApi")
local mythosAreaApi = require("core/MythosAreaApi") local mythosAreaApi = require("core/MythosAreaApi")
local navigationOverlayApi = require("core/NavigationOverlayApi") local navigationOverlayApi = require("core/NavigationOverlayApi")
local playAreaApi = require("core/PlayAreaApi") local playAreaApi = require("core/PlayAreaApi")
local playermatApi = require("playermat/PlayermatApi") local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib") local searchLib = require("util/SearchLib")
local soundCubeApi = require("core/SoundCubeApi") local soundCubeApi = require("core/SoundCubeApi")
local tokenArrangerApi = require("accessories/TokenArrangerApi") local tokenArrangerApi = require("accessories/TokenArrangerApi")
local tokenChecker = require("core/token/TokenChecker") local tokenChecker = require("core/token/TokenChecker")
local tokenManager = require("core/token/TokenManager") local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
--------------------------------------------------------- ---------------------------------------------------------
-- general setup -- general setup
--------------------------------------------------------- ---------------------------------------------------------
ENCOUNTER_DECK_POS = { -3.93, 1, 5.76 } ENCOUNTER_DECK_POS = { -3.93, 1, 5.76 }
ENCOUNTER_DECK_DISCARD_POSITION = { -3.85, 1, 10.38 } ENCOUNTER_DECK_DISCARD_POSITION = { -3.85, 1, 10.38 }
-- GUIDs that will not be interactable (e.g. parts of the table) -- GUIDs that will not be interactable (e.g. parts of the table)
local NOT_INTERACTABLE = { local NOT_INTERACTABLE = {
"6161b4", -- Decoration-Map "6161b4", -- Decoration-Map
"9f334f", -- MythosArea "9f334f", -- MythosArea
"463022", -- Panel behind tentacle stand "463022", -- Panel behind tentacle stand
@ -30,23 +30,23 @@ local NOT_INTERACTABLE = {
"975c39", -- vertical border right "975c39", -- vertical border right
} }
local chaosTokens = {} local chaosTokens = {}
local chaosTokensLastMatGUID = nil local chaosTokensLastMatGUID = nil
-- chaos token stat tracking -- chaos token stat tracking
local tokenDrawingStats = { ["Overall"] = {} } local tokenDrawingStats = { ["Overall"] = {} }
local bagSearchers = {} local bagSearchers = {}
local hideTitleSplashWaitFunctionId = nil local hideTitleSplashWaitFunctionId = nil
-- online functionality related variables -- online functionality related variables
local MOD_VERSION = "3.9.1" local MOD_VERSION = "3.9.1"
local SOURCE_REPO = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main' local SOURCE_REPO = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main'
local library, requestObj, modMeta local library, requestObj, modMeta
local acknowledgedUpgradeVersions = {} local acknowledgedUpgradeVersions = {}
local contentToShow = "campaigns" local contentToShow = "campaigns"
local currentListItem = 1 local currentListItem = 1
local tabIdTable = { local tabIdTable = {
tab1 = "campaigns", tab1 = "campaigns",
tab2 = "scenarios", tab2 = "scenarios",
tab3 = "fanmadeCampaigns", tab3 = "fanmadeCampaigns",
@ -55,8 +55,8 @@ local tabIdTable = {
} }
-- optionPanel data (intentionally not local!) -- optionPanel data (intentionally not local!)
optionPanel = {} optionPanel = {}
local LANGUAGES = { local LANGUAGES = {
{ code = "zh_CN", name = "简体中文" }, { code = "zh_CN", name = "简体中文" },
{ code = "zh_TW", name = "繁體中文" }, { code = "zh_TW", name = "繁體中文" },
{ code = "de", name = "Deutsch" }, { code = "de", name = "Deutsch" },
@ -65,7 +65,7 @@ local LANGUAGES = {
{ code = "fr", name = "Français" }, { code = "fr", name = "Français" },
{ code = "it", name = "Italiano" } { code = "it", name = "Italiano" }
} }
local RESOURCE_OPTIONS = { local RESOURCE_OPTIONS = {
"enabled", "enabled",
"custom", "custom",
"disabled" "disabled"
@ -75,7 +75,7 @@ local RESOURCE_OPTIONS = {
-- data for tokens -- data for tokens
--------------------------------------------------------- ---------------------------------------------------------
TOKEN_DATA = { TOKEN_DATA = {
damage = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357115146/903D11AAE7BD5C254C8DC136E9202EE516289DEA/", scale = { 0.17, 0.17, 0.17 } }, damage = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357115146/903D11AAE7BD5C254C8DC136E9202EE516289DEA/", scale = { 0.17, 0.17, 0.17 } },
horror = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357163535/6D9E0756503664D65BDB384656AC6D4BD713F5FC/", scale = { 0.17, 0.17, 0.17 } }, horror = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357163535/6D9E0756503664D65BDB384656AC6D4BD713F5FC/", scale = { 0.17, 0.17, 0.17 } },
resource = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/", scale = { 0.17, 0.17, 0.17 } }, resource = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/", scale = { 0.17, 0.17, 0.17 } },
@ -83,7 +83,7 @@ TOKEN_DATA = {
clue = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357164917/1D06F1DC4D6888B6F57124BD2AFE20D0B0DA15A8/", scale = { 0.15, 0.15, 0.15 } } clue = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357164917/1D06F1DC4D6888B6F57124BD2AFE20D0B0DA15A8/", scale = { 0.15, 0.15, 0.15 } }
} }
ID_URL_MAP = { ID_URL_MAP = {
['blue'] = { name = "Elder Sign", url = 'https://i.imgur.com/nEmqjmj.png' }, ['blue'] = { name = "Elder Sign", url = 'https://i.imgur.com/nEmqjmj.png' },
['p1'] = { name = "+1", url = 'https://i.imgur.com/uIx8jbY.png' }, ['p1'] = { name = "+1", url = 'https://i.imgur.com/uIx8jbY.png' },
['0'] = { name = "0", url = 'https://i.imgur.com/btEtVfd.png' }, ['0'] = { name = "0", url = 'https://i.imgur.com/btEtVfd.png' },
@ -105,6 +105,26 @@ ID_URL_MAP = {
['frost'] = { name = "Frost", url = 'https://steamusercontent-a.akamaihd.net/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/' } ['frost'] = { name = "Frost", url = 'https://steamusercontent-a.akamaihd.net/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/' }
} }
TokenManager = {}
local tokenOffsets = {}
-- 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 playerCardData, locationData
-- stateIDs for the multi-stated resource tokens
local stateTable = {
["resource"] = 1,
["ammo"] = 2,
["bounty"] = 3,
["charge"] = 4,
["evidence"] = 5,
["secret"] = 6,
["supply"] = 7,
["offering"] = 8
}
--------------------------------------------------------- ---------------------------------------------------------
-- general code -- general code
--------------------------------------------------------- ---------------------------------------------------------
@ -148,6 +168,7 @@ function onLoad(savedData)
getModVersion() getModVersion()
math.randomseed(os.time()) math.randomseed(os.time())
TokenManager.initialiize()
-- initialization of loadable objects library (delay to let Navigation Overlay build) -- initialization of loadable objects library (delay to let Navigation Overlay build)
Wait.time(function() Wait.time(function()
@ -432,7 +453,7 @@ function returnAndRedraw(_, tokenGUID)
-- perform the actual token replacing -- perform the actual token replacing
trackChaosToken(tokenName, mat.getGUID(), true) trackChaosToken(tokenName, mat.getGUID(), true)
local params = {token = returnedToken, fromBag = true} local params = { token = returnedToken, fromBag = true }
returnChaosTokenToBag(params) returnChaosTokenToBag(params)
chaosTokens[indexOfReturnedToken] = drawChaosToken({ chaosTokens[indexOfReturnedToken] = drawChaosToken({
@ -566,11 +587,15 @@ end
-- token spawning -- token spawning
--------------------------------------------------------- ---------------------------------------------------------
-- DEPRECATED. Use TokenManager instead. -- DEPRECATED. Use TokenManager instead --> TODO: Remove this with the new downloads repo (v.4.0.0)
-- Spawns a single token. -- Spawns a single token.
---@param params table Array with arguments to the method. 1 = position, 2 = type, 3 = rotation ---@param params table Array with arguments to the method. 1 = position, 2 = type, 3 = rotation
function spawnToken(params) function spawnToken(params)
return tokenManager.spawnToken(params[1], params[2], params[3]) return TokenManager.spawnToken({
position = params[1],
tokenType = params[2],
rotation = params[3]
})
end end
--------------------------------------------------------- ---------------------------------------------------------
@ -1153,12 +1178,9 @@ function onClick_toggleUi(player, windowId)
return return
end end
-- hide the playAreaGallery if visible -- hide the playAreaGallery / downloadWindow if visible
if windowId == "downloadWindow" then if windowId == "downloadWindow" or windowId == "playAreaGallery" then
changeWindowVisibilityForColor(player.color, "playAreaGallery", false) changeWindowVisibilityForColor(player.color, windowId, false)
-- hide the downloadWindow if visible
elseif windowId == "playAreaGallery" then
changeWindowVisibilityForColor(player.color, "downloadWindow", false)
end end
changeWindowVisibilityForColor(player.color, windowId) changeWindowVisibilityForColor(player.color, windowId)
@ -1236,7 +1258,8 @@ function updatePreviewWindow()
-- set default image if not defined -- set default image if not defined
if item.boxsize == nil or item.boxsize == "" or item.boxart == nil or item.boxart == "" then if item.boxsize == nil or item.boxsize == "" or item.boxart == nil or item.boxart == "" then
item.boxsize = "big" item.boxsize = "big"
item.boxart = "https://steamusercontent-a.akamaihd.net/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/" item.boxart =
"https://steamusercontent-a.akamaihd.net/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/"
end end
UI.setValue("previewTitle", item.name) UI.setValue("previewTitle", item.name)
@ -1394,7 +1417,7 @@ function contentDownloadCallback(request, params)
spawnTable.position = pos spawnTable.position = pos
else else
broadcastToAll( broadcastToAll(
"Please make space in the area below the tentacle stand in the upper middle of the table and try again.", "Red") "Please make space in the area below the tentacle stand in the upper middle of the table and try again.", "Red")
return return
end end
end end
@ -1883,6 +1906,479 @@ function onClick_notification(_, parameter)
UI.hide("updateNotification") UI.hide("updateNotification")
end end
---------------------------------------------------------
-- Token Manager
---------------------------------------------------------
function TokenManager.initialiize()
TokenManager.generateOffsets(12)
end
-- Generates the offsets for tokens on a card (clues on locations are different and have their own function)
---@param maxTokens number Maximum amount of tokens on a card
function TokenManager.generateOffsets(maxTokens)
tokenOffsets = {}
for numTokens = 1, maxTokens do
if numTokens == 1 then
tokenOffsets[1] = Vector(0, 3, -0.2)
else
local offsets = {}
local rows = math.min(4, math.ceil(numTokens / 3))
local tokensPlaced = 0
for row = 1, rows do
local y = 3
local z = -0.9 + (row - 1) * 0.7
local tokensInRow = math.min(3, numTokens - tokensPlaced)
for col = 1, tokensInRow do
local x = 0
if tokensInRow == 2 then
x = col == 1 and -0.4 or 0.4
elseif tokensInRow == 3 then
x = (col - 2) * 0.7
end
table.insert(offsets, Vector(x, y, z))
tokensPlaced = tokensPlaced + 1
end
end
tokenOffsets[numTokens] = offsets
end
end
end
-- 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.
function TokenManager.spawnForCard(params)
if tokenSpawnTrackerApi.hasSpawnedTokens(params.card.getGUID()) then return end
local metadata = JSON.decode(params.card.getGMNotes())
if metadata ~= nil then
TokenManager.spawnTokensFromUses(params.card, params.extraUses)
else
TokenManager.spawnTokensFromDataHelper(params.card)
end
end
-- Spawns a set of tokens on the given card.
function TokenManager.spawnTokenGroup(param)
local card = param.card
local tokenType = param.tokenType
local tokenCount = param.tokenCount
local shiftDown = param.shiftDown
local subType = param.subType
if tokenType == "damage" or tokenType == "horror" then
TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown)
elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "enabled" then
TokenManager.spawnResourceCounterToken(card, tokenCount)
elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "custom" and tokenCount == 0 then
TokenManager.spawnResourceCounterToken(card, tokenCount)
else
TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType)
end
end
-- Spawns a single counter token and sets the value to tokenValue. Used for damage and horror tokens.
---@param card tts__Object Card to spawn tokens on
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@param tokenValue number Value to set the damage/horror to
function TokenManager.spawnCounterToken(card, tokenType, tokenValue, shiftDown)
if tokenValue < 1 or tokenValue > 50 then return end
local pos = card.positionToWorld(tokenOffsets[1][1] + Vector(0, 0, shiftDown))
local rot = card.getRotation()
TokenManager.spawnToken({
position = pos,
tokenType = tokenType,
rotation = rot,
callback = function(spawned)
-- token starts in state 1, so don't attempt to change it to avoid error
if tokenValue ~= 1 then
spawned.setState(tokenValue)
end
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({
position = pos,
tokenType = "resourceCounter",
rotation = rot,
callback = function(spawned)
spawned.call("updateVal", tokenCount)
end
})
end
-- Spawns a number of tokens.
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@param tokenCount number How many tokens to spawn
---@param shiftDown? number An offset for the z-value of this group of tokens
---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource or action tokens
function TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType)
if tokenCount < 1 then return end
local offsets = {}
if tokenType == "clue" then
offsets = TokenManager.buildClueOffsets(card, tokenCount)
else
if tokenCount > 12 then
printToAll("Attempting to spawn " .. tokenCount .. " tokens. Spawning clickable counter instead.")
TokenManager.spawnResourceCounterToken(card, tokenCount)
return
end
for i = 1, tokenCount do
offsets[i] = card.positionToWorld(tokenOffsets[tokenCount][i])
end
end
if shiftDown ~= nil then
-- Copy the offsets to make sure we don't change the static values
local baseOffsets = offsets
offsets = {}
-- get a vector for the shifting (downwards local to the card)
local shiftDownVector = Vector(0, 0, shiftDown):rotateOver("y", card.getRotation().y)
for i, baseOffset in ipairs(baseOffsets) do
offsets[i] = baseOffset + shiftDownVector
end
end
if offsets == nil then
error("couldn't find offsets for " .. tokenCount .. ' tokens')
return
end
-- this is used to load the correct state for additional resource tokens (e.g. "Ammo")
local callback = nil
local stateID = stateTable[string.lower(subType or "")]
if tokenType == "resource" and stateID ~= nil and stateID ~= 1 then
callback = function(spawned) spawned.setState(stateID) end
elseif tokenType == "universalActionAbility" then
local matColor = playermatApi.getMatColorByPosition(card.getPosition())
local class = playermatApi.returnInvestigatorClass(matColor)
callback = function(spawned) spawned.call("updateClassAndSymbol", { class = class, symbol = subType or class }) end
end
for i = 1, tokenCount do
TokenManager.spawnToken({
position = offsets[i],
tokenType = tokenType,
rotation = card.getRotation(),
callback = callback
})
end
end
-- Spawns a single token at the given global position by copying it from the template bag.
function TokenManager.spawnToken(params)
local position = params.position
local rotation = params.rotation
local tokenType = params.tokenType
local callback = params.callback
TokenManager.initTokenTemplates()
local loadTokenType = tokenType
if tokenType == "clue" or tokenType == "doom" then
loadTokenType = "clueDoom"
end
local tokenTemplate = tokenTemplates[loadTokenType]
if tokenTemplate == nil then
error("Unknown token type '" .. loadTokenType .. "'")
return
end
-- 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
-- Checks a card for metadata to maybe replenish it
function TokenManager.maybeReplenishCard(params)
for _, useInfo in ipairs(params.uses) do
if useInfo.count and useInfo.replenish then
TokenManager.replenishTokens(params.card, useInfo)
end
end
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
function TokenManager.addPlayerCardData(dataTable)
TokenManager.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
function TokenManager.addLocationData(dataTable)
TokenManager.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 tts__Object Card to check for data
---@return boolean: True if this card has data in the helper, false otherwise
function TokenManager.hasLocationData(card)
TokenManager.initDataHelperData()
return TokenManager.getLocationData(card) ~= nil
end
function TokenManager.initTokenTemplates()
if tokenTemplates ~= nil then return end
tokenTemplates = {}
local tokenSource = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSource")
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.
function TokenManager.initDataHelperData()
if playerCardData ~= nil then return end
local dataHelper = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
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 tts__Object Card to maybe spawn tokens for
---@param extraUses table A table of <use type>=<count> which will modify the number of tokens
--- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1
function TokenManager.spawnTokensFromUses(card, extraUses)
local uses = TokenManager.getUses(card)
if uses == nil then return end
-- go through tokens to spawn
local tokenCount
for i, useInfo in ipairs(uses) do
tokenCount = (useInfo.count or 0) + (useInfo.countPerInvestigator or 0) * playAreaApi.getInvestigatorCount()
if extraUses ~= nil and extraUses[useInfo.type] ~= nil then
tokenCount = tokenCount + extraUses[useInfo.type]
end
-- Shift each spawned group after the first down so they don't pile on each other
TokenManager.spawnTokenGroup({
card = card,
tokenType = useInfo.token,
tokenCount = tokenCount,
shiftDown = (i - 1) * 0.8,
subType = useInfo.type
})
end
tokenSpawnTrackerApi.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 tts__Object Card to maybe spawn tokens for
function TokenManager.spawnTokensFromDataHelper(card)
TokenManager.initDataHelperData()
local playerData = TokenManager.getPlayerCardData(card)
if playerData ~= nil then
TokenManager.spawnPlayerCardTokensFromDataHelper(card, playerData)
end
local specificLocationData = TokenManager.getLocationData(card)
if specificLocationData ~= nil then
TokenManager.spawnLocationTokensFromDataHelper(card, specificLocationData)
end
end
-- Spawn tokens for a player card using data retrieved from the Data Helper.
---@param card tts__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.
function TokenManager.spawnPlayerCardTokensFromDataHelper(card, playerData)
TokenManager.spawnTokenGroup({
card = card,
tokenType = playerData.tokenType,
tokenCount = playerData.tokenCount
})
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
end
-- Spawn tokens for a location using data retrieved from the Data Helper.
---@param card tts__Object Card to maybe spawn tokens for
---@param locationData table Location data structure retrieved from the DataHelper. Should be
-- the right data for this card.
function TokenManager.spawnLocationTokensFromDataHelper(card, locationData)
local clueCount = TokenManager.getClueCountFromData(card, locationData)
if clueCount > 0 then
TokenManager.spawnTokenGroup({ card = card, tokenType = "clue", tokenCount = clueCount })
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
end
end
function TokenManager.getPlayerCardData(card)
return playerCardData[card.getName() .. ':' .. card.getDescription()]
or playerCardData[card.getName()]
end
function TokenManager.getLocationData(card)
return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()]
end
function TokenManager.getClueCountFromData(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
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 * playAreaApi.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 tts__Object Card to pull the uses from
TokenManager.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 tts__Object Card the clues will be placed on
---@param count number How many clues?
---@return table: Array of global positions to spawn the clues at
TokenManager.buildClueOffsets = function(card, count)
-- make sure clues always spawn from left to right
local modifier = card.is_face_down and 1 or -1
local cluePositions = {}
for i = 1, count do
-- get the set number (1 for clue 1-16, 2 for 17-32 etc.)
local set = math.floor((i - 1) / 16) + 1
-- get the local index (always number from 1-16)
local localIndex = (i - 1) % 16
-- get row and column for this clue
local row = math.floor(localIndex / 4) + 1
local column = localIndex % 4
-- calculate local position
local localPos = Vector((-0.825 + 0.55 * column) * modifier, 0, -1.5 + 0.55 * row)
-- get the global clue position (higher y-position for each set)
local cluePos = card.positionToWorld(localPos) + Vector(0, 0.03 + 0.103 * (set - 1), 0)
-- add position to table
table.insert(cluePositions, cluePos)
end
return cluePositions
end
---@param card tts__Object Card object to be replenished
---@param useInfo table The already decoded subtable of metadata.uses (to avoid decoding again)
TokenManager.replenishTokens = function(card, useInfo)
-- get current amount of matching resource tokens on the card
local clickableResourceCounter = nil
local foundTokens = 0
local maybeDeleteThese = {}
if useInfo.token == "clue" then
for _, obj in ipairs(searchLib.onObject(card, "isClue")) do
foundTokens = foundTokens + math.abs(obj.getQuantity())
table.insert(maybeDeleteThese, obj)
end
elseif useInfo.token == "doom" then
for _, obj in ipairs(searchLib.onObject(card, "isDoom")) do
foundTokens = foundTokens + math.abs(obj.getQuantity())
table.insert(maybeDeleteThese, obj)
end
else
-- search for the token instead if there's no special resource state for it
local searchType = string.lower(useInfo.type)
if stateTable[searchType] == nil then
searchType = useInfo.token
end
for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do
local memo = obj.getMemo()
if searchType == memo then
foundTokens = foundTokens + math.abs(obj.getQuantity())
table.insert(maybeDeleteThese, obj)
elseif memo == "resourceCounter" then
foundTokens = obj.getVar("val")
clickableResourceCounter = obj
break
end
end
end
-- this is the theoretical new amount of uses (to be checked below)
local newCount = foundTokens + useInfo.replenish
-- if there are already more uses than the replenish amount, keep them
if foundTokens > useInfo.count then
newCount = foundTokens
-- only replenish up until the replenish amount
elseif newCount > useInfo.count then
newCount = useInfo.count
end
-- update the clickable counter or spawn a group of tokens
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", newCount)
else
-- delete existing tokens
for _, obj in ipairs(maybeDeleteThese) do
obj.destruct()
end
-- spawn new token group
TokenManager.spawnTokenGroup({
card = card,
tokenType = useInfo.token,
tokenCount = newCount,
subType = useInfo.type
})
end
end
--------------------------------------------------------- ---------------------------------------------------------
-- Utility functions -- Utility functions
--------------------------------------------------------- ---------------------------------------------------------

View File

@ -1,6 +1,6 @@
local guidReferenceApi = require("core/GUIDReferenceApi") local guidReferenceApi = require("core/GUIDReferenceApi")
local searchLib = require("util/SearchLib") local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager") local tokenManagerApi = require("core/token/TokenManagerApi")
-- Location connection directional options -- Location connection directional options
local BIDIRECTIONAL = 0 local BIDIRECTIONAL = 0
@ -80,7 +80,7 @@ end
function updateLocations(args) function updateLocations(args)
customDataHelper = getObjectFromGUID(args[1]) customDataHelper = getObjectFromGUID(args[1])
if customDataHelper ~= nil then if customDataHelper ~= nil then
tokenManager.addLocationData(customDataHelper.getTable("LOCATIONS_DATA")) tokenManagerApi.addLocationData(customDataHelper.getTable("LOCATIONS_DATA"))
end end
end end
@ -102,7 +102,7 @@ function onCollisionEnter(collisionInfo)
-- check if we should spawn clues here and do so according to playercount -- check if we should spawn clues here and do so according to playercount
if shouldSpawnTokens(object) then if shouldSpawnTokens(object) then
tokenManager.spawnForCard(object) tokenManagerApi.spawnForCard(object)
end end
-- If this card was being dragged, clear the dragging connections. A multi-drag/drop may send -- If this card was being dragged, clear the dragging connections. A multi-drag/drop may send
@ -192,7 +192,7 @@ end
function shouldSpawnTokens(card) function shouldSpawnTokens(card)
local metadata = JSON.decode(card.getGMNotes()) local metadata = JSON.decode(card.getGMNotes())
if metadata == nil then if metadata == nil then
return tokenManager.hasLocationData(card) return tokenManagerApi.hasLocationData(card)
end end
return metadata.type == "Location" return metadata.type == "Location"
or metadata.type == "Enemy" or metadata.type == "Enemy"

View File

@ -1,486 +0,0 @@
do
local GlobalApi = require("core/GlobalApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playAreaApi = require("core/PlayAreaApi")
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
local TokenManager = {}
local internal = {}
-- stateIDs for the multi-stated resource tokens
local stateTable = {
["resource"] = 1,
["ammo"] = 2,
["bounty"] = 3,
["charge"] = 4,
["evidence"] = 5,
["secret"] = 6,
["supply"] = 7,
["offering"] = 8
}
-- 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 playerCardData
local locationData
function internal.generateOffsets(maxTokens)
local totalOffsets = {}
for numTokens = 1, maxTokens do
local offsets = {}
local rows = math.min(4, math.ceil(numTokens / 3))
local tokensPlaced = 0
for row = 1, rows do
local y = 3
local z = -0.9 + (row - 1) * 0.7
local tokensInRow = math.min(3, numTokens - tokensPlaced)
for col = 1, tokensInRow do
local x = 0
if tokensInRow == 2 then
x = col == 1 and -0.4 or 0.4
elseif tokensInRow == 3 then
x = (col - 2) * 0.7
end
table.insert(offsets, Vector(x, y, z))
tokensPlaced = tokensPlaced + 1
end
end
-- Handle special case for 1 token
if numTokens == 1 then
offsets[1] = Vector(0, 3, -0.2)
end
totalOffsets[numTokens] = offsets
end
return totalOffsets
end
local PLAYER_CARD_TOKEN_OFFSETS = internal.generateOffsets(12)
-- 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 tts__Object Card to maybe spawn tokens for
---@param extraUses table A table of <use type>=<count> which will modify the number of tokens
--- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1
TokenManager.spawnForCard = function(card, extraUses)
if tokenSpawnTrackerApi.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 tts__Object Card to spawn tokens on
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@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
---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource tokens
TokenManager.spawnTokenGroup = function(card, tokenType, tokenCount, shiftDown, subType)
local optionPanel = GlobalApi.getOptionPanelState()
if tokenType == "damage" or tokenType == "horror" then
TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown)
elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "enabled" then
TokenManager.spawnResourceCounterToken(card, tokenCount)
elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "custom" and tokenCount == 0 then
TokenManager.spawnResourceCounterToken(card, tokenCount)
else
TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType)
end
end
-- Spawns a single counter token and sets the value to tokenValue. Used for damage and horror tokens.
---@param card tts__Object Card to spawn tokens on
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@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)
-- token starts in state 1, so don't attempt to change it to avoid error
if tokenValue ~= 1 then
spawned.setState(tokenValue)
end
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 (template needs to be in source bag)
---@param tokenCount number How many tokens to spawn
---@param shiftDown? number An offset for the z-value of this group of tokens
---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource or action tokens
TokenManager.spawnMultipleTokens = function(card, tokenType, tokenCount, shiftDown, subType)
-- not checking the max at this point since clue offsets are calculated dynamically
if tokenCount < 1 then return end
local offsets = {}
if tokenType == "clue" then
offsets = internal.buildClueOffsets(card, tokenCount)
else
-- only up to 12 offset tables defined
if tokenCount > 12 then
printToAll("Attempting to spawn " .. tokenCount .. " tokens. Spawning clickable counter instead.")
TokenManager.spawnResourceCounterToken(card, tokenCount)
return
end
for i = 1, tokenCount do
offsets[i] = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[tokenCount][i])
end
end
if shiftDown ~= nil then
-- Copy the offsets to make sure we don't change the static values
local baseOffsets = offsets
offsets = {}
-- get a vector for the shifting (downwards local to the card)
local shiftDownVector = Vector(0, 0, shiftDown):rotateOver("y", card.getRotation().y)
for i, baseOffset in ipairs(baseOffsets) do
offsets[i] = baseOffset + shiftDownVector
end
end
if offsets == nil then
error("couldn't find offsets for " .. tokenCount .. ' tokens')
return
end
-- this is used to load the correct state for additional resource tokens (e.g. "Ammo")
local callback = nil
local stateID = stateTable[string.lower(subType or "")]
if tokenType == "resource" and stateID ~= nil and stateID ~= 1 then
callback = function(spawned) spawned.setState(stateID) end
elseif tokenType == "universalActionAbility" then
local matColor = playermatApi.getMatColorByPosition(card.getPosition())
local class = playermatApi.returnInvestigatorClass(matColor)
callback = function(spawned) spawned.call("updateClassAndSymbol", { class = class, symbol = subType or class }) end
end
for i = 1, tokenCount do
TokenManager.spawnToken(offsets[i], tokenType, card.getRotation(), callback)
end
end
-- Spawns a single token at the given global position by copying it from the template bag.
---@param position tts__Vector Global position to spawn the token
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@param rotation tts__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
-- Checks a card for metadata to maybe replenish it
---@param card tts__Object Card object to be replenished
---@param uses table The already decoded metadata.uses (to avoid decoding again)
TokenManager.maybeReplenishCard = function(card, uses)
for _, useInfo in ipairs(uses) do
if useInfo.count and useInfo.replenish then
internal.replenishTokens(card, useInfo)
end
end
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 tts__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 = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSource")
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 = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
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 tts__Object Card to maybe spawn tokens for
---@param extraUses table A table of <use type>=<count> which will modify the number of tokens
--- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1
internal.spawnTokensFromUses = function(card, extraUses)
local uses = internal.getUses(card)
if uses == nil then return end
-- go through tokens to spawn
local tokenCount
for i, useInfo in ipairs(uses) do
tokenCount = (useInfo.count or 0) + (useInfo.countPerInvestigator or 0) * playAreaApi.getInvestigatorCount()
if extraUses ~= nil and extraUses[useInfo.type] ~= nil then
tokenCount = tokenCount + extraUses[useInfo.type]
end
-- Shift each spawned group after the first down so they don't pile on each other
TokenManager.spawnTokenGroup(card, useInfo.token, tokenCount, (i - 1) * 0.8, useInfo.type)
end
tokenSpawnTrackerApi.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 tts__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 tts__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)
local token = playerData.tokenType
local tokenCount = playerData.tokenCount
TokenManager.spawnTokenGroup(card, token, tokenCount)
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
end
-- Spawn tokens for a location using data retrieved from the Data Helper.
---@param card tts__Object Card to maybe spawn tokens for
---@param locationData 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)
tokenSpawnTrackerApi.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
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 * playAreaApi.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 tts__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 tts__Object Card the clues will be placed on
---@param count number How many clues?
---@return table: Array of global positions to spawn the clues at
internal.buildClueOffsets = function(card, count)
-- make sure clues always spawn from left to right
local modifier = card.is_face_down and 1 or -1
local cluePositions = {}
for i = 1, count do
-- get the set number (1 for clue 1-16, 2 for 17-32 etc.)
local set = math.floor((i - 1) / 16) + 1
-- get the local index (always number from 1-16)
local localIndex = (i - 1) % 16
-- get row and column for this clue
local row = math.floor(localIndex / 4) + 1
local column = localIndex % 4
-- calculate local position
local localPos = Vector((-0.825 + 0.55 * column) * modifier, 0, -1.5 + 0.55 * row)
-- get the global clue position (higher y-position for each set)
local cluePos = card.positionToWorld(localPos) + Vector(0, 0.03 + 0.103 * (set - 1), 0)
-- add position to table
table.insert(cluePositions, cluePos)
end
return cluePositions
end
---@param card tts__Object Card object to be replenished
---@param useInfo table The already decoded subtable of metadata.uses (to avoid decoding again)
internal.replenishTokens = function(card, useInfo)
-- get current amount of matching resource tokens on the card
local clickableResourceCounter = nil
local foundTokens = 0
local maybeDeleteThese = {}
if useInfo.token == "clue" then
for _, obj in ipairs(searchLib.onObject(card, "isClue")) do
foundTokens = foundTokens + math.abs(obj.getQuantity())
table.insert(maybeDeleteThese, obj)
end
elseif useInfo.token == "doom" then
for _, obj in ipairs(searchLib.onObject(card, "isDoom")) do
foundTokens = foundTokens + math.abs(obj.getQuantity())
table.insert(maybeDeleteThese, obj)
end
else
-- search for the token instead if there's no special resource state for it
local searchType = string.lower(useInfo.type)
if stateTable[searchType] == nil then
searchType = useInfo.token
end
for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do
local memo = obj.getMemo()
if searchType == memo then
foundTokens = foundTokens + math.abs(obj.getQuantity())
table.insert(maybeDeleteThese, obj)
elseif memo == "resourceCounter" then
foundTokens = obj.getVar("val")
clickableResourceCounter = obj
break
end
end
end
-- this is the theoretical new amount of uses (to be checked below)
local newCount = foundTokens + useInfo.replenish
-- if there are already more uses than the replenish amount, keep them
if foundTokens > useInfo.count then
newCount = foundTokens
-- only replenish up until the replenish amount
elseif newCount > useInfo.count then
newCount = useInfo.count
end
-- update the clickable counter or spawn a group of tokens
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", newCount)
else
-- delete existing tokens
for _, obj in ipairs(maybeDeleteThese) do
obj.destruct()
end
-- spawn new token group
TokenManager.spawnTokenGroup(card, useInfo.token, newCount, _, useInfo.type)
end
end
return TokenManager
end

View File

@ -0,0 +1,67 @@
do
local TokenManagerApi = {}
-- Pushes new location data into the local copy of the Data Helper location data.
---@param dataTable table Key/Value pairs following the DataHelper style
function TokenManagerApi.addLocationData(dataTable)
Global.call("TokenManager.addLocationData", dataTable)
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
function TokenManagerApi.addPlayerCardData(dataTable)
Global.call("TokenManager.addPlayerCardData", dataTable)
end
-- 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 tts__Object Card to maybe spawn tokens for
---@param extraUses table A table of <use type>=<count> which will modify the number of tokens
--- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1
function TokenManagerApi.spawnForCard(card, extraUses)
Global.call("TokenManager.spawnForCard", { card = card, extraUses = extraUses })
end
-- Spawns a single token at the given global position by copying it from the template bag.
---@param position tts__Vector Global position to spawn the token
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@param rotation tts__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
function TokenManagerApi.spawnToken(position, tokenType, rotation, callback)
Global.call("TokenManager.spawnToken", {
position = position,
tokenType = tokenType,
rotation = rotation,
callback = callback
})
end
-- Spawns a set of tokens on the given card.
---@param card tts__Object Card to spawn tokens on
---@param tokenType string Type of token to spawn (template needs to be in source bag)
---@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
---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource tokens
function TokenManagerApi.spawnTokenGroup(card, tokenType, tokenCount, shiftDown, subType)
Global.call("TokenManager.spawnTokenGroup", {
card = card,
tokenType = tokenType,
tokenCount = tokenCount,
shiftDown = shiftDown,
subType = subType
})
end
-- Checks a card for metadata to maybe replenish it
---@param card tts__Object Card object to be replenished
---@param uses table The already decoded metadata.uses (to avoid decoding again)
function TokenManagerApi.maybeReplenishCard(card, uses)
Global.call("TokenManager.maybeReplenishCard", { card = card, uses = uses })
end
return TokenManagerApi
end

View File

@ -1,7 +1,7 @@
require("playercards/CardsWithHelper") require("playercards/CardsWithHelper")
local playermatApi = require("playermat/PlayermatApi") local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib") local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager") local tokenManagerApi = require("core/token/TokenManagerApi")
-- intentionally global -- intentionally global
hasXML = true hasXML = true
@ -45,7 +45,7 @@ function add4()
if clickableResourceCounter then if clickableResourceCounter then
clickableResourceCounter.call("updateVal", newCount) clickableResourceCounter.call("updateVal", newCount)
else else
tokenManager.spawnTokenGroup(self, "resource", newCount) tokenManagerApi.spawnTokenGroup(self, "resource", newCount)
end end
end end

View File

@ -3,7 +3,7 @@ local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi") local guidReferenceApi = require("core/GUIDReferenceApi")
local playermatApi = require("playermat/PlayermatApi") local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib") local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager") local tokenManagerApi = require("core/token/TokenManagerApi")
-- intentionally global -- intentionally global
hasXML = true hasXML = true
@ -88,7 +88,7 @@ function removeAndExtraAction()
spawned.call("updateClassAndSymbol", { class = "Mystic", symbol = "Mystic" }) spawned.call("updateClassAndSymbol", { class = "Mystic", symbol = "Mystic" })
spawned.addTag("Temporary") spawned.addTag("Temporary")
end end
tokenManager.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callback) tokenManagerApi.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callback)
end end
function elderSignAbility() function elderSignAbility()

View File

@ -6,7 +6,7 @@ local mythosAreaApi = require("core/MythosAreaApi")
local navigationOverlayApi = require("core/NavigationOverlayApi") local navigationOverlayApi = require("core/NavigationOverlayApi")
local searchLib = require("util/SearchLib") local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker") local tokenChecker = require("core/token/TokenChecker")
local tokenManager = require("core/token/TokenManager") local tokenManagerApi = require("core/token/TokenManagerApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
-- option panel data -- option panel data
@ -361,7 +361,7 @@ function doUpkeep(_, clickedByColor, isRightClick)
-- maybe replenish uses on certain cards (don't continue for cards on the deck (Norman) or in the discard pile) -- maybe replenish uses on certain cards (don't continue for cards on the deck (Norman) or in the discard pile)
if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 and not obj.is_face_down then if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 and not obj.is_face_down then
tokenManager.maybeReplenishCard(obj, cardMetadata.uses, self) tokenManagerApi.maybeReplenishCard(obj, cardMetadata.uses, self)
end end
elseif obj.type == "Deck" and forcedLearning == false then elseif obj.type == "Deck" and forcedLearning == false then
-- check decks for forced learning -- check decks for forced learning
@ -1103,7 +1103,7 @@ function spawnTokensFor(object)
extraUses["Charge"] = 1 extraUses["Charge"] = 1
end end
tokenManager.spawnForCard(object, extraUses) tokenManagerApi.spawnForCard(object, extraUses)
end end
function onCollisionEnter(collisionInfo) function onCollisionEnter(collisionInfo)
@ -1243,11 +1243,13 @@ function maybeUpdateActiveInvestigator(card)
-- spawn three regular action tokens (investigator specific one in the bottom spot) -- spawn three regular action tokens (investigator specific one in the bottom spot)
for i = 1, 3 do for i = 1, 3 do
local pos = self.positionToWorld(Vector(-1.54 + i * 0.17, 0, -0.28)):add(Vector(0, 0.2, 0)) local pos = self.positionToWorld(Vector(-1.54 + i * 0.17, 0, -0.28)):add(Vector(0, 0.2, 0))
tokenManagerApi.spawnToken(pos, "universalActionAbility", self.getRotation(),
tokenManager.spawnToken(pos, "universalActionAbility", self.getRotation(),
function(spawned) function(spawned)
spawned.call("updateClassAndSymbol", spawned.call("updateClassAndSymbol",
{ class = activeInvestigatorData.class, symbol = activeInvestigatorData.class }) {
class = activeInvestigatorData.class,
symbol = activeInvestigatorData.class
})
end) end)
end end
@ -1280,7 +1282,7 @@ function maybeUpdateActiveInvestigator(card)
local localSpawnPos = tokenSpawnPos[type][count[type]] local localSpawnPos = tokenSpawnPos[type][count[type]]
local globalSpawnPos = self.positionToWorld(localSpawnPos):add(Vector(0, 0.2, 0)) local globalSpawnPos = self.positionToWorld(localSpawnPos):add(Vector(0, 0.2, 0))
tokenManager.spawnToken(globalSpawnPos, "universalActionAbility", self.getRotation(), tokenManagerApi.spawnToken(globalSpawnPos, "universalActionAbility", self.getRotation(),
function(spawned) function(spawned)
spawned.call("updateClassAndSymbol", { class = activeInvestigatorData.class, symbol = str }) spawned.call("updateClassAndSymbol", { class = activeInvestigatorData.class, symbol = str })
end) end)
@ -1466,7 +1468,7 @@ function clickableClues(showCounter)
local pos = self.positionToWorld({ x = -1.12, y = 0.05, z = 0.7 }) local pos = self.positionToWorld({ x = -1.12, y = 0.05, z = 0.7 })
for i = 1, clueCount do for i = 1, clueCount do
pos.y = pos.y + 0.045 * i pos.y = pos.y + 0.045 * i
tokenManager.spawnToken(pos, "clue", self.getRotation()) tokenManagerApi.spawnToken(pos, "clue", self.getRotation())
end end
end end
end end
@ -1546,7 +1548,7 @@ end
function updatePlayerCards(args) function updatePlayerCards(args)
local customDataHelper = getObjectFromGUID(args[1]) local customDataHelper = getObjectFromGUID(args[1])
local playerCardData = customDataHelper.getTable("PLAYER_CARD_DATA") local playerCardData = customDataHelper.getTable("PLAYER_CARD_DATA")
tokenManager.addPlayerCardData(playerCardData) tokenManagerApi.addPlayerCardData(playerCardData)
end end
-- returns the colored steam name or color -- returns the colored steam name or color

View File

@ -1,16 +1,16 @@
local playermatApi = require("playermat/PlayermatApi") local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib") local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager") local tokenManagerApi = require("core/token/TokenManagerApi")
local TOKEN_INDEX = {}
TOKEN_INDEX[1] = "universalActionAbility" local TOKEN_INDEX = {}
TOKEN_INDEX[3] = "resourceCounter" TOKEN_INDEX[1] = "universalActionAbility"
TOKEN_INDEX[4] = "damage" TOKEN_INDEX[3] = "resourceCounter"
TOKEN_INDEX[5] = "path" TOKEN_INDEX[4] = "damage"
TOKEN_INDEX[6] = "horror" TOKEN_INDEX[5] = "path"
TOKEN_INDEX[7] = "doom" TOKEN_INDEX[6] = "horror"
TOKEN_INDEX[8] = "clue" TOKEN_INDEX[7] = "doom"
TOKEN_INDEX[9] = "resource" TOKEN_INDEX[8] = "clue"
TOKEN_INDEX[9] = "resource"
---@param index number Index of the pressed key ---@param index number Index of the pressed key
---@param playerColor string Color of the triggering player ---@param playerColor string Color of the triggering player
@ -75,7 +75,7 @@ function onScriptingButtonDown(index, playerColor)
end end
end end
tokenManager.spawnToken(position, tokenType, rotation, callback) tokenManagerApi.spawnToken(position, tokenType, rotation, callback)
end end
-- gets the target card for this operation -- gets the target card for this operation
@ -128,7 +128,7 @@ function addUseToCard(card, useType)
-- if matching uses were found, perform the "fake" replenish -- if matching uses were found, perform the "fake" replenish
if match then if match then
tokenManager.maybeReplenishCard(card, metadata.uses) tokenManagerApi.maybeReplenishCard(card, metadata.uses)
return true return true
else else
return false return false