Merge pull request #218 from argonui/token-metadata

Token Arranger: token metadata support and misc. updates
This commit is contained in:
Chr1Z 2023-03-01 18:04:47 +01:00 committed by GitHub
commit ceef316adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 349 additions and 283 deletions

View File

@ -49,8 +49,8 @@
"normalized": "location"
},
{
"displayed": "to_be_deleted",
"normalized": "to_be_deleted"
"displayed": "tempToken",
"normalized": "temptoken"
},
{
"displayed": "Minicard",

View File

@ -34,18 +34,21 @@
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "require(\"accessories/TokenArranger\")",
"LuaScriptState_path": "Fan-MadeAccessories.aa8b38/TokenArranger.022907.luascriptstate",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Custom_Token",
"Nickname": "Token Arranger",
"Snap": true,
"Sticky": true,
"Tags": [
"TokenArranger"
],
"Tooltip": true,
"Transform": {
"posX": 22.951,
"posY": 5.242,
"posZ": -30.295,
"rotX": 1,
"posX": -42.3,
"posY": 1.53,
"posZ": -46.5,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 2,

View File

@ -1 +0,0 @@
{"":[0,11],"Auto-fail":[-100,7],"Bless":[101,8],"Cultist":[-2,4],"Curse":[-101,9],"Elder Sign":[100,2],"Elder Thing":[-4,6],"Frost":[-99,10],"Skull":[-1,3],"Tablet":[-3,5]}

View File

@ -1,20 +1,150 @@
-- names of tokens in order
-- common parameters
local buttonParameters = {}
buttonParameters.function_owner = self
buttonParameters.label = ""
buttonParameters.tooltip = "Add / Remove"
buttonParameters.color = { 0, 0, 0, 0 }
buttonParameters.width = 325
buttonParameters.height = 325
local inputParameters = {}
inputParameters.function_owner = self
inputParameters.font_size = 100
inputParameters.width = 250
inputParameters.height = inputParameters.font_size + 23
inputParameters.alignment = 3
inputParameters.validation = 2
inputParameters.tab = 2
local latestLoad = "XXX"
local updating = false
local tokenPrecedence = {}
local TOKEN_NAMES = {
"Elder Sign",
"Skull",
"Cultist",
"Tablet",
"Elder Thing",
"Auto-fail",
"Bless",
"Curse",
"Frost",
""
"Elder Sign",
"Skull",
"Cultist",
"Tablet",
"Elder Thing",
"Auto-fail",
"Bless",
"Curse",
"Frost",
""
}
-- token modifiers for sorting (and order for same modifier)
-- order starts at 2 because there is a "+1" token
local TOKEN_PRECEDENCE = {
-- saving the precedence settings and information on the most recently loaded data
function onSave()
return JSON.encode({
tokenPrecedence = tokenPrecedence,
latestLoad = latestLoad
})
end
function onLoad(saveState)
if saveState ~= nil and saveState ~= "" then
local loadedData = JSON.decode(saveState)
tokenPrecedence = loadedData.tokenPrecedence
latestLoad = loadedData.latestLoad or "XXX"
else
loadDefaultValues()
end
-- create UI
local offset = 0.725
local pos = { x = { -1.067, 0.377 }, z = -2.175 }
-- button and inputs index 0-9
for i = 1, 10 do
if i < 6 then
buttonParameters.position = { pos.x[1], 0, pos.z + i * offset }
inputParameters.position = { pos.x[1] + offset, 0.1, pos.z + i * offset }
else
buttonParameters.position = { pos.x[2], 0, pos.z + (i - 5) * offset }
inputParameters.position = { pos.x[2] + offset, 0.1, pos.z + (i - 5) * offset }
end
buttonParameters.click_function = attachIndex("tokenClick", i)
inputParameters.input_function = attachIndex2("tokenInput", i)
inputParameters.value = tokenPrecedence[TOKEN_NAMES[i]][1]
self.createButton(buttonParameters)
self.createInput(inputParameters)
end
-- index 10: "Update / Hide" button
buttonParameters.label = "Update / Hide"
buttonParameters.click_function = "layout"
buttonParameters.tooltip = "Left-Click: Update!\nRight-Click: Hide Tokens!"
buttonParameters.position = { 0.725, 0.1, 2.025 }
buttonParameters.color = { 1, 1, 1 }
buttonParameters.width = 675
buttonParameters.height = 175
self.createButton(buttonParameters)
-- reset context menu
self.addContextMenuItem("Load default values", function()
loadDefaultValues()
updateUI()
layout()
end)
-- grab token metadata from mythos area
local mythosArea = getObjectFromGUID("9f334f")
Wait.time(function() mythosArea.call("fireTokenDataChangedEvent") end, 0.5)
Wait.time(layout, 2)
end
-- delete temporary tokens when destroyed
function onDestroy() deleteCopiedTokens() end
-- layout tokens when dropped (after 2 seconds)
function onDrop() Wait.time(layout, 2) end
-- delete temporary tokens when picked up
function onPickUp() deleteCopiedTokens() end
-- helper functions to carry index
function attachIndex(click_function, index)
local fn_name = click_function .. index
_G[fn_name] = function(_, _, isRightClick)
_G[click_function](isRightClick, index)
end
return fn_name
end
function attachIndex2(input_function, index)
local fn_name = input_function .. index
_G[fn_name] = function(_, _, input, selected)
_G[input_function](input, selected, index)
end
return fn_name
end
-- click_function for buttons on chaos tokens
function tokenClick(isRightClick, index)
local change = tonumber(isRightClick and "-1" or "1")
tokenPrecedence[TOKEN_NAMES[index]][1] = tokenPrecedence[TOKEN_NAMES[index]][1] + change
self.editInput({ index = index - 1, value = tokenPrecedence[TOKEN_NAMES[index]][1] })
layout()
end
-- input_function for input_boxes
function tokenInput(input, selected, index)
if selected == false then
local num = tonumber(input)
if num ~= nil then
tokenPrecedence[TOKEN_NAMES[index]][1] = num
end
layout()
end
end
-- loads the default precedence table
function loadDefaultValues()
-- token modifiers for sorting (and order for same modifier)
-- order starts at 2 because there is a "+1" token
tokenPrecedence = {
["Elder Sign"] = { 100, 2 },
["Skull"] = { -1, 3 },
["Cultist"] = { -2, 4 },
@ -25,223 +155,128 @@ local TOKEN_PRECEDENCE = {
["Curse"] = { -101, 9 },
["Frost"] = { -99, 10 },
[""] = { 0, 11 }
}
-- common parameters
local buttonParameters = {}
buttonParameters.function_owner = self
buttonParameters.label = ""
buttonParameters.tooltip = "Add / Remove"
buttonParameters.color = { 0, 0, 0, 0 }
buttonParameters.width = 325
buttonParameters.height = 325
local inputParameters = {}
inputParameters.function_owner = self
inputParameters.font_size = 100
inputParameters.width = 250
inputParameters.height = inputParameters.font_size + 23
inputParameters.alignment = 3
inputParameters.validation = 2
inputParameters.tab = 2
updating = false
function onSave() return JSON.encode(TOKEN_PRECEDENCE) end
function onLoad(save_state)
if save_state ~= nil then
TOKEN_PRECEDENCE = JSON.decode(save_state)
end
-- create UI
local offset = 0.725
local pos = { x = { -1.067, 0.377 }, z = -2.175 }
-- button and inputs index 1-10
for i = 1, 10 do
if i < 6 then
buttonParameters.position = { pos.x[1], 0, pos.z + i * offset }
inputParameters.position = { pos.x[1] + offset, 0.1, pos.z + i * offset }
else
buttonParameters.position = { pos.x[2], 0, pos.z + (i - 5) * offset }
inputParameters.position = { pos.x[2] + offset, 0.1, pos.z + (i - 5) * offset }
end
buttonParameters.click_function = attachIndex("tokenClick", i)
inputParameters.input_function = attachIndex2("tokenInput", i)
inputParameters.value = TOKEN_PRECEDENCE[TOKEN_NAMES[i]][1]
self.createButton(buttonParameters)
self.createInput(inputParameters)
end
-- index 11: "Update / Hide" button
buttonParameters.label = "Update / Hide"
buttonParameters.click_function = "layout"
buttonParameters.tooltip = "Left-Click: Update!\nRight-Click: Hide Tokens!"
buttonParameters.position = { 0.725, 0.1, 2.025 }
buttonParameters.color = { 1, 1, 1 }
buttonParameters.width = 675
buttonParameters.height = 175
self.createButton(buttonParameters)
self.addContextMenuItem("More Information", function()
printToAll("------------------------------", "White")
printToAll("Token Arranger by Chr1Z", "Orange")
printToAll("original concept by Whimsical", "White")
end)
-- send object reference to bless/curse manager
Wait.time(function() getObjectFromGUID("5933fb").setVar("tokenArranger", self) end, 1)
}
end
function onDestroy()
deleteCopiedTokens()
-- remove object reference from bless/curse manager
getObjectFromGUID("5933fb").setVar("tokenArranger", nil)
end
function onPickUp()
deleteCopiedTokens()
end
-- helper functions to carry index
function attachIndex(click_function, index)
local fn_name = click_function .. index
_G[fn_name] = function(obj, player_color, isRightClick)
_G[click_function](obj, player_color, isRightClick, index)
end
return fn_name
end
function attachIndex2(input_function, index)
local fn_name = input_function .. index
_G[fn_name] = function(obj, player_color, input, selected)
_G[input_function](obj, player_color, input, selected, index)
end
return fn_name
end
-- click_function for buttons on chaos tokens
function tokenClick(_, _, isRightClick, index)
if not updating then
updating = true
local change = tonumber(isRightClick and "-1" or "1")
TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] = TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] + change
self.editInput({ index = index - 1, value = TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] })
layout()
end
end
-- input_function for input_boxes
function tokenInput(_, _, input, selected, index)
if selected == false and not updating then
updating = true
local num = tonumber(input)
if num ~= nil then
TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] = num
end
layout()
end
-- update input fields
function updateUI()
for i = 1, 10 do
self.editInput({ index = i - 1, value = tokenPrecedence[TOKEN_NAMES[i]][1] })
end
end
-- order function for data sorting
function token_value_comparator(left, right)
if left.value > right.value then return true
elseif right.value > left.value then return false
elseif left.order < right.order then return true
elseif right.order < left.order then return false
else return left.token.getGUID() > right.token.getGUID()
end
if left.value > right.value then
return true
elseif right.value > left.value then
return false
elseif left.order < right.order then
return true
elseif right.order < left.order then
return false
else
return left.token.getGUID() > right.token.getGUID()
end
end
-- get chaos bag from scripting zone and description
function getChaosBag()
local chaosbag = nil
local chaosbag_zone = getObjectFromGUID("83ef06")
-- error handling: scripting zone not found
if chaosbag_zone == nil then
printToAll("Zone for chaos bag detection couldn't be found.", "Red")
return nil
-- 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
for _, v in ipairs(chaosbag_zone.getObjects()) do
if v.getDescription() == "Chaos Bag" then
chaosbag = getObjectFromGUID(v.getGUID())
break
end
end
-- error handling: chaos bag not found
if chaosbag == nil then
printToAll("Chaos bag couldn't be found.", "Red")
end
return chaosbag
end
end
-- deletes previously placed tokens
function deleteCopiedTokens()
for _, token in ipairs(getObjectsWithTag("to_be_deleted")) do token.destruct() end
for _, token in ipairs(getObjectsWithTag("tempToken")) do token.destruct() end
end
-- main function (delete old tokens, clone chaos bag content, sort it and position it)
function layout(_, _, isRightClick)
deleteCopiedTokens()
if updating then return end
updating = true
deleteCopiedTokens()
-- stop here if right-clicked
if isRightClick then return end
local chaosBag = getChaosBag()
local data = {}
-- clone tokens from chaos bag (default position above trash can)
for i, obj in ipairs(chaosBag.getData().ContainedObjects) do
obj["Tags"] = { "to_be_deleted" }
local spawnedObj = spawnObjectData({
data = obj,
position = { 0.49, 3, 0 }
})
local value = tonumber(obj["Nickname"])
local precedence = TOKEN_PRECEDENCE[obj["Nickname"]]
data[i] = {
token = spawnedObj,
value = value or precedence[1]
}
if precedence ~= nil then
data[i].order = precedence[2]
else
data[i].order = value
end
end
-- sort table by value (symbols last if same value)
table.sort(data, token_value_comparator)
-- error handling for removal of token arranger
if self == nil then
for _, token in ipairs(getObjectsWithTag("to_be_deleted")) do token.destruct() end
return
end
-- laying out the tokens
local pos = self.getPosition() + Vector(3.55, -0.05, -3.95)
local location = { x = pos.x, y = pos.y, z = pos.z }
local current_value = data[1].value
for _, item in ipairs(data) do
if item.value ~= current_value then
location.x = location.x - 1.75
location.z = pos.z
current_value = item.value
end
item.token.setPosition(location)
item.token.setRotation(self.getRotation())
location.z = location.z - 1.75
end
-- stop here if right-clicked
if isRightClick then
updating = false
return
end
local chaosBag = findChaosBag()
local data = {}
-- clone tokens from chaos bag (default position above trash can)
for i, obj in ipairs(chaosBag.getData().ContainedObjects) do
obj["Tags"] = { "tempToken" }
local spawnedObj = spawnObjectData({
data = obj,
position = { 0.49, 3, 0 }
})
local value = tonumber(obj["Nickname"])
local precedence = tokenPrecedence[obj["Nickname"]]
data[i] = {
token = spawnedObj,
value = value or precedence[1]
}
if precedence ~= nil then
data[i].order = precedence[2]
else
data[i].order = value
end
end
-- sort table by value (symbols last if same value)
table.sort(data, token_value_comparator)
-- error handling for removal of token arranger
if self == nil then
for _, token in ipairs(getObjectsWithTag("tempToken")) do token.destruct() end
return
end
-- laying out the tokens
local pos = self.getPosition() + Vector(3.55, -0.05, -3.95)
local location = { x = pos.x, y = pos.y, z = pos.z }
local current_value = data[1].value
for _, item in ipairs(data) do
if item.value ~= current_value then
location.x = location.x - 1.75
location.z = pos.z
current_value = item.value
end
item.token.setPosition(location)
item.token.setRotation(self.getRotation())
location.z = location.z - 1.75
end
Wait.time(function() updating = false end, 0.1)
end
-- called from outside to set default values for tokens
function onTokenDataChanged(parameters)
local tokenData = parameters.tokenData or {}
local currentScenario = parameters.currentScenario or ""
local useFrontData = parameters.useFrontData or "true"
-- only update if this data is new
local info = currentScenario .. useFrontData
if latestLoad == info then return end
latestLoad = info
-- update token precedence
for key, table in pairs(tokenData) do
local modifier = table.modifier
if modifier == -999 then modifier = 0 end
tokenPrecedence[key][1] = modifier
end
updateUI()
layout()
end

