Merge branch 'main' into starter-decks

This commit is contained in:
Chr1Z93 2024-05-29 10:09:46 +02:00
commit 9e3de1a528
8 changed files with 446 additions and 335 deletions

View File

@ -22,6 +22,13 @@
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/2342503777940937086/92256BDF101E6272AD1E3F5F0043D311DF708F03/",
"WidthScale": 0
},
"CustomUIAssets": [
{
"Name": "OtherCards",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2446096169989812196/B5C491331EB348C261F561DC7A19968ECF9FC74A/"
}
],
"Description": "",
"DragSelectable": true,
"GMNotes": "",
@ -53,5 +60,5 @@
"scaleZ": 10
},
"Value": 0,
"XmlUI": "\u003cInclude src=\"playercards/PlayerCardPanel.xml\"/\u003e"
"XmlUI": ""
}

View File

@ -150,11 +150,8 @@ end
-- Event handlers for deck ID change
function redDeckChanged(_, _, inputValue) redDeckId = inputValue end
function orangeDeckChanged(_, _, inputValue) orangeDeckId = inputValue end
function whiteDeckChanged(_, _, inputValue) whiteDeckId = inputValue end
function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end
-- Event handlers for toggle buttons
@ -174,14 +171,7 @@ function loadInvestigatorsChanged()
end
function loadDecks()
-- testLoadLotsOfDecks()
-- Method in DeckImporterMain, visible due to inclusion
local indexReady = allCardsBagApi.isIndexReady()
if (not indexReady) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return
end
if not allCardsBagApi.isIndexReady() then return end
if (redDeckId ~= nil and redDeckId ~= "") then
buildDeck("Red", redDeckId)
end

View File

