Merge pull request #777 from argonui/weakness-importing

Updated random basic weakness drawing for deck importer
This commit is contained in:
dscarpac 2024-08-01 15:42:13 -05:00 committed by GitHub
commit 439548f139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 237 additions and 138 deletions

View File

@ -138,7 +138,7 @@
"Playermat3Green.383d8b", "Playermat3Green.383d8b",
"Playermat4Red.0840d5", "Playermat4Red.0840d5",
"LeadInvestigator.acaa93", "LeadInvestigator.acaa93",
"ArkhamDBDeckImporter.a28140", "DeckImporter.a28140",
"Configuration.03804b", "Configuration.03804b",
"DrawingTool.280086", "DrawingTool.280086",
"PlayAreaImageSwapper.b7b45b", "PlayAreaImageSwapper.b7b45b",

View File

@ -6,5 +6,6 @@
"traits": "Pact.", "traits": "Pact.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"modeRestriction": "Campaign",
"cycle": "The Forgotten Age" "cycle": "The Forgotten Age"
} }

View File

@ -5,5 +5,6 @@
"traits": "Curse.", "traits": "Curse.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"modeRestriction": "Campaign",
"cycle": "The Forgotten Age" "cycle": "The Forgotten Age"
} }

View File

@ -5,5 +5,6 @@
"traits": "Monster. Geist.", "traits": "Monster. Geist.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"classRestriction": "Mystic",
"cycle": "The Scarlet Keys" "cycle": "The Scarlet Keys"
} }

View File

@ -5,5 +5,6 @@
"traits": "Monster. Shoggoth.", "traits": "Monster. Shoggoth.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"classRestriction": "Guardian",
"cycle": "The Scarlet Keys" "cycle": "The Scarlet Keys"
} }

View File

@ -5,5 +5,6 @@
"traits": "Pact.", "traits": "Pact.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"modeRestriction": "Campaign",
"cycle": "Return to the Forgotten Age" "cycle": "Return to the Forgotten Age"
} }

View File

@ -6,5 +6,6 @@
"traits": "Pact.", "traits": "Pact.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"classRestriction": "Rogue",
"cycle": "The Scarlet Keys" "cycle": "The Scarlet Keys"
} }

View File

@ -6,5 +6,6 @@
"traits": "Paradox.", "traits": "Paradox.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"classRestriction": "Seeker",
"cycle": "The Scarlet Keys" "cycle": "The Scarlet Keys"
} }

View File

@ -6,5 +6,6 @@
"traits": "Blunder.", "traits": "Blunder.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"classRestriction": "Survivor",
"cycle": "The Scarlet Keys" "cycle": "The Scarlet Keys"
} }

View File

@ -5,6 +5,7 @@
"traits": "Madness. Pact.", "traits": "Madness. Pact.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"modeRestriction": "Campaign",
"hidden": true, "hidden": true,
"cycle": "Return to the Path to Carcosa" "cycle": "Return to the Path to Carcosa"
} }

View File

@ -5,6 +5,7 @@
"traits": "Madness. Pact.", "traits": "Madness. Pact.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"modeRestriction": "Campaign",
"hidden": true, "hidden": true,
"cycle": "Return to the Path to Carcosa" "cycle": "Return to the Path to Carcosa"
} }

View File

@ -5,6 +5,7 @@
"traits": "Madness. Pact.", "traits": "Madness. Pact.",
"weakness": true, "weakness": true,
"basicWeaknessCount": 1, "basicWeaknessCount": 1,
"modeRestriction": "Campaign",
"hidden": true, "hidden": true,
"cycle": "Return to the Path to Carcosa" "cycle": "Return to the Path to Carcosa"
} }

View File

@ -1 +0,0 @@
{"greenDeck":"","investigators":true,"loadNewest":true,"orangeDeck":"","privateDeck":true,"redDeck":"","whiteDeck":""}

View File

