Merge branch 'main' into redclock

This commit is contained in:
dscarpac 2024-10-05 10:03:00 -05:00 committed by GitHub
commit 16948afebe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 228 additions and 209 deletions

View File

@ -19,7 +19,6 @@
"LuckyPenny.2ab443", "LuckyPenny.2ab443",
"SecretObjectivesUltimatums.b2077d", "SecretObjectivesUltimatums.b2077d",
"UnderworldMarketHelper.3650ea", "UnderworldMarketHelper.3650ea",
"Subject5U-21Helper.1335e8",
"Auto-failCounter.a9a321", "Auto-failCounter.a9a321",
"ElderSignCounter.e62cb5", "ElderSignCounter.e62cb5",
"AdditionalVictoryPoints.958bc0" "AdditionalVictoryPoints.958bc0"

View File

@ -17,6 +17,7 @@
"CYOACampaignGuides.e87ea2", "CYOACampaignGuides.e87ea2",
"AttachmentHelper.7f4976", "AttachmentHelper.7f4976",
"SearchAssistant.17aed0", "SearchAssistant.17aed0",
"Subject5U-21Helper.1335e8",
"HandHelper.450688", "HandHelper.450688",
"DisplacementTool.0f1374", "DisplacementTool.0f1374",
"CleanUpHelper.26cf4b" "CleanUpHelper.26cf4b"

View File

@ -1,128 +1,5 @@
-- data for difficulty selector scripts to set up chaos bag -- data for difficulty selector scripts to set up chaos bag
modeData = { modeData = {
-----------------The Forgotten Age
['The Forgotten Age'] = {
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm2', 'm3', 'skull', 'skull', 'elder', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', '0', 'm1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'elder', 'red', 'blue' } },
hard = { token = { 'p1', '0', '0', 'm1', 'm2', 'm3', 'm3', 'm4', 'm6', 'skull', 'skull', 'elder', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm6', 'm8', 'skull', 'skull', 'elder', 'red', 'blue' } }
},
['The Doom of Eztli'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['Threads of Fate'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['The Boundary Beyond'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['The City of Archives'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['The Depths of Yoth'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['Heart of the Elders'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm5', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['Shattered Aeons'] = {
standalone = { token = { 'p1', '0', '0', '0','m1', 'm2', 'm2', 'm3', 'm4', 'm5', 'skull', 'skull', 'elder', 'red', 'blue' } }
},
-----------------The Circle Undone
['The Circle Undone'] = {
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm2', 'm3', 'skull', 'skull', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'red', 'blue' } },
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm5', 'skull', 'skull', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm6', 'm8', 'skull', 'skull', 'red', 'blue' } }
},
["At Death's Doorstep"] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } }
},
['The Secret Name'] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } }
},
['The Wages of Sin'] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['For the Greater Good'] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['Union and Disillusion'] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['In the Clutches of Chaos'] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
['Before the Black Throne'] = {
standalone = { token = { 'p1', '0', '0', 'm1','m1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
-----------------The Dream-Eaters
['TDE_A'] = {
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'cultist', 'tablet', 'tablet', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'cultist', 'tablet', 'tablet', 'red', 'blue' } },
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'cultist', 'tablet', 'tablet', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }
},
['TDE_B'] = {
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } },
hard = { token = { '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }
},
['The Search For Kadath'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }
},
['A Thousand Shapes of Horror'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }
},
['Dark Side of the Moon'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }
},
['Point of No Return'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }
},
['Where the Gods Dwell'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'tablet', 'red', 'blue' } }
},
['Weaver of the Cosmos'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'skull', 'cultist', 'elder', 'elder', 'red', 'blue' } }
},
-----------------The Innsmouth Conspiracy
['The Innsmouth Conspiracy'] = {
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } },
hard = { token = { '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } } ,
expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } }
},
['TIC_Standalone'] = {
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } }
},
-----------------Edge of the Earth
['Edge of the Earth'] = {
easy = { token = { 'p1', 'p1', 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } },
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm7', 'frost', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } }
},
['City of the Elder Things'] = {
easy = { token = { 'p1', 'p1', 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm7', 'frost', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
},
-----------------The Scarlet Keys
['The Scarlet Keys'] = {
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } },
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } },
hard = { token = { '0', '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm5', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } },
expert = { token = { '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm3', 'm4', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'tablet', 'elder', 'red', 'blue' } }
},
-----------------The Side Missions -----------------The Side Missions
--official --official
['Curse of the Rougarou'] = { ['Curse of the Rougarou'] = {

View File

@ -2525,6 +2525,53 @@ function TokenManager.addUseToCard(params)
else else
return false return false
end end
-- generates the data to spawn an infinite bag of a specific type of resources
function TokenManager.getDataForInfiniteBag(params)
-- re-assign parameters for convenience
local tokenType = params.tokenType
local position = params.position
local rotation = params.rotation
-- make sure the token templates are initialized
TokenManager.initTokenTemplates()
-- create a copy of the resource token (to not modify the source)
local template = deepCopy(tokenTemplates["resource"])
local subTypeStateId = stateTable[tokenType]
local subTypeData = template["States"][subTypeStateId]
-- add states to data
subTypeData["States"] = template["States"]
-- add "1" state and remove the current state
subTypeData["States"][1] = template
subTypeData["States"][1]["States"] = nil
subTypeData["States"][subTypeStateId] = nil
-- update rotation of the main state
subTypeData["Transform"].rotX = 0
subTypeData["Transform"].rotY = 0
subTypeData["Transform"].rotZ = 0
-- generate and return data for the infinite bag
local properTypeName = tokenType:gsub("^%l", string.upper)
return {
Name = "Infinite_Bag",
Nickname = properTypeName .. " Bag",
ContainedObjects = { subTypeData },
Transform = {
posX = position.x,
posY = position.y,
posZ = position.z,
rotX = rotation.x,
rotY = rotation.y,
rotZ = rotation.z,
scaleX = 1,
scaleY = 1,
scaleZ = 1
}
}
end end
--------------------------------------------------------- ---------------------------------------------------------
@ -2596,3 +2643,17 @@ function getColoredName(playerColor)
-- add bb-code -- add bb-code
return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]" return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]"
end end
-- creates a deep copy of a table
function deepCopy(data)
if type(data) ~= "table" then return data end
local copiedList = {}
for key, value in pairs(data) do
if type(value) == "table" then
copiedList[key] = deepCopy(value)
else
copiedList[key] = value
end
end
return copiedList
end

View File

@ -50,7 +50,7 @@ do
---@param playerColor string Color of the player that needs the visibility toggled ---@param playerColor string Color of the player that needs the visibility toggled
---@param handColor string Color of the hand to toggle the visibility for ---@param handColor string Color of the hand to toggle the visibility for
function GlobalApi.handVisibilityToggle(playerColor, handColor) function GlobalApi.handVisibilityToggle(playerColor, handColor)
Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor}) Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor })
end end
-- loads saved options -- loads saved options