@ -1,3 +1,5 @@
local guidReferenceApi = require("core/GUIDReferenceApi")
local cardIdIndex = {}
local classAndLevelIndex = {}
local basicWeaknessList = {}
@ -5,6 +7,7 @@ local uniqueWeaknessList = { }
local cycleIndex = {}
local indexingDone = false
local otherCardsDetected = false
function onLoad()
self.addContextMenuItem("Rebuild Index", startIndexBuild)
@ -18,7 +21,7 @@ end
-- called once indexing is complete it means the hotfix bag has been added
-- later, and we should rebuild the index to integrate the hotfix bag.
function rebuildIndexForHotfix()
if (indexingDone) then
if indexingDone then
startIndexBuild()
end
end
@ -63,6 +66,7 @@ end
function buildIndex()
local cardCount = 0
indexingDone = false
otherCardsDetected = false
-- process the allcardsbag itself
for _, cardData in ipairs(self.getData().ContainedObjects) do
@ -84,8 +88,8 @@ function buildIndex()
end
for _, cardData in ipairs(hotfixData.ContainedObjects) do
-- process containers
if cardData.ContainedObjects then
-- process containers
for _, deepCardData in ipairs(cardData.ContainedObjects) do
addCardToIndex(deepCardData)
cardCount = cardCount + 1
@ -94,8 +98,8 @@ function buildIndex()
coroutine.yield(0)
end
end
-- process single cards
else
-- process single cards
addCardToIndex(cardData)
cardCount = cardCount + 1
if cardCount > 19 then
@ -108,6 +112,7 @@ function buildIndex()
end
buildSupplementalIndexes()
updatePlayerCardPanel()
indexingDone = true
return 1
end
@ -116,15 +121,19 @@ end
---@param cardData table TTS object data for the card
function addCardToIndex(cardData)
-- using the more efficient 'json.parse()' to speed this process up
local status, cardMetadata = pcall(function() json.parse(cardData.GMNotes) end)
local status, cardMetadata = pcall(function() return json.parse(cardData.GMNotes) end)
-- if an error happens, fallback to the regular parser
if status ~= true then
if status ~= true or cardMetadata == nil then
log("Fast parser failed for " .. cardData.Nickname .. ", using old parser instead.")
cardMetadata = JSON.decode(cardData.GMNotes)
end
-- if metadata was not valid JSON or empty, don't add the card
if not cardMetadata then return end
if not cardMetadata == nil then
log("Error parsing " .. cardData.Nickname)
return
end
-- use the ZoopGuid as fallback if no id present
cardMetadata.id = cardMetadata.id or cardMetadata.TtsZoopGuid
@ -138,37 +147,35 @@ function addCardToIndex(cardData)
end
end
-- Creates the supplemental indexes for classes, weaknesses etc.
function buildSupplementalIndexes()
for cardId, card in pairs(cardIdIndex) do
local cardMetadata = card.metadata
-- If the ID key and the metadata ID don't match this is a duplicate card created by an alternate_id, and we should skip it
if cardId == cardMetadata.id then
if cardId == card.metadata.id then
-- Add card to the basic weakness list, if appropriate. Some weaknesses have multiple copies, and are added multiple times
if cardMetadata.weakness then
table.insert(uniqueWeaknessList, cardMetadata.id)
if cardMetadata.basicWeaknessCount ~= nil then
for i = 1, cardMetadata.basicWeaknessCount do
table.insert(basicWeaknessList, cardMetadata.id)
if card.metadata.weakness then
table.insert(uniqueWeaknessList, card.metadata.id)
if card.metadata.basicWeaknessCount ~= nil then
for i = 1, card.metadata.basicWeaknessCount do
table.insert(basicWeaknessList, card.metadata.id)
end
end
end
-- Excludes signature cards (which have no class or level)
if cardMetadata.class ~= nil and cardMetadata.level ~= nil then
local upgradeKey
if cardMetadata.level > 0 then
if card.metadata.class ~= nil and card.metadata.level ~= nil then
local upgradeKey = "-level0"
if card.metadata.level > 0 then
upgradeKey = "-upgrade"
else
upgradeKey = "-level0"
end
-- parse classes (separated by "|") and add the card to the appropriate class and level indices
for str in cardMetadata.class:gmatch("([^|]+)") do
table.insert(classAndLevelIndex[str .. upgradeKey], cardMetadata.id)
for str in card.metadata.class:gmatch("([^|]+)") do
table.insert(classAndLevelIndex[str .. upgradeKey], card.metadata.id)
end
-- add to cycle index
local cycleName = cardMetadata.cycle
local cycleName = card.metadata.cycle
if cycleName ~= nil then
cycleName = string.lower(cycleName)
@ -177,12 +184,17 @@ function buildSupplementalIndexes()
-- override cycle name for night of the zealot
cycleName = cycleName:gsub("the night of the zealot", "core")
else
-- track cards without defined cycle (should only be fan-made cards)
cycleName = "other"
otherCardsDetected = true
end
-- maybe initialize table
if cycleIndex[cycleName] == nil then
cycleIndex[cycleName] = {}
end
table.insert(cycleIndex[cycleName], cardMetadata.id)
end
table.insert(cycleIndex[cycleName], card.metadata.id)
end
end
end
@ -216,64 +228,131 @@ function cardComparator(id1, id2)
end
end
-- inform the player card panel about the presence of other cards (no cycle -> fan-made)
function updatePlayerCardPanel()
local panel = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayerCardPanel")
panel.call("createXML", otherCardsDetected)
end
---@return boolean: If true, the bag is currently not indexing and ready to be accessed
function isIndexReady()
if not indexingDone then
broadcastToAll("Still loading player cards, please try again in a few seconds", { 0.9, 0.2, 0.2 })
end
return indexingDone
end
-- Returns a specific card from the bag, based on ArkhamDB ID
-- Params table:
-- id: String ID of the card to retrieve
-- Return: If the indexes are still being constructed, an empty table is
-- returned. Otherwise, a single table with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
---@param params table ID of the card to retrieve
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a single table with the following fields
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
function getCardById(params)
if (not indexingDone) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return { }
end
if not isIndexReady() then return {} end
return cardIdIndex[params.id]
end
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
-- Params table:
---@param params table
-- class: String class to retrieve ("Guardian", "Seeker", etc)
-- isUpgraded: true for upgraded cards (Level 1-5), false for Level 0
-- Return: If the indexes are still being constructed, returns an empty table.
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a list of tables, each with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
function getCardsByClassAndLevel(params)
if (not indexingDone) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return { }
end
local upgradeKey
if (params.upgraded) then
if not isIndexReady() then return {} end
local upgradeKey = "-level0"
if params.upgraded then
upgradeKey = "-upgrade"
else
upgradeKey = "-level0"
end
return classAndLevelIndex[params.class..upgradeKey];
return classAndLevelIndex[params.class .. upgradeKey]
end
function getCardsByCycle(cycleName)
if (not indexingDone) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return { }
-- Returns a list of cards from the bag matching a cycle
---@param params table
-- cycle: String cycle to retrieve ("The Scarlet Keys" etc.)
-- sortByMetadata: true to sort the table by metadata instead of ID
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a list of tables, each with the following fields
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
function getCardsByCycle(params)
if not isIndexReady() then return {} end
if not params.sortByMetadata then
return cycleIndex[string.lower(params.cycle)]
end
return cycleIndex[string.lower(cycleName)]
-- sort list by metadata (useful for custom cards without proper IDs)
local cardList = {}
for _, id in ipairs(cycleIndex[string.lower(params.cycle)]) do
table.insert(cardList, id)
end
table.sort(cardList, metadataSortFunction)
return cardList
end
-- sorts cards by metadata: class, type, level, name and then description
function metadataSortFunction(id1, id2)
local card1 = cardIdIndex[id1]
local card2 = cardIdIndex[id2]
-- extract class per card
local classValue1 = getClassValueFromString(card1.metadata.class)
local classValue2 = getClassValueFromString(card2.metadata.class)
-- conversion tables to simplify type sorting
local typeConversion = {
Asset = 1,
Event = 2,
Skill = 3
}
if classValue1 ~= classValue2 then
return classValue1 < classValue2
elseif typeConversion[card1.metadata.type] ~= typeConversion[card2.metadata.type] then
return typeConversion[card1.metadata.type] < typeConversion[card2.metadata.type]
elseif card1.metadata.level ~= card2.metadata.level then
return card1.metadata.level < card2.metadata.level
elseif card1.data.Nickname ~= card2.data.Nickname then
return card1.data.Nickname < card2.data.Nickname
else
return card1.data.Description < card2.data.Description
end
end
-- helper function to calculate the class value for sorting from the "|" separated string
function getClassValueFromString(s)
local classValueList = {
Guardian = 1,
Seeker = 2,
Rogue = 3,
Mystic = 4,
Survivor = 5,
Neutral = 6
}
local classValue = 0
for str in s:gmatch("([^|]+)") do
-- this sorts multiclass cards
classValue = classValue * 10 + classValueList[str]
end
return classValue
end
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
-- Parameter array must contain these fields to define the search:
-- name String or string fragment to search for names
-- exact Whether the name match should be exact
-- name: String or string fragment to search for names
-- exact: Whether the name match should be exact
function getCardsByName(params)
local name = params.name
local exact = params.exact
local results = {}
-- Track cards (by ID) that we've added to avoid duplicates that may come from alternate IDs
local addedCards = {}
for _, cardData in pairs(cardIdIndex) do
@ -288,33 +367,30 @@ function getCardsByName(params)
return results
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.
-- Return: String ID of the selected weakness.
-- 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.
---@return string: ID of the selected weakness
function getRandomWeaknessId()
local availableWeaknesses = buildAvailableWeaknesses()
if (#availableWeaknesses > 0) then
if #availableWeaknesses > 0 then
return availableWeaknesses[math.random(#availableWeaknesses)]
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
-- 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()
local weaknessesInPlay = {}
local allObjects = getAllObjects()
for _, object in ipairs(allObjects) do
if (object.name == "Deck") then
if object.type == "Deck" then
for _, cardData in ipairs(object.getData().ContainedObjects) do
local cardMetadata = JSON.decode(cardData.GMNotes)
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
incrementWeaknessCount(weaknessesInPlay, JSON.decode(cardData.GMNotes))
end
elseif (object.name == "Card") then
local cardMetadata = JSON.decode(object.getGMNotes())
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
elseif object.type == "Card" then
incrementWeaknessCount(weaknessesInPlay, JSON.decode(object.getGMNotes()))
end
end
@ -339,8 +415,8 @@ end
-- Helper function that adds one to the table entry for the number of weaknesses in play
function incrementWeaknessCount(table, cardMetadata)
if (isBasicWeakness(cardMetadata)) then
if (table[cardMetadata.id] == nil) then
if isBasicWeakness(cardMetadata) then
if table[cardMetadata.id] == nil then
table[cardMetadata.id] = 1
else
table[cardMetadata.id] = table[cardMetadata.id] + 1

View File

@ -6,22 +6,29 @@ do
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "AllCardsBag")
end
-- internal function to create a copy of the table to avoid operating on variables owned by different objects
local function returnCopyOfList(data)
local copiedList = {}
for _, id in ipairs(data) do
table.insert(copiedList, id)
end
return copiedList
end
-- Returns a specific card from the bag, based on ArkhamDB ID
---@param id table String ID of the card to retrieve
---@return table table
-- If the indexes are still being constructed, an empty table is
-- returned. Otherwise, a single table with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
---@param id string ID of the card to retrieve
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a single table with the following fields
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
AllCardsBagApi.getCardById = function(id)
return getAllCardsBag().call("getCardById", { id = id })
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.
---@return string: ID of the selected weakness.
-- 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.
---@return string: ID of the selected weakness
AllCardsBagApi.getRandomWeaknessId = function()
return getAllCardsBag().call("getRandomWeaknessId")
end
@ -36,15 +43,15 @@ do
-- called once indexing is complete it means the hotfix bag has been added
-- later, and we should rebuild the index to integrate the hotfix bag.
AllCardsBagApi.rebuildIndexForHotfix = function()
return getAllCardsBag().call("rebuildIndexForHotfix")
getAllCardsBag().call("rebuildIndexForHotfix")
end
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
-- Searches the bag for cards which match the given name and returns a list.
-- Note that this is an O(n) search without index support. It may be slow.
---@param name string or string fragment to search for names
---@param exact boolean Whether the name match should be exact
AllCardsBagApi.getCardsByName = function(name, exact)
return getAllCardsBag().call("getCardsByName", {name = name, exact = exact})
return returnCopyOfList(getAllCardsBag().call("getCardsByName", { name = name, exact = exact }))
end
AllCardsBagApi.isBagPresent = function()
@ -53,21 +60,28 @@ do
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
---@param class string class to retrieve ("Guardian", "Seeker", etc)
---@param upgraded boolean true for upgraded cards (Level 1-5), false for Level 0
---@param upgraded boolean True for upgraded cards (Level 1-5), false for Level 0
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a list of tables, each with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded)
return getAllCardsBag().call("getCardsByClassAndLevel", {class = class, upgraded = upgraded})
return returnCopyOfList(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded }))
end
AllCardsBagApi.getCardsByCycle = function(cycle)
return getAllCardsBag().call("getCardsByCycle", cycle)
-- Returns a list of cards from the bag matching a cycle
---@param cycle string Cycle to retrieve ("The Scarlet Keys" etc.)
---@param sortByMetadata boolean If true, sorts the table by metadata instead of ID
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a list of tables, each with the following fields
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
AllCardsBagApi.getCardsByCycle = function(cycle, sortByMetadata)
return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", { cycle = cycle, sortByMetadata = sortByMetadata }))
end
AllCardsBagApi.getUniqueWeaknesses = function()
return getAllCardsBag().call("getUniqueWeaknesses")
return returnCopyOfList(getAllCardsBag().call("getUniqueWeaknesses"))
end
return AllCardsBagApi