@ -19,7 +19,7 @@
}, },
"ImageScalar": 1, "ImageScalar": 1,
"ImageSecondaryURL": "", "ImageSecondaryURL": "",
"ImageURL": "https://i.imgur.com/wDp1Woo.jpg", "ImageURL": "https://steamusercontent-a.akamaihd.net/ugc/2466368617691603054/ABA4AB3A811107629323CDA2765871EB36626242/",
"WidthScale": 0 "WidthScale": 0
}, },
"Description": "", "Description": "",
@ -34,10 +34,10 @@
"LayoutGroupSortIndex": 0, "LayoutGroupSortIndex": 0,
"Locked": true, "Locked": true,
"LuaScript": "require(\"arkhamdb/DeckImporter\")", "LuaScript": "require(\"arkhamdb/DeckImporter\")",
"LuaScriptState_path": "ArkhamDBDeckImporter.a28140.luascriptstate", "LuaScriptState_path": "DeckImporter.a28140.luascriptstate",
"MeasureMovement": false, "MeasureMovement": false,
"Name": "Custom_Tile", "Name": "Custom_Tile",
"Nickname": "ArkhamDB Deck Importer", "Nickname": "Deck Importer",
"Snap": false, "Snap": false,
"Sticky": true, "Sticky": true,
"Tooltip": false, "Tooltip": false,
@ -54,4 +54,4 @@
}, },
"Value": 0, "Value": 0,
"XmlUI": "" "XmlUI": ""
} }

View File

@ -0,0 +1,9 @@
{
"greenDeck": "",
"loadNewest": true,
"orangeDeck": "",
"privateDeck": true,
"redDeck": "",
"standalone": false,
"whiteDeck": ""
}

View File

@ -34,19 +34,19 @@ do
end) end)
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. -- 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 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 deckId string ArkhamDB deck id to be loaded
---@param isPrivate boolean Whether this deck is published or private on ArkhamDB ---@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 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 ---@param callback function Callback which will be sent the results of this load
--- Parameters to the callback will be: --- Parameters to the callback will be:
--- slots table A map of card ID to count in the deck --- 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 --- 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 boolean
---@return string ---@return string
ArkhamDb.getDecklist = function( ArkhamDb.getDecklist = function(
@ -54,7 +54,7 @@ do
deckId, deckId,
isPrivate, isPrivate,
loadNewest, loadNewest,
loadInvestigators, standalone,
callback) callback)
-- Get a simple card to see if the bag indexes are complete. If not, abort -- 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. -- the deck load. The called method will handle player notification.
@ -90,11 +90,11 @@ do
return true, json return true, json
end) end)
deck:with(internal.onDeckResult, playerColor, loadNewest, loadInvestigators, callback) deck:with(internal.onDeckResult, playerColor, loadNewest, standalone, callback)
end end
-- Logs that a card could not be loaded in the mod by printing it to the console in the given -- 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. -- 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 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 ---@param playerColor string Color of the player's deck that had the problem
@ -118,23 +118,23 @@ do
end) end)
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 -- 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. -- passes to loadCards to actually spawn the defined deck.
---@param deck table ArkhamImportDeck ---@param deck table ArkhamImportDeck
---@param playerColor string Color name of the player mat to place this deck on (e.g. "Red") ---@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 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. ---@param callback function Callback which will be sent the results of this load.
--- Parameters to the callback will be: --- Parameters to the callback will be:
--- slots table A map of card ID to count in the deck --- 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
--- bondedList A table of cardID keys to meaningless values. Card IDs in this list were --- bondedList A table of cardID keys to meaningless values. Card IDs in this list were
--- added from a parent bonded card. --- added from a parent bonded card.
--- customizations table The decoded table of customization upgrades 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
internal.onDeckResult = function(deck, playerColor, loadNewest, loadInvestigators, callback) internal.onDeckResult = function(deck, playerColor, loadNewest, standalone, callback)
-- Load the next deck in the upgrade path if the option is enabled -- 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 if (loadNewest and deck.next_deck ~= nil and deck.next_deck ~= "") then
buildDeck(playerColor, deck.next_deck) buildDeck(playerColor, deck.next_deck)
@ -143,17 +143,20 @@ do
internal.maybePrint(table.concat({ "Found decklist: ", deck.name }), playerColor) internal.maybePrint(table.concat({ "Found decklist: ", deck.name }), playerColor)
-- Initialize deck slot table and perform common transformations. The order of these should not -- 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 -- 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 -- investigator may have bonded cards or taboo entries, and should be present
local slots = deck.slots 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) -- handles alternative investigators (parallel, promo or revised art)
local loadAltInvestigator = "normal" local loadAltInvestigator = internal.addInvestigatorCards(deck, slots)
if loadInvestigators then
loadAltInvestigator = internal.addInvestigatorCards(deck, slots)
end
internal.maybeModifyDeckFromDescription(slots, deck.description_md, playerColor) internal.maybeModifyDeckFromDescription(slots, deck.description_md, playerColor)
internal.maybeAddSummonedServitor(slots) internal.maybeAddSummonedServitor(slots)
@ -179,22 +182,26 @@ do
--- of those cards which will be spawned --- of those cards which will be spawned
---@param playerColor string Color of the player this deck is being loaded for. Used for broadcast ---@param playerColor string Color of the player this deck is being loaded for. Used for broadcast
--- if a weakness is added. --- 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 local randomWeaknessAmount = slots[RANDOM_WEAKNESS_ID] or 0
slots[RANDOM_WEAKNESS_ID] = nil slots[RANDOM_WEAKNESS_ID] = nil
if randomWeaknessAmount > 0 then if randomWeaknessAmount > 0 then
for i = 1, randomWeaknessAmount do local weaknessIds = allCardsBagApi.getRandomWeaknessIds(randomWeaknessAmount, restrictions)
local weaknessId = allCardsBagApi.getRandomWeaknessId() for _, weaknessId in ipairs(weaknessIds) do
slots[weaknessId] = (slots[weaknessId] or 0) + 1 slots[weaknessId] = (slots[weaknessId] or 0) + 1
end 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
end end
-- Adds both the investigator (XXXXX) and minicard (XXXXX-m) slots with one copy each -- Adds both the investigator (XXXXX) and minicard (XXXXX-m) slots with one copy each
---@param deck table The processed ArkhamDB deck response ---@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 --- number of those cards which will be spawned
---@return string: Contains the name of the art that should be loaded ("normal", "promo" or "revised") ---@return string: Contains the name of the art that should be loaded ("normal", "promo" or "revised")
internal.addInvestigatorCards = function(deck, slots) internal.addInvestigatorCards = function(deck, slots)
@ -271,7 +278,7 @@ do
end end
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 -- 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 ---@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 -- of those cards which will be spawned

