Code commit for token spawn refactoring
This commit is contained in:
parent
ff7ed57bca
commit
7d820601a9
@ -202,8 +202,7 @@ end
|
||||
-- Returns the simple name of a card given its ID. This will find the card and strip any trailing
|
||||
-- SCED-specific suffixes such as (Taboo) or (Level)
|
||||
function getCardName(cardId)
|
||||
local configuration = getConfiguration()
|
||||
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
||||
local allCardsBag = getObjectFromGUID("15bb07")
|
||||
local card = allCardsBag.call("getCardById", { id = cardId })
|
||||
if (card ~= nil) then
|
||||
local name = card.data.Nickname
|
||||
|
@ -1,3 +1,5 @@
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
|
||||
---------------------------------------------------------
|
||||
-- general setup
|
||||
---------------------------------------------------------
|
||||
@ -331,32 +333,11 @@ end
|
||||
-- token spawning
|
||||
---------------------------------------------------------
|
||||
|
||||
-- DEPRECATED. Use TokenManager instead.
|
||||
-- Spawns a single token.
|
||||
---@param params Table. Array with arguments to the method. 1 = position, 2 = type, 3 = rotation
|
||||
function spawnToken(params)
|
||||
local position = params[1]
|
||||
local tokenType = params[2]
|
||||
local rotation = params[3] or {0, 270, 0}
|
||||
local tokenData = TOKEN_DATA[tokenType]
|
||||
|
||||
if tokenData == nil then
|
||||
error("no token data found for '" .. tokenType .. "'")
|
||||
end
|
||||
|
||||
local token = spawnObject({
|
||||
type = 'Custom_Token',
|
||||
position = position,
|
||||
rotation = rotation
|
||||
})
|
||||
|
||||
token.setCustomObject({
|
||||
image = tokenData['image'],
|
||||
thickness = 0.3,
|
||||
merge_distance = 5,
|
||||
stackable = true
|
||||
})
|
||||
|
||||
token.use_snap_points = false
|
||||
token.scale(tokenData['scale'])
|
||||
return token
|
||||
return tokenManager.spawnToken(params[1], params[2], params[3])
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
@ -614,7 +595,7 @@ end
|
||||
-- Content Importing and XML functions
|
||||
---------------------------------------------------------
|
||||
|
||||
local source_repo = 'https://raw.githubusercontent.com/seth-sced/loadable-objects/main'
|
||||
local source_repo = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main'
|
||||
local library = nil
|
||||
local request_obj
|
||||
|
||||
|
@ -1,4 +1,14 @@
|
||||
local playAreaApi = require("core/PlayAreaApi")
|
||||
local tokenSpawnTracker = require("core/token/TokenSpawnTrackerApi")
|
||||
|
||||
local ENCOUNTER_DECK_AREA = {
|
||||
upperLeft = { x = 0.9, z = 0.42 },
|
||||
lowerRight = { x = 0.86, z = 0.38 },
|
||||
}
|
||||
local ENCOUNTER_DISCARD_AREA = {
|
||||
upperLeft = { x = 1.62, z = 0.42 },
|
||||
lowerRight = { x = 1.58, z = 0.38 },
|
||||
}
|
||||
|
||||
local currentScenario
|
||||
|
||||
@ -15,8 +25,7 @@ function onSave()
|
||||
})
|
||||
end
|
||||
|
||||
-- TTS event handler. Checks for a scenrio card, extracts the scenario name from the description,
|
||||
-- and fires it to the relevant listeners.
|
||||
-- TTS event handler. Handles scenario name event triggering and encounter card token resets.
|
||||
function onCollisionEnter(collisionInfo)
|
||||
local object = collisionInfo.collision_object
|
||||
if object.getName() == "Scenario" then
|
||||
@ -25,9 +34,38 @@ function onCollisionEnter(collisionInfo)
|
||||
fireScenarioChangedEvent()
|
||||
end
|
||||
end
|
||||
|
||||
local localPos = self.positionToLocal(object.getPosition())
|
||||
if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then
|
||||
tokenSpawnTracker.resetTokensSpawned(object.getGUID())
|
||||
end
|
||||
end
|
||||
|
||||
-- Listens for cards entering the encounter deck or encounter discard, and resets the spawn state
|
||||
-- for the cards when they do.
|
||||
function onObjectEnterContainer(container, object)
|
||||
Wait.frames(function() resetTokensIfInDeckZone(container, object) end, 1)
|
||||
end
|
||||
|
||||
function resetTokensIfInDeckZone(container, object)
|
||||
local localPos = self.positionToLocal(container.getPosition())
|
||||
if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then
|
||||
tokenSpawnTracker.resetTokensSpawned(object.getGUID())
|
||||
end
|
||||
end
|
||||
|
||||
function fireScenarioChangedEvent()
|
||||
log("Firing")
|
||||
playAreaApi.onScenarioChanged(currentScenario)
|
||||
end
|
||||
|
||||
-- Simple method to check if the given point is in a specified area. Local use only,
|
||||
---@param point Vector. Point to check, only x and z values are relevant
|
||||
---@param bounds Table. Defined area to see if the point is within. See MAIN_PLAY_AREA for sample
|
||||
-- bounds definition.
|
||||
---@return Boolean. True if the point is in the area defined by bounds
|
||||
function inArea(point, bounds)
|
||||
return (point.x < bounds.upperLeft.x
|
||||
and point.x > bounds.lowerRight.x
|
||||
and point.z < bounds.upperLeft.z
|
||||
and point.z > bounds.lowerRight.z)
|
||||
end
|
||||
|
@ -1,3 +1,4 @@
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
---------------------------------------------------------
|
||||
-- general setup
|
||||
---------------------------------------------------------
|
||||
@ -23,8 +24,6 @@ local SHIFT_EXCLUSION = {
|
||||
local INVESTIGATOR_COUNTER_GUID = "f182ee"
|
||||
local PLAY_AREA_ZONE_GUID = "a2f932"
|
||||
|
||||
local clueData = {}
|
||||
spawnedLocationGUIDs = {}
|
||||
local currentScenario
|
||||
|
||||
---------------------------------------------------------
|
||||
@ -33,7 +32,6 @@ local currentScenario
|
||||
|
||||
function onSave()
|
||||
return JSON.encode({
|
||||
spawnedLocs = spawnedLocationGUIDs,
|
||||
currentScenario = currentScenario
|
||||
})
|
||||
end
|
||||
@ -41,21 +39,8 @@ end
|
||||
function onLoad(saveState)
|
||||
-- records locations we have spawned clues for
|
||||
local saveData = JSON.decode(saveState) or {}
|
||||
spawnedLocationGUIDs = saveData.spawnedLocs or { }
|
||||
currentScenario = saveData.currentScenario
|
||||
|
||||
local TOKEN_DATA = Global.getTable('TOKEN_DATA')
|
||||
clueData = {
|
||||
thickness = 0.1,
|
||||
stackable = true,
|
||||
type = 2,
|
||||
image = TOKEN_DATA.clue.image,
|
||||
image_bottom = TOKEN_DATA.doom.image
|
||||
}
|
||||
|
||||
local dataHelper = getObjectFromGUID('708279')
|
||||
LOCATIONS = dataHelper.getTable('LOCATIONS_DATA')
|
||||
|
||||
self.interactable = DEBUG
|
||||
Wait.time(function() COLLISION_ENABLED = true end, 1)
|
||||
end
|
||||
@ -64,72 +49,13 @@ function log(message)
|
||||
if DEBUG then print(message) end
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- clue spawning
|
||||
---------------------------------------------------------
|
||||
|
||||
-- try the compound key then the name alone as default
|
||||
function getLocation(object)
|
||||
return LOCATIONS[object.getName() .. '_' .. object.getGUID()] or LOCATIONS[object.getName()]
|
||||
end
|
||||
|
||||
-- Return the number of clues to spawn on this location
|
||||
function getClueCount(object, isFaceDown, playerCount)
|
||||
local details = getLocation(object)
|
||||
if details == nil then
|
||||
error('attempted to get clue for unexpected object: ' .. object.getName())
|
||||
end
|
||||
|
||||
log(object.getName() .. ' : ' .. details['type'] .. ' : ' .. details['value'] .. ' : ' .. details['clueSide'])
|
||||
if ((isFaceDown and details['clueSide'] == 'back') or (not isFaceDown and details['clueSide'] == 'front')) then
|
||||
if details['type'] == 'fixed' then
|
||||
return details['value']
|
||||
elseif details['type'] == 'perPlayer' then
|
||||
return details['value'] * playerCount
|
||||
end
|
||||
error('unexpected location type: ' .. details['type'])
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function spawnCluesAtLocation(clueCount, object)
|
||||
if spawnedLocationGUIDs[object.getGUID()] ~= nil then
|
||||
error('tried to spawn clue for already spawned location:' .. object.getName())
|
||||
end
|
||||
|
||||
log('spawning clues for ' .. object.getName() .. '_' .. object.getGUID())
|
||||
log('player count is ' .. getInvestigatorCount() .. ', clue count is ' .. clueCount)
|
||||
|
||||
-- mark this location as spawned, can't happen again
|
||||
spawnedLocationGUIDs[object.getGUID()] = true
|
||||
|
||||
-- spawn clues (starting top right, moving to the next row after 4 clues)
|
||||
local pos = object.getPosition()
|
||||
for i = 1, clueCount do
|
||||
local row = math.floor(1 + (i - 1) / 4)
|
||||
local column = (i - 1) % 4
|
||||
spawnClue({ pos.x + 1.5 - 0.55 * row, pos.y, pos.z - 0.825 + 0.55 * column })
|
||||
end
|
||||
end
|
||||
|
||||
function spawnClue(position)
|
||||
local token = spawnObject({
|
||||
position = position,
|
||||
rotation = { 3.88, -90, 0.24 },
|
||||
type = 'Custom_Tile'
|
||||
})
|
||||
|
||||
token.setCustomObject(clueData)
|
||||
token.scale { 0.25, 1, 0.25 }
|
||||
token.use_snap_points = false
|
||||
end
|
||||
|
||||
-- Called by Custom Data Helpers to push their location data into the Data Helper. This adds the
|
||||
-- data to the local token manager instance.
|
||||
---@param args Table Single-value array holding the GUID of the Custom Data Helper making the call
|
||||
function updateLocations(args)
|
||||
custom_data_helper_guid = args[1]
|
||||
local custom_data_helper = getObjectFromGUID(args[1])
|
||||
|
||||
for k, v in pairs(custom_data_helper.getTable("LOCATIONS_DATA")) do
|
||||
LOCATIONS[k] = v
|
||||
local customDataHelper = getObjectFromGUID(args[1])
|
||||
if customDataHelper ~= nil then
|
||||
tokenManager.addLocationData(customDataHelper.getTable("LOCATIONS_DATA"))
|
||||
end
|
||||
end
|
||||
|
||||
@ -137,13 +63,23 @@ function onCollisionEnter(collision_info)
|
||||
if not COLLISION_ENABLED then return end
|
||||
|
||||
-- check if we should spawn clues here and do so according to playercount
|
||||
local object = collision_info.collision_object
|
||||
if getLocation(object) ~= nil and spawnedLocationGUIDs[object.getGUID()] == nil then
|
||||
local clueCount = getClueCount(object, object.is_face_down, getInvestigatorCount())
|
||||
if clueCount > 0 then spawnCluesAtLocation(clueCount, object) end
|
||||
local card = collision_info.collision_object
|
||||
if shouldSpawnTokens(card) then
|
||||
tokenManager.spawnForCard(card)
|
||||
end
|
||||
end
|
||||
|
||||
function shouldSpawnTokens(card)
|
||||
local metadata = JSON.decode(card.getGMNotes())
|
||||
if metadata == nil then
|
||||
return tokenManager.hasLocationData(card) ~= nil
|
||||
end
|
||||
return metadata.type == "Location"
|
||||
or metadata.type == "Enemy"
|
||||
or metadata.type == "Treachery"
|
||||
or metadata.weakness
|
||||
end
|
||||
|
||||
-- Move all contents on the play area (cards, tokens, etc) one slot in the given direction. Certain
|
||||
-- fixed objects will be ignored, as will anything the player has tagged with
|
||||
-- 'displacement_excluded'
|
||||
|
446
src/core/token/TokenManager.ttslua
Normal file
446
src/core/token/TokenManager.ttslua
Normal file
@ -0,0 +1,446 @@
|
||||
do
|
||||
local tokenSpawnTracker = require("core/token/TokenSpawnTrackerApi")
|
||||
local playAreaApi = require("core/PlayAreaApi")
|
||||
|
||||
local PLAYER_CARD_TOKEN_OFFSETS = {
|
||||
[1] = {
|
||||
Vector(0, 8, -0.2)
|
||||
},
|
||||
[2] = {
|
||||
Vector(0.4, 20, -0.2),
|
||||
Vector(-0.4, 20, -0.2)
|
||||
},
|
||||
[3] = {
|
||||
Vector(0, 8, -0.9),
|
||||
Vector(0.4, 8, -0.2),
|
||||
Vector(-0.4, 8, -0.2)
|
||||
},
|
||||
[4] = {
|
||||
Vector(0.4, 10, -0.9),
|
||||
Vector(-0.4, 10, -0.9),
|
||||
Vector(0.4, 10, -0.2),
|
||||
Vector(-0.4, 10, -0.2)
|
||||
},
|
||||
[5] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.4, 3, -0.2),
|
||||
Vector(-0.4, 3, -0.2)
|
||||
},
|
||||
[6] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2)
|
||||
},
|
||||
[7] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2),
|
||||
Vector(0, 3, 0.5)
|
||||
},
|
||||
[8] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2),
|
||||
Vector(-0.35, 3, 0.5),
|
||||
Vector(0.35, 3, 0.5)
|
||||
},
|
||||
[9] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2),
|
||||
Vector(0.7, 3, 0.5),
|
||||
Vector(0, 3, 0.5),
|
||||
Vector(-0.7, 3, 0.5)
|
||||
},
|
||||
[10] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2),
|
||||
Vector(0.7, 3, 0.5),
|
||||
Vector(0, 3, 0.5),
|
||||
Vector(-0.7, 3, 0.5),
|
||||
Vector(0, 3, 1.2)
|
||||
},
|
||||
[11] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2),
|
||||
Vector(0.7, 3, 0.5),
|
||||
Vector(0, 3, 0.5),
|
||||
Vector(-0.7, 3, 0.5),
|
||||
Vector(-0.35, 3, 1.2),
|
||||
Vector(0.35, 3, 1.2)
|
||||
},
|
||||
[12] = {
|
||||
Vector(0.7, 3, -0.9),
|
||||
Vector(0, 3, -0.9),
|
||||
Vector(-0.7, 3, -0.9),
|
||||
Vector(0.7, 3, -0.2),
|
||||
Vector(0, 3, -0.2),
|
||||
Vector(-0.7, 3, -0.2),
|
||||
Vector(0.7, 3, 0.5),
|
||||
Vector(0, 3, 0.5),
|
||||
Vector(-0.7, 3, 0.5),
|
||||
Vector(0.7, 3, 1.2),
|
||||
Vector(0, 3, 1.2),
|
||||
Vector(-0.7, 3, 1.2)
|
||||
}
|
||||
}
|
||||
|
||||
local SOURCE_BAG_GUIDS = {
|
||||
damage = "480bda",
|
||||
horror = "c3ecf4",
|
||||
resource = "9fadf9",
|
||||
doom = "47ffc3",
|
||||
clue = "31fa39",
|
||||
}
|
||||
|
||||
local DATA_HELPER_GUID = "708279"
|
||||
|
||||
local playerCardData
|
||||
local locationData
|
||||
|
||||
local TokenManager = { }
|
||||
local internal = { }
|
||||
|
||||
-- Spawns tokens for the card. This function is built to just throw a card at it and let it do
|
||||
-- the work. It will check to see if the card has already spawned, find appropriate data from
|
||||
-- either the uses metadata or the Data Helper, and spawn the tokens.
|
||||
---@param card Object Card to maybe spawn tokens for
|
||||
---@param extraUses Table A table of <use type>=<count> which will modify the number of tokens
|
||||
--- spawned for that type. e.g. Akachi's playmat should pass "Charge"=1
|
||||
TokenManager.spawnForCard = function(card, extraUses)
|
||||
if tokenSpawnTracker.hasSpawnedTokens(card.getGUID()) then
|
||||
return
|
||||
end
|
||||
local metadata = JSON.decode(card.getGMNotes())
|
||||
if metadata ~= nil then
|
||||
internal.spawnTokensFromUses(card, extraUses)
|
||||
else
|
||||
internal.spawnTokensFromDataHelper(card)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawns a set of tokens on the given card.
|
||||
---@param card Object Card to spawn tokens on
|
||||
---@param tokenType String type of token to spawn, valid values are "damage", "horror",
|
||||
-- "resource", "doom", or "clue"
|
||||
---@param tokenCount 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 An offset for the z-value of this group of tokens
|
||||
TokenManager.spawnTokenGroup = function(card, tokenType, tokenCount, shiftDown)
|
||||
if tokenType == "damage" or tokenType == "horror" then
|
||||
TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown)
|
||||
else
|
||||
TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawns a single counter token and sets the value to tokenValue. Used for damage and horror
|
||||
-- tokens.
|
||||
---@param card Object Card to spawn tokens on
|
||||
---@param tokenType String type of token to spawn, valid values are "damage" and "horror". Other
|
||||
-- types should use spawnMultipleTokens()
|
||||
---@param tokenValue Value to set the damage/horror to
|
||||
---@param shiftDown An offset for the z-value of this group of tokens
|
||||
TokenManager.spawnCounterToken = function(card, tokenType, tokenValue, shiftDown)
|
||||
if tokenCount < 1 or tokenCount > 50 then
|
||||
return
|
||||
end
|
||||
local offsets = PLAYER_CARD_TOKEN_OFFSETS[1]
|
||||
if shiftDown ~= nil then
|
||||
-- Copy the offsets to make sure we don't change the static values
|
||||
local baseOffsets = offsets
|
||||
offsets = { }
|
||||
for i, baseOffset in ipairs(baseOffsets) do
|
||||
offsets[i] = baseOffset
|
||||
offsets[i][3] = offsets[i][3] + shiftDown
|
||||
end
|
||||
end
|
||||
local pos = card.positionToWorld(offsets[1])
|
||||
pos.y = card.getPosition().y + 0.15
|
||||
TokenManager.spawnToken(pos, tokenType, card.getRotation(), function(spawned)
|
||||
spawned.setState(tokenValue)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Spawns a number of tokens.
|
||||
---@param tokenType String type of token to spawn, valid values are resource", "doom", or "clue".
|
||||
-- Other types should use spawnCounterToken()
|
||||
---@param tokenCount 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 An offset for the z-value of this group of tokens
|
||||
TokenManager.spawnMultipleTokens = function(card, tokenType, tokenCount, shiftDown)
|
||||
if tokenCount < 1 or tokenCount > 12 then
|
||||
return
|
||||
end
|
||||
local offsets
|
||||
if tokenType == "clue" then
|
||||
offsets = internal.buildClueOffsets(card, tokenCount)
|
||||
else
|
||||
offsets = { }
|
||||
for i = 1, tokenCount do
|
||||
offsets[i] = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[tokenCount][i])
|
||||
-- Fix the y-position for the spawn, since positionToWorld considers rotation which can
|
||||
-- have bad results for face up/down differences
|
||||
offsets[i].y = card.getPosition().y + 0.15
|
||||
end
|
||||
end
|
||||
-- end
|
||||
if shiftDown ~= nil then
|
||||
-- Copy the offsets to make sure we don't change the static values
|
||||
local baseOffsets = offsets
|
||||
offsets = { }
|
||||
for i, baseOffset in ipairs(baseOffsets) do
|
||||
offsets[i] = baseOffset
|
||||
offsets[i][3] = offsets[i][3] + shiftDown
|
||||
end
|
||||
end
|
||||
if offsets == nil then
|
||||
error("couldn't find offsets for " .. tokenCount .. ' tokens')
|
||||
end
|
||||
|
||||
for i = 1, tokenCount do
|
||||
TokenManager.spawnToken(offsets[i], tokenType, card.getRotation())
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawns a single token at the given global position by copying it from the template bag.
|
||||
---@param position Global position to spawn the token
|
||||
---@param tokenType String type of token to spawn, valid values are "damage", "horror",
|
||||
-- "resource", "doom", or "clue"
|
||||
---@param rotation Vector Rotation to be used for the new token. Only the y-value will be used,
|
||||
-- x and z will use the default rotation from the source bag
|
||||
---@param callback A callback function triggered after the new token is spawned
|
||||
TokenManager.spawnToken = function(position, tokenType, rotation, callback)
|
||||
if SOURCE_BAG_GUIDS[tokenType] == nil then
|
||||
error("Unknown token type '" .. tokenType .. "'")
|
||||
return
|
||||
end
|
||||
local sourceBag = getObjectFromGUID(SOURCE_BAG_GUIDS[tokenType])
|
||||
if sourceBag == nil then
|
||||
error("No token source for '" .. tokenType .. "'")
|
||||
return
|
||||
end
|
||||
-- All the source bags are infinite, so we just grab the first object
|
||||
local tokenTemplate = sourceBag.getData().ContainedObjects[1]
|
||||
-- Take ONLY the Y-value for rotation, so we don't flip the token coming out of the bag
|
||||
local tokenRotation = rotation or { x = 0, y = 270, z = 0 }
|
||||
tokenTemplate.Transform.rotY = tokenRotation.y
|
||||
log("x=" .. tokenTemplate.Transform.rotX .. ",y=" .. tokenTemplate.Transform.rotY .. "z=" .. tokenTemplate.Transform.rotZ)
|
||||
return spawnObjectData({
|
||||
data = tokenTemplate,
|
||||
position = position,
|
||||
callback_function = callback
|
||||
})
|
||||
end
|
||||
|
||||
-- Delegate function to the token spawn tracker. Exists to avoid circular dependencies in some
|
||||
-- callers.
|
||||
---@param card Object Card object to reset the tokens for
|
||||
TokenManager.resetTokensSpawned = function(card)
|
||||
tokenSpawnTracker.resetTokensSpawned(card.getGUID())
|
||||
end
|
||||
|
||||
-- Pushes new player card data into the local copy of the Data Helper player data.
|
||||
---@param dataTable Table Key/Value pairs following the DataHelper style
|
||||
TokenManager.addPlayerCardData = function(dataTable)
|
||||
internal.initDataHelperData()
|
||||
for k, v in pairs(dataTable) do
|
||||
playerCardData[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Pushes new location data into the local copy of the Data Helper location data.
|
||||
---@param dataTable Table Key/Value pairs following the DataHelper style
|
||||
TokenManager.addLocationData = function(dataTable)
|
||||
internal.initDataHelperData()
|
||||
for k, v in pairs(dataTable) do
|
||||
locationData[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Checks to see if the given card has location data in the DataHelper
|
||||
---@param card Object Card to check for data
|
||||
---@return Boolean True if this card has data in the helper, false otherwise
|
||||
TokenManager.hasLocationData = function(card)
|
||||
return internal.getLocationData(card) ~= nil
|
||||
end
|
||||
|
||||
-- Copies the data from the DataHelper. Will only happen once.
|
||||
internal.initDataHelperData = function()
|
||||
if playerCardData ~= nil then
|
||||
return
|
||||
end
|
||||
local dataHelper = getObjectFromGUID(DATA_HELPER_GUID)
|
||||
playerCardData = dataHelper.getTable('PLAYER_CARD_DATA')
|
||||
locationData = dataHelper.getTable('LOCATIONS_DATA')
|
||||
end
|
||||
|
||||
-- Spawn tokens for a card based on the uses metadata. This will consider the face up/down state
|
||||
-- of the card for both locations and standard cards.
|
||||
---@param card Object Card to maybe spawn tokens for
|
||||
---@param extraUses Table A table of <use type>=<count> which will modify the number of tokens
|
||||
--- spawned for that type. e.g. Akachi's playmat should pass "Charge"=1
|
||||
internal.spawnTokensFromUses = function(card, extraUses)
|
||||
local uses = internal.getUses(card)
|
||||
if uses == nil then
|
||||
return
|
||||
end
|
||||
local type = nil
|
||||
local token = nil
|
||||
local tokenCount = 0
|
||||
-- Uses structure underwent a breaking change in 2.4.0, have to check to see if this is
|
||||
-- a single entry or an array. This is ugly and duplicated, but impossible to replicate the
|
||||
-- multi-spawn vs. single spawn otherwise. TODO: Clean this up when 2.4.0 has been out long
|
||||
-- enough that saved decks don't have old data
|
||||
if uses.count != nil then
|
||||
type = cardMetadata.uses.type
|
||||
token = cardMetadata.uses.token
|
||||
tokenCount = cardMetadata.uses.count
|
||||
if extraUses ~= nil and extraUses[type] ~= nil then
|
||||
tokenCount = tokenCount + extraUses[type]
|
||||
end
|
||||
log("Spawning single use tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
|
||||
TokenManager.spawnTokenGroup(card, token, tokenCount)
|
||||
else
|
||||
for i, useInfo in ipairs(uses) do
|
||||
type = useInfo.type
|
||||
token = useInfo.token
|
||||
tokenCount = (useInfo.count or 0)
|
||||
+ (useInfo.countPerInvestigator or 0) * playAreaApi.getInvestigatorCount()
|
||||
if extraUses ~= nil and extraUses[type] ~= nil then
|
||||
tokenCount = tokenCount + extraUses[type]
|
||||
end
|
||||
log("Spawning use array tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
|
||||
-- Shift each spawned group after the first down so they don't pile on each other
|
||||
TokenManager.spawnTokenGroup(card, token, tokenCount, (i - 1) * 0.6)
|
||||
end
|
||||
end
|
||||
tokenSpawnTracker.markTokensSpawned(card.getGUID())
|
||||
end
|
||||
|
||||
-- Spawn tokens for a card based on the data helper data. This will consider the face up/down state
|
||||
-- of the card for both locations and standard cards.
|
||||
---@param card Object Card to maybe spawn tokens for
|
||||
internal.spawnTokensFromDataHelper = function(card)
|
||||
internal.initDataHelperData()
|
||||
local playerData = internal.getPlayerCardData(card)
|
||||
if playerData ~= nil then
|
||||
internal.spawnPlayerCardTokensFromDataHelper(card, playerData)
|
||||
end
|
||||
local locationData = internal.getLocationData(card)
|
||||
if locationData ~= nil then
|
||||
internal.spawnLocationTokensFromDataHelper(card, locationData)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawn tokens for a player card using data retrieved from the Data Helper.
|
||||
---@param card Object Card to maybe spawn tokens for
|
||||
---@param playerData Table Player card data structure retrieved from the DataHelper. Should be
|
||||
-- the right data for this card.
|
||||
internal.spawnPlayerCardTokensFromDataHelper = function(card, playerData)
|
||||
token = playerData.tokenType
|
||||
tokenCount = playerData.tokenCount
|
||||
log("Spawning data helper tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
|
||||
TokenManager.spawnTokenGroup(card, token, tokenCount)
|
||||
tokenSpawnTracker.markTokensSpawned(card.getGUID())
|
||||
end
|
||||
|
||||
-- Spawn tokens for a location using data retrieved from the Data Helper.
|
||||
---@param card Object Card to maybe spawn tokens for
|
||||
---@param playerData Table Location data structure retrieved from the DataHelper. Should be
|
||||
-- the right data for this card.
|
||||
internal.spawnLocationTokensFromDataHelper = function(card, locationData)
|
||||
local clueCount = internal.getClueCountFromData(card, locationData)
|
||||
if clueCount > 0 then
|
||||
TokenManager.spawnTokenGroup(card, "clue", clueCount)
|
||||
tokenSpawnTracker.markTokensSpawned(card.getGUID())
|
||||
end
|
||||
end
|
||||
|
||||
internal.getPlayerCardData = function(card)
|
||||
return playerCardData[card.getName()..':'..card.getDescription()]
|
||||
or playerCardData[card.getName()]
|
||||
end
|
||||
|
||||
internal.getLocationData = function(card)
|
||||
return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()]
|
||||
end
|
||||
|
||||
internal.getClueCountFromData = function(card, locationData)
|
||||
-- Return the number of clues to spawn on this location
|
||||
if locationData == nil then
|
||||
error('attempted to get clue for unexpected object: ' .. card.getName())
|
||||
return 0
|
||||
end
|
||||
|
||||
log(card.getName() .. ' : ' .. locationData.type .. ' : ' .. locationData.value .. ' : ' .. locationData.clueSide)
|
||||
if ((card.is_face_down and locationData.clueSide == 'back')
|
||||
or (not card.is_face_down and locationData.clueSide == 'front')) then
|
||||
if locationData.type == 'fixed' then
|
||||
return locationData.value
|
||||
elseif locationData.type == 'perPlayer' then
|
||||
return locationData.value * 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 Object Card to pull the uses from
|
||||
internal.getUses = function(card)
|
||||
local metadata = JSON.decode(card.getGMNotes()) or { }
|
||||
if metadata.type == "Location" then
|
||||
if card.is_face_down and metadata.locationBack ~= nil then
|
||||
return metadata.locationBack.uses
|
||||
elseif not card.is_face_down and metadata.locationFront ~= nil then
|
||||
return metadata.locationFront.uses
|
||||
end
|
||||
elseif not card.is_face_down then
|
||||
return metadata.uses
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Dynamically create positions for clues on a card.
|
||||
---@param card Object Card the clues will be placed on
|
||||
---@param count Integer How many clues?
|
||||
---@return Table Array of global positions to spawn the clues at
|
||||
internal.buildClueOffsets = function(card, count)
|
||||
local pos = card.getPosition()
|
||||
local cluePositions = { }
|
||||
for i = 1, count do
|
||||
local row = math.floor(1 + (i - 1) / 4)
|
||||
local column = (i - 1) % 4
|
||||
table.insert(cluePositions, Vector(pos.x + 1.5 - 0.55 * row, pos.y + 0.15, pos.z - 0.825 + 0.55 * column))
|
||||
end
|
||||
|
||||
return cluePositions
|
||||
end
|
||||
|
||||
return TokenManager
|
||||
|
||||
end
|
85
src/core/token/TokenSpawnTracker.ttslua
Normal file
85
src/core/token/TokenSpawnTracker.ttslua
Normal file
@ -0,0 +1,85 @@
|
||||
local spawnedCardGuids = { }
|
||||
|
||||
local HAND_ZONES = { }
|
||||
HAND_ZONES["c506bf"] = true -- White
|
||||
HAND_ZONES["67ce9a"] = true -- Green
|
||||
HAND_ZONES["cbc751"] = true -- Orange
|
||||
HAND_ZONES["57c22c"] = true -- Red
|
||||
|
||||
function onLoad(saveState)
|
||||
if saveState ~= nil then
|
||||
local saveTable = JSON.decode(saveState) or { }
|
||||
spawnedCardGuids = saveTable.cards or { }
|
||||
end
|
||||
|
||||
createResetMenuItems()
|
||||
end
|
||||
|
||||
function onSave()
|
||||
return JSON.encode({
|
||||
cards = spawnedCardGuids
|
||||
})
|
||||
end
|
||||
|
||||
function createResetMenuItems()
|
||||
self.addContextMenuItem("Reset All", resetAll)
|
||||
self.addContextMenuItem("Reset Locations", resetAllLocations)
|
||||
self.addContextMenuItem("Reset Player Cards", resetAllAssetAndEvents)
|
||||
end
|
||||
|
||||
function hasSpawnedTokens(cardGuid)
|
||||
return spawnedCardGuids[cardGuid] == true
|
||||
end
|
||||
|
||||
function markTokensSpawned(cardGuid)
|
||||
spawnedCardGuids[cardGuid] = true
|
||||
end
|
||||
|
||||
function resetTokensSpawned(cardGuid)
|
||||
spawnedCardGuids[cardGuid] = nil
|
||||
end
|
||||
|
||||
function resetAllAssetAndEvents()
|
||||
local resetList = { }
|
||||
for cardGuid, _ in pairs(spawnedCardGuids) do
|
||||
local card = getObjectFromGUID(cardGuid)
|
||||
if card ~= nil then
|
||||
local cardMetadata = JSON.decode(card.getGMNotes()) or { }
|
||||
-- Check this by type rather than the PlayerCard tag so we don't reset weaknesses
|
||||
if cardMetadata.type == "Asset" or cardMetadata.type == "Event" then
|
||||
resetList[cardGuid] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for cardGuid, _ in pairs(resetList) do
|
||||
spawnedCardGuids[cardGuid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function resetAllLocations()
|
||||
local resetList = { }
|
||||
for cardGuid, _ in pairs(spawnedCardGuids) do
|
||||
local card = getObjectFromGUID(cardGuid)
|
||||
if card ~= nil then
|
||||
local cardMetadata = JSON.decode(card.getGMNotes()) or { }
|
||||
-- Check this by type rather than the PlayerCard tag so we don't reset weaknesses
|
||||
if cardMetadata.type == "Location" then
|
||||
resetList[cardGuid] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for cardGuid, _ in pairs(resetList) do
|
||||
spawnedCardGuids[cardGuid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function resetAll()
|
||||
spawnedCardGuids = { }
|
||||
end
|
||||
|
||||
-- Listener to reset card token spawns when they enter a hand.
|
||||
function onObjectEnterZone(zone, enterObject)
|
||||
if HAND_ZONES[zone.getGUID()] then
|
||||
resetTokensSpawned(enterObject.getGUID())
|
||||
end
|
||||
end
|
31
src/core/token/TokenSpawnTrackerApi.ttslua
Normal file
31
src/core/token/TokenSpawnTrackerApi.ttslua
Normal file
@ -0,0 +1,31 @@
|
||||
do
|
||||
local TokenSpawnTracker = { }
|
||||
|
||||
local SPAWN_TRACKER_GUID = "e3ffc9"
|
||||
|
||||
TokenSpawnTracker.hasSpawnedTokens = function(cardGuid)
|
||||
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("hasSpawnedTokens", cardGuid)
|
||||
end
|
||||
|
||||
TokenSpawnTracker.markTokensSpawned = function(cardGuid)
|
||||
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("markTokensSpawned", cardGuid)
|
||||
end
|
||||
|
||||
TokenSpawnTracker.resetTokensSpawned = function(cardGuid)
|
||||
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetTokensSpawned", cardGuid)
|
||||
end
|
||||
|
||||
TokenSpawnTracker.resetAllAssetAndEvents = function()
|
||||
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllAssetAndEvents")
|
||||
end
|
||||
|
||||
TokenSpawnTracker.resetAllLocations = function()
|
||||
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllLocations")
|
||||
end
|
||||
|
||||
TokenSpawnTracker.resetAll = function()
|
||||
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAll")
|
||||
end
|
||||
|
||||
return TokenSpawnTracker
|
||||
end
|
@ -1,5 +1,7 @@
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
|
||||
-- set true to enable debug logging and show Physics.cast()
|
||||
local DEBUG = false
|
||||
local DEBUG = true
|
||||
|
||||
-- we use this to turn off collision handling until onLoad() is complete
|
||||
local COLLISION_ENABLED = false
|
||||
@ -36,6 +38,16 @@ local INVESTIGATOR_AREA = {
|
||||
z = -0.0805,
|
||||
}
|
||||
}
|
||||
local THREAT_AREA = {
|
||||
upperLeft = {
|
||||
x = 1.53,
|
||||
z = -0.34
|
||||
},
|
||||
lowerRight = {
|
||||
x = -1.13,
|
||||
z = -0.92,
|
||||
}
|
||||
}
|
||||
|
||||
local PLAY_ZONE_ROTATION = self.getRotation()
|
||||
|
||||
@ -58,9 +70,6 @@ end
|
||||
|
||||
function onLoad(save_state)
|
||||
self.interactable = DEBUG
|
||||
DATA_HELPER = getObjectFromGUID('708279')
|
||||
PLAYER_CARDS = DATA_HELPER.getTable('PLAYER_CARD_DATA')
|
||||
PLAYER_CARD_TOKEN_OFFSETS = DATA_HELPER.getTable('PLAYER_CARD_TOKEN_OFFSETS')
|
||||
|
||||
TRASHCAN = getObjectFromGUID(TRASHCAN_GUID)
|
||||
STAT_TRACKER = getObjectFromGUID(STAT_TRACKER_GUID)
|
||||
@ -387,38 +396,6 @@ function shuffleDiscardIntoDeck()
|
||||
discardPile = nil
|
||||
end
|
||||
|
||||
function spawnTokenOn(object, offsets, tokenType)
|
||||
local tokenPosition = object.positionToWorld(offsets)
|
||||
spawnToken(tokenPosition, tokenType)
|
||||
end
|
||||
|
||||
-- Spawn a group of tokens of the given type on the object
|
||||
-- @param object Object to spawn the tokens on
|
||||
-- @param tokenType Type of token to be spawned
|
||||
-- @param tokenCount Number of tokens to spawn
|
||||
-- @param shiftDown Amount to shift this group down to avoid spawning multiple token groups on
|
||||
-- top of each other. Negative values are allowed, and will move the group up instead. This is
|
||||
-- a static value and is unaware of how many tokens were spawned previously; callers should
|
||||
-- ensure the proper shift.
|
||||
function spawnTokenGroup(object, tokenType, tokenCount, shiftDown)
|
||||
if (tokenCount < 1 or tokenCount > 12) then return end
|
||||
local offsets = PLAYER_CARD_TOKEN_OFFSETS[tokenCount]
|
||||
if shiftDown ~= nil then
|
||||
-- Copy the offsets to make sure we don't change the static values
|
||||
local baseOffsets = offsets
|
||||
offsets = { }
|
||||
for i, baseOffset in ipairs(baseOffsets) do
|
||||
offsets[i] = baseOffset
|
||||
offsets[i][3] = offsets[i][3] + shiftDown
|
||||
end
|
||||
end
|
||||
if offsets == nil then error("couldn't find offsets for " .. tokenCount .. ' tokens') end
|
||||
|
||||
for i = 1, tokenCount do
|
||||
spawnTokenOn(object, offsets[i], tokenType)
|
||||
end
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- playmat token spawning
|
||||
---------------------------------------------------------
|
||||
@ -457,133 +434,102 @@ function replenishTokens(card, count, replenish)
|
||||
|
||||
local newCount = foundTokens + replenish
|
||||
if newCount > count then newCount = count end
|
||||
spawnTokenGroup(card, "resource", newCount)
|
||||
tokenManager.spawnTokenGroup(card, "resource", newCount)
|
||||
end
|
||||
|
||||
function getPlayerCardData(object)
|
||||
return PLAYER_CARDS[object.getName()..':'..object.getDescription()] or PLAYER_CARDS[object.getName()]
|
||||
end
|
||||
|
||||
function shouldSpawnTokens(object)
|
||||
-- don't spawn tokens if in doubt, this should only ever happen onLoad and prevents respawns
|
||||
local spawned = DATA_HELPER.call('getSpawnedPlayerCardGuid', {object.getGUID()})
|
||||
local hasDataHelperData = getPlayerCardData(object)
|
||||
local cardMetadata = JSON.decode(object.getGMNotes()) or {}
|
||||
local hasUses = cardMetadata.uses ~= nil
|
||||
return not spawned and (hasDataHelperData or hasUses)
|
||||
end
|
||||
|
||||
function markSpawned(object)
|
||||
local saved = DATA_HELPER.call('setSpawnedPlayerCardGuid', {object.getGUID(), true})
|
||||
if not saved then error('attempt to mark player card spawned before data loaded') end
|
||||
end
|
||||
|
||||
-- contains position and amount of boxes for the upgradesheets that change uses
|
||||
-- Alchemical Distillation, Damning Testimony, Living Ink and Hyperphysical Shotcaster
|
||||
local customizationsTable = {
|
||||
["09040"] = {5, 2},
|
||||
["09059"] = {2, 2},
|
||||
["09079"] = {3, 2},
|
||||
["09119"] = {6, 4}
|
||||
}
|
||||
|
||||
function spawnTokensFor(object)
|
||||
local cardMetadata = JSON.decode(object.getGMNotes()) or {}
|
||||
local type = nil
|
||||
local token = nil
|
||||
local tokenCount = 0
|
||||
if cardMetadata.uses ~= nil then
|
||||
-- Uses structure underwent a breaking change in 2.4.0, have to check to see if this is
|
||||
-- a single entry or an array. This is ugly and duplicated, but impossible to replicate the
|
||||
-- multi-spawn vs. single spawn otherwise. TODO: Clean this up when 2.4.0 has been out long
|
||||
-- enough that saved decks don't have old data
|
||||
if cardMetadata.uses.count != nil then
|
||||
type = cardMetadata.uses.type
|
||||
token = cardMetadata.uses.token
|
||||
tokenCount = cardMetadata.uses.count
|
||||
if activeInvestigatorId == "03004" and type == "Charge" then tokenCount = tokenCount + 1 end
|
||||
|
||||
log("Spawning tokens for "..object.getName()..'['..object.getDescription()..']: '..tokenCount.."x "..token)
|
||||
spawnTokenGroup(object, token, tokenCount)
|
||||
else
|
||||
for i, useInfo in ipairs(cardMetadata.uses) do
|
||||
type = useInfo.type
|
||||
token = useInfo.token
|
||||
tokenCount = useInfo.count
|
||||
|
||||
-- additional uses for certain customizable cards (by checking the upgradesheets)
|
||||
if customizationsTable[cardMetadata.id] ~= nil then
|
||||
function syncCustomizableMetadata(card)
|
||||
local cardMetadata = JSON.decode(card.getGMNotes()) or { }
|
||||
if cardMetadata ~= nil and cardMetadata.customizations ~= nil then
|
||||
for _, obj in ipairs(searchArea(PLAY_ZONE_POSITION, PLAY_ZONE_SCALE)) do
|
||||
local obj = obj.hit_object
|
||||
if obj.tag == "Card" then
|
||||
local notes = JSON.decode(obj.getGMNotes()) or {}
|
||||
local notes = JSON.decode(obj.getGMNotes()) or { }
|
||||
if notes.id == (cardMetadata.id .. "-c") then
|
||||
local pos = customizationsTable[cardMetadata.id][1]
|
||||
local boxes = customizationsTable[cardMetadata.id][2]
|
||||
if obj.getVar("markedBoxes")[pos] == boxes then tokenCount = tokenCount + 2 end
|
||||
break
|
||||
for i, customization in ipairs(cardMetadata.customizations) do
|
||||
if obj.getVar("markedBoxes")[i] == customization.xp
|
||||
and customization.replaces ~= nil
|
||||
and customization.replaces.uses ~= nil then
|
||||
cardMetadata.uses = customization.replaces.uses
|
||||
card.setGMNotes(JSON.encode(cardMetadata))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- additional charge for Akachi
|
||||
if activeInvestigatorId == "03004" and type == "Charge" then tokenCount = tokenCount + 1 end
|
||||
|
||||
log("Spawning tokens for "..object.getName()..'['..object.getDescription()..']: '..tokenCount.."x "..token)
|
||||
-- Shift each spawned group after the first down so they don't pile on each other
|
||||
spawnTokenGroup(object, token, tokenCount, (i - 1) * 0.6)
|
||||
end
|
||||
end
|
||||
else
|
||||
local data = getPlayerCardData(object)
|
||||
token = data['tokenType']
|
||||
tokenCount = data['tokenCount']
|
||||
log("Spawning tokens for "..object.getName()..'['..object.getDescription()..']: '..tokenCount.."x "..token)
|
||||
spawnTokenGroup(object, token, tokenCount)
|
||||
end
|
||||
markSpawned(object)
|
||||
end
|
||||
|
||||
function resetSpawnState()
|
||||
local zone = getObjectFromGUID(zoneID)
|
||||
if zone == nil then return end
|
||||
|
||||
for _, object in ipairs(zone.getObjects()) do
|
||||
if object.tag == "Card" then
|
||||
unmarkSpawned(object.getGUID(), true)
|
||||
elseif object.tag == "Deck" then
|
||||
local cards = object.getObjects()
|
||||
for _, v in ipairs(cards) do
|
||||
unmarkSpawned(v.guid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function unmarkSpawned(guid, force)
|
||||
if not force and getObjectFromGUID(guid) ~= nil then return end
|
||||
DATA_HELPER.call('setSpawnedPlayerCardGuid', {guid, false})
|
||||
function spawnTokensFor(object)
|
||||
local extraUses = { }
|
||||
if activeInvestigatorId == "03004" then
|
||||
extraUses["Charge"] = 1
|
||||
end
|
||||
|
||||
tokenManager.spawnForCard(object, extraUses)
|
||||
end
|
||||
|
||||
function onCollisionEnter(collision_info)
|
||||
if not COLLISION_ENABLED then return end
|
||||
Wait.time(resetSpawnState, 1)
|
||||
local object = collision_info.collision_object
|
||||
|
||||
-- only continue for cards
|
||||
if object.name ~= "Card" and object.name ~= "CardCustom" then return end
|
||||
maybeUpdateActiveInvestigator(object)
|
||||
|
||||
-- don't spawn tokens for cards in discard pile / threat area
|
||||
local localpos = self.positionToLocal(object.getPosition())
|
||||
if localpos.x < -0.7 or localpos.z < -0.3 then
|
||||
log('Not spawning tokens, relative coordinates are x: ' .. localpos.x .. ' z: ' .. localpos.z)
|
||||
elseif not object.is_face_down and shouldSpawnTokens(object) then
|
||||
syncCustomizableMetadata(object)
|
||||
if isInDeckZone(object) then
|
||||
tokenManager.resetTokensSpawned(object)
|
||||
elseif shouldSpawnTokens(object) then
|
||||
spawnTokensFor(object)
|
||||
end
|
||||
end
|
||||
|
||||
function shouldSpawnTokens(card)
|
||||
if card.is_face_down then
|
||||
return false
|
||||
end
|
||||
local localCardPos = self.positionToLocal(card.getPosition())
|
||||
local metadata = JSON.decode(card.getGMNotes())
|
||||
-- If no metadata we don't know the type, so only spawn in the main area
|
||||
if metadata == nil then
|
||||
return inArea(localCardPos, MAIN_PLAY_AREA)
|
||||
end
|
||||
-- Spawn tokens for assets and events on the main area, and all encounter types in the threat area
|
||||
if inArea(localCardPos, MAIN_PLAY_AREA)
|
||||
and (metadata.type == "Asset"
|
||||
or metadata.type == "Event") then
|
||||
return true
|
||||
end
|
||||
if inArea(localCardPos, THREAT_AREA)
|
||||
and (metadata.type == "Treachery"
|
||||
or metadata.type == "Enemy"
|
||||
or metadata.weakness) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function onObjectEnterContainer(container, object)
|
||||
Wait.frames(function() resetTokensIfInDeckZone(container, object) end, 1)
|
||||
end
|
||||
|
||||
function resetTokensIfInDeckZone(container, object)
|
||||
if isInDeckZone(container) then
|
||||
tokenManager.resetTokensSpawned(object)
|
||||
end
|
||||
end
|
||||
|
||||
function isInDeckZone(checkObject)
|
||||
local deckZone = getObjectFromGUID(zoneID)
|
||||
if deckZone == nil then
|
||||
return false
|
||||
end
|
||||
for _, obj in ipairs(deckZone.getObjects()) do
|
||||
if obj == checkObject then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- investigator ID grabbing and stat tracker
|
||||
---------------------------------------------------------
|
||||
@ -655,10 +601,6 @@ function drawEncountercard(_, _, isRightClick)
|
||||
Global.call("drawEncountercard", {self.positionToWorld(DRAWN_ENCOUNTER_CARD_OFFSET), self.getRotation(), isRightClick})
|
||||
end
|
||||
|
||||
function spawnToken(position, tokenType)
|
||||
Global.call('spawnToken', {position, tokenType, PLAY_ZONE_ROTATION})
|
||||
end
|
||||
|
||||
-- Sets this playermat's draw 1 button to visible
|
||||
---@param visible Boolean. Whether the draw 1 button should be visible
|
||||
function showDrawButton(visible)
|
||||
@ -721,7 +663,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
|
||||
spawnToken(pos, "clue")
|
||||
TokenManager.spawnToken(pos, "clue", self.getRotation())
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -802,9 +744,7 @@ end
|
||||
-- called by custom data helpers to add player card data
|
||||
---@param args table Contains only one entry, the GUID of the custom data helper
|
||||
function updatePlayerCards(args)
|
||||
local custom_data_helper = getObjectFromGUID(args[1])
|
||||
data_player_cards = custom_data_helper.getTable("PLAYER_CARD_DATA")
|
||||
for k, v in pairs(data_player_cards) do
|
||||
PLAYER_CARDS[k] = v
|
||||
end
|
||||
local customDataHelper = getObjectFromGUID(args[1])
|
||||
local playerCardData = customDataHelper.getTable("PLAYER_CARD_DATA")
|
||||
tokenManager.addPlayerCardData(playerCardData)
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user