diff --git a/objects/Fan-MadeAccessories.aa8b38.json b/objects/Fan-MadeAccessories.aa8b38.json
index 33b400c4..042ee175 100644
--- a/objects/Fan-MadeAccessories.aa8b38.json
+++ b/objects/Fan-MadeAccessories.aa8b38.json
@@ -19,7 +19,6 @@
"LuckyPenny.2ab443",
"SecretObjectivesUltimatums.b2077d",
"UnderworldMarketHelper.3650ea",
- "Subject5U-21Helper.1335e8",
"Auto-failCounter.a9a321",
"ElderSignCounter.e62cb5",
"AdditionalVictoryPoints.958bc0"
diff --git a/objects/OptionPanelSource.830bd0.json b/objects/OptionPanelSource.830bd0.json
index 56ede1b5..743e1af4 100644
--- a/objects/OptionPanelSource.830bd0.json
+++ b/objects/OptionPanelSource.830bd0.json
@@ -17,6 +17,7 @@
"CYOACampaignGuides.e87ea2",
"AttachmentHelper.7f4976",
"SearchAssistant.17aed0",
+ "Subject5U-21Helper.1335e8",
"HandHelper.450688",
"DisplacementTool.0f1374",
"CleanUpHelper.26cf4b"
diff --git a/objects/Fan-MadeAccessories.aa8b38/Subject5U-21Helper.1335e8.json b/objects/OptionPanelSource.830bd0/Subject5U-21Helper.1335e8.json
similarity index 100%
rename from objects/Fan-MadeAccessories.aa8b38/Subject5U-21Helper.1335e8.json
rename to objects/OptionPanelSource.830bd0/Subject5U-21Helper.1335e8.json
diff --git a/src/core/DataHelper.ttslua b/src/core/DataHelper.ttslua
index 707c9add..88c74857 100644
--- a/src/core/DataHelper.ttslua
+++ b/src/core/DataHelper.ttslua
@@ -1,128 +1,5 @@
-- data for difficulty selector scripts to set up chaos bag
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
--official
['Curse of the Rougarou'] = {
diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua
index 07a39e22..de7d627d 100644
--- a/src/core/Global.ttslua
+++ b/src/core/Global.ttslua
@@ -2525,6 +2525,53 @@ function TokenManager.addUseToCard(params)
else
return false
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
---------------------------------------------------------
@@ -2596,3 +2643,17 @@ function getColoredName(playerColor)
-- add bb-code
return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]"
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
diff --git a/src/core/GlobalApi.ttslua b/src/core/GlobalApi.ttslua
index 0f55e584..1a15d394 100644
--- a/src/core/GlobalApi.ttslua
+++ b/src/core/GlobalApi.ttslua
@@ -50,7 +50,7 @@ do
---@param playerColor string Color of the player that needs the visibility toggled
---@param handColor string Color of the hand to toggle the visibility for
function GlobalApi.handVisibilityToggle(playerColor, handColor)
- Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor})
+ Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor })
end
-- loads saved options
diff --git a/src/core/token/TokenManagerApi.ttslua b/src/core/token/TokenManagerApi.ttslua
index 1894a09c..ea00f301 100644
--- a/src/core/token/TokenManagerApi.ttslua
+++ b/src/core/token/TokenManagerApi.ttslua
@@ -91,9 +91,18 @@ do
function TokenManagerApi.maybeReplenishCard(card, uses)
Global.call("callTable", {
{ "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,
- uses = uses
+ tokenType = tokenType,
+ position = position,
+ rotation = rotation
}
})
end
diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua
index 11e97bf4..f01ae11a 100644
--- a/src/playercards/AllCardsBag.ttslua
+++ b/src/playercards/AllCardsBag.ttslua
@@ -268,6 +268,9 @@ function buildSupplementalIndexes()
if OFFICIAL_CYCLE_LIST[cycleName] ~= true then
otherCardsDetected = true
+ -- overwrite the cycle for easier handling by the playercard panel
+ cycleName = "other"
+
-- maybe add to special investigator / minicard index
if card.metadata.type == "Investigator" then
writeToNestedTable(customInvestigatorData, "InvestigatorGroup", cardId)
diff --git a/src/playercards/CardSearch.ttslua b/src/playercards/CardSearch.ttslua
index f5143f2e..2aca432e 100644
--- a/src/playercards/CardSearch.ttslua
+++ b/src/playercards/CardSearch.ttslua
@@ -1,37 +1,37 @@
require("playercards/PlayerCardSpawner")
-local allCardsBagApi = require("playercards/AllCardsBagApi")
+local allCardsBagApi = require("playercards/AllCardsBagApi")
-local BUTTON_LABELS = {}
-BUTTON_LABELS["spawn"] = {}
-BUTTON_LABELS["spawn"][true] = "All matching cards"
-BUTTON_LABELS["spawn"][false] = "First matching card"
-BUTTON_LABELS["search"] = {}
-BUTTON_LABELS["search"][true] = "Name equals search term"
-BUTTON_LABELS["search"][false] = "Name contains search term"
+local BUTTON_LABELS = {}
+BUTTON_LABELS["spawn"] = {}
+BUTTON_LABELS["spawn"][true] = "All matching cards"
+BUTTON_LABELS["spawn"][false] = "First matching card"
+BUTTON_LABELS["search"] = {}
+BUTTON_LABELS["search"][true] = "Name equals search term"
+BUTTON_LABELS["search"][false] = "Name contains search term"
-local inputParameters = {}
-inputParameters.label = "Click to enter card name"
-inputParameters.input_function = "input_func"
-inputParameters.function_owner = self
-inputParameters.alignment = 2
-inputParameters.position = { x = 0, y = 0.1, z = -0.62 }
-inputParameters.width = 3750
-inputParameters.height = 380
-inputParameters.font_size = 350
-inputParameters.scale = { 0.1, 1, 0.1 }
-inputParameters.color = { 0.9, 0.7, 0.5 }
-inputParameters.font_color = { 0, 0, 0 }
+local inputParameters = {}
+inputParameters.label = "Click to enter card name"
+inputParameters.input_function = "input_func"
+inputParameters.function_owner = self
+inputParameters.alignment = 2
+inputParameters.position = { x = 0, y = 0.1, z = -0.62 }
+inputParameters.width = 3750
+inputParameters.height = 380
+inputParameters.font_size = 350
+inputParameters.scale = { 0.1, 1, 0.1 }
+inputParameters.color = { 0.9, 0.7, 0.5 }
+inputParameters.font_color = { 0, 0, 0 }
function onSave()
return JSON.encode({ spawnAll, searchExact, inputParameters.value })
end
function onLoad(savedData)
- local loadedData = JSON.decode(savedData)
- spawnAll = loadedData[1] or false
- searchExact = loadedData[2] or false
- inputParameters.value = loadedData[3] or ""
+ local loadedData = JSON.decode(savedData)
+ spawnAll = loadedData[1] or false
+ searchExact = loadedData[2] or false
+ inputParameters.value = loadedData[3] or ""
self.createInput(inputParameters)
-- shared parameters
@@ -103,6 +103,13 @@ function startSearch()
return
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
local cardList = allCardsBagApi.getCardsByName(inputParameters.value, searchExact)
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)
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 pos = self.positionToWorld(Vector(0, 2, -0.08))
Spawner.spawnCards(cardList, pos, rot, true)
diff --git a/src/playermat/Playermat.ttslua b/src/playermat/Playermat.ttslua
index c2c0e74e..a955e187 100644
--- a/src/playermat/Playermat.ttslua
+++ b/src/playermat/Playermat.ttslua
@@ -8,6 +8,7 @@ local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker")
local tokenManagerApi = require("core/token/TokenManagerApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
+local zones = require("playermat/Zones")
-- option panel data
local availableOptions = {
@@ -271,9 +272,15 @@ function round(num, numDecimalPlaces)
end
-- 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)
- 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
---------------------------------------------------------
@@ -341,19 +348,19 @@ function doUpkeep(_, clickedByColor, isRightClick)
local forcedLearning = false
local rot = self.getRotation()
for _, obj in ipairs(searchAroundSelf()) do
- if obj.hasTag("Temporary") == true then
+ if obj.hasTag("Temporary") then
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()
- elseif obj.type == "Card" then
- if obj.hasTag("DoInUpkeep") then
- obj.call("doInUpkeep")
- end
- -- do not rotate, replenish, etc. on cards in investigator card area
- if inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
- break
- end
-
+ end
+
+ -- call the 'doInUpkeep' function for face-up objects with the respective tag
+ if obj.hasTag("DoInUpkeep") and not obj.is_face_down then
+ obj.call("doInUpkeep")
+ 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 {}
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
tokenManagerApi.maybeReplenishCard(obj, cardMetadata.uses, self)
end
- elseif obj.type == "Deck" and forcedLearning == false then
- -- check decks for forced learning
+ elseif obj.type == "Deck" and not obj.is_face_down and not forcedLearning then
+ -- check face up decks for forced learning
for _, deepObj in ipairs(obj.getObjects()) do
local cardMetadata = JSON.decode(deepObj.gm_notes) or {}
if cardMetadata.id == "08031" then
@@ -1265,36 +1272,34 @@ end
-- 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
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
- local notes = JSON.decode(card.getGMNotes())
- local extraToken
+ -- get metadata
+ local notes = JSON.decode(card.getGMNotes()) or {}
- if notes ~= nil and notes.type == "Investigator" and notes.id ~= nil then
- if notes.id == activeInvestigatorData.id then return end
- activeInvestigatorData.class = notes.class
- activeInvestigatorData.id = notes.id
- activeInvestigatorData.miniId = getMiniId(notes.id)
- extraToken = notes.extraToken
- ownedObjects.InvestigatorSkillTracker.call("updateStats", {
- notes.willpowerIcons,
- notes.intellectIcons,
- notes.combatIcons,
- notes.agilityIcons
- })
- updateTexture()
- elseif activeInvestigatorData.id ~= "00000" then
- activeInvestigatorData.class = "Neutral"
- activeInvestigatorData.id = "00000"
- activeInvestigatorData.miniId = "00000-m"
- ownedObjects.InvestigatorSkillTracker.call("updateStats", { 1, 1, 1, 1 })
- updateTexture()
- else
- return
- end
+ -- don't continue for cards without proper metadata
+ if notes.type ~= "Investigator" or notes.id == nil then return end
+
+ -- don't continue if this is already the active investigator
+ if notes.id == activeInvestigatorData.id then return end
+
+ -- extract relevant data from the metadata
+ activeInvestigatorData.class = notes.class
+ activeInvestigatorData.id = notes.id
+ activeInvestigatorData.miniId = getMiniId(notes.id)
+ ownedObjects.InvestigatorSkillTracker.call("updateStats", {
+ notes.willpowerIcons,
+ notes.intellectIcons,
+ notes.combatIcons,
+ notes.agilityIcons
+ })
+ updateTexture()
+
+ newInvestigatorCallback(notes.id)
-- set proper scale for investigators
local cardData = card.getData()
@@ -1322,10 +1327,10 @@ function maybeUpdateActiveInvestigator(card)
end
-- 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 "|")
local count = { action = 0, ability = 0 }
- for str in string.gmatch(extraToken, "([^|]+)") do
+ for str in string.gmatch(notes.extraToken, "([^|]+)") do
local type = "action"
if str == "FreeTrigger" or str == "Reaction" then
type = "ability"
@@ -1344,6 +1349,53 @@ function maybeUpdateActiveInvestigator(card)
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
function getMiniId(baseId)
if #baseId < 16 then
diff --git a/src/playermat/Zones.ttslua b/src/playermat/Zones.ttslua
index 36f4742b..27d5aac6 100644
--- a/src/playermat/Zones.ttslua
+++ b/src/playermat/Zones.ttslua
@@ -19,6 +19,9 @@
-- SetAside4: Upgrade sheets for customizable cards
-- SetAside5: Hunch Deck for Joe Diamond
-- SetAside6: currently unused
+-- AboveSetAside: Investigator specific object
+-- BelowSetAside: Investigator specific object
+
do
local playermatApi = require("playermat/PlayermatApi")
local Zones = { }
@@ -78,6 +81,8 @@ do
zoneData["White"]["SetAside5"] = { 2.78, 0, 0.042 }
zoneData["White"]["SetAside6"] = { 2.78, 0, 0.605 }
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"]["Investigator"] = commonZones["Investigator"]
@@ -110,6 +115,8 @@ do
zoneData["Orange"]["SetAside5"] = { -2.78, 0, 0.042 }
zoneData["Orange"]["SetAside6"] = { -2.78, 0, 0.605 }
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
zoneData["Red"] = zoneData["Orange"]
diff --git a/xml/Global/BottomBar.xml b/xml/Global/BottomBar.xml
index 5cdc3c87..d680540e 100644
--- a/xml/Global/BottomBar.xml
+++ b/xml/Global/BottomBar.xml
@@ -6,7 +6,8 @@
color="clear"/>
-
+
+ offsetXY="-1 160">
+
+
+
+
+
-
-
-
-
-
-
+
\ No newline at end of file