View File

@ -91,9 +91,18 @@ do
function TokenManagerApi.maybeReplenishCard(card, uses) function TokenManagerApi.maybeReplenishCard(card, uses)
Global.call("callTable", { Global.call("callTable", {
{ "TokenManager", "maybeReplenishCard" }, { "TokenManager", "maybeReplenishCard" },
{ card = card, uses = uses }
})
end
-- Generates the data to spawn an infinite bag of a specific type of resources
function TokenManagerApi.getDataForInfiniteBag(tokenType, position, rotation)
return Global.call("callTable", {
{ "TokenManager", "getDataForInfiniteBag" },
{ {
card = card, tokenType = tokenType,
uses = uses position = position,
rotation = rotation
} }
}) })
end end

View File

@ -268,6 +268,9 @@ function buildSupplementalIndexes()
if OFFICIAL_CYCLE_LIST[cycleName] ~= true then if OFFICIAL_CYCLE_LIST[cycleName] ~= true then
otherCardsDetected = true otherCardsDetected = true
-- overwrite the cycle for easier handling by the playercard panel
cycleName = "other"
-- maybe add to special investigator / minicard index -- maybe add to special investigator / minicard index
if card.metadata.type == "Investigator" then if card.metadata.type == "Investigator" then
writeToNestedTable(customInvestigatorData, "InvestigatorGroup", cardId) writeToNestedTable(customInvestigatorData, "InvestigatorGroup", cardId)

View File

