ah_sce_unpacked/unpacked/go_game_piece_white Game Key Handler fce69c.ttslua
2024-07-27 21:47:52 -04:00

1281 lines
46 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/MythosAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local MythosAreaApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getMythosArea()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "MythosArea")
end
---@return any: Table of chaos token metadata (if provided through scenario reference card)
MythosAreaApi.returnTokenData = function()
return getMythosArea().call("returnTokenData")
end
---@return any: Object reference to the encounter deck
MythosAreaApi.getEncounterDeck = function()
return getMythosArea().call("getEncounterDeck")
end
-- draw an encounter card for the requesting mat to the first empty spot from the right
---@param matColor string Playermat that triggered this
---@param position tts__Vector Position for the encounter card
MythosAreaApi.drawEncounterCard = function(matColor, position)
getMythosArea().call("drawEncounterCard", { matColor = matColor, position = position })
end
-- reshuffle the encounter deck
MythosAreaApi.reshuffleEncounterDeck = function()
getMythosArea().call("reshuffleEncounterDeck")
end
return MythosAreaApi
end
end)
__bundle_register("core/NavigationOverlayApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local NavigationOverlayApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getNOHandler()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "NavigationOverlayHandler")
end
-- copies the visibility for the Navigation overlay
---@param startColor string Color of the player to copy from
---@param targetColor string Color of the targeted player
NavigationOverlayApi.copyVisibility = function(startColor, targetColor)
getNOHandler().call("copyVisibility", {
startColor = startColor,
targetColor = targetColor
})
end
-- changes the Navigation Overlay view ("Full View" --> "Play Areas" --> "Closed" etc.)
---@param playerColor string Color of the player to update the visibility for
NavigationOverlayApi.cycleVisibility = function(playerColor)
getNOHandler().call("cycleVisibility", playerColor)
end
-- loads the specified camera for a player
---@param player tts__Player Player whose camera should be moved
---@param camera number|string If number: Index of the camera view to load | If string: Color of the playermat to swap to
NavigationOverlayApi.loadCamera = function(player, camera)
getNOHandler().call("loadCameraFromApi", {
player = player,
camera = camera
})
end
return NavigationOverlayApi
end
end)
__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("core/VictoryDisplayApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local VictoryDisplayApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getVictoryDisplay()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "VictoryDisplay")
end
-- triggers an update of the victory count
---@param delay? number Delay in seconds after which the update call is executed
VictoryDisplayApi.update = function(delay)
getVictoryDisplay().call("startUpdate", delay)
end
-- moves a card to the victory display (in the first empty spot)
---@param object tts__Object Object that should be checked and potentially moved
VictoryDisplayApi.placeCard = function(object)
if object ~= nil and object.tag == "Card" then
getVictoryDisplay().call("placeCard", object)
end
end
return VictoryDisplayApi
end
end)
__bundle_register("core/token/TokenChecker", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local CHAOS_TOKEN_NAMES = {
["Elder Sign"] = true,
["+1"] = true,
["0"] = true,
["-1"] = true,
["-2"] = true,
["-3"] = true,
["-4"] = true,
["-5"] = true,
["-6"] = true,
["-7"] = true,
["-8"] = true,
["Skull"] = true,
["Cultist"] = true,
["Tablet"] = true,
["Elder Thing"] = true,
["Auto-fail"] = true,
["Bless"] = true,
["Curse"] = true,
["Frost"] = true
}
local TokenChecker = {}
-- returns true if the passed object is a chaos token (by name)
TokenChecker.isChaosToken = function(obj)
if obj.type == "Tile" and CHAOS_TOKEN_NAMES[obj.getName()] then
return true
else
return false
end
end
return TokenChecker
end
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("core/GameKeyHandler")
end)
__bundle_register("core/GameKeyHandler", function(require, _LOADED, __bundle_register, __bundle_modules)
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local mythosAreaApi = require("core/MythosAreaApi")
local navigationOverlayApi = require("core/NavigationOverlayApi")
local optionPanelApi = require("core/OptionPanelApi")
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker")
local victoryDisplayApi = require("core/VictoryDisplayApi")
function onLoad()
addHotkey("Add doom to agenda", addDoomToAgenda)
addHotkey("Add Bless/Curse context menu", addBlurseSealingMenu)
addHotkey("Discard object", discardObject)
addHotkey("Discard top card", discardTopDeck)
addHotkey("Display Bless/Curse status", showBlessCurseStatus)
addHotkey("Move card to Victory Display", moveCardToVictoryDisplay)
addHotkey("Place card into threat area", takeCardIntoThreatArea)
addHotkey("Remove a use", removeOneUse)
addHotkey("Reshuffle encounter deck", mythosAreaApi.reshuffleEncounterDeck)
addHotkey("Switch seat clockwise", switchSeatClockwise)
addHotkey("Switch seat counter-clockwise", switchSeatCounterClockwise)
addHotkey("Take clue from location", takeClueFromLocation)
addHotkey("Take clue from location (White)", takeClueFromLocationWhite)
addHotkey("Take clue from location (Orange)", takeClueFromLocationOrange)
addHotkey("Take clue from location (Green)", takeClueFromLocationGreen)
addHotkey("Take clue from location (Red)", takeClueFromLocationRed)
addHotkey("Upkeep", triggerUpkeep)
addHotkey("Upkeep (Multi-handed)", triggerUpkeepMultihanded)
end
-- triggers the "Upkeep" function of the calling player's playermat
function triggerUpkeep(playerColor)
if playerColor == "Black" then
broadcastToColor("Triggering 'Upkeep (Multihanded)' instead", playerColor, "Yellow")
triggerUpkeepMultihanded(playerColor)
return
end
local matColor = playermatApi.getMatColor(playerColor)
playermatApi.doUpkeepFromHotkey(matColor, playerColor)
end
-- triggers the "Upkeep" function of the calling player's playermat AND
-- for all playermats that don't have a seated player, but an investigator card
function triggerUpkeepMultihanded(playerColor)
if playerColor ~= "Black" then
triggerUpkeep(playerColor)
end
local colors = Player.getAvailableColors()
for _, handColor in ipairs(colors) do
local matColor = playermatApi.getMatColor(handColor)
if playermatApi.returnInvestigatorId(matColor) ~= "00000" and Player[handColor].seated == false then
playermatApi.doUpkeepFromHotkey(matColor, playerColor)
end
end
end
-- adds 1 doom to the agenda
function addDoomToAgenda()
local doomCounter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DoomCounter")
doomCounter.call("addVal", 1)
end
-- move the hovered object to the nearest empty slot on the playermat
function takeCardIntoThreatArea(playerColor, hoveredObject)
-- only continue if an unlocked card
if hoveredObject == nil
or hoveredObject.type ~= "Card" and hoveredObject.type ~= "Deck"
or hoveredObject.hasTag("Location")
or hoveredObject.locked then
broadcastToColor("Hover a non-location card and try again.", playerColor, "Yellow")
return
end
local matColor = playermatApi.getMatColor(playerColor)
local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
-- do not continue if the threat area is already full
local threatAreaPos = playermatApi.getEncounterCardDrawPosition(matColor, false)
if threatAreaPos == playermatApi.getEncounterCardDrawPosition(matColor, true) then
broadcastToColor("Threat area is full.", playerColor, "Yellow")
return
end
-- initialize list of objects to move (and store local positions)
local additionalObjects = {}
for _, obj in ipairs(searchLib.onObject(hoveredObject, "isTileOrToken")) do
local data = {}
data.object = obj
data.localPos = hoveredObject.positionToLocal(obj.getPosition())
table.insert(additionalObjects, data)
end
-- find out if the original card is on the green or red playermats
local originalMatColor = guidReferenceApi.getOwnerOfObject(hoveredObject)
-- determine modifiers for the playermats
local modifierY = 0
if originalMatColor == "Red" then
modifierY = 90
elseif originalMatColor == "Green" then
modifierY = -90
end
local cardName = hoveredObject.getName()
if cardName == "" then cardName = "card" end
broadcastToAll("Moved " .. cardName .. " to " .. getColoredName(playerColor) .. "'s threat area.", "White")
-- get new rotation (rounded)
local cardRot = hoveredObject.getRotation()
local roundedRotY = roundToMultiple(cardRot.y, 45)
local deltaRotY = 270 - mat.getRotation().y - modifierY
local newCardRot = cardRot:setAt("y", roundedRotY - deltaRotY)
-- move the main card to threat area
hoveredObject.setRotation(newCardRot)
hoveredObject.setPosition(threatAreaPos)
-- move tokens/tiles (to new global position)
for _, data in ipairs(additionalObjects) do
if not data.object.locked then
data.object.setPosition(hoveredObject.positionToWorld(data.localPos))
data.object.setRotation(data.object.getRotation() - Vector(0, deltaRotY, 0))
end
end
end
-- discard the hovered or selected objects to the respective trashcan and discard tokens on it if it was a card
function discardObject(playerColor, hoveredObject)
-- if more than one object is selected, discard them all, one at a time
local selectedObjects = Player[playerColor].getSelectedObjects()
if #selectedObjects > 0 then
discardGroup(playerColor, selectedObjects)
return
-- only continue if an unlocked card, deck or tile was hovered
elseif hoveredObject == nil
or (hoveredObject.type ~= "Card" and hoveredObject.type ~= "Deck" and hoveredObject.type ~= "Tile")
or hoveredObject.locked then
broadcastToColor("Hover a token/tile or a card/deck and try again.", playerColor, "Yellow")
return
end
-- These should probably not be discarded normally. Ask player for confirmation.
local tokenData = mythosAreaApi.returnTokenData()
local scenarioName = tokenData.currentScenario
if scenarioName ~= "Lost in Time and Space" and scenarioName ~= "The Secret Name" then
if hoveredObject.type == "Deck" or hoveredObject.hasTag("Location") then
local suspect = (hoveredObject.type == "Deck") and "Deck" or "Location"
Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?",
function() performDiscard(playerColor, hoveredObject) end)
return
end
end
performDiscard(playerColor, hoveredObject)
end
-- actually performs the discarding of the object and tokens / tiles on it
function performDiscard(playerColor, hoveredObject)
-- initialize list of objects to discard
local discardTheseObjects = { hoveredObject }
-- discard tokens / tiles on cards / decks
if hoveredObject.type ~= "Tile" then
for _, obj in ipairs(searchLib.onObject(hoveredObject, "isTileOrToken")) do
table.insert(discardTheseObjects, obj)
end
end
local discardForMatColor = getColorToDiscardFor(hoveredObject, playerColor)
playermatApi.discardListOfObjects(discardForMatColor, discardTheseObjects)
end
function discardGroup(playerColor, selectedObjects)
local count = #selectedObjects
-- discarding one at a time avoids an error with cards in the discard pile losing the 'hands' toggle and uses multiple mats
for i = count, 1, -1 do
Wait.time(function()
if (selectedObjects[i].type == "Card" or selectedObjects[i].type ~= "Deck" or selectedObjects[i].type == "Tile") then
performDiscard(playerColor, selectedObjects[i])
end
end, (count - i + 1) * 0.1)
end
end
-- discard the top card of hovered deck, calling discardObject function
function discardTopDeck(playerColor, hoveredObject)
-- only continue if an unlocked card or deck was hovered
if hoveredObject == nil
or (hoveredObject.type ~= "Card" and hoveredObject.type ~= "Deck")
or hoveredObject.locked then
broadcastToColor("Hover a deck/card and try again.", playerColor, "Yellow")
return
end
-- take top card from deck (unless it is already a single card)
local takenCard = hoveredObject
if hoveredObject.type == "Deck" then
takenCard = hoveredObject.takeObject({ index = 0 })
end
Wait.frames(function() performDiscard(playerColor, takenCard) end, 1)
end
-- helper function to get the player to trigger the discard function for
function getColorToDiscardFor(hoveredObject, playerColor)
local pos = hoveredObject.getPosition()
local closestMatColor = playermatApi.getMatColorByPosition(pos)
-- check if actually on the closest playermat
local closestMat = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, "Playermat")
local bounds = closestMat.getBounds()
-- define the area "near" the playermat
local bufferAroundPlayermat = 2
local areaNearPlayermat = {}
areaNearPlayermat.minX = bounds.center.x - bounds.size.x / 2 - bufferAroundPlayermat
areaNearPlayermat.maxX = bounds.center.x + bounds.size.x / 2 + bufferAroundPlayermat
areaNearPlayermat.minZ = bounds.center.z - bounds.size.z / 2 - bufferAroundPlayermat
areaNearPlayermat.maxZ = bounds.center.z + bounds.size.z / 2 + bufferAroundPlayermat
-- discard to closest mat if near it
if inArea(pos, areaNearPlayermat) then
return closestMatColor
end
-- discard to closest mat if card is in a hand
local handZone = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, "HandZone")
for _, zone in ipairs(hoveredObject.getZones()) do
if zone == handZone then
return closestMatColor
end
end
-- discard to triggering mat if previous conditions weren't met
return playermatApi.getMatColor(playerColor)
end
-- moves the hovered card to the victory display
function moveCardToVictoryDisplay(_, hoveredObject)
victoryDisplayApi.placeCard(hoveredObject)
end
-- removes a use from a card (or a token if hovered)
function removeOneUse(playerColor, hoveredObject)
-- only continue if an unlocked card or tile was hovered
if hoveredObject == nil
or (hoveredObject.type ~= "Card" and hoveredObject.type ~= "Tile")
or hoveredObject.locked then
broadcastToColor("Hover a token/tile or a card and try again.", playerColor, "Yellow")
return
end
local targetObject = nil
-- discard hovered token / tile
if hoveredObject.type == "Tile" then
targetObject = hoveredObject
elseif hoveredObject.type == "Card" then
-- grab the first use type from the metadata (or nil)
local notes = JSON.decode(hoveredObject.getGMNotes()) or {}
local usesData = notes.uses or {}
local useInfo = usesData[1] or {}
local searchForType = useInfo.type
if searchForType then searchForType = searchForType:lower() end
for _, obj in ipairs(searchLib.onObject(hoveredObject, "isTileOrToken")) do
if not obj.locked and obj.memo ~= "resourceCounter" then
-- check for matching object, otherwise use the first hit
if obj.memo and obj.memo == searchForType then
targetObject = obj
break
elseif not targetObject then
targetObject = obj
end
end
end
end
-- release sealed token if card has one and no uses
if tokenChecker.isChaosToken(targetObject) and hoveredObject.hasTag("CardThatSeals") then
local func = hoveredObject.getVar("releaseOneToken") -- check if function exists
if func ~= nil then
hoveredObject.call("releaseOneToken", playerColor)
return
end
end
-- error handling
if not targetObject then
broadcastToColor("No tokens found!", playerColor, "Yellow")
return
end
-- handling for stacked tokens
if targetObject.getQuantity() > 1 then
targetObject = targetObject.takeObject()
end
-- feedback message
local tokenName = targetObject.getName()
if tokenName == "" then
if targetObject.memo ~= "" then
-- name handling for clue / doom
if targetObject.memo == "clueDoom" then
if targetObject.is_face_down then
tokenName = "Doom"
else
tokenName = "Clue"
end
else
tokenName = titleCase(targetObject.memo)
end
else
tokenName = "Unknown"
end
end
broadcastToAll(getColoredName(playerColor) .. " removed a token: " .. tokenName, playerColor)
local discardForMatColor = getColorToDiscardFor(hoveredObject, playerColor)
playermatApi.discardListOfObjects(discardForMatColor, { targetObject })
end
-- switches the triggering player to the next seat (clockwise)
function switchSeatClockwise(playerColor)
switchSeat(playerColor, "clockwise")
end
-- switches the triggering player to the next seat (counter-clockwise)
function switchSeatCounterClockwise(playerColor)
switchSeat(playerColor, "counter-clockwise")
end
-- handles seat switching in the given direction
function switchSeat(playerColor, direction)
if playerColor == "Black" or playerColor == "Grey" then
broadcastToColor("This hotkey is only available to seated players.", playerColor, "Orange")
return
end
-- sort function for matcolors based on hand position (Green, White, Orange, Red)
local function sortByHandPosition(color1, color2)
local pos1 = Player[color1].getHandTransform().position
local pos2 = Player[color2].getHandTransform().position
return pos1.z > pos2.z
end
-- get used playermats
local usedColors = playermatApi.getUsedMatColors()
table.sort(usedColors, sortByHandPosition)
-- get current seat index
local index
for i, color in ipairs(usedColors) do
if color == playerColor then
index = i
break
end
end
if not index then
broadcastToColor("Couldn't detect investigator.", playerColor, "Orange")
return
end
-- get next color
index = index + ((direction == "clockwise") and -1 or 1)
if index == 0 then
index = #usedColors
elseif index > #usedColors then
index = 1
end
-- swap color
navigationOverlayApi.loadCamera(Player[playerColor], usedColors[index])
end
function takeClueFromLocationWhite(_, hoveredObject)
takeClueFromLocation("White", hoveredObject)
end
function takeClueFromLocationOrange(_, hoveredObject)
takeClueFromLocation("Orange", hoveredObject)
end
function takeClueFromLocationGreen(_, hoveredObject)
takeClueFromLocation("Green", hoveredObject)
end
function takeClueFromLocationRed(_, hoveredObject)
takeClueFromLocation("Red", hoveredObject)
end
-- takes a clue from a location, player needs to hover the clue directly or the location
function takeClueFromLocation(playerColor, hoveredObject)
-- use different color for messages if player is not seated (because this hotkey is called for a different mat)
local messageColor = playerColor
if not Player[playerColor] or not Player[playerColor].seated then
messageColor = getFirstSeatedPlayer()
end
local cardName, clue
if hoveredObject == nil then
broadcastToColor("Hover a clue or card with clues and try again.", messageColor, "Yellow")
return
elseif hoveredObject.type == "Card" then
cardName = hoveredObject.getName()
local searchResult = searchLib.onObject(hoveredObject, "isClue")
if #searchResult == 0 then
broadcastToColor("This card does not have any clues on it.", messageColor, "Yellow")
return
else
clue = searchResult[1]
end
elseif hoveredObject.memo == "clueDoom" then
if hoveredObject.is_face_down then
broadcastToColor("This is a doom token and not a clue.", messageColor, "Yellow")
return
end
clue = hoveredObject
local searchResult = searchLib.belowPosition(clue.getPosition(), "isCard")
if #searchResult ~= 0 then
cardName = searchResult[1].getName()
end
elseif hoveredObject.type == "Infinite" and hoveredObject.getName() == "Clue tokens" then
clue = hoveredObject.takeObject()
cardName = "token pool"
else
broadcastToColor("Hover a clue or card with clues and try again.", messageColor, "Yellow")
return
end
local clickableClues = optionPanelApi.getOptions()["useClueClickers"]
-- handling for calling this for a specific mat via hotkey
local matColor, pos
if Player[playerColor] and Player[playerColor].seated then
matColor = playermatApi.getMatColor(playerColor)
else
matColor = playerColor
end
if clickableClues then
pos = { x = 0.49, y = 2.66, z = 0.00 }
playermatApi.updateCounter(matColor, "ClickableClueCounter", _, 1)
else
pos = playermatApi.transformLocalPosition({ x = -1.12, y = 0.05, z = 0.7 }, matColor)
end
local rot = playermatApi.returnRotation(matColor)
-- check if found clue is a stack or single token
if clue.getQuantity() > 1 then
clue.takeObject({ position = pos, rotation = rot })
else
clue.setPositionSmooth(pos)
clue.setRotation(rot)
end
if cardName then
broadcastToAll(getColoredName(playerColor) .. " took one clue from " .. cardName .. ".", "White")
else
broadcastToAll(getColoredName(playerColor) .. " took one clue.", "White")
end
victoryDisplayApi.update()
end
-- broadcasts the bless/curse status to the calling player
function showBlessCurseStatus(playerColor)
blessCurseManagerApi.broadcastStatus(playerColor)
end
-- adds Wendy's menu to the hovered card
function addBlurseSealingMenu(playerColor, hoveredObject)
blessCurseManagerApi.addBlurseSealingMenu(playerColor, hoveredObject)
end
-- Simple method to check if the given point is in a specified area
---@param point tts__Vector Point to check, only x and z values are relevant
---@param bounds table Defined area to see if the point is within
function inArea(point, bounds)
return (point.x > bounds.minX
and point.x < bounds.maxX
and point.z > bounds.minZ
and point.z < bounds.maxZ)
end
-- capitalizes the first letter
function titleCase(str)
local first = str:sub(1, 1)
local rest = str:sub(2)
return first:upper() .. rest:lower()
end
-- returns the color of the first seated player
function getFirstSeatedPlayer()
for _, color in ipairs(getSeatedPlayers()) do
return color
end
end
-- returns the colored steam name or color
function getColoredName(playerColor)
local displayName = playerColor
if Player[playerColor].steam_name then
displayName = Player[playerColor].steam_name
end
-- add bb-code
return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]"
end
function roundToMultiple(num, mult)
return math.floor((num + mult / 2) / mult) * mult
end
end)
__bundle_register("chaosbag/BlessCurseManagerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local BlessCurseManagerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
end
-- removes all taken tokens and resets the counts
BlessCurseManagerApi.removeTakenTokensAndReset = function()
local BlessCurseManager = getManager()
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Bless") end, 0.05)
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Curse") end, 0.10)
Wait.time(function() BlessCurseManager.call("doReset", "White") end, 0.15)
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.sealedToken = function(type, guid)
getManager().call("sealedToken", { type = type, guid = guid })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
---@param fromBag? boolean Whether or not token was just drawn from the chaos bag
BlessCurseManagerApi.releasedToken = function(type, guid, fromBag)
getManager().call("releasedToken", { type = type, guid = guid, fromBag = fromBag })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.returnedToken = function(type, guid)
getManager().call("returnedToken", { type = type, guid = guid })
end
-- broadcasts the current status for bless/curse tokens
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.broadcastStatus = function(playerColor)
getManager().call("broadcastStatus", playerColor)
end
-- removes all bless / curse tokens from the chaos bag and play
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.removeAll = function(playerColor)
getManager().call("doRemove", playerColor)
end
-- adds bless / curse sealing to the hovered card
---@param playerColor string Color of the player to show the broadcast to
---@param hoveredObject tts__Object Hovered object
BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject)
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
end
-- adds bless / curse to the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.addToken = function(type)
getManager().call("addToken", type)
end
-- removes bless / curse from the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.removeToken = function(type)
getManager().call("removeToken", type)
end
BlessCurseManagerApi.getBlessCurseInBag = function()
return getManager().call("getBlessCurseInBag", {})
end
return BlessCurseManagerApi
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/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("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)
return __bundle_require("__root")