diff --git a/config.json b/config.json
index 2adead0d..0cd2293c 100644
--- a/config.json
+++ b/config.json
@@ -250,7 +250,8 @@
"TokenSpawnTracker.e3ffc9",
"TokenSource.124381",
"GameData.3dbe47",
- "SCEDTour.0e5aa8"
+ "SCEDTour.0e5aa8",
+ "PlayerCards.2d30ee"
],
"PlayArea": 1,
"PlayerCounts": [
diff --git a/objects/PlayerCards.2d30ee.json b/objects/PlayerCards.2d30ee.json
new file mode 100644
index 00000000..8bea32fe
--- /dev/null
+++ b/objects/PlayerCards.2d30ee.json
@@ -0,0 +1,57 @@
+{
+ "AltLookAngle": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "Autoraise": true,
+ "ColorDiffuse": {
+ "b": 1,
+ "g": 1,
+ "r": 1
+ },
+ "CustomImage": {
+ "CustomTile": {
+ "Stackable": false,
+ "Stretch": true,
+ "Thickness": 0.1,
+ "Type": 3
+ },
+ "ImageScalar": 1,
+ "ImageSecondaryURL": "https://i.imgur.com/dISlnEk.jpg",
+ "ImageURL": "https://i.imgur.com/dISlnEk.jpg",
+ "WidthScale": 0
+ },
+ "Description": "",
+ "DragSelectable": true,
+ "GMNotes": "",
+ "GUID": "2d30ee",
+ "Grid": true,
+ "GridProjection": false,
+ "Hands": false,
+ "HideWhenFaceDown": false,
+ "IgnoreFoW": false,
+ "LayoutGroupSortIndex": 0,
+ "Locked": false,
+ "LuaScript": "require(\"playercards/PlayerCardPanel\")",
+ "LuaScriptState": "{\"spawnBagState\":{\"placed\":[],\"placedObjects\":[]}}",
+ "MeasureMovement": false,
+ "Name": "Custom_Tile",
+ "Nickname": "Player Cards",
+ "Snap": true,
+ "Sticky": true,
+ "Tooltip": false,
+ "Transform": {
+ "posX": -1.083,
+ "posY": 1.255,
+ "posZ": 69.985,
+ "rotX": 0,
+ "rotY": 270,
+ "rotZ": 0,
+ "scaleX": 9.39,
+ "scaleY": 1,
+ "scaleZ": 9.39
+ },
+ "Value": 0,
+ "XmlUI": ""
+}
diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua
index 63ed2ffe..97f853f0 100644
--- a/src/arkhamdb/ArkhamDb.ttslua
+++ b/src/arkhamdb/ArkhamDb.ttslua
@@ -134,15 +134,6 @@ do
internal.maybePrint(table.concat({ "Found decklist: ", deck.name }), playerColor)
- log(table.concat({ "-", deck.name, "-" }))
- for k, v in pairs(deck) do
- if type(v) == "table" then
- log(table.concat { k, ":
" })
- else
- log(table.concat { k, ": ", tostring(v) })
- end
- end
-
-- Initialize deck slot table and perform common transformations. The order of these should not
-- be changed, as later steps may act on cards added in each. For example, a random weakness or
-- investigator may have bonded cards or taboo entries, and should be present
diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua
index a59a1cb6..a5954e2d 100644
--- a/src/playercards/AllCardsBag.ttslua
+++ b/src/playercards/AllCardsBag.ttslua
@@ -7,6 +7,8 @@ local WEAKNESS_CHECK_Z = 37
local cardIdIndex = { }
local classAndLevelIndex = { }
local basicWeaknessList = { }
+local uniqueWeaknessList = { }
+local cycleIndex = { }
local indexingDone = false
local allowRemoval = false
@@ -45,7 +47,9 @@ function clearIndexes()
classAndLevelIndex["Survivor-level0"] = { }
classAndLevelIndex["Rogue-level0"] = { }
classAndLevelIndex["Neutral-level0"] = { }
+ cycleIndex = { }
basicWeaknessList = { }
+ uniqueWeaknessList = { }
end
-- Clears the bag indexes and starts the coroutine to rebuild the indexes
@@ -121,27 +125,27 @@ function buildSupplementalIndexes()
local cardMetadata = card.metadata
-- If the ID key and the metadata ID don't match this is a duplicate card created by an
-- alternate_id, and we should skip it
- if (cardId == cardMetadata.id) then
+ if cardId == cardMetadata.id then
-- Add card to the basic weakness list, if appropriate. Some weaknesses have
-- multiple copies, and are added multiple times
- if (cardMetadata.weakness and cardMetadata.basicWeaknessCount ~= nil) then
+ if cardMetadata.weakness and cardMetadata.basicWeaknessCount ~= nil then
+ table.insert(uniqueWeaknessList, cardMetadata.id)
for i = 1, cardMetadata.basicWeaknessCount do
table.insert(basicWeaknessList, cardMetadata.id)
+ end
end
- end
- table.sort(basicWeaknessList, cardComparator)
- -- Add the card to the appropriate class and level indexes
- local isGuardian = false
- local isSeeker = false
- local isMystic = false
- local isRogue = false
- local isSurvivor = false
- local isNeutral = false
- local upgradeKey
- -- Excludes signature cards (which have no class or level) and alternate
- -- ID entries
- if (cardMetadata.class ~= nil and cardMetadata.level ~= nil) then
+ -- Add the card to the appropriate class and level indexes
+ local isGuardian = false
+ local isSeeker = false
+ local isMystic = false
+ local isRogue = false
+ local isSurvivor = false
+ local isNeutral = false
+ local upgradeKey
+ -- Excludes signature cards (which have no class or level) and alternate
+ -- ID entries
+ if (cardMetadata.class ~= nil and cardMetadata.level ~= nil) then
isGuardian = string.match(cardMetadata.class, "Guardian")
isSeeker = string.match(cardMetadata.class, "Seeker")
isMystic = string.match(cardMetadata.class, "Mystic")
@@ -171,12 +175,32 @@ function buildSupplementalIndexes()
if (isNeutral) then
table.insert(classAndLevelIndex["Neutral"..upgradeKey], cardMetadata.id)
end
+
+ local cycleName = cardMetadata.cycle
+ if cycleName ~= nil then
+ cycleName = string.lower(cycleName)
+ if string.match(cycleName, "return") then
+ cycleName = string.sub(cycleName, 11)
+ end
+ if cycleName == "the night of the zealot" then
+ cycleName = "core"
+ end
+ if cycleIndex[cycleName] == nil then
+ cycleIndex[cycleName] = { }
+ end
+ table.insert(cycleIndex[cycleName], cardMetadata.id)
+ end
end
end
end
for _, indexTable in pairs(classAndLevelIndex) do
table.sort(indexTable, cardComparator)
end
+ for _, indexTable in pairs(cycleIndex) do
+ table.sort(indexTable)
+ end
+ table.sort(basicWeaknessList, cardComparator)
+ table.sort(uniqueWeaknessList, cardComparator)
end
-- Comparison function used to sort the class card bag indexes. Sorts by card
@@ -235,6 +259,14 @@ function getCardsByClassAndLevel(params)
return classAndLevelIndex[params.class..upgradeKey];
end
+function getCardsByCycle(cycleName)
+ if (not indexingDone) then
+ broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
+ return { }
+ end
+ return cycleIndex[string.lower(cycleName)]
+end
+
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
-- Parameter array must contain these fields to define the search:
@@ -303,6 +335,10 @@ function getBasicWeaknesses()
return basicWeaknessList
end
+function getUniqueWeaknesses()
+ return uniqueWeaknessList
+end
+
-- Helper function that adds one to the table entry for the number of weaknesses in play
function incrementWeaknessCount(table, cardMetadata)
if (isBasicWeakness(cardMetadata)) then
@@ -322,6 +358,7 @@ function isInPlayArea(object)
return position.x < WEAKNESS_CHECK_X
and position.z < WEAKNESS_CHECK_Z
end
+
function isBasicWeakness(cardMetadata)
return cardMetadata ~= nil
and cardMetadata.weakness
diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua
index d6ad9f29..9b85aa2b 100644
--- a/src/playercards/PlayerCardPanel.ttslua
+++ b/src/playercards/PlayerCardPanel.ttslua
@@ -2,24 +2,49 @@ require("playercards/PlayerCardPanelData")
local spawnBag = require("playercards/spawnbag/SpawnBag")
local arkhamDb = require("arkhamdb/ArkhamDb")
--- TODO: Update when the real UI image is in place
-local BUTTON_WIDTH = 150
-local BUTTON_HEIGHT = 550
+-- Size and position information for the three rows of class buttons
+local CIRCLE_BUTTON_SIZE = 250
+local CLASS_BUTTONS_X_OFFSET = 0.1325
+local INVESTIGATOR_ROW_START = Vector(0.125, 0.1, -0.447)
+local LEVEL_ZERO_ROW_START = Vector(0.125, 0.1, -0.007)
+local UPGRADED_ROW_START = Vector(0.125, 0.1, 0.333)
+
+-- Size and position information for the two blocks of other buttons
+local MISC_BUTTONS_X_OFFSET = 0.155
+local WEAKNESS_ROW_START = Vector(0.157, 0.1, 0.666)
+local OTHER_ROW_START = Vector(0.605, 0.1, 0.666)
+
+-- Size and position information for the Cycle (box) buttons
+local CYCLE_BUTTON_SIZE = 468
+local CYCLE_BUTTON_START = Vector(-0.716, 0.1, -0.39)
+local CYCLE_COLUMN_COUNT = 3
+local CYCLE_BUTTONS_X_OFFSET = 0.267
+local CYCLE_BUTTONS_Z_OFFSET = 0.2665
local ALL_CARDS_BAG_GUID = "15bb07"
+local STARTER_DECK_MODE_SELECTED_COLOR = { 0.2, 0.2, 0.2, 0.8 }
+local TRANSPARENT = { 0, 0, 0, 0 }
+local STARTER_DECK_MODE_STARTERS = "starters"
+local STARTER_DECK_MODE_CARDS_ONLY = "cards"
+
local FACE_UP_ROTATION = { x = 0, y = 270, z = 0}
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180}
--- Coordinates to begin laying out cards to match the reserved areas of the
--- table. Cards will lay out horizontally, then create additional rows
+-- Coordinates to begin laying out cards. These vary based on the cards that are being placed
local START_POSITIONS = {
- skill = Vector(58.384, 1.36, 92.4),
- event = Vector(53.229, 1.36, 92.4),
- asset = Vector(40.960, 1.36, 92.4),
- investigator = Vector(60, 1.36, 80)
+ classCards = Vector(58.384, 1.36, 92.4),
+ investigator = Vector(60, 1.36, 86),
+ cycle = Vector(48, 1.36, 92.4),
+ other = Vector(56, 1.36, 86),
+ summonedServitor = Vector(55.5, 1.36, 60.2),
+ randomWeakness = Vector(55, 1.36, 75)
}
+-- Shifts to move rows of cards, and groups of rows, as different groupings are laid out
+local CARD_ROW_OFFSET = 3.7
+local CARD_GROUP_OFFSET = 2
+
-- Position offsets for investigator decks in investigator mode, defines the spacing for how the
-- rows and columns are laid out
local INVESTIGATOR_POSITION_SHIFT_ROW = Vector(-11, 0, 0)
@@ -31,7 +56,21 @@ local INVESTIGATOR_MAX_COLS = 6
local INVESTIGATOR_CARD_OFFSET = Vector(-2.55, 0, 0)
local INVESTIGATOR_SIGNATURE_OFFSET = Vector(-5.75, 0, 0)
-local spawnStarterDecks = false
+local CLASS_LIST = { "Guardian", "Seeker", "Rogue", "Mystic", "Survivor", "Neutral" }
+local CYCLE_LIST = {
+ "Core",
+ "The Dunwich Legacy",
+ "The Path to Carcosa",
+ "The Forgotten Age",
+ "The Circle Undone",
+ "The Dream-Eaters",
+ "The Innsmouth Conspiracy",
+ "Edge of the Earth",
+ "The Scarlet Keys",
+ "Investigator Packs"
+}
+
+local starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
function onSave()
local saveState = {
@@ -48,232 +87,223 @@ function onLoad(savedData)
spawnBag.loadFromSave(saveState.spawnBagState)
end
end
+ createButtons()
+end
- self.createButton({
- label="Guardian", click_function="spawnInvestigatorsGuardian", function_owner=self,
- position={-0.3,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Seeker", click_function="spawnInvestigatorsSeeker", function_owner=self,
- position={0,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Mystic", click_function="spawnInvestigatorsMystic", function_owner=self,
- position={0.3,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Rogue", click_function="spawnInvestigatorsRogue", function_owner=self,
- position={-0.3,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Survivor", click_function="spawnSurvivor", function_owner=self,
- position={0,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Neutral", click_function="spawnNeutral", function_owner=self,
- position={0.3,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
+function createButtons()
+ createInvestigatorButtons()
+ createLevelZeroButtons()
+ createUpgradedButtons()
+ createWeaknessButtons()
+ createOtherButtons()
+ createCycleButtons()
+ createClearButton()
+ -- Create investigator mode buttons last so the indexes are set when we need to update them
+ createInvestigatorModeButtons()
+end
- self.createButton({
- label="Core", click_function="spawnCore", function_owner=self,
- position={-0.3,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Dunwich", click_function="spawnDunwich", function_owner=self,
- position={0,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Carcosa", click_function="spawnCarcosa", function_owner=self,
- position={0.3,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Forgotten Age", click_function="spawnForgottenAge", function_owner=self,
- position={-0.3,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Circle Undone", click_function="spawnCircleUndone", function_owner=self,
- position={0,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Dream Eaters", click_function="spawnDreamEaters", function_owner=self,
- position={0.3,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Innsmouth", click_function="spawnInnsmouth", function_owner=self,
- position={-0.3,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="EotE", click_function="spawnEotE", function_owner=self,
- position={0,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Scarlet Keys", click_function="spawnScarletKeys", function_owner=self,
- position={0.3,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="InvPacks", click_function="spawnInvestigatorDecks", function_owner=self,
- position={-0.3,0.2,0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Investigators", click_function="setInvestigators", function_owner=self,
- position={-0.15,0.2,-0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Starters", click_function="setStarters", function_owner=self,
- position={0.15,0.2,-0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
-
- self.createButton({
- label="L0 Guardian", click_function="spawnBasicGuardian", function_owner=self,
- position={-0.15,0.2,0.3}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Guardian", click_function="spawnUpgradedGuardian", function_owner=self,
- position={0.15,0.2,0.3}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Seeker", click_function="spawnBasicSeeker", function_owner=self,
- position={-0.15,0.2,0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Seeker", click_function="spawnUpgradedSeeker", function_owner=self,
- position={0.15,0.2,0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Mystic", click_function="spawnBasicMystic", function_owner=self,
- position={-0.15,0.2,0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Mystic", click_function="spawnUpgradedGuardian", function_owner=self,
- position={0.15,0.2,0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Rogue", click_function="spawnBasicRogue", function_owner=self,
- position={-0.15,0.2,0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Rogue", click_function="spawnUpgradedRogue", function_owner=self,
- position={0.15,0.2,0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Survivor", click_function="spawnBasicSurvivor", function_owner=self,
- position={-0.15,0.2,0.7}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Survivor", click_function="spawnUpgradedSurvivor", function_owner=self,
- position={0.15,0.2,0.7}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Neutral", click_function="spawnBasicNeutral", function_owner=self,
- position={-0.15,0.2,0.8}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Neutral", click_function="spawnUpgradedNeutral", function_owner=self,
- position={0.15,0.2,0.8}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Clear", click_function="deleteAll", function_owner=self,
- position={0.5,0.2,0.9}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Weaknesses", click_function="spawnWeaknesses", function_owner=self,
- position={-0.5,0.2,0.9}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- local classList = { "Guardian", "Seeker", "Mystic", "Rogue", "Survivor", "Neutral" }
- for _, className in ipairs(classList) do
- local funcName = "spawnInvestigators"..className
- self.setVar(funcName, function(_, _, _) spawnGroup(className) end)
- funcName = "spawnBasic"..className
- self.setVar(funcName, function(_, _, _) spawnClassCards(className, false) end)
- funcName = "spawnUpgraded"..className
- self.setVar(funcName, function(_, _, _) spawnClassCards(className, true) end)
+function createInvestigatorButtons()
+ local invButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = INVESTIGATOR_ROW_START:copy()
+ for _, class in ipairs(CLASS_LIST) do
+ invButtonParams.click_function = "spawnInvestigators" .. class
+ invButtonParams.position = buttonPos
+ self.createButton(invButtonParams)
+ buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
+ self.setVar(invButtonParams.click_function, function(_, _, _) spawnInvestigatorGroup(class) end)
end
end
--- TODO: Replace these with something less manual once the full data in in place so we know what
--- keys to use
-function placeCore()
- spawnGroup("Core")
+function createLevelZeroButtons()
+ local l0ButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = LEVEL_ZERO_ROW_START:copy()
+ for _, class in ipairs(CLASS_LIST) do
+ l0ButtonParams.click_function = "spawnBasic" .. class
+ l0ButtonParams.position = buttonPos
+ self.createButton(l0ButtonParams)
+ buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
+ self.setVar(l0ButtonParams.click_function, function(_, _, _) spawnClassCards(class, false) end)
+ end
end
-function placeDunwich()
- spawnGroup("Dunwich")
+function createUpgradedButtons()
+ local upgradedButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = UPGRADED_ROW_START:copy()
+ for _, class in ipairs(CLASS_LIST) do
+ upgradedButtonParams.click_function = "spawnUpgraded" .. class
+ upgradedButtonParams.position = buttonPos
+ self.createButton(upgradedButtonParams)
+ buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
+ self.setVar(upgradedButtonParams.click_function, function(_, _, _) spawnClassCards(class, true) end)
+ end
end
-function placeCarcosa()
- spawnGroup("Carcosa")
+function createWeaknessButtons()
+ local weaknessButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = WEAKNESS_ROW_START:copy()
+ weaknessButtonParams.click_function = "spawnWeaknesses"
+ weaknessButtonParams.tooltip = "Basic Weaknesses"
+ weaknessButtonParams.position = buttonPos
+ self.createButton(weaknessButtonParams)
+ buttonPos.x = buttonPos.x + MISC_BUTTONS_X_OFFSET
+ weaknessButtonParams.click_function = "spawnRandomWeakness"
+ weaknessButtonParams.tooltip = "Random Weakness"
+ weaknessButtonParams.position = buttonPos
+ self.createButton(weaknessButtonParams)
end
-function placeForgottenAge()
- spawnGroup("ForgottenAge")
+function createOtherButtons()
+ local otherButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = OTHER_ROW_START:copy()
+ otherButtonParams.click_function = "spawnBonded"
+ otherButtonParams.tooltip = "Bonded Cards"
+ otherButtonParams.position = buttonPos
+ self.createButton(otherButtonParams)
+ buttonPos.x = buttonPos.x + MISC_BUTTONS_X_OFFSET
+ otherButtonParams.click_function = "spawnUpgradeSheets"
+ otherButtonParams.tooltip = "Customization Upgrade Sheets"
+ otherButtonParams.position = buttonPos
+ self.createButton(otherButtonParams)
end
-function placeCircleUndone()
- spawnGroup("CircleUndone")
+function createCycleButtons()
+ local cycleButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CYCLE_BUTTON_SIZE,
+ width = CYCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = CYCLE_BUTTON_START:copy()
+ local rowCount = 0
+ local colCount = 0
+ for _, cycle in ipairs(CYCLE_LIST) do
+ cycleButtonParams.click_function = "spawnCycle" .. cycle
+ cycleButtonParams.position = buttonPos
+ cycleButtonParams.tooltip = cycle
+ self.createButton(cycleButtonParams)
+ self.setVar(cycleButtonParams.click_function, function(_, _, _) spawnCycle(cycle) end)
+ colCount = colCount + 1
+ -- If we've reached the end of a row, shift down and back to the first column
+ if colCount >= CYCLE_COLUMN_COUNT then
+ buttonPos = CYCLE_BUTTON_START:copy()
+ rowCount = rowCount + 1
+ colCount = 0
+ buttonPos.z = buttonPos.z + CYCLE_BUTTONS_Z_OFFSET * rowCount
+ if rowCount == 3 then
+ -- Account for centered button on the final row
+ buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
+ end
+ else
+ buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
+ end
+ end
end
-function placeDreamEaters()
- spawnGroup("DreamEaters")
+function createClearButton()
+ self.createButton({
+ function_owner = self,
+ click_function = "deleteAll",
+ position = Vector(0, 0.1, 0.852),
+ rotation = Vector(0, 0, 0),
+ height = 170,
+ width = 750,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ })
end
-function placeInnsmouth()
- spawnGroup("Innsmouth")
+function createInvestigatorModeButtons()
+ local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
+
+ self.createButton({
+ function_owner = self,
+ click_function = "setCardsOnlyMode",
+ position = Vector(0.251, 0.1, -0.322),
+ rotation = Vector(0, 0, 0),
+ height = 170,
+ width = 760,
+ scale = Vector(0.25, 1, 0.25),
+ color = starterMode and TRANSPARENT or STARTER_DECK_MODE_SELECTED_COLOR
+ })
+ self.createButton({
+ function_owner = self,
+ click_function = "setStarterDeckMode",
+ position = Vector(0.66, 0.1, -0.322),
+ rotation = Vector(0, 0, 0),
+ height = 170,
+ width = 760,
+ scale = Vector(0.25, 1, 0.25),
+ color = starterMode and STARTER_DECK_MODE_SELECTED_COLOR or TRANSPARENT
+ })
+ local checkX = starterMode and 0.52 or 0.11
+ self.createButton({
+ function_owner = self,
+ label = "✓",
+ click_function = "doNothing",
+ position = Vector(checkX, 0.11, -0.317),
+ rotation = Vector(0, 0, 0),
+ height = 0,
+ width = 0,
+ scale = Vector(0.3, 1, 0.3),
+ font_color = { 0, 0, 0 },
+ color = { 1, 1, 1 }
+ })
end
-function placeEotE()
- spawnGroup("EotE")
+function setStarterDeckMode()
+ starterDeckMode = STARTER_DECK_MODE_STARTERS
+ updateStarterModeButtons()
end
-function placeScarletKeys()
- spawnGroup("ScarletKeys")
+function setCardsOnlyMode()
+ starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
+ updateStarterModeButtons()
end
-function placeInvestigatorDecks()
- spawnGroup("InvestigatorDecks")
-end
-
--- UI handler to put the investigator spawn in investigator mode.
-function setInvestigators()
- spawnStarterDecks = false
- printToAll("Spawning investigator piles")
-end
-
--- UI handler to put the investigator spawn in starter deck mode.
-function setStarters()
- spawnStarterDecks = true
- printToAll("Spawning starter decks")
+function updateStarterModeButtons()
+ local buttonCount = #self.getButtons()
+ -- Buttons are 0-indexed, so the last three are -1, -2, and -3 from the size
+ self.removeButton(buttonCount - 1)
+ self.removeButton(buttonCount - 2)
+ self.removeButton(buttonCount - 3)
+ createInvestigatorModeButtons()
end
-- Deletes all cards currently placed on the table
@@ -284,10 +314,11 @@ end
-- Spawn an investigator group, based on the current UI setting for either investigators or starter
-- decks.
---@param groupName String. Name of the group to spawn, matching a key in InvestigatorPanelData
-function spawnGroup(groupName)
+function spawnInvestigatorGroup(groupName)
+ local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
spawnBag.recall(true)
Wait.frames(function()
- if spawnStarterDecks then
+ if starterMode then
spawnStarters(groupName)
else
spawnInvestigators(groupName)
@@ -359,13 +390,13 @@ function buildCommonSpawnSpec(investigatorName, investigatorData, position, oneC
return {
{
name = investigatorName.."minicards",
- cards = oneCardOnly and investigatorData.minicards[1] or investigatorData.minicards,
+ cards = oneCardOnly and { investigatorData.minicards[1] } or investigatorData.minicards,
globalPos = position,
rotation = FACE_UP_ROTATION,
},
{
name = investigatorName.."cards",
- cards = oneCardOnly and investigatorData.cards[1] or investigatorData.cards,
+ cards = oneCardOnly and { investigatorData.cards[1] } or investigatorData.cards,
globalPos = cardPos,
rotation = FACE_UP_ROTATION,
},
@@ -452,31 +483,34 @@ function placeClassCards(cardClass, isUpgraded)
table.insert(assetList, cardId)
end
end
+ local groupPos = Vector(START_POSITIONS.classCards)
if #skillList > 0 then
spawnBag.spawn({
name = cardClass .. (isUpgraded and "upgraded" or "basic"),
cards = skillList,
- globalPos = START_POSITIONS.skill,
+ globalPos = groupPos,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
+ groupPos.x = groupPos.x - math.ceil(#skillList / 20) * CARD_ROW_OFFSET - CARD_GROUP_OFFSET
end
if #eventList > 0 then
spawnBag.spawn({
name = cardClass .. "event" .. (isUpgraded and "upgraded" or "basic"),
cards = eventList,
- globalPos = START_POSITIONS.event,
+ globalPos = groupPos,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
+ groupPos.x = groupPos.x - math.ceil(#eventList / 20) * CARD_ROW_OFFSET - CARD_GROUP_OFFSET
end
if #assetList > 0 then
spawnBag.spawn({
name = cardClass .. "asset" .. (isUpgraded and "upgraded" or "basic"),
cards = assetList,
- globalPos = START_POSITIONS.asset,
+ globalPos = groupPos,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
@@ -484,26 +518,108 @@ function placeClassCards(cardClass, isUpgraded)
end
end
--- Clears the current cards, and places all basic weaknesses on the table.
-function spawnWeaknesses()
- spawnBag.recall(fast)
+-- Spawns the investigator sets and all cards for the given cycle
+---@param cycle String Name of a cycle, should match the standard used in card metadata
+function spawnCycle(cycle)
+ spawnBag.recall(true)
+ spawnInvestigators(cycle)
local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
local indexReady = allCardsBag.call("isIndexReady")
if (not indexReady) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return
end
- local weaknessIdList = allCardsBag.call("getBasicWeaknesses")
+ local cycleCardList = allCardsBag.call("getCardsByCycle", cycle)
local copiedList = { }
- for i, id in ipairs(weaknessIdList) do
+ for i, id in ipairs(cycleCardList) do
copiedList[i] = id
end
spawnBag.spawn({
- name = "weaknesses",
+ name = "cycle"..cycle,
cards = copiedList,
- globalPos = START_POSITIONS.asset,
+ globalPos = START_POSITIONS.cycle,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
end
+
+function spawnBonded()
+ spawnBag.recall(true)
+ spawnBag.spawn({
+ name = "bonded",
+ cards = BONDED_CARD_LIST,
+ globalPos = START_POSITIONS.classCards,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+end
+
+function spawnUpgradeSheets()
+ spawnBag.recall(true)
+ spawnBag.spawn({
+ name = "upgradeSheets",
+ cards = UPGRADE_SHEET_LIST,
+ globalPos = START_POSITIONS.classCards,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+ spawnBag.spawn({
+ name = "servitor",
+ cards = { "09080-m" },
+ globalPos = START_POSITIONS.summonedServitor,
+ rotation = FACE_UP_ROTATION,
+ })
+end
+
+-- Clears the current cards, and places all basic weaknesses on the table.
+function spawnWeaknesses()
+ spawnBag.recall(true)
+ local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
+ local indexReady = allCardsBag.call("isIndexReady")
+ if (not indexReady) then
+ broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
+ return
+ end
+ local weaknessIdList = allCardsBag.call("getUniqueWeaknesses")
+ local copiedList = { }
+ for i, id in ipairs(weaknessIdList) do
+ copiedList[i] = id
+ end
+ local groupPos = Vector(START_POSITIONS.classCards)
+ spawnBag.spawn({
+ name = "weaknesses",
+ cards = copiedList,
+ globalPos = groupPos,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+ groupPos.x = groupPos.x - math.ceil(#copiedList / 20) * CARD_ROW_OFFSET - CARD_GROUP_OFFSET
+ spawnBag.spawn({
+ name = "evolvedWeaknesses",
+ cards = EVOLVED_WEAKNESSES,
+ globalPos = groupPos,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+end
+
+function spawnRandomWeakness()
+ spawnBag.recall(true)
+ local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
+ local weaknessId = allCardsBag.call("getRandomWeaknessId")
+ if (weaknessId == nil) then
+ broadcastToAll("All basic weaknesses are in play!", {0.9, 0.2, 0.2})
+ return
+ end
+ spawnBag.spawn({
+ name = "randomWeakness",
+ cards = { weaknessId },
+ globalPos = START_POSITIONS.randomWeakness,
+ rotation = FACE_UP_ROTATION,
+ })
+end
diff --git a/src/playercards/PlayerCardPanelData.ttslua b/src/playercards/PlayerCardPanelData.ttslua
index 62e13e6e..6d186e55 100644
--- a/src/playercards/PlayerCardPanelData.ttslua
+++ b/src/playercards/PlayerCardPanelData.ttslua
@@ -1,3 +1,45 @@
+BONDED_CARD_LIST = {
+ "05314", -- Soothing Melody
+ "06277", -- Wish Eater
+ "06019", -- Bloodlust
+ "06022", -- Pendant of the Queen
+ "05317", -- Blood-rite
+ "06113", -- Essence of the Dream
+ "06028", -- Stars Are Right
+ "06025", -- Guardian of the Crystallizer
+ "06283", -- Unbound Beast
+ "06032", -- Zeal
+ "06031", -- Hope
+ "06033", -- Augur
+ "06331", -- Dream Parasite
+ "06015a", -- Dream-Gate
+}
+
+UPGRADE_SHEET_LIST = {
+ "09040-c", -- Alchemical Distillation
+ "09023-c", -- Custom Modifications
+ "09059-c", -- Damning Testimony
+ "09041-c", -- Emperical Hypothesis
+ "09060-c", -- Friends in Low Places
+ "09101-c", -- Grizzled
+ "09061-c", -- Honed Instinct
+ "09021-c", -- Hunter's Armor
+ "09119-c", -- Hyperphysical Shotcaster
+ "09079-c", -- Living Ink
+ "09100-c", -- Makeshift Trap
+ "09099-c", -- Pocket Multi Tool
+ "09081-c", -- Power Word
+ "09022-c", -- Runic Axe
+ "09080-c", -- Summoned Servitor
+ "09042-c", -- Raven's Quill
+}
+
+EVOLVED_WEAKNESSES = {
+ "04039",
+ "04041",
+ "04042",
+}
+
------------------ START INVESTIGATOR DATA DEFINITION ------------------
INVESTIGATOR_GROUPS = {
Guardian = {
@@ -13,10 +55,6 @@ INVESTIGATOR_GROUPS = {
"D2",
"R3",
"D3",
- "R4",
- "D4",
- "R5",
- "D5",
},
}
@@ -25,13 +63,13 @@ INVESTIGATORS["Roland Banks"] = {
cards = { "01001", "01001-promo", "01001-p", "01001-pf", "01001-pb", },
minicards = { "01001-m", "01001-promo-m", },
signatures = { "01006", "01007", "90030", "90031", },
- starterDeck = "1462",
+ starterDeck = "2624931",
}
INVESTIGATORS["Daisy Walker"] = {
cards = { "01002", "01002-p", "01002-pf", "01002-pb", },
minicards = { "01002-m", },
signatures = { "01008", "01009", "90002", "90003" },
- starterDeck = "42652",
+ starterDeck = "2624938",
}
------------------ END INVESTIGATOR DATA DEFINITION ------------------
INVESTIGATORS["R2"] = INVESTIGATORS["Roland Banks"]