2022-12-13 14:02:30 -05:00
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
2020-11-28 13:23:58 -05:00
2022-12-13 14:02:30 -05:00
local register
local modules = {}
2020-11-28 13:23:58 -05:00
2022-12-13 14:02:30 -05:00
local require
local loaded = {}
2020-11-28 13:23:58 -05:00
2022-12-13 14:02:30 -05:00
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
2020-12-06 09:42:32 -05:00
2022-12-13 14:02:30 -05:00
require = function(name)
local loadedModule = loaded[name]
2021-03-23 10:59:55 -04:00
2022-12-13 14:02:30 -05:00
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
2020-11-28 13:23:58 -05:00
2022-12-13 14:02:30 -05:00
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
2021-03-23 10:59:55 -04:00
2022-12-13 14:02:30 -05:00
return require, loaded, register, modules
end)(nil)
2024-02-04 10:51:51 -05:00
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)
2023-01-29 19:31:52 -05:00
do
2024-02-04 10:51:51 -05:00
local GUIDReferenceApi = {}
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
local function getGuidHandler()
return getObjectFromGUID("123456")
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
-- returns all matching objects as a table with references
---@param owner String Parent object for this search
---@param type String Type of object to search for
GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)
return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type })
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
-- returns all matching objects as a table with references
---@param type String Type of object to search for
GUIDReferenceApi.getObjectsByType = function(type)
return getGuidHandler().call("getObjectsByType", type)
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- returns all matching objects as a table with references
---@param owner String Parent object for this search
GUIDReferenceApi.getObjectsByOwner = function(owner)
return getGuidHandler().call("getObjectsByOwner", owner)
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- 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
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
return GUIDReferenceApi
2023-04-22 16:56:01 -04:00
end
end)
__bundle_register("core/PlayAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
2024-02-04 10:51:51 -05:00
local PlayAreaApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
local function getPlayArea()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayArea")
2023-01-29 19:31:52 -05:00
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
local function getInvestigatorCounter()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "InvestigatorCounter")
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
2023-04-22 16:56:01 -04:00
-- Returns the current value of the investigator counter from the playmat
---@return Integer. Number of investigators currently set on the counter
PlayAreaApi.getInvestigatorCount = function()
2024-02-04 10:51:51 -05:00
return getInvestigatorCounter().getVar("val")
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
-- Updates the current value of the investigator counter from the playmat
---@param count Number of investigators to set on the counter
PlayAreaApi.setInvestigatorCount = function(count)
2024-02-04 10:51:51 -05:00
getInvestigatorCounter().call("updateVal", count)
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
-- Move all contents on the play area (cards, tokens, etc) one slot in the given direction. Certain
2024-02-04 10:51:51 -05:00
-- fixed objects will be ignored, as will anything the player has tagged with 'displacement_excluded'
---@param playerColor Color Color of the player requesting the shift for messages
2023-04-22 16:56:01 -04:00
PlayAreaApi.shiftContentsUp = function(playerColor)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("shiftContentsUp", playerColor)
2023-04-22 16:56:01 -04:00
end
PlayAreaApi.shiftContentsDown = function(playerColor)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("shiftContentsDown", playerColor)
2023-04-22 16:56:01 -04:00
end
PlayAreaApi.shiftContentsLeft = function(playerColor)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("shiftContentsLeft", playerColor)
2023-04-22 16:56:01 -04:00
end
PlayAreaApi.shiftContentsRight = function(playerColor)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("shiftContentsRight", playerColor)
2023-04-22 16:56:01 -04:00
end
-- Reset the play area's tracking of which cards have had tokens spawned.
PlayAreaApi.resetSpawnedCards = function()
2024-02-04 10:51:51 -05:00
return getPlayArea().call("resetSpawnedCards")
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- Sets whether location connections should be drawn
PlayAreaApi.setConnectionDrawState = function(state)
getPlayArea().call("setConnectionDrawState", state)
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- Sets the connection color
PlayAreaApi.setConnectionColor = function(color)
getPlayArea().call("setConnectionColor", color)
2023-04-22 16:56:01 -04:00
end
-- Event to be called when the current scenario has changed.
---@param scenarioName Name of the new scenario
PlayAreaApi.onScenarioChanged = function(scenarioName)
2024-02-04 10:51:51 -05:00
getPlayArea().call("onScenarioChanged", scenarioName)
2023-04-22 16:56:01 -04:00
end
-- Sets this playmat's snap points to limit snapping to locations or not.
-- If matchTypes is false, snap points will be reset to snap all cards.
2024-02-04 10:51:51 -05:00
---@param matchCardTypes Boolean Whether snap points should only snap for the matching card types.
2023-04-22 16:56:01 -04:00
PlayAreaApi.setLimitSnapsByType = function(matchCardTypes)
2024-02-04 10:51:51 -05:00
getPlayArea().call("setLimitSnapsByType", matchCardTypes)
2023-04-22 16:56:01 -04:00
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)
2024-02-04 10:51:51 -05:00
getPlayArea().call("tryObjectEnterContainer", { container = container, object = object })
2023-04-22 16:56:01 -04:00
end
-- counts the VP on locations in the play area
PlayAreaApi.countVP = function()
2024-02-04 10:51:51 -05:00
return getPlayArea().call("countVP")
2023-04-22 16:56:01 -04:00
end
-- highlights all locations in the play area without metadata
---@param state Boolean True if highlighting should be enabled
PlayAreaApi.highlightMissingData = function(state)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("highlightMissingData", state)
2023-04-22 16:56:01 -04:00
end
-- highlights all locations in the play area with VP
---@param state Boolean True if highlighting should be enabled
PlayAreaApi.highlightCountedVP = function(state)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("countVP", state)
2023-04-22 16:56:01 -04:00
end
-- Checks if an object is in the play area (returns true or false)
PlayAreaApi.isInPlayArea = function(object)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("isInPlayArea", object)
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
PlayAreaApi.getSurface = function()
2024-02-04 10:51:51 -05:00
return getPlayArea().getCustomObject().image
2023-08-27 21:09:46 -04:00
end
PlayAreaApi.updateSurface = function(url)
2024-02-04 10:51:51 -05:00
return getPlayArea().call("updateSurface", url)
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
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
PlayAreaApi.getCustomDataHelper = function()
return getPlayArea().getVar("customDataHelper")
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
return PlayAreaApi
end
end)
2024-02-04 10:51:51 -05:00
__bundle_register("core/SoundCubeApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local SoundCubeApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- this table links the name of a trigger effect to its index
local soundIndices = {
["Vacuum"] = 0,
["Deep Bell"] = 1,
["Dark Souls"] = 2
}
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
local function playTriggerEffect(index)
local SoundCube = guidReferenceApi.getObjectByOwnerAndType("Mythos", "SoundCube")
SoundCube.AssetBundle.playTriggerEffect(index)
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- plays the by name requested sound
---@param soundName String Name of the sound to play
SoundCubeApi.playSoundByName = function(soundName)
playTriggerEffect(soundIndices[soundName])
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
return SoundCubeApi
2024-01-06 21:32:07 -05:00
end
2024-02-04 10:51:51 -05:00
end)
__bundle_register("accessories/TokenArrangerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local TokenArrangerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- local function to call the token arranger, if it is on the table
---@param functionName String Name of the function to cal
---@param argument Variant Parameter to pass
local function callIfExistent(functionName, argument)
local tokenArranger = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenArranger")
if tokenArranger ~= nil then
tokenArranger.call(functionName, argument)
end
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- updates the token modifiers with the provided data
---@param fullData Table Contains the chaos token metadata
TokenArrangerApi.onTokenDataChanged = function(fullData)
callIfExistent("onTokenDataChanged", fullData)
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- deletes already laid out tokens
TokenArrangerApi.deleteCopiedTokens = function()
callIfExistent("deleteCopiedTokens")
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- updates the laid out tokens
TokenArrangerApi.layout = function()
Wait.time(function() callIfExistent("layout") end, 0.1)
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
return TokenArrangerApi
end
end)
__bundle_register("core/NavigationOverlayApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local NavigationOverlayApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
local function getNOHandler()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "NavigationOverlayHandler")
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- 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
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- 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
2024-01-06 21:32:07 -05:00
2024-02-04 10:51:51 -05:00
-- loads the specified camera for a player
---@param player TTSPlayerInstance Player whose camera should be moved
---@param camera Variant 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
})
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
return NavigationOverlayApi
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
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
}
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local TokenChecker = {}
-- returns true if the passed object is a chaos token (by name)
TokenChecker.isChaosToken = function(obj)
if CHAOS_TOKEN_NAMES[obj.getName()] then
return true
else
return false
2024-01-06 21:32:07 -05:00
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
return TokenChecker
end
end)
2023-08-27 21:09:46 -04:00
__bundle_register("core/token/TokenManager", function(require, _LOADED, __bundle_register, __bundle_modules)
2023-04-22 16:56:01 -04:00
do
2024-02-04 10:51:51 -05:00
local guidReferenceApi = require("core/GUIDReferenceApi")
local optionPanelApi = require("core/OptionPanelApi")
local playAreaApi = require("core/PlayAreaApi")
local searchLib = require("util/SearchLib")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
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)
}
2023-01-29 19:31:52 -05:00
}
2023-08-27 21:09:46 -04:00
-- stateIDs for the multi-stated resource tokens
local stateTable = {
["resource"] = 1,
["ammo"] = 2,
["bounty"] = 3,
["charge"] = 4,
["evidence"] = 5,
["secret"] = 6,
["supply"] = 7
}
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
local playerCardData
local locationData
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
local TokenManager = { }
local internal = { }
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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 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)
2024-02-04 10:51:51 -05:00
if tokenSpawnTrackerApi.hasSpawnedTokens(card.getGUID()) then
2023-08-27 21:09:46 -04:00
return
end
local metadata = JSON.decode(card.getGMNotes())
if metadata ~= nil then
internal.spawnTokensFromUses(card, extraUses)
else
internal.spawnTokensFromDataHelper(card)
end
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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 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
2024-02-04 10:51:51 -05:00
---@param subType String Subtype of token to spawn. This will only differ from the tokenName for resource tokens
2023-08-27 21:09:46 -04:00
TokenManager.spawnTokenGroup = function(card, tokenType, tokenCount, shiftDown, subType)
2024-02-04 10:51:51 -05:00
local optionPanel = optionPanelApi.getOptions()
2023-08-27 21:09:46 -04:00
if tokenType == "damage" or tokenType == "horror" then
TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown)
2024-02-04 10:51:51 -05:00
elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "enabled" then
TokenManager.spawnResourceCounterToken(card, tokenCount)
elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "custom" and tokenCount == 0 then
2023-08-27 21:09:46 -04:00
TokenManager.spawnResourceCounterToken(card, tokenCount)
else
TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType)
end
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- 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 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) spawned.setState(tokenValue) end)
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
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)
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- 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 Number How many tokens to spawn
---@param shiftDown Number An offset for the z-value of this group of tokens
2024-02-04 10:51:51 -05:00
---@param subType String Subtype of token to spawn. This will only differ from the tokenName for resource tokens
2023-08-27 21:09:46 -04:00
TokenManager.spawnMultipleTokens = function(card, tokenType, tokenCount, shiftDown, subType)
2024-02-04 10:51:51 -05:00
-- not checking the max at this point since clue offsets are calculated dynamically
if tokenCount < 1 then return end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
local offsets = {}
if tokenType == "clue" then
offsets = internal.buildClueOffsets(card, tokenCount)
else
2024-02-04 10:51:51 -05:00
-- only up to 12 offset tables defined
if tokenCount > 12 then return end
2023-08-27 21:09:46 -04:00
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
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
if shiftDown ~= nil then
-- Copy the offsets to make sure we don't change the static values
local baseOffsets = offsets
offsets = { }
2024-02-04 10:51:51 -05:00
-- get a vector for the shifting (downwards local to the card)
local shiftDownVector = Vector(0, 0, shiftDown):rotateOver("y", card.getRotation().y)
2023-08-27 21:09:46 -04:00
for i, baseOffset in ipairs(baseOffsets) do
2024-02-04 10:51:51 -05:00
offsets[i] = baseOffset + shiftDownVector
2023-08-27 21:09:46 -04:00
end
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
if offsets == nil then
error("couldn't find offsets for " .. tokenCount .. ' tokens')
return
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- handling for not provided subtype (for example when spawning from custom data helpers)
if subType == nil then
subType = ""
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)]
if tokenType == "resource" and stateID ~= nil and stateID ~= 1 then
callback = function(spawned) spawned.setState(stateID) end
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
for i = 1, tokenCount do
TokenManager.spawnToken(offsets[i], tokenType, card.getRotation(), callback)
end
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- 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 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]
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- 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
2023-07-22 00:42:48 -04:00
2023-08-27 21:09:46 -04:00
tokenTemplate.Nickname = ""
return spawnObjectData({
data = tokenTemplate,
position = position,
rotation = rot,
callback_function = callback
})
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- Checks a card for metadata to maybe replenish it
---@param card Object Card object to be replenished
---@param uses Table The already decoded metadata.uses (to avoid decoding again)
---@param mat Object The playmat the card is placed on (for rotation and casting)
TokenManager.maybeReplenishCard = function(card, uses, mat)
-- 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, mat)
end
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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)
2024-02-04 10:51:51 -05:00
tokenSpawnTrackerApi.resetTokensSpawned(card.getGUID())
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- 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)
internal.initDataHelperData()
return internal.getLocationData(card) ~= nil
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
internal.initTokenTemplates = function()
if tokenTemplates ~= nil then
return
end
2024-02-04 10:51:51 -05:00
tokenTemplates = {}
local tokenSource = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSource")
2023-08-27 21:09:46 -04:00
for _, tokenTemplate in ipairs(tokenSource.getData().ContainedObjects) do
local tokenName = tokenTemplate.Memo
tokenTemplates[tokenName] = tokenTemplate
end
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
-- Copies the data from the DataHelper. Will only happen once.
internal.initDataHelperData = function()
if playerCardData ~= nil then
return
end
2024-02-04 10:51:51 -05:00
local dataHelper = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
2023-08-27 21:09:46 -04:00
playerCardData = dataHelper.getTable('PLAYER_CARD_DATA')
locationData = dataHelper.getTable('LOCATIONS_DATA')
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
-- 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
-- go through tokens to spawn
2024-02-04 10:51:51 -05:00
local tokenCount
2023-08-27 21:09:46 -04:00
for i, useInfo in ipairs(uses) do
2024-02-04 10:51:51 -05:00
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]
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- Shift each spawned group after the first down so they don't pile on each other
2024-02-04 10:51:51 -05:00
TokenManager.spawnTokenGroup(card, useInfo.token, tokenCount, (i - 1) * 0.8, useInfo.type)
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- 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)
2023-01-29 19:31:52 -05:00
end
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
-- 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)
2024-02-04 10:51:51 -05:00
local token = playerData.tokenType
local tokenCount = playerData.tokenCount
2023-08-27 21:09:46 -04:00
TokenManager.spawnTokenGroup(card, token, tokenCount)
2024-02-04 10:51:51 -05:00
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- Spawn tokens for a location using data retrieved from the Data Helper.
---@param card Object Card to maybe spawn tokens for
2024-02-04 10:51:51 -05:00
---@param locationData Table Location data structure retrieved from the DataHelper. Should be
2023-08-27 21:09:46 -04:00
-- 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)
2024-02-04 10:51:51 -05:00
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
2023-01-29 19:31:52 -05:00
end
end
2023-08-27 21:09:46 -04:00
internal.getPlayerCardData = function(card)
return playerCardData[card.getName() .. ':' .. card.getDescription()]
or playerCardData[card.getName()]
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
internal.getLocationData = function(card)
return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()]
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
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
2024-02-04 10:51:51 -05:00
return locationData.value * playAreaApi.getInvestigatorCount()
2023-08-27 21:09:46 -04:00
end
error('unexpected location type: ' .. locationData.type)
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
return 0
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- 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
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
return nil
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- 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
2023-09-02 22:53:29 -04:00
local column = math.floor((i - 1) / 4)
local row = (i - 1) % 4
table.insert(cluePositions, Vector(pos.x - 1 + 0.55 * row, pos.y, pos.z - 1.4 - 0.55 * column))
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
return cluePositions
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
---@param card Object Card object to be replenished
---@param uses Table The already decoded metadata.uses (to avoid decoding again)
---@param mat Object The playmat the card is placed on (for rotation and casting)
internal.replenishTokens = function(card, uses, mat)
local cardPos = card.getPosition()
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- don't continue for cards on the deck (Norman) or in the discard pile
if mat.positionToLocal(cardPos).x < -1 then return end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- get current amount of resource tokens on the card
local clickableResourceCounter = nil
local foundTokens = 0
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do
2023-08-27 21:09:46 -04:00
local memo = obj.getMemo()
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
if (stateTable[memo] or 0) > 0 then
foundTokens = foundTokens + math.abs(obj.getQuantity())
obj.destruct()
elseif memo == "resourceCounter" then
foundTokens = obj.getVar("val")
clickableResourceCounter = obj
break
end
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- this is the theoretical new amount of uses (to be checked below)
local newCount = foundTokens + uses[1].replenish
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- 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
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- 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
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
end)
2024-02-04 10:51:51 -05:00
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
2023-08-27 21:09:46 -04:00
do
2024-02-04 10:51:51 -05:00
local OptionPanelApi = {}
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- loads saved options
---@param options Table New options table
OptionPanelApi.loadSettings = function(options)
return Global.call("loadSettings", options)
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- returns option panel table
OptionPanelApi.getOptions = function()
return Global.getTable("optionPanel")
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
return OptionPanelApi
2023-08-27 21:09:46 -04:00
end
end)
2024-02-04 10:51:51 -05:00
__bundle_register("core/MythosAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules)
2023-08-27 21:09:46 -04:00
do
2024-02-04 10:51:51 -05:00
local MythosAreaApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local function getMythosArea()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "MythosArea")
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- returns the chaos token metadata (if provided through scenario reference card)
MythosAreaApi.returnTokenData = function()
return getMythosArea().call("returnTokenData")
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- returns an object reference to the encounter deck
MythosAreaApi.getEncounterDeck = function()
return getMythosArea().call("getEncounterDeck")
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- draw an encounter card for the requesting mat
MythosAreaApi.drawEncounterCard = function(mat, alwaysFaceUp)
getMythosArea().call("drawEncounterCard", {mat = mat, alwaysFaceUp = alwaysFaceUp})
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
return MythosAreaApi
end
end)
2024-02-04 10:51:51 -05:00
__bundle_register("playermat/PlaymatApi", function(require, _LOADED, __bundle_register, __bundle_modules)
2023-08-27 21:09:46 -04:00
do
2024-02-04 10:51:51 -05:00
local PlaymatApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local searchLib = require("util/SearchLib")
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- Convenience function to look up a mat's object by color, or get all mats.
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
---@return array Table Single-element if only single playmat is requested
local function getMatForColor(matColor)
if matColor == "All" then
return guidReferenceApi.getObjectsByType("Playermat")
2023-08-27 21:09:46 -04:00
else
2024-02-04 10:51:51 -05:00
return { matColor = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat") }
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- Returns the color of the closest playmat
---@param startPos Table Starting position to get the closest mat from
2023-08-27 21:09:46 -04:00
PlaymatApi.getMatColorByPosition = function(startPos)
2024-02-04 10:51:51 -05:00
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
2023-08-27 21:09:46 -04:00
end
end
2024-02-04 10:51:51 -05:00
return result
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- Returns the color of the player's hand that is seated next to the playmat
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
2023-08-27 21:09:46 -04:00
PlaymatApi.getPlayerColor = function(matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("playerColor")
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- Returns the color of the playmat that owns the playercolor's hand
---@param handColor String Color of the playmat
2023-08-27 21:09:46 -04:00
PlaymatApi.getMatColor = function(handColor)
2024-02-04 10:51:51 -05:00
for matColor, mat in pairs(getMatForColor("All")) do
local playerColor = mat.getVar("playerColor")
if playerColor == handColor then
return matColor
end
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- Returns if there is the card "Dream-Enhancing Serum" on the requested playmat
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.isDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("isDES")
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- Performs a search of the deck area of the requested playmat and returns the result as table
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDeckAreaObjects = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getDeckAreaObjects")
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- Flips the top card of the deck (useful after deck manipulation for Norman Withers)
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.flipTopCardFromDeck = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("flipTopCardFromDeck")
end
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- Returns the position of the discard pile of the requested playmat
2024-02-04 10:51:51 -05:00
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
2023-08-27 21:09:46 -04:00
PlaymatApi.getDiscardPosition = function(matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDiscardPosition")
end
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- Transforms a local position into a global position
---@param localPos Table Local position to be transformed
2024-02-04 10:51:51 -05:00
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
2023-08-27 21:09:46 -04:00
PlaymatApi.transformLocalPosition = function(localPos, matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
return mat.positionToWorld(localPos)
end
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- Returns the rotation of the requested playmat
2024-02-04 10:51:51 -05:00
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
2023-08-27 21:09:46 -04:00
PlaymatApi.returnRotation = function(matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getRotation()
end
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- Returns a table with spawn data (position and rotation) for a helper object
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
---@param helperName String Name of the helper object
PlaymatApi.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
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2024-01-06 21:32:29 -05:00
2023-08-27 21:09:46 -04:00
-- Triggers the Upkeep for the requested playmat
2024-02-04 10:51:51 -05:00
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
2023-08-27 21:09:46 -04:00
---@param playerColor String Color of the calling player (for messages)
PlaymatApi.doUpkeepFromHotkey = function(matColor, playerColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doUpkeepFromHotkey", playerColor)
end
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- Handles discarding for the requested playmat for the provided list of objects
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
---@param objList Table List of objects to discard
PlaymatApi.discardListOfObjects = function(matColor, objList)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("discardListOfObjects", objList)
end
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- Returns the active investigator id
2024-02-04 10:51:51 -05:00
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
2023-08-27 21:09:46 -04:00
PlaymatApi.returnInvestigatorId = function(matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorId")
end
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2024-02-04 10:51:51 -05:00
-- Sets the requested playmat's snap points to limit snapping to matching card types or not. If
2023-08-27 21:09:46 -04:00
-- 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.
2024-02-04 10:51:51 -05:00
---@param matchCardTypes Boolean Whether snap points should only snap for the matching card types
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
2023-08-27 21:09:46 -04:00
PlaymatApi.setLimitSnapsByType = function(matchCardTypes, matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
2023-08-27 21:09:46 -04:00
mat.call("setLimitSnapsByType", matchCardTypes)
2023-01-29 19:31:52 -05:00
end
end
2024-02-04 10:51:51 -05:00
-- Sets the requested playmat'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 playmat - White, Orange, Green, Red or All
2023-08-27 21:09:46 -04:00
PlaymatApi.showDrawButton = function(isDrawButtonVisible, matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
2023-08-27 21:09:46 -04:00
mat.call("showDrawButton", isDrawButtonVisible)
end
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- Shows or hides the clickable clue counter for the requested playmat
---@param showCounter Boolean Whether the clickable counter should be present or not
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
2023-08-27 21:09:46 -04:00
PlaymatApi.clickableClues = function(showCounter, matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
2023-08-27 21:09:46 -04:00
mat.call("clickableClues", showCounter)
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- Removes all clues (to the trash for tokens and counters set to 0) for the requested playmat
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
2023-08-27 21:09:46 -04:00
PlaymatApi.removeClues = function(matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
2023-08-27 21:09:46 -04:00
mat.call("removeClues")
end
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- Reports the clue count for the requested playmat
2023-08-27 21:09:46 -04:00
---@param useClickableCounters Boolean Controls which type of counter is getting checked
PlaymatApi.getClueCount = function(useClickableCounters, matColor)
local count = 0
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
count = count + mat.call("getClueCount", useClickableCounters)
2023-08-27 21:09:46 -04:00
end
return count
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- updates the specified owned counter
---@param matColor String Color of the playmat - 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
PlaymatApi.updateCounter = function(matColor, type, newValue, modifier)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("updateCounter", { type = type, newValue = newValue, modifier = modifier })
end
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- triggers the draw function for the specified playmat
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
---@param number Number Amount of cards to draw
PlaymatApi.drawCardsWithReshuffle = function(matColor, number)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("drawCardsWithReshuffle", number)
end
end
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- returns the resource counter amount
---@param matColor String Color of the playmat - White, Orange, Green or Red (does not support "All")
---@param type String Counter to target
PlaymatApi.getCounterValue = function(matColor, type)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getCounterValue", type)
end
end
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- returns a list of mat colors that have an investigator placed
PlaymatApi.getUsedMatColors = function()
local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }
local usedColors = {}
for matColor, mat in pairs(getMatForColor("All")) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
local searchResult = searchLib.atPosition(searchPos, "isCardOrDeck")
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
if #searchResult > 0 then
table.insert(usedColors, matColor)
end
end
return usedColors
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- resets the specified skill tracker to "1, 1, 1, 1"
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.resetSkillTracker = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("resetSkillTracker")
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- finds all objects on the playmat and associated set aside zone and returns a table
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
---@param filter String Name of the filte function (see util/SearchLib)
PlaymatApi.searchAroundPlaymat = 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
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
return objList
2024-01-06 21:32:29 -05:00
end
2023-08-27 21:09:46 -04:00
-- Discard a non-hidden card from the corresponding player's hand
2024-02-04 10:51:51 -05:00
---@param matColor String Color of the playmat - White, Orange, Green, Red or All
2023-08-27 21:09:46 -04:00
PlaymatApi.doDiscardOne = function(matColor)
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor(matColor)) do
2023-08-27 21:09:46 -04:00
mat.call("doDiscardOne")
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- Triggers the metadata sync for all playmats
2023-08-27 21:09:46 -04:00
PlaymatApi.syncAllCustomizableCards = function()
2024-02-04 10:51:51 -05:00
for _, mat in pairs(getMatForColor("All")) do
2023-08-27 21:09:46 -04:00
mat.call("syncAllCustomizableCards")
2023-04-22 16:56:01 -04:00
end
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
return PlaymatApi
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
end)
__bundle_register("util/SearchLib", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local SearchLib = {}
local filterFunctions = {
isActionToken = function(x) return x.getDescription() == "Action Token" end,
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
}
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- performs the actual search and returns a filtered list of object references
---@param pos Table Global position
---@param rot Table 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)
if filter then filter = 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
})
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- filtering the result
local objList = {}
for _, v in ipairs(searchResult) do
if not filter or filter(v.hit_object) then
table.insert(objList, v.hit_object)
2024-01-06 21:32:29 -05:00
end
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
return objList
2023-04-22 16:56:01 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- searches the specified area
SearchLib.inArea = function(pos, rot, size, filter)
return returnSearchResult(pos, rot, size, filter)
end
2024-01-06 21:32:07 -05:00
2024-02-04 10:51:51 -05:00
-- searches the area on an object
SearchLib.onObject = function(obj, filter)
pos = obj.getPosition()
size = obj.getBounds().size:setAt("y", 1)
return returnSearchResult(pos, _, size, filter)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- searches the specified position (a single point)
SearchLib.atPosition = function(pos, filter)
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)
direction = { 0, -1, 0 }
maxDistance = pos.y
return returnSearchResult(pos, _, size, filter, direction, maxDistance)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
return SearchLib
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("core/Global")
end)
__bundle_register("core/Global", function(require, _LOADED, __bundle_register, __bundle_modules)
2024-02-04 10:51:51 -05:00
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local mythosAreaApi = require("core/MythosAreaApi")
local navigationOverlayApi = require("core/NavigationOverlayApi")
local playAreaApi = require("core/PlayAreaApi")
local playmatApi = require("playermat/PlaymatApi")
local searchLib = require("util/SearchLib")
local soundCubeApi = require("core/SoundCubeApi")
local tokenArrangerApi = require("accessories/TokenArrangerApi")
local tokenChecker = require("core/token/TokenChecker")
local tokenManager = require("core/token/TokenManager")
2022-12-13 14:02:30 -05:00
---------------------------------------------------------
2023-08-27 21:09:46 -04:00
-- general setup
2022-12-13 14:02:30 -05:00
---------------------------------------------------------
2024-02-04 10:51:51 -05:00
ENCOUNTER_DECK_POS = { -3.93, 1, 5.76 }
ENCOUNTER_DECK_DISCARD_POSITION = { -3.85, 1, 10.38 }
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- GUIDs that will not be interactable (e.g. parts of the table)
local NOT_INTERACTABLE = {
"6161b4", -- Decoration-Map
"9f334f", -- MythosArea
"463022", -- Panel behind tentacle stand
"f182ee", -- InvestigatorCount
"7bff34", -- Tentacle stand
"8646eb", -- horizontal border left
"75937e", -- horizontal border right
"612072", -- vertical border left
"975c39", -- vertical border right
}
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
local chaosTokens = {}
2024-02-04 10:51:51 -05:00
local chaosTokensLastMatGUID = nil
-- chaos token stat tracking
local tokenDrawingStats = { ["Overall"] = {} }
2023-08-27 21:09:46 -04:00
local bagSearchers = {}
2024-02-04 10:51:51 -05:00
local MAT_COLORS = { "White", "Orange", "Green", "Red" }
2023-08-27 21:09:46 -04:00
local hideTitleSplashWaitFunctionId = nil
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- online functionality related variables
2024-02-04 10:51:51 -05:00
local MOD_VERSION = "3.5.0"
2023-08-27 21:09:46 -04:00
local SOURCE_REPO = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main'
2024-02-04 10:51:51 -05:00
local library, requestObj, modMeta
2023-08-27 21:09:46 -04:00
local acknowledgedUpgradeVersions = {}
2024-02-04 10:51:51 -05:00
local contentToShow = "campaigns"
local currentListItem = 1
local xmlVisibility = {
downloadWindow = false,
optionPanel = false,
playAreaGallery = false,
updateNotification = false
}
local tabIdTable = {
tab1 = "campaigns",
tab2 = "scenarios",
tab3 = "fanmadeCampaigns",
tab4 = "fanmadeScenarios",
tab5 = "fanmadePlayerCards"
}
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- optionPanel data
optionPanel = {}
2023-08-27 21:09:46 -04:00
local LANGUAGES = {
{ code = "zh_CN", name = "简体中文" },
{ code = "zh_TW", name = "繁體中文" },
{ code = "de", name = "Deutsch" },
{ code = "en", name = "English" },
{ code = "es", name = "Español" },
{ code = "fr", name = "Français" },
{ code = "it", name = "Italiano" }
}
2024-02-04 10:51:51 -05:00
local RESOURCE_OPTIONS = {
"enabled",
"custom",
"disabled"
}
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
---------------------------------------------------------
-- data for tokens
---------------------------------------------------------
2020-12-06 09:42:32 -05:00
2023-08-27 21:09:46 -04:00
TOKEN_DATA = {
damage = {image = "http://cloud-3.steamusercontent.com/ugc/1758068501357115146/903D11AAE7BD5C254C8DC136E9202EE516289DEA/", scale = {0.17, 0.17, 0.17}},
horror = {image = "http://cloud-3.steamusercontent.com/ugc/1758068501357163535/6D9E0756503664D65BDB384656AC6D4BD713F5FC/", scale = {0.17, 0.17, 0.17}},
resource = {image = "http://cloud-3.steamusercontent.com/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/", scale = {0.17, 0.17, 0.17}},
doom = {image = "https://i.imgur.com/EoL7yaZ.png", scale = {0.17, 0.17, 0.17}},
clue = {image = "http://cloud-3.steamusercontent.com/ugc/1758068501357164917/1D06F1DC4D6888B6F57124BD2AFE20D0B0DA15A8/", scale = {0.15, 0.15, 0.15}}
}
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
ID_URL_MAP = {
['blue'] = {name = "Elder Sign", url = 'https://i.imgur.com/nEmqjmj.png'},
['p1'] = {name = "+1", url = 'https://i.imgur.com/uIx8jbY.png'},
['0'] = {name = "0", url = 'https://i.imgur.com/btEtVfd.png'},
['m1'] = {name = "-1", url = 'https://i.imgur.com/w3XbrCC.png'},
['m2'] = {name = "-2", url = 'https://i.imgur.com/bfTg2hb.png'},
['m3'] = {name = "-3", url = 'https://i.imgur.com/yfs8gHq.png'},
['m4'] = {name = "-4", url = 'https://i.imgur.com/qrgGQRD.png'},
['m5'] = {name = "-5", url = 'https://i.imgur.com/3Ym1IeG.png'},
['m6'] = {name = "-6", url = 'https://i.imgur.com/c9qdSzS.png'},
['m7'] = {name = "-7", url = 'https://i.imgur.com/4WRD42n.png'},
['m8'] = {name = "-8", url = 'https://i.imgur.com/9t3rPTQ.png'},
['skull'] = {name = "Skull", url = 'https://i.imgur.com/stbBxtx.png'},
['cultist'] = {name = "Cultist", url = 'https://i.imgur.com/VzhJJaH.png'},
['tablet'] = {name = "Tablet", url = 'https://i.imgur.com/1plY463.png'},
['elder'] = {name = "Elder Thing", url = 'https://i.imgur.com/ttnspKt.png'},
['red'] = {name = "Auto-fail", url = 'https://i.imgur.com/lns4fhz.png'},
['bless'] = {name = "Bless", url = 'http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/'},
['curse'] = {name = "Curse", url = 'http://cloud-3.steamusercontent.com/ugc/1655601092778636039/2A25BD38E8C44701D80DD96BF0121DA21843672E/'},
['frost'] = {name = "Frost", url = 'http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/'}
}
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
---------------------------------------------------------
-- general code
---------------------------------------------------------
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- saving state of optionPanel to restore later
2024-02-04 10:51:51 -05:00
function onSave()
local chaosTokensGUID = {}
for _, obj in ipairs(chaosTokens) do
if obj ~= nil then
table.insert(chaosTokensGUID, obj.getGUID())
2024-01-06 21:32:29 -05:00
end
end
2020-12-06 09:42:32 -05:00
2024-02-04 10:51:51 -05:00
return JSON.encode({
optionPanel = optionPanel,
acknowledgedUpgradeVersions = acknowledgedUpgradeVersions,
chaosTokensLastMatGUID = chaosTokensLastMatGUID,
chaosTokensGUID = chaosTokensGUID
})
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
function onLoad(savedData)
if savedData then
loadedData = JSON.decode(savedData)
optionPanel = loadedData.optionPanel
acknowledgedUpgradeVersions = loadedData.acknowledgedUpgradeVersions
2020-11-28 13:23:58 -05:00
2023-09-02 22:53:29 -04:00
-- hack to set options on load
2024-02-04 12:34:49 -05:00
optionPanel["useResourceCounters"] = "enabled"
updateOptionPanelState()
2024-02-04 11:11:59 -05:00
2024-02-04 10:51:51 -05:00
-- restore saved state for drawn chaos tokens
for _, guid in ipairs(loadedData.chaosTokensGUID or {}) do
table.insert(chaosTokens, getObjectFromGUID(guid))
end
chaosTokensLastMatGUID = loadedData.chaosTokensLastMatGUID
2023-08-27 21:09:46 -04:00
else
print("Saved state could not be found!")
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
for _, guid in ipairs(NOT_INTERACTABLE) do
local obj = getObjectFromGUID(guid)
if obj ~= nil then obj.interactable = false end
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
getModVersion()
math.randomseed(os.time())
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- initialization of loadable objects library (delay to let Navigation Overlay build)
Wait.time(function()
WebRequest.get(SOURCE_REPO .. '/library.json', libraryDownloadCallback)
end, 1)
2024-01-06 21:32:07 -05:00
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
-- chaos bag during search operations to avoid this.
function onObjectSearchStart(object, playerColor)
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
if object == chaosBag then
2023-08-27 21:09:46 -04:00
bagSearchers[playerColor] = true
2020-11-28 13:23:58 -05:00
end
2023-08-27 21:09:46 -04:00
end
2022-12-13 14:02:30 -05:00
2023-08-27 21:09:46 -04:00
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
-- chaos bag during search operations to avoid this.
function onObjectSearchEnd(object, playerColor)
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
if object == chaosBag then
2023-08-27 21:09:46 -04:00
bagSearchers[playerColor] = nil
2020-11-28 13:23:58 -05:00
end
2022-12-13 14:02:30 -05:00
end
2023-08-27 21:09:46 -04:00
-- Pass object enter container events to the PlayArea to clear vector lines from dragged cards.
-- This requires the try method as cards won't exist any more after they enter a deck, so the lines
-- can't be cleared.
function tryObjectEnterContainer(container, object)
2024-02-04 10:51:51 -05:00
playAreaApi.tryObjectEnterContainer(container, object)
2023-08-27 21:09:46 -04:00
return true
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- TTS event for objects that enter zones
-- used to detect the "token discard zones" beneath the hand zones
function onObjectEnterZone(zone, enteringObj)
if zone.getName() ~= "TokenDiscardZone" then return end
if tokenChecker.isChaosToken(enteringObj) then return end
if enteringObj.type == "Tile" and enteringObj.getMemo() and enteringObj.getLock() == false then
local matcolor = playmatApi.getMatColorByPosition(enteringObj.getPosition())
local trash = guidReferenceApi.getObjectByOwnerAndType(matcolor, "Trash")
trash.putObject(enteringObj)
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
end
2022-12-13 14:02:30 -05:00
2024-02-04 10:51:51 -05:00
-- handle card drawing via number typing for multihanded gameplay
-- (and additionally allow Norman Withers to draw multiple cards via number)
function onObjectNumberTyped(hoveredObject, playerColor, number)
-- only continue for decks or cards
if hoveredObject.type ~= "Deck" and hoveredObject.type ~= "Card" then return end
-- check whether the hovered object is part of a players draw objects
for _, color in ipairs(playmatApi.getUsedMatColors()) do
local deckAreaObjects = playmatApi.getDeckAreaObjects(color)
if deckAreaObjects.topCard == hoveredObject or deckAreaObjects.draw == hoveredObject then
playmatApi.drawCardsWithReshuffle(color, number)
return true
2023-04-22 16:56:01 -04:00
end
2020-11-28 13:23:58 -05:00
end
end
2022-12-13 14:02:30 -05:00
---------------------------------------------------------
2023-08-27 21:09:46 -04:00
-- chaos token drawing
2022-12-13 14:02:30 -05:00
---------------------------------------------------------
2023-08-27 21:09:46 -04:00
-- checks scripting zone for chaos bag (also called by a lot of objects!)
function findChaosBag()
2024-02-04 10:51:51 -05:00
local chaosBagZone = guidReferenceApi.getObjectByOwnerAndType("Mythos", "ChaosBagZone")
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- error handling: scripting zone not found
2024-02-04 10:51:51 -05:00
if chaosBagZone == nil then
2023-08-27 21:09:46 -04:00
printToAll("Zone for chaos bag detection couldn't be found.", "Red")
2023-04-22 16:56:01 -04:00
return
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
for _, item in ipairs(chaosBagZone.getObjects()) do
2023-08-27 21:09:46 -04:00
if item.getDescription() == "Chaos Bag" then
return item
end
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- error handling: chaos bag not found
printToAll("Chaos bag couldn't be found.", "Red")
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
function returnChaosTokens()
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
2023-08-27 21:09:46 -04:00
for _, token in pairs(chaosTokens) do
2024-02-04 10:51:51 -05:00
if token ~= nil then chaosBag.putObject(token) end
2023-08-27 21:09:46 -04:00
end
chaosTokens = {}
2020-11-28 13:23:58 -05:00
end
2024-02-04 10:51:51 -05:00
-- returns a single chaos token to the bag and calls respective functions
function returnChaosTokenToBag(token)
local name = token.getName()
local guid = token.getGUID()
local chaosBag = findChaosBag()
chaosBag.putObject(token)
tokenArrangerApi.layout()
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.releasedToken(name, guid)
2020-12-06 09:42:32 -05:00
end
end
2023-08-27 21:09:46 -04:00
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens
-- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the
-- contents of the bag should check this method before doing so.
-- This method will broadcast a message to all players if the bag is being searched.
---@return Boolean. True if the bag is manipulated, false if it should be blocked.
function canTouchChaosTokens()
for color, searching in pairs(bagSearchers) do
if searching then
broadcastToAll("Someone is searching the chaos bag, can't touch the tokens.", "Red")
return false
2023-04-22 16:56:01 -04:00
end
2020-11-28 13:23:58 -05:00
end
2023-08-27 21:09:46 -04:00
return true
2020-11-28 13:23:58 -05:00
end
2023-08-27 21:09:46 -04:00
-- called by playermats (by the "Draw chaos token" button)
function drawChaosToken(params)
if not canTouchChaosTokens() then return end
2024-02-04 10:51:51 -05:00
local tokenOffset = {-1.55, 0.25, -0.58}
local matGUID = params.mat.getGUID()
2023-08-27 21:09:46 -04:00
-- return token(s) on other playmat first
2024-02-04 10:51:51 -05:00
if chaosTokensLastMatGUID ~= nil and chaosTokensLastMatGUID ~= matGUID and #chaosTokens ~= 0 then
2023-08-27 21:09:46 -04:00
returnChaosTokens()
2024-02-04 10:51:51 -05:00
chaosTokensLastMatGUID = nil
2023-08-27 21:09:46 -04:00
return
end
2024-02-04 10:51:51 -05:00
chaosTokensLastMatGUID = matGUID
2023-08-27 21:09:46 -04:00
-- if we have left clicked and have no tokens OR if we have right clicked
2024-02-04 10:51:51 -05:00
if params.drawAdditional or #chaosTokens == 0 then
local chaosBag = findChaosBag()
if #chaosBag.getObjects() == 0 then return end
chaosBag.shuffle()
2023-08-27 21:09:46 -04:00
-- add the token to the list, compute new position based on list length
tokenOffset[1] = tokenOffset[1] + (0.17 * #chaosTokens)
2024-02-04 10:51:51 -05:00
local token = chaosBag.takeObject({
2023-08-27 21:09:46 -04:00
index = 0,
2024-02-04 10:51:51 -05:00
position = params.mat.positionToWorld(tokenOffset),
rotation = params.mat.getRotation()
2023-08-27 21:09:46 -04:00
})
-- get data for token description
local name = token.getName()
local tokenData = mythosAreaApi.returnTokenData().tokenData or {}
local specificData = tokenData[name] or {}
token.setDescription(specificData.description or "")
-- track the chaos token (for stat tracker and future returning)
2024-02-04 10:51:51 -05:00
trackChaosToken(name, matGUID)
2023-08-27 21:09:46 -04:00
chaosTokens[#chaosTokens + 1] = token
2023-04-22 16:56:01 -04:00
else
2023-08-27 21:09:46 -04:00
returnChaosTokens()
2022-12-13 14:02:30 -05:00
end
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
---------------------------------------------------------
-- 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)
return tokenManager.spawnToken(params[1], params[2], params[3])
end
---------------------------------------------------------
-- chaos token stat tracker
---------------------------------------------------------
function trackChaosToken(tokenName, matGUID)
2024-02-04 10:51:51 -05:00
-- initialize tables
if not tokenDrawingStats[matGUID] then tokenDrawingStats[matGUID] = {} end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- increase stats by 1
2023-08-27 21:09:46 -04:00
tokenDrawingStats["Overall"][tokenName] = (tokenDrawingStats["Overall"][tokenName] or 0) + 1
tokenDrawingStats[matGUID][tokenName] = (tokenDrawingStats[matGUID][tokenName] or 0) + 1
end
-- Left-click: print stats, Right-click: reset stats
function handleStatTrackerClick(_, _, isRightClick)
if isRightClick then
resetChaosTokenStatTracker()
else
2024-02-04 10:51:51 -05:00
local squidKing = "Nobody"
local maxSquid = 0
2023-08-27 21:09:46 -04:00
local foundAnyStats = false
for key, personalStats in pairs(tokenDrawingStats) do
local playerColor, playerName
if key == "Overall" then
playerColor = "White"
playerName = "Overall"
else
2024-02-04 10:51:51 -05:00
-- get mat color
local matColor = playmatApi.getMatColorByPosition(getObjectFromGUID(key).getPosition())
playerColor = playmatApi.getPlayerColor(matColor)
2023-08-27 21:09:46 -04:00
playerName = Player[playerColor].steam_name or playerColor
2024-02-04 10:51:51 -05:00
local playerSquidCount = personalStats["Auto-fail"] or 0
2023-08-27 21:09:46 -04:00
if playerSquidCount > maxSquid then
squidKing = playerName
maxSquid = playerSquidCount
end
end
-- get the total count of drawn tokens for the player
local totalCount = 0
for tokenName, value in pairs(personalStats) do
totalCount = totalCount + value
end
-- only print the personal stats if any tokens were drawn
if totalCount > 0 then
foundAnyStats = true
printToAll("------------------------------")
2024-02-04 10:51:51 -05:00
printToAll(playerName .. " Stats", playerColor)
2023-08-27 21:09:46 -04:00
for tokenName, value in pairs(personalStats) do
if value ~= 0 then
printToAll(tokenName .. ': ' .. tostring(value))
end
end
printToAll('Total: ' .. tostring(totalCount))
end
end
-- detect if any player drew tokens
if foundAnyStats then
printToAll("------------------------------")
2024-02-04 10:51:51 -05:00
printToAll(squidKing .. " is an auto-fail magnet.", { 255, 0, 0 })
2023-08-27 21:09:46 -04:00
else
printToAll("No tokens have been drawn yet.", "Yellow")
end
2020-11-28 13:23:58 -05:00
end
end
2023-08-27 21:09:46 -04:00
-- resets the count for each token to 0
function resetChaosTokenStatTracker()
2024-02-04 10:51:51 -05:00
tokenDrawingStats = { ["Overall"] = {} }
2023-08-27 21:09:46 -04:00
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
---------------------------------------------------------
-- Difficulty selector script
---------------------------------------------------------
-- called for button creation on the difficulty selectors
2024-02-04 10:51:51 -05:00
---@param args Table Parameters for this function:
-- object TTSObject Usually "self"
-- key String Name of the scenario
2023-08-27 21:09:46 -04:00
function createSetupButtons(args)
local data = getDataValue('modeData', args.key)
if data ~= nil then
local buttonParameters = {}
buttonParameters.function_owner = args.object
2024-02-04 10:51:51 -05:00
buttonParameters.position = { 0, 0.1, -0.15 }
buttonParameters.scale = { 0.47, 1, 0.47 }
2023-08-27 21:09:46 -04:00
buttonParameters.height = 200
buttonParameters.width = 1150
2024-02-04 10:51:51 -05:00
buttonParameters.color = { 0.87, 0.8, 0.7 }
2023-08-27 21:09:46 -04:00
if data.easy ~= nil then
buttonParameters.label = "Easy"
buttonParameters.click_function = "easyClick"
args.object.createButton(buttonParameters)
buttonParameters.position[3] = buttonParameters.position[3] + 0.20
end
if data.normal ~= nil then
buttonParameters.label = "Standard"
buttonParameters.click_function = "normalClick"
args.object.createButton(buttonParameters)
buttonParameters.position[3] = buttonParameters.position[3] + 0.20
end
if data.hard ~= nil then
buttonParameters.label = "Hard"
buttonParameters.click_function = "hardClick"
args.object.createButton(buttonParameters)
buttonParameters.position[3] = buttonParameters.position[3] + 0.20
end
if data.expert ~= nil then
buttonParameters.label = "Expert"
buttonParameters.click_function = "expertClick"
args.object.createButton(buttonParameters)
buttonParameters.position[3] = buttonParameters.position[3] + 0.20
end
if data.standalone ~= nil then
buttonParameters.label = "Standalone"
buttonParameters.click_function = "standaloneClick"
args.object.createButton(buttonParameters)
end
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- called for adding chaos tokens
2024-02-04 10:51:51 -05:00
---@param args Table Parameters for this function:
-- object object Usually "self"
-- key string Name of the scenario
-- mode string difficulty (e.g. "hard" or "expert")
2023-08-27 21:09:46 -04:00
function fillContainer(args)
local data = getDataValue('modeData', args.key)
if data == nil then return end
local value = data[args.mode]
if value == nil or value.token == nil then return end
local tokenList = {}
for _, tokenId in ipairs(value.token) do
table.insert(tokenList, tokenId)
2022-12-13 14:02:30 -05:00
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
if value.append ~= nil then
for _, tokenId in ipairs(value.append) do
table.insert(tokenList, tokenId)
end
2022-12-13 14:02:30 -05:00
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- randomly choose tokens for specific Carcosa scenarios in standalone
if value.random then
local n = #value.random
if n > 0 then
for _, tokenId in ipairs(value.random[math.random(1, n)]) do
table.insert(tokenList, tokenId)
end
end
end
setChaosBagState(tokenList)
if value.message then
broadcastToAll(value.message)
end
if value.warning then
broadcastToAll(value.warning, { 1, 0.5, 0.5 })
end
2022-12-13 14:02:30 -05:00
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
function getDataValue(storage, key)
2024-02-04 10:51:51 -05:00
local DATA_HELPER = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
local data = DATA_HELPER.getTable(storage)
2023-08-27 21:09:46 -04:00
if data ~= nil then
local value = data[key]
if value ~= nil then
local res = {}
for m, v in pairs(value) do
res[m] = v
if res[m].parent ~= nil then
local parentData = getDataValue(storage, res[m].parent)
if parentData ~= nil and parentData[m] ~= nil and parentData[m].token ~= nil then
res[m].token = parentData[m].token
end
res[m].parent = nil
end
end
return res
end
end
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
function createChaosTokenNameLookupTable()
local namesToIds = {}
for k, v in pairs(ID_URL_MAP) do
namesToIds[v.name] = k
end
return namesToIds
end
2022-12-13 14:02:30 -05:00
2024-02-04 10:51:51 -05:00
-- returns the currently drawn chaos tokens
---@api ChaosBagApi
function getChaosTokensinPlay()
return chaosTokens
end
2024-01-06 21:32:29 -05:00
2023-08-27 21:09:46 -04:00
-- returns a Table List of chaos token ids in the current chaos bag
2024-02-04 10:51:51 -05:00
---@api ChaosBag / ChaosBagApi
2023-08-27 21:09:46 -04:00
function getChaosBagState()
local tokens = {}
local invertedTable = createChaosTokenNameLookupTable()
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
for _, v in ipairs(chaosBag.getObjects()) do
2023-08-27 21:09:46 -04:00
local id = invertedTable[v.name]
if id then
table.insert(tokens, id)
else
printToAll(v.name .. " token not recognized. Will not be recorded.", "Yellow")
end
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
return tokens
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- respawns the chaos bag with a new state of tokens
---@param tokenList Table List of chaos token ids
2024-02-04 10:51:51 -05:00
---@api ChaosBag / ChaosBagApi
2023-08-27 21:09:46 -04:00
function setChaosBagState(tokenList)
if not canTouchChaosTokens() then return end
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
local chaosBagData = chaosBag.getData()
2023-08-27 21:09:46 -04:00
local reserveData = getObjectFromGUID("106418").getData()
local tokenCache = {}
local containedObjects = {}
-- create a temporary copy of the data for each chaos token
for _, objData in ipairs(reserveData.ContainedObjects) do
tokenCache[objData.Nickname] = objData
2022-12-13 14:02:30 -05:00
end
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- iterate over tokenlist and insert specified tokens into new table
for _, tokenId in ipairs(tokenList) do
local tokenName = ID_URL_MAP[tokenId].name
table.insert(containedObjects, tokenCache[tokenName])
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- overwrite chaos bag content and respawn it
2024-02-04 10:51:51 -05:00
chaosBagData.ContainedObjects = containedObjects
chaosBag.destruct()
spawnObjectData({ data = chaosBagData })
2023-08-27 21:09:46 -04:00
-- remove tokens that are still in play
for _, token in pairs(chaosTokens) do
if token ~= nil then token.destruct() end
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
chaosTokens = {}
2024-02-04 10:51:51 -05:00
chaosTokensLastMatGUID = nil
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
-- reset bless / curse manager
blessCurseManagerApi.removeTakenTokensAndReset()
2020-11-28 13:23:58 -05:00
2023-08-27 21:09:46 -04:00
printToAll("Chaos bag set to chosen difficulty.", "Green")
end
-- spawns the specified chaos token and puts it into the chaos bag
---@param id String ID of the chaos token
function spawnChaosToken(id)
if not canTouchChaosTokens() then return end
id = id:lower()
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
2023-08-27 21:09:46 -04:00
local url = ID_URL_MAP[id].url or ""
if url ~= "" then
return spawnObject({
type = 'Custom_Tile',
position = { 0.49, 3, 0 },
scale = { 0.81, 1.0, 0.81 },
2024-02-04 10:51:51 -05:00
rotation = { 0, 270, 0 },
2023-08-27 21:09:46 -04:00
callback_function = function(obj)
obj.setName(ID_URL_MAP[id].name)
2024-02-04 10:51:51 -05:00
chaosBag.putObject(obj)
2023-08-27 21:09:46 -04:00
tokenArrangerApi.layout()
end
}).setCustomObject({
type = 2,
image = url,
thickness = 0.1
})
end
end
-- removes the specified chaos token from the chaos bag
---@param id String ID of the chaos token
function removeChaosToken(id)
if not canTouchChaosTokens() then return end
local tokens = {}
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
2023-08-27 21:09:46 -04:00
local name = ID_URL_MAP[id].name
2024-02-04 10:51:51 -05:00
for _, v in ipairs(chaosBag.getObjects()) do
2023-08-27 21:09:46 -04:00
if v.name == name then table.insert(tokens, v.guid) end
end
-- error handling: no matching token found
if #tokens == 0 then
printToAll("No " .. name .. " tokens in the chaos bag.", "Yellow")
return
2023-04-22 16:56:01 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
chaosBag.takeObject({
2023-08-27 21:09:46 -04:00
guid = tokens[1],
smooth = false,
callback_function = function(obj)
obj.destruct()
tokenArrangerApi.layout()
end
})
printToAll("Removing " .. name .. " token (in bag: " .. #tokens - 1 .. ")", "White")
end
-- empty the chaos bag
function emptyChaosBag()
if not canTouchChaosTokens() then return end
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
for _, object in ipairs(chaosBag.getObjects()) do
chaosBag.takeObject({ callback_function = function(item) item.destruct() end })
2023-08-27 21:09:46 -04:00
end
end
-- returns all sealed tokens on cards to the chaos bag
function releaseAllSealedTokens(playerColor)
2024-02-04 10:51:51 -05:00
local chaosBag = findChaosBag()
2023-08-27 21:09:46 -04:00
for _, obj in ipairs(getObjectsWithTag("CardThatSeals")) do
obj.call("releaseAllTokens", playerColor)
end
end
---------------------------------------------------------
-- Content Importing and XML functions
---------------------------------------------------------
2024-02-04 10:51:51 -05:00
-- forwards the requested content type to the update function and sets highlight to clicked tab
---@param tabId String Id of the clicked tab
function onClick_tab(_, _, tabId)
for listId, listContent in pairs(tabIdTable) do
if listId == tabId then
UI.setClass(listId, 'downloadTab activeTab')
contentToShow = listContent
2024-01-06 21:32:29 -05:00
else
2024-02-04 10:51:51 -05:00
UI.setClass(listId, 'downloadTab')
2024-01-06 21:32:29 -05:00
end
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
currentListItem = 1
updateDownloadItemList()
2023-08-27 21:09:46 -04:00
end
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- click function for the items in the download window
-- updates backgroundcolor for row panel and fontcolor for list item
function onClick_select(_, _, identificationKey)
UI.setAttribute("panel" .. currentListItem, "color", "clear")
UI.setAttribute(contentToShow .. "_" .. currentListItem, "color", "white")
-- parses the identification key (contentToShow_currentListItem)
if identificationKey then
contentToShow = nil
currentListItem = nil
for str in string.gmatch(identificationKey, "([^_]+)") do
if not contentToShow then
-- grab the first part to know the content type
contentToShow = str
else
-- get the index
currentListItem = tonumber(str)
break
end
end
2024-01-06 21:32:29 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
UI.setAttribute("panel" .. currentListItem, "color", "grey")
UI.setAttribute(contentToShow .. "_" .. currentListItem, "color", "black")
updatePreviewWindow()
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- click function for the "Custom URL" button in the playarea image gallery
function onClick_customUrl(player)
onClick_toggleUi(_, "playareaGallery")
Wait.time(function()
player.showInputDialog("Enter a custom URL for the playarea image", "", function(newURL)
playAreaApi.updateSurface(newURL)
end)
end, 0.15)
end
-- click function for the download button in the preview window
function onClick_download(player)
local params = library[contentToShow][currentListItem]
params.player = player
placeholder_download(params)
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
-- the download button on the placeholder objects calls this to directly initiate a download
---@param params Table contains url and guid of replacement object
function placeholder_download(params)
2023-08-27 21:09:46 -04:00
local url = SOURCE_REPO .. '/' .. params.url
2024-02-04 10:51:51 -05:00
requestObj = WebRequest.get(url, function (request) contentDownloadCallback(request, params) end)
2023-08-27 21:09:46 -04:00
startLuaCoroutine(Global, 'downloadCoroutine')
end
2024-02-04 10:51:51 -05:00
function downloadCoroutine()
-- show progress bar
UI.setAttribute('download_progress', 'active', true)
-- update progress bar
while requestObj do
UI.setAttribute('download_progress', 'percentage', requestObj.download_progress * 100)
coroutine.yield(0)
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
UI.setAttribute('download_progress', 'percentage', 100)
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- wait 30 frames
for i = 1, 30 do
coroutine.yield(0)
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
-- hide progress bar
UI.setAttribute('download_progress', 'active', false)
-- hide download window
if xmlVisibility.downloadWindow then
xmlVisibility.downloadWindow = false
UI.hide('downloadWindow')
2024-01-06 21:32:07 -05:00
end
2024-02-04 10:51:51 -05:00
return 1
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- spawns a bag that contains every object from the library
function onClick_downloadAll()
broadcastToAll("Download initiated - this will take a few minutes!")
-- hide download window
if xmlVisibility.downloadWindow then
xmlVisibility.downloadWindow = false
UI.hide('downloadWindow')
2024-01-06 21:32:07 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
startLuaCoroutine(Global, "coroutineDownloadAll")
end
function coroutineDownloadAll()
local JSON = [[
{
"Name": "Bag",
"Transform": {
"posX": {{POSX}},
"posY": 2,
"posZ": -95,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 1.0,
"scaleY": 1.0,
"scaleZ": 1.0
},
"Nickname": "{{NICKNAME}}",
"Bag": {
"Order": 0
},
"ContainedObjects": [
]]
local posx = -45.0
local downloadedItems = 0
local skippedItems = 0
-- loop through the library to add content
for contentType, objectList in pairs(library) do
broadcastToAll("Downloading " .. contentType .. "...")
local contained = ""
for _, params in ipairs(objectList) do
local request = WebRequest.get(SOURCE_REPO .. '/' .. params.url)
local start = os.time()
while true do
if request.is_done then
contained = contained .. request.text .. ","
downloadedItems = downloadedItems + 1
break
-- time-out if item can't be loaded in 5s
elseif request.is_error or (os.time() - start) > 5 then
skippedItems = skippedItems + 1
break
end
coroutine.yield(0)
end
end
local JSONCopy = JSON
JSONCopy = JSONCopy .. contained .. "]}"
JSONCopy = JSONCopy:gsub("{{POSX}}", posx)
JSONCopy = JSONCopy:gsub("{{NICKNAME}}", contentType)
spawnObjectJSON({json = JSONCopy})
posx = posx + 3
2024-01-06 21:32:07 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
broadcastToAll(downloadedItems .. " objects downloaded.", "Green")
broadcastToAll(skippedItems .. " objects had a time-out / error.", "Orange")
return 1
2023-08-27 21:09:46 -04:00
end
2020-12-06 09:42:32 -05:00
2024-02-04 10:51:51 -05:00
-- spawns a placeholder box for the selected object
function onClick_spawnPlaceholder()
-- get object references
local item = library[contentToShow][currentListItem]
local dummy = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlaceholderBoxDummy")
-- error handling
if not item.boxsize or item.boxsize == "" or not item.boxart or item.boxart == "" then
print("Error loading object.")
return
2024-01-06 21:32:07 -05:00
end
2020-12-06 09:42:32 -05:00
2024-02-04 10:51:51 -05:00
-- get data for placeholder
local spawnPos = {-39.5, 2, -87}
2020-12-06 09:42:32 -05:00
2024-02-04 10:51:51 -05:00
local meshTable = {
big = "https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/advboxes/core_h_MSH.obj",
small = "https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/advboxes/tuckbox_h_MSH.obj",
wide = "http://cloud-3.steamusercontent.com/ugc/2278324073260846176/33EFCAF30567F8756F665BE5A2A6502E9C61C7F7/"
}
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local scaleTable = {
big = {1.00, 0.14, 1.00},
small = {2.21, 0.46, 2.42},
wide = {2.00, 0.11, 1.69}
}
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local placeholder = spawnObject({
type = "Custom_Model",
position = spawnPos,
rotation = {0, 270, 0},
scale = scaleTable[item.boxsize],
})
placeholder.setCustomObject({
mesh = meshTable[item.boxsize],
diffuse = item.boxart,
material = 3
})
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
placeholder.setColorTint({1, 1, 1, 71/255})
placeholder.setName(item.name)
placeholder.setDescription("by " .. (item.author or "Unknown"))
placeholder.setGMNotes(item.url)
placeholder.setLuaScript(dummy.getLuaScript())
Player.getPlayers()[1].pingTable(spawnPos)
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- hide download window
if xmlVisibility.downloadWindow then
xmlVisibility.downloadWindow = false
UI.hide('downloadWindow')
2024-01-06 21:32:07 -05:00
end
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
-- toggles the visibility of the respective UI
---@param player LuaPlayer Player that triggered this
---@param title String Name of the UI to toggle
2023-08-27 21:09:46 -04:00
function onClick_toggleUi(player, title)
if title == "Navigation Overlay" then
navigationOverlayApi.cycleVisibility(player.color)
return
2024-02-04 10:51:51 -05:00
-- hide the playareaGallery if visible
elseif title == "downloadWindow" and xmlVisibility.playAreaGallery then
onClick_toggleUi(_, "playAreaGallery")
-- hide the downloadWindow if visible
elseif title == "playAreaGallery" and xmlVisibility.downloadWindow then
onClick_toggleUi(_, "downloadWindow")
2023-04-22 16:56:01 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
if xmlVisibility[title] then
-- small delay to allow button click sounds to play
Wait.time(function() UI.hide(title) end, 0.1)
2023-08-27 21:09:46 -04:00
else
2024-02-04 10:51:51 -05:00
UI.show(title)
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
xmlVisibility[title] = not xmlVisibility[title]
2023-08-27 21:09:46 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- forwards the call to the onClick function
function togglePlayAreaGallery()
onClick_toggleUi(_, "playAreaGallery")
2023-08-27 21:09:46 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- updates the preview window
function updatePreviewWindow()
local item = library[contentToShow][currentListItem]
local tempImage = "http://cloud-3.steamusercontent.com/ugc/2115061845788345842/2CD6ABC551555CCF58F9D0DDB7620197BA398B06/"
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- set default image if not defined
if item.boxsize == nil or item.boxsize == "" or item.boxart == nil or item.boxart == "" then
item.boxsize = "big"
item.boxart = "http://cloud-3.steamusercontent.com/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/"
2024-01-06 21:32:07 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
UI.setValue("previewTitle", item.name)
UI.setValue("previewAuthor", "by " .. (item.author or "- Author not found -"))
UI.setValue("previewDescription", item.description or "- Description not found -")
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- update mask according to size (hardcoded values to align image in mask)
local maskData = {}
if item.boxsize == "big" then
maskData = {
image = "box-cover-mask-big",
width = "870",
height = "435",
offsetXY = "154 60"
2024-01-06 21:32:07 -05:00
}
2024-02-04 10:51:51 -05:00
elseif item.boxsize == "small" then
maskData = {
image = "box-cover-mask-small",
width = "792",
height = "594",
offsetXY = "135 13"
}
elseif item.boxsize == "wide" then
maskData = {
image = "box-cover-mask-wide",
width = "756",
height = "630",
offsetXY = "-190 -70"
}
2023-04-22 16:56:01 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- loading empty image as placeholder until real image is loaded
UI.setAttribute("previewArtImage", "image", tempImage)
-- insert the image itself
UI.setAttribute("previewArtImage", "image", item.boxart)
UI.setAttributes("previewArtMask", maskData)
end
2021-10-18 15:54:27 -04:00
2024-02-04 10:51:51 -05:00
-- formats the json response from the webrequest into a key-value lua table
-- strips the prefix from the community content items
function formatLibrary(json_response)
library = {}
library["campaigns"] = json_response.campaigns
library["scenarios"] = json_response.scenarios
library["extras"] = json_response.extras
library["fanmadeCampaigns"] = {}
library["fanmadeScenarios"] = {}
library["fanmadePlayerCards"] = {}
2024-01-06 21:32:07 -05:00
2024-02-04 10:51:51 -05:00
for _, item in ipairs(json_response.community) do
local identifier = nil
for str in string.gmatch(item.name, "([^:]+)") do
if not identifier then
-- grab the first part to know the content type
identifier = str
else
-- update the name without the content type
item.name = str
break
end
2021-10-18 15:54:27 -04:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
if identifier == "Fan Investigators" then
table.insert(library["fanmadePlayerCards"], item)
elseif identifier == "Fan Campaign" then
table.insert(library["fanmadeCampaigns"], item)
elseif identifier == "Fan Scenario" then
table.insert(library["fanmadeScenarios"], item)
2024-01-06 21:32:07 -05:00
end
end
2023-08-27 21:09:46 -04:00
end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- updates the window content to the requested content
function updateDownloadItemList()
2023-08-27 21:09:46 -04:00
if not library then return end
2020-11-28 13:23:58 -05:00
2024-02-04 10:51:51 -05:00
-- addition of list items according to library file
local globalXml = UI.getXmlTable()
local contentList = getXmlTableElementById(globalXml, 'contentList')
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
contentList.children = {}
for i, v in ipairs(library[contentToShow]) do
table.insert(contentList.children,
{
tag = "Panel",
attributes = { id = "panel" .. i },
children = {
tag = 'Text',
value = v.name,
attributes = {
id = contentToShow .. "_" .. i,
onClick = 'onClick_select',
alignment = 'MiddleLeft'
}
}
})
2022-12-13 14:02:30 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
contentList.attributes.height = #contentList.children * 27
UI.setXmlTable(globalXml)
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
-- select the first item
Wait.time(onClick_select, 0.2)
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- called after the webrequest of downloading an item
-- deletes the placeholder and spawns the downloaded item
function contentDownloadCallback(request, params)
requestObj = nil
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- error handling
2023-08-27 21:09:46 -04:00
if request.is_error or request.response_code ~= 200 then
2024-02-04 10:51:51 -05:00
print('Error: ' .. request.error)
return
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- initiate content spawning
local spawnTable = { json = request.text }
if params.replace then
local replacedObject = getObjectFromGUID(params.replace)
if replacedObject then
spawnTable.position = replacedObject.getPosition()
spawnTable.rotation = replacedObject.getRotation()
spawnTable.scale = replacedObject.getScale()
destroyObject(replacedObject)
2024-01-06 21:32:07 -05:00
end
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- if position is undefined, get empty position
if not spawnTable.position then
spawnTable.rotation = { 0, 270, 0}
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local pos = getValidSpawnPosition()
if pos then
spawnTable.position = pos
2023-08-27 21:09:46 -04:00
else
2024-02-04 10:51:51 -05:00
broadcastToAll("Please make space in the area below the tentacle stand in the upper middle of the table and try again.", "Red")
return
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- if spawned from menu, move the camera and/or ping the table
if params.name then
spawnTable["callback_function"] = function(obj)
Wait.time(function()
-- move camera
if params.player then
params.player.lookAt({
position = obj.getPosition(),
pitch = 65,
yaw = 90,
distance = 65
})
end
-- ping object
local pingPlayer = params.player or Player.getPlayers()[1]
pingPlayer.pingTable(obj.getPosition())
end, 0.1)
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
if pcall(function() spawnObjectJSON(spawnTable) end) then
print('Object loaded.')
else
print('Error loading object.')
end
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
-- gets the first empty position to spawn a custom content object safely
function getValidSpawnPosition()
local potentialSpawnPositionX = { 65, 50, 35 }
local potentialSpawnPositionY = 1.5
local potentialSpawnPositionZ = { 35, 21, 7, -7, -21, -35 }
for i, posX in ipairs(potentialSpawnPositionX) do
for j, posZ in ipairs(potentialSpawnPositionZ) do
local pos = {
x = posX,
y = potentialSpawnPositionY,
z = posZ,
}
if checkPositionForContentSpawn(pos) then
return pos
end
2024-01-06 21:32:07 -05:00
end
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
return nil
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
-- checks whether something is in the specified position
-- returns true if empty
function checkPositionForContentSpawn(checkPos)
local searchResult = searchLib.atPosition(checkPos)
-- first hit is the table surface, additional hits means something is there
return #searchResult == 1
end
-- downloading of the library file
function libraryDownloadCallback(request)
2023-08-27 21:09:46 -04:00
if request.is_error or request.response_code ~= 200 then
print('error: ' .. request.error)
2024-02-04 10:51:51 -05:00
return
2021-10-18 15:54:27 -04:00
end
2024-02-04 10:51:51 -05:00
local json_response = nil
if pcall(function () json_response = JSON.decode(request.text) end) then
formatLibrary(json_response)
updateDownloadItemList()
else
print('error parsing downloaded library')
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
end
2024-02-04 10:51:51 -05:00
-- loops through an XML table and returns the specified object
---@param ui Table XmlTable (get this via getXmlTable)
---@param id String Id of the object to return
function getXmlTableElementById(ui, id)
2023-08-27 21:09:46 -04:00
for _, obj in ipairs(ui) do
if obj.attributes and obj.attributes.id and obj.attributes.id == id then return obj end
if obj.children then
2024-02-04 10:51:51 -05:00
local result = getXmlTableElementById(obj.children, id)
2023-08-27 21:09:46 -04:00
if result then return result end
2023-04-22 16:56:01 -04:00
end
2021-10-18 15:54:27 -04:00
end
2023-08-27 21:09:46 -04:00
return nil
end
2021-10-18 15:54:27 -04:00
2023-08-27 21:09:46 -04:00
---------------------------------------------------------
-- Option Panel related functionality
---------------------------------------------------------
-- called by toggling an option
function onClick_toggleOption(_, id)
local state = self.UI.getAttribute(id, "isOn")
-- flip state (and handle stupid "False" value)
if state == "False" then
state = true
else
state = false
2023-04-22 16:56:01 -04:00
end
2021-10-18 15:54:27 -04:00
2023-08-27 21:09:46 -04:00
self.UI.setAttribute(id, "isOn", state)
applyOptionPanelChange(id, state)
end
2024-02-04 10:51:51 -05:00
-- color selection for playArea
function onClick_playAreaConnectionColor(player, _, id)
player.showColorDialog(optionPanel[id], function(color)
applyOptionPanelChange(id, color)
end)
end
2023-08-27 21:09:46 -04:00
-- called by the language selection dropdown
function languageSelected(_, selectedIndex, id)
optionPanel[id] = LANGUAGES[tonumber(selectedIndex) + 1].code
end
-- returns the ID (position in the table) for a provided language code
function returnLanguageId(code)
for index, tbl in ipairs(LANGUAGES) do
if tbl.code == code then
return index
2023-04-22 16:56:01 -04:00
end
2021-10-18 15:54:27 -04:00
end
2023-08-27 21:09:46 -04:00
end
2021-10-18 15:54:27 -04:00
2024-02-04 10:51:51 -05:00
-- called by the resource counter selection dropdown
function resourceCounterSelected(_, selectedIndex, id)
optionPanel[id] = RESOURCE_OPTIONS[tonumber(selectedIndex) + 1]
end
-- returns the ID for the provided option name
function returnResourceCounterId(name)
for index, optionName in ipairs(RESOURCE_OPTIONS) do
if optionName == name then
return index
2024-01-06 21:32:07 -05:00
end
end
2024-02-04 10:51:51 -05:00
end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- called by the playermat removal selection dropdown
function playermatRemovalSelected(player, selectedIndex, id)
if selectedIndex == "0" then return end
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local matColorList = { "White", "Orange", "Green", "Red" }
local matColor = matColorList[tonumber(selectedIndex)]
local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
if mat then
-- confirmation dialog about deletion
player.pingTable(mat.getPosition())
player.showConfirmDialog("Do you really want to remove " .. matColor .. "'s playermat and related objects? This can't be reversed.", function() removePlayermat(matColor) end)
else
-- info dialog that it is already deleted
player.showInfoDialog(matColor .. "'s playermat has already been removed.")
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- set selected value back to first option
UI.setAttribute(id, "value", 0)
end
-- removes a playermat and all related objects from play
---@param matColor String Color of the playermat to remove
function removePlayermat(matColor)
local matObjects = guidReferenceApi.getObjectsByOwner(matColor)
if not matObjects.Playermat then return end
-- remove action tokens
local actionTokens = playmatApi.searchAroundPlaymat(matColor, "isActionToken")
for _, obj in ipairs(actionTokens) do
obj.destruct()
2023-04-22 16:56:01 -04:00
end
2024-02-04 10:51:51 -05:00
-- remove mat owned objects
for _, obj in pairs(matObjects) do
obj.destruct()
2024-01-06 21:32:07 -05:00
end
2024-02-04 10:51:51 -05:00
end
2024-01-06 21:32:07 -05:00
2023-08-27 21:09:46 -04:00
-- sets the option panel to the correct state (corresponding to 'optionPanel')
function updateOptionPanelState()
for id, optionValue in pairs(optionPanel) do
if id == "cardLanguage" and type(optionValue) == "string" then
local dropdownId = returnLanguageId(optionValue) - 1
UI.setAttribute(id, "value", dropdownId)
2024-02-04 10:51:51 -05:00
elseif id == "useResourceCounters" and type(optionValue) == "string" then
local dropdownId = returnResourceCounterId(optionValue) - 1
UI.setAttribute(id, "value", dropdownId)
elseif id == "playAreaConnectionColor" then
UI.setAttribute(id, "color", "#" .. Color.new(optionValue):toHex())
2023-08-27 21:09:46 -04:00
elseif (type(optionValue) == "boolean" and optionValue)
or (type(optionValue) == "string" and optionValue)
or (type(optionValue) == "table" and #optionValue ~= 0) then
UI.setAttribute(id, "isOn", true)
2021-10-18 15:54:27 -04:00
else
2023-08-27 21:09:46 -04:00
UI.setAttribute(id, "isOn", "False")
2021-10-18 15:54:27 -04:00
end
end
2023-08-27 21:09:46 -04:00
end
2021-10-18 15:54:27 -04:00
2023-08-27 21:09:46 -04:00
-- handles the applying of option selections and calls the respective functions based
---@param id String ID of the option that was selected or deselected
---@param state Boolean State of the option (true = enabled)
function applyOptionPanelChange(id, state)
-- option: Snap tags
if id == "useSnapTags" then
playmatApi.setLimitSnapsByType(state, "All")
optionPanel[id] = state
-- option: Draw 1 button
elseif id == "showDrawButton" then
playmatApi.showDrawButton(state, "All")
optionPanel[id] = state
-- option: Clickable clue counters
elseif id == "useClueClickers" then
playmatApi.clickableClues(state, "All")
optionPanel[id] = state
-- update master clue counter
2024-02-04 10:51:51 -05:00
local counter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "MasterClueCounter")
counter.setVar("useClickableCounters", state)
-- option: Play area snap tags
elseif id == "playAreaConnections" then
playAreaApi.setConnectionDrawState(state)
optionPanel[id] = state
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- option: Play area connection color
elseif id == "playAreaConnectionColor" then
playAreaApi.setConnectionColor(state)
UI.setAttribute(id, "color", "#" .. Color.new(state):toHex())
2023-08-27 21:09:46 -04:00
optionPanel[id] = state
-- option: Play area snap tags
elseif id == "playAreaSnapTags" then
2024-02-04 10:51:51 -05:00
playAreaApi.setLimitSnapsByType(state)
2023-08-27 21:09:46 -04:00
optionPanel[id] = state
-- option: Show Title on placing scenarios
elseif id == "showTitleSplash" then
optionPanel[id] = state
2024-02-04 10:51:51 -05:00
-- option: Change custom playarea image on setup
elseif id == "changePlayAreaImage" then
optionPanel[id] = state
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- option: Show clean up helper
elseif id == "showCleanUpHelper" then
optionPanel[id] = spawnOrRemoveHelper(state, "Clean Up Helper", {-66, 1.6, 46})
-- option: Show hand helper for each player
elseif id == "showHandHelper" then
2024-02-04 10:51:51 -05:00
local helperName = "Hand Helper"
local spawnData = playmatApi.getHelperSpawnData("All", helperName)
local i = 0
for color, data in pairs(spawnData) do
i = i + 1
optionPanel[id][i] = spawnOrRemoveHelper(state, helperName, data.position, data.rotation, color)
2021-10-18 15:54:27 -04:00
end
2023-08-27 21:09:46 -04:00
-- option: Show search assistant for each player
elseif id == "showSearchAssistant" then
2024-02-04 10:51:51 -05:00
local helperName = "Search Assistant"
local spawnData = playmatApi.getHelperSpawnData("All", helperName)
local i = 0
for color, data in pairs(spawnData) do
i = i + 1
optionPanel[id][i] = spawnOrRemoveHelper(state, helperName, data.position, data.rotation, color)
2021-10-18 15:54:27 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- option: Show attachment helper
elseif id == "showAttachmentHelper" then
optionPanel[id] = spawnOrRemoveHelper(state, "Attachment Helper", {-62, 1.4, 0})
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- option: Show CYOA campaign guides
elseif id == "showCYOA" then
2024-02-04 10:51:51 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "CYOA Campaign Guides", {39, 1.3, -20})
2023-08-27 21:09:46 -04:00
-- option: Show displacement tool
elseif id == "showDisplacementTool" then
optionPanel[id] = spawnOrRemoveHelper(state, "Displacement Tool", {-57, 1.6, 46})
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- handler for spawn / remove functions of helper objects
---@param state Boolean Contains the state of the option: true = spawn it, false = remove it
---@param name String Name of the helper object
---@param position Vector Position of the object (where it will spawn)
---@param rotation Vector Rotation of the object for spawning (default: {0, 270, 0})
2024-02-04 10:51:51 -05:00
---@param owner String Owner of the object (defaults to "Mythos")
2023-08-27 21:09:46 -04:00
---@return. GUID of the spawnedObj (or nil if object was removed)
2024-02-04 10:51:51 -05:00
function spawnOrRemoveHelper(state, name, position, rotation, owner)
2023-08-27 21:09:46 -04:00
if (type(state) == "table" and #state == 0) then
return removeHelperObject(name)
elseif state then
Player.getPlayers()[1].pingTable(position)
2024-02-04 10:51:51 -05:00
local spawnedGUID = spawnHelperObject(name, position, rotation).getGUID()
local cleanName = name:gsub("%s+", "")
guidReferenceApi.editIndex(owner or "Mythos", cleanName, spawnedGUID)
return spawnedGUID
2023-08-27 21:09:46 -04:00
else
return removeHelperObject(name)
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- copies the specified tool (by name) from the option panel source bag
---@param name String Name of the object that should be copied
---@param position Table Desired position of the object
2024-02-04 10:51:51 -05:00
---@param rotation Table Desired rotation of the object (defaults to object's rotation)
2023-08-27 21:09:46 -04:00
function spawnHelperObject(name, position, rotation)
2024-02-04 10:51:51 -05:00
local sourceBag = guidReferenceApi.getObjectByOwnerAndType("Mythos","OptionPanelSource")
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- error handling for missing sourceBag
if not sourceBag then
broadcastToAll("Option panel source bag could not be found!", "Red")
return
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
local spawnTable = { position = position }
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- only overrride rotation if there is one provided (object's rotation used instead)
if rotation then
spawnTable.rotation = rotation
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
for _, obj in ipairs(sourceBag.getData().ContainedObjects) do
if obj["Nickname"] == name then
spawnTable.data = obj
spawnTable.callback_function = function(spawnedObj)
Wait.time(function() spawnedObj.setLock(true) end, 2)
end
return spawnObjectData(spawnTable)
2023-01-29 19:31:52 -05:00
end
end
end
2023-08-27 21:09:46 -04:00
-- removes the specified tool (by name)
---@param name String Object that should be removed
function removeHelperObject(name)
-- links objects name to the respective option name (to grab the GUID for removal)
local referenceTable = {
["Clean Up Helper"] = "showCleanUpHelper",
["Hand Helper"] = "showHandHelper",
["Search Assistant"] = "showSearchAssistant",
["Displacement Tool"] = "showDisplacementTool",
["Attachment Helper"] = "showAttachmentHelper",
["CYOA Campaign Guides"] = "showCYOA"
2023-01-29 19:31:52 -05:00
}
2023-08-27 21:09:46 -04:00
local data = optionPanel[referenceTable[name]]
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- if there is a GUID stored, remove that object
if type(data) == "string" then
local obj = getObjectFromGUID(data)
if obj then obj.destruct() end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- if it is a table (e.g. for the "Hand Helper", remove all of them)
elseif type(data) == "table" then
for _, guid in pairs(data) do
local obj = getObjectFromGUID(guid)
if obj then obj.destruct() end
2023-01-29 19:31:52 -05:00
end
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- loads saved options
function loadSettings(newOptions)
optionPanel = newOptions
updateOptionPanelState()
for id, state in pairs(optionPanel) do
applyOptionPanelChange(id, state)
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
end
2023-04-22 16:56:01 -04:00
2023-08-27 21:09:46 -04:00
-- loads the default options
function onClick_defaultSettings()
for id, _ in pairs(optionPanel) do
local state = false
-- override for settings that are enabled by default
if id == "useSnapTags" or id == "showTitleSplash" then
state = true
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
applyOptionPanelChange(id, state)
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- clean reset of variables
2023-08-27 21:09:46 -04:00
optionPanel = {
2024-02-04 10:51:51 -05:00
cardLanguage = "en",
playAreaConnectionColor = { 0.4, 0.4, 0.4, 1 },
playAreaConnections = true,
2023-08-27 21:09:46 -04:00
playAreaSnapTags = true,
showAttachmentHelper = false,
showCleanUpHelper = false,
showCYOA = false,
showDisplacementTool = false,
showDrawButton = false,
showHandHelper = {},
showSearchAssistant = {},
showTitleSplash = true,
useClueClickers = false,
2024-02-04 10:51:51 -05:00
useResourceCounters = "disabled",
2023-08-27 21:09:46 -04:00
useSnapTags = true
}
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- update UI
updateOptionPanelState()
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- splash scenario title on setup
function titleSplash(scenarioName)
if optionPanel['showTitleSplash'] then
-- if there's any ongoing title being displayed, hide it and cancel the waiting function
if hideTitleSplashWaitFunctionId then
Wait.stop(hideTitleSplashWaitFunctionId)
hideTitleSplashWaitFunctionId = nil
UI.setAttribute('title_splash', 'active', false)
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- display scenario name and set a 4 seconds (2 seconds animation and 2 seconds on screen)
-- wait timer to hide the scenario name
UI.setValue('title_splash_text', scenarioName)
UI.show('title_splash')
hideTitleSplashWaitFunctionId = Wait.time(function()
UI.hide('title_splash')
hideTitleSplashWaitFunctionId = nil
end, 4)
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
soundCubeApi.playSoundByName("Deep Bell")
2023-04-22 16:56:01 -04:00
end
2023-08-27 21:09:46 -04:00
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
---------------------------------------------------------
-- Update notification related functionality
---------------------------------------------------------
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- grabs the latest mod version and release notes from GitHub (called onLoad())
function getModVersion()
WebRequest.get(SOURCE_REPO .. '/modversion.json', compareVersion)
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- compares the modversion with GitHub and possibly shows the update notification
function compareVersion(request)
if request.is_error then
log(request.error)
return
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
-- global variable to make it accessible for other functions
modMeta = JSON.decode(request.text)
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
-- stop here if on latest or newer version
if convertVersionToNumber(MOD_VERSION) >= convertVersionToNumber(modMeta["latestVersion"]) then return end
2024-01-06 21:32:29 -05:00
2023-08-27 21:09:46 -04:00
-- stop here if "don't show again" was clicked for this version before
if acknowledgedUpgradeVersions[modMeta["latestVersion"]] then return end
updateNotificationLoading()
-- delay to avoid lagging during onLoad()
Wait.time(function() UI.show("FinnIcon") end, 1)
end
2024-02-04 10:51:51 -05:00
-- converts a version number to a string
---@param version String Version number, separated by dots (e.g. 3.3.1)
function convertVersionToNumber(version)
local major, minor, patch = string.match(version, "(%d+)%.(%d+)%.(%d+)")
return major * 100 + minor * 10 + patch
end
2023-08-27 21:09:46 -04:00
-- updates the XML update notification based on the mod metadata
function updateNotificationLoading()
-- grab data
local highlights = modMeta["releaseHighlights"]
-- concatenate the release highlights
local highlightText = "• " .. highlights[1]
for i, entry in pairs(highlights) do
if i ~= 1 then
highlightText = highlightText .. "\n• " .. entry
2023-04-22 16:56:01 -04:00
end
2023-01-29 19:31:52 -05:00
end
2024-01-06 21:32:29 -05:00
2023-08-27 21:09:46 -04:00
-- update the XML UI
2024-02-04 10:51:51 -05:00
UI.setValue("notificationHeader", "New version available: " .. modMeta["latestVersion"])
2023-08-27 21:09:46 -04:00
UI.setValue("releaseHighlightText", highlightText)
UI.setAttribute("highlightRow", "preferredHeight", 20*#highlights)
UI.setAttribute("updateNotification", "height", 20*#highlights + 125)
end
2023-01-29 19:31:52 -05:00
2023-08-27 21:09:46 -04:00
-- close / don't show again buttons on the update notification
function onClick_notification(_, parameter)
if parameter == "dontShowAgain" then
-- this variable tracks if "don't show again" was pressed for a version
acknowledgedUpgradeVersions[modMeta["latestVersion"]] = true
2023-01-29 19:31:52 -05:00
end
2023-08-27 21:09:46 -04:00
UI.hide("FinnIcon")
UI.hide("updateNotification")
2024-02-04 10:51:51 -05:00
xmlVisibility["updateNotification"] = false
2023-08-27 21:09:46 -04:00
end
end)
2024-02-04 10:51:51 -05:00
__bundle_register("chaosbag/BlessCurseManagerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
2023-08-27 21:09:46 -04:00
do
2024-02-04 10:51:51 -05:00
local BlessCurseManagerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2023-01-29 19:31:52 -05:00
2024-02-04 10:51:51 -05:00
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
2024-01-06 21:32:29 -05:00
end
2023-08-27 21:09:46 -04:00
2024-02-04 10:51:51 -05:00
-- 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)
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- updates the internal count (called by cards that seal bless/curse tokens)
BlessCurseManagerApi.sealedToken = function(type, guid)
getManager().call("sealedToken", { type = type, guid = guid })
2023-01-29 19:31:52 -05:00
end
2024-02-04 10:51:51 -05:00
-- updates the internal count (called by cards that seal bless/curse tokens)
BlessCurseManagerApi.releasedToken = function(type, guid)
getManager().call("releasedToken", { type = type, guid = guid })
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- updates the internal count (called by cards that seal bless/curse tokens)
BlessCurseManagerApi.returnedToken = function(type, guid)
getManager().call("returnedToken", { type = type, guid = guid })
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- 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)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- 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)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
-- adds bless / curse sealing to the hovered card
---@param playerColor String Color of the player to show the broadcast to
---@param hoveredObject TTSObject Hovered object
BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject)
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
return BlessCurseManagerApi
end
end)
__bundle_register("core/token/TokenSpawnTrackerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local TokenSpawnTracker = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
2024-01-06 21:32:29 -05:00
2024-02-04 10:51:51 -05:00
local function getSpawnTracker()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSpawnTracker")
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
TokenSpawnTracker.hasSpawnedTokens = function(cardGuid)
return getSpawnTracker().call("hasSpawnedTokens", cardGuid)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
TokenSpawnTracker.markTokensSpawned = function(cardGuid)
return getSpawnTracker().call("markTokensSpawned", cardGuid)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
TokenSpawnTracker.resetTokensSpawned = function(cardGuid)
return getSpawnTracker().call("resetTokensSpawned", cardGuid)
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
TokenSpawnTracker.resetAllAssetAndEvents = function()
return getSpawnTracker().call("resetAllAssetAndEvents")
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
TokenSpawnTracker.resetAllLocations = function()
return getSpawnTracker().call("resetAllLocations")
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
TokenSpawnTracker.resetAll = function()
return getSpawnTracker().call("resetAll")
2024-01-06 21:32:29 -05:00
end
2024-02-04 10:51:51 -05:00
return TokenSpawnTracker
2023-01-29 19:31:52 -05:00
end
2022-12-13 14:02:30 -05:00
end)
2022-12-13 18:01:00 -05:00
return __bundle_require("__root")