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"]