From 7853b090f241208e80c202469c9cfed347234d10 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Wed, 24 Jul 2024 22:52:36 +0200 Subject: [PATCH 1/6] added data for class and mode restriction --- objects/AllPlayerCards.15bb07/DarkPact.dd3d09.gmnotes | 1 + objects/AllPlayerCards.15bb07/Doomed.ba2ae1.gmnotes | 1 + objects/AllPlayerCards.15bb07/EctoplasmicHorror.379582.gmnotes | 1 + objects/AllPlayerCards.15bb07/LurkerintheDark.79cc11.gmnotes | 1 + .../AllPlayerCards.15bb07/OfferYouCannotRefuse.e27c93.gmnotes | 1 + objects/AllPlayerCards.15bb07/PayYourDue.5b6c9f.gmnotes | 1 + objects/AllPlayerCards.15bb07/QuantumParadox.0e4c07.gmnotes | 1 + objects/AllPlayerCards.15bb07/Underprepared.709a54.gmnotes | 1 + .../UnspeakableOathBloodthirst.a5be8b.gmnotes | 1 + .../UnspeakableOathCowardice.ea0fa1.gmnotes | 1 + .../UnspeakableOathCuriosity.f6aba5.gmnotes | 1 + 11 files changed, 11 insertions(+) diff --git a/objects/AllPlayerCards.15bb07/DarkPact.dd3d09.gmnotes b/objects/AllPlayerCards.15bb07/DarkPact.dd3d09.gmnotes index 7d08212b..1f19aa9d 100644 --- a/objects/AllPlayerCards.15bb07/DarkPact.dd3d09.gmnotes +++ b/objects/AllPlayerCards.15bb07/DarkPact.dd3d09.gmnotes @@ -6,5 +6,6 @@ "traits": "Pact.", "weakness": true, "basicWeaknessCount": 1, + "modeRestriction": "Campaign", "cycle": "The Forgotten Age" } diff --git a/objects/AllPlayerCards.15bb07/Doomed.ba2ae1.gmnotes b/objects/AllPlayerCards.15bb07/Doomed.ba2ae1.gmnotes index a4221436..5c63a8db 100644 --- a/objects/AllPlayerCards.15bb07/Doomed.ba2ae1.gmnotes +++ b/objects/AllPlayerCards.15bb07/Doomed.ba2ae1.gmnotes @@ -5,5 +5,6 @@ "traits": "Curse.", "weakness": true, "basicWeaknessCount": 1, + "modeRestriction": "Campaign", "cycle": "The Forgotten Age" } diff --git a/objects/AllPlayerCards.15bb07/EctoplasmicHorror.379582.gmnotes b/objects/AllPlayerCards.15bb07/EctoplasmicHorror.379582.gmnotes index fa174d5a..e667f703 100644 --- a/objects/AllPlayerCards.15bb07/EctoplasmicHorror.379582.gmnotes +++ b/objects/AllPlayerCards.15bb07/EctoplasmicHorror.379582.gmnotes @@ -5,5 +5,6 @@ "traits": "Monster. Geist.", "weakness": true, "basicWeaknessCount": 1, + "classRestriction": "Mystic", "cycle": "The Scarlet Keys" } diff --git a/objects/AllPlayerCards.15bb07/LurkerintheDark.79cc11.gmnotes b/objects/AllPlayerCards.15bb07/LurkerintheDark.79cc11.gmnotes index dfb64053..70a22544 100644 --- a/objects/AllPlayerCards.15bb07/LurkerintheDark.79cc11.gmnotes +++ b/objects/AllPlayerCards.15bb07/LurkerintheDark.79cc11.gmnotes @@ -5,5 +5,6 @@ "traits": "Monster. Shoggoth.", "weakness": true, "basicWeaknessCount": 1, + "classRestriction": "Guardian", "cycle": "The Scarlet Keys" } diff --git a/objects/AllPlayerCards.15bb07/OfferYouCannotRefuse.e27c93.gmnotes b/objects/AllPlayerCards.15bb07/OfferYouCannotRefuse.e27c93.gmnotes index a39c3eba..c2e6663c 100644 --- a/objects/AllPlayerCards.15bb07/OfferYouCannotRefuse.e27c93.gmnotes +++ b/objects/AllPlayerCards.15bb07/OfferYouCannotRefuse.e27c93.gmnotes @@ -5,5 +5,6 @@ "traits": "Pact.", "weakness": true, "basicWeaknessCount": 1, + "modeRestriction": "Campaign", "cycle": "Return to the Forgotten Age" } diff --git a/objects/AllPlayerCards.15bb07/PayYourDue.5b6c9f.gmnotes b/objects/AllPlayerCards.15bb07/PayYourDue.5b6c9f.gmnotes index f4d24a5f..ed4b4e28 100644 --- a/objects/AllPlayerCards.15bb07/PayYourDue.5b6c9f.gmnotes +++ b/objects/AllPlayerCards.15bb07/PayYourDue.5b6c9f.gmnotes @@ -6,5 +6,6 @@ "traits": "Pact.", "weakness": true, "basicWeaknessCount": 1, + "classRestriction": "Rogue", "cycle": "The Scarlet Keys" } diff --git a/objects/AllPlayerCards.15bb07/QuantumParadox.0e4c07.gmnotes b/objects/AllPlayerCards.15bb07/QuantumParadox.0e4c07.gmnotes index 64c172dc..e920fc72 100644 --- a/objects/AllPlayerCards.15bb07/QuantumParadox.0e4c07.gmnotes +++ b/objects/AllPlayerCards.15bb07/QuantumParadox.0e4c07.gmnotes @@ -6,5 +6,6 @@ "traits": "Paradox.", "weakness": true, "basicWeaknessCount": 1, + "classRestriction": "Seeker", "cycle": "The Scarlet Keys" } diff --git a/objects/AllPlayerCards.15bb07/Underprepared.709a54.gmnotes b/objects/AllPlayerCards.15bb07/Underprepared.709a54.gmnotes index 1b71a2c2..a7630283 100644 --- a/objects/AllPlayerCards.15bb07/Underprepared.709a54.gmnotes +++ b/objects/AllPlayerCards.15bb07/Underprepared.709a54.gmnotes @@ -6,5 +6,6 @@ "traits": "Blunder.", "weakness": true, "basicWeaknessCount": 1, + "classRestriction": "Survivor", "cycle": "The Scarlet Keys" } diff --git a/objects/AllPlayerCards.15bb07/UnspeakableOathBloodthirst.a5be8b.gmnotes b/objects/AllPlayerCards.15bb07/UnspeakableOathBloodthirst.a5be8b.gmnotes index 2c31b542..d72dd55a 100644 --- a/objects/AllPlayerCards.15bb07/UnspeakableOathBloodthirst.a5be8b.gmnotes +++ b/objects/AllPlayerCards.15bb07/UnspeakableOathBloodthirst.a5be8b.gmnotes @@ -5,6 +5,7 @@ "traits": "Madness. Pact.", "weakness": true, "basicWeaknessCount": 1, + "modeRestriction": "Campaign", "hidden": true, "cycle": "Return to the Path to Carcosa" } diff --git a/objects/AllPlayerCards.15bb07/UnspeakableOathCowardice.ea0fa1.gmnotes b/objects/AllPlayerCards.15bb07/UnspeakableOathCowardice.ea0fa1.gmnotes index eaca90a9..e70ab205 100644 --- a/objects/AllPlayerCards.15bb07/UnspeakableOathCowardice.ea0fa1.gmnotes +++ b/objects/AllPlayerCards.15bb07/UnspeakableOathCowardice.ea0fa1.gmnotes @@ -5,6 +5,7 @@ "traits": "Madness. Pact.", "weakness": true, "basicWeaknessCount": 1, + "modeRestriction": "Campaign", "hidden": true, "cycle": "Return to the Path to Carcosa" } diff --git a/objects/AllPlayerCards.15bb07/UnspeakableOathCuriosity.f6aba5.gmnotes b/objects/AllPlayerCards.15bb07/UnspeakableOathCuriosity.f6aba5.gmnotes index dae10b77..1c9fab51 100644 --- a/objects/AllPlayerCards.15bb07/UnspeakableOathCuriosity.f6aba5.gmnotes +++ b/objects/AllPlayerCards.15bb07/UnspeakableOathCuriosity.f6aba5.gmnotes @@ -5,6 +5,7 @@ "traits": "Madness. Pact.", "weakness": true, "basicWeaknessCount": 1, + "modeRestriction": "Campaign", "hidden": true, "cycle": "Return to the Path to Carcosa" } From 8a8293d1a2dd625dde2f348c3449e017c6813698 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 1 Aug 2024 20:59:22 +0200 Subject: [PATCH 2/6] deck importer update --- config.json | 2 +- ...ArkhamDBDeckImporter.a28140.luascriptstate | 1 - ...r.a28140.json => DeckImporter.a28140.json} | 8 +- objects/DeckImporter.a28140.luascriptstate | 9 ++ src/arkhamdb/ArkhamDb.ttslua | 57 ++++---- src/arkhamdb/DeckImporter.ttslua | 128 +++++++++--------- src/playercards/AllCardsBag.ttslua | 54 ++++++-- src/playercards/AllCardsBagApi.ttslua | 17 ++- src/playercards/PlayerCardPanel.ttslua | 14 +- 9 files changed, 174 insertions(+), 116 deletions(-) delete mode 100644 objects/ArkhamDBDeckImporter.a28140.luascriptstate rename objects/{ArkhamDBDeckImporter.a28140.json => DeckImporter.a28140.json} (81%) create mode 100644 objects/DeckImporter.a28140.luascriptstate diff --git a/config.json b/config.json index 09d117fe..9569f41b 100644 --- a/config.json +++ b/config.json @@ -138,7 +138,7 @@ "Playermat3Green.383d8b", "Playermat4Red.0840d5", "LeadInvestigator.acaa93", - "ArkhamDBDeckImporter.a28140", + "DeckImporter.a28140", "Configuration.03804b", "DrawingTool.280086", "PlayAreaImageSwapper.b7b45b", diff --git a/objects/ArkhamDBDeckImporter.a28140.luascriptstate b/objects/ArkhamDBDeckImporter.a28140.luascriptstate deleted file mode 100644 index 6c7562c2..00000000 --- a/objects/ArkhamDBDeckImporter.a28140.luascriptstate +++ /dev/null @@ -1 +0,0 @@ -{"greenDeck":"","investigators":true,"loadNewest":true,"orangeDeck":"","privateDeck":true,"redDeck":"","whiteDeck":""} diff --git a/objects/ArkhamDBDeckImporter.a28140.json b/objects/DeckImporter.a28140.json similarity index 81% rename from objects/ArkhamDBDeckImporter.a28140.json rename to objects/DeckImporter.a28140.json index ef572d40..db6aa0bb 100644 --- a/objects/ArkhamDBDeckImporter.a28140.json +++ b/objects/DeckImporter.a28140.json @@ -19,7 +19,7 @@ }, "ImageScalar": 1, "ImageSecondaryURL": "", - "ImageURL": "https://i.imgur.com/wDp1Woo.jpg", + "ImageURL": "https://steamusercontent-a.akamaihd.net/ugc/2466368617690977900/DCFE14F204C4038FA382741683862B95DC892F8E/", "WidthScale": 0 }, "Description": "", @@ -34,10 +34,10 @@ "LayoutGroupSortIndex": 0, "Locked": true, "LuaScript": "require(\"arkhamdb/DeckImporter\")", - "LuaScriptState_path": "ArkhamDBDeckImporter.a28140.luascriptstate", + "LuaScriptState_path": "DeckImporter.a28140.luascriptstate", "MeasureMovement": false, "Name": "Custom_Tile", - "Nickname": "ArkhamDB Deck Importer", + "Nickname": "Deck Importer", "Snap": false, "Sticky": true, "Tooltip": false, @@ -54,4 +54,4 @@ }, "Value": 0, "XmlUI": "" -} +} \ No newline at end of file diff --git a/objects/DeckImporter.a28140.luascriptstate b/objects/DeckImporter.a28140.luascriptstate new file mode 100644 index 00000000..77353414 --- /dev/null +++ b/objects/DeckImporter.a28140.luascriptstate @@ -0,0 +1,9 @@ +{ + "greenDeck": "", + "loadNewest": true, + "orangeDeck": "", + "privateDeck": true, + "redDeck": "", + "standalone": false, + "whiteDeck": "" +} \ No newline at end of file diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua index 3e80625a..691e9d65 100644 --- a/src/arkhamdb/ArkhamDb.ttslua +++ b/src/arkhamdb/ArkhamDb.ttslua @@ -34,19 +34,19 @@ do end) end - -- Start the deck build process for the given player color and deck ID. This + -- Start the deck build process for the given player color and deck ID. This -- will retrieve the deck from ArkhamDB, and pass to a callback for processing. ---@param playerColor string Color name of the player mat to place this deck on (e.g. "Red"). ---@param deckId string ArkhamDB deck id to be loaded ---@param isPrivate boolean Whether this deck is published or private on ArkhamDB ---@param loadNewest boolean Whether the newest version of this deck should be loaded - ---@param loadInvestigators boolean Whether investigator cards should be loaded as part of this deck + ---@param standalone boolean Whether 'Campaign only' weaknesses should be exluded ---@param callback function Callback which will be sent the results of this load --- Parameters to the callback will be: --- slots table A map of card ID to count in the deck - --- investigatorCode String. ID of the investigator in this deck + --- investigatorCode String. ID of the investigator in this deck --- customizations table The decoded table of customization upgrades in this deck - --- playerColor String. Color this deck is being loaded for + --- playerColor String. Color this deck is being loaded for ---@return boolean ---@return string ArkhamDb.getDecklist = function( @@ -54,7 +54,7 @@ do deckId, isPrivate, loadNewest, - loadInvestigators, + standalone, callback) -- Get a simple card to see if the bag indexes are complete. If not, abort -- the deck load. The called method will handle player notification. @@ -90,11 +90,11 @@ do return true, json end) - deck:with(internal.onDeckResult, playerColor, loadNewest, loadInvestigators, callback) + deck:with(internal.onDeckResult, playerColor, loadNewest, standalone, callback) end -- Logs that a card could not be loaded in the mod by printing it to the console in the given - -- color of the player owning the deck. Attempts to look up the name on ArkhamDB for clarity, + -- color of the player owning the deck. Attempts to look up the name on ArkhamDB for clarity, -- but prints the card ID if the name cannot be retrieved. ---@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 @@ -118,23 +118,23 @@ do end) end - -- Callback when the deck information is received from ArkhamDB. Parses the + -- Callback when the deck information is received from ArkhamDB. Parses the -- response then applies standard transformations to the deck such as adding - -- random weaknesses and checking for taboos. Once the deck is processed, + -- random weaknesses and checking for taboos. Once the deck is processed, -- passes to loadCards to actually spawn the defined deck. ---@param deck table ArkhamImportDeck ---@param playerColor string Color name of the player mat to place this deck on (e.g. "Red") ---@param loadNewest boolean Whether the newest version of this deck should be loaded - ---@param loadInvestigators boolean Whether investigator cards should be loaded as part of this deck + ---@param standalone boolean Whether 'Campaign only' weaknesses should be exluded ---@param callback function Callback which will be sent the results of this load. --- Parameters to the callback will be: --- slots table A map of card ID to count in the deck - --- investigatorCode String. ID of the investigator in this deck - --- bondedList A table of cardID keys to meaningless values. Card IDs in this list were + --- investigatorCode String. ID of the investigator in this deck + --- bondedList A table of cardID keys to meaningless values. Card IDs in this list were --- added from a parent bonded card. --- customizations table The decoded table of customization upgrades in this deck - --- playerColor String. Color this deck is being loaded for - internal.onDeckResult = function(deck, playerColor, loadNewest, loadInvestigators, callback) + --- playerColor String. Color this deck is being loaded for + internal.onDeckResult = function(deck, playerColor, loadNewest, standalone, callback) -- Load the next deck in the upgrade path if the option is enabled if (loadNewest and deck.next_deck ~= nil and deck.next_deck ~= "") then buildDeck(playerColor, deck.next_deck) @@ -143,17 +143,20 @@ do internal.maybePrint(table.concat({ "Found decklist: ", deck.name }), playerColor) - -- 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 + -- 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 local slots = deck.slots - internal.maybeDrawRandomWeakness(slots, playerColor) + + -- get class for investigator to handle specific weaknesses + local class + local card = allCardsBagApi.getCardById(deck.investigator_code) + if card and card.metadata then class = card.metadata.class end + local restrictions = { class = class, standalone = standalone } + internal.maybeDrawRandomWeakness(slots, playerColor, restrictions) -- handles alternative investigators (parallel, promo or revised art) - local loadAltInvestigator = "normal" - if loadInvestigators then - loadAltInvestigator = internal.addInvestigatorCards(deck, slots) - end + local loadAltInvestigator = internal.addInvestigatorCards(deck, slots) internal.maybeModifyDeckFromDescription(slots, deck.description_md, playerColor) internal.maybeAddSummonedServitor(slots) @@ -179,13 +182,17 @@ do --- of those cards which will be spawned ---@param playerColor string Color of the player this deck is being loaded for. Used for broadcast --- if a weakness is added. - internal.maybeDrawRandomWeakness = function(slots, playerColor) + ---@param restrictions table Additional restrictions: + --- class string Class to restrict weakness to + --- standalone boolean Whether 'Campaign only' weaknesses should be exluded + --- traits? string Trait(s) to use as filter + internal.maybeDrawRandomWeakness = function(slots, playerColor, restrictions) local randomWeaknessAmount = slots[RANDOM_WEAKNESS_ID] or 0 slots[RANDOM_WEAKNESS_ID] = nil if randomWeaknessAmount > 0 then for i = 1, randomWeaknessAmount do - local weaknessId = allCardsBagApi.getRandomWeaknessId() + local weaknessId = allCardsBagApi.getRandomWeaknessId(restrictions) slots[weaknessId] = (slots[weaknessId] or 0) + 1 end internal.maybePrint("Added " .. randomWeaknessAmount .. " random basic weakness(es) to deck", playerColor) @@ -194,7 +201,7 @@ do -- Adds both the investigator (XXXXX) and minicard (XXXXX-m) slots with one copy each ---@param deck table The processed ArkhamDB deck response - ---@param slots table The slot list for cards in this deck. Table key is the cardId, value is the + ---@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 ---@return string: Contains the name of the art that should be loaded ("normal", "promo" or "revised") internal.addInvestigatorCards = function(deck, slots) @@ -271,7 +278,7 @@ do end end - -- On the Mend should have 1-per-investigator copies set aside, but ArkhamDB always sends 1. Update + -- On the Mend should have 1-per-investigator copies set aside, but ArkhamDB always sends 1. Update -- the count based on the investigator count ---@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 diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 9f78d30d..33b2a20d 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -21,9 +21,9 @@ local UPGRADED_TOGGLE_LABELS = {} UPGRADED_TOGGLE_LABELS[true] = "Upgraded" UPGRADED_TOGGLE_LABELS[false] = "Specific" -local LOAD_INVESTIGATOR_TOGGLE_LABELS = {} -LOAD_INVESTIGATOR_TOGGLE_LABELS[true] = "Yes" -LOAD_INVESTIGATOR_TOGGLE_LABELS[false] = "No" +local STANDALONE_TOGGLE_LABELS = {} +STANDALONE_TOGGLE_LABELS[true] = "Yes" +STANDALONE_TOGGLE_LABELS[false] = "No" local redDeckId = "" local orangeDeckId = "" @@ -32,7 +32,7 @@ local greenDeckId = "" local privateDeck = true local loadNewestDeck = true -local loadInvestigators = false +local standalone = false function onLoad(script_state) initializeUi(JSON.decode(script_state)) @@ -53,7 +53,7 @@ function getUiState() greenDeck = greenDeckId, privateDeck = privateDeck, loadNewest = loadNewestDeck, - investigators = loadInvestigators + standalone = standalone } end @@ -74,7 +74,7 @@ function initializeUi(savedUiState) greenDeckId = savedUiState.greenDeck privateDeck = savedUiState.privateDeck loadNewestDeck = savedUiState.loadNewest - loadInvestigators = savedUiState.investigators + standalone = savedUiState.standalone end makeOptionToggles() @@ -84,72 +84,74 @@ end function makeOptionToggles() -- common parameters - 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 + local cParams = {} + cParams.function_owner = self + cParams.width = 1750 + cParams.height = INPUT_FIELD_HEIGHT + 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 } + cParams.color = FIELD_COLOR -- public / private deck - 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) + cParams.click_function = "publishedPrivateChanged" + cParams.tooltip = "Published or private deck?\n\nPLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!\n\nMake sure to enable deck sharing in your account settings.\n\nKeep this on 'Private' for arkham.build." + cParams.label = PRIVATE_TOGGLE_LABELS[privateDeck] + self.createButton(cParams) -- load upgraded? - 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) + cParams.click_function = "loadUpgradedChanged" + cParams.position.z = -0.01 + cParams.tooltip = "Load newest upgrade or exact deck?" + cParams.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] + self.createButton(cParams) - -- load investigators? - 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) + -- standalone mode? + cParams.click_function = "standaloneChanged" + cParams.position.z = 0.081 + cParams.tooltip = "Are you playing standalone mode? Enabling this will make all 'Campaign Only' weaknesses ineligible when determining the random basic weakness(es)." + cParams.label = STANDALONE_TOGGLE_LABELS[standalone] + self.createButton(cParams) end -- Create the four deck ID entry fields function makeDeckIdFields() - local inputParameters = {} - -- Parameters common to all entry fields - 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) + local iParams = {} + iParams.function_owner = self + iParams.scale = { 0.1, 0.1, 0.1 } + iParams.width = INPUT_FIELD_WIDTH + iParams.height = INPUT_FIELD_HEIGHT + iParams.font_size = 320 + iParams.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!" + iParams.alignment = 3 -- Center + iParams.color = FIELD_COLOR + iParams.font_color = { 0, 0, 0 } + iParams.validation = 4 -- alphanumeric (to support arkham.build IDs) -- Green - inputParameters.input_function = "greenDeckChanged" - inputParameters.position = { -0.166, 0.1, 0.385 } - inputParameters.value = greenDeckId - self.createInput(inputParameters) + iParams.input_function = "greenDeckChanged" + iParams.position = { -0.16, 0.1, 0.385 } + iParams.value = greenDeckId + self.createInput(iParams) + -- Red - inputParameters.input_function = "redDeckChanged" - inputParameters.position = { 0.171, 0.1, 0.385 } - inputParameters.value = redDeckId - self.createInput(inputParameters) + iParams.input_function = "redDeckChanged" + iParams.position = { 0.165, 0.1, 0.385 } + iParams.value = redDeckId + self.createInput(iParams) + -- White - inputParameters.input_function = "whiteDeckChanged" - inputParameters.position = { -0.166, 0.1, 0.474 } - inputParameters.value = whiteDeckId - self.createInput(inputParameters) + iParams.input_function = "whiteDeckChanged" + iParams.position = { -0.16, 0.1, 0.474 } + iParams.value = whiteDeckId + self.createInput(iParams) + -- Orange - inputParameters.input_function = "orangeDeckChanged" - inputParameters.position = { 0.171, 0.1, 0.474 } - inputParameters.value = orangeDeckId - self.createInput(inputParameters) + iParams.input_function = "orangeDeckChanged" + iParams.position = { 0.165, 0.1, 0.474 } + iParams.value = orangeDeckId + self.createInput(iParams) end -- Create the Build All button. This is a transparent button which covers the Build All portion of the background graphic @@ -172,7 +174,7 @@ function whiteDeckChanged(_, _, inputValue) whiteDeckId = inputValue end function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end -- Event handlers for toggle buttons -function publicPrivateChanged() +function publishedPrivateChanged() privateDeck = not privateDeck self.editButton({ index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] }) end @@ -182,9 +184,9 @@ function loadUpgradedChanged() 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] }) +function standaloneChanged() + standalone = not standalone + self.editButton({ index = 2, label = STANDALONE_TOGGLE_LABELS[standalone] }) end function loadDecks() @@ -247,7 +249,7 @@ function buildDeck(playerColor, deckId) deckId, uiState.privateDeck, uiState.loadNewest, - uiState.investigators, + uiState.standalone, loadCards) end diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index 652d4f70..0f864339 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -370,19 +370,29 @@ end -- Gets a random basic weakness from the bag. Once a given ID has been returned it will be -- removed from the list and cannot be selected again until a reload occurs or the indexes -- are rebuilt, which will refresh the list to include all weaknesses. +---@param restrictions? table Additional restrictions: +--- class string Class to restrict weakness to +--- standalone boolean Whether 'Campaign only' weaknesses should be exluded +--- traits? string Trait(s) to use as filter ---@return string: ID of the selected weakness -function getRandomWeaknessId() - local availableWeaknesses = buildAvailableWeaknesses() +function getRandomWeaknessId(restrictions) + local availableWeaknesses = buildAvailableWeaknesses(restrictions) if #availableWeaknesses > 0 then return availableWeaknesses[math.random(#availableWeaknesses)] + else + broadcastToAll("No basic weakness available!", { 0.9, 0.2, 0.2 }) end end -- Constructs a list of available basic weaknesses by starting with the full pool of basic -- weaknesses then removing any which are currently in the play or deck construction areas ----@param traits? string Trait(s) to use as filter +---@param restrictions? table Additional restrictions: +--- class string Class to restrict weakness to +--- standalone boolean Whether 'Campaign only' weaknesses should be exluded +--- traits? string Trait(s) to use as filter ---@return table: Array of weakness IDs which are valid to choose from -function buildAvailableWeaknesses(traits) +function buildAvailableWeaknesses(restrictions) + restrictions = restrictions or {} local weaknessesInPlay = {} local allObjects = getAllObjects() for _, object in ipairs(allObjects) do @@ -400,10 +410,29 @@ function buildAvailableWeaknesses(traits) if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1 else - if traits then + local eligible = true + + -- disable 'Campaign only' weaknesses in standalone mode + if restrictions.standalone then + local card = cardIdIndex[weaknessId] + if card.metadata.modeRestriction == "Campaign" then + eligible = false + end + end + + -- disable class restricted weaknesses + if restrictions.class then + local card = cardIdIndex[weaknessId] + if card.metadata.classRestriction ~= restrictions.class then + eligible = false + end + end + + -- disable non-matching traits + if restrictions.traits then -- split the string into separate traits (separated by "|") local allowedTraits = {} - for str in traits:gmatch("([^|]+)") do + for str in restrictions.traits:gmatch("([^|]+)") do -- remove dots str = str:gsub("[%.]", "") @@ -415,15 +444,24 @@ function buildAvailableWeaknesses(traits) table.insert(allowedTraits, str) end + local match = false + -- make sure the trait is present on the weakness local card = cardIdIndex[weaknessId] for _, allowedTrait in ipairs(allowedTraits) do if string.contains(string.lower(card.metadata.traits), allowedTrait) then - table.insert(availableWeaknesses, weaknessId) + match = true break end end - else + + if not match then + eligible = false + end + end + + -- add weakness to list if eligible + if eligible then table.insert(availableWeaknesses, weaknessId) end end diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index 154aed39..b62b1592 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -28,9 +28,13 @@ do -- Gets a random basic weakness from the bag. Once a given ID has been returned it -- will be removed from the list and cannot be selected again until a reload occurs -- or the indexes are rebuilt, which will refresh the list to include all weaknesses. + ---@param restrictions table Additional restrictions: + --- class string Class to restrict weakness to + --- standalone boolean Whether 'Campaign only' weaknesses should be exluded + --- traits? string Trait(s) to use as filter ---@return string: ID of the selected weakness - AllCardsBagApi.getRandomWeaknessId = function() - return getAllCardsBag().call("getRandomWeaknessId") + AllCardsBagApi.getRandomWeaknessId = function(restrictions) + return getAllCardsBag().call("getRandomWeaknessId", restrictions) end AllCardsBagApi.isIndexReady = function() @@ -82,10 +86,13 @@ do -- Constructs a list of available basic weaknesses by starting with the full pool of basic -- weaknesses then removing any which are currently in the play or deck construction areas - ---@param traits? string Trait(s) to use as filter + ---@param restrictions table Additional restrictions: + --- class string Class to restrict weakness to + --- standalone boolean Whether 'Campaign only' weaknesses should be exluded + --- traits? string Trait(s) to use as filter ---@return table: Array of weakness IDs which are valid to choose from - AllCardsBagApi.buildAvailableWeaknesses = function(traits) - return returnCopyOfList(getAllCardsBag().call("buildAvailableWeaknesses", traits)) + AllCardsBagApi.buildAvailableWeaknesses = function(restrictions) + return returnCopyOfList(getAllCardsBag().call("buildAvailableWeaknesses", restrictions)) end AllCardsBagApi.getUniqueWeaknesses = function() diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua index 09587e62..c2404696 100644 --- a/src/playercards/PlayerCardPanel.ttslua +++ b/src/playercards/PlayerCardPanel.ttslua @@ -783,20 +783,16 @@ function spawnRandomWeakness(_, playerColor, isRightClick) if not isRightClick then local weaknessId = allCardsBagApi.getRandomWeaknessId() - if weaknessId == nil then - broadcastToAll("All basic weaknesses are in play!", { 0.9, 0.2, 0.2 }) - else + if weaknessId then spawnSingleWeakness(weaknessId) end else Player[playerColor].showInputDialog("Specify a trait for the weakness (split multiple eligible traits with '|'):", lastWeaknessTrait, function(text) lastWeaknessTrait = text - local availableWeaknesses = allCardsBagApi.buildAvailableWeaknesses(text) - if #availableWeaknesses > 0 then - spawnSingleWeakness(availableWeaknesses[math.random(#availableWeaknesses)]) - else - broadcastToAll("No matching weakness available!", { 0.9, 0.2, 0.2 }) + local weaknessId = allCardsBagApi.getRandomWeaknessId({ traits = text }) + if weaknessId then + spawnSingleWeakness(weaknessId) end end) end @@ -808,6 +804,6 @@ function spawnSingleWeakness(weaknessId) name = "randomWeakness", cards = { weaknessId }, globalPos = self.positionToWorld(startPositions.randomWeakness), - rotation = FACE_UP_ROTATION, + rotation = FACE_UP_ROTATION }) end From 10df46854a08a862afdf6b7f21530778cb5f57f3 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 1 Aug 2024 21:07:47 +0200 Subject: [PATCH 3/6] fixed class restriction logic --- src/playercards/AllCardsBag.ttslua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index 0f864339..b0aa65ab 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -423,7 +423,7 @@ function buildAvailableWeaknesses(restrictions) -- disable class restricted weaknesses if restrictions.class then local card = cardIdIndex[weaknessId] - if card.metadata.classRestriction ~= restrictions.class then + if card.metadata.classRestriction and card.metadata.classRestriction ~= restrictions.class then eligible = false end end From 893bd05801137b020dc385a00d85080fd8102c5b Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 1 Aug 2024 22:15:35 +0200 Subject: [PATCH 4/6] updated deck importer --- src/arkhamdb/ArkhamDb.ttslua | 6 ++-- src/arkhamdb/DeckImporter.ttslua | 40 ++++++++++++++++---------- src/playercards/AllCardsBag.ttslua | 38 +++++++++++++++++------- src/playercards/AllCardsBagApi.ttslua | 7 +++-- src/playercards/PlayerCardPanel.ttslua | 12 ++++---- 5 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua index 691e9d65..1e9bfcbd 100644 --- a/src/arkhamdb/ArkhamDb.ttslua +++ b/src/arkhamdb/ArkhamDb.ttslua @@ -191,11 +191,11 @@ do slots[RANDOM_WEAKNESS_ID] = nil if randomWeaknessAmount > 0 then - for i = 1, randomWeaknessAmount do - local weaknessId = allCardsBagApi.getRandomWeaknessId(restrictions) + local weaknessIds = allCardsBagApi.getRandomWeaknessIds(randomWeaknessAmount, restrictions) + for _, weaknessId in ipairs(weaknessIds) do slots[weaknessId] = (slots[weaknessId] or 0) + 1 end - internal.maybePrint("Added " .. randomWeaknessAmount .. " random basic weakness(es) to deck", playerColor) + internal.maybePrint("Added " .. #weaknessIds .. " random basic weakness(es) to deck", playerColor) end end diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 33b2a20d..5bcebb3a 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -25,10 +25,10 @@ local STANDALONE_TOGGLE_LABELS = {} STANDALONE_TOGGLE_LABELS[true] = "Yes" STANDALONE_TOGGLE_LABELS[false] = "No" -local redDeckId = "" -local orangeDeckId = "" -local whiteDeckId = "" -local greenDeckId = "" +redDeckId = "" +orangeDeckId = "" +whiteDeckId = "" +greenDeckId = "" local privateDeck = true local loadNewestDeck = true @@ -123,7 +123,7 @@ function makeDeckIdFields() iParams.width = INPUT_FIELD_WIDTH iParams.height = INPUT_FIELD_HEIGHT iParams.font_size = 320 - iParams.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!" + iParams.tooltip = "Deck ID from ArkhamDB URL of the deck\nPublished 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!" iParams.alignment = 3 -- Center iParams.color = FIELD_COLOR iParams.font_color = { 0, 0, 0 } @@ -190,19 +190,28 @@ function standaloneChanged() end function loadDecks() + co = coroutine.create(loadDecksCoroutine) + resumeLoadDecks() +end + +function loadDecksCoroutine() if not allCardsBagApi.isIndexReady() then return end matsWithInvestigator = playermatApi.getUsedMatColors() - if redDeckId ~= nil and redDeckId ~= "" then - buildDeck("Red", redDeckId) + + 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) + coroutine.yield() + end end - if orangeDeckId ~= nil and orangeDeckId ~= "" then - buildDeck("Orange", orangeDeckId) - end - if whiteDeckId ~= nil and whiteDeckId ~= "" then - buildDeck("White", whiteDeckId) - end - if greenDeckId ~= nil and greenDeckId ~= "" then - buildDeck("Green", greenDeckId) +end + +function resumeLoadDecks() + log("resume") + if co and coroutine.status(co) ~= "dead" then + local status, err = coroutine.resume(co) + if not status then error(err) end end end @@ -345,6 +354,7 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo if (not hadError) then printToAll("Deck loaded successfully!", playerColor) end + resumeLoadDecks() return 1 end diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index b0aa65ab..9e6d6f50 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -370,18 +370,34 @@ end -- Gets a random basic weakness from the bag. Once a given ID has been returned it will be -- removed from the list and cannot be selected again until a reload occurs or the indexes -- are rebuilt, which will refresh the list to include all weaknesses. ----@param restrictions? table Additional restrictions: ---- class string Class to restrict weakness to ---- standalone boolean Whether 'Campaign only' weaknesses should be exluded ---- traits? string Trait(s) to use as filter ----@return string: ID of the selected weakness -function getRandomWeaknessId(restrictions) - local availableWeaknesses = buildAvailableWeaknesses(restrictions) - if #availableWeaknesses > 0 then - return availableWeaknesses[math.random(#availableWeaknesses)] - else - broadcastToAll("No basic weakness available!", { 0.9, 0.2, 0.2 }) +---@param params table Bundled parameters: +--- count number Number of weaknesses +--- restrictions table Additional restrictions: +--- class string Class to restrict weakness to +--- standalone boolean Whether 'Campaign only' weaknesses should be exluded +--- traits? string Trait(s) to use as filter +---@return table: Table with IDs of the selected weaknesses +function getRandomWeaknessIds(params) + params.count = params.count or 1 + local availableWeaknesses = buildAvailableWeaknesses(params.restrictions) + + -- check if enough weaknesses are available + local missingWeaknesses = params.count - #availableWeaknesses + if missingWeaknesses > 0 then + broadcastToAll("Not enough basic weaknesses available! (" .. missingWeaknesses .. " missing)", { 0.9, 0.2, 0.2 }) end + + local drawnWeaknesses = {} + + -- Fisher-Yates shuffle algorithm + local n = #availableWeaknesses + for i = 1, math.min(params.count, n) do + local index = math.random(i, n) + table.insert(drawnWeaknesses, availableWeaknesses[index]) + availableWeaknesses[index], availableWeaknesses[i] = availableWeaknesses[i], availableWeaknesses[index] + end + + return drawnWeaknesses end -- Constructs a list of available basic weaknesses by starting with the full pool of basic diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index b62b1592..2a69145b 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -28,13 +28,14 @@ do -- Gets a random basic weakness from the bag. Once a given ID has been returned it -- will be removed from the list and cannot be selected again until a reload occurs -- or the indexes are rebuilt, which will refresh the list to include all weaknesses. + ---@param count number Number of weaknesses ---@param restrictions table Additional restrictions: --- class string Class to restrict weakness to --- standalone boolean Whether 'Campaign only' weaknesses should be exluded --- traits? string Trait(s) to use as filter - ---@return string: ID of the selected weakness - AllCardsBagApi.getRandomWeaknessId = function(restrictions) - return getAllCardsBag().call("getRandomWeaknessId", restrictions) + ---@return table: Table with IDs of the selected weaknesses + AllCardsBagApi.getRandomWeaknessIds = function(count, restrictions) + return returnCopyOfList(getAllCardsBag().call("getRandomWeaknessIds", {count = count, restrictions = restrictions})) end AllCardsBagApi.isIndexReady = function() diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua index c2404696..51427e6a 100644 --- a/src/playercards/PlayerCardPanel.ttslua +++ b/src/playercards/PlayerCardPanel.ttslua @@ -782,17 +782,17 @@ function spawnRandomWeakness(_, playerColor, isRightClick) prepareToPlaceCards() if not isRightClick then - local weaknessId = allCardsBagApi.getRandomWeaknessId() - if weaknessId then - spawnSingleWeakness(weaknessId) + local weaknessIds = allCardsBagApi.getRandomWeaknessIds(1) + if weaknessIds[1] then + spawnSingleWeakness(weaknessIds[1]) end else Player[playerColor].showInputDialog("Specify a trait for the weakness (split multiple eligible traits with '|'):", lastWeaknessTrait, function(text) lastWeaknessTrait = text - local weaknessId = allCardsBagApi.getRandomWeaknessId({ traits = text }) - if weaknessId then - spawnSingleWeakness(weaknessId) + local weaknessIds = allCardsBagApi.getRandomWeaknessIds(1, { traits = text }) + if weaknessIds[1] then + spawnSingleWeakness(weaknessIds[1]) end end) end From 42cda036a0820b70fe8dc4ad700d045ac701b304 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 1 Aug 2024 22:17:59 +0200 Subject: [PATCH 5/6] added comments --- src/arkhamdb/DeckImporter.ttslua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/arkhamdb/DeckImporter.ttslua b/src/arkhamdb/DeckImporter.ttslua index 5bcebb3a..4d878aa2 100644 --- a/src/arkhamdb/DeckImporter.ttslua +++ b/src/arkhamdb/DeckImporter.ttslua @@ -189,11 +189,14 @@ function standaloneChanged() self.editButton({ index = 2, label = STANDALONE_TOGGLE_LABELS[standalone] }) end +-- start the deck importing process function loadDecks() co = coroutine.create(loadDecksCoroutine) resumeLoadDecks() end +-- perform the deck importing (with a pause after each deck load) +-- this pause will for example allow weaknesses to be spawned so that the RBW drawing can detect them function loadDecksCoroutine() if not allCardsBagApi.isIndexReady() then return end matsWithInvestigator = playermatApi.getUsedMatColors() @@ -207,8 +210,8 @@ function loadDecksCoroutine() end end +-- resume the deck importing process function resumeLoadDecks() - log("resume") if co and coroutine.status(co) ~= "dead" then local status, err = coroutine.resume(co) if not status then error(err) end From 38d017084ec1217eeb14495dc101fe255a25d66d Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 1 Aug 2024 22:27:50 +0200 Subject: [PATCH 6/6] updated image --- objects/DeckImporter.a28140.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/objects/DeckImporter.a28140.json b/objects/DeckImporter.a28140.json index db6aa0bb..2bc4cd7d 100644 --- a/objects/DeckImporter.a28140.json +++ b/objects/DeckImporter.a28140.json @@ -19,7 +19,7 @@ }, "ImageScalar": 1, "ImageSecondaryURL": "", - "ImageURL": "https://steamusercontent-a.akamaihd.net/ugc/2466368617690977900/DCFE14F204C4038FA382741683862B95DC892F8E/", + "ImageURL": "https://steamusercontent-a.akamaihd.net/ugc/2466368617691603054/ABA4AB3A811107629323CDA2765871EB36626242/", "WidthScale": 0 }, "Description": "",