View File

@ -21,18 +21,18 @@ local UPGRADED_TOGGLE_LABELS = {}
UPGRADED_TOGGLE_LABELS[true] = "Upgraded" UPGRADED_TOGGLE_LABELS[true] = "Upgraded"
UPGRADED_TOGGLE_LABELS[false] = "Specific" UPGRADED_TOGGLE_LABELS[false] = "Specific"
local LOAD_INVESTIGATOR_TOGGLE_LABELS = {} local STANDALONE_TOGGLE_LABELS = {}
LOAD_INVESTIGATOR_TOGGLE_LABELS[true] = "Yes" STANDALONE_TOGGLE_LABELS[true] = "Yes"
LOAD_INVESTIGATOR_TOGGLE_LABELS[false] = "No" STANDALONE_TOGGLE_LABELS[false] = "No"
local redDeckId = "" redDeckId = ""
local orangeDeckId = "" orangeDeckId = ""
local whiteDeckId = "" whiteDeckId = ""
local greenDeckId = "" greenDeckId = ""
local privateDeck = true local privateDeck = true
local loadNewestDeck = true local loadNewestDeck = true
local loadInvestigators = false local standalone = false
function onLoad(script_state) function onLoad(script_state)
initializeUi(JSON.decode(script_state)) initializeUi(JSON.decode(script_state))
@ -53,7 +53,7 @@ function getUiState()
greenDeck = greenDeckId, greenDeck = greenDeckId,
privateDeck = privateDeck, privateDeck = privateDeck,
loadNewest = loadNewestDeck, loadNewest = loadNewestDeck,
investigators = loadInvestigators standalone = standalone
} }
end end
@ -74,7 +74,7 @@ function initializeUi(savedUiState)
greenDeckId = savedUiState.greenDeck greenDeckId = savedUiState.greenDeck
privateDeck = savedUiState.privateDeck privateDeck = savedUiState.privateDeck
loadNewestDeck = savedUiState.loadNewest loadNewestDeck = savedUiState.loadNewest
loadInvestigators = savedUiState.investigators standalone = savedUiState.standalone
end end
makeOptionToggles() makeOptionToggles()
@ -84,72 +84,74 @@ end
function makeOptionToggles() function makeOptionToggles()
-- common parameters -- common parameters
local checkboxParameters = {} local cParams = {}
checkboxParameters.function_owner = self cParams.function_owner = self
checkboxParameters.width = INPUT_FIELD_WIDTH cParams.width = 1750
checkboxParameters.height = INPUT_FIELD_HEIGHT cParams.height = INPUT_FIELD_HEIGHT
checkboxParameters.scale = { 0.1, 0.1, 0.1 } cParams.position = Vector( 0.22, 0.1, -0.102)
checkboxParameters.font_size = 240 cParams.scale = { 0.1, 0.1, 0.1 }
checkboxParameters.hover_color = { 0.4, 0.6, 0.8 } cParams.font_size = 240
checkboxParameters.color = FIELD_COLOR cParams.hover_color = { 0.4, 0.6, 0.8 }
cParams.color = FIELD_COLOR
-- public / private deck -- public / private deck
checkboxParameters.click_function = "publicPrivateChanged" cParams.click_function = "publishedPrivateChanged"
checkboxParameters.position = { 0.25, 0.1, -0.102 } 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."
checkboxParameters.tooltip = "Published or private deck?\n\nPLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!" cParams.label = PRIVATE_TOGGLE_LABELS[privateDeck]
checkboxParameters.label = PRIVATE_TOGGLE_LABELS[privateDeck] self.createButton(cParams)
self.createButton(checkboxParameters)
-- load upgraded? -- load upgraded?
checkboxParameters.click_function = "loadUpgradedChanged" cParams.click_function = "loadUpgradedChanged"
checkboxParameters.position = { 0.25, 0.1, -0.01 } cParams.position.z = -0.01
checkboxParameters.tooltip = "Load newest upgrade or exact deck?" cParams.tooltip = "Load newest upgrade or exact deck?"
checkboxParameters.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] cParams.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck]
self.createButton(checkboxParameters) self.createButton(cParams)
-- load investigators? -- standalone mode?
checkboxParameters.click_function = "loadInvestigatorsChanged" cParams.click_function = "standaloneChanged"
checkboxParameters.position = { 0.25, 0.1, 0.081 } cParams.position.z = 0.081
checkboxParameters.tooltip = "Spawn investigator cards?" cParams.tooltip = "Are you playing standalone mode? Enabling this will make all 'Campaign Only' weaknesses ineligible when determining the random basic weakness(es)."
checkboxParameters.label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] cParams.label = STANDALONE_TOGGLE_LABELS[standalone]
self.createButton(checkboxParameters) self.createButton(cParams)
end end
-- Create the four deck ID entry fields -- Create the four deck ID entry fields
function makeDeckIdFields() function makeDeckIdFields()
local inputParameters = {} local iParams = {}
-- Parameters common to all entry fields iParams.function_owner = self
inputParameters.function_owner = self iParams.scale = { 0.1, 0.1, 0.1 }
inputParameters.scale = { 0.1, 0.1, 0.1 } iParams.width = INPUT_FIELD_WIDTH
inputParameters.width = INPUT_FIELD_WIDTH iParams.height = INPUT_FIELD_HEIGHT
inputParameters.height = INPUT_FIELD_HEIGHT iParams.font_size = 320
inputParameters.font_size = 320 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!"
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!" iParams.alignment = 3 -- Center
inputParameters.alignment = 3 -- Center iParams.color = FIELD_COLOR
inputParameters.color = FIELD_COLOR iParams.font_color = { 0, 0, 0 }
inputParameters.font_color = { 0, 0, 0 } iParams.validation = 4 -- alphanumeric (to support arkham.build IDs)
inputParameters.validation = 4 -- alphanumeric (to support arkham.build IDs)
-- Green -- Green
inputParameters.input_function = "greenDeckChanged" iParams.input_function = "greenDeckChanged"
inputParameters.position = { -0.166, 0.1, 0.385 } iParams.position = { -0.16, 0.1, 0.385 }
inputParameters.value = greenDeckId iParams.value = greenDeckId
self.createInput(inputParameters) self.createInput(iParams)
-- Red -- Red
inputParameters.input_function = "redDeckChanged" iParams.input_function = "redDeckChanged"
inputParameters.position = { 0.171, 0.1, 0.385 } iParams.position = { 0.165, 0.1, 0.385 }
inputParameters.value = redDeckId iParams.value = redDeckId
self.createInput(inputParameters) self.createInput(iParams)
-- White -- White
inputParameters.input_function = "whiteDeckChanged" iParams.input_function = "whiteDeckChanged"
inputParameters.position = { -0.166, 0.1, 0.474 } iParams.position = { -0.16, 0.1, 0.474 }
inputParameters.value = whiteDeckId iParams.value = whiteDeckId
self.createInput(inputParameters) self.createInput(iParams)
-- Orange -- Orange
inputParameters.input_function = "orangeDeckChanged" iParams.input_function = "orangeDeckChanged"
inputParameters.position = { 0.171, 0.1, 0.474 } iParams.position = { 0.165, 0.1, 0.474 }
inputParameters.value = orangeDeckId iParams.value = orangeDeckId
self.createInput(inputParameters) self.createInput(iParams)
end end
-- Create the Build All button. This is a transparent button which covers the Build All portion of the background graphic -- 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 function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end
-- Event handlers for toggle buttons -- Event handlers for toggle buttons
function publicPrivateChanged() function publishedPrivateChanged()
privateDeck = not privateDeck privateDeck = not privateDeck
self.editButton({ index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] }) self.editButton({ index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] })
end end
@ -182,25 +184,37 @@ function loadUpgradedChanged()
self.editButton({ index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] }) self.editButton({ index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] })
end end
function loadInvestigatorsChanged() function standaloneChanged()
loadInvestigators = not loadInvestigators standalone = not standalone
self.editButton({ index = 2, label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] }) self.editButton({ index = 2, label = STANDALONE_TOGGLE_LABELS[standalone] })
end end
-- start the deck importing process
function loadDecks() 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 if not allCardsBagApi.isIndexReady() then return end
matsWithInvestigator = playermatApi.getUsedMatColors() 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 end
if orangeDeckId ~= nil and orangeDeckId ~= "" then end
buildDeck("Orange", orangeDeckId)
end -- resume the deck importing process
if whiteDeckId ~= nil and whiteDeckId ~= "" then function resumeLoadDecks()
buildDeck("White", whiteDeckId) if co and coroutine.status(co) ~= "dead" then
end local status, err = coroutine.resume(co)
if greenDeckId ~= nil and greenDeckId ~= "" then if not status then error(err) end
buildDeck("Green", greenDeckId)
end end
end end
@ -247,7 +261,7 @@ function buildDeck(playerColor, deckId)
deckId, deckId,
uiState.privateDeck, uiState.privateDeck,
uiState.loadNewest, uiState.loadNewest,
uiState.investigators, uiState.standalone,
loadCards) loadCards)
end end
@ -343,6 +357,7 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo
if (not hadError) then if (not hadError) then
printToAll("Deck loaded successfully!", playerColor) printToAll("Deck loaded successfully!", playerColor)
end end
resumeLoadDecks()
return 1 return 1
end end