View File

@ -26,8 +26,6 @@ local CYCLE_BUTTONS_Z_OFFSET = 0.2665
local STARTER_DECK_MODE_SELECTED_COLOR = { 0.2, 0.2, 0.2, 0.8 }
local TRANSPARENT = { 0, 0, 0, 0 }
local STARTER_DECK_MODE_STARTERS = "starters"
local STARTER_DECK_MODE_CARDS_ONLY = "cards"
local FACE_UP_ROTATION = { x = 0, y = 270, z = 0 }
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180 }
@ -78,7 +76,14 @@ local investigatorPositionShiftCol
local investigatorCardOffset
local investigatorSignatureOffset
local CLASS_LIST = { "Guardian", "Seeker", "Rogue", "Mystic", "Survivor", "Neutral" }
local CLASS_LIST = {
"Guardian",
"Seeker",
"Rogue",
"Mystic",
"Survivor",
"Neutral"
}
local CYCLE_LIST = {
"Core",
"The Dunwich Legacy",
@ -94,8 +99,7 @@ local CYCLE_LIST = {
}
local excludedNonBasicWeaknesses
local starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
local spawnStarterDecks = false
local helpVisibleToPlayers = {}
function onSave()
@ -274,9 +278,8 @@ function createCycleButtons()
if rowCount == 3 then
-- Account for two centered buttons on the final row
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET / 2
--[[ Account for centered button on the final row
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
]]
-- Account for centered button on the final row
-- buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
end
else
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
@ -297,8 +300,6 @@ function createClearButton()
end
function createInvestigatorModeButtons()
local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
self.createButton({
function_owner = self,
click_function = "setCardsOnlyMode",
@ -306,18 +307,18 @@ function createInvestigatorModeButtons()
height = 170,
width = 760,
scale = Vector(0.25, 1, 0.25),
color = starterMode and TRANSPARENT or STARTER_DECK_MODE_SELECTED_COLOR
color = spawnStarterDecks and TRANSPARENT or STARTER_DECK_MODE_SELECTED_COLOR
})
self.createButton({
function_owner = self,
click_function = "setStarterDeckMode",
click_function = "setspawnStarterDecks",
position = Vector(0.66, 0.1, -0.322),
height = 170,
width = 760,
scale = Vector(0.25, 1, 0.25),
color = starterMode and STARTER_DECK_MODE_SELECTED_COLOR or TRANSPARENT
color = spawnStarterDecks and STARTER_DECK_MODE_SELECTED_COLOR or TRANSPARENT
})
local checkX = starterMode and 0.52 or 0.11
local checkX = spawnStarterDecks and 0.52 or 0.11
self.createButton({
function_owner = self,
label = "✓",
@ -325,12 +326,77 @@ function createInvestigatorModeButtons()
position = Vector(checkX, 0.11, -0.317),
height = 0,
width = 0,
scale = Vector(0.3, 1, 0.3),
font_size = 300,
scale = Vector(0.1, 1, 0.1),
font_color = { 0, 0, 0 },
color = { 1, 1, 1 }
})
end
function createXML(showOtherCardsButton)
-- basic XML for the help button
local xmlTable = {
{
tag = "Panel",
attributes = {
active = "false",
id = "helpPanel",
position = "-165 -70 -2",
rotation = "0 0 180",
height = "50",
width = "107",
color = "#00000099"
},
children = {
tag = "Text",
attributes = {
id = "helpText",
rectAlignment = "MiddleCenter",
height = "480",
width = "1000",
scale = "0.1 0.1 1",
fontSize = "66",
color = "#F5F5F5",
backgroundColor = "#FF0000",
alignment = "MiddleLeft",
horizontalOverflow = "wrap",
text = "• Select a group to place cards\n" ..
"• Copy the cards you want for your deck\n" ..
"• Select a new group to clear the placed cards and see new ones\n" ..
"• Clear to remove all cards"
}
}
}
}
-- add the "Additional Cards" button if cards without cycle were detected
if showOtherCardsButton then
local otherCardsButtonXml = {
tag = "Panel",
attributes = {
position = "44.25 65.75 -11",
rotation = "0 0 180",
height = "225",
width = "225",
scale = "0.1 0.1 1",
onClick = "spawnOtherCards"
},
children = {
tag = "Image",
attributes = { image = "OtherCards" }
}
}
table.insert(xmlTable, otherCardsButtonXml)
end
helpVisibleToPlayers = {}
self.UI.setXmlTable(xmlTable)
end
-- click function for the XML button for the additional player cards
function spawnOtherCards()
spawnCycle("Other")
end
function toggleHelp(_, playerColor, _)
if helpVisibleToPlayers[playerColor] then
helpVisibleToPlayers[playerColor] = nil
@ -354,13 +420,13 @@ function updateHelpVisibility()
self.UI.setAttribute("helpPanel", "active", string.len(visibility) > 0)
end
function setStarterDeckMode()
starterDeckMode = STARTER_DECK_MODE_STARTERS
function setspawnStarterDecks()
spawnStarterDecks = true
updateStarterModeButtons()
end
function setCardsOnlyMode()
starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
spawnStarterDecks = false
updateStarterModeButtons()
end
@ -405,14 +471,12 @@ function deleteAll()
spawnBag.recall(true)
end
-- Spawn an investigator group, based on the current UI setting for either investigators or starter
-- decks.
-- Spawn an investigator group, based on the current UI setting for either investigators or starter decks
---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData
function spawnInvestigatorGroup(groupName)
local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
prepareToPlaceCards()
Wait.frames(function()
if starterMode then
if spawnStarterDecks then
spawnStarters(groupName)
else
spawnInvestigators(groupName)
@ -425,7 +489,7 @@ end
---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData
function spawnInvestigators(groupName)
if INVESTIGATOR_GROUPS[groupName] == nil then
printToAll("No " .. groupName .. " data yet")
printToAll("No investigator data for " .. groupName .. " yet")
return
end
@ -434,7 +498,7 @@ function spawnInvestigators(groupName)
local investigatorCount = #INVESTIGATOR_GROUPS[groupName]
local position = getInvestigatorRowStartPos(investigatorCount, row)
for i, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do
for _, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do
for _, spawnSpec in ipairs(buildInvestigatorSpawnSpec(investigatorName, INVESTIGATORS[investigatorName], position)) do
spawnBag.spawn(spawnSpec)
end
@ -547,6 +611,7 @@ function spawnStarterDeck(investigatorName, investigatorData, position)
})
end)
end
-- Clears the currently placed cards, then places cards for the given class and level spread
---@param cardClass string Class to place ("Guardian", "Seeker", etc)
---@param isUpgraded boolean If true, spawn the Level 1-5 cards. Otherwise, Level 0.
@ -559,11 +624,8 @@ end
---@param cardClass string Class to place ("Guardian", "Seeker", etc)
---@param isUpgraded boolean If true, spawn the Level 1-5 cards. Otherwise, Level 0.
function placeClassCards(cardClass, isUpgraded)
local indexReady = allCardsBagApi.isIndexReady()
if (not indexReady) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return
end
if not allCardsBagApi.isIndexReady() then return end
local cardIdList = allCardsBagApi.getCardsByClassAndLevel(cardClass, isUpgraded)
local skillList = {}
@ -617,21 +679,20 @@ end
-- Spawns the investigator sets and all cards for the given cycle
---@param cycle string Name of a cycle, should match the standard used in card metadata
function spawnCycle(cycle)
if not allCardsBagApi.isIndexReady() then return end
prepareToPlaceCards()
spawnInvestigators(cycle)
local indexReady = allCardsBagApi.isIndexReady()
if (not indexReady) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return
end
local cycleCardList = allCardsBagApi.getCardsByCycle(cycle)
local copiedList = { }
for i, id in ipairs(cycleCardList) do
copiedList[i] = id
-- sort custom cards
local sortByMetadata = false
if cycle == "Other" then
sortByMetadata = true
end
spawnBag.spawn({
name = "cycle" .. cycle,
cards = copiedList,
cards = allCardsBagApi.getCardsByCycle(cycle, sortByMetadata),
globalPos = self.positionToWorld(startPositions.cycle),
rotation = FACE_UP_ROTATION,
spread = true,
@ -671,16 +732,13 @@ end
-- Clears the current cards, and places all basic weaknesses on the table.
function spawnWeaknesses()
if not allCardsBagApi.isIndexReady() then return end
prepareToPlaceCards()
local indexReady = allCardsBagApi.isIndexReady()
if (not indexReady) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return
end
local weaknessIdList = allCardsBagApi.getUniqueWeaknesses()
local basicWeaknessList = {}
local otherWeaknessList = {}
for i, id in ipairs(weaknessIdList) do
for _, id in ipairs(allCardsBagApi.getUniqueWeaknesses()) do
local cardMetadata = allCardsBagApi.getCardById(id).metadata
if cardMetadata.basicWeaknessCount ~= nil and cardMetadata.basicWeaknessCount > 0 then
table.insert(basicWeaknessList, id)

View File

@ -16,7 +16,7 @@ Spawner = { }
---@param sort boolean True if this list of cards should be sorted before spawning
---@param callback? function Callback to be called after the card/deck spawns.
Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
if (sort) then
if sort then
table.sort(cardList, Spawner.cardComparator)
end
@ -25,9 +25,9 @@ Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
local investigatorCards = { }
for _, card in ipairs(cardList) do
if (card.metadata.type == "Investigator") then
if card.metadata.type == "Investigator" then
table.insert(investigatorCards, card)
elseif (card.metadata.type == "Minicard") then
elseif card.metadata.type == "Minicard" then
table.insert(miniCards, card)
else
table.insert(standardCards, card)
@ -46,7 +46,7 @@ Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
end
Spawner.spawnCardSpread = function(cardList, startPos, maxCols, rot, sort, callback)
if (sort) then
if sort then
table.sort(cardList, Spawner.cardComparator)
end
@ -201,7 +201,7 @@ end
---@return string id >= startId
Spawner.findNextAvailableId = function(objectTable, startId)
local id = startId
while (objectTable[id] ~= nil) do
while objectTable[id] ~= nil do
id = tostring(tonumber(id) + 1)
end
return id

View File

@ -81,24 +81,16 @@ do
-- Places the given spawnSpec on the table. See comment at the start of the file for spawnSpec table data and examples
SpawnBag.spawn = function(spawnSpec)
-- Limit to one placement at a time
if (placedSpecs[spawnSpec.name]) then
return
end
if (spawnSpec == nil) then
-- TODO: error here
return
end
if placedSpecs[spawnSpec.name] or spawnSpec == nil then return end
local cardsToSpawn = {}
local cardList = spawnSpec.cards
for _, cardId in ipairs(cardList) do
local cardData = allCardsBagApi.getCardById(cardId)
if (cardData ~= nil) then
table.insert(cardsToSpawn, cardData)
else
-- TODO: error here
for _, cardId in ipairs(spawnSpec.cards) do
local card = allCardsBagApi.getCardById(cardId)
if card ~= nil then
table.insert(cardsToSpawn, card)
end
end
if (spawnSpec.spread) then
if spawnSpec.spread then
Spawner.spawnCardSpread(cardsToSpawn, spawnSpec.globalPos, spawnSpec.spreadCols or 9999, spawnSpec.rotation, false, internal.recordPlacedObject)
else
-- TTS decks come out in reverse order of the cards, reverse the list so the input order stays
@ -120,14 +112,13 @@ do
internal.recallSpawned()
end
-- We've recalled everything we can, some cards may have been moved out of the
-- card area. Just reset at this point.
-- We've recalled everything we can, some cards may have been moved out of the card area. Just reset at this point.
placedSpecs = {}
placedObjectGuids = {}
recallZone = nil
end
-- Deleted all spawned cards.
-- Delete all spawned cards
internal.deleteSpawned = function()
for guid, _ in pairs(placedObjectGuids) do
local obj = getObjectFromGUID(guid)
@ -140,7 +131,7 @@ do
end
end
-- Recalls spawned cards with a fake bag that replicates the memory bag recall style.
-- Recalls spawned cards with a fake bag that replicates the memory bag recall style
internal.recallSpawned = function()
local trash = spawnObjectData({ data = RECALL_BAG, position = self.getPosition() })
for guid, _ in pairs(placedObjectGuids) do
@ -156,9 +147,7 @@ do
trash.destruct()
end
-- Callback for when an object has been spawned. Tracks the object for later recall and updates the
-- recall zone.
-- Callback for when an object has been spawned. Tracks the object for later recall and updates the recall zone.
internal.recordPlacedObject = function(spawned)
placedObjectGuids[spawned.getGUID()] = true
internal.expandRecallZone(spawned)
@ -174,21 +163,22 @@ do
recallZone.upperLeft = { x = pos.x + RECALL_BUFFER_X, z = pos.z + RECALL_BUFFER_Z }
recallZone.lowerRight = { x = pos.x - RECALL_BUFFER_X, z = pos.z - RECALL_BUFFER_Z }
return
else
if (pos.x > recallZone.upperLeft.x) then
end
if pos.x > recallZone.upperLeft.x then
recallZone.upperLeft.x = pos.x + RECALL_BUFFER_X
end
if (pos.x < recallZone.lowerRight.x) then
if pos.x < recallZone.lowerRight.x then
recallZone.lowerRight.x = pos.x - RECALL_BUFFER_X
end
if (pos.z > recallZone.upperLeft.z) then
if pos.z > recallZone.upperLeft.z then
recallZone.upperLeft.z = pos.z + RECALL_BUFFER_Z
end
if (pos.z < recallZone.lowerRight.z) then
if pos.z < recallZone.lowerRight.z then
recallZone.lowerRight.z = pos.z - RECALL_BUFFER_Z
end
end
if (SHOW_RECALL_ZONE) then
if SHOW_RECALL_ZONE then
local y = 1.5
local thick = 0.05
Global.setVectorLines({

View File

@ -1,24 +0,0 @@
<Panel
active="false"
id="helpPanel"
position="-165 -60 -2"
rotation="0 0 180"
height="55"
width="107"
color="#00000099">
<Text
id="helpText"
rectAlignment="MiddleCenter"
height="490"
width="1000"
scale="0.1 0.1 1"
fontSize="66"
color="#F5F5F5"
backgroundColor="#FF0000"
alignment="UpperLeft"
horizontalOverflow="wrap">
• Select a group to place cards
• Copy the cards you want for your deck
• Select a new group to clear the placed cards and see new ones
• Clear to remove all cards</Text>
</Panel>