SCED/src/core/Global.ttslua
Chr1Z 7c396e58fc
Merge pull request #167 from argonui/accessories-positions-update
Option Panel: Accessories spawn position update
2023-01-09 06:59:24 +01:00

1029 lines
33 KiB
Plaintext

---------------------------------------------------------
-- general setup
---------------------------------------------------------
ENCOUNTER_DECK_POS = {-3.93, 1, 5.76}
ENCOUNTER_DECK_DISCARD_POSITION = {-3.85, 1, 10.38}
-- optionPanel data
optionPanel = {}
-- GUID of data helper
DATA_HELPER_GUID = "708279"
-- GUID of fan-made accessories bag (also just called "barrel")
BARREL_GUID = "aa8b38"
-- GUIDs that will not be interactable (e.g. parts of the table)
local NOT_INTERACTABLE = {
"6161b4", -- Decoration-Map
"721ba2", -- PlayArea
"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
}
local chaosTokens = {}
local chaosTokensLastMat = nil
local IS_RESHUFFLING = false
local bagSearchers = {}
local hideTitleSplashWaitFunctionId = nil
local playmatAPI = require("playermat/PlaymatApi")
local tokenManager = require("core/token/TokenManager")
local playAreaAPI = require("core/PlayAreaApi")
---------------------------------------------------------
-- data for tokens
---------------------------------------------------------
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}}
}
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"
}
---------------------------------------------------------
-- data for chaos token stat tracker
---------------------------------------------------------
local maxSquid = 0
MAT_GUID_TO_COLOUR = {
["8b081b"] = "White",
["bd0ff4"] = "Orange",
["383d8b"] = "Green",
["0840d5"] = "Red"
}
local personalStats = {
["8b081b"] = {},
["bd0ff4"] = {},
["383d8b"] = {},
["0840d5"] = {}
}
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,
}
---------------------------------------------------------
-- general code
---------------------------------------------------------
-- saving state of optionPanel to restore later
function onSave() return JSON.encode({ optionPanel = optionPanel }) end
function onLoad(savedData)
if savedData then
loadedData = JSON.decode(savedData)
optionPanel = loadedData.optionPanel
updateOptionPanelState()
else
print("Saved state could not be found!")
end
for _, guid in ipairs(NOT_INTERACTABLE) do
local obj = getObjectFromGUID(guid)
if obj ~= nil then obj.interactable = false end
end
math.randomseed(os.time())
end
---------------------------------------------------------
-- encounter card drawing
---------------------------------------------------------
function isDeck(x) return x.tag == 'Deck' end
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)
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)
chaosbag = findChaosBag()
if object == chaosbag then
bagSearchers[playerColor] = nil
end
end
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
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)
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
if getObjectFromGUID(DATA_HELPER_GUID).call('checkHiddenCard', card.getName()) then
faceUpRotation = 180
end
end
card.setPositionSmooth(position, false, false)
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)
deck.setPositionSmooth({ENCOUNTER_DECK_POS[1], ENCOUNTER_DECK_POS[2] + 2, ENCOUNTER_DECK_POS[3]}, false, true)
actualEncounterCardDraw(deck.takeObject({index=0}), params)
Wait.time(function() IS_RESHUFFLING = false end, 1)
end
-- bail out if we're mid reshuffle
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]
if not deck.is_face_down then deck.flip() end
deck.shuffle()
Wait.time(|| move(deck), 0.3)
else
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
if item.getDescription() == "Chaos Bag" then
return item
end
end
end
function returnChaosTokens()
for _, token in pairs(chaosTokens) do
if token ~= nil then chaosbag.putObject(token) end
end
chaosTokens = {}
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]
chaosbag = findChaosBag()
-- return token(s) on other playmat first
if chaosTokensLastMat ~= nil and chaosTokensLastMat ~= mat and #chaosTokens ~= 0 then
returnChaosTokens()
chaosTokensLastMat = nil
return
end
chaosTokensLastMat = mat
-- if we have left clicked and have no tokens OR if we have right clicked
if isRightClick or #chaosTokens == 0 then
if #chaosbag.getObjects() == 0 then return end
chaosbag.shuffle()
-- add the token to the list, compute new position based on list length
tokenOffset[1] = tokenOffset[1] + (0.17 * #chaosTokens)
local token = chaosbag.takeObject({
index = 0,
position = mat.positionToWorld(tokenOffset),
rotation = mat.getRotation(),
callback_function = function(obj) trackChaosToken(obj, mat.getGUID()) end
})
chaosTokens[#chaosTokens + 1] = token
return
else
returnChaosTokens()
end
end
---------------------------------------------------------
-- token spawning
---------------------------------------------------------
-- DEPRECATED. Use TokenManager instead.
-- Spawns a single token.
---@param params Table. Array with arguments to the method. 1 = position, 2 = type, 3 = rotation
function spawnToken(params)
return tokenManager.spawnToken(params[1], params[2], params[3])
end
---------------------------------------------------------
-- chaos token stat tracker
---------------------------------------------------------
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
function handleStatTrackerClick(_, _, isRightClick)
if isRightClick then
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
end
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
end
printToAll(playerName .. " Stats", playerColour)
printNonZeroTokenPairs(personalStats[matGUID])
if playerSquidCount > maxSquid then
squidKing = playerName
maxSquid = playerSquidCount
end
end
printToAll(squidKing .. " is an auto-fail magnet.", {255, 0, 0})
end
function printNonZeroTokenPairs(theTable)
for key, value in pairs(theTable) do
if value ~= 0 then
printToAll(IMAGE_TOKEN_MAP[key] .. ': ' .. tostring(value))
end
end
end
---------------------------------------------------------
-- 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)
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
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
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")
function fillContainer(args)
chaosbag = findChaosBag()
if chaosbag ~= nil then
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 pos = chaosbag.getPosition()
if args.object ~= nil then
pos = args.object.getPosition()
end
-- empty the chaos bag
for _, item in ipairs(chaosbag.getObjects()) do
destroyObject(chaosbag.takeObject({}))
end
for _, token in ipairs(value.token) do
local obj = spawnChaosToken(token, pos)
if obj ~= nil then
chaosbag.putObject(obj)
end
end
if value.append ~= nil then
for _, token in ipairs(value.append) do
local obj = spawnChaosToken(token, pos)
if obj ~= nil then
chaosbag.putObject(obj)
end
end
end
-- 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
end
end
if value.message then
broadcastToAll(value.message)
end
if value.warning then
broadcastToAll(value.warning, { 1, 0.5, 0.5 })
end
end
end
function getDataValue(storage, key)
local data = getObjectFromGUID(DATA_HELPER_GUID).getTable(storage)
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
function spawnChaosToken(id, pos)
local url = getChaosTokenImageURL(id)
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
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")
function getChaosTokenImageURL(id)
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
---------------------------------------------------------
-- 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()
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))
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)
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 = {}
for _, v in ipairs(objects) do
local s = JSON.encode(v);
table.insert(update_children.children,
{ tag = 'Text',
value = v.name,
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)
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
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
-- 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)
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)
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)
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
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)
-- option: Clickable resource counters
elseif id == "useResourceCounters" then
optionPanel[id] = state
-- option: Play area snap tags
elseif id == "playAreaSnapTags" then
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
-- delete previously pulled out tokens
for _, token in ipairs(getObjectsWithTag("to_be_deleted")) do token.destruct() end
optionPanel[id] = spawnOrRemoveHelper(state, "Token Arranger", {-42.3, 1.6, -46.5})
-- 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
optionPanel[id][1] = spawnOrRemoveHelper(state, "Hand Helper", {-50.85, 1.6, 7.32}, {0, 270, 0}, "White")
optionPanel[id][2] = spawnOrRemoveHelper(state, "Hand Helper", {-50.85, 1.6, -24.88}, {0, 270, 0}, "Orange")
optionPanel[id][3] = spawnOrRemoveHelper(state, "Hand Helper", {-39.13, 1.6, 22.45}, {0, 000, 0}, "Green")
optionPanel[id][4] = spawnOrRemoveHelper(state, "Hand Helper", {-21.57, 1.6, -22.45}, {0, 180, 0}, "Red")
-- option: Show search assistant for each player
elseif id == "showSearchAssistant" then
optionPanel[id][1] = spawnOrRemoveHelper(state, "Search Assistant", {-50.85, 1.6, 10.25}, {0, 270, 0})
optionPanel[id][2] = spawnOrRemoveHelper(state, "Search Assistant", {-50.85, 1.6, -21.95}, {0, 270, 0})
optionPanel[id][3] = spawnOrRemoveHelper(state, "Search Assistant", {-36.20, 1.6, 22.45}, {0, 000, 0})
optionPanel[id][4] = spawnOrRemoveHelper(state, "Search Assistant", {-24.50, 1.6, -22.45}, {0, 180, 0})
-- option: Show chaos bag manager
elseif id == "showChaosBagManager" then
optionPanel[id] = spawnOrRemoveHelper(state, "Chaos Bag Manager", {-66, 1.6, -49.5})
-- 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
optionPanel[id] = spawnOrRemoveHelper(state, "jaqenZann's Navigation Overlay", {-11.7, 1.6, -15})
-- option: Show CYOA campaign guides
elseif id == "showCYOA" then
optionPanel[id] = spawnOrRemoveHelper(state, "CYOA Campaign Guides", {65, 1.6, -11})
-- option: Show custom playmat images
elseif id == "showCustomPlaymatImages" then
optionPanel[id] = spawnOrRemoveHelper(state, "Custom Playmat Images", {67.5, 1.6, 37})
-- option: Show displacement tool
elseif id == "showDisplacementTool" then
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})
---@param color String This is only needed for correctly setting the color of the "Hand Helper"
---@return. GUID of the spawnedObj (or nil if object was removed)
function spawnOrRemoveHelper(state, name, position, rotation, color)
if state then
Player.getPlayers()[1].pingTable(position)
return spawnHelperObject(name, position, rotation, color).getGUID()
else
return removeHelperObject(name)
end
end
-- copies the specified tool (by name) from the barrel
---@param name String Name of the object that should be copied
---@param position Table Desired position of the object
function spawnHelperObject(name, position, rotation, color)
local barrel = getObjectFromGUID(BARREL_GUID)
-- error handling for missing barrel
if not barrel then
broadcastToAll("'Barrel' with fan-made accessories could not be found!", "Red")
return
end
local spawnTable = {
position = position,
callback_function = function(object)
if name == "Hand Helper" then
Wait.time(function() object.call("externalColorChange", color) end, 0.1)
elseif name == "Token Arranger" then
Wait.time(function() object.call("layout") end, 0.1)
end
end
}
-- only overrride rotation if there is one provided (object's rotation used instead)
if rotation then
spawnTable.rotation = rotation
end
for _, obj in ipairs(barrel.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",
["Search Assistant"] = "showSearchAssistant",
["Chaos Bag Manager"] = "showChaosBagManager",
["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 = {
playAreaSnapTags = true,
showAttachmentHelper = false,
showCleanUpHelper = false,
showChaosBagManager = false,
showCustomPlaymatImages = false,
showCYOA = false,
showDisplacementTool = false,
showDrawButton = false,
showHandHelper = {},
showNavigationOverlay = false,
showSearchAssistant = {},
showTitleSplash = true,
showTokenArranger = false,
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', scenarioName)
UI.show('title_splash')
hideTitleSplashWaitFunctionId = Wait.time(function()
UI.hide('title_splash')
hideTitleSplashWaitFunctionId = nil
end, 4)
end
end