Merge pull request #716 from dscarpac/chaos-token-redraw

Adds helpers for automatic token redrawing and replacement
This commit is contained in:
Chr1Z 2024-06-25 13:12:44 +02:00 committed by GitHub
commit 9b85382e7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 365 additions and 107 deletions

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/ClaypoolsFurs\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/CustomModifications\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/FalseCovenant\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/HeavyFurs\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/WendyAdams\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/WendyAdams\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/WendyAdams\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -236,8 +236,11 @@ function releasedToken(param)
break
end
end
if not param.fromBag then
updateDisplayAndBroadcast(param.type)
end
end
-- removes a token (called by cards that seal bless/curse tokens)
---@param param table This contains the type and guid of the released token

View File

@ -24,8 +24,9 @@ do
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.releasedToken = function(type, guid)
getManager().call("releasedToken", { type = type, guid = guid })
---@param fromBag? boolean Whether or not token was just drawn from the chaos bag
BlessCurseManagerApi.releasedToken = function(type, guid, fromBag)
getManager().call("releasedToken", { type = type, guid = guid, fromBag = fromBag })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
@ -54,5 +55,17 @@ do
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
end
-- adds bless / curse to the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.addToken = function(type)
getManager().call("addToken", type)
end
-- removes bless / curse from the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.removeToken = function(type)
getManager().call("removeToken", type)
end
return BlessCurseManagerApi
end

View File

@ -71,9 +71,9 @@ do
---@param drawAdditional boolean Controls whether additional tokens should be drawn
---@param tokenType? string Name of token (e.g. "Bless") to be drawn from the bag
---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag
---@param returnedToken? tts__Object Token to be replaced with newly drawn token
ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, returnedToken)
return Global.call("drawChaosToken", {mat = mat, drawAdditional = drawAdditional, tokenType = tokenType, guidToBeResolved = guidToBeResolved, returnedToken = returnedToken})
---@param takeParameters? table Position and rotation of the location where the new token should be drawn to, usually to replace a returned token
ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved)
return Global.call("drawChaosToken", {mat = mat, drawAdditional = drawAdditional, tokenType = tokenType, guidToBeResolved = guidToBeResolved, takeParameters = takeParameters})
end
-- returns a Table List of chaos token ids in the current chaos bag

View File