@ -1,37 +1,37 @@
require("playercards/PlayerCardSpawner") require("playercards/PlayerCardSpawner")
local allCardsBagApi = require("playercards/AllCardsBagApi") local allCardsBagApi = require("playercards/AllCardsBagApi")
local BUTTON_LABELS = {} local BUTTON_LABELS = {}
BUTTON_LABELS["spawn"] = {} BUTTON_LABELS["spawn"] = {}
BUTTON_LABELS["spawn"][true] = "All matching cards" BUTTON_LABELS["spawn"][true] = "All matching cards"
BUTTON_LABELS["spawn"][false] = "First matching card" BUTTON_LABELS["spawn"][false] = "First matching card"
BUTTON_LABELS["search"] = {} BUTTON_LABELS["search"] = {}
BUTTON_LABELS["search"][true] = "Name equals search term" BUTTON_LABELS["search"][true] = "Name equals search term"
BUTTON_LABELS["search"][false] = "Name contains search term" BUTTON_LABELS["search"][false] = "Name contains search term"
local inputParameters = {} local inputParameters = {}
inputParameters.label = "Click to enter card name" inputParameters.label = "Click to enter card name"
inputParameters.input_function = "input_func" inputParameters.input_function = "input_func"
inputParameters.function_owner = self inputParameters.function_owner = self
inputParameters.alignment = 2 inputParameters.alignment = 2
inputParameters.position = { x = 0, y = 0.1, z = -0.62 } inputParameters.position = { x = 0, y = 0.1, z = -0.62 }
inputParameters.width = 3750 inputParameters.width = 3750
inputParameters.height = 380 inputParameters.height = 380
inputParameters.font_size = 350 inputParameters.font_size = 350
inputParameters.scale = { 0.1, 1, 0.1 } inputParameters.scale = { 0.1, 1, 0.1 }
inputParameters.color = { 0.9, 0.7, 0.5 } inputParameters.color = { 0.9, 0.7, 0.5 }
inputParameters.font_color = { 0, 0, 0 } inputParameters.font_color = { 0, 0, 0 }
function onSave() function onSave()
return JSON.encode({ spawnAll, searchExact, inputParameters.value }) return JSON.encode({ spawnAll, searchExact, inputParameters.value })
end end
function onLoad(savedData) function onLoad(savedData)
local loadedData = JSON.decode(savedData) local loadedData = JSON.decode(savedData)
spawnAll = loadedData[1] or false spawnAll = loadedData[1] or false
searchExact = loadedData[2] or false searchExact = loadedData[2] or false
inputParameters.value = loadedData[3] or "" inputParameters.value = loadedData[3] or ""
self.createInput(inputParameters) self.createInput(inputParameters)
-- shared parameters -- shared parameters
@ -103,6 +103,13 @@ function startSearch()
return return
end end
-- if the search string is a number, assume it's an ID and spawn the card directly
if tonumber(inputParameters.value) then
local singleCard = allCardsBagApi.getCardById(inputParameters.value)
spawnCardList({ singleCard })
return
end
-- search all objects in bag -- search all objects in bag
local cardList = allCardsBagApi.getCardsByName(inputParameters.value, searchExact) local cardList = allCardsBagApi.getCardsByName(inputParameters.value, searchExact)
if cardList == nil or #cardList == 0 then if cardList == nil or #cardList == 0 then
@ -117,6 +124,10 @@ function startSearch()
-- sort table by name (reverse for multiple results, because bottom card spawns first) -- sort table by name (reverse for multiple results, because bottom card spawns first)
table.sort(cardList, function(k1, k2) return spawnAll == (k1.data.Nickname > k2.data.Nickname) end) table.sort(cardList, function(k1, k2) return spawnAll == (k1.data.Nickname > k2.data.Nickname) end)
spawnCardList(cardList)
end
function spawnCardList(cardList)
local rot = self.getRotation() local rot = self.getRotation()
local pos = self.positionToWorld(Vector(0, 2, -0.08)) local pos = self.positionToWorld(Vector(0, 2, -0.08))
Spawner.spawnCards(cardList, pos, rot, true) Spawner.spawnCards(cardList, pos, rot, true)

View File

