From 5b38ea1c0200df9cfa83dd61ae12c4bfe3f6641e Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Sat, 25 May 2024 00:40:18 +0200 Subject: [PATCH] added sorting for custom cards --- src/playercards/AllCardsBag.ttslua | 111 +++++++++++++++++++------ src/playercards/AllCardsBagApi.ttslua | 4 +- src/playercards/PlayerCardPanel.ttslua | 26 +++++- 3 files changed, 111 insertions(+), 30 deletions(-) diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index 2355b2f4..95175ccf 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -12,9 +12,9 @@ function onLoad() Wait.frames(startIndexBuild, 30) end --- Called by Hotfix bags when they load. If we are still loading indexes, then +-- Called by Hotfix bags when they load. If we are still loading indexes, then -- the all cards and hotfix bags are being loaded together, and we can ignore --- this call as the hotfix will be included in the initial indexing. If it is +-- this call as the hotfix will be included in the initial indexing. If it is -- 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() @@ -170,12 +170,16 @@ function buildSupplementalIndexes() -- override cycle name for night of the zealot cycleName = cycleName:gsub("the night of the zealot", "core") - - if cycleIndex[cycleName] == nil then - cycleIndex[cycleName] = { } - end - table.insert(cycleIndex[cycleName], cardMetadata.id) + else + -- track cards without defined cycle (should only be fan-made cards) + cycleName = "other" end + + -- maybe initialize table + if cycleIndex[cycleName] == nil then + cycleIndex[cycleName] = { } + end + table.insert(cycleIndex[cycleName], cardMetadata.id) end end end @@ -220,7 +224,7 @@ end -- 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 +-- returned. Otherwise, a single table with the following fields -- cardData: TTS object data, suitable for spawning the card -- cardMetadata: Table of parsed metadata function getCardById(params) @@ -240,21 +244,80 @@ function getCardsByClassAndLevel(params) if not isIndexReady() then return {} end local upgradeKey - if (params.upgraded) then + if params.upgraded then upgradeKey = "-upgrade" else upgradeKey = "-level0" end - return classAndLevelIndex[params.class..upgradeKey]; + return classAndLevelIndex[params.class..upgradeKey] end -function getCardsByCycle(cycleName) +function getCardsByCycle(params) if not isIndexReady() then return {} end - return cycleIndex[string.lower(cycleName)] + + if not params.sortByMetadata then + return cycleIndex[string.lower(params.cycle)] + end + + -- 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 --- 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. +-- 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 @@ -276,14 +339,14 @@ function getCardsByName(params) return results end --- Gets a random basic weakness from the bag. Once a given ID has been returned +-- 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 + local availableWeaknesses = buildAvailableWeaknesses() + if #availableWeaknesses > 0 then return availableWeaknesses[math.random(#availableWeaknesses)] end end @@ -295,14 +358,12 @@ 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 @@ -327,8 +388,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 diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index c7baf29f..2d6801d1 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -70,8 +70,8 @@ do return returnCopyOfList(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded })) end - AllCardsBagApi.getCardsByCycle = function(cycle) - return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", cycle)) + AllCardsBagApi.getCardsByCycle = function(cycle, sortByMetadata) + return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", { cycle = cycle, sortByMetadata = sortByMetadata })) end AllCardsBagApi.getUniqueWeaknesses = function() diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua index f84bb340..90b2e1b6 100644 --- a/src/playercards/PlayerCardPanel.ttslua +++ b/src/playercards/PlayerCardPanel.ttslua @@ -425,7 +425,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 +434,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 @@ -625,9 +625,15 @@ function spawnCycle(cycle) prepareToPlaceCards() spawnInvestigators(cycle) + -- sort custom cards + local sortByMetadata = false + if cycle == "Other" then + sortByMetadata = true + end + spawnBag.spawn({ name = "cycle" .. cycle, - cards = allCardsBagApi.getCardsByCycle(cycle), + cards = allCardsBagApi.getCardsByCycle(cycle, sortByMetadata), globalPos = self.positionToWorld(startPositions.cycle), rotation = FACE_UP_ROTATION, spread = true, @@ -635,6 +641,20 @@ function spawnCycle(cycle) }) end +-- Comparison function used to sort the class card bag indexes. Sorts by card level, then name, then subname. +function cardComparator(id1, id2) + local card1 = cardIdIndex[id1] + local card2 = cardIdIndex[id2] + + if 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 + function spawnBonded() prepareToPlaceCards() spawnBag.spawn({