@ -261,6 +261,7 @@ function returnChaosTokens()
if token ~= nil then chaosBag.putObject(token) end
end
chaosTokens = {}
isTokenXMLActive = false
end
-- returns a single chaos token to the bag and calls respective functions
@ -271,7 +272,7 @@ function returnChaosTokenToBag(token)
chaosBag.putObject(token)
tokenArrangerApi.layout()
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.releasedToken(name, guid)
blessCurseManagerApi.releasedToken(name, guid, true)
end
end
@ -283,6 +284,128 @@ function getTokenIndex(token)
end
end
function activeRedrawEffect(originParams)
redrawData = originParams
makeButtonsToRedraw()
end
function makeButtonsToRedraw()
if isTokenXMLActive == true then
broadcastToAll("Clear already active buttons first, then try again", "Red")
return
end
if #chaosTokens == 0 then
broadcastToAll("No tokens found in play area", "Red")
return
end
local matchingTokensInPlay = {}
-- nil handling
redrawData.VALID_TOKENS = redrawData.VALID_TOKENS or {}
redrawData.INVALID_TOKENS = redrawData.INVALID_TOKENS or {}
-- determine if only some tokens are able to be returned to the bag
for _, token in ipairs(chaosTokens) do
local tokenName = getReadableTokenName(token.getName())
-- allow valid tokens or not invalid tokens, also allow any token if both lists empty
if (redrawData.VALID_TOKENS[tokenName] ~= nil and isTableEmpty(redrawData.INVALID_TOKENS)) or (isTableEmpty(redrawData.VALID_TOKENS) and not redrawData.INVALID_TOKENS[tokenName]) or
(isTableEmpty(redrawData.VALID_TOKENS) and isTableEmpty(redrawData.INVALID_TOKENS)) then
table.insert(matchingTokensInPlay, token)
end
end
if #matchingTokensInPlay == 0 then
broadcastToAll("No eligible token found in play area", "Red")
return
end
if #matchingTokensInPlay > 1 then
for _, token in ipairs(matchingTokensInPlay) do
-- draw XML to return token to bag
token.UI.setXmlTable({
{
tag = "VerticalLayout",
attributes = {
height = 275,
width = 275,
spacing = 0,
padding = "0 0 20 25",
scale = "0.4 0.4 1",
rotation = "0 0 180",
position = "0 0 -15",
color = "rgba(0,0,0,0.7)",
onClick = "Global/returnAndRedraw(" .. token.getGUID() .. ")",
},
children = {
{
tag = "Text",
attributes = {
fontSize = "100",
font = "font_teutonic-arkham",
color = "#ffffff",
text = "Redraw"
}
},
{
tag = "Text",
attributes = {
fontSize = "125",
font = "font_arkhamicons",
color = "#ffffff",
text = "u"
}
}
}
}
})
end
isTokenXMLActive = true
-- no need to make buttons if there is only one eligible token to return and redraw
else
returnAndRedraw(_, matchingTokensInPlay[1].getGUID())
end
end
-- returns a chaos token to the chaos bag and redraws another, always called by an XML button through makeButtonsToRedraw() above
function returnAndRedraw(_, tokenGUID)
local takeParameters = {}
local returnedToken = getObjectFromGUID(tokenGUID)
local matColor = playmatApi.getMatColorByPosition(returnedToken.getPosition())
local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
isTokenXMLActive = false
trackChaosToken(returnedToken.getName(), mat.getGUID(), true)
local indexOfReturnedToken = getTokenIndex(returnedToken)
takeParameters.position = returnedToken.getPosition()
if #chaosTokens > indexOfReturnedToken then
takeParameters.rotation = mat.getRotation() + Vector(0, 0, -8)
else
takeParameters.rotation = returnedToken.getRotation()
end
returnChaosTokenToBag(returnedToken)
local params = { mat = mat, drawAdditional = true, takeParameters = takeParameters }
if redrawData.redrawnTokenType ~= "random" then
params.tokenType = redrawData.redrawnTokenType
end
chaosTokens[indexOfReturnedToken] = drawChaosToken(params)
if redrawData.triggeringCard == "FalseCovenant" then
blessCurseManagerApi.removeToken("Curse")
end
-- remove XML from tokens in play
for _, token in ipairs(getChaosTokensinPlay()) do
token.UI.setXml("")
end
redrawData = {}
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.
@ -336,20 +459,12 @@ function drawChaosToken(params)
if #chaosBag.getObjects() == 0 then return end
chaosBag.shuffle()
local indexOfReturnedToken
local takeParameters = {}
-- add the token to the list, compute new position based on list length
if params.returnedToken then
trackChaosToken(params.returnedToken.getName(), matGUID, true)
indexOfReturnedToken = getTokenIndex(params.returnedToken)
takeParameters.position = params.returnedToken.getPosition()
if #chaosTokens > indexOfReturnedToken then
takeParameters.rotation = params.mat.getRotation() + Vector(0, 0, -8)
else
takeParameters.rotation = params.returnedToken.getRotation()
end
returnChaosTokenToBag(params.returnedToken)
if params.takeParameters then
takeParameters.position = params.takeParameters.position
takeParameters.rotation = params.takeParameters.rotation
else
tokenOffset[1] = tokenOffset[1] + (0.17 * #chaosTokens)
takeParameters.position = params.mat.positionToWorld(tokenOffset)
@ -357,6 +472,7 @@ function drawChaosToken(params)
end
local token
if params.guidToBeResolved then
-- resolve a sealed token from a card
token = getObjectFromGUID(params.guidToBeResolved)
@ -386,10 +502,11 @@ function drawChaosToken(params)
token.setDescription(specificData.description or "")
trackChaosToken(name, matGUID)
if params.returnedToken then
chaosTokens[indexOfReturnedToken] = token
if params.takeParameters then
return token
else
chaosTokens[#chaosTokens + 1] = token
table.insert(chaosTokens, token)
return token
end
else
returnChaosTokens()
@ -1697,3 +1814,11 @@ function removeValueFromTable(t, val)
end
end
end
function isTableEmpty(tbl)
if next(tbl) == nil then
return true
else
return false
end
end

View File

@ -0,0 +1,116 @@
--[[ Library for cards that return and redraw tokens
This file is used to add an XML button to a card, turned on via context menu.
Valid options modify the appearance of the XML button, as well as the
behavior of the return and redraw function. Set options before requiring this file.
originParams{} --@type table
- includes parameters for the return and redraw functions. Typically set VALID_TOKENS
or INVALID_TOKENS, not both. If there are no restrictions on which tokens can be redrawn
(e.g. Wendy Adams), do not include either parameter.
* VALID_TOKENS --@type table
- keyed table which lists all tokens that can be redrawn by the card
- example usage: "False Covenant"
> VALID_TOKENS = {
> ["Curse"] = true
> }
* INVALID_TOKENS --@type table
- keyed table which lists all tokens that cannot be redrawn by the card
- example usage: "Custom Ammunition"
> INVALID_TOKENS = {
> ["Auto-fail"] = true
> }
* redrawnTokenType --@type string ("random" or name of token)
- determines which kind of token is drawn from the bag. Typically "random"
but could be a set token name (e.g. Nkosi Mabati)
* triggeringCard --@type string (name of card button is on)
- allows for the name of the card to be passed onto Global for any special handling
The following parameters modify the appearence of the XML button and are not listed as part of a table.
- buttonHeight (default is 450)
- buttonWidth (default is 1400)
- buttonPosition (default is "0 -55 -22")
- buttonFontSize (default is 250)
- buttonRotation (change if button is placed on an investigator cards)
- buttonValue (to change the label of the button, default is "Redraw Token")
- buttonIcon (to add an icon to the right)
- buttonColor (default is "#77674DE6")
----------------------------------------------------------
EXAMPLE: Claypool's Furs
This card can only redraw the Frost token, and is replaced with a random token from the bag.
As a nice reminder the XML button takes on the Frost color and icon with the text "Cancel".
> buttonValue = "Cancel"
> buttonIcon = "token-frost"
> buttonColor = "#404450E6"
> buttonFontSize = 300
> originParams = {
> triggeringCard = "ClaypoolsFurs",
> redrawnTokenType = "random",
> VALID_TOKENS = {
> ["Frost"] = true
> }
> }
> require...
----------------------------------------------------------]]
local turnOnHelper
function onSave()
return JSON.encode(turnOnHelper)
end
function onLoad(savedData)
self.addContextMenuItem("Enable Helper", makeXMLButton)
if savedData and savedData ~= "" then
turnOnHelper = JSON.decode(savedData)
if turnOnHelper == true then
makeXMLButton()
end
end
end
function makeXMLButton()
turnOnHelper = true
self.clearContextMenu()
self.addContextMenuItem("Clear Helper", deleteButton)
local xmlTable = {{
tag = "Button",
attributes = {
height = buttonHeight or 450,
width = buttonWidth or 1400,
rotation = buttonRotation or "0 0 180",
scale = "0.1 0.1 1",
position = buttonPosition or "0 -55 -22",
padding = "50 50 50 50",
font = "font_teutonic-arkham",
fontSize = buttonFontSize or 250,
onClick = "triggerXMLTokenLabelCreation()",
color = buttonColor or "#77674DE6",
textColor = "White"
},
value = buttonValue or "Redraw Token"
}}
if buttonIcon then
xmlTable[1].attributes.iconWidth = "400"
xmlTable[1].attributes.iconAlignment = "Right"
xmlTable[1].attributes.icon = buttonIcon
end
self.UI.setXmlTable(xmlTable)
end
function triggerXMLTokenLabelCreation()
-- needs to be its own function in order to pass originParams as a table
Global.call("activeRedrawEffect", originParams)
end
-- Delete button
function deleteButton()
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", makeXMLButton)
self.UI.setXml("")
turnOnHelper = false
end

View File

@ -0,0 +1,14 @@
buttonValue = "Cancel"
buttonIcon = "token-frost"
buttonColor = "#404450E6"
buttonFontSize = 300
originParams = {
triggeringCard = "ClaypoolsFurs",
redrawnTokenType = "random",
VALID_TOKENS = {
["Frost"] = true
}
}
require("playercards/CardsThatRedrawTokens")

View File

@ -0,0 +1,10 @@
originParams = {
triggeringCard = "CustomModifications",
redrawnTokenType = "random",
VALID_TOKENS = {},
INVALID_TOKENS = {
["Auto-fail"] = true
}
}
require("playercards/CardsThatRedrawTokens")

View File

@ -0,0 +1,14 @@
buttonValue = "Cancel"
buttonIcon = "token-curse"
buttonColor = "#633A84E6"
buttonFontSize = 300
originParams = {
triggeringCard = "FalseCovenant",
redrawnTokenType = "random",
VALID_TOKENS = {
["Curse"] = true
}
}
require("playercards/CardsThatRedrawTokens")

View File

@ -0,0 +1,17 @@
originParams = {
triggeringCard = "HeavyFurs",
redrawnTokenType = "random",
VALID_TOKENS = {
["Skull"] = true,
["Tablet"] = true,
["Elder Thing"] = true,
["Cultist"] = true,
["Frost"] = true,
["Custom Token"] = true,
["Elder Sign"] = true,
["Bless"] = true,
["Curse"] = true
},
}
require("playercards/CardsThatRedrawTokens")

View File

@ -15,8 +15,6 @@ local tokenColor = {
[""] = "#77674DE6"
}
local sigil
function onSave()
return JSON.encode(sigil)
end
@ -52,7 +50,6 @@ function makeXMLButton()
iconWidth = "400",
iconAlignment = "Right",
onClick = "resolveSigil",
id = sigil,
icon = iconName,
color = tokenColor[sigil],
textColor = "White"
@ -109,77 +106,14 @@ function resolveSigil()
return
end
local matchingSymbolsInPlay = {}
for _, token in ipairs(tokensInPlay) do
if (token.getName() == "Cultist"
or token.getName() == "Tablet"
or token.getName() == "Elder Thing")
and token.getName() ~= sigil then
matchingSymbolsInPlay[#matchingSymbolsInPlay + 1] = token
end
end
if #matchingSymbolsInPlay == 0 then
broadcastToAll("No eligible symbol token found in play area", "Red")
return
elseif #matchingSymbolsInPlay > 1 then
for _, token in ipairs(matchingSymbolsInPlay) do
-- draw XML to return token to bag
token.UI.setXmlTable({
{
tag = "VerticalLayout",
attributes = {
height = 275,
width = 275,
spacing = 0,
padding = "0 0 20 25",
scale = "0.4 0.4 1",
rotation = "0 0 180",
position = "0 0 -15",
color = "rgba(0,0,0,0.7)",
onClick = self.getGUID() .. "/drawSigil(" .. token.getGUID() .. ")"
},
children = {
{
tag = "Text",
attributes = {
fontSize = "100",
font = "font_teutonic-arkham",
color = "#ffffff",
text = "Nkosi"
}
},
{
tag = "Text",
attributes = {
fontSize = "125",
font = "font_arkhamicons",
color = "#ffffff",
text = "u"
originParams = {
triggeringCard = "Nkosi",
redrawnTokenType = sigil,
VALID_TOKENS = {
["Tablet"] = true,
["Elder Thing"] = true,
["Cultist"] = true
}
}
}
}
})
end
else
drawSigil(_, matchingSymbolsInPlay[1].getGUID())
end
end
function drawSigil(player, tokenGUID)
local returnedToken = getObjectFromGUID(tokenGUID)
local matColor = playmatApi.getMatColorByPosition(returnedToken.getPosition())
local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
chaosBagApi.drawChaosToken(mat, true, sigil, _, returnedToken)
-- remove XML from tokens in play
for _, token in ipairs(chaosBagApi.getTokensInPlay()) do
if token.getName() == "Cultist"
or token.getName() == "Tablet"
or token.getName() == "Elder Thing" then
token.UI.setXml("")
end
end
Global.call("activeRedrawEffect", originParams)
end

View File

@ -0,0 +1,12 @@
buttonHeight = "320"
buttonWidth = "1100"
buttonPosition = "70 -70 -22"
buttonFontSize = 200
buttonRotation = "0 0 90"
originParams = {
triggeringCard = "Wendy",
redrawnTokenType = "random",
}
require("playercards/CardsThatRedrawTokens")