diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua index f60d8e94..3e80625a 100644 --- a/src/arkhamdb/ArkhamDb.ttslua +++ b/src/arkhamdb/ArkhamDb.ttslua @@ -4,7 +4,7 @@ do local ArkhamDb = {} local internal = {} - + local tabooList = {} local configuration @@ -69,6 +69,11 @@ do deckId } + -- use secondary api (arkham.build) if deckid is too long + if string.len(deckId) == 15 then + deckUri = { configuration.api_uri2, deckId } + end + local deck = Request.start(deckUri, function(status) if string.find(status.text, "") then internal.maybePrint("Private deck ID " .. deckId .. " is not shared.", playerColor) @@ -94,11 +99,11 @@ do ---@param cardId string ArkhamDB ID of the card that could not be found ---@param playerColor string Color of the player's deck that had the problem ArkhamDb.logCardNotFound = function(cardId, playerColor) - local request = Request.start({ - configuration.api_uri, - configuration.cards, - cardId - }, + Request.start({ + configuration.api_uri, + configuration.cards, + cardId + }, function(result) local adbCardInfo = JSON.decode(internal.fixUtf16String(result.text)) local cardName = adbCardInfo.real_name @@ -360,8 +365,8 @@ do ---@param slots table The slot list for cards in this deck. Table key is the cardId, value is the number of those cards which will be spawned internal.extractBondedCards = function(slots) -- Create a list of bonded cards first so we don't modify slots while iterating - local bondedCards = { } - local bondedList = { } + local bondedCards = {} + local bondedList = {} for cardId, cardCount in pairs(slots) do local card = allCardsBagApi.getCardById(cardId) if card ~= nil and card.metadata.bonded ~= nil then @@ -534,8 +539,7 @@ do if self.is_successful then callback(self.content, table.unpack(arguments)) end - end, function() return self.is_done - end) + end, function() return self.is_done end) end return ArkhamDb diff --git a/src/arkhamdb/Configuration.ttslua b/src/arkhamdb/Configuration.ttslua index 29ef3c36..bddf9eff 100644 --- a/src/arkhamdb/Configuration.ttslua +++ b/src/arkhamdb/Configuration.ttslua @@ -1,9 +1,9 @@ ---@type table Contains fields used by the deck importer configuration = { api_uri = "https://arkhamdb.com/api/public", + api_uri2 = "https://api.arkham.build/v1/public/share", public_deck = "decklist", private_deck = "deck", cards = "card", - taboo = "taboos", - card_bag_guid = "15bb07" + taboo = "taboos" } diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 563bd21e..9f78d30d 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -84,85 +84,85 @@ end function makeOptionToggles() -- common parameters - local checkbox_parameters = {} - checkbox_parameters.function_owner = self - checkbox_parameters.width = INPUT_FIELD_WIDTH - checkbox_parameters.height = INPUT_FIELD_HEIGHT - checkbox_parameters.scale = { 0.1, 0.1, 0.1 } - checkbox_parameters.font_size = 240 - checkbox_parameters.hover_color = { 0.4, 0.6, 0.8 } - checkbox_parameters.color = FIELD_COLOR + local checkboxParameters = {} + checkboxParameters.function_owner = self + checkboxParameters.width = INPUT_FIELD_WIDTH + checkboxParameters.height = INPUT_FIELD_HEIGHT + checkboxParameters.scale = { 0.1, 0.1, 0.1 } + checkboxParameters.font_size = 240 + checkboxParameters.hover_color = { 0.4, 0.6, 0.8 } + checkboxParameters.color = FIELD_COLOR -- public / private deck - checkbox_parameters.click_function = "publicPrivateChanged" - checkbox_parameters.position = { 0.25, 0.1, -0.102 } - checkbox_parameters.tooltip = "Published or private deck?\n\nPLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!" - checkbox_parameters.label = PRIVATE_TOGGLE_LABELS[privateDeck] - self.createButton(checkbox_parameters) + checkboxParameters.click_function = "publicPrivateChanged" + checkboxParameters.position = { 0.25, 0.1, -0.102 } + checkboxParameters.tooltip = "Published or private deck?\n\nPLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!" + checkboxParameters.label = PRIVATE_TOGGLE_LABELS[privateDeck] + self.createButton(checkboxParameters) -- load upgraded? - checkbox_parameters.click_function = "loadUpgradedChanged" - checkbox_parameters.position = { 0.25, 0.1, -0.01 } - checkbox_parameters.tooltip = "Load newest upgrade or exact deck?" - checkbox_parameters.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] - self.createButton(checkbox_parameters) + checkboxParameters.click_function = "loadUpgradedChanged" + checkboxParameters.position = { 0.25, 0.1, -0.01 } + checkboxParameters.tooltip = "Load newest upgrade or exact deck?" + checkboxParameters.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] + self.createButton(checkboxParameters) -- load investigators? - checkbox_parameters.click_function = "loadInvestigatorsChanged" - checkbox_parameters.position = { 0.25, 0.1, 0.081 } - checkbox_parameters.tooltip = "Spawn investigator cards?" - checkbox_parameters.label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] - self.createButton(checkbox_parameters) + checkboxParameters.click_function = "loadInvestigatorsChanged" + checkboxParameters.position = { 0.25, 0.1, 0.081 } + checkboxParameters.tooltip = "Spawn investigator cards?" + checkboxParameters.label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] + self.createButton(checkboxParameters) end -- Create the four deck ID entry fields function makeDeckIdFields() - local input_parameters = {} + local inputParameters = {} -- Parameters common to all entry fields - input_parameters.function_owner = self - input_parameters.scale = { 0.1, 0.1, 0.1 } - input_parameters.width = INPUT_FIELD_WIDTH - input_parameters.height = INPUT_FIELD_HEIGHT - input_parameters.font_size = 320 - input_parameters.tooltip = "Deck ID from ArkhamDB URL of the deck\nPublic URL: 'https://arkhamdb.com/decklist/view/101/knowledge-overwhelming-solo-deck-1.0' = '101'\nPrivate URL: 'https://arkhamdb.com/deck/view/102' = '102'" - input_parameters.alignment = 3 -- Center - input_parameters.color = FIELD_COLOR - input_parameters.font_color = { 0, 0, 0 } - input_parameters.validation = 2 -- Integer + inputParameters.function_owner = self + inputParameters.scale = { 0.1, 0.1, 0.1 } + inputParameters.width = INPUT_FIELD_WIDTH + inputParameters.height = INPUT_FIELD_HEIGHT + inputParameters.font_size = 320 + inputParameters.tooltip = "Deck ID from ArkhamDB URL of the deck\nPublic URL: 'https://arkhamdb.com/decklist/view/101/knowledge-overwhelming-solo-deck-1.0' = '101'\nPrivate URL: 'https://arkhamdb.com/deck/view/102' = '102'\n\nAlso supports the deck ID from shared decks from arkham.build!" + inputParameters.alignment = 3 -- Center + inputParameters.color = FIELD_COLOR + inputParameters.font_color = { 0, 0, 0 } + inputParameters.validation = 4 -- alphanumeric (to support arkham.build IDs) -- Green - input_parameters.input_function = "greenDeckChanged" - input_parameters.position = { -0.166, 0.1, 0.385 } - input_parameters.value = greenDeckId - self.createInput(input_parameters) + inputParameters.input_function = "greenDeckChanged" + inputParameters.position = { -0.166, 0.1, 0.385 } + inputParameters.value = greenDeckId + self.createInput(inputParameters) -- Red - input_parameters.input_function = "redDeckChanged" - input_parameters.position = { 0.171, 0.1, 0.385 } - input_parameters.value = redDeckId - self.createInput(input_parameters) + inputParameters.input_function = "redDeckChanged" + inputParameters.position = { 0.171, 0.1, 0.385 } + inputParameters.value = redDeckId + self.createInput(inputParameters) -- White - input_parameters.input_function = "whiteDeckChanged" - input_parameters.position = { -0.166, 0.1, 0.474 } - input_parameters.value = whiteDeckId - self.createInput(input_parameters) + inputParameters.input_function = "whiteDeckChanged" + inputParameters.position = { -0.166, 0.1, 0.474 } + inputParameters.value = whiteDeckId + self.createInput(inputParameters) -- Orange - input_parameters.input_function = "orangeDeckChanged" - input_parameters.position = { 0.171, 0.1, 0.474 } - input_parameters.value = orangeDeckId - self.createInput(input_parameters) + inputParameters.input_function = "orangeDeckChanged" + inputParameters.position = { 0.171, 0.1, 0.474 } + inputParameters.value = orangeDeckId + self.createInput(inputParameters) end -- Create the Build All button. This is a transparent button which covers the Build All portion of the background graphic function makeBuildButton() - local button_parameters = {} - button_parameters.click_function = "loadDecks" - button_parameters.function_owner = self - button_parameters.position = { 0, 0.1, 0.71 } - button_parameters.width = 320 - button_parameters.height = 30 - button_parameters.color = { 0, 0, 0, 0 } - button_parameters.tooltip = "Click to build all four decks!" - self.createButton(button_parameters) + local buttonParameters = {} + buttonParameters.click_function = "loadDecks" + buttonParameters.function_owner = self + buttonParameters.position = { 0, 0.1, 0.71 } + buttonParameters.width = 320 + buttonParameters.height = 30 + buttonParameters.color = { 0, 0, 0, 0 } + buttonParameters.tooltip = "Click to build all four decks!" + self.createButton(buttonParameters) end -- Event handlers for deck ID change @@ -174,17 +174,17 @@ function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end -- Event handlers for toggle buttons function publicPrivateChanged() privateDeck = not privateDeck - self.editButton { index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] } + self.editButton({ index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] }) end function loadUpgradedChanged() loadNewestDeck = not loadNewestDeck - self.editButton { index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] } + self.editButton({ index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] }) end function loadInvestigatorsChanged() loadInvestigators = not loadInvestigators - self.editButton { index = 2, label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] } + self.editButton({ index = 2, label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] }) end function loadDecks() @@ -243,12 +243,12 @@ end function buildDeck(playerColor, deckId) local uiState = getUiState() arkhamDb.getDecklist( - playerColor, - deckId, - uiState.privateDeck, - uiState.loadNewest, - uiState.investigators, - loadCards) + playerColor, + deckId, + uiState.privateDeck, + uiState.loadNewest, + uiState.investigators, + loadCards) end -- Process the slot list, which defines the card Ids and counts of cards to load. Spawn those cards @@ -360,9 +360,9 @@ function deckSpawned(deck, playerColor) -- Process in reverse order so taking cards out doesn't upset the indexing for i = #deckCards, 1, -1 do - local cardMetadata = JSON.decode(deckCards[i].GMNotes) or { } + local cardMetadata = JSON.decode(deckCards[i].GMNotes) or {} if cardMetadata.startsInHand then - deck.takeObject({ index = i - 1, position = handPos, flip = true, smooth = true}) + deck.takeObject({ index = i - 1, position = handPos, flip = true, smooth = true }) end end @@ -556,9 +556,10 @@ function handleHunchDeck(investigatorId, cardList, bondedList, playerColor) table.insert(insightList, i) 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 + + -- 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" @@ -663,7 +664,7 @@ function handleCustomizableUpgrades(cardList, customizations) row = tonumber(str) + 1 elseif counter == 2 then if selectedUpgrades[row] == nil then - selectedUpgrades[row] = { } + selectedUpgrades[row] = {} end selectedUpgrades[row].xp = tonumber(str) elseif counter == 3 and str ~= "" then @@ -674,7 +675,7 @@ function handleCustomizableUpgrades(cardList, customizations) elseif baseId == "09079" then -- Living Ink skill selection -- All skills, regardless of row, are placed in upgrade slot 1 as a comma-delimited list if selectedUpgrades[1] == nil then - selectedUpgrades[1] = { } + selectedUpgrades[1] = {} end if selectedUpgrades[1].text == nil then selectedUpgrades[1].text = str diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua index 89cb3819..7b99970e 100644 --- a/src/core/GameKeyHandler.ttslua +++ b/src/core/GameKeyHandler.ttslua @@ -103,9 +103,11 @@ function takeCardIntoThreatArea(playerColor, hoveredObject) modifierY = -90 end + -- contruct feedback message local cardName = hoveredObject.getName() - if cardName == "" then cardName = "card" end - broadcastToAll("Moved " .. cardName .. " to " .. getColoredName(playerColor) .. "'s threat area.", "White") + if cardName == "" then cardName = "a card" end + local playerName = getColoredName(playerColor) + broadcastToAll("Moved " .. cardName .. " to " .. playerName .. "'s threat area.", "White") -- get new rotation (rounded) local cardRot = hoveredObject.getRotation() @@ -266,27 +268,66 @@ function removeOneUse(playerColor, hoveredObject) if hoveredObject.type == "Tile" then targetObject = hoveredObject elseif hoveredObject.type == "Card" then - -- grab the first use type from the metadata (or nil) - local notes = JSON.decode(hoveredObject.getGMNotes()) or {} - local usesData = notes.uses or {} - local useInfo = usesData[1] or {} - local searchForType = useInfo.type - if searchForType then searchForType = searchForType:lower() end + local searchResult = searchLib.onObject(hoveredObject, "isTileOrToken") - for _, obj in ipairs(searchLib.onObject(hoveredObject, "isTileOrToken")) do - if not obj.locked and obj.memo ~= "resourceCounter" then - -- check for matching object, otherwise use the first hit - if obj.memo and obj.memo == searchForType then - targetObject = obj - break - elseif not targetObject then - targetObject = obj + if #searchResult == 0 then + broadcastToColor("No tokens found!", playerColor, "Yellow") + return + end + + -- index the found tokens by memo (only the first of each type) + local indexByMemo = {} + for _, obj in ipairs(searchResult) do + if not obj.locked then + if obj.memo and indexByMemo[obj.memo] == nil then + indexByMemo[obj.memo] = obj + elseif indexByMemo["NO_MEMO"] == nil then + indexByMemo["NO_MEMO"] = obj end end end + + -- use metadata (if present) to determine targetObject + local usesAreTypeOfResource = false + local notes = JSON.decode(hoveredObject.getGMNotes()) or {} + for _, useInfo in ipairs(notes.uses or {}) do + if useInfo.type then + local discardMemo = useInfo.type:lower() + if indexByMemo[discardMemo] then + targetObject = indexByMemo[discardMemo] + break + end + end + if useInfo.token == "resource" then + usesAreTypeOfResource = true + end + end + + -- check for alternatives (check resources first if tokens are a type of resource) + if not targetObject then + if usesAreTypeOfResource and indexByMemo["resource"] then + targetObject = indexByMemo["resource"] + else + for memo, obj in pairs(indexByMemo) do + if memo ~= "resourceCounter" and memo ~= "NO_MEMO" then + targetObject = obj + break + end + end + end + end + + -- if there's still not a target check for clickable counter and token without memo + if not targetObject then + if indexByMemo["resourceCounter"] then + indexByMemo["resourceCounter"].call("modifyValue", -1) + return + elseif indexByMemo["NO_MEMO"] then + targetObject = indexByMemo["NO_MEMO"] + end + end end - -- error handling if not targetObject then broadcastToColor("No tokens found!", playerColor, "Yellow") return @@ -307,6 +348,16 @@ function removeOneUse(playerColor, hoveredObject) end -- feedback message + local cardName + if hoveredObject.type == "Card" then + cardName = hoveredObject.getName() + else + local searchResult = searchLib.belowPosition(targetObject.getPosition(), "isCard") + if #searchResult > 0 then + cardName = searchResult[1].getName() + end + end + local tokenName = targetObject.getName() if tokenName == "" then if targetObject.memo ~= "" then @@ -317,15 +368,23 @@ function removeOneUse(playerColor, hoveredObject) else tokenName = "Clue" end + elseif targetObject.memo == "resourceCounter" then + tokenName = "Resource Counter" else tokenName = titleCase(targetObject.memo) end else - tokenName = "Unknown" + tokenName = "unknown token" end end - broadcastToAll(getColoredName(playerColor) .. " removed a token: " .. tokenName, playerColor) + -- construct feedback message + local playerName = getColoredName(playerColor) + local cardInfo = "" + if cardName and cardName ~= "" then + cardInfo = " from " .. cardName + end + broadcastToAll(playerName .. " removed a token (" .. tokenName .. ")".. cardInfo .. ".", "White") local discardForMatColor = getColorToDiscardFor(hoveredObject, playerColor) playermatApi.discardListOfObjects(discardForMatColor, { targetObject }) @@ -437,7 +496,7 @@ function takeClueFromLocation(playerColor, hoveredObject) end elseif hoveredObject.type == "Infinite" and hoveredObject.getName() == "Clue tokens" then clue = hoveredObject.takeObject() - cardName = "token pool" + cardName = "the token pool" else broadcastToColor("Hover a clue or card with clues and try again.", messageColor, "Yellow") return @@ -470,11 +529,13 @@ function takeClueFromLocation(playerColor, hoveredObject) clue.setRotation(rot) end - if cardName then - broadcastToAll(getColoredName(playerColor) .. " took one clue from " .. cardName .. ".", "White") - else - broadcastToAll(getColoredName(playerColor) .. " took one clue.", "White") + -- construct feedback message + local playerName = getColoredName(playerColor) + local cardInfo = "" + if cardName and cardName ~= "" then + cardInfo = " from " .. cardName end + broadcastToAll(playerName .. " took one clue" .. cardInfo .. ".", "White") victoryDisplayApi.update() end diff --git a/src/core/GenericCounter.ttslua b/src/core/GenericCounter.ttslua index 8f64d949..2751abc3 100644 --- a/src/core/GenericCounter.ttslua +++ b/src/core/GenericCounter.ttslua @@ -49,6 +49,10 @@ function updateVal(newVal) end function addOrSubtract(_, _, isRightClick) - val = math.min(math.max(val + (isRightClick and -1 or 1), MIN_VALUE), MAX_VALUE) + modifyValue(isRightClick and -1 or 1) +end + +function modifyValue(mod) + val = math.min(math.max(val + tonumber(mod), MIN_VALUE), MAX_VALUE) self.editButton({ index = 0, label = tostring(val) }) end diff --git a/src/util/TokenSpawnTool.ttslua b/src/util/TokenSpawnTool.ttslua index 479f9326..cc91d966 100644 --- a/src/util/TokenSpawnTool.ttslua +++ b/src/util/TokenSpawnTool.ttslua @@ -114,7 +114,7 @@ function addUseToCard(card, useType) end local match = false - for _, useInfo in ipairs(metadata.uses) do + for _, useInfo in ipairs(metadata.uses or {}) do if useInfo.token == useType then -- artificially create replenish data to re-use that existing functionality useInfo.count = 999