View File

@ -0,0 +1,35 @@
do
local TokenArrangerApi = {}
-- 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 = getObjectsWithTag("TokenArranger")[1]
if tokenArranger ~= nil then
tokenArranger.call(functionName, argument)
end
end
-- updates the token modifiers with the provided data
---@param tokenData Table Contains the chaos token metadata
TokenArrangerApi.onTokenDataChanged = function(tokenData, currentScenario, useFrontData)
callIfExistent("onTokenDataChanged", {
tokenData = tokenData,
currentScenario = currentScenario,
useFrontData = useFrontData
})
end
-- deletes already laid out tokens
TokenArrangerApi.deleteCopiedTokens = function()
callIfExistent("deleteCopiedTokens")
end
-- updates the laid out tokens
TokenArrangerApi.layout = function()
callIfExistent("layout")
end
return TokenArrangerApi
end

View File

@ -1,11 +1,4 @@
-- Bless / Curse Manager
-- updated by: Chr1Z
-- made by: Tikatoy
-- description: helps with adding / removing and sealing of bless and curse tokens
information = {
version = "3.5",
last_updated = "12.11.2022"
}
local tokenArrangerApi = require("accessories/TokenArrangerApi")
local IMAGE_URL = {
Bless = "http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/",
@ -25,9 +18,6 @@ local BUTTON_COLOR = { [false] = { 0.4, 0.4, 0.4 }, [true] = { 0.9, 0.9, 0.9 } }
local FONT_COLOR = { [false] = { 1, 1, 1 }, [true] = { 0, 0, 0 } }
local whitespace = " "
-- variable will be set by outside call
tokenArranger = nil
---------------------------------------------------------
-- creating buttons and menus + initializing tables
---------------------------------------------------------
@ -67,12 +57,6 @@ function onLoad(saved_state)
-- context menu
self.addContextMenuItem("Remove all", doRemove)
self.addContextMenuItem("Reset", doReset)
self.addContextMenuItem("More Information", function()
printToAll("------------------------------", "White")
printToAll("Bless / Curse Manager v" .. information["version"] .. " by Chr1Z", "Orange")
printToAll("last updated: " .. information["last_updated"], "White")
printToAll("original by Tikatoy", "White")
end)
-- hotkeys
addHotkey("Bless Curse Status", printStatus, false)
@ -173,15 +157,13 @@ end
-- context menu function 2
function doReset(color)
-- delete previously pulled out tokens by the token arranger
if tokenArranger then
tokenArranger.call("deleteCopiedTokens")
end
tokenArrangerApi.deleteCopiedTokens()
playerColor = color
numInPlay = { Bless = 0, Curse = 0 }
tokensTaken = { Bless = {}, Curse = {} }
initializeState()
updateTokenArranger()
tokenArrangerApi.layout()
end
---------------------------------------------------------
@ -263,18 +245,7 @@ function callFunctions(token, isRightClick)
success = takeToken(token, false)
end
end
if success ~= 0 then updateTokenArranger() end
end
UPDATING = false
function updateTokenArranger()
if tokenArranger and not UPDATING then
UPDATING = true
Wait.time(function()
UPDATING = false
tokenArranger.call("layout")
end, 1.5)
end
if success ~= 0 then tokenArrangerApi.layout() end
end
function getChaosBag()
@ -405,22 +376,22 @@ function addMenuOptions(playerColor, hoveredObject)
hoveredObject.addContextMenuItem("Seal Bless", function(color)
sealToken("Bless", color, hoveredObject)
updateTokenArranger()
tokenArrangerApi.layout()
end, true)
hoveredObject.addContextMenuItem("Release Bless", function(color)
releaseToken("Bless", color, hoveredObject)
updateTokenArranger()
tokenArrangerApi.layout()
end, true)
hoveredObject.addContextMenuItem("Seal Curse", function(color)
sealToken("Curse", color, hoveredObject)
updateTokenArranger()
tokenArrangerApi.layout()
end, true)
hoveredObject.addContextMenuItem("Release Curse", function(color)
releaseToken("Curse", color, hoveredObject)
updateTokenArranger()
tokenArrangerApi.layout()
end, true)
broadcastToColor("Right-click seal options added to " .. hoveredObject.getName(), playerColor)

View File

@ -845,9 +845,6 @@ function applyOptionPanelChange(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
@ -921,14 +918,7 @@ function spawnHelperObject(name, position, rotation)
return
end
local spawnTable = {
position = position,
callback_function = function(object)
if name == "Token Arranger" then
Wait.time(function() object.call("layout") end, 0.1)
end
end
}
local spawnTable = {position = position}
-- only overrride rotation if there is one provided (object's rotation used instead)
if rotation then

View File

@ -1,5 +1,6 @@
local playArea = require("core/PlayAreaApi")
local tokenSpawnTracker = require("core/token/TokenSpawnTrackerApi")
local playAreaApi = require("core/PlayAreaApi")
local tokenArrangerApi = require("accessories/TokenArrangerApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
local ENCOUNTER_DECK_AREA = {
upperLeft = { x = 0.9, z = 0.42 },
@ -11,39 +12,65 @@ local ENCOUNTER_DISCARD_AREA = {
}
local currentScenario
local useFrontData
local tokenData
-- we use this to turn off collision handling until onLoad() is complete
local COLLISION_ENABLED = false
local collisionEnabled = false
function onLoad(saveState)
if saveState ~= nil then
local loadedState = JSON.decode(saveState) or { }
currentScenario = loadedState.currentScenario
local loadedState = JSON.decode(saveState) or {}
currentScenario = loadedState.currentScenario or ""
useFrontData = loadedState.useFrontData or true
tokenData = loadedState.tokenData or {}
end
COLLISION_ENABLED = true
collisionEnabled = true
end
function onSave()
return JSON.encode({
currentScenario = currentScenario
currentScenario = currentScenario,
useFrontData = useFrontData,
tokenData = tokenData
})
end
-- TTS event handler. Handles scenario name event triggering and encounter card token resets.
function onCollisionEnter(collisionInfo)
if not COLLISION_ENABLED then
if not collisionEnabled then
return
end
local object = collisionInfo.collision_object
if object.getName() == "Scenario" then
if currentScenario ~= object.getDescription() then
currentScenario = object.getDescription()
local updateNeeded = false
local description = object.getDescription()
-- detect if orientation of scenario card changed (flipped to other side)
if object.is_face_down == useFrontData then
useFrontData = not useFrontData
updateNeeded = true
end
-- detect if another scenario card is placed down
if currentScenario ~= description then
currentScenario = description
updateNeeded = true
fireScenarioChangedEvent()
end
-- trigger update if a change was detected and push new data
if updateNeeded then
local metadata = JSON.decode(object.getGMNotes()) or {}
if not metadata["tokens"] then return end
tokenData = metadata["tokens"][(useFrontData and "front" or "back")]
fireTokenDataChangedEvent()
end
end
local localPos = self.positionToLocal(object.getPosition())
if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then
tokenSpawnTracker.resetTokensSpawned(object.getGUID())
tokenSpawnTrackerApi.resetTokensSpawned(object.getGUID())
end
end
@ -52,13 +79,19 @@ end
function onObjectEnterContainer(container, object)
local localPos = self.positionToLocal(container.getPosition())
if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then
tokenSpawnTracker.resetTokensSpawned(object.getGUID())
tokenSpawnTrackerApi.resetTokensSpawned(object.getGUID())
end
end
-- fires if the scenario title changes
function fireScenarioChangedEvent()
Wait.frames(function() Global.call('titleSplash', currentScenario) end, 20)
playArea.onScenarioChanged(currentScenario)
playAreaApi.onScenarioChanged(currentScenario)
end
-- fires if the scenario title or the difficulty changes
function fireTokenDataChangedEvent()
tokenArrangerApi.onTokenDataChanged(tokenData, currentScenario, tostring(useFrontData))
end
-- Simple method to check if the given point is in a specified area. Local use only,