ah_sce_unpacked/unpacked/Bag All Player Cards 15bb07/Card Family Inheritance 394603.ttslua
2024-07-27 21:47:52 -04:00

1365 lines
50 KiB
Plaintext

-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local OptionPanelApi = {}
-- loads saved options
---@param options table Set a new state for the option table
OptionPanelApi.loadSettings = function(options)
return Global.call("loadSettings", options)
end
---@return any: Table of option panel state
OptionPanelApi.getOptions = function()
return Global.getTable("optionPanel")
end
return OptionPanelApi
end
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/FamilyInheritance")
end)
__bundle_register("core/token/TokenManager", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local guidReferenceApi = require("core/GUIDReferenceApi")
local optionPanelApi = require("core/OptionPanelApi")
local playAreaApi = require("core/PlayAreaApi")
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
local PLAYER_CARD_TOKEN_OFFSETS = {
[1] = {
Vector(0, 3, -0.2)
},
[2] = {
Vector(0.4, 3, -0.2),
Vector(-0.4, 3, -0.2)
},
[3] = {
Vector(0, 3, -0.9),
Vector(0.4, 3, -0.2),
Vector(-0.4, 3, -0.2)
},
[4] = {
Vector(0.4, 3, -0.9),
Vector(-0.4, 3, -0.9),
Vector(0.4, 3, -0.2),
Vector(-0.4, 3, -0.2)
},
[5] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.4, 3, -0.2),
Vector(-0.4, 3, -0.2)
},
[6] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2)
},
[7] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2),
Vector(0, 3, 0.5)
},
[8] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2),
Vector(-0.35, 3, 0.5),
Vector(0.35, 3, 0.5)
},
[9] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2),
Vector(0.7, 3, 0.5),
Vector(0, 3, 0.5),
Vector(-0.7, 3, 0.5)
},
[10] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2),
Vector(0.7, 3, 0.5),
Vector(0, 3, 0.5),
Vector(-0.7, 3, 0.5),
Vector(0, 3, 1.2)
},
[11] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2),
Vector(0.7, 3, 0.5),
Vector(0, 3, 0.5),
Vector(-0.7, 3, 0.5),
Vector(-0.35, 3, 1.2),
Vector(0.35, 3, 1.2)
},
[12] = {
Vector(0.7, 3, -0.9),
Vector(0, 3, -0.9),
Vector(-0.7, 3, -0.9),
Vector(0.7, 3, -0.2),
Vector(0, 3, -0.2),
Vector(-0.7, 3, -0.2),
Vector(0.7, 3, 0.5),
Vector(0, 3, 0.5),
Vector(-0.7, 3, 0.5),
Vector(0.7, 3, 1.2),
Vector(0, 3, 1.2),
Vector(-0.7, 3, 1.2)
}
}
-- 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
local TokenManager = {}
local internal = {}
-- Spawns tokens for the card. This function is built to just throw a card at it and let it do
-- the work once a card has hit an area where it might spawn tokens. It will check to see if
-- the card has already spawned, find appropriate data from either the uses metadata or the Data
-- Helper, and spawn the tokens.
---@param card 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 = optionPanelApi.getOptions()
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])
-- Fix the y-position for the spawn, since positionToWorld considers rotation which can
-- have bad results for face up/down differences
offsets[i].y = card.getPosition().y + 0.15
end
end
if shiftDown ~= nil then
-- Copy the offsets to make sure we don't change the static values
local baseOffsets = offsets
offsets = {}
-- 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)
-- TODO: support for cards with multiple uses AND replenish (as of yet, no official card needs that)
if uses[1].count and uses[1].replenish then
internal.replenishTokens(card, uses)
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)
local cluePositions = {}
for i = 1, count do
local row = math.floor(1 + (i - 1) / 4)
local column = (i - 1) % 4
local cluePos = card.positionToWorld(Vector(-0.825 + 0.55 * column, 0, -1.5 + 0.55 * row))
cluePos.y = cluePos.y + 0.05
table.insert(cluePositions, cluePos)
end
return cluePositions
end
---@param card tts__Object Card object to be replenished
---@param uses table The already decoded metadata.uses (to avoid decoding again)
internal.replenishTokens = function(card, uses)
-- get current amount of matching resource tokens on the card
local clickableResourceCounter = nil
local foundTokens = 0
local searchType = string.lower(uses[1].type)
for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do
local memo = obj.getMemo()
if searchType == memo then
foundTokens = foundTokens + math.abs(obj.getQuantity())
obj.destruct()
elseif memo == "resourceCounter" then
foundTokens = obj.getVar("val")
clickableResourceCounter = obj
break
end
end
-- this is the theoretical new amount of uses (to be checked below)
local newCount = foundTokens + uses[1].replenish
-- if there are already more uses than the replenish amount, keep them
if foundTokens > uses[1].count then
newCount = foundTokens
-- only replenish up until the replenish amount
elseif newCount > uses[1].count then
newCount = uses[1].count
end
-- update the clickable counter or spawn a group of tokens
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", newCount)
else
TokenManager.spawnTokenGroup(card, uses[1].token, newCount, _, uses[1].type)
end
end
return TokenManager
end
end)
__bundle_register("playercards/CardsWithHelper", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
else
updateDisplay()
end
end
-- forces a new state
function setHelperState(newState)
isHelperEnabled = newState
updateSave()
updateDisplay()
end
-- toggles the current state
function toggleHelper()
isHelperEnabled = not isHelperEnabled
updateSave()
updateDisplay()
end
-- updates the visibility and calls events (after a small delay to allow XML being set)
function updateDisplay()
Wait.frames(actualDisplayUpdate, 5)
end
function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end
end)
__bundle_register("util/SearchLib", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local SearchLib = {}
local filterFunctions = {
isCard = function(x) return x.type == "Card" end,
isDeck = function(x) return x.type == "Deck" end,
isCardOrDeck = function(x) return x.type == "Card" or x.type == "Deck" end,
isClue = function(x) return x.memo == "clueDoom" and x.is_face_down == false end,
isTileOrToken = function(x) return x.type == "Tile" end,
isUniversalToken = function(x) return x.getMemo() == "universalActionAbility" end,
}
-- performs the actual search and returns a filtered list of object references
---@param pos tts__Vector Global position
---@param rot? tts__Vector Global rotation
---@param size table Size
---@param filter? string Name of the filter function
---@param direction? table Direction (positive is up)
---@param maxDistance? number Distance for the cast
local function returnSearchResult(pos, rot, size, filter, direction, maxDistance)
local filterFunc
if filter then
filterFunc = filterFunctions[filter]
end
local searchResult = Physics.cast({
origin = pos,
direction = direction or { 0, 1, 0 },
orientation = rot or { 0, 0, 0 },
type = 3,
size = size,
max_distance = maxDistance or 0
})
-- filter the result for matching objects
local objList = {}
for _, v in ipairs(searchResult) do
if not filter or filterFunc(v.hit_object) then
table.insert(objList, v.hit_object)
end
end
return objList
end
-- searches the specified area
SearchLib.inArea = function(pos, rot, size, filter)
return returnSearchResult(pos, rot, size, filter)
end
-- searches the area on an object
SearchLib.onObject = function(obj, filter)
local pos = obj.getPosition()
local size = obj.getBounds().size:setAt("y", 1)
return returnSearchResult(pos, _, size, filter)
end
-- searches the specified position (a single point)
SearchLib.atPosition = function(pos, filter)
local size = { 0.1, 2, 0.1 }
return returnSearchResult(pos, _, size, filter)
end
-- searches below the specified position (downwards until y = 0)
SearchLib.belowPosition = function(pos, filter)
local size = { 0.1, 2, 0.1 }
local direction = { 0, -1, 0 }
local maxDistance = pos.y
return returnSearchResult(pos, _, size, filter, direction, maxDistance)
end
return SearchLib
end
end)
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local GUIDReferenceApi = {}
local function getGuidHandler()
return getObjectFromGUID("123456")
end
-- Returns the matching object
---@param owner string Parent object for this search
---@param type string Type of object to search for
---@return any: Object reference to the matching object
GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)
return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type })
end
-- Returns all matching objects as a table with references
---@param type string Type of object to search for
---@return table: List of object references to matching objects
GUIDReferenceApi.getObjectsByType = function(type)
return getGuidHandler().call("getObjectsByType", type)
end
-- Returns all matching objects as a table with references
---@param owner string Parent object for this search
---@return table: List of object references to matching objects
GUIDReferenceApi.getObjectsByOwner = function(owner)
return getGuidHandler().call("getObjectsByOwner", owner)
end
-- Sends new information to the reference handler to edit the main index
---@param owner string Parent of the object
---@param type string Type of the object
---@param guid string GUID of the object
GUIDReferenceApi.editIndex = function(owner, type, guid)
return getGuidHandler().call("editIndex", {
owner = owner,
type = type,
guid = guid
})
end
-- Returns the owner of an object or the object it's located on
---@param object tts__GameObject Object for this search
---@return string: Parent of the object or object it's located on
GUIDReferenceApi.getOwnerOfObject = function(object)
return getGuidHandler().call("getOwnerOfObject", object)
end
return GUIDReferenceApi
end
end)
__bundle_register("playercards/cards/FamilyInheritance", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/CardsWithHelper")
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager")
-- intentionally global
hasXML = true
isHelperEnabled = false
local clickableResourceCounter = nil
local foundTokens = 0
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
syncDisplayWithOptionPanel()
end
function searchSelf()
clickableResourceCounter = nil
foundTokens = 0
for _, obj in ipairs(searchLib.onObject(self, "isTileOrToken")) do
local image = obj.getCustomObject().image
if image == "http://cloud-3.steamusercontent.com/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/" then
foundTokens = foundTokens + math.abs(obj.getQuantity())
obj.destruct()
elseif obj.getMemo() == "resourceCounter" then
foundTokens = obj.getVar("val")
clickableResourceCounter = obj
return
end
end
end
function add4()
searchSelf()
local newCount = foundTokens + 4
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", newCount)
else
tokenManager.spawnTokenGroup(self, "resource", newCount)
end
end
function takeAll(player)
searchSelf()
local matColor = playermatApi.getMatColorByPosition(self.getPosition())
playermatApi.updateCounter(matColor, "ResourceCounter", _, foundTokens)
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", 0)
end
printToColor("Moved " .. foundTokens .. " resource(s) to " .. matColor .. "'s resource pool.", player.color)
end
function loseAll(player)
searchSelf()
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", 0)
end
printToColor("Discarded " .. foundTokens .. " resource(s).", player.color)
end
end)
__bundle_register("playermat/PlayermatApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local PlayermatApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local searchLib = require("util/SearchLib")
local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }
-- Convenience function to look up a mat's object by color, or get all mats.
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@return table: Single-element if only single playermat is requested
local function getMatForColor(matColor)
if matColor == "All" then
return guidReferenceApi.getObjectsByType("Playermat")
else
return { matColor = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat") }
end
end
-- Returns the color of the closest playermat
---@param startPos table Starting position to get the closest mat from
PlayermatApi.getMatColorByPosition = function(startPos)
local result, smallestDistance
for matColor, mat in pairs(getMatForColor("All")) do
local distance = Vector.between(startPos, mat.getPosition()):magnitude()
if smallestDistance == nil or distance < smallestDistance then
smallestDistance = distance
result = matColor
end
end
return result
end
-- Returns the color of the player's hand that is seated next to the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getPlayerColor = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("playerColor")
end
end
-- Returns the color of the playermat that owns the playercolor's hand
---@param handColor string Color of the playermat
PlayermatApi.getMatColor = function(handColor)
for matColor, mat in pairs(getMatForColor("All")) do
local playerColor = mat.getVar("playerColor")
if playerColor == handColor then
return matColor
end
end
end
-- Instructs a playermat to check for DES
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.checkForDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("checkForDES")
end
end
-- Returns if there is the card "Dream-Enhancing Serum" on the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@return boolean: whether DES is present on the playermat
PlayermatApi.hasDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("hasDES")
end
end
-- gets the slot data for the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getSlotData = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getTable("slotData")
end
end
-- sets the slot data for the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param newSlotData table New slot data for the playermat
PlayermatApi.loadSlotData = function(matColor, newSlotData)
for _, mat in pairs(getMatForColor(matColor)) do
mat.setTable("slotData", newSlotData)
mat.call("redrawSlotSymbols")
return
end
end
-- Performs a search of the deck area of the requested playermat and returns the result as table
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDeckAreaObjects = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getDeckAreaObjects")
end
end
-- Flips the top card of the deck (useful after deck manipulation for Norman Withers)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.flipTopCardFromDeck = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("flipTopCardFromDeck")
end
end
-- Returns the position of the discard pile of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDiscardPosition = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDiscardPosition")
end
end
-- Returns the position of the draw pile of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDrawPosition = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDrawPosition")
end
end
-- Transforms a local position into a global position
---@param localPos table Local position to be transformed
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.transformLocalPosition = function(localPos, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.positionToWorld(localPos)
end
end
-- Returns the rotation of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnRotation = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getRotation()
end
end
-- Returns a table with spawn data (position and rotation) for a helper object
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param helperName string Name of the helper object
PlayermatApi.getHelperSpawnData = function(matColor, helperName)
local resultTable = {}
local localPositionTable = {
["Hand Helper"] = {0.05, 0, -1.182},
["Search Assistant"] = {-0.3, 0, -1.182}
}
for color, mat in pairs(getMatForColor(matColor)) do
resultTable[color] = {
position = mat.positionToWorld(localPositionTable[helperName]),
rotation = mat.getRotation()
}
end
return resultTable
end
-- Triggers the Upkeep for the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param playerColor string Color of the calling player (for messages)
PlayermatApi.doUpkeepFromHotkey = function(matColor, playerColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doUpkeepFromHotkey", playerColor)
end
end
-- Handles discarding for the requested playermat for the provided list of objects
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param objList table List of objects to discard
PlayermatApi.discardListOfObjects = function(matColor, objList)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("discardListOfObjects", objList)
end
end
-- Returns the active investigator id
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnInvestigatorId = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorId")
end
end
-- Returns the class of the active investigator
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnInvestigatorClass = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorClass")
end
end
-- Returns the position for encounter card drawing
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param stack boolean If true, returns the leftmost position instead of the first empty from the right
PlayermatApi.getEncounterCardDrawPosition = function(matColor, stack)
for _, mat in pairs(getMatForColor(matColor)) do
return Vector(mat.call("getEncounterCardDrawPosition", stack))
end
end
-- Sets the requested playermat's snap points to limit snapping to matching card types or not. If
-- matchTypes is true, the main card slot snap points will only snap assets, while the
-- investigator area point will only snap Investigators. If matchTypes is false, snap points will
-- be reset to snap all cards.
---@param matchCardTypes boolean Whether snap points should only snap for the matching card types
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.setLimitSnapsByType = function(matchCardTypes, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("setLimitSnapsByType", matchCardTypes)
end
end
-- Sets the requested playermat's draw 1 button to visible
---@param isDrawButtonVisible boolean Whether the draw 1 button should be visible or not
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.showDrawButton = function(isDrawButtonVisible, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("showDrawButton", isDrawButtonVisible)
end
end
-- Shows or hides the clickable clue counter for the requested playermat
---@param showCounter boolean Whether the clickable counter should be present or not
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.clickableClues = function(showCounter, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("clickableClues", showCounter)
end
end
-- Toggles the use of class textures for the requested playermat
---@param state boolean Whether the class texture should be used or not
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.useClassTexture = function(state, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("useClassTexture", state)
end
end
-- Removes all clues (to the trash for tokens and counters set to 0) for the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.removeClues = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("removeClues")
end
end
-- Reports the clue count for the requested playermat
---@param useClickableCounters boolean Controls which type of counter is getting checked
PlayermatApi.getClueCount = function(useClickableCounters, matColor)
local count = 0
for _, mat in pairs(getMatForColor(matColor)) do
count = count + mat.call("getClueCount", useClickableCounters)
end
return count
end
-- Updates the specified owned counter
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param type string Counter to target
---@param newValue number Value to set the counter to
---@param modifier number If newValue is not provided, the existing value will be adjusted by this modifier
PlayermatApi.updateCounter = function(matColor, type, newValue, modifier)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("updateCounter", { type = type, newValue = newValue, modifier = modifier })
end
end
-- Triggers the draw function for the specified playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param number number Amount of cards to draw
PlayermatApi.drawCardsWithReshuffle = function(matColor, number)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("drawCardsWithReshuffle", number)
end
end
-- Returns the resource counter amount
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param type string Counter to target
PlayermatApi.getCounterValue = function(matColor, type)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getCounterValue", type)
end
end
-- Returns a list of mat colors that have an investigator placed
PlayermatApi.getUsedMatColors = function()
local usedColors = {}
for matColor, mat in pairs(getMatForColor("All")) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
local searchResult = searchLib.atPosition(searchPos, "isCardOrDeck")
if #searchResult > 0 then
table.insert(usedColors, matColor)
end
end
return usedColors
end
-- Returns investigator name
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getInvestigatorName = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
local searchResult = searchLib.atPosition(searchPos, "isCardOrDeck")
if #searchResult == 1 then
return searchResult[1].getName()
end
end
return ""
end
-- Resets the specified skill tracker to "1, 1, 1, 1"
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.resetSkillTracker = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("resetSkillTracker")
end
end
-- Redraws the XML for the slot symbols based on the slotData table
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.redrawSlotSymbols = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("redrawSlotSymbols")
end
end
-- Finds all objects on the playermat and associated set aside zone and returns a table
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param filter string Name of the filte function (see util/SearchLib)
PlayermatApi.searchAroundPlayermat = function(matColor, filter)
local objList = {}
for _, mat in pairs(getMatForColor(matColor)) do
for _, obj in ipairs(mat.call("searchAroundSelf", filter)) do
table.insert(objList, obj)
end
end
return objList
end
-- Discard a non-hidden card from the corresponding player's hand
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.doDiscardOne = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doDiscardOne")
end
end
-- Triggers the metadata sync for all playermats
PlayermatApi.syncAllCustomizableCards = function()
for _, mat in pairs(getMatForColor("All")) do
mat.call("syncAllCustomizableCards")
end
end
return PlayermatApi
end
end)
__bundle_register("core/PlayAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local PlayAreaApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getPlayArea()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayArea")
end
local function getInvestigatorCounter()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "InvestigatorCounter")
end
-- Returns the current value of the investigator counter from the playermat
---@return number: Number of investigators currently set on the counter
PlayAreaApi.getInvestigatorCount = function()
return getInvestigatorCounter().getVar("val")
end
-- Updates the current value of the investigator counter from the playermat
---@param count number Number of investigators to set on the counter
PlayAreaApi.setInvestigatorCount = function(count)
getInvestigatorCounter().call("updateVal", count)
end
-- Move all contents on the play area (cards, tokens, etc) one slot in the given direction. Certain
-- fixed objects will be ignored, as will anything the player has tagged with 'displacement_excluded'
---@param playerColor string Color of the player requesting the shift for messages
PlayAreaApi.shiftContentsUp = function(playerColor)
getPlayArea().call("shiftContentsUp", playerColor)
end
PlayAreaApi.shiftContentsDown = function(playerColor)
getPlayArea().call("shiftContentsDown", playerColor)
end
PlayAreaApi.shiftContentsLeft = function(playerColor)
getPlayArea().call("shiftContentsLeft", playerColor)
end
PlayAreaApi.shiftContentsRight = function(playerColor)
getPlayArea().call("shiftContentsRight", playerColor)
end
---@param state boolean This controls whether location connections should be drawn
PlayAreaApi.setConnectionDrawState = function(state)
getPlayArea().call("setConnectionDrawState", state)
end
---@param color string Connection color to be used for location connections
PlayAreaApi.setConnectionColor = function(color)
getPlayArea().call("setConnectionColor", color)
end
-- Event to be called when the current scenario has changed
---@param scenarioName string Name of the new scenario
PlayAreaApi.onScenarioChanged = function(scenarioName)
getPlayArea().call("onScenarioChanged", scenarioName)
end
-- Sets this playermat's snap points to limit snapping to locations or not.
-- If matchTypes is false, snap points will be reset to snap all cards.
---@param matchCardTypes boolean Whether snap points should only snap for the matching card types
PlayAreaApi.setLimitSnapsByType = function(matchCardTypes)
getPlayArea().call("setLimitSnapsByType", matchCardTypes)
end
-- Receiver for the Global tryObjectEnterContainer event. Used to clear vector lines from dragged
-- cards before they're destroyed by entering the container
PlayAreaApi.tryObjectEnterContainer = function(container, object)
getPlayArea().call("tryObjectEnterContainer", { container = container, object = object })
end
-- Counts the VP on locations in the play area
PlayAreaApi.countVP = function()
return getPlayArea().call("countVP")
end
-- Highlights all locations in the play area without metadata
---@param state boolean True if highlighting should be enabled
PlayAreaApi.highlightMissingData = function(state)
return getPlayArea().call("highlightMissingData", state)
end
-- Highlights all locations in the play area with VP
---@param state boolean True if highlighting should be enabled
PlayAreaApi.highlightCountedVP = function(state)
return getPlayArea().call("countVP", state)
end
-- Checks if an object is in the play area (returns true or false)
PlayAreaApi.isInPlayArea = function(object)
return getPlayArea().call("isInPlayArea", object)
end
-- Returns the current surface of the play area
PlayAreaApi.getSurface = function()
return getPlayArea().getCustomObject().image
end
-- Updates the surface of the play area
PlayAreaApi.updateSurface = function(url)
return getPlayArea().call("updateSurface", url)
end
-- Returns a deep copy of the currently tracked locations
PlayAreaApi.getTrackedLocations = function()
local t = {}
for k, v in pairs(getPlayArea().call("getTrackedLocations", {})) do
t[k] = v
end
return t
end
-- Called by Custom Data Helpers to push their location data into the Data Helper. This adds the
-- data to the local token manager instance.
---@param args table Single-value array holding the GUID of the Custom Data Helper making the call
PlayAreaApi.updateLocations = function(args)
getPlayArea().call("updateLocations", args)
end
PlayAreaApi.getCustomDataHelper = function()
return getPlayArea().getVar("customDataHelper")
end
return PlayAreaApi
end
end)
__bundle_register("core/token/TokenSpawnTrackerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local TokenSpawnTracker = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getSpawnTracker()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSpawnTracker")
end
TokenSpawnTracker.hasSpawnedTokens = function(cardGuid)
return getSpawnTracker().call("hasSpawnedTokens", cardGuid)
end
TokenSpawnTracker.markTokensSpawned = function(cardGuid)
return getSpawnTracker().call("markTokensSpawned", cardGuid)
end
TokenSpawnTracker.resetTokensSpawned = function(card)
return getSpawnTracker().call("resetTokensSpawned", card)
end
TokenSpawnTracker.resetAllAssetAndEvents = function()
return getSpawnTracker().call("resetAllAssetAndEvents")
end
TokenSpawnTracker.resetAllLocations = function()
return getSpawnTracker().call("resetAllLocations")
end
TokenSpawnTracker.resetAll = function()
return getSpawnTracker().call("resetAll")
end
return TokenSpawnTracker
end
end)
return __bundle_require("__root")