View File

@ -370,19 +370,45 @@ end
-- Gets a random basic weakness from the bag. Once a given ID has been returned it will be -- 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 -- 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. -- are rebuilt, which will refresh the list to include all weaknesses.
---@return string: ID of the selected weakness ---@param params table Bundled parameters:
function getRandomWeaknessId() --- count number Number of weaknesses
local availableWeaknesses = buildAvailableWeaknesses() --- restrictions table Additional restrictions:
if #availableWeaknesses > 0 then --- class string Class to restrict weakness to
return availableWeaknesses[math.random(#availableWeaknesses)] --- 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 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 end
-- Constructs a list of available basic weaknesses by starting with the full pool of basic -- 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 -- 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 ---@return table: Array of weakness IDs which are valid to choose from
function buildAvailableWeaknesses(traits) function buildAvailableWeaknesses(restrictions)
restrictions = restrictions or {}
local weaknessesInPlay = {} local weaknessesInPlay = {}
local allObjects = getAllObjects() local allObjects = getAllObjects()
for _, object in ipairs(allObjects) do for _, object in ipairs(allObjects) do
@ -400,10 +426,29 @@ function buildAvailableWeaknesses(traits)
if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then
weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1 weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1
else 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 and 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 "|") -- split the string into separate traits (separated by "|")
local allowedTraits = {} local allowedTraits = {}
for str in traits:gmatch("([^|]+)") do for str in restrictions.traits:gmatch("([^|]+)") do
-- remove dots -- remove dots
str = str:gsub("[%.]", "") str = str:gsub("[%.]", "")
@ -415,15 +460,24 @@ function buildAvailableWeaknesses(traits)
table.insert(allowedTraits, str) table.insert(allowedTraits, str)
end end
local match = false
-- make sure the trait is present on the weakness -- make sure the trait is present on the weakness
local card = cardIdIndex[weaknessId] local card = cardIdIndex[weaknessId]
for _, allowedTrait in ipairs(allowedTraits) do for _, allowedTrait in ipairs(allowedTraits) do
if string.contains(string.lower(card.metadata.traits), allowedTrait) then if string.contains(string.lower(card.metadata.traits), allowedTrait) then
table.insert(availableWeaknesses, weaknessId) match = true
break break
end end
end end
else
if not match then
eligible = false
end
end
-- add weakness to list if eligible
if eligible then
table.insert(availableWeaknesses, weaknessId) table.insert(availableWeaknesses, weaknessId)
end end
end end

View File

@ -28,9 +28,14 @@ do
-- Gets a random basic weakness from the bag. Once a given ID has been returned it -- 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 -- 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. -- or the indexes are rebuilt, which will refresh the list to include all weaknesses.
---@return string: ID of the selected weakness ---@param count number Number of weaknesses
AllCardsBagApi.getRandomWeaknessId = function() ---@param restrictions table Additional restrictions:
return getAllCardsBag().call("getRandomWeaknessId") --- 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
AllCardsBagApi.getRandomWeaknessIds = function(count, restrictions)
return returnCopyOfList(getAllCardsBag().call("getRandomWeaknessIds", {count = count, restrictions = restrictions}))
end end
AllCardsBagApi.isIndexReady = function() AllCardsBagApi.isIndexReady = function()
@ -82,10 +87,13 @@ do
-- Constructs a list of available basic weaknesses by starting with the full pool of basic -- 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 -- 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 ---@return table: Array of weakness IDs which are valid to choose from
AllCardsBagApi.buildAvailableWeaknesses = function(traits) AllCardsBagApi.buildAvailableWeaknesses = function(restrictions)
return returnCopyOfList(getAllCardsBag().call("buildAvailableWeaknesses", traits)) return returnCopyOfList(getAllCardsBag().call("buildAvailableWeaknesses", restrictions))
end end
AllCardsBagApi.getUniqueWeaknesses = function() AllCardsBagApi.getUniqueWeaknesses = function()

View File

@ -782,21 +782,17 @@ function spawnRandomWeakness(_, playerColor, isRightClick)
prepareToPlaceCards() prepareToPlaceCards()
if not isRightClick then if not isRightClick then
local weaknessId = allCardsBagApi.getRandomWeaknessId() local weaknessIds = allCardsBagApi.getRandomWeaknessIds(1)
if weaknessId == nil then if weaknessIds[1] then
broadcastToAll("All basic weaknesses are in play!", { 0.9, 0.2, 0.2 }) spawnSingleWeakness(weaknessIds[1])
else
spawnSingleWeakness(weaknessId)
end end
else else
Player[playerColor].showInputDialog("Specify a trait for the weakness (split multiple eligible traits with '|'):", lastWeaknessTrait, Player[playerColor].showInputDialog("Specify a trait for the weakness (split multiple eligible traits with '|'):", lastWeaknessTrait,
function(text) function(text)
lastWeaknessTrait = text lastWeaknessTrait = text
local availableWeaknesses = allCardsBagApi.buildAvailableWeaknesses(text) local weaknessIds = allCardsBagApi.getRandomWeaknessIds(1, { traits = text })
if #availableWeaknesses > 0 then if weaknessIds[1] then
spawnSingleWeakness(availableWeaknesses[math.random(#availableWeaknesses)]) spawnSingleWeakness(weaknessIds[1])
else
broadcastToAll("No matching weakness available!", { 0.9, 0.2, 0.2 })
end end
end) end)
end end
@ -808,6 +804,6 @@ function spawnSingleWeakness(weaknessId)
name = "randomWeakness", name = "randomWeakness",
cards = { weaknessId }, cards = { weaknessId },
globalPos = self.positionToWorld(startPositions.randomWeakness), globalPos = self.positionToWorld(startPositions.randomWeakness),
rotation = FACE_UP_ROTATION, rotation = FACE_UP_ROTATION
}) })
end end