@ -8,6 +8,7 @@ local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker") local tokenChecker = require("core/token/TokenChecker")
local tokenManagerApi = require("core/token/TokenManagerApi") local tokenManagerApi = require("core/token/TokenManagerApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
local zones = require("playermat/Zones")
-- option panel data -- option panel data
local availableOptions = { local availableOptions = {
@ -271,9 +272,15 @@ function round(num, numDecimalPlaces)
end end
-- updates the internal "messageColor" which is used for print/broadcast statements if no player is seated -- updates the internal "messageColor" which is used for print/broadcast statements if no player is seated
---@param clickedByColor string Colorstring of player who clicked a button ---@param clickedByColor? string Colorstring of player who clicked a button
function updateMessageColor(clickedByColor) function updateMessageColor(clickedByColor)
messageColor = Player[playerColor].seated and playerColor or clickedByColor if Player[playerColor].seated then
messageColor = playerColor
elseif clickedByColor and Player[clickedByColor].seated then
messageColor = clickedByColor
else
messageColor = Player.getPlayers()[1].color
end
end end
--------------------------------------------------------- ---------------------------------------------------------
@ -341,19 +348,19 @@ function doUpkeep(_, clickedByColor, isRightClick)
local forcedLearning = false local forcedLearning = false
local rot = self.getRotation() local rot = self.getRotation()
for _, obj in ipairs(searchAroundSelf()) do for _, obj in ipairs(searchAroundSelf()) do
if obj.hasTag("Temporary") == true then if obj.hasTag("Temporary") then
discardListOfObjects({ obj }) discardListOfObjects({ obj })
elseif obj.hasTag("UniversalToken") == true and obj.is_face_down then elseif obj.hasTag("UniversalToken") and obj.is_face_down then
obj.flip() obj.flip()
elseif obj.type == "Card" then end
if obj.hasTag("DoInUpkeep") then
obj.call("doInUpkeep") -- call the 'doInUpkeep' function for face-up objects with the respective tag
end if obj.hasTag("DoInUpkeep") and not obj.is_face_down then
-- do not rotate, replenish, etc. on cards in investigator card area obj.call("doInUpkeep")
if inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then end
break
end if obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
-- do not continue for cards in investigator card area
local cardMetadata = JSON.decode(obj.getGMNotes()) or {} local cardMetadata = JSON.decode(obj.getGMNotes()) or {}
if not (obj.getVar("do_not_ready") or obj.hasTag("DoNotReady")) then if not (obj.getVar("do_not_ready") or obj.hasTag("DoNotReady")) then
@ -384,8 +391,8 @@ function doUpkeep(_, clickedByColor, isRightClick)
if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 and not obj.is_face_down then if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 and not obj.is_face_down then
tokenManagerApi.maybeReplenishCard(obj, cardMetadata.uses, self) tokenManagerApi.maybeReplenishCard(obj, cardMetadata.uses, self)
end end
elseif obj.type == "Deck" and forcedLearning == false then elseif obj.type == "Deck" and not obj.is_face_down and not forcedLearning then
-- check decks for forced learning -- check face up decks for forced learning
for _, deepObj in ipairs(obj.getObjects()) do for _, deepObj in ipairs(obj.getObjects()) do
local cardMetadata = JSON.decode(deepObj.gm_notes) or {} local cardMetadata = JSON.decode(deepObj.gm_notes) or {}
if cardMetadata.id == "08031" then if cardMetadata.id == "08031" then
@ -1265,36 +1272,34 @@ end
-- investigator ID grabbing and skill tracker -- investigator ID grabbing and skill tracker
--------------------------------------------------------- ---------------------------------------------------------
-- updates the internal investigator id and action tokens if an investigator card is detected -- updates the internal investigator data and performs additional operations if an investigator card is detected
---@param card tts__Object Card that might be an investigator ---@param card tts__Object Card that might be an investigator
function maybeUpdateActiveInvestigator(card) function maybeUpdateActiveInvestigator(card)
-- don't continue if this card is not in the investigator area
if not inArea(self.positionToLocal(card.getPosition()), INVESTIGATOR_AREA) then return end if not inArea(self.positionToLocal(card.getPosition()), INVESTIGATOR_AREA) then return end
local notes = JSON.decode(card.getGMNotes()) -- get metadata
local extraToken local notes = JSON.decode(card.getGMNotes()) or {}
if notes ~= nil and notes.type == "Investigator" and notes.id ~= nil then -- don't continue for cards without proper metadata
if notes.id == activeInvestigatorData.id then return end if notes.type ~= "Investigator" or notes.id == nil then return end
activeInvestigatorData.class = notes.class
activeInvestigatorData.id = notes.id -- don't continue if this is already the active investigator
activeInvestigatorData.miniId = getMiniId(notes.id) if notes.id == activeInvestigatorData.id then return end
extraToken = notes.extraToken
ownedObjects.InvestigatorSkillTracker.call("updateStats", { -- extract relevant data from the metadata
notes.willpowerIcons, activeInvestigatorData.class = notes.class
notes.intellectIcons, activeInvestigatorData.id = notes.id
notes.combatIcons, activeInvestigatorData.miniId = getMiniId(notes.id)
notes.agilityIcons ownedObjects.InvestigatorSkillTracker.call("updateStats", {
}) notes.willpowerIcons,
updateTexture() notes.intellectIcons,
elseif activeInvestigatorData.id ~= "00000" then notes.combatIcons,
activeInvestigatorData.class = "Neutral" notes.agilityIcons
activeInvestigatorData.id = "00000" })
activeInvestigatorData.miniId = "00000-m" updateTexture()
ownedObjects.InvestigatorSkillTracker.call("updateStats", { 1, 1, 1, 1 })
updateTexture() newInvestigatorCallback(notes.id)
else
return
end
-- set proper scale for investigators -- set proper scale for investigators
local cardData = card.getData() local cardData = card.getData()
@ -1322,10 +1327,10 @@ function maybeUpdateActiveInvestigator(card)
end end
-- spawn additional token (maybe specific for investigator) -- spawn additional token (maybe specific for investigator)
if extraToken and extraToken ~= "None" then if notes.extraToken and notes.extraToken ~= "None" then
-- spawn tokens (split string by "|") -- spawn tokens (split string by "|")
local count = { action = 0, ability = 0 } local count = { action = 0, ability = 0 }
for str in string.gmatch(extraToken, "([^|]+)") do for str in string.gmatch(notes.extraToken, "([^|]+)") do
local type = "action" local type = "action"
if str == "FreeTrigger" or str == "Reaction" then if str == "FreeTrigger" or str == "Reaction" then
type = "ability" type = "ability"
@ -1344,6 +1349,53 @@ function maybeUpdateActiveInvestigator(card)
end end
end end
-- does something for specific investigators when they are loaded
function newInvestigatorCallback(newId)
updateMessageColor()
-- remove existing object that was placed for a specific investigator
local obj = guidReferenceApi.getObjectByOwnerAndType(playerColor, "InvestigatorSpecifics")
if obj ~= nil then
obj.destruct()
guidReferenceApi.editIndex(playerColor, "InvestigatorSpecifics")
end
if newId == "01005-p" or newId == "01005-pf" then -- parallel Wendy Adams
printToColor("Wendy Adams: There's a Game Key to add sealing options to any card:" ..
" Top menu bar > Options > Game Keys", messageColor)
elseif newId == "06003" then -- Tony Morgan
spawnInfiniteTokenBag("bounty")
printToColor("Tony Morgan: Spawned bounty tokens near your playermat.", messageColor)
elseif newId == "08004" then -- Norman Withers
printToColor("Norman Withers: At the start of the game flip the top card of your deck manually " ..
"and then the mod should keep it flipped throughout the game.", messageColor)
elseif newId == "09015" then -- Darrell Simmons
spawnInfiniteTokenBag("evidence")
printToColor("Darrell Simmons: Spawned evidence tokens near your playermat.", messageColor)
elseif newId == "89001" then -- Subject 5U-21
local pos = zones.getZonePosition(playerColor, "BelowSetAside")
local rot = self.getRotation()
local sourceBag = guidReferenceApi.getObjectByOwnerAndType("Mythos", "OptionPanelSource")
for _, objData in ipairs(sourceBag.getData().ContainedObjects) do
if objData["Nickname"] == "Subject 5U-21 Helper" then
local spawnedObj = spawnObjectData({ data = objData, position = pos, rotation = rot })
guidReferenceApi.editIndex(playerColor, "InvestigatorSpecifics", spawnedObj.getGUID())
break
end
end
printToColor("Subject 5U-21: Spawned a helper to track the classes of devoured cards near your playermat. " ..
"Note that this and 'Ravenous' will work with the Attachment Helper from the option panel.", messageColor)
end
end
-- spawns an infinite token bag of the specified type near the set aside area
function spawnInfiniteTokenBag(tokenType)
local pos = zones.getZonePosition(playerColor, "AboveSetAside")
local rot = self.getRotation()
local spawnedObj = spawnObjectData({ data = tokenManagerApi.getDataForInfiniteBag(tokenType, pos, rot) })
guidReferenceApi.editIndex(playerColor, "InvestigatorSpecifics", spawnedObj.getGUID())
end
-- returns the mini ID for the currently placed investigator -- returns the mini ID for the currently placed investigator
function getMiniId(baseId) function getMiniId(baseId)
if #baseId < 16 then if #baseId < 16 then

View File

@ -19,6 +19,9 @@
-- SetAside4: Upgrade sheets for customizable cards -- SetAside4: Upgrade sheets for customizable cards
-- SetAside5: Hunch Deck for Joe Diamond -- SetAside5: Hunch Deck for Joe Diamond
-- SetAside6: currently unused -- SetAside6: currently unused
-- AboveSetAside: Investigator specific object
-- BelowSetAside: Investigator specific object
do do
local playermatApi = require("playermat/PlayermatApi") local playermatApi = require("playermat/PlayermatApi")
local Zones = { } local Zones = { }
@ -78,6 +81,8 @@ do
zoneData["White"]["SetAside5"] = { 2.78, 0, 0.042 } zoneData["White"]["SetAside5"] = { 2.78, 0, 0.042 }
zoneData["White"]["SetAside6"] = { 2.78, 0, 0.605 } zoneData["White"]["SetAside6"] = { 2.78, 0, 0.605 }
zoneData["White"]["UnderSetAside6"] = { 2.93, 0, 0.805 } zoneData["White"]["UnderSetAside6"] = { 2.93, 0, 0.805 }
zoneData["White"]["AboveSetAside"] = { 2.35, 0, -1.069 }
zoneData["White"]["BelowSetAside"] = { 2.85, 0, 1.650 }
zoneData["Orange"] = {} zoneData["Orange"] = {}
zoneData["Orange"]["Investigator"] = commonZones["Investigator"] zoneData["Orange"]["Investigator"] = commonZones["Investigator"]
@ -110,6 +115,8 @@ do
zoneData["Orange"]["SetAside5"] = { -2.78, 0, 0.042 } zoneData["Orange"]["SetAside5"] = { -2.78, 0, 0.042 }
zoneData["Orange"]["SetAside6"] = { -2.78, 0, 0.605 } zoneData["Orange"]["SetAside6"] = { -2.78, 0, 0.605 }
zoneData["Orange"]["UnderSetAside6"] = { -2.93, 0, 0.805 } zoneData["Orange"]["UnderSetAside6"] = { -2.93, 0, 0.805 }
zoneData["Orange"]["AboveSetAside"] = { -2.35, 0, -1.069 }
zoneData["Orange"]["BelowSetAside"] = { -2.85, 0, 1.650 }
-- Green positions are the same as White and Red the same as Orange -- Green positions are the same as White and Red the same as Orange
zoneData["Red"] = zoneData["Orange"] zoneData["Red"] = zoneData["Orange"]

View File

@ -6,7 +6,8 @@
color="clear"/> color="clear"/>
<VerticalLayout class="navbar" <VerticalLayout class="navbar"
width="800" width="800"
padding="70 70 70 70" height="1600"
padding="75 75 75 75"
scale="0.05 0.05 1" scale="0.05 0.05 1"
color="#000000" color="#000000"
outlineSize="20 20" outlineSize="20 20"
@ -14,15 +15,23 @@
rectAlignment="LowerRight"/> rectAlignment="LowerRight"/>
</Defaults> </Defaults>
<!-- Buttons at the bottom right (height: n * width) --> <!-- Navigation Overlay + Bless / Curse Manager (height: n * width) -->
<VerticalLayout class="navbar" <VerticalLayout class="navbar"
visibility="Admin" offsetXY="-1 160">
height="2400"
offsetXY="-1 120">
<Button class="navbar" <Button class="navbar"
icon="blurse" icon="blurse"
tooltip="Bless/Curse Manager" tooltip="Bless/Curse Manager"
onClick="onClick_toggleUi(blessCurseManager)"/> onClick="onClick_toggleUi(blessCurseManager)"/>
<Button class="navbar"
icon="NavigationOverlayIcon"
tooltip="Navigation Overlay"
onClick="onClick_toggleUi(Navigation Overlay)"/>
</VerticalLayout>
<!-- Download Menu + Option Panel (height: n * width) -->
<VerticalLayout class="navbar"
visibility="Admin"
offsetXY="-1 80">
<Button class="navbar" <Button class="navbar"
icon="devourer" icon="devourer"
tooltip="Downloadable Content" tooltip="Downloadable Content"
@ -31,14 +40,4 @@
icon="option-gear" icon="option-gear"
tooltip="Options" tooltip="Options"
onClick="onClick_toggleUi(optionPanel)"/> onClick="onClick_toggleUi(optionPanel)"/>
</VerticalLayout> </VerticalLayout>
<!-- Navigation Overlay button -->
<VerticalLayout class="navbar"
height="800"
offsetXY="-1 80">
<Button class="navbar"
icon="NavigationOverlayIcon"
tooltip="Navigation Overlay"
onClick="onClick_toggleUi(Navigation Overlay)"/>
</VerticalLayout>