From 9be0201909e7d6cd53c82d9d001eedb1859c8417 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 29 Oct 2024 17:02:54 +0100 Subject: [PATCH 1/7] updated attachment importing --- src/arkhamdb/DeckImporter.ttslua | 186 +++++++++++++++++++------------ 1 file changed, 116 insertions(+), 70 deletions(-) diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index ac201af4..f7cbe96b 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -1,38 +1,38 @@ require("playercards/PlayerCardSpawner") -local allCardsBagApi = require("playercards/AllCardsBagApi") -local arkhamDb = require("arkhamdb/ArkhamDb") +local allCardsBagApi = require("playercards/AllCardsBagApi") +local arkhamDb = require("arkhamdb/ArkhamDb") +local GlobalApi = require("core/GlobalApi") local guidReferenceApi = require("core/GUIDReferenceApi") -local playermatApi = require("playermat/PlayermatApi") -local zones = require("playermat/Zones") +local playermatApi = require("playermat/PlayermatApi") +local zones = require("playermat/Zones") local matsWithInvestigator = {} -local startsInPlayCount = 0 +local startsInPlayCount = 0 +local INPUT_FIELD_HEIGHT = 340 +local INPUT_FIELD_WIDTH = 1500 +local FIELD_COLOR = { 0.9, 0.7, 0.5 } -local INPUT_FIELD_HEIGHT = 340 -local INPUT_FIELD_WIDTH = 1500 -local FIELD_COLOR = { 0.9, 0.7, 0.5 } +local PRIVATE_TOGGLE_LABELS = {} +PRIVATE_TOGGLE_LABELS[true] = "Private" +PRIVATE_TOGGLE_LABELS[false] = "Published" -local PRIVATE_TOGGLE_LABELS = {} -PRIVATE_TOGGLE_LABELS[true] = "Private" -PRIVATE_TOGGLE_LABELS[false] = "Published" +local UPGRADED_TOGGLE_LABELS = {} +UPGRADED_TOGGLE_LABELS[true] = "Upgraded" +UPGRADED_TOGGLE_LABELS[false] = "Specific" -local UPGRADED_TOGGLE_LABELS = {} -UPGRADED_TOGGLE_LABELS[true] = "Upgraded" -UPGRADED_TOGGLE_LABELS[false] = "Specific" - -local STANDALONE_TOGGLE_LABELS = {} -STANDALONE_TOGGLE_LABELS[true] = "Yes" +local STANDALONE_TOGGLE_LABELS = {} +STANDALONE_TOGGLE_LABELS[true] = "Yes" STANDALONE_TOGGLE_LABELS[false] = "No" -redDeckId = "" +redDeckId = "" orangeDeckId = "" -whiteDeckId = "" -greenDeckId = "" +whiteDeckId = "" +greenDeckId = "" -local privateDeck = true +local privateDeck = true local loadNewestDeck = true -local standalone = false +local standalone = false function onLoad(script_state) initializeUi(JSON.decode(script_state)) @@ -88,7 +88,7 @@ function makeOptionToggles() cParams.function_owner = self cParams.width = 1750 cParams.height = INPUT_FIELD_HEIGHT - cParams.position = Vector( 0.22, 0.1, -0.102) + cParams.position = Vector(0.22, 0.1, -0.102) cParams.scale = { 0.1, 0.1, 0.1 } cParams.font_size = 240 cParams.hover_color = { 0.4, 0.6, 0.8 } @@ -201,7 +201,7 @@ function loadDecksCoroutine() if not allCardsBagApi.isIndexReady() then return end matsWithInvestigator = playermatApi.getUsedMatColors() - for _, matColor in ipairs({"White", "Orange", "Green", "Red"}) do + for _, matColor in ipairs({ "White", "Orange", "Green", "Red" }) do local deckId = _G[string.lower(matColor) .. "DeckId"] if deckId ~= nil and deckId ~= "" then buildDeck(matColor, deckId) @@ -222,16 +222,16 @@ end ---@param cardMetadata table Contains card metadata ---@return string Zone Name of the zone such as "Deck", "SetAside1", etc. (See zones file for a list of valid zones) function getDefaultCardZone(cardMetadata, bondedList) - if cardMetadata.id == "09080-m" then -- Have to check the Servitor before other minicards + if cardMetadata.id == "09080-m" then + -- Have to check the Servitor before other minicards return "SetAside6" - elseif cardMetadata.id == "09006" then -- On The Mend is set aside - return "SetAside2" - elseif cardMetadata.id == "06233" or cardMetadata.id == "06275" then -- False Awakening is set aside - return "SetAside2" - elseif cardMetadata.id == "71052" then -- Jewel of Sarnath is set aside - return "SetAside2" - elseif bondedList[cardMetadata.id] then + elseif cardMetadata.id == "09006" or cardMetadata.id == "06233" or cardMetadata.id == "06275" + or cardMetadata.id == "71052" or bondedList[cardMetadata.id] then + -- On The Mend, False Awakening, Jewel of Sarnath and bonded cards are set aside return "SetAside2" + elseif cardMetadata.id == "07303" or cardMetadata.id == "09077" then + -- Ancestral Knowledge / Underworld Market + return "SetAside3" elseif cardMetadata.type == "Investigator" then return "Investigator" elseif cardMetadata.type == "Minicard" then @@ -242,7 +242,6 @@ function getDefaultCardZone(cardMetadata, bondedList) return startsInPlayTracker() elseif cardMetadata.permanent then return "SetAside1" - -- SetAside3 is used for Ancestral Knowledge / Underworld Market else return "Deck" end @@ -284,11 +283,19 @@ end ---@param loadAltInvestigator string Contains the name of alternative art for the investigator ("normal", "revised" or "promo") function loadCards(slots, investigatorId, bondedList, customizations, playerColor, loadAltInvestigator) function coinside() + local slotsCopy = deepCopy(slots) local cardsToSpawn = {} local resourceModifier = 0 -- reset the startsInPlayCount startsInPlayCount = 0 + + -- Attachment Helper handling + if GlobalApi.getOptionPanelState()["showAttachmentHelper"] then + + else + + end for cardId, cardCount in pairs(slots) do local card = allCardsBagApi.getCardById(cardId) if card ~= nil then @@ -308,10 +315,14 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo end updateStartingResources(playerColor, resourceModifier) - handleAncestralKnowledge(cardsToSpawn) - handleUnderworldMarket(cardsToSpawn, playerColor) - handleHunchDeck(investigatorId, cardsToSpawn, bondedList, playerColor) - handleSpiritDeck(investigatorId, cardsToSpawn, playerColor, customizations) + handleAncestralKnowledge(cardsToSpawn, slotsCopy) + handleNonRandomSelections(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) + + -- parallel Jim Culver handling + if investigatorId == "02004-p" or investigatorId == "02004-pb" then + handleSpiritDeck(cardsToSpawn, playerColor, customizations) + end + handleCustomizableUpgrades(cardsToSpawn, customizations) handlePeteSignatureAssets(investigatorId, cardsToSpawn) @@ -495,25 +506,19 @@ end -- Check to see if the deck list has Ancestral Knowledge. If it does, move 5 random skills to SetAside3 ---@param cardList table Deck list being created -function handleAncestralKnowledge(cardList) - local hasAncestralKnowledge = false +function handleAncestralKnowledge(cardList, slotsCopy) + if not slotsCopy["07303"] or slotsCopy["07303"] == 0 then return end + local skillList = {} - -- Have to process the entire list to check for Ancestral Knowledge and get all possible skills, so do both in one pass + -- process cardlist for skills for i, card in ipairs(cardList) do - if card.metadata.id == "07303" then - hasAncestralKnowledge = true - card.zone = "SetAside3" - elseif (card.metadata.type == "Skill" - and card.zone == "Deck" - and not card.metadata.weakness) then + if card.metadata.type == "Skill" and card.zone == "Deck" and not card.metadata.weakness then table.insert(skillList, i) end end - if not hasAncestralKnowledge then return end - - -- Move 5 random skills to SetAside3 + -- move 5 random skills to SetAside3 for i = 1, 5 do local skillListIndex = math.random(#skillList) cardList[skillList[skillListIndex]].zone = "UnderSetAside3" @@ -521,25 +526,56 @@ function handleAncestralKnowledge(cardList) end end +function handleNonRandomSelections(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) + -- handle cards with attachments (Stick to the Plan, Underworld Market and Bewitching) + for _, cardId in ipairs({ "03264", "09077", "10079" }) do + if slotsCopy[cardId] and slotsCopy[cardId] > 0 then + if customizations["attachments_" .. cardId] then + handleAttachment(cardId, cardsToSpawn, customizations) + elseif cardId == "09077" then + handleUnderworldMarket(cardsToSpawn, slotsCopy, playerColor) + elseif cardId == "05002" then + handleHunchDeck(cardsToSpawn, bondedList, playerColor) + end + end + end +end + +function handleAttachment(parentId, cardsToSpawn, customizations) + -- get list of cards that are attached + local attachmentList = {} + + -- split by "," + for str in string.gmatch(customizations["attachments_" .. parentId], "([^,]+)") do + table.insert(attachmentList, str) + end + + -- process cards + for i = #attachmentList, 1, -1 do + for j = #cardsToSpawn, 1, -1 do + if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone ~= "BelowSetAside" then + cardsToSpawn[j].zone = "BelowSetAside" + break + end + end + end +end + -- Check for and handle Underworld Market by moving all Illicit cards to UnderSetAside3 ---@param cardList table Deck list being created ---@param playerColor string Color this deck is being loaded for -function handleUnderworldMarket(cardList, playerColor) - local hasMarket = false +function handleUnderworldMarket(cardList, slotsCopy, playerColor) + if not slotsCopy["09077"] or slotsCopy["09077"] == 0 then return end + local illicitList = {} - -- Process the entire list to check for Underworld Market and get all possible Illicit cards, doing both in one pass + -- get all possible Illicit cards for i, card in ipairs(cardList) do - if card.metadata.id == "09077" then - hasMarket = true - card.zone = "SetAside3" - elseif card.metadata.traits ~= nil and string.find(card.metadata.traits, "Illicit", 1, true) and card.zone == "Deck" then + if card.metadata.traits ~= nil and string.find(card.metadata.traits, "Illicit", 1, true) and card.zone == "Deck" then table.insert(illicitList, i) end end - if not hasMarket then return end - if #illicitList < 10 then printToAll("Only " .. #illicitList .. " Illicit cards in your deck, you can't trigger Underworld Market's ability.", playerColor) else @@ -561,13 +597,10 @@ function handleUnderworldMarket(cardList, playerColor) end end --- If the investigator is Joe Diamond, extract all Insight events to SetAside5 to build the Hunch Deck ----@param investigatorId string ID for the deck's investigator card +-- Extract all Insight events to SetAside5 to build the Hunch Deck for Joe Diamond ---@param cardList table Deck list being created ---@param playerColor string Color this deck is being loaded for -function handleHunchDeck(investigatorId, cardList, bondedList, playerColor) - if investigatorId ~= "05002" then return end - +function handleHunchDeck(cardList, bondedList, playerColor) local insightList = {} for i, card in ipairs(cardList) do if (card.metadata.type == "Event" @@ -597,14 +630,11 @@ function handleHunchDeck(investigatorId, cardList, bondedList, playerColor) end end --- If the investigator is Parallel Jim Culver, extract all Ally assets to SetAside5 to build the Spirit Deck ----@param investigatorId string ID for the deck's investigator card +-- Extract all Ally assets to SetAside5 to build the Spirit Deck for parallel Jim Culver ---@param cardList table Deck list being created ---@param playerColor string Color this deck is being loaded for ---@param customizations table Additional deck information -function handleSpiritDeck(investigatorId, cardList, playerColor, customizations) - if investigatorId ~= "02004-p" and investigatorId ~= "02004-pb" then return end - +function handleSpiritDeck(cardList, playerColor, customizations) local spiritList = {} if customizations["extra_deck"] then -- split by "," @@ -639,9 +669,11 @@ function handleSpiritDeck(investigatorId, cardList, playerColor, customizations) end if #spiritList < 10 then - printToAll("Jim's spirit deck must have 9 Ally assets but the deck only has " .. (#spiritList - 1) .. " Ally assets.", playerColor) + printToAll("Jim's spirit deck must have 9 Ally assets but the deck only has " .. + (#spiritList - 1) .. " Ally assets.", playerColor) elseif #spiritList > 11 then - printToAll("Moved all " .. (#spiritList - 1) .. " Ally assets to the spirit deck, reduce it to 10 (including Vengeful Shade).", playerColor) + printToAll("Moved all " .. (#spiritList - 1) .. + " Ally assets to the spirit deck, reduce it to 10 (including Vengeful Shade).", playerColor) else printToAll("Built Jim's spirit deck", playerColor) end @@ -762,3 +794,17 @@ function updateStartingResources(playerColor, resourceModifier) printToAll("Modified starting resources", playerColor) end end + +-- internal function to create a 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 From 591661b9fcb8ec804c5c743cfbafa05702915427 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 29 Oct 2024 19:03:01 +0100 Subject: [PATCH 2/7] added attachment helper spawning --- .../AttachmentHelper.7f4976.json | 4 + src/arkhamdb/DeckImporter.ttslua | 73 +++++++++++++------ 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/objects/ObjectSourceBag.830bd0/AttachmentHelper.7f4976.json b/objects/ObjectSourceBag.830bd0/AttachmentHelper.7f4976.json index fd21c75a..3a46602d 100644 --- a/objects/ObjectSourceBag.830bd0/AttachmentHelper.7f4976.json +++ b/objects/ObjectSourceBag.830bd0/AttachmentHelper.7f4976.json @@ -34,6 +34,10 @@ "Nickname": "Attachment Helper", "Snap": true, "Sticky": true, + "Tags": [ + "AttachmentHelperBag", + "CleanUpHelper_ignore" + ], "Tooltip": true, "Transform": { "posX": 27.677, diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index f7cbe96b..14e93203 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -286,20 +286,36 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo local slotsCopy = deepCopy(slots) local cardsToSpawn = {} local resourceModifier = 0 + cardsWithAttachments = { + ["03264"] = true, + ["07303"] = true, + ["09077"] = true, + ["10079"] = true + } -- reset the startsInPlayCount startsInPlayCount = 0 - -- Attachment Helper handling - if GlobalApi.getOptionPanelState()["showAttachmentHelper"] then - - else + -- reserve slots for cards with attachments + for cardId, _ in pairs(cardsWithAttachments) do + if slotsCopy[cardId] and slotsCopy[cardId] > 0 then + -- increase startsInPlayCount by 1 and reserve slot for this card + startsInPlayCount = startsInPlayCount + 1 + cardsWithAttachments[cardId] = startsInPlayCount + -- reserve an additional slot for the attachments + startsInPlayCount = startsInPlayCount + 1 + end end + for cardId, cardCount in pairs(slots) do local card = allCardsBagApi.getCardById(cardId) if card ~= nil then - local cardZone = getDefaultCardZone(card.metadata, bondedList) + if cardsWithAttachments[cardId] and cardsWithAttachments[cardId] < 6 then + cardZone = "Blank" .. cardsWithAttachments[cardId] + else + cardZone = getDefaultCardZone(card.metadata, bondedList) + end for i = 1, cardCount do table.insert(cardsToSpawn, { data = card.data, metadata = card.metadata, zone = cardZone }) end @@ -315,8 +331,7 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo end updateStartingResources(playerColor, resourceModifier) - handleAncestralKnowledge(cardsToSpawn, slotsCopy) - handleNonRandomSelections(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) + handleAllAttachments(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) -- parallel Jim Culver handling if investigatorId == "02004-p" or investigatorId == "02004-pb" then @@ -338,6 +353,21 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo local deckRot = zones.getDefaultCardRotation(playerColor, zone) local callback = nil + -- spawn attachment helpers if option is enabled + if GlobalApi.getOptionPanelState()["showAttachmentHelper"] then + local objs = getObjectsWithTag("AttachmentHelperBag") + if #objs > 0 then + for cardId, reservedSlot in pairs(cardsWithAttachments) do + if reservedSlot < 7 then + objs[1].takeObject({ + position = zones.getZonePosition(playerColor, "Blank" .. reservedSlot):setAt("y", 1.6), + rotation = deckRot + }) + end + end + end + end + -- If cards are spread too close together TTS groups them weirdly, selecting multiples -- when hovering over a single card. This distance is the minimum to avoid that. local spreadDistance = 1.15 @@ -504,15 +534,11 @@ function removeBusyZones(playerColor, zoneDecks) end end --- Check to see if the deck list has Ancestral Knowledge. If it does, move 5 random skills to SetAside3 ----@param cardList table Deck list being created -function handleAncestralKnowledge(cardList, slotsCopy) - if not slotsCopy["07303"] or slotsCopy["07303"] == 0 then return end - +function handleAncestralKnowledge(cardsToSpawn) local skillList = {} -- process cardlist for skills - for i, card in ipairs(cardList) do + for i, card in ipairs(cardsToSpawn) do if card.metadata.type == "Skill" and card.zone == "Deck" and not card.metadata.weakness then table.insert(skillList, i) end @@ -521,40 +547,41 @@ function handleAncestralKnowledge(cardList, slotsCopy) -- move 5 random skills to SetAside3 for i = 1, 5 do local skillListIndex = math.random(#skillList) - cardList[skillList[skillListIndex]].zone = "UnderSetAside3" + cardsToSpawn[skillList[skillListIndex]].zone = "UnderSetAside3" table.remove(skillList, skillListIndex) end end -function handleNonRandomSelections(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) - -- handle cards with attachments (Stick to the Plan, Underworld Market and Bewitching) - for _, cardId in ipairs({ "03264", "09077", "10079" }) do +-- handle cards with attachments (Stick to the Plan, Underworld Market and Bewitching) +function handleAllAttachments(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) + for cardId, reservedSlot in pairs(cardsWithAttachments) do if slotsCopy[cardId] and slotsCopy[cardId] > 0 then - if customizations["attachments_" .. cardId] then + if customizations["attachments_" .. cardId] and reservedSlot < 6 then handleAttachment(cardId, cardsToSpawn, customizations) elseif cardId == "09077" then handleUnderworldMarket(cardsToSpawn, slotsCopy, playerColor) elseif cardId == "05002" then handleHunchDeck(cardsToSpawn, bondedList, playerColor) + elseif cardId == "07303" then + handleAncestralKnowledge(cardsToSpawn) end end end end function handleAttachment(parentId, cardsToSpawn, customizations) - -- get list of cards that are attached + -- get list of cards that are attached (split by ",") local attachmentList = {} - - -- split by "," for str in string.gmatch(customizations["attachments_" .. parentId], "([^,]+)") do table.insert(attachmentList, str) end -- process cards + local zone = "Blank" .. (cardsWithAttachments[parentId] + 1) for i = #attachmentList, 1, -1 do for j = #cardsToSpawn, 1, -1 do - if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone ~= "BelowSetAside" then - cardsToSpawn[j].zone = "BelowSetAside" + if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone ~= zone then + cardsToSpawn[j].zone = zone break end end From a40960457d74b54995ee7d24d690dc61e4e04bde Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 29 Oct 2024 22:45:26 +0100 Subject: [PATCH 3/7] updated attachment helper handling --- src/accessories/AttachmentHelper.ttslua | 39 +++-- src/arkhamdb/ArkhamDb.ttslua | 6 +- src/arkhamdb/DeckImporter.ttslua | 204 ++++++++++++++---------- src/playermat/Zones.ttslua | 163 +++++++------------ 4 files changed, 214 insertions(+), 198 deletions(-) diff --git a/src/accessories/AttachmentHelper.ttslua b/src/accessories/AttachmentHelper.ttslua index 16012562..87b950b3 100644 --- a/src/accessories/AttachmentHelper.ttslua +++ b/src/accessories/AttachmentHelper.ttslua @@ -164,28 +164,47 @@ function onDrop(playerColor) -- assume the first eligible object as intended sync object local syncObj = searchResult[1] local syncName = syncObj.getName() - local notes = syncObj.getGMNotes() - local md = JSON.decode(notes) or {} + local md = JSON.decode(syncObj.getGMNotes()) or {} + + local found = loadDataFromMetadata({ + md = md, + playerColor = playerColor, + syncName = syncName, + syncRotation = syncObj.getRotation() + }) + if not found then + printToColor("Didn't find background for '" .. syncName .. "'!", playerColor, "Orange") + end +end + +function loadDataFromMetadata(params) + local md = params.md + local syncRotation = params.syncRotation + local playerColor = params.playerColor + local syncName = params.syncName -- loop through background table for _, bgInfo in ipairs(BACKGROUNDS) do if bgInfo.id == md.id or bgInfo.id2 == md.id then - printToColor("Background for '" .. syncName .. "' loaded!", playerColor, "Green") + if syncName and playerColor then + printToColor("Background for '" .. syncName .. "' loaded!", playerColor, "Green") + end showIcons = bgInfo.icons - -- update rotation - local syncRotY = syncObj.getRotation().y - if md.type == "Investigator" then - syncRotY = syncRotY + 90 + -- maybe update rotation + if syncRotation then + if md.type == "Investigator" then + syncRotation.y = syncRotation.y + 90 + end + self.setRotation(self.getRotation():setAt("y", syncRotation.y)) end - self.setRotation(self.getRotation():setAt("y", syncRotY)) -- update the image updateImage(bgInfo.url) - return + return true end end - printToColor("Didn't find background for '" .. syncName .. "'!", playerColor, "Orange") + return false end -- called by context menu to change background image diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua index 6a9e1812..91a82bef 100644 --- a/src/arkhamdb/ArkhamDb.ttslua +++ b/src/arkhamdb/ArkhamDb.ttslua @@ -193,7 +193,11 @@ do for _, weaknessId in ipairs(weaknessIds) do slots[weaknessId] = (slots[weaknessId] or 0) + 1 end - internal.maybePrint("Added " .. #weaknessIds .. " random basic weakness(es) to deck", playerColor) + if #weaknessIds == 1 then + internal.maybePrint("Added 1 random basic weakness to deck", playerColor) + else + internal.maybePrint("Added " .. #weaknessIds .. " random basic weaknesses to deck", playerColor) + end end end diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 14e93203..08f79b37 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -1,17 +1,17 @@ require("playercards/PlayerCardSpawner") -local allCardsBagApi = require("playercards/AllCardsBagApi") -local arkhamDb = require("arkhamdb/ArkhamDb") -local GlobalApi = require("core/GlobalApi") -local guidReferenceApi = require("core/GUIDReferenceApi") -local playermatApi = require("playermat/PlayermatApi") -local zones = require("playermat/Zones") +local allCardsBagApi = require("playercards/AllCardsBagApi") +local arkhamDb = require("arkhamdb/ArkhamDb") +local GlobalApi = require("core/GlobalApi") +local guidReferenceApi = require("core/GUIDReferenceApi") +local playermatApi = require("playermat/PlayermatApi") +local zones = require("playermat/Zones") -local matsWithInvestigator = {} -local startsInPlayCount = 0 -local INPUT_FIELD_HEIGHT = 340 -local INPUT_FIELD_WIDTH = 1500 -local FIELD_COLOR = { 0.9, 0.7, 0.5 } +local matsWithInvestigator = {} +local startsInPlayCount = 0 +local INPUT_FIELD_HEIGHT = 340 +local INPUT_FIELD_WIDTH = 1500 +local FIELD_COLOR = { 0.9, 0.7, 0.5 } local PRIVATE_TOGGLE_LABELS = {} PRIVATE_TOGGLE_LABELS[true] = "Private" @@ -25,14 +25,14 @@ local STANDALONE_TOGGLE_LABELS = {} STANDALONE_TOGGLE_LABELS[true] = "Yes" STANDALONE_TOGGLE_LABELS[false] = "No" -redDeckId = "" -orangeDeckId = "" -whiteDeckId = "" -greenDeckId = "" +redDeckId = "" +orangeDeckId = "" +whiteDeckId = "" +greenDeckId = "" -local privateDeck = true -local loadNewestDeck = true -local standalone = false +local privateDeck = true +local loadNewestDeck = true +local standalone = false function onLoad(script_state) initializeUi(JSON.decode(script_state)) @@ -47,13 +47,13 @@ function onSave() return JSON.encode(getUiState()) end ---@return uiStateTable uiStateTable Contains data about the current UI state function getUiState() return { - redDeck = redDeckId, - orangeDeck = orangeDeckId, - whiteDeck = whiteDeckId, - greenDeck = greenDeckId, + redDeck = redDeckId, + orangeDeck = orangeDeckId, + whiteDeck = whiteDeckId, + greenDeck = greenDeckId, privateDeck = privateDeck, - loadNewest = loadNewestDeck, - standalone = standalone + loadNewest = loadNewestDeck, + standalone = standalone } end @@ -68,13 +68,13 @@ end -- Sets up the UI for the deck loader, populating fields from the given save state table decoded from onLoad() function initializeUi(savedUiState) if savedUiState ~= nil then - redDeckId = savedUiState.redDeck - orangeDeckId = savedUiState.orangeDeck - whiteDeckId = savedUiState.whiteDeck - greenDeckId = savedUiState.greenDeck - privateDeck = savedUiState.privateDeck + redDeckId = savedUiState.redDeck + orangeDeckId = savedUiState.orangeDeck + whiteDeckId = savedUiState.whiteDeck + greenDeckId = savedUiState.greenDeck + privateDeck = savedUiState.privateDeck loadNewestDeck = savedUiState.loadNewest - standalone = savedUiState.standalone + standalone = savedUiState.standalone end makeOptionToggles() @@ -205,7 +205,7 @@ function loadDecksCoroutine() local deckId = _G[string.lower(matColor) .. "DeckId"] if deckId ~= nil and deckId ~= "" then buildDeck(matColor, deckId) - coroutine.yield() + coWaitFrames(3) end end end @@ -224,14 +224,17 @@ end function getDefaultCardZone(cardMetadata, bondedList) if cardMetadata.id == "09080-m" then -- Have to check the Servitor before other minicards - return "SetAside6" + return "SetAside5" elseif cardMetadata.id == "09006" or cardMetadata.id == "06233" or cardMetadata.id == "06275" or cardMetadata.id == "71052" or bondedList[cardMetadata.id] then -- On The Mend, False Awakening, Jewel of Sarnath and bonded cards are set aside return "SetAside2" - elseif cardMetadata.id == "07303" or cardMetadata.id == "09077" then - -- Ancestral Knowledge / Underworld Market + elseif cardMetadata.id == "07303" then + -- Ancestral Knowledge return "SetAside3" + elseif cardMetadata.id == "09077" then + -- Underworld Market + return "SetAside6" elseif cardMetadata.type == "Investigator" then return "Investigator" elseif cardMetadata.type == "Minicard" then @@ -292,26 +295,29 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo ["09077"] = true, ["10079"] = true } + local zoneWithAttachments = {} -- reset the startsInPlayCount startsInPlayCount = 0 - -- reserve slots for cards with attachments + -- reserve slots for cards with attachments (not for Underworld Market) for cardId, _ in pairs(cardsWithAttachments) do - if slotsCopy[cardId] and slotsCopy[cardId] > 0 then + if cardId ~= "09077" and slotsCopy[cardId] and slotsCopy[cardId] > 0 then -- increase startsInPlayCount by 1 and reserve slot for this card startsInPlayCount = startsInPlayCount + 1 cardsWithAttachments[cardId] = startsInPlayCount -- reserve an additional slot for the attachments startsInPlayCount = startsInPlayCount + 1 + zoneWithAttachments["Blank" .. startsInPlayCount] = true end end for cardId, cardCount in pairs(slots) do local card = allCardsBagApi.getCardById(cardId) if card ~= nil then - if cardsWithAttachments[cardId] and cardsWithAttachments[cardId] < 6 then + local reservedSlot = cardsWithAttachments[cardId] + if type(reservedSlot) == "number" and reservedSlot < 6 then cardZone = "Blank" .. cardsWithAttachments[cardId] else cardZone = getDefaultCardZone(card.metadata, bondedList) @@ -347,25 +353,35 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo -- Check for existing cards in zones and maybe skip them removeBusyZones(playerColor, zoneDecks) + -- Spawn attachment helpers if option is enabled + if GlobalApi.getOptionPanelState()["showAttachmentHelper"] then + local objs = getObjectsWithTag("AttachmentHelperBag") + if #objs > 0 then + local helperRot = zones.getDefaultCardRotation(playerColor, "Blank1") + for cardId, reservedSlot in pairs(cardsWithAttachments) do + if type(reservedSlot) == "number" and reservedSlot < 6 then + local helper = objs[1].takeObject({ + position = zones.getZonePosition(playerColor, "Blank" .. (reservedSlot + 1)):setAt("y", 1.8), + rotation = helperRot, + smooth = false + }) + coWaitFrames(3) + helper.call("loadDataFromMetadata", { md = { id = cardId } }) + coWaitFrames(3) + end + end + end + end + -- Spawn the list for each zone for zone, zoneCards in pairs(zoneDecks) do local deckPos = zones.getZonePosition(playerColor, zone):setAt("y", 3) local deckRot = zones.getDefaultCardRotation(playerColor, zone) local callback = nil - -- spawn attachment helpers if option is enabled - if GlobalApi.getOptionPanelState()["showAttachmentHelper"] then - local objs = getObjectsWithTag("AttachmentHelperBag") - if #objs > 0 then - for cardId, reservedSlot in pairs(cardsWithAttachments) do - if reservedSlot < 7 then - objs[1].takeObject({ - position = zones.getZonePosition(playerColor, "Blank" .. reservedSlot):setAt("y", 1.6), - rotation = deckRot - }) - end - end - end + -- if this is a zone with attachments, spawn cards facedown and add additional delay + if zoneWithAttachments[zone] then + deckRot = deckRot:setAt("z", 180) end -- If cards are spread too close together TTS groups them weirdly, selecting multiples @@ -388,7 +404,7 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo callback = function(card) loadAltArt(card, loadAltInvestigator) end end Spawner.spawnCards(zoneCards, deckPos, deckRot, true, callback) - coroutine.yield(0) + coWaitFrames(3) end -- Look for any cards which haven't been loaded @@ -529,6 +545,7 @@ function removeBusyZones(playerColor, zoneDecks) zoneDecks["Blank" .. i] = nil end zoneDecks["UnderSetAside3"] = nil + zoneDecks["UnderSetAside6"] = nil zoneDecks["Deck"] = nil printToAll("Skipped deck import", playerColor) end @@ -548,7 +565,6 @@ function handleAncestralKnowledge(cardsToSpawn) for i = 1, 5 do local skillListIndex = math.random(#skillList) cardsToSpawn[skillList[skillListIndex]].zone = "UnderSetAside3" - table.remove(skillList, skillListIndex) end end @@ -556,10 +572,10 @@ end function handleAllAttachments(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) for cardId, reservedSlot in pairs(cardsWithAttachments) do if slotsCopy[cardId] and slotsCopy[cardId] > 0 then - if customizations["attachments_" .. cardId] and reservedSlot < 6 then + if customizations["attachments_" .. cardId] and type(reservedSlot) == "number" and reservedSlot < 6 then handleAttachment(cardId, cardsToSpawn, customizations) elseif cardId == "09077" then - handleUnderworldMarket(cardsToSpawn, slotsCopy, playerColor) + handleUnderworldMarket(cardsToSpawn, customizations, playerColor) elseif cardId == "05002" then handleHunchDeck(cardsToSpawn, bondedList, playerColor) elseif cardId == "07303" then @@ -580,7 +596,7 @@ function handleAttachment(parentId, cardsToSpawn, customizations) local zone = "Blank" .. (cardsWithAttachments[parentId] + 1) for i = #attachmentList, 1, -1 do for j = #cardsToSpawn, 1, -1 do - if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone ~= zone then + if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone == "Deck" then cardsToSpawn[j].zone = zone break end @@ -588,38 +604,51 @@ function handleAttachment(parentId, cardsToSpawn, customizations) end end --- Check for and handle Underworld Market by moving all Illicit cards to UnderSetAside3 ----@param cardList table Deck list being created +-- Check for and handle Underworld Market by moving all Illicit cards to UnderSetAside6 +---@param cardsToSpawn table Deck list being created ---@param playerColor string Color this deck is being loaded for -function handleUnderworldMarket(cardList, slotsCopy, playerColor) - if not slotsCopy["09077"] or slotsCopy["09077"] == 0 then return end - - local illicitList = {} - - -- get all possible Illicit cards - for i, card in ipairs(cardList) do - if card.metadata.traits ~= nil and string.find(card.metadata.traits, "Illicit", 1, true) and card.zone == "Deck" then - table.insert(illicitList, i) +function handleUnderworldMarket(cardsToSpawn, customizations, playerColor) + local attachmentList = {} + if customizations["attachments_09077"] then + -- get list of cards that are attached (split by ",") + for str in string.gmatch(customizations["attachments_09077"], "([^,]+)") do + table.insert(attachmentList, str) end end - if #illicitList < 10 then - printToAll("Only " .. #illicitList .. " Illicit cards in your deck, you can't trigger Underworld Market's ability.", playerColor) + if #attachmentList == 10 then + -- handling for 10 cards selected in deck data + for i = #attachmentList, 1, -1 do + for j = #cardsToSpawn, 1, -1 do + if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone == "Deck" then + cardsToSpawn[j].zone = "UnderSetAside6" + break + end + end + end else - -- Process cards to move them to the market deck. This is done in reverse order because the sorting needs - -- to be reversed (deck sorts for face down). Performance here may be an issue, as table.remove() is an O(n) - -- operation which makes the full shift O(n^2). But keep it simple unless it becomes a problem - for i = #illicitList, 1, -1 do - local moving = cardList[illicitList[i]] - moving.zone = "UnderSetAside3" - table.remove(cardList, illicitList[i]) - table.insert(cardList, moving) + -- regular handling + local illicitList = {} + + -- get all possible Illicit cards + for i, card in ipairs(cardsToSpawn) do + if card.metadata.traits ~= nil and string.find(card.metadata.traits, "Illicit", 1, true) and card.zone == "Deck" then + table.insert(illicitList, i) + end end - if #illicitList > 10 then - printToAll("Moved all " .. #illicitList .. " Illicit cards to the Market deck, reduce it to 10", playerColor) + if #illicitList < 10 then + printToAll("Only " .. #illicitList .. " Illicit cards in your deck, you can't trigger Underworld Market's ability.", playerColor) else - printToAll("Built the Market deck", playerColor) + for i = #illicitList, 1, -1 do + cardsToSpawn[illicitList[i]].zone = "UnderSetAside6" + end + + if #illicitList > 10 then + printToAll("Moved all " .. #illicitList .. " Illicit cards to the Market deck, reduce it to 10", playerColor) + else + printToAll("Built the Market deck", playerColor) + end end end end @@ -630,10 +659,10 @@ end function handleHunchDeck(cardList, bondedList, playerColor) local insightList = {} for i, card in ipairs(cardList) do - if (card.metadata.type == "Event" + if card.metadata.type == "Event" and card.metadata.traits ~= nil and string.match(card.metadata.traits, "Insight") - and bondedList[card.metadata.id] == nil) then + and bondedList[card.metadata.id] == nil then table.insert(insightList, i) end end @@ -696,11 +725,9 @@ function handleSpiritDeck(cardList, playerColor, customizations) end if #spiritList < 10 then - printToAll("Jim's spirit deck must have 9 Ally assets but the deck only has " .. - (#spiritList - 1) .. " Ally assets.", playerColor) + printToAll("Jim's spirit deck must have 9 Ally assets but the deck only has " .. (#spiritList - 1) .. " Ally assets.", playerColor) elseif #spiritList > 11 then - printToAll("Moved all " .. (#spiritList - 1) .. - " Ally assets to the spirit deck, reduce it to 10 (including Vengeful Shade).", playerColor) + printToAll("Moved all " .. (#spiritList - 1) .. " Ally assets to the spirit deck, reduce it to 10 (including Vengeful Shade).", playerColor) else printToAll("Built Jim's spirit deck", playerColor) end @@ -835,3 +862,10 @@ function deepCopy(data) end return copiedList end + +-- pauses the current coroutine for 'frameCount' frames +function coWaitFrames(frameCount) + for k = 1, frameCount do + coroutine.yield(0) + end +end diff --git a/src/playermat/Zones.ttslua b/src/playermat/Zones.ttslua index 27d5aac6..7d689136 100644 --- a/src/playermat/Zones.ttslua +++ b/src/playermat/Zones.ttslua @@ -15,108 +15,70 @@ -- SetAside[4-6]: Column farther away from the mat, with 4 at the top and 6 at the bottom. -- SetAside1: Permanent cards -- SetAside2: Bonded cards --- SetAside3: Ancestral Knowledge / Underworld Market +-- SetAside3: Ancestral Knowledge -- SetAside4: Upgrade sheets for customizable cards --- SetAside5: Hunch Deck for Joe Diamond --- SetAside6: currently unused +-- SetAside5: Hunch Deck for Joe Diamond // Summoned Servitor Minicard +-- SetAside6: Underworld Market -- AboveSetAside: Investigator specific object -- BelowSetAside: Investigator specific object do - local playermatApi = require("playermat/PlayermatApi") - local Zones = { } + local playermatApi = require("playermat/PlayermatApi") + local Zones = {} - local commonZones = {} - commonZones["Investigator"] = { -1.177, 0, 0.002 } - commonZones["Deck"] = { -1.82, 0, 0 } - commonZones["Discard"] = { -1.82, 0, 0.61 } - commonZones["Ally"] = { -0.615, 0, 0.024 } - commonZones["Body"] = { -0.630, 0, 0.553 } - commonZones["Hand1"] = { 0.215, 0, 0.042 } - commonZones["Hand2"] = { -0.180, 0, 0.037 } - commonZones["Arcane1"] = { 0.212, 0, 0.559 } - commonZones["Arcane2"] = { -0.171, 0, 0.557 } - commonZones["Tarot"] = { 0.602, 0, 0.033 } - commonZones["Accessory"] = { 0.602, 0, 0.555 } - commonZones["Blank1"] = { 1.758, 0, 0.040 } - commonZones["Blank2"] = { 1.754, 0, 0.563 } - commonZones["Blank3"] = { 1.371, 0, 0.038 } - commonZones["Blank4"] = { 1.371, 0, 0.558 } - commonZones["Blank5"] = { 0.98, 0, 0.035 } - commonZones["Blank6"] = { 0.977, 0, 0.556 } - commonZones["Threat1"] = { -0.911, 0, -0.625 } - commonZones["Threat2"] = { -0.454, 0, -0.625 } - commonZones["Threat3"] = { 0.002, 0, -0.625 } - commonZones["Threat4"] = { 0.459, 0, -0.625 } + -- local coordinates for each playermat + local commonZones = {} + commonZones["Investigator"] = Vector(-1.177, 0, 0.002) + commonZones["Deck"] = Vector(-1.82, 0, 0) + commonZones["Discard"] = Vector(-1.82, 0, 0.61) + commonZones["Ally"] = Vector(-0.616, 0, 0.024) + commonZones["Body"] = Vector(-0.631, 0, 0.551) + commonZones["Hand1"] = Vector(0.217, 0, 0.035) + commonZones["Hand2"] = Vector(-0.177, 0, 0.032) + commonZones["Arcane1"] = Vector(0.212, 0, 0.559) + commonZones["Arcane2"] = Vector(-0.174, 0, 0.551) + commonZones["Tarot"] = Vector(0.602, 0, 0.033) + commonZones["Accessory"] = Vector(0.605, 0, 0.555) + commonZones["Blank1"] = Vector(1.758, 0, 0.040) + commonZones["Blank2"] = Vector(1.754, 0, 0.563) + commonZones["Blank3"] = Vector(1.371, 0, 0.038) + commonZones["Blank4"] = Vector(1.371, 0, 0.558) + commonZones["Blank5"] = Vector(0.98, 0, 0.035) + commonZones["Blank6"] = Vector(0.977, 0, 0.556) + commonZones["Threat1"] = Vector(-0.911, 0, -0.625) + commonZones["Threat2"] = Vector(-0.454, 0, -0.625) + commonZones["Threat3"] = Vector(0.002, 0, -0.625) + commonZones["Threat4"] = Vector(0.459, 0, -0.625) - local zoneData = {} - zoneData["White"] = {} - zoneData["White"]["Investigator"] = commonZones["Investigator"] - zoneData["White"]["Deck"] = commonZones["Deck"] - zoneData["White"]["Discard"] = commonZones["Discard"] - zoneData["White"]["Ally"] = commonZones["Ally"] - zoneData["White"]["Body"] = commonZones["Body"] - zoneData["White"]["Hand1"] = commonZones["Hand1"] - zoneData["White"]["Hand2"] = commonZones["Hand2"] - zoneData["White"]["Arcane1"] = commonZones["Arcane1"] - zoneData["White"]["Arcane2"] = commonZones["Arcane2"] - zoneData["White"]["Tarot"] = commonZones["Tarot"] - zoneData["White"]["Accessory"] = commonZones["Accessory"] - zoneData["White"]["Blank1"] = commonZones["Blank1"] - zoneData["White"]["Blank2"] = commonZones["Blank2"] - zoneData["White"]["Blank3"] = commonZones["Blank3"] - zoneData["White"]["Blank4"] = commonZones["Blank4"] - zoneData["White"]["Blank5"] = commonZones["Blank5"] - zoneData["White"]["Blank6"] = commonZones["Blank6"] - zoneData["White"]["Threat1"] = commonZones["Threat1"] - zoneData["White"]["Threat2"] = commonZones["Threat2"] - zoneData["White"]["Threat3"] = commonZones["Threat3"] - zoneData["White"]["Threat4"] = commonZones["Threat4"] - zoneData["White"]["Minicard"] = { -1, 0, -1.45 } - zoneData["White"]["SetAside1"] = { 2.35, 0, -0.520 } - zoneData["White"]["SetAside2"] = { 2.35, 0, 0.042 } - zoneData["White"]["SetAside3"] = { 2.35, 0, 0.605 } - zoneData["White"]["UnderSetAside3"] = { 2.50, 0, 0.805 } - zoneData["White"]["SetAside4"] = { 2.78, 0, -0.520 } - 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 } + -- local coordinates for white / green (inverted X for orange / red) + local mirrorZones = {} + mirrorZones["Minicard"] = Vector(-1, 0, -1.45) + mirrorZones["SetAside1"] = Vector(2.35, 0, -0.520) + mirrorZones["SetAside2"] = Vector(2.35, 0, 0.042) + mirrorZones["SetAside3"] = Vector(2.35, 0, 0.605) + mirrorZones["UnderSetAside3"] = Vector(2.50, 0, 0.805) + mirrorZones["SetAside4"] = Vector(2.78, 0, -0.520) + mirrorZones["SetAside5"] = Vector(2.78, 0, 0.042) + mirrorZones["SetAside6"] = Vector(2.78, 0, 0.605) + mirrorZones["UnderSetAside6"] = Vector(2.93, 0, 0.805) + mirrorZones["AboveSetAside"] = Vector(2.35, 0, -1.069) + mirrorZones["BelowSetAside"] = Vector(2.85, 0, 1.650) - zoneData["Orange"] = {} - zoneData["Orange"]["Investigator"] = commonZones["Investigator"] - zoneData["Orange"]["Deck"] = commonZones["Deck"] - zoneData["Orange"]["Discard"] = commonZones["Discard"] - zoneData["Orange"]["Ally"] = commonZones["Ally"] - zoneData["Orange"]["Body"] = commonZones["Body"] - zoneData["Orange"]["Hand1"] = commonZones["Hand1"] - zoneData["Orange"]["Hand2"] = commonZones["Hand2"] - zoneData["Orange"]["Arcane1"] = commonZones["Arcane1"] - zoneData["Orange"]["Arcane2"] = commonZones["Arcane2"] - zoneData["Orange"]["Tarot"] = commonZones["Tarot"] - zoneData["Orange"]["Accessory"] = commonZones["Accessory"] - zoneData["Orange"]["Blank1"] = commonZones["Blank1"] - zoneData["Orange"]["Blank2"] = commonZones["Blank2"] - zoneData["Orange"]["Blank3"] = commonZones["Blank3"] - zoneData["Orange"]["Blank4"] = commonZones["Blank4"] - zoneData["Orange"]["Blank5"] = commonZones["Blank5"] - zoneData["Orange"]["Blank6"] = commonZones["Blank6"] - zoneData["Orange"]["Threat1"] = commonZones["Threat1"] - zoneData["Orange"]["Threat2"] = commonZones["Threat2"] - zoneData["Orange"]["Threat3"] = commonZones["Threat3"] - zoneData["Orange"]["Threat4"] = commonZones["Threat4"] - zoneData["Orange"]["Minicard"] = { 1, 0, -1.45 } - zoneData["Orange"]["SetAside1"] = { -2.35, 0, -0.520 } - zoneData["Orange"]["SetAside2"] = { -2.35, 0, 0.042} - zoneData["Orange"]["SetAside3"] = { -2.35, 0, 0.605 } - zoneData["Orange"]["UnderSetAside3"] = { -2.50, 0, 0.805 } - zoneData["Orange"]["SetAside4"] = { -2.78, 0, -0.520 } - 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 } + local zoneData = {} + zoneData["White"] = {} + zoneData["Orange"] = {} + + -- copy common coordinates + for zoneName, zonePos in pairs(commonZones) do + zoneData["White"][zoneName] = zonePos + zoneData["Orange"][zoneName] = zonePos + end + + -- copy mirrored coordinates + for zoneName, zonePos in pairs(mirrorZones) do + zoneData["White"][zoneName] = zonePos + zoneData["Orange"][zoneName] = zonePos * Vector(-1, 1, 1) + end -- Green positions are the same as White and Red the same as Orange zoneData["Red"] = zoneData["Orange"] @@ -127,13 +89,10 @@ do ---@param zoneName string Name of the zone to get the position for. See Zones object documentation for a list of valid zones. ---@return tts__Vector|nil: Global position table, or nil if an invalid player color or zone is specified Zones.getZonePosition = function(playerColor, zoneName) - if (playerColor ~= "Red" - and playerColor ~= "Orange" - and playerColor ~= "White" - and playerColor ~= "Green") then - return nil + if zoneData[playerColor] then + return playermatApi.transformLocalPosition(zoneData[playerColor][zoneName], playerColor) end - return playermatApi.transformLocalPosition(zoneData[playerColor][zoneName], playerColor) + return nil end -- Return the global rotation for a card on the given player mat, based on its zone. @@ -144,8 +103,8 @@ do -- Z rotation to place the card face up or face down. Zones.getDefaultCardRotation = function(playerColor, zoneName) local cardRotation = playermatApi.returnRotation(playerColor) - if zoneName == "Deck" then - cardRotation = cardRotation + Vector(0, 0, 180) + if zoneName == "Deck" or zoneName == "UnderSetAside3" or zoneName == "UnderSetAside6" then + cardRotation = cardRotation:setAt("z", 180) end return cardRotation end From d73ffdaa9607ff47f9e4fe345644035dbf398860 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Wed, 30 Oct 2024 15:14:05 +0100 Subject: [PATCH 4/7] removed additional space --- src/arkhamdb/DeckInstructionGenerator.ttslua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arkhamdb/DeckInstructionGenerator.ttslua b/src/arkhamdb/DeckInstructionGenerator.ttslua index 34dd09d0..2f6183b6 100644 --- a/src/arkhamdb/DeckInstructionGenerator.ttslua +++ b/src/arkhamdb/DeckInstructionGenerator.ttslua @@ -67,7 +67,7 @@ function generate(_, playerColor) else local cardString = #idList .. " cards" if #idList == 1 then - cardString = " 1 card" + cardString = "1 card" end broadcastToColor("Parsed " .. cardString .. ": Go to Notebook > Deck Instructions", playerColor, "Green") end From 0daf24192a940532bf3717902ee5113aa31879e0 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 31 Oct 2024 13:09:53 +0100 Subject: [PATCH 5/7] updated code --- src/arkhamdb/DeckImporter.ttslua | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 08f79b37..4ac19fb7 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -205,7 +205,7 @@ function loadDecksCoroutine() local deckId = _G[string.lower(matColor) .. "DeckId"] if deckId ~= nil and deckId ~= "" then buildDeck(matColor, deckId) - coWaitFrames(3) + coroutine.yield() end end end @@ -289,13 +289,13 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo local slotsCopy = deepCopy(slots) local cardsToSpawn = {} local resourceModifier = 0 + local zoneWithAttachments = {} cardsWithAttachments = { ["03264"] = true, ["07303"] = true, ["09077"] = true, ["10079"] = true } - local zoneWithAttachments = {} -- reset the startsInPlayCount startsInPlayCount = 0 @@ -421,7 +421,6 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo resumeLoadDecks() return 1 end - startLuaCoroutine(self, "coinside") end @@ -450,8 +449,7 @@ end -- Converts the Raven Quill's selections from card IDs to card names. This could be more elegant -- but the inputs are very static so we're using some brute force. ----@param selectionString string provided by ArkhamDB, indicates the customization selections --- Should be either a single card ID or two separated by a ^ (e.g. XXXXX^YYYYY) +---@param selectionString string customization selections (either a single card ID or two separated by a ^: XXXXX^YYYYY) function convertRavenQuillSelections(selectionString) if string.len(selectionString) == 5 then return getCardName(selectionString) @@ -461,8 +459,7 @@ function convertRavenQuillSelections(selectionString) end -- Converts Grizzled's selections from a single string with "^". ----@param selectionString string provided by ArkhamDB, indicates the customization selections --- Should be two traits separated by a ^ (e.g. XXXXX^YYYYY) +---@param selectionString string customization selections (two traits separated by a ^: XXXXX^YYYYY) function convertGrizzledSelections(selectionString) return selectionString:gsub("%^", ", ") end @@ -866,6 +863,6 @@ end -- pauses the current coroutine for 'frameCount' frames function coWaitFrames(frameCount) for k = 1, frameCount do - coroutine.yield(0) + coroutine.yield() end end From 5ddce528904f1939be0138f2cde4b152c3b815b1 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 31 Oct 2024 22:24:27 +0100 Subject: [PATCH 6/7] updated code --- src/arkhamdb/DeckImporter.ttslua | 214 +++++++++++++++---------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 4ac19fb7..768dd8b4 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -291,25 +291,28 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo local resourceModifier = 0 local zoneWithAttachments = {} cardsWithAttachments = { - ["03264"] = true, - ["07303"] = true, - ["09077"] = true, - ["10079"] = true + ["03264"] = "reserve", -- Stick to the Plan + ["05002"] = true, -- Joe Diamond + ["07303"] = "reserve", -- Ancestral Knowledge + ["09077"] = true, -- Underworld Market + ["10079"] = "reserve" -- Bewitching } -- reset the startsInPlayCount startsInPlayCount = 0 - -- reserve slots for cards with attachments (not for Underworld Market) - for cardId, _ in pairs(cardsWithAttachments) do - if cardId ~= "09077" and slotsCopy[cardId] and slotsCopy[cardId] > 0 then - -- increase startsInPlayCount by 1 and reserve slot for this card - startsInPlayCount = startsInPlayCount + 1 - cardsWithAttachments[cardId] = startsInPlayCount + -- reserve slots for certain cards with attachments + for cardId, state in pairs(cardsWithAttachments) do + if state == "reserve" then + if slotsCopy[cardId] and slotsCopy[cardId] > 0 then + -- increase startsInPlayCount by 1 and reserve slot for this card + startsInPlayCount = startsInPlayCount + 1 + cardsWithAttachments[cardId] = startsInPlayCount - -- reserve an additional slot for the attachments - startsInPlayCount = startsInPlayCount + 1 - zoneWithAttachments["Blank" .. startsInPlayCount] = true + -- reserve an additional slot for the attachments + startsInPlayCount = startsInPlayCount + 1 + zoneWithAttachments["Blank" .. startsInPlayCount] = true + end end end @@ -548,23 +551,6 @@ function removeBusyZones(playerColor, zoneDecks) end end -function handleAncestralKnowledge(cardsToSpawn) - local skillList = {} - - -- process cardlist for skills - for i, card in ipairs(cardsToSpawn) do - if card.metadata.type == "Skill" and card.zone == "Deck" and not card.metadata.weakness then - table.insert(skillList, i) - end - end - - -- move 5 random skills to SetAside3 - for i = 1, 5 do - local skillListIndex = math.random(#skillList) - cardsToSpawn[skillList[skillListIndex]].zone = "UnderSetAside3" - end -end - -- handle cards with attachments (Stick to the Plan, Underworld Market and Bewitching) function handleAllAttachments(cardsToSpawn, slotsCopy, bondedList, customizations, playerColor) for cardId, reservedSlot in pairs(cardsWithAttachments) do @@ -574,7 +560,7 @@ function handleAllAttachments(cardsToSpawn, slotsCopy, bondedList, customization elseif cardId == "09077" then handleUnderworldMarket(cardsToSpawn, customizations, playerColor) elseif cardId == "05002" then - handleHunchDeck(cardsToSpawn, bondedList, playerColor) + handleHunchDeck(cardsToSpawn, customizations, bondedList, playerColor) elseif cardId == "07303" then handleAncestralKnowledge(cardsToSpawn) end @@ -601,105 +587,84 @@ function handleAttachment(parentId, cardsToSpawn, customizations) end end --- Check for and handle Underworld Market by moving all Illicit cards to UnderSetAside6 ----@param cardsToSpawn table Deck list being created ----@param playerColor string Color this deck is being loaded for -function handleUnderworldMarket(cardsToSpawn, customizations, playerColor) - local attachmentList = {} - if customizations["attachments_09077"] then - -- get list of cards that are attached (split by ",") - for str in string.gmatch(customizations["attachments_09077"], "([^,]+)") do - table.insert(attachmentList, str) +function handleAncestralKnowledge(cardsToSpawn) + local skillList = {} + + -- process cardlist for skills + for i, card in ipairs(cardsToSpawn) do + if card.metadata.type == "Skill" and card.zone == "Deck" and not card.metadata.weakness then + table.insert(skillList, i) end end - if #attachmentList == 10 then - -- handling for 10 cards selected in deck data - for i = #attachmentList, 1, -1 do - for j = #cardsToSpawn, 1, -1 do - if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone == "Deck" then - cardsToSpawn[j].zone = "UnderSetAside6" - break - end - end - end - else - -- regular handling - local illicitList = {} - - -- get all possible Illicit cards - for i, card in ipairs(cardsToSpawn) do - if card.metadata.traits ~= nil and string.find(card.metadata.traits, "Illicit", 1, true) and card.zone == "Deck" then - table.insert(illicitList, i) - end - end - - if #illicitList < 10 then - printToAll("Only " .. #illicitList .. " Illicit cards in your deck, you can't trigger Underworld Market's ability.", playerColor) - else - for i = #illicitList, 1, -1 do - cardsToSpawn[illicitList[i]].zone = "UnderSetAside6" - end - - if #illicitList > 10 then - printToAll("Moved all " .. #illicitList .. " Illicit cards to the Market deck, reduce it to 10", playerColor) - else - printToAll("Built the Market deck", playerColor) - end - end + -- move 5 random skills to SetAside3 + for i = 1, 5 do + local skillListIndex = math.random(#skillList) + cardsToSpawn[skillList[skillListIndex]].zone = "UnderSetAside3" end end -- Extract all Insight events to SetAside5 to build the Hunch Deck for Joe Diamond ----@param cardList table Deck list being created +---@param cardsToSpawn table Deck list being created +---@param customizations table ArkhamDB data for customizations on customizable cards ---@param playerColor string Color this deck is being loaded for -function handleHunchDeck(cardList, bondedList, playerColor) - local insightList = {} - for i, card in ipairs(cardList) do - if card.metadata.type == "Event" - and card.metadata.traits ~= nil - and string.match(card.metadata.traits, "Insight") - and bondedList[card.metadata.id] == nil then - table.insert(insightList, i) +function handleHunchDeck(cardsToSpawn, customizations, bondedList, playerColor) + local attachmentList = {} + if customizations["attachments_05002"] then + -- get list of cards that are attached (split by ",") + for str in string.gmatch(customizations["attachments_05002"], "([^,]+)") do + table.insert(attachmentList, str) + end + else + -- regular handling, get all possible insight events + for i, card in ipairs(cardsToSpawn) do + if card.metadata.type == "Event" + and card.metadata.traits ~= nil + and string.match(card.metadata.traits, "Insight") + and bondedList[card.metadata.id] == nil then + table.insert(attachmentList, card.metadata.id) + end end end - -- Process cards to move them to the hunch deck. This is done in reverse order because the sorting needs - -- to be reversed (deck sorts for face down). Performance here may be an issue, as table.remove() is an O(n) - -- operation which makes the full shift O(n^2). But keep it simple unless it becomes a problem - for i = #insightList, 1, -1 do - local moving = cardList[insightList[i]] - moving.zone = "SetAside5" - table.remove(cardList, insightList[i]) - table.insert(cardList, moving) + local count = #attachmentList + + -- change zone for cards + for i = count, 1, -1 do + for j = #cardsToSpawn, 1, -1 do + if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone == "Deck" then + cardsToSpawn[j].zone = "SetAside5" + break + end + end end - if #insightList < 11 then - printToAll("Joe's hunch deck must have 11 cards but the deck only has " .. #insightList .. " Insight events.", playerColor) - elseif #insightList > 11 then - printToAll("Moved all " .. #insightList .. " Insight events to the hunch deck, reduce it to 11.", playerColor) + if count < 11 then + printToAll("Joe's hunch deck must have 11 cards but the deck only has " .. count .. " Insight events.", playerColor) + elseif count > 11 then + printToAll("Moved all " .. count .. " Insight events to the hunch deck, reduce it to 11.", playerColor) else printToAll("Built Joe's hunch deck", playerColor) end end -- Extract all Ally assets to SetAside5 to build the Spirit Deck for parallel Jim Culver ----@param cardList table Deck list being created +---@param cardsToSpawn table Deck list being created ---@param playerColor string Color this deck is being loaded for ----@param customizations table Additional deck information -function handleSpiritDeck(cardList, playerColor, customizations) +---@param customizations table ArkhamDB data for customizations on customizable cards +function handleSpiritDeck(cardsToSpawn, playerColor, customizations) local spiritList = {} if customizations["extra_deck"] then -- split by "," for str in string.gmatch(customizations["extra_deck"], "([^,]+)") do local card = allCardsBagApi.getCardById(str) if card ~= nil then - table.insert(cardList, { data = card.data, metadata = card.metadata, zone = "SetAside5" }) + table.insert(cardsToSpawn, { data = card.data, metadata = card.metadata, zone = "SetAside5" }) table.insert(spiritList, str) end end else - for i, card in ipairs(cardList) do + for i, card in ipairs(cardsToSpawn) do if card.metadata.id == "90053" or ( card.metadata.type == "Asset" and card.metadata.traits ~= nil @@ -710,14 +675,8 @@ function handleSpiritDeck(cardList, playerColor, customizations) end end - -- Process cards to move them to the spirit deck. This is done in reverse order because the sorting needs - -- to be reversed (deck sorts for face down). Performance here may be an issue, as table.remove() is an O(n) - -- operation which makes the full shift O(n^2). But keep it simple unless it becomes a problem for i = #spiritList, 1, -1 do - local moving = cardList[spiritList[i]] - moving.zone = "SetAside5" - table.remove(cardList, spiritList[i]) - table.insert(cardList, moving) + cardsToSpawn[spiritList[i]].zone = "SetAside5" end end @@ -730,6 +689,47 @@ function handleSpiritDeck(cardList, playerColor, customizations) end end +-- Check for and handle Underworld Market by moving all Illicit cards to UnderSetAside6 +---@param cardsToSpawn table Deck list being created +---@param customizations table ArkhamDB data for customizations on customizable cards +---@param playerColor string Color this deck is being loaded for +function handleUnderworldMarket(cardsToSpawn, customizations, playerColor) + local attachmentList = {} + if customizations["attachments_09077"] then + -- get list of cards that are attached (split by ",") + for str in string.gmatch(customizations["attachments_09077"], "([^,]+)") do + table.insert(attachmentList, str) + end + else + -- regular handling, get all possible Illicit cards + for i, card in ipairs(cardsToSpawn) do + if card.metadata.traits ~= nil and string.match(card.metadata.traits, "Illicit") and card.zone == "Deck" then + table.insert(attachmentList, card.metadata.id) + end + end + end + + local count = #attachmentList + + -- change zone for cards + for i = count, 1, -1 do + for j = #cardsToSpawn, 1, -1 do + if cardsToSpawn[j].metadata.id == attachmentList[i] and cardsToSpawn[j].zone == "Deck" then + cardsToSpawn[j].zone = "UnderSetAside6" + break + end + end + end + + if count < 10 then + printToAll("Only " .. count .. " Illicit cards in your deck, you can't trigger Underworld Market's ability.", playerColor) + elseif count > 10 then + printToAll("Moved all " .. count .. " Illicit cards to the Market deck, reduce it to 10", playerColor) + else + printToAll("Built the Market deck", playerColor) + end +end + -- For any customization upgrade cards in the card list, process the metadata from the deck to -- set the save state to show the correct checkboxes/text field values ---@param cardList table Deck list being created From 452fddf68bd3d605e7bafe846febb847697395af Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 31 Oct 2024 22:26:15 +0100 Subject: [PATCH 7/7] added taboo'd underworld market --- src/arkhamdb/DeckImporter.ttslua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 768dd8b4..cb4a4625 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -232,7 +232,7 @@ function getDefaultCardZone(cardMetadata, bondedList) elseif cardMetadata.id == "07303" then -- Ancestral Knowledge return "SetAside3" - elseif cardMetadata.id == "09077" then + elseif cardMetadata.id == "09077" or cardMetadata.id == "09077-t" then -- Underworld Market return "SetAside6" elseif cardMetadata.type == "Investigator" then @@ -295,6 +295,7 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo ["05002"] = true, -- Joe Diamond ["07303"] = "reserve", -- Ancestral Knowledge ["09077"] = true, -- Underworld Market + ["09077-t"] = true, -- Underworld Market (Taboo) ["10079"] = "reserve" -- Bewitching }