SCED/src/core/Global.ttslua

1024 lines
33 KiB
Plaintext
Raw Normal View History

2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- general setup
---------------------------------------------------------
ENCOUNTER_DECK_POS = {-3.93, 1, 5.76}
ENCOUNTER_DECK_DISCARD_POSITION = {-3.85, 1, 10.38}
-- optionPanel data
optionPanel = {}
2022-11-12 08:42:06 -05:00
-- GUID of data helper
2022-11-14 05:03:06 -05:00
DATA_HELPER_GUID = "708279"
2022-11-12 08:42:06 -05:00
-- GUIDs that will not be interactable (e.g. parts of the table)
2022-11-14 05:03:06 -05:00
local NOT_INTERACTABLE = {
2023-01-08 10:07:38 -05:00
"6161b4", -- Decoration-Map
"721ba2", -- PlayArea
"9f334f", -- MythosArea
"463022", -- Panel behind tentacle stand
"f182ee", -- InvestigatorCount
"7bff34", -- Tentacle stand
2023-01-07 20:38:26 -05:00
"8646eb", -- horizontal border left
"75937e", -- horizontal border right
"612072", -- vertical border left
"975c39", -- vertical border right
2022-11-12 08:42:06 -05:00
}
2022-11-14 05:03:06 -05:00
local chaosTokens = {}
local chaosTokensLastMat = nil
local IS_RESHUFFLING = false
2022-12-12 06:18:45 -05:00
local bagSearchers = {}
2023-03-09 18:19:01 -05:00
local MAT_COLORS = {"White", "Orange", "Green", "Red"}
local hideTitleSplashWaitFunctionId = nil
2022-12-12 06:18:45 -05:00
local playmatAPI = require("playermat/PlaymatApi")
2023-01-01 20:23:34 -05:00
local tokenManager = require("core/token/TokenManager")
2023-01-02 05:53:29 -05:00
local playAreaAPI = require("core/PlayAreaApi")
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- data for tokens
---------------------------------------------------------
2022-11-12 08:42:06 -05: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}}
}
2022-11-12 08:42:06 -05:00
IMAGE_TOKEN_MAP = {
["https://i.imgur.com/nEmqjmj.png"] = "Elder Sign",
["https://i.imgur.com/uIx8jbY.png"] = "+1",
["https://i.imgur.com/btEtVfd.png"] = "0",
["https://i.imgur.com/w3XbrCC.png"] = "-1",
["https://i.imgur.com/bfTg2hb.png"] = "-2",
["https://i.imgur.com/yfs8gHq.png"] = "-3",
["https://i.imgur.com/qrgGQRD.png"] = "-4",
["https://i.imgur.com/3Ym1IeG.png"] = "-5",
["https://i.imgur.com/c9qdSzS.png"] = "-6",
["https://i.imgur.com/4WRD42n.png"] = "-7",
["https://i.imgur.com/9t3rPTQ.png"] = "-8",
["https://i.imgur.com/stbBxtx.png"] = "Skull",
["https://i.imgur.com/VzhJJaH.png"] = "Cultist",
["https://i.imgur.com/1plY463.png"] = "Tablet",
["https://i.imgur.com/ttnspKt.png"] = "Elder Thing",
["https://i.imgur.com/lns4fhz.png"] = "Auto-fail",
["http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/"] = "Bless",
["http://cloud-3.steamusercontent.com/ugc/1655601092778636039/2A25BD38E8C44701D80DD96BF0121DA21843672E/"] = "Curse",
["http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/"] = "Frost"
}
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- data for chaos token stat tracker
---------------------------------------------------------
2022-11-14 05:03:06 -05:00
local maxSquid = 0
MAT_GUID_TO_COLOUR = {
["8b081b"] = "White",
["bd0ff4"] = "Orange",
["383d8b"] = "Green",
["0840d5"] = "Red"
}
2022-11-14 05:03:06 -05:00
local personalStats = {
["8b081b"] = {},
["bd0ff4"] = {},
["383d8b"] = {},
["0840d5"] = {}
}
2022-11-14 05:03:06 -05:00
local overallStats = {
-- cultist
["https://i.imgur.com/VzhJJaH.png"] = 0,
-- skull
["https://i.imgur.com/stbBxtx.png"] = 0,
-- tablet
["https://i.imgur.com/1plY463.png"] = 0,
-- curse
["http://cloud-3.steamusercontent.com/ugc/1655601092778636039/2A25BD38E8C44701D80DD96BF0121DA21843672E/"] = 0,
-- tentacle
["https://i.imgur.com/lns4fhz.png"] = 0,
-- minus eight
["https://i.imgur.com/9t3rPTQ.png"] = 0,
-- minus seven
["https://i.imgur.com/4WRD42n.png"] = 0,
-- minus six
["https://i.imgur.com/c9qdSzS.png"] = 0,
-- minus five
["https://i.imgur.com/3Ym1IeG.png"] = 0,
-- minus four
["https://i.imgur.com/qrgGQRD.png"] = 0,
-- minus three
["https://i.imgur.com/yfs8gHq.png"] = 0,
-- minus two
["https://i.imgur.com/bfTg2hb.png"] = 0,
-- minus one
["https://i.imgur.com/w3XbrCC.png"] = 0,
-- zero
["https://i.imgur.com/btEtVfd.png"] = 0,
-- plus one
["https://i.imgur.com/uIx8jbY.png"] = 0,
-- elder thing
["https://i.imgur.com/ttnspKt.png"] = 0,
-- bless
["http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/"] = 0,
-- elder sign
["https://i.imgur.com/nEmqjmj.png"] = 0,
-- frost
["http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/"] = 0,
}
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- general code
---------------------------------------------------------
-- saving state of optionPanel to restore later
2022-12-12 06:18:45 -05:00
function onSave() return JSON.encode({ optionPanel = optionPanel }) end
function onLoad(savedData)
if savedData then
2022-12-12 06:18:45 -05:00
loadedData = JSON.decode(savedData)
optionPanel = loadedData.optionPanel
updateOptionPanelState()
else
2022-12-12 06:18:45 -05:00
print("Saved state could not be found!")
end
2022-11-12 08:42:06 -05:00
for _, guid in ipairs(NOT_INTERACTABLE) do
2023-01-05 07:03:15 -05:00
local obj = getObjectFromGUID(guid)
if obj ~= nil then obj.interactable = false end
end
2022-11-12 08:42:06 -05:00
math.randomseed(os.time())
end
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- encounter card drawing
---------------------------------------------------------
2022-11-12 08:42:06 -05:00
function isDeck(x) return x.tag == 'Deck' end
2022-11-12 08:42:06 -05:00
function isCardOrDeck(x) return x.tag == 'Card' or x.tag == 'Deck' end
-- 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)
2023-01-05 09:59:07 -05:00
chaosbag = findChaosBag()
if object == chaosbag then
bagSearchers[playerColor] = true
end
end
-- 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)
2023-01-05 09:59:07 -05:00
chaosbag = findChaosBag()
if object == chaosbag then
bagSearchers[playerColor] = nil
end
end
-- 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)
playAreaAPI.tryObjectEnterContainer(container, object)
return true
end
2022-11-12 08:42:06 -05:00
function drawEncountercard(params)
local position = params[1]
local rotation = params[2]
local alwaysFaceUp = params[3]
local card
local items = findInRadiusBy(ENCOUNTER_DECK_POS, 4, isCardOrDeck)
if #items > 0 then
2022-11-12 08:42:06 -05:00
for _, v in ipairs(items) do
if v.tag == 'Deck' then
card = v.takeObject({index = 0})
break
end
end
-- we didn't find the deck so just pull the first thing we did find
if card == nil then card = items[1] end
actualEncounterCardDraw(card, params)
2022-11-12 08:42:06 -05:00
else
-- nothing here, time to reshuffle
reshuffleEncounterDeck(params)
end
end
function actualEncounterCardDraw(card, params)
local position = params[1]
local rotation = params[2]
local alwaysFaceUp = params[3]
local faceUpRotation = 0
if not alwaysFaceUp then
2022-11-14 05:03:06 -05:00
if getObjectFromGUID(DATA_HELPER_GUID).call('checkHiddenCard', card.getName()) then
faceUpRotation = 180
end
end
card.setPositionSmooth(position, false, false)
2022-11-12 08:42:06 -05:00
card.setRotationSmooth({0, rotation.y, faceUpRotation}, false, false)
end
function reshuffleEncounterDeck(params)
-- finishes moving the deck back and draws a card
local function move(deck)
2022-11-12 08:42:06 -05:00
deck.setPositionSmooth({ENCOUNTER_DECK_POS[1], ENCOUNTER_DECK_POS[2] + 2, ENCOUNTER_DECK_POS[3]}, false, true)
actualEncounterCardDraw(deck.takeObject({index=0}), params)
2022-11-12 08:42:06 -05:00
Wait.time(function() IS_RESHUFFLING = false end, 1)
end
-- bail out if we're mid reshuffle
2022-11-12 08:42:06 -05:00
if IS_RESHUFFLING then return end
local discarded = findInRadiusBy(ENCOUNTER_DECK_DISCARD_POSITION, 4, isDeck)
if #discarded > 0 then
IS_RESHUFFLING = true
local deck = discarded[1]
2022-11-12 08:42:06 -05:00
if not deck.is_face_down then deck.flip() end
deck.shuffle()
Wait.time(|| move(deck), 0.3)
else
2022-11-12 08:42:06 -05:00
printToAll("Couldn't find encounter discard pile to reshuffle.", {1, 0, 0})
end
end
function findInRadiusBy(pos, radius, filter)
local objList = Physics.cast({
origin = pos,
direction = {0, 1, 0},
type = 2,
size = {radius, radius, radius},
max_distance = 0
})
local filteredList = {}
for _, obj in ipairs(objList) do
if filter and filter(obj.hit_object) then
table.insert(filteredList, obj.hit_object)
end
end
return filteredList
end
---------------------------------------------------------
-- chaos token drawing
---------------------------------------------------------
-- checks scripting zone for chaos bag
function findChaosBag()
for _, item in ipairs(getObjectFromGUID("83ef06").getObjects()) do
2023-01-05 09:59:07 -05:00
if item.getDescription() == "Chaos Bag" then
return item
end
end
end
2022-11-14 05:03:06 -05:00
function returnChaosTokens()
for _, token in pairs(chaosTokens) do
2022-11-12 08:42:06 -05:00
if token ~= nil then chaosbag.putObject(token) end
end
2022-11-14 05:03:06 -05:00
chaosTokens = {}
2022-11-12 08:42:06 -05:00
end
-- 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
end
end
return true
end
function drawChaostoken(params)
if not canTouchChaosTokens() then
return
end
local mat = params[1]
local tokenOffset = params[2]
local isRightClick = params[3]
2023-01-05 09:59:07 -05:00
chaosbag = findChaosBag()
2022-11-12 08:42:06 -05:00
-- return token(s) on other playmat first
2022-11-14 05:03:06 -05:00
if chaosTokensLastMat ~= nil and chaosTokensLastMat ~= mat and #chaosTokens ~= 0 then
returnChaosTokens()
chaosTokensLastMat = nil
2022-11-12 08:42:06 -05:00
return
end
2022-11-12 08:42:06 -05:00
2022-11-14 05:03:06 -05:00
chaosTokensLastMat = mat
-- if we have left clicked and have no tokens OR if we have right clicked
2022-11-14 05:03:06 -05:00
if isRightClick or #chaosTokens == 0 then
2022-11-12 08:42:06 -05:00
if #chaosbag.getObjects() == 0 then return end
chaosbag.shuffle()
2022-11-12 08:42:06 -05:00
-- add the token to the list, compute new position based on list length
2022-11-14 05:03:06 -05:00
tokenOffset[1] = tokenOffset[1] + (0.17 * #chaosTokens)
local token = chaosbag.takeObject({
index = 0,
2022-11-12 08:42:06 -05:00
position = mat.positionToWorld(tokenOffset),
rotation = mat.getRotation(),
2022-11-14 05:03:06 -05:00
callback_function = function(obj) trackChaosToken(obj, mat.getGUID()) end
})
2022-11-14 05:03:06 -05:00
chaosTokens[#chaosTokens + 1] = token
return
else
2022-11-14 05:03:06 -05:00
returnChaosTokens()
end
end
2022-11-12 08:42:06 -05: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
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- chaos token stat tracker
---------------------------------------------------------
2022-11-14 05:03:06 -05:00
function trackChaosToken(token, matGUID)
local image = token.getCustomObject().image
overallStats[image] = (overallStats[image] or 0) + 1
personalStats[matGUID][image] = (personalStats[matGUID][image] or 0) + 1
end
2022-11-14 05:03:06 -05:00
function handleStatTrackerClick(_, _, isRightClick)
2022-11-12 08:42:06 -05:00
if isRightClick then
2022-11-14 05:03:06 -05:00
resetChaosTokenStats()
else
printChaosTokenStats()
end
end
function resetChaosTokenStats()
for key, _ in pairs(overallStats) do
overallStats[key] = 0
end
for playerKey, _ in pairs(personalStats) do
for key, value in pairs(overallStats) do
personalStats[playerKey][key] = value
2022-11-12 08:42:06 -05:00
end
2022-11-14 05:03:06 -05:00
end
end
function printChaosTokenStats()
local squidKing = "Nobody"
printToAll("")
printToAll("Overall Stats")
printToAll("------------------------------")
printNonZeroTokenPairs(overallStats)
printToAll("")
printToAll("Individual Stats")
printToAll("------------------------------")
for matGUID, _ in pairs(personalStats) do
local playerColour = MAT_GUID_TO_COLOUR[matGUID]
local playerSquidCount = personalStats[matGUID]["https://i.imgur.com/lns4fhz.png"] or 0
local playerName = playerColour
if Player[playerColour].seated then
playerName = Player[playerColour].steam_name
2022-11-12 08:42:06 -05:00
end
2022-11-14 05:03:06 -05:00
printToAll(playerName .. " Stats", playerColour)
printNonZeroTokenPairs(personalStats[matGUID])
2022-11-14 05:03:06 -05:00
if playerSquidCount > maxSquid then
squidKing = playerName
maxSquid = playerSquidCount
2022-11-12 08:42:06 -05:00
end
end
2022-11-14 05:03:06 -05:00
printToAll(squidKing .. " is an auto-fail magnet.", {255, 0, 0})
end
2022-11-12 08:42:06 -05:00
function printNonZeroTokenPairs(theTable)
for key, value in pairs(theTable) do
2022-11-14 05:03:06 -05:00
if value ~= 0 then
printToAll(IMAGE_TOKEN_MAP[key] .. ': ' .. tostring(value))
end
end
end
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- Difficulty selector script
---------------------------------------------------------
-- called for button creation on the difficulty selectors
---@param object object Usually "self"
---@param key string Name of the scenario
function createSetupButtons(args)
2022-11-12 08:42:06 -05:00
local data = getDataValue('modeData', args.key)
if data ~= nil then
local buttonParameters = {}
buttonParameters.function_owner = args.object
buttonParameters.position = {0, 0.1, -0.15}
buttonParameters.scale = {0.47, 1, 0.47}
buttonParameters.height = 200
buttonParameters.width = 1150
buttonParameters.color = {0.87, 0.8, 0.7}
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
2022-11-12 08:42:06 -05:00
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
2022-11-12 08:42:06 -05:00
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
2022-11-12 08:42:06 -05:00
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
2022-11-12 08:42:06 -05:00
if data.standalone ~= nil then
buttonParameters.label = "Standalone"
buttonParameters.click_function = "standaloneClick"
args.object.createButton(buttonParameters)
end
2022-11-12 08:42:06 -05:00
end
end
-- called for adding chaos tokens
---@param object object Usually "self"
---@param key string Name of the scenario
---@param mode string diffculty (e.g. "hard" or "expert")
2022-11-12 08:42:06 -05:00
function fillContainer(args)
2023-01-05 09:59:07 -05:00
chaosbag = findChaosBag()
2022-11-12 08:42:06 -05:00
if chaosbag ~= nil then
local data = getDataValue('modeData', args.key)
if data == nil then return end
2022-11-12 08:42:06 -05:00
local value = data[args.mode]
if value == nil or value.token == nil then return end
2022-11-12 08:42:06 -05:00
local pos = chaosbag.getPosition()
if args.object ~= nil then
pos = args.object.getPosition()
end
2022-11-12 08:42:06 -05:00
-- empty the chaos bag
for _, item in ipairs(chaosbag.getObjects()) do
destroyObject(chaosbag.takeObject({}))
end
2022-11-12 08:42:06 -05:00
for _, token in ipairs(value.token) do
local obj = spawnChaosToken(token, pos)
if obj ~= nil then
chaosbag.putObject(obj)
end
end
2022-11-12 08:42:06 -05:00
if value.append ~= nil then
for _, token in ipairs(value.append) do
local obj = spawnChaosToken(token, pos)
if obj ~= nil then
2022-11-12 08:42:06 -05:00
chaosbag.putObject(obj)
end
2022-11-12 08:42:06 -05:00
end
end
2022-11-12 08:42:06 -05:00
-- randomly choose tokens for specific Carcosa scenarios in standalone
if value.random then
local n = #value.random
if n > 0 then
for _, token in ipairs(value.random[math.random(1, n)]) do
local obj = spawnChaosToken(token, pos)
if obj ~= nil then
chaosbag.putObject(obj)
end
end
2022-11-12 08:42:06 -05:00
end
end
if value.message then
broadcastToAll(value.message)
end
if value.warning then
broadcastToAll(value.warning, { 1, 0.5, 0.5 })
end
2022-11-12 08:42:06 -05:00
end
end
2022-11-12 08:42:06 -05:00
function getDataValue(storage, key)
2022-11-14 05:03:06 -05:00
local data = getObjectFromGUID(DATA_HELPER_GUID).getTable(storage)
2022-11-12 08:42:06 -05: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
2022-11-12 08:42:06 -05:00
end
end
2022-11-12 08:42:06 -05:00
function spawnChaosToken(id, pos)
2022-11-14 05:03:06 -05:00
local url = getChaosTokenImageURL(id)
2022-11-12 08:42:06 -05:00
if url ~= '' then
local obj = spawnObject({
type = 'Custom_Tile',
position = {pos.x, pos.y + 3, pos.z},
rotation = {0, 260, 0}
})
obj.setCustomObject({
type = 2,
image = url,
thickness = 0.1
})
obj.scale {0.81, 1, 0.81}
obj.setName(getTokenName({ url=url }))
return obj
end
end
-- chaos bag needs this for renaming chaos tokens
2022-11-12 08:42:06 -05:00
function getTokenName(params)
local name = IMAGE_TOKEN_MAP[params.url]
if name == nil then name = "" end
return name
end
-- returns the image url for a chaos token (identified by the "id")
2022-11-14 05:03:06 -05:00
function getChaosTokenImageURL(id)
2022-11-12 08:42:06 -05:00
if id == 'p1' then return 'https://i.imgur.com/uIx8jbY.png' end
if id == '0' then return 'https://i.imgur.com/btEtVfd.png' end
if id == 'm1' then return 'https://i.imgur.com/w3XbrCC.png' end
if id == 'm2' then return 'https://i.imgur.com/bfTg2hb.png' end
if id == 'm3' then return 'https://i.imgur.com/yfs8gHq.png' end
if id == 'm4' then return 'https://i.imgur.com/qrgGQRD.png' end
if id == 'm5' then return 'https://i.imgur.com/3Ym1IeG.png' end
if id == 'm6' then return 'https://i.imgur.com/c9qdSzS.png' end
if id == 'm7' then return 'https://i.imgur.com/4WRD42n.png' end
if id == 'm8' then return 'https://i.imgur.com/9t3rPTQ.png' end
if id == 'skull' then return 'https://i.imgur.com/stbBxtx.png' end
if id == 'cultist' then return 'https://i.imgur.com/VzhJJaH.png' end
if id == 'tablet' then return 'https://i.imgur.com/1plY463.png' end
if id == 'elder' then return 'https://i.imgur.com/ttnspKt.png' end
if id == 'red' then return 'https://i.imgur.com/lns4fhz.png' end
if id == 'blue' then return 'https://i.imgur.com/nEmqjmj.png' end
if id == 'frost' then return 'http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/' end
return ''
end
2022-11-12 08:42:06 -05:00
---------------------------------------------------------
-- Content Importing and XML functions
---------------------------------------------------------
local source_repo = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main'
local library = nil
local request_obj
function onClick_refreshList()
2022-11-12 08:42:06 -05:00
local request = WebRequest.get(source_repo .. '/library.json', completed_list_update)
request_obj = request
startLuaCoroutine(Global, 'downloadCoroutine')
end
function onClick_select(player, params)
params = JSON.decode(urldecode(params))
2022-11-12 08:42:06 -05:00
local url = source_repo .. '/' .. params.url
local request = WebRequest.get(url, function (request) complete_obj_download(request, params) end )
request_obj = request
startLuaCoroutine(Global, 'downloadCoroutine')
end
function onClick_load()
UI.show('progress_display')
UI.hide('load_button')
end
function onClick_toggleUi(_, title)
UI.hide('optionPanel')
UI.hide('load_ui')
-- when same button is clicked or close window button is pressed, don't open UI
if UI.getValue('title') ~= title and title ~= 'Hidden' then
UI.setValue('title', title)
2022-12-08 06:01:10 -05:00
if title == "Options" then
UI.show('optionPanel')
else
update_window_content(title)
UI.show('load_ui')
end
else
UI.setValue('title', "Hidden")
end
end
function downloadCoroutine()
while request_obj do
UI.setAttribute('download_progress', 'percentage', request_obj.download_progress * 100)
coroutine.yield(0)
end
return 1
end
function update_list(objects)
local ui = UI.getXmlTable()
local update_height = find_tag_with_id(ui, 'ui_update_height')
local update_children = find_tag_with_id(update_height.children, 'ui_update_point')
update_children.children = {}
2022-11-12 08:42:06 -05:00
for _, v in ipairs(objects) do
local s = JSON.encode(v);
table.insert(update_children.children,
2022-11-12 08:42:06 -05:00
{ tag = 'Text',
value = v.name,
2022-11-12 08:42:06 -05:00
attributes = { onClick = 'onClick_select(' .. urlencode(JSON.encode(v)) .. ')', alignment = 'MiddleLeft' }
})
end
update_height.attributes.height = #(update_children.children) * 24
UI.setXmlTable(ui)
end
function update_window_content(new_title)
2022-11-12 08:42:06 -05:00
if not library then return end
if new_title == 'Campaigns' then
update_list(library.campaigns)
elseif new_title == 'Standalone Scenarios' then
update_list(library.scenarios)
elseif new_title == 'Investigators' then
update_list(library.investigators)
elseif new_title == 'Community Content' then
update_list(library.community)
elseif new_title == 'Extras' then
update_list(library.extras)
else
update_list({})
end
end
function complete_obj_download(request, params)
assert(request.is_done)
if request.is_error or request.response_code ~= 200 then
print('error: ' .. request.error)
else
2022-11-12 08:42:06 -05:00
if pcall(function()
local replaced_object
pcall(function()
if params.replace then
replaced_object = getObjectFromGUID(params.replace)
end
end)
local json = request.text
if replaced_object then
local pos = replaced_object.getPosition()
local rot = replaced_object.getRotation()
destroyObject(replaced_object)
Wait.frames(function()
spawnObjectJSON({json = json, position = pos, rotation = rot})
end, 1)
else
spawnObjectJSON({json = json})
end
end) then
print('Object loaded.')
else
print('Error loading object.')
end
end
request_obj = nil
UI.setAttribute('download_progress', 'percentage', 100)
end
-- the download button on the placeholder objects calls this to directly initiate a download
2022-11-12 08:42:06 -05:00
-- params is a table with url and guid of replacement object, which happens to match what onClick_select wants
function placeholder_download(params)
onClick_select(nil, JSON.encode(params))
end
function completed_list_update(request)
assert(request.is_done)
if request.is_error or request.response_code ~= 200 then
print('error: ' .. request.error)
else
local json_response = nil
if pcall(function () json_response = JSON.decode(request.text) end) then
library = json_response
update_window_content(UI.getValue('title'))
else
print('error parsing downloaded library')
end
end
request_obj = nil
UI.setAttribute('download_progress', 'percentage', 100)
end
function find_tag_with_id(ui, id)
2022-11-12 08:42:06 -05: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
local result = find_tag_with_id(obj.children, id)
if result then return result end
end
end
return nil
end
function urlencode(str)
2022-11-12 08:42:06 -05:00
local str = string.gsub(str, "([^A-Za-z0-9-_.~])",
function (c) return string.format("%%%02X", string.byte(c)) end)
return str
end
function urldecode(str)
2022-11-12 08:42:06 -05:00
local str = string.gsub(str, "%%(%x%x)",
function (h) return string.char(tonumber(h, 16)) end)
return str
end
---------------------------------------------------------
-- 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
end
self.UI.setAttribute(id, "isOn", state)
applyOptionPanelChange(id, state)
end
-- sets the option panel to the correct state (corresponding to 'optionPanel')
function updateOptionPanelState()
for id, enabled in pairs(optionPanel) do
2022-12-21 19:42:32 -05:00
if (type(enabled) == "boolean" and enabled)
or (type(enabled) == "string" and enabled)
or (type(enabled) == "table" and #enabled ~= 0) then
self.UI.setAttribute(id, "isOn", true)
else
self.UI.setAttribute(id, "isOn", "False")
end
end
end
-- 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
getObjectFromGUID("4a3aa4").setVar("useClickableCounters", state)
2023-01-01 20:23:34 -05:00
-- option: Clickable resource counters
elseif id == "useResourceCounters" then
optionPanel[id] = state
2023-01-03 17:04:59 -05:00
-- option: Play area snap tags
elseif id == "playAreaSnapTags" then
2023-01-02 05:53:29 -05:00
playAreaAPI.setLimitSnapsByType(state)
optionPanel[id] = state
-- option: Show Title on placing scenarios
elseif id == "showTitleSplash" then
optionPanel[id] = state
-- option: Show token arranger
elseif id == "showTokenArranger" then
2023-01-01 11:55:21 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "Token Arranger", {-42.3, 1.6, -46.5})
-- option: Show clean up helper
elseif id == "showCleanUpHelper" then
2023-01-08 07:01:48 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "Clean Up Helper", {-66, 1.6, 46})
-- option: Show hand helper for each player
elseif id == "showHandHelper" then
2023-03-09 18:19:01 -05:00
for i, color in ipairs(MAT_COLORS) do
local pos = playmatAPI.transformLocalPosition({0.05, 0, -1.182}, color)
local rot = playmatAPI.returnRotation(color)
optionPanel[id][i] = spawnOrRemoveHelper(state, "Hand Helper", pos, rot)
end
2023-01-05 20:02:24 -05:00
-- option: Show search assistant for each player
elseif id == "showSearchAssistant" then
2023-03-09 18:19:01 -05:00
for i, color in ipairs(MAT_COLORS) do
local pos = playmatAPI.transformLocalPosition({-0.3, 0, -1.182}, color)
local rot = playmatAPI.returnRotation(color)
optionPanel[id][i] = spawnOrRemoveHelper(state, "Search Assistant", pos, rot)
end
2023-01-05 20:02:24 -05:00
-- option: Show chaos bag manager
elseif id == "showChaosBagManager" then
2023-01-08 07:01:48 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "Chaos Bag Manager", {-66, 1.6, -49.5})
2022-12-30 16:37:05 -05:00
-- option: Show attachment helper
elseif id == "showAttachmentHelper" then
optionPanel[id] = spawnOrRemoveHelper(state, "Attachment Helper", {-64, 1.4, 0})
-- option: Show navigation overlay
elseif id == "showNavigationOverlay" then
2023-01-01 11:55:21 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "jaqenZann's Navigation Overlay", {-11.7, 1.6, -15})
2022-12-30 16:37:05 -05:00
-- option: Show CYOA campaign guides
elseif id == "showCYOA" then
2023-01-08 07:01:48 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "CYOA Campaign Guides", {65, 1.6, -11})
2022-12-30 16:37:05 -05:00
-- option: Show custom playmat images
elseif id == "showCustomPlaymatImages" then
2023-01-08 07:01:48 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "Custom Playmat Images", {67.5, 1.6, 37})
2022-12-30 16:37:05 -05:00
-- option: Show displacement tool
elseif id == "showDisplacementTool" then
2023-01-08 07:01:48 -05:00
optionPanel[id] = spawnOrRemoveHelper(state, "Displacement Tool", {-57, 1.6, 46})
end
end
-- 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})
2022-12-30 16:37:05 -05:00
---@return. GUID of the spawnedObj (or nil if object was removed)
2023-02-28 12:22:03 -05:00
function spawnOrRemoveHelper(state, name, position, rotation)
if state then
Player.getPlayers()[1].pingTable(position)
2023-02-28 12:22:03 -05:00
return spawnHelperObject(name, position, rotation).getGUID()
else
return removeHelperObject(name)
end
end
-- copies the specified tool (by name) from the option panel source bag
---@param name String Name of the object that should be copied
2022-12-30 16:37:05 -05:00
---@param position Table Desired position of the object
2023-02-28 12:22:03 -05:00
function spawnHelperObject(name, position, rotation)
local sourceBag = getObjectFromGUID("830bd0")
-- error handling for missing sourceBag
if not sourceBag then
broadcastToAll("Option panel source bag could not be found!", "Red")
return
end
2023-03-01 08:42:10 -05:00
local spawnTable = {position = position}
-- only overrride rotation if there is one provided (object's rotation used instead)
if rotation then
spawnTable.rotation = rotation
end
for _, obj in ipairs(sourceBag.getData().ContainedObjects) do
if obj["Nickname"] == name then
spawnTable.data = obj
return spawnObjectData(spawnTable)
end
end
end
-- 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 = {
["Token Arranger"] = "showTokenArranger",
["Clean Up Helper"] = "showCleanUpHelper",
["Hand Helper"] = "showHandHelper",
2023-01-05 20:02:24 -05:00
["Search Assistant"] = "showSearchAssistant",
["Chaos Bag Manager"] = "showChaosBagManager",
2022-12-30 16:37:05 -05:00
["jaqenZann's Navigation Overlay"] = "showNavigationOverlay",
["Displacement Tool"] = "showDisplacementTool",
["Custom Playmat Images"] = "showCustomPlaymatImages",
["Attachment Helper"] = "showAttachmentHelper",
["CYOA Campaign Guides"] = "showCYOA"
}
local data = optionPanel[referenceTable[name]]
-- if there is a GUID stored, remove that object
if type(data) == "string" then
local obj = getObjectFromGUID(data)
if obj then obj.destruct() end
-- 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
end
end
end
-- 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
end
applyOptionPanelChange(id, state)
end
-- clean reset of variable
optionPanel = {
2023-01-03 18:45:48 -05:00
playAreaSnapTags = true,
2023-01-03 16:00:28 -05:00
showAttachmentHelper = false,
showCleanUpHelper = false,
showChaosBagManager = false,
2023-01-03 16:00:28 -05:00
showCustomPlaymatImages = false,
showCYOA = false,
showDisplacementTool = false,
showDrawButton = false,
2023-01-03 16:00:28 -05:00
showHandHelper = {},
showNavigationOverlay = false,
2023-01-05 20:02:24 -05:00
showSearchAssistant = {},
showTitleSplash = true,
showTokenArranger = false,
2023-01-03 16:00:28 -05:00
useClueClickers = false,
useSnapTags = true
}
-- update UI
updateOptionPanelState()
end
-- 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
-- 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)
end
end