token manager including
This commit is contained in:
parent
5eb6873c45
commit
96f251183f
@ -8,7 +8,7 @@ local searchLib = require("util/SearchLib")
|
||||
local soundCubeApi = require("core/SoundCubeApi")
|
||||
local tokenArrangerApi = require("accessories/TokenArrangerApi")
|
||||
local tokenChecker = require("core/token/TokenChecker")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
|
||||
|
||||
---------------------------------------------------------
|
||||
-- general setup
|
||||
@ -105,6 +105,26 @@ ID_URL_MAP = {
|
||||
['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
|
||||
---------------------------------------------------------
|
||||
@ -148,6 +168,7 @@ function onLoad(savedData)
|
||||
|
||||
getModVersion()
|
||||
math.randomseed(os.time())
|
||||
TokenManager.initialiize()
|
||||
|
||||
-- initialization of loadable objects library (delay to let Navigation Overlay build)
|
||||
Wait.time(function()
|
||||
@ -566,11 +587,15 @@ end
|
||||
-- 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.
|
||||
---@param params table Array with arguments to the method. 1 = position, 2 = type, 3 = rotation
|
||||
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
|
||||
|
||||
---------------------------------------------------------
|
||||
@ -1153,12 +1178,9 @@ function onClick_toggleUi(player, windowId)
|
||||
return
|
||||
end
|
||||
|
||||
-- hide the playAreaGallery if visible
|
||||
if windowId == "downloadWindow" then
|
||||
changeWindowVisibilityForColor(player.color, "playAreaGallery", false)
|
||||
-- hide the downloadWindow if visible
|
||||
elseif windowId == "playAreaGallery" then
|
||||
changeWindowVisibilityForColor(player.color, "downloadWindow", false)
|
||||
-- hide the playAreaGallery / downloadWindow if visible
|
||||
if windowId == "downloadWindow" or windowId == "playAreaGallery" then
|
||||
changeWindowVisibilityForColor(player.color, windowId, false)
|
||||
end
|
||||
|
||||
changeWindowVisibilityForColor(player.color, windowId)
|
||||
@ -1236,7 +1258,8 @@ function updatePreviewWindow()
|
||||
-- set default image if not defined
|
||||
if item.boxsize == nil or item.boxsize == "" or item.boxart == nil or item.boxart == "" then
|
||||
item.boxsize = "big"
|
||||
item.boxart = "https://steamusercontent-a.akamaihd.net/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/"
|
||||
item.boxart =
|
||||
"https://steamusercontent-a.akamaihd.net/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/"
|
||||
end
|
||||
|
||||
UI.setValue("previewTitle", item.name)
|
||||
@ -1883,6 +1906,479 @@ function onClick_notification(_, parameter)
|
||||
UI.hide("updateNotification")
|
||||
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
|
||||
---------------------------------------------------------
|
||||
|
@ -1,6 +1,6 @@
|
||||
local guidReferenceApi = require("core/GUIDReferenceApi")
|
||||
local searchLib = require("util/SearchLib")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local tokenManagerApi = require("core/token/TokenManagerApi")
|
||||
|
||||
-- Location connection directional options
|
||||
local BIDIRECTIONAL = 0
|
||||
@ -80,7 +80,7 @@ end
|
||||
function updateLocations(args)
|
||||
customDataHelper = getObjectFromGUID(args[1])
|
||||
if customDataHelper ~= nil then
|
||||
tokenManager.addLocationData(customDataHelper.getTable("LOCATIONS_DATA"))
|
||||
tokenManagerApi.addLocationData(customDataHelper.getTable("LOCATIONS_DATA"))
|
||||
end
|
||||
end
|
||||
|
||||
@ -102,7 +102,7 @@ function onCollisionEnter(collisionInfo)
|
||||
|
||||
-- check if we should spawn clues here and do so according to playercount
|
||||
if shouldSpawnTokens(object) then
|
||||
tokenManager.spawnForCard(object)
|
||||
tokenManagerApi.spawnForCard(object)
|
||||
end
|
||||
|
||||
-- If this card was being dragged, clear the dragging connections. A multi-drag/drop may send
|
||||
@ -192,7 +192,7 @@ end
|
||||
function shouldSpawnTokens(card)
|
||||
local metadata = JSON.decode(card.getGMNotes())
|
||||
if metadata == nil then
|
||||
return tokenManager.hasLocationData(card)
|
||||
return tokenManagerApi.hasLocationData(card)
|
||||
end
|
||||
return metadata.type == "Location"
|
||||
or metadata.type == "Enemy"
|
||||
|
@ -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
|
67
src/core/token/TokenManagerApi.ttslua
Normal file
67
src/core/token/TokenManagerApi.ttslua
Normal 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
|
@ -1,7 +1,7 @@
|
||||
require("playercards/CardsWithHelper")
|
||||
local playermatApi = require("playermat/PlayermatApi")
|
||||
local searchLib = require("util/SearchLib")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local tokenManagerApi = require("core/token/TokenManagerApi")
|
||||
|
||||
-- intentionally global
|
||||
hasXML = true
|
||||
@ -45,7 +45,7 @@ function add4()
|
||||
if clickableResourceCounter then
|
||||
clickableResourceCounter.call("updateVal", newCount)
|
||||
else
|
||||
tokenManager.spawnTokenGroup(self, "resource", newCount)
|
||||
tokenManagerApi.spawnTokenGroup(self, "resource", newCount)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -3,7 +3,7 @@ local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
|
||||
local guidReferenceApi = require("core/GUIDReferenceApi")
|
||||
local playermatApi = require("playermat/PlayermatApi")
|
||||
local searchLib = require("util/SearchLib")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local tokenManagerApi = require("core/token/TokenManagerApi")
|
||||
|
||||
-- intentionally global
|
||||
hasXML = true
|
||||
@ -88,7 +88,7 @@ function removeAndExtraAction()
|
||||
spawned.call("updateClassAndSymbol", { class = "Mystic", symbol = "Mystic" })
|
||||
spawned.addTag("Temporary")
|
||||
end
|
||||
tokenManager.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callback)
|
||||
tokenManagerApi.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callback)
|
||||
end
|
||||
|
||||
function elderSignAbility()
|
||||
|
@ -6,7 +6,7 @@ local mythosAreaApi = require("core/MythosAreaApi")
|
||||
local navigationOverlayApi = require("core/NavigationOverlayApi")
|
||||
local searchLib = require("util/SearchLib")
|
||||
local tokenChecker = require("core/token/TokenChecker")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local tokenManagerApi = require("core/token/TokenManagerApi")
|
||||
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
|
||||
|
||||
-- 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)
|
||||
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
|
||||
elseif obj.type == "Deck" and forcedLearning == false then
|
||||
-- check decks for forced learning
|
||||
@ -1103,7 +1103,7 @@ function spawnTokensFor(object)
|
||||
extraUses["Charge"] = 1
|
||||
end
|
||||
|
||||
tokenManager.spawnForCard(object, extraUses)
|
||||
tokenManagerApi.spawnForCard(object, extraUses)
|
||||
end
|
||||
|
||||
function onCollisionEnter(collisionInfo)
|
||||
@ -1243,11 +1243,13 @@ function maybeUpdateActiveInvestigator(card)
|
||||
-- spawn three regular action tokens (investigator specific one in the bottom spot)
|
||||
for i = 1, 3 do
|
||||
local pos = self.positionToWorld(Vector(-1.54 + i * 0.17, 0, -0.28)):add(Vector(0, 0.2, 0))
|
||||
|
||||
tokenManager.spawnToken(pos, "universalActionAbility", self.getRotation(),
|
||||
tokenManagerApi.spawnToken(pos, "universalActionAbility", self.getRotation(),
|
||||
function(spawned)
|
||||
spawned.call("updateClassAndSymbol",
|
||||
{ class = activeInvestigatorData.class, symbol = activeInvestigatorData.class })
|
||||
{
|
||||
class = activeInvestigatorData.class,
|
||||
symbol = activeInvestigatorData.class
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
@ -1280,7 +1282,7 @@ function maybeUpdateActiveInvestigator(card)
|
||||
local localSpawnPos = tokenSpawnPos[type][count[type]]
|
||||
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)
|
||||
spawned.call("updateClassAndSymbol", { class = activeInvestigatorData.class, symbol = str })
|
||||
end)
|
||||
@ -1466,7 +1468,7 @@ function clickableClues(showCounter)
|
||||
local pos = self.positionToWorld({ x = -1.12, y = 0.05, z = 0.7 })
|
||||
for i = 1, clueCount do
|
||||
pos.y = pos.y + 0.045 * i
|
||||
tokenManager.spawnToken(pos, "clue", self.getRotation())
|
||||
tokenManagerApi.spawnToken(pos, "clue", self.getRotation())
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1546,7 +1548,7 @@ end
|
||||
function updatePlayerCards(args)
|
||||
local customDataHelper = getObjectFromGUID(args[1])
|
||||
local playerCardData = customDataHelper.getTable("PLAYER_CARD_DATA")
|
||||
tokenManager.addPlayerCardData(playerCardData)
|
||||
tokenManagerApi.addPlayerCardData(playerCardData)
|
||||
end
|
||||
|
||||
-- returns the colored steam name or color
|
||||
|
@ -1,8 +1,8 @@
|
||||
local playermatApi = require("playermat/PlayermatApi")
|
||||
local searchLib = require("util/SearchLib")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local TOKEN_INDEX = {}
|
||||
local tokenManagerApi = require("core/token/TokenManagerApi")
|
||||
|
||||
local TOKEN_INDEX = {}
|
||||
TOKEN_INDEX[1] = "universalActionAbility"
|
||||
TOKEN_INDEX[3] = "resourceCounter"
|
||||
TOKEN_INDEX[4] = "damage"
|
||||
@ -75,7 +75,7 @@ function onScriptingButtonDown(index, playerColor)
|
||||
end
|
||||
end
|
||||
|
||||
tokenManager.spawnToken(position, tokenType, rotation, callback)
|
||||
tokenManagerApi.spawnToken(position, tokenType, rotation, callback)
|
||||
end
|
||||
|
||||
-- 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 match then
|
||||
tokenManager.maybeReplenishCard(card, metadata.uses)
|
||||
tokenManagerApi.maybeReplenishCard(card, metadata.uses)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
Loading…
Reference in New Issue
Block a user