From bfb504fc35d648183c910ed35e6dd6dedf44a313 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 09:32:26 +0200 Subject: [PATCH 01/21] Added regular JSON parser as fallback --- src/playercards/AllCardsBag.ttslua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index c141564b..413c906d 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -116,7 +116,14 @@ 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 cardMetadata = json.parse(cardData.GMNotes) + local status, cardMetadata = pcall(function() json.parse(cardData.GMNotes) end) + + -- if an error happens, fallback to the regular parser + if status ~= true then + 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 -- use the ZoopGuid as fallback if no id present From b2bd2fe8fcd4599e5d8c352ebb18e3a928ba47b2 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 10:42:43 +0200 Subject: [PATCH 02/21] handling for empty or almost empty bag --- src/accessories/TokenArranger.ttslua | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/accessories/TokenArranger.ttslua b/src/accessories/TokenArranger.ttslua index 159a1a4b..bdea64d5 100644 --- a/src/accessories/TokenArranger.ttslua +++ b/src/accessories/TokenArranger.ttslua @@ -302,12 +302,14 @@ function layout(_, _, isRightClick) end -- clone tokens from chaos bag (default position above trash can) - local rawData = chaosBag.getData().ContainedObjects + local rawData = chaosBag.getData().ContainedObjects or {} -- optionally get the data for tokens in play if includeDrawnTokens then for _, token in pairs(chaosBagApi.getTokensInPlay()) do - if token ~= nil then table.insert(rawData, token.getData()) end + if token ~= nil then + table.insert(rawData, token.getData()) + end end end @@ -335,8 +337,15 @@ function layout(_, _, isRightClick) end end - -- sort table by value (symbols last if same value) - table.sort(data, tokenValueComparator) + -- handling for near empty chaos bag + if #data == 0 then + -- small delay to limit update calls + Wait.time(function() updating = false end, 0.1) + return + elseif #data > 1 then + -- sort table by value (symbols last if same value) + table.sort(data, tokenValueComparator) + end -- laying out the tokens local pos = self.getPosition() + Vector(3.55, -0.05, -3.95) @@ -349,7 +358,7 @@ function layout(_, _, isRightClick) local valueCount = 1 local tokenName = false - for i, item in ipairs(data) do + for _, item in ipairs(data) do -- this is true for the first token in a new row if item.value ~= currentValue then if percentage then @@ -380,18 +389,14 @@ function layout(_, _, isRightClick) createPercentageButton(tokenCount, valueCount, tokenName) end - -- introducing a small delay to limit update calls + -- small delay to limit update calls Wait.time(function() updating = false end, 0.1) end -- called from outside to set default values for tokens function onTokenDataChanged(parameters) - local tokenData = parameters.tokenData or {} - local currentScenario = parameters.currentScenario or "" - local useFrontData = parameters.useFrontData - -- update token precedence - for key, table in pairs(tokenData) do + for key, table in pairs(parameters.tokenData or {}) do local modifier = table.modifier if modifier == -999 then modifier = 0 end tokenPrecedence[key][1] = modifier From c61d6dc6d5e79122d14ffa1206129ac3ae54fba5 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 11:23:11 +0200 Subject: [PATCH 03/21] Updated drawing tool --- modsettings/TabStates.json | 2 +- objects/DrawingTool.280086.json | 2 +- src/util/ConnectionDrawingTool.ttslua | 91 +++++++++++++++------------ 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/modsettings/TabStates.json b/modsettings/TabStates.json index b3158b44..b072a08a 100644 --- a/modsettings/TabStates.json +++ b/modsettings/TabStates.json @@ -1,6 +1,6 @@ { "1": { - "body": "Welcome to Arkham Horror LCG - Super Complete Edition!\n\nMake sure to take the tour that can be started with the token in the middle of the main playarea. Some basic notes:\n\nDECKBUILDING\n- All currently existing investigators and player cards are accessible via the player card panel in the upper left corner of the table.\n\n- On the leftside underneath the Investigators, you will find the ArkhamDB Deckimporter. Insert your deck ID and it will build the deck automatically for you.\n\nSCENARIOS & SETUP\n- Arkham Horror LCG comes with a core campaign (Night of the Zealot) and several expansions. Within each box you will find all the cards required for each scenario setup, as well as a the official campaign guide PDF.\n\n2. Each scenario is setup differently, and while some of the work has been prepared beforehand (such as building encounter decks), you will have to refer to the Campaign Guide for specific instructions on how to set up each scenario.\n\nINVESTIGATOR PLAYMAT AND GAMEPLAY\n- Playermats are scripted to automate most of the gameplay for you.", + "body": "Welcome to Arkham Horror LCG - Super Complete Edition!\n\nMake sure to take the tour that can be started with the token in the middle of the main playarea. Some basic notes:\n\nDECKBUILDING\n- All currently existing investigators and player cards are accessible via the player card panel in the upper left corner of the table.\n\n- On the leftside underneath the Investigators, you will find the ArkhamDB Deckimporter. Insert your deck ID and it will build the deck automatically for you.\n\nSCENARIOS \u0026 SETUP\n- Arkham Horror LCG comes with a core campaign (Night of the Zealot) and several expansions. Within each box you will find all the cards required for each scenario setup, as well as a the official campaign guide PDF.\n\n2. Each scenario is setup differently, and while some of the work has been prepared beforehand (such as building encounter decks), you will have to refer to the Campaign Guide for specific instructions on how to set up each scenario.\n\nINVESTIGATOR PLAYMAT AND GAMEPLAY\n- Playermats are scripted to automate most of the gameplay for you.", "color": "Grey", "id": 1, "title": "Basic Intro", diff --git a/objects/DrawingTool.280086.json b/objects/DrawingTool.280086.json index c93d123c..6a968693 100644 --- a/objects/DrawingTool.280086.json +++ b/objects/DrawingTool.280086.json @@ -34,7 +34,7 @@ "LayoutGroupSortIndex": 0, "Locked": true, "LuaScript": "require(\"util/ConnectionDrawingTool\")", - "LuaScriptState": "{\"e8e04b\":[]}", + "LuaScriptState": "{\"connections\":[]}", "MeasureMovement": false, "Name": "Custom_Token", "Nickname": "Drawing Tool", diff --git a/src/util/ConnectionDrawingTool.ttslua b/src/util/ConnectionDrawingTool.ttslua index 89fdf225..8bff5866 100644 --- a/src/util/ConnectionDrawingTool.ttslua +++ b/src/util/ConnectionDrawingTool.ttslua @@ -1,87 +1,98 @@ -local lines = {} +local connections = {} --- save "lines" to be able to remove them after loading function onSave() - return JSON.encode(lines) + return JSON.encode({ connections = connections }) end function onLoad(savedData) if savedData and savedData ~= "" then - lines = JSON.decode(savedData) or {} + local loadedData = JSON.decode(savedData) or {} + connections = loadedData.connections + processLines() end + + addHotkey("Drawing Tool: Reset", function() connections = {} processLines() end) + addHotkey("Drawing Tool: Redraw", processLines) end --- create timer when numpad 0 is pressed function onScriptingButtonDown(index, player_color) if index ~= 10 then return end - TimerID = Wait.time(function() draw_from(Player[player_color]) end, 1) + + Timer.create { + identifier = player_color .. "_draw_from", + function_name = "draw_from", + parameters = { Player[player_color] }, + delay = 1 + } end --- called for long press of numpad 0, draws lines from hovered object to selected objects function draw_from(player) local source = player.getHoverObject() if not source then return end for _, item in ipairs(player.getSelectedObjects()) do - if item.getGUID() ~= source.getGUID() then + if item ~= source then if item.getGUID() > source.getGUID() then - draw_with_pair(item, source) + addPair(item, source) else - draw_with_pair(source, item) + addPair(source, item) end end end - process_lines() + processLines() end --- general drawing of all lines between selected objects function onScriptingButtonUp(index, player_color) if index ~= 10 then return end + -- returns true only if there is a timer to cancel. If this is false then we've waited longer than a second. - if not Wait.stop(TimerID) then return end + if not Timer.destroy(player_color .. "_draw_from") then return end local items = Player[player_color].getSelectedObjects() - if #items < 2 then - broadcastToColor("You must have at least two items selected (currently: " .. #items .. ").", player_color, "Red") - return - end + if #items < 2 then return end table.sort(items, function(a, b) return a.getGUID() > b.getGUID() end) - for f = 1, #items - 1 do - for s = f + 1, #items do - draw_with_pair(items[f], items[s]) + for i = 1, #items do + local first = items[i] + + for j = i, #items do + local second = items[j] + addPair(first, second) end end - process_lines() + processLines() end --- adds two objects to table of vector lines -function draw_with_pair(first, second) - local guid_first = first.getGUID() - local guid_second = second.getGUID() +function addPair(first, second) + local first_guid = first.getGUID() + local second_guid = second.getGUID() - if Global.getVectorLines() == nil then lines = {} end - if not lines[guid_first] then lines[guid_first] = {} end - - if lines[guid_first][guid_second] then - lines[guid_first][guid_second] = nil - else - lines[guid_first][guid_second] = { points = { first.getPosition(), second.getPosition() }, color = "White" } - end + if not connections[first_guid] then connections[first_guid] = {} end + connections[first_guid][second_guid] = not connections[first_guid][second_guid] end --- updates the global vector lines based on "lines" -function process_lines() - local drawing = {} +function processLines() + local lines = {} - for _, first in pairs(lines) do - for _, data in pairs(first) do - table.insert(drawing, data) + for source_guid, target_guids in pairs(connections) do + local source = getObjectFromGUID(source_guid) + + for target_guid, exists in pairs(target_guids) do + if exists then + local target = getObjectFromGUID(target_guid) + + if source and target then + table.insert(lines, { + points = { source.getPosition(), target.getPosition() }, + color = Color.White + }) + end + end end end - Global.setVectorLines(drawing) + Global.setVectorLines(lines) end From a62e08c65b0949c1b615f67cd507200578938990 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 11:35:07 +0200 Subject: [PATCH 04/21] bugfix --- config.json | 2 +- src/util/ConnectionDrawingTool.ttslua | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config.json b/config.json index 6943f0ee..993dbc4a 100644 --- a/config.json +++ b/config.json @@ -20,6 +20,7 @@ "Note": "", "ObjectStates_order": [ "GUIDReferenceHandler.123456", + "GameKeyHandler.fce69c", "TokenSpawnTracker.e3ffc9", "HandTrigger.5fe087", "HandTrigger.be2f17", @@ -196,7 +197,6 @@ "Fan-MadeExpansionOverview.de7cae", "OptionPanelSource.830bd0", "SoundCube.3c988f", - "GameKeyHandler.fce69c", "TokenSpawningReference.f8b3a7", "3DText.d628cc", "NavigationOverlayHandler.797ede", diff --git a/src/util/ConnectionDrawingTool.ttslua b/src/util/ConnectionDrawingTool.ttslua index 8bff5866..33d8f929 100644 --- a/src/util/ConnectionDrawingTool.ttslua +++ b/src/util/ConnectionDrawingTool.ttslua @@ -15,22 +15,22 @@ function onLoad(savedData) addHotkey("Drawing Tool: Redraw", processLines) end -function onScriptingButtonDown(index, player_color) +function onScriptingButtonDown(index, playerColor) if index ~= 10 then return end Timer.create { - identifier = player_color .. "_draw_from", + identifier = playerColor .. "_draw_from", function_name = "draw_from", - parameters = { Player[player_color] }, + parameters = { player = Player[playerColor] }, delay = 1 } end -function draw_from(player) - local source = player.getHoverObject() +function draw_from(params) + local source = params.player.getHoverObject() if not source then return end - for _, item in ipairs(player.getSelectedObjects()) do + for _, item in ipairs(params.player.getSelectedObjects()) do if item ~= source then if item.getGUID() > source.getGUID() then addPair(item, source) @@ -43,13 +43,13 @@ function draw_from(player) processLines() end -function onScriptingButtonUp(index, player_color) +function onScriptingButtonUp(index, playerColor) if index ~= 10 then return end -- returns true only if there is a timer to cancel. If this is false then we've waited longer than a second. - if not Timer.destroy(player_color .. "_draw_from") then return end + if not Timer.destroy(playerColor .. "_draw_from") then return end - local items = Player[player_color].getSelectedObjects() + local items = Player[playerColor].getSelectedObjects() if #items < 2 then return end table.sort(items, function(a, b) return a.getGUID() > b.getGUID() end) From ea2d57eccb0d03bcfd747027eb68e88959c4a318 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 12:44:55 +0200 Subject: [PATCH 05/21] cleaner labels --- objects/TokenArranger.022907.luascriptstate | 2 +- src/accessories/TokenArranger.ttslua | 46 +++++++++++---------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/objects/TokenArranger.022907.luascriptstate b/objects/TokenArranger.022907.luascriptstate index 919c5c6c..0c189c64 100644 --- a/objects/TokenArranger.022907.luascriptstate +++ b/objects/TokenArranger.022907.luascriptstate @@ -1 +1 @@ -{"includeDrawnTokens":true,"percentage":false,"tokenPrecedence":{"":[0,11],"Auto-fail":[-100,7],"Bless":[101,8],"Cultist":[-2,4],"Curse":[-101,9],"Elder Sign":[100,2],"Elder Thing":[-4,6],"Frost":[-99,10],"Skull":[-1,3],"Tablet":[-3,5]}} +{"includeDrawnTokens":true,"percentage":false,"tokenPrecedence":{"":[0,11],"Auto-fail":[-100,7],"Bless":[110,8],"Cultist":[-2,4],"Curse":[-110,9],"Elder Sign":[100,2],"Elder Thing":[-4,6],"Frost":[-105,10],"Skull":[-1,3],"Tablet":[-3,5]}} diff --git a/src/accessories/TokenArranger.ttslua b/src/accessories/TokenArranger.ttslua index bdea64d5..a5a8ef5b 100644 --- a/src/accessories/TokenArranger.ttslua +++ b/src/accessories/TokenArranger.ttslua @@ -12,16 +12,18 @@ buttonParameters.height = 325 local inputParameters = {} inputParameters.function_owner = self -inputParameters.font_size = 100 -inputParameters.width = 250 -inputParameters.height = inputParameters.font_size + 23 +inputParameters.font_size = 200 +inputParameters.width = 500 +inputParameters.height = inputParameters.font_size + 46 inputParameters.alignment = 3 inputParameters.validation = 2 inputParameters.tab = 2 +inputParameters.scale = { 0.5, 0.5, 0.5 } local percentageLabel = {} percentageLabel.function_owner = self percentageLabel.click_function = "none" +percentageLabel.font_size = 200 percentageLabel.width = 0 percentageLabel.height = 0 @@ -69,13 +71,13 @@ function onLoad(savedData) end createButtonsAndInputs() - + -- maybe trigger layout() to draw percentage buttons local objList = getObjectsWithTag("tempToken") if #objList > 0 then Wait.time(layout, 0.5) end - + -- context menu items self.addContextMenuItem("Load default values", function() loadDefaultValues() @@ -153,10 +155,10 @@ function loadDefaultValues() ["Tablet"] = { -3, 5}, ["Elder Thing"] = { -4, 6}, ["Auto-fail"] = { -100, 7}, - ["Bless"] = { 101, 8}, - ["Curse"] = { -101, 9}, - ["Frost"] = { -99, 10}, - [""] = { 0, 11} + ["Bless"] = { 110, 8}, + ["Curse"] = { -110, 9}, + ["Frost"] = { -105, 10}, + [""] = { 0, 11} } end @@ -195,9 +197,11 @@ function createButtonsAndInputs() click_function = "layout", tooltip = "Left-Click: Update!\nRight-Click: Hide Tokens!", position = { 0.725, 0.1, 2.025 }, + scale = { 0.5, 0.5, 0.5 }, color = { 1, 1, 1 }, - width = 675, - height = 175 + font_size = 200, + width = 1350, + height = 325 }) end @@ -238,14 +242,14 @@ function deleteCopiedTokens() end -- creates buttons as labels as display for percentage values -function createPercentageButton(tokenCount, valueCount, tokenName) - local startPos = Vector(2.3, -0.04, 0.875 * valueCount) +function createPercentageButton(tokenCount, rowCount, tokenName) + local startPos = Vector(2.3, -0.04, 0.875 * rowCount) if percentage == "cumulative" then - percentageLabel.scale = { 1.5, 1.5, 1.5 } + percentageLabel.scale = { 0.75, 0.75, 0.75 } percentageLabel.position = startPos - Vector(0, 0, 2.85) else - percentageLabel.scale = { 2, 2, 2 } + percentageLabel.scale = { 1, 1, 1 } percentageLabel.position = startPos - Vector(0, 0, 2.675) end @@ -254,8 +258,8 @@ function createPercentageButton(tokenCount, valueCount, tokenName) percentageLabel.font_color = { 0.35, 0.71, 0.85 } elseif tokenName == "Auto-fail" then percentageLabel.font_color = { 0.86, 0.1, 0.1 } - -- check if the tokenName contains letters (e.g. symbol token) elseif string.match(tokenName, "%a") ~= nil then + -- tokenName contains letters (e.g. symbol token) percentageLabel.font_color = { 0.68, 0.53, 0.86 } else percentageLabel.font_color = { 0.85, 0.67, 0.33 } @@ -355,21 +359,21 @@ function layout(_, _, isRightClick) local rotation = self.getRotation() local currentValue = data[1].value local tokenCount = { row = 0, sum = 0, total = #data } - local valueCount = 1 - local tokenName = false + local rowCount = 1 + local tokenName for _, item in ipairs(data) do -- this is true for the first token in a new row if item.value ~= currentValue then if percentage then tokenCount.sum = tokenCount.sum + tokenCount.row - createPercentageButton(tokenCount, valueCount, tokenName) + createPercentageButton(tokenCount, rowCount, tokenName) end location.x = location.x - 1.75 location.z = pos.z currentValue = item.value - valueCount = valueCount + 1 + rowCount = rowCount + 1 tokenCount.row = 0 end @@ -386,7 +390,7 @@ function layout(_, _, isRightClick) -- this is repeated to create the button for the last token if percentage then tokenCount.sum = tokenCount.sum + tokenCount.row - createPercentageButton(tokenCount, valueCount, tokenName) + createPercentageButton(tokenCount, rowCount, tokenName) end -- small delay to limit update calls From 8d8662209befe4321c09e7c7ec5654f4fff94fbf Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 13:26:01 +0200 Subject: [PATCH 06/21] added playermat slot symbol saving to campaign exporter --- .../CampaignImporterExporter.ttslua | 20 ++++++++++++++++--- src/playermat/Playmat.ttslua | 4 ++-- src/playermat/PlaymatApi.ttslua | 19 ++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/accessories/CampaignImporterExporter.ttslua b/src/accessories/CampaignImporterExporter.ttslua index 380974a6..4d9a51b5 100644 --- a/src/accessories/CampaignImporterExporter.ttslua +++ b/src/accessories/CampaignImporterExporter.ttslua @@ -161,12 +161,12 @@ function restoreCampaignData(importData, coin) deckImporterApi.setUiState(importData["decks"]) end - -- maybe set campaign guide page - if importData["guide"] then + -- maybe set campaign guide page (unless it was on the first page) + if importData["guide"] and importData["guide"] ~= 0 then local campaignGuide = findUniqueObjectWithTag("CampaignGuide") if campaignGuide then Wait.condition( - -- Called after the condition function returns true + -- Called after the condition function returns true function() printToAll("Campaign Guide import successful!") end, -- Condition function that is called continuously until it returns true or timeout is reached function() return campaignGuide.Book.setPage(importData["guide"]) end, @@ -190,6 +190,13 @@ function restoreCampaignData(importData, coin) playAreaApi.updateSurface(importData["playarea"]) playAreaApi.setInvestigatorCount(importData["clueCount"]) + -- restore Playmat slots + if importData["slotData"] then + for matColor, slotData in pairs(importData["slotData"]) do + playmatApi.loadSlotData(matColor, slotData) + end + end + coin.destruct() broadcastToAll("Campaign successfully imported!", "Green") end @@ -265,6 +272,13 @@ function createCampaignToken(_, playerColor, _) table.insert(campaignTokenData.ContainedObjects, indexData) end + -- get the slot symbol data for each playmat (use GUIDReferenceApi to only get this for existing playmats) + campaignData.slotData = {} + for matColor, _ in pairs(guidReferenceApi.getObjectsByType("Playermat")) do + local slotData = playmatApi.getSlotData(matColor) + campaignData.slotData[matColor] = slotData + end + -- finish the data for the campaign token campaignTokenData.GMNotes = JSON.encode(campaignData) campaignTokenData.Nickname = campaignBox.getName() .. os.date(" %b %d") .. " Save" diff --git a/src/playermat/Playmat.ttslua b/src/playermat/Playmat.ttslua index b117134d..1403394b 100644 --- a/src/playermat/Playmat.ttslua +++ b/src/playermat/Playmat.ttslua @@ -72,8 +72,8 @@ local slotNameToChar = { ["Tarot"] = "A" } --- slot symbol for the respective slot (from top left to bottom right) -local slotData = {} +-- slot symbol for the respective slot (from top left to bottom right) - intentionally global! +slotData = {} local defaultSlotData = { -- 1st row "any", "any", "any", "Tarot", "Hand (left)", "Hand (right)", "Ally", diff --git a/src/playermat/PlaymatApi.ttslua b/src/playermat/PlaymatApi.ttslua index 17af3a37..d5aa0edd 100644 --- a/src/playermat/PlaymatApi.ttslua +++ b/src/playermat/PlaymatApi.ttslua @@ -55,6 +55,25 @@ do end end + -- gets the slot data for the playmat + ---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All") + PlaymatApi.getSlotData = function(matColor) + for _, mat in pairs(getMatForColor(matColor)) do + return mat.getTable("slotData") + end + end + + -- sets the slot data for the playmat + ---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All") + ---@param newSlotData table New slot data for the playmat + PlaymatApi.loadSlotData = function(matColor, newSlotData) + for _, mat in pairs(getMatForColor(matColor)) do + mat.setTable("slotData", newSlotData) + mat.call("redrawSlotSymbols") + return + end + end + -- Performs a search of the deck area of the requested playmat and returns the result as table ---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All") PlaymatApi.getDeckAreaObjects = function(matColor) From 46f9eac6317f73f6b6839ef5e61f6422d84a58f8 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 14:12:34 +0200 Subject: [PATCH 07/21] turned "DoNotReady" into a tag --- objects/AllPlayerCards.15bb07/JacobMorrison3.aa38d0.json | 3 ++- src/playermat/Playmat.ttslua | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/objects/AllPlayerCards.15bb07/JacobMorrison3.aa38d0.json b/objects/AllPlayerCards.15bb07/JacobMorrison3.aa38d0.json index e52dd100..1b3f9e22 100644 --- a/objects/AllPlayerCards.15bb07/JacobMorrison3.aa38d0.json +++ b/objects/AllPlayerCards.15bb07/JacobMorrison3.aa38d0.json @@ -33,7 +33,7 @@ "IgnoreFoW": false, "LayoutGroupSortIndex": 0, "Locked": false, - "LuaScript": "do_not_ready = true", + "LuaScript": "", "LuaScriptState": "", "MeasureMovement": false, "Name": "Card", @@ -43,6 +43,7 @@ "Sticky": true, "Tags": [ "Asset", + "DoNotReady", "PlayerCard" ], "Tooltip": true, diff --git a/src/playermat/Playmat.ttslua b/src/playermat/Playmat.ttslua index b117134d..6bd2e230 100644 --- a/src/playermat/Playmat.ttslua +++ b/src/playermat/Playmat.ttslua @@ -295,7 +295,7 @@ function doUpkeep(_, clickedByColor, isRightClick) obj.flip() elseif obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then local cardMetadata = JSON.decode(obj.getGMNotes()) or {} - if not (obj.getVar("do_not_ready") or false) then + if not (obj.getVar("do_not_ready") or obj.hasTag("DoNotReady")) then local cardRotation = round(obj.getRotation().y, 0) - rot.y local yRotDiff = 0 From 6e2379b575dd5e66d6d83bcdfa9b2c7f1089458f Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 21 May 2024 15:41:32 +0200 Subject: [PATCH 08/21] bugfixes --- src/arkhamdb/ArkhamDb.ttslua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua index fe89293e..5ff08a63 100644 --- a/src/arkhamdb/ArkhamDb.ttslua +++ b/src/arkhamdb/ArkhamDb.ttslua @@ -13,7 +13,7 @@ do ---@class Request local Request = {} - -- Sets up the ArkhamDb interface. Should be called from the parent object on load. + -- Sets up the ArkhamDb interface. Should be called from the parent object on load. ArkhamDb.initialize = function() configuration = internal.getConfiguration() Request.start({ configuration.api_uri, configuration.taboo }, function(status) @@ -71,13 +71,14 @@ do local deck = Request.start(deckUri, function(status) if string.find(status.text, "") then - internal.maybePrint("Private deck ID " .. deckId .. " is not shared", playerColor) + internal.maybePrint("Private deck ID " .. deckId .. " is not shared.", playerColor) return false, "Private deck " .. deckId .. " is not shared" end - local json = JSON.decode(status.text) + + local json = JSON.decode(internal.fixUtf16String(status.text)) if not json then - internal.maybePrint("Deck ID " .. deckId .. " not found", playerColor) + internal.maybePrint("Deck ID " .. deckId .. " not found.", playerColor) return false, "Deck not found!" end @@ -177,8 +178,8 @@ do local randomWeaknessAmount = slots[RANDOM_WEAKNESS_ID] or 0 slots[RANDOM_WEAKNESS_ID] = nil - if randomWeaknessAmount ~= 0 then - for i=1, randomWeaknessAmount do + if randomWeaknessAmount > 0 then + for i = 1, randomWeaknessAmount do local weaknessId = allCardsBagApi.getRandomWeaknessId() slots[weaknessId] = (slots[weaknessId] or 0) + 1 end From d96a34e38f2eb697cc3324e11b14827ef055eaaa Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Thu, 23 May 2024 23:41:43 +0200 Subject: [PATCH 09/21] fixed scale --- .../AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.json b/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.json index aab2b7ea..a0533416 100644 --- a/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.json +++ b/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.json @@ -53,9 +53,9 @@ "rotX": 0, "rotY": 180, "rotZ": 0, - "scaleX": 1, + "scaleX": 1.15, "scaleY": 1, - "scaleZ": 1 + "scaleZ": 1.15 }, "Value": 0, "XmlUI": "" From d7c328d3dd09b0ef9012084cd6766e332e135f9c Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Fri, 24 May 2024 19:47:26 +0200 Subject: [PATCH 10/21] fixed phase tracker broadcasting --- src/accessories/PhaseTracker.ttslua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/accessories/PhaseTracker.ttslua b/src/accessories/PhaseTracker.ttslua index d77f2be7..74e8013e 100644 --- a/src/accessories/PhaseTracker.ttslua +++ b/src/accessories/PhaseTracker.ttslua @@ -11,7 +11,8 @@ local phaseImages = { "http://cloud-3.steamusercontent.com/ugc/982233321870237261/C287CAED2423970F33E72D6C7415CBEC6794C533/" } -local phaseId, broadcastChange +-- these are intentionally global for remote updating +-- phaseId, broadcastChange function onSave() return JSON.encode({ From afff593874d9814cd8e1e1f860e1bb94358725bd Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Fri, 24 May 2024 21:54:29 +0200 Subject: [PATCH 11/21] foundation for custom cards --- src/arkhamdb/DeckImporterUi.ttslua | 12 +- src/playercards/AllCardsBag.ttslua | 21 ++-- src/playercards/AllCardsBagApi.ttslua | 22 ++-- src/playercards/PlayerCardPanel.ttslua | 157 ++++++++++++------------- src/playercards/SpawnBag.ttslua | 130 ++++++++++---------- xml/playercards/PlayerCardPanel.xml | 23 ++-- 6 files changed, 174 insertions(+), 191 deletions(-) diff --git a/src/arkhamdb/DeckImporterUi.ttslua b/src/arkhamdb/DeckImporterUi.ttslua index 5788446d..524907c0 100644 --- a/src/arkhamdb/DeckImporterUi.ttslua +++ b/src/arkhamdb/DeckImporterUi.ttslua @@ -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 diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index c141564b..2355b2f4 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -18,7 +18,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 @@ -210,6 +210,9 @@ function cardComparator(id1, id2) end 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 @@ -221,10 +224,7 @@ end -- cardData: TTS object data, suitable for spawning the card -- cardMetadata: 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 @@ -237,10 +237,8 @@ end -- cardData: TTS object data, suitable for spawning the card -- cardMetadata: 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 + if not isIndexReady() then return {} end + local upgradeKey if (params.upgraded) then upgradeKey = "-upgrade" @@ -251,10 +249,7 @@ function getCardsByClassAndLevel(params) 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 { } - end + if not isIndexReady() then return {} end return cycleIndex[string.lower(cycleName)] end diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index 350f2f8d..c7baf29f 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -6,6 +6,14 @@ do return guidReferenceApi.getObjectByOwnerAndType("Mythos", "AllCardsBag") end + 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 @@ -14,7 +22,7 @@ do -- cardData: TTS object data, suitable for spawning the card -- cardMetadata: Table of parsed metadata AllCardsBagApi.getCardById = function(id) - return getAllCardsBag().call("getCardById", {id = id}) + return getAllCardsBag().call("getCardById", { id = id }) end -- Gets a random basic weakness from the bag. Once a given ID has been returned @@ -36,7 +44,7 @@ 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 @@ -44,7 +52,7 @@ do ---@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() @@ -59,16 +67,16 @@ do -- cardData: TTS object data, suitable for spawning the card -- cardMetadata: 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) + return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", cycle)) end AllCardsBagApi.getUniqueWeaknesses = function() - return getAllCardsBag().call("getUniqueWeaknesses") + return returnCopyOfList(getAllCardsBag().call("getUniqueWeaknesses")) end return AllCardsBagApi -end \ No newline at end of file +end diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua index d1ae7311..f84bb340 100644 --- a/src/playercards/PlayerCardPanel.ttslua +++ b/src/playercards/PlayerCardPanel.ttslua @@ -26,22 +26,20 @@ 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} +local FACE_UP_ROTATION = { x = 0, y = 270, z = 0 } +local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180 } -- ---------- IMPORTANT ---------- -- Coordinates defined below are in global dimensions relative to the panel - DO NOT USE THESE --- DIRECTLY. Call scalePositions() before use, and reference the variables below +-- DIRECTLY. Call scalePositions() before use, and reference the variables below -- Layout width for a single card, in global coordinate space local CARD_WIDTH = 2.3 --- Coordinates to begin laying out cards. These vary based on the cards that are being placed by +-- Coordinates to begin laying out cards. These vary based on the cards that are being placed by -- considering the width of the cards, number of cards, and desired spread intervals. --- IMPORTANT! Because of the mix of global card sizes and relative-to-scale positions, the X and Y +-- IMPORTANT! Because of the mix of global card sizes and relative-to-scale positions, the X and Y -- coordinates on these provide global disances while the Z is local. local START_POSITIONS = { classCards = Vector(CARD_WIDTH * 9.5, 2, 1.4), @@ -50,7 +48,7 @@ local START_POSITIONS = { other = Vector(CARD_WIDTH * 9.5, 2, 1.4), randomWeakness = Vector(0, 2, 1.4), -- Because the card spread is handled by the SpawnBag, we don't know (programatically) where this - -- should be placed. If more customizable cards are added it will need to be moved. + -- should be placed. If more customizable cards are added it will need to be moved. summonedServitor = Vector(CARD_WIDTH * -7.5, 2, 1.7) } @@ -64,12 +62,12 @@ local INVESTIGATOR_POSITION_SHIFT_ROW = Vector(0, 0, 11) local INVESTIGATOR_POSITION_SHIFT_COL = Vector(-6, 0, 0) local INVESTIGATOR_MAX_COLS = 6 --- Positions relative to the minicard to place other stacks. Both signature card piles and starter +-- Positions relative to the minicard to place other stacks. Both signature card piles and starter -- decks use SIGNATURE_OFFSET local INVESTIGATOR_CARD_OFFSET = Vector(0, 0, 2.55) local INVESTIGATOR_SIGNATURE_OFFSET = Vector(0, 0, 5.75) --- USE THESE! Positions and offset shifts accounting for the scale of the panel +-- USE THESE! Positions and offset shifts accounting for the scale of the panel local startPositions local cardRowOffset local cardGroupOffset @@ -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,9 +99,8 @@ local CYCLE_LIST = { } local excludedNonBasicWeaknesses - -local starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY -local helpVisibleToPlayers = { } +local spawnStarterDecks = false +local helpVisibleToPlayers = {} function onSave() return JSON.encode({ spawnBagState = spawnBag.getStateForSave() }) @@ -104,7 +108,7 @@ end function onLoad(savedData) if savedData and savedData ~= "" then - local saveState = JSON.decode(savedData) or { } + local saveState = JSON.decode(savedData) or {} if saveState.spawnBagState ~= nil then spawnBag.loadFromSave(saveState.spawnBagState) end @@ -117,7 +121,7 @@ end -- Build a list of non-basic weaknesses which should be excluded from the last weakness set, -- including all signature cards and evolved weaknesses. function buildExcludedWeaknessList() - excludedNonBasicWeaknesses = { } + excludedNonBasicWeaknesses = {} for _, investigator in pairs(INVESTIGATORS) do for _, signatureId in ipairs(investigator.signatures) do excludedNonBasicWeaknesses[signatureId] = true @@ -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,7 +326,8 @@ 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 } }) @@ -354,13 +356,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 @@ -384,7 +386,7 @@ end function scalePositions() -- Assume scaling is consistent in X and Z dimensions local scale = 1 / self.getScale().x - startPositions = { } + startPositions = {} for key, pos in pairs(START_POSITIONS) do -- Because a scaled object means a different global size, using global distance for Z results in -- the cards being closer or farther depending on the scale. Leave the Z values and only scale X and Y @@ -405,14 +407,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) @@ -420,7 +420,7 @@ function spawnInvestigatorGroup(groupName) end, 2) end --- Spawn cards for all investigators in the given group. This creates piles for all defined +-- Spawn cards for all investigators in the given group. This creates piles for all defined -- investigator cards and minicards as well as the signature cards. ---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData function spawnInvestigators(groupName) @@ -451,14 +451,14 @@ end function getInvestigatorRowStartPos(investigatorCount, row) local rowStart = Vector(startPositions.investigator) rowStart:add(Vector( - investigatorPositionShiftRow.x * (row - 1), - investigatorPositionShiftRow.y * (row - 1), - investigatorPositionShiftRow.z * (row - 1))) + investigatorPositionShiftRow.x * (row - 1), + investigatorPositionShiftRow.y * (row - 1), + investigatorPositionShiftRow.z * (row - 1))) local investigatorsInRow = math.min(investigatorCount - INVESTIGATOR_MAX_COLS * (row - 1), INVESTIGATOR_MAX_COLS) rowStart:add(Vector( - investigatorPositionShiftCol.x * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2, - investigatorPositionShiftCol.y * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2, - investigatorPositionShiftCol.z * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2)) + investigatorPositionShiftCol.x * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2, + investigatorPositionShiftCol.y * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2, + investigatorPositionShiftCol.z * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2)) return rowStart end @@ -470,23 +470,23 @@ function buildInvestigatorSpawnSpec(investigatorName, investigatorData, position local sigPos = Vector(position):add(investigatorSignatureOffset) local spawns = buildCommonSpawnSpec(investigatorName, investigatorData, position) table.insert(spawns, { - name = investigatorName .. "signatures", - cards = investigatorData.signatures, - globalPos = self.positionToWorld(sigPos), - rotation = FACE_UP_ROTATION - }) + name = investigatorName .. "signatures", + cards = investigatorData.signatures, + globalPos = self.positionToWorld(sigPos), + rotation = FACE_UP_ROTATION + }) return spawns end --- Builds the spawn specs for minicards and investigator cards. These are common enough to be +-- Builds the spawn specs for minicards and investigator cards. These are common enough to be -- shared, and will only differ in whether they spawn the full stack of possible investigator and -- minicards, or only the first of each. ---@param investigatorName string Name of the investigator, matching a key in InvestigatorPanelData ---@param investigatorData table Spawn definition for the investigator, retrieved from INVESTIGATORS ---@param position tts__Vector Where to spawn the minicard; investigagor cards will be placed below ---@param oneCardOnly? boolean If true, will spawn only the first card in the investigator card ---- and minicard lists. Otherwise, spawn them all in a deck +--- and minicard lists. Otherwise, spawn them all in a deck function buildCommonSpawnSpec(investigatorName, investigatorData, position, oneCardOnly) local cardPos = Vector(position):add(investigatorCardOffset) return { @@ -533,23 +533,24 @@ function spawnStarterDeck(investigatorName, investigatorData, position) end local deckPos = Vector(position):add(investigatorSignatureOffset) arkhamDb.getDecklist("None", investigatorData.starterDeck, true, false, false, function(slots) - local cardIdList = { } + local cardIdList = {} for id, count in pairs(slots) do for i = 1, count do table.insert(cardIdList, id) end end spawnBag.spawn({ - name = investigatorName.."starter", + name = investigatorName .. "starter", cards = cardIdList, globalPos = self.positionToWorld(deckPos), rotation = FACE_DOWN_ROTATION }) 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. +---@param isUpgraded boolean If true, spawn the Level 1-5 cards. Otherwise, Level 0. function spawnClassCards(cardClass, isUpgraded) prepareToPlaceCards() Wait.frames(function() placeClassCards(cardClass, isUpgraded) end, 2) @@ -557,18 +558,15 @@ end -- Spawn the class cards. ---@param cardClass string Class to place ("Guardian", "Seeker", etc) ----@param isUpgraded boolean If true, spawn the Level 1-5 cards. Otherwise, Level 0. +---@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 = { } - local eventList = { } - local assetList = { } + local skillList = {} + local eventList = {} + local assetList = {} for _, cardId in ipairs(cardIdList) do local cardMetadata = allCardsBagApi.getCardById(cardId).metadata if (cardMetadata.type == "Skill") then @@ -614,24 +612,22 @@ function placeClassCards(cardClass, isUpgraded) end end +-- called by the XML UI to spawn cards from fan-made camnpaigns +function spawnOtherCards() + spawnCycle("Other") +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 - end + spawnBag.spawn({ - name = "cycle"..cycle, - cards = copiedList, + name = "cycle" .. cycle, + cards = allCardsBagApi.getCardsByCycle(cycle), globalPos = self.positionToWorld(startPositions.cycle), rotation = FACE_UP_ROTATION, spread = true, @@ -671,16 +667,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 + + local basicWeaknessList = {} + local otherWeaknessList = {} + 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) @@ -721,7 +714,7 @@ function spawnRandomWeakness() prepareToPlaceCards() local weaknessId = allCardsBagApi.getRandomWeaknessId() if (weaknessId == nil) then - broadcastToAll("All basic weaknesses are in play!", {0.9, 0.2, 0.2}) + broadcastToAll("All basic weaknesses are in play!", { 0.9, 0.2, 0.2 }) return end spawnBag.spawn({ diff --git a/src/playercards/SpawnBag.ttslua b/src/playercards/SpawnBag.ttslua index 3bdcb80e..982d489c 100644 --- a/src/playercards/SpawnBag.ttslua +++ b/src/playercards/SpawnBag.ttslua @@ -1,32 +1,32 @@ require("playercards/PlayerCardSpawner") -- Allows spawning of defined lists of cards which will be created from the template in the All --- Player Cards bag. SpawnBag.spawn will create objects based on a table definition, while --- SpawnBag.recall will clean them all up. Recall will be limited to a small area around the --- spawned objects. Objects moved out of this area will not be cleaned up. +-- Player Cards bag. SpawnBag.spawn will create objects based on a table definition, while +-- SpawnBag.recall will clean them all up. Recall will be limited to a small area around the +-- spawned objects. Objects moved out of this area will not be cleaned up. -- -- SpawnSpec: Spawning requires a spawn specification with the following structure: -- { --- name: Name of this spawn content, used for internal tracking. Multiple specs can be spawned, +-- name: Name of this spawn content, used for internal tracking. Multiple specs can be spawned, -- but each requires a separate name -- cards: A list of card IDs to be spawned --- globalPos: Where the spawned objects should be placed, in global coordinates. This should be +-- globalPos: Where the spawned objects should be placed, in global coordinates. This should be -- a valid Vector with x, y, and z defined, e.g. { x = 5, y = 1, z = 15 } --- rotation: Rotation for the spawned objects. X=180 should be used for face down items. As with +-- rotation: Rotation for the spawned objects. X=180 should be used for face down items. As with -- globalPos, this should be a valid Vector with x, y, and z defined --- spread: Optional Boolean. If present and true, cards will be spawned next to each other in a --- spread moving to the right. globalPos will define the location of the first card, each +-- spread: Optional Boolean. If present and true, cards will be spawned next to each other in a +-- spread moving to the right. globalPos will define the location of the first card, each -- after that will be moved a predefined distance --- spreadCols: Optional integer. If spread is true, specifies the maximum columns cards will be --- laid out in before starting a new row. If spread is true but spreadCols is not set, all +-- spreadCols: Optional integer. If spread is true, specifies the maximum columns cards will be +-- laid out in before starting a new row. If spread is true but spreadCols is not set, all -- cards will be in a single row (however long that may be) -- } -- See BondedBag.ttslua for an example do local allCardsBagApi = require("playercards/AllCardsBagApi") - local SpawnBag = { } - local internal = { } + local SpawnBag = {} + local internal = {} -- To assist debugging, will draw a box around the recall zone when it's set up local SHOW_RECALL_ZONE = false @@ -36,8 +36,8 @@ do local RECALL_BUFFER_Z = 0.5 -- In order to mimic the behavior of the previous memory buttons we use a temporary bag when - -- recalling objects. This bag is tiny and transparent, and will be placed at the same location as - -- this object. Once all placed cards are recalled bag to this bag, it will be destroyed + -- recalling objects. This bag is tiny and transparent, and will be placed at the same location as + -- this object. Once all placed cards are recalled bag to this bag, it will be destroyed local RECALL_BAG = { Name = "Bag", Transform = { @@ -58,8 +58,8 @@ do } -- Tracks what has been placed by this "bag" so they can be recalled - local placedSpecs = { } - local placedObjectGuids = { } + local placedSpecs = {} + local placedObjectGuids = {} local recallZone = nil -- Loads a table of saved state, extracted during the parent object's onLoad @@ -81,21 +81,13 @@ 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 - local cardsToSpawn = { } - local cardList = spawnSpec.cards - for _, cardId in ipairs(cardList) do + if placedSpecs[spawnSpec.name] or spawnSpec == nil then return end + + local cardsToSpawn = {} + for _, cardId in ipairs(spawnSpec.cards) do local cardData = allCardsBagApi.getCardById(cardId) - if (cardData ~= nil) then + if cardData ~= nil then table.insert(cardsToSpawn, cardData) - else - -- TODO: error here end end if (spawnSpec.spread) then @@ -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. - placedSpecs = { } - placedObjectGuids = { } + -- 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,9 +131,9 @@ 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()}) + local trash = spawnObjectData({ data = RECALL_BAG, position = self.getPosition() }) for guid, _ in pairs(placedObjectGuids) do local obj = getObjectFromGUID(guid) if (obj ~= nil) then @@ -156,71 +147,70 @@ 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) end - -- Expands the current recall zone based on the position of the given object. The recall zone will + -- Expands the current recall zone based on the position of the given object. The recall zone will -- be maintained as the bounding box of the extreme object positions, plus a small amount of buffer internal.expandRecallZone = function(spawnedCard) local pos = spawnedCard.getPosition() if (recallZone == nil) then -- First card out of the bag, initialize surrounding that - recallZone = { } + recallZone = {} 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 - recallZone.upperLeft.x = pos.x + RECALL_BUFFER_X - end - if (pos.x < recallZone.lowerRight.x) then - recallZone.lowerRight.x = pos.x - RECALL_BUFFER_X - end - if (pos.z > recallZone.upperLeft.z) then - recallZone.upperLeft.z = pos.z + RECALL_BUFFER_Z - end - if (pos.z < recallZone.lowerRight.z) then - recallZone.lowerRight.z = pos.z - RECALL_BUFFER_Z - end end - if (SHOW_RECALL_ZONE) then + + if pos.x > recallZone.upperLeft.x then + recallZone.upperLeft.x = pos.x + RECALL_BUFFER_X + end + if pos.x < recallZone.lowerRight.x then + recallZone.lowerRight.x = pos.x - RECALL_BUFFER_X + end + if pos.z > recallZone.upperLeft.z then + recallZone.upperLeft.z = pos.z + RECALL_BUFFER_Z + end + if pos.z < recallZone.lowerRight.z then + recallZone.lowerRight.z = pos.z - RECALL_BUFFER_Z + end + + if SHOW_RECALL_ZONE then local y = 1.5 local thick = 0.05 Global.setVectorLines({ { - points = { {recallZone.upperLeft.x,y,recallZone.upperLeft.z}, {recallZone.upperLeft.x,y,recallZone.lowerRight.z} }, - color = {1,0,0}, + points = { { recallZone.upperLeft.x, y, recallZone.upperLeft.z }, { recallZone.upperLeft.x, y, recallZone.lowerRight.z } }, + color = { 1, 0, 0 }, thickness = thick, - rotation = {0,0,0} + rotation = { 0, 0, 0 } }, { - points = { {recallZone.upperLeft.x,y,recallZone.lowerRight.z}, {recallZone.lowerRight.x,y,recallZone.lowerRight.z} }, - color = {1,0,0}, + points = { { recallZone.upperLeft.x, y, recallZone.lowerRight.z }, { recallZone.lowerRight.x, y, recallZone.lowerRight.z } }, + color = { 1, 0, 0 }, thickness = thick, - rotation = {0,0,0} + rotation = { 0, 0, 0 } }, { - points = { {recallZone.lowerRight.x,y,recallZone.lowerRight.z}, {recallZone.lowerRight.x,y,recallZone.upperLeft.z} }, - color = {1,0,0}, + points = { { recallZone.lowerRight.x, y, recallZone.lowerRight.z }, { recallZone.lowerRight.x, y, recallZone.upperLeft.z } }, + color = { 1, 0, 0 }, thickness = thick, - rotation = {0,0,0} + rotation = { 0, 0, 0 } }, { - points = { {recallZone.lowerRight.x,y,recallZone.upperLeft.z}, {recallZone.upperLeft.x,y,recallZone.upperLeft.z} }, - color = {1,0,0}, + points = { { recallZone.lowerRight.x, y, recallZone.upperLeft.z }, { recallZone.upperLeft.x, y, recallZone.upperLeft.z } }, + color = { 1, 0, 0 }, thickness = thick, - rotation = {0,0,0} + rotation = { 0, 0, 0 } } }) end end - -- Checks to see if the given object is in the current recall zone. If there isn't a recall zone, + -- Checks to see if the given object is in the current recall zone. If there isn't a recall zone, -- will return true so that everything can be easily cleaned up. internal.isInRecallZone = function(obj) if (recallZone == nil) then @@ -228,11 +218,11 @@ do end local pos = obj.getPosition() return (pos.x < recallZone.upperLeft.x and pos.x > recallZone.lowerRight.x - and pos.z < recallZone.upperLeft.z and pos.z > recallZone.lowerRight.z) + and pos.z < recallZone.upperLeft.z and pos.z > recallZone.lowerRight.z) end internal.reverseList = function(list) - local reversed = { } + local reversed = {} for i = 1, #list do reversed[i] = list[#list - i + 1] end diff --git a/xml/playercards/PlayerCardPanel.xml b/xml/playercards/PlayerCardPanel.xml index 17ecfb2c..d193129f 100644 --- a/xml/playercards/PlayerCardPanel.xml +++ b/xml/playercards/PlayerCardPanel.xml @@ -1,24 +1,31 @@ - - • 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 + + + \ No newline at end of file From 5b38ea1c0200df9cfa83dd61ae12c4bfe3f6641e Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Sat, 25 May 2024 00:40:18 +0200 Subject: [PATCH 12/21] 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({ From eb63d0a532573204323accc0efc37a33eb9a76e4 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Sat, 25 May 2024 00:52:01 +0200 Subject: [PATCH 13/21] added some documentation --- src/playercards/AllCardsBag.ttslua | 39 ++++++++++++++++----------- src/playercards/AllCardsBagApi.ttslua | 35 ++++++++++++++---------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index 95175ccf..e555848d 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -131,6 +131,7 @@ 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 @@ -213,6 +214,7 @@ function cardComparator(id1, id2) end 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}) @@ -221,25 +223,24 @@ function isIndexReady() 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 -function getCardById(params) +---@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 +-- cardData: TTS object data, suitable for spawning the card +-- cardMetadata: Table of parsed metadata +function getCardById(id) if not isIndexReady() then return {} end - return cardIdIndex[params.id] + return cardIdIndex[id] end -- Returns a list of cards from the bag matching a class and level (0 or upgraded) --- 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. --- Otherwise, a list of tables, each with the following fields --- cardData: TTS object data, suitable for spawning the card --- cardMetadata: Table of parsed metadata +---@param params table +-- class: String class to retrieve ("Guardian", "Seeker", etc) +-- isUpgraded: 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 function getCardsByClassAndLevel(params) if not isIndexReady() then return {} end @@ -252,6 +253,14 @@ function getCardsByClassAndLevel(params) return classAndLevelIndex[params.class..upgradeKey] end +-- 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 +-- cardData: TTS object data, suitable for spawning the card +-- cardMetadata: Table of parsed metadata function getCardsByCycle(params) if not isIndexReady() then return {} end diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index 2d6801d1..03bfabe3 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -6,6 +6,7 @@ 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 @@ -15,17 +16,16 @@ do 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 + ---@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 -- cardData: TTS object data, suitable for spawning the card -- cardMetadata: Table of parsed metadata AllCardsBagApi.getCardById = function(id) - return getAllCardsBag().call("getCardById", { id = id }) + return getAllCardsBag().call("getCardById", id) 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. @@ -38,17 +38,17 @@ do return getAllCardsBag().call("isIndexReady") 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. AllCardsBagApi.rebuildIndexForHotfix = function() 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) @@ -61,15 +61,22 @@ 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 + -- Otherwise, a list of tables, each with the following fields + -- cardData: TTS object data, suitable for spawning the card + -- cardMetadata: Table of parsed metadata AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded) return returnCopyOfList(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded })) end + -- 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 + -- cardData: TTS object data, suitable for spawning the card + -- cardMetadata: Table of parsed metadata AllCardsBagApi.getCardsByCycle = function(cycle, sortByMetadata) return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", { cycle = cycle, sortByMetadata = sortByMetadata })) end From 326b021b4a3a8ee1f95aba83c0dc6ecd0e38285d Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Sat, 25 May 2024 11:44:46 +0200 Subject: [PATCH 14/21] redraw the slot XML when a player joins --- src/core/Global.ttslua | 5 +++++ src/playermat/PlaymatApi.ttslua | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua index 7874fa21..24a796c4 100644 --- a/src/core/Global.ttslua +++ b/src/core/Global.ttslua @@ -222,6 +222,11 @@ function onObjectNumberTyped(hoveredObject, playerColor, number) end end +-- TTS event, used to redraw the playmat slot symbols after a small delay to account for the custom font loading +function onPlayerConnect() + Wait.time(function() playmatApi.redrawSlotSymbols("All") end, 0.2) +end + --------------------------------------------------------- -- chaos token drawing --------------------------------------------------------- diff --git a/src/playermat/PlaymatApi.ttslua b/src/playermat/PlaymatApi.ttslua index 17af3a37..b6feca59 100644 --- a/src/playermat/PlaymatApi.ttslua +++ b/src/playermat/PlaymatApi.ttslua @@ -258,6 +258,14 @@ do end end + -- Redraws the XML for the slot symbols based on the slotData table + ---@param matColor string Color of the playmat - White, Orange, Green, Red or All + PlaymatApi.redrawSlotSymbols = function(matColor) + for _, mat in pairs(getMatForColor(matColor)) do + mat.call("redrawSlotSymbols") + end + end + -- Finds all objects on the playmat and associated set aside zone and returns a table ---@param matColor string Color of the playmat - White, Orange, Green, Red or All ---@param filter string Name of the filte function (see util/SearchLib) From acbd1ea9229fa3de09058bc63d8f461d7193adf6 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Mon, 27 May 2024 15:20:55 +0200 Subject: [PATCH 15/21] added image for other cards --- objects/PlayerCards.2d30ee.json | 7 +++++++ xml/playercards/PlayerCardPanel.xml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/objects/PlayerCards.2d30ee.json b/objects/PlayerCards.2d30ee.json index 8608be69..a623b186 100644 --- a/objects/PlayerCards.2d30ee.json +++ b/objects/PlayerCards.2d30ee.json @@ -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": "", diff --git a/xml/playercards/PlayerCardPanel.xml b/xml/playercards/PlayerCardPanel.xml index d193129f..b1f900d1 100644 --- a/xml/playercards/PlayerCardPanel.xml +++ b/xml/playercards/PlayerCardPanel.xml @@ -26,6 +26,6 @@ height="200" width="200" scale="0.1 0.1 1" - color="#FFFFFF" onClick="spawnOtherCards"> + \ No newline at end of file From fbd70c0f74342be76270e5e1e88e31f2becdc58d Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Tue, 28 May 2024 01:02:36 +0200 Subject: [PATCH 16/21] updated XML --- objects/PlayerCards.2d30ee.json | 2 +- src/playercards/AllCardsBag.ttslua | 32 ++++++--- src/playercards/AllCardsBagApi.ttslua | 21 +++--- src/playercards/PlayerCardPanel.ttslua | 83 ++++++++++++++++++------ src/playercards/PlayerCardSpawner.ttslua | 10 +-- src/playercards/SpawnBag.ttslua | 8 +-- xml/playercards/PlayerCardPanel.xml | 31 --------- 7 files changed, 106 insertions(+), 81 deletions(-) delete mode 100644 xml/playercards/PlayerCardPanel.xml diff --git a/objects/PlayerCards.2d30ee.json b/objects/PlayerCards.2d30ee.json index a623b186..606c796d 100644 --- a/objects/PlayerCards.2d30ee.json +++ b/objects/PlayerCards.2d30ee.json @@ -60,5 +60,5 @@ "scaleZ": 10 }, "Value": 0, - "XmlUI": "\u003cInclude src=\"playercards/PlayerCardPanel.xml\"/\u003e" + "XmlUI": "" } diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index e555848d..bbfed2c2 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -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) @@ -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 @@ -108,6 +112,7 @@ function buildIndex() end buildSupplementalIndexes() + updatePlayerCardPanel() indexingDone = true return 1 end @@ -174,6 +179,7 @@ function buildSupplementalIndexes() else -- track cards without defined cycle (should only be fan-made cards) cycleName = "other" + otherCardsDetected = true end -- maybe initialize table @@ -214,6 +220,12 @@ 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 @@ -223,14 +235,14 @@ function isIndexReady() end -- Returns a specific card from the bag, based on ArkhamDB ID ----@param id string ID of the card to retrieve +---@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 --- cardData: TTS object data, suitable for spawning the card --- cardMetadata: Table of parsed metadata -function getCardById(id) +-- data: TTS object data, suitable for spawning the card +-- metadata: Table of parsed metadata +function getCardById(params) if not isIndexReady() then return {} end - return cardIdIndex[id] + return cardIdIndex[params.id] end -- Returns a list of cards from the bag matching a class and level (0 or upgraded) @@ -239,8 +251,8 @@ end -- isUpgraded: 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 function getCardsByClassAndLevel(params) if not isIndexReady() then return {} end @@ -250,7 +262,7 @@ function getCardsByClassAndLevel(params) else upgradeKey = "-level0" end - return classAndLevelIndex[params.class..upgradeKey] + return classAndLevelIndex[params.class .. upgradeKey] end -- Returns a list of cards from the bag matching a cycle @@ -259,8 +271,8 @@ end -- 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 --- 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 getCardsByCycle(params) if not isIndexReady() then return {} end diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index 03bfabe3..e43e693a 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -19,16 +19,15 @@ do ---@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 - -- 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.getCardById = function(id) - return getAllCardsBag().call("getCardById", 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. + -- 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") @@ -64,8 +63,8 @@ do ---@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 returnCopyOfList(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded })) end @@ -75,8 +74,8 @@ do ---@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 - -- 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.getCardsByCycle = function(cycle, sortByMetadata) return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", { cycle = cycle, sortByMetadata = sortByMetadata })) end diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua index 90b2e1b6..dcefb509 100644 --- a/src/playercards/PlayerCardPanel.ttslua +++ b/src/playercards/PlayerCardPanel.ttslua @@ -333,6 +333,70 @@ function createInvestigatorModeButtons() }) 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 @@ -612,11 +676,6 @@ function placeClassCards(cardClass, isUpgraded) end end --- called by the XML UI to spawn cards from fan-made camnpaigns -function spawnOtherCards() - spawnCycle("Other") -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) @@ -641,20 +700,6 @@ 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({ diff --git a/src/playercards/PlayerCardSpawner.ttslua b/src/playercards/PlayerCardSpawner.ttslua index 1fbfdc0e..e0501619 100644 --- a/src/playercards/PlayerCardSpawner.ttslua +++ b/src/playercards/PlayerCardSpawner.ttslua @@ -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 diff --git a/src/playercards/SpawnBag.ttslua b/src/playercards/SpawnBag.ttslua index 982d489c..31db3b9d 100644 --- a/src/playercards/SpawnBag.ttslua +++ b/src/playercards/SpawnBag.ttslua @@ -85,12 +85,12 @@ do local cardsToSpawn = {} for _, cardId in ipairs(spawnSpec.cards) do - local cardData = allCardsBagApi.getCardById(cardId) - if cardData ~= nil then - table.insert(cardsToSpawn, cardData) + 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 diff --git a/xml/playercards/PlayerCardPanel.xml b/xml/playercards/PlayerCardPanel.xml deleted file mode 100644 index b1f900d1..00000000 --- a/xml/playercards/PlayerCardPanel.xml +++ /dev/null @@ -1,31 +0,0 @@ - - -• 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 - - - - - \ No newline at end of file From 2c4d84400c0769a896f7715dfa9365b763b38acd Mon Sep 17 00:00:00 2001 From: Whimsical-Cloudheart <88459121+Whimsical-Cloudheart@users.noreply.github.com> Date: Tue, 28 May 2024 07:29:02 -0300 Subject: [PATCH 17/21] Update GameKeyHandler.ttslua Added a confirmation to players for discarding decks or locations --- src/core/GameKeyHandler.ttslua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua index 1570280b..88ad8778 100644 --- a/src/core/GameKeyHandler.ttslua +++ b/src/core/GameKeyHandler.ttslua @@ -135,11 +135,17 @@ function discardObject(playerColor, hoveredObject) return end - -- warning for locations since these are usually not meant to be discarded - if hoveredObject.hasTag("Location") then - broadcastToAll("Watch out: A location was discarded.", "Yellow") + -- These should probably not be discarded normally. Ask player for confirmation. + if (hoveredObject.type == "Deck") or hoveredObject.hasTag("Location") then + local suspect = (hoveredObject.type == "Deck") and "Deck" or "Location" + Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?", function () performDiscard(hoveredObject, playerColor) end) + return end + performDiscard(hoveredObject, playerColor) +end + +function performDiscard(hoveredObject, playerColor) -- initialize list of objects to discard local discardTheseObjects = { hoveredObject } From 377b436feba148fb29255851d53a17b21db4c4d3 Mon Sep 17 00:00:00 2001 From: Whimsical-Cloudheart <88459121+Whimsical-Cloudheart@users.noreply.github.com> Date: Tue, 28 May 2024 07:39:59 -0300 Subject: [PATCH 18/21] Update GameKeyHandler.ttslua --- src/core/GameKeyHandler.ttslua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua index 88ad8778..3e7ccd0c 100644 --- a/src/core/GameKeyHandler.ttslua +++ b/src/core/GameKeyHandler.ttslua @@ -174,7 +174,7 @@ function discardTopDeck(playerColor, hoveredObject) else takenCard = hoveredObject end - Wait.frames(function() discardObject(playerColor, takenCard) end, 1) + Wait.frames(function() performDiscard(takenCard, playerColor) end, 1) end -- helper function to get the player to trigger the discard function for From f6bff32b149199db1b938fe171984345fae8a0dd Mon Sep 17 00:00:00 2001 From: Whimsical-Cloudheart <88459121+Whimsical-Cloudheart@users.noreply.github.com> Date: Tue, 28 May 2024 08:39:40 -0300 Subject: [PATCH 19/21] Update GameKeyHandler.ttslua Reversed order of performDiscard's arguments --- src/core/GameKeyHandler.ttslua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua index 3e7ccd0c..adb2d4bf 100644 --- a/src/core/GameKeyHandler.ttslua +++ b/src/core/GameKeyHandler.ttslua @@ -138,14 +138,14 @@ function discardObject(playerColor, hoveredObject) -- These should probably not be discarded normally. Ask player for confirmation. if (hoveredObject.type == "Deck") or hoveredObject.hasTag("Location") then local suspect = (hoveredObject.type == "Deck") and "Deck" or "Location" - Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?", function () performDiscard(hoveredObject, playerColor) end) + Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?", function () performDiscard(playerColor, hoveredObject) end) return end - performDiscard(hoveredObject, playerColor) + performDiscard(playerColor, hoveredObject) end -function performDiscard(hoveredObject, playerColor) +function performDiscard(playerColor, hoveredObject) -- initialize list of objects to discard local discardTheseObjects = { hoveredObject } @@ -174,7 +174,7 @@ function discardTopDeck(playerColor, hoveredObject) else takenCard = hoveredObject end - Wait.frames(function() performDiscard(takenCard, playerColor) end, 1) + Wait.frames(function() performDiscard(playerColor, hoveredObject) end, 1) end -- helper function to get the player to trigger the discard function for From 7ce026fb3ddb7ea6b30643efead0133683441e7e Mon Sep 17 00:00:00 2001 From: Whimsical-Cloudheart <88459121+Whimsical-Cloudheart@users.noreply.github.com> Date: Tue, 28 May 2024 08:54:17 -0300 Subject: [PATCH 20/21] Update GameKeyHandler.ttslua --- src/core/GameKeyHandler.ttslua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua index adb2d4bf..e3faced4 100644 --- a/src/core/GameKeyHandler.ttslua +++ b/src/core/GameKeyHandler.ttslua @@ -174,7 +174,7 @@ function discardTopDeck(playerColor, hoveredObject) else takenCard = hoveredObject end - Wait.frames(function() performDiscard(playerColor, hoveredObject) end, 1) + Wait.frames(function() performDiscard(playerColor, takenCard) end, 1) end -- helper function to get the player to trigger the discard function for From b2d9ef5cd65704eb32085e66f5eca2cfc75c1521 Mon Sep 17 00:00:00 2001 From: Chr1Z93 Date: Wed, 29 May 2024 00:30:43 +0200 Subject: [PATCH 21/21] fixed bug with index + formatting --- src/playercards/AllCardsBag.ttslua | 129 +++++++++++++------------- src/playercards/AllCardsBagApi.ttslua | 6 +- 2 files changed, 67 insertions(+), 68 deletions(-) diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index c08978e2..0e961400 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -1,10 +1,10 @@ local guidReferenceApi = require("core/GUIDReferenceApi") -local cardIdIndex = { } -local classAndLevelIndex = { } -local basicWeaknessList = { } -local uniqueWeaknessList = { } -local cycleIndex = { } +local cardIdIndex = {} +local classAndLevelIndex = {} +local basicWeaknessList = {} +local uniqueWeaknessList = {} +local cycleIndex = {} local indexingDone = false local otherCardsDetected = false @@ -29,23 +29,23 @@ end -- Resets all current bag indexes function clearIndexes() indexingDone = false - cardIdIndex = { } - classAndLevelIndex = { } - classAndLevelIndex["Guardian-upgrade"] = { } - classAndLevelIndex["Seeker-upgrade"] = { } - classAndLevelIndex["Mystic-upgrade"] = { } - classAndLevelIndex["Survivor-upgrade"] = { } - classAndLevelIndex["Rogue-upgrade"] = { } - classAndLevelIndex["Neutral-upgrade"] = { } - classAndLevelIndex["Guardian-level0"] = { } - classAndLevelIndex["Seeker-level0"] = { } - classAndLevelIndex["Mystic-level0"] = { } - classAndLevelIndex["Survivor-level0"] = { } - classAndLevelIndex["Rogue-level0"] = { } - classAndLevelIndex["Neutral-level0"] = { } - cycleIndex = { } - basicWeaknessList = { } - uniqueWeaknessList = { } + cardIdIndex = {} + classAndLevelIndex = {} + classAndLevelIndex["Guardian-upgrade"] = {} + classAndLevelIndex["Seeker-upgrade"] = {} + classAndLevelIndex["Mystic-upgrade"] = {} + classAndLevelIndex["Survivor-upgrade"] = {} + classAndLevelIndex["Rogue-upgrade"] = {} + classAndLevelIndex["Neutral-upgrade"] = {} + classAndLevelIndex["Guardian-level0"] = {} + classAndLevelIndex["Seeker-level0"] = {} + classAndLevelIndex["Mystic-level0"] = {} + classAndLevelIndex["Survivor-level0"] = {} + classAndLevelIndex["Rogue-level0"] = {} + classAndLevelIndex["Neutral-level0"] = {} + cycleIndex = {} + basicWeaknessList = {} + uniqueWeaknessList = {} end -- Clears the bag indexes and starts the coroutine to rebuild the indexes @@ -88,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 @@ -98,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 @@ -121,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 @@ -146,35 +150,32 @@ 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) @@ -191,9 +192,9 @@ function buildSupplementalIndexes() -- maybe initialize table if cycleIndex[cycleName] == nil then - cycleIndex[cycleName] = { } + cycleIndex[cycleName] = {} end - table.insert(cycleIndex[cycleName], cardMetadata.id) + table.insert(cycleIndex[cycleName], card.metadata.id) end end end @@ -236,7 +237,7 @@ 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}) + broadcastToAll("Still loading player cards, please try again in a few seconds", { 0.9, 0.2, 0.2 }) end return indexingDone end @@ -263,11 +264,9 @@ end function getCardsByClassAndLevel(params) if not isIndexReady() then return {} end - local upgradeKey + local upgradeKey = "-level0" if params.upgraded then upgradeKey = "-upgrade" - else - upgradeKey = "-level0" end return classAndLevelIndex[params.class .. upgradeKey] end @@ -347,31 +346,31 @@ 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 = { } + local results = {} + -- Track cards (by ID) that we've added to avoid duplicates that may come from alternate IDs - local addedCards = { } + local addedCards = {} for _, cardData in pairs(cardIdIndex) do if (not addedCards[cardData.metadata.id]) then if (exact and (string.lower(cardData.data.Nickname) == string.lower(name))) or (not exact and string.find(string.lower(cardData.data.Nickname), string.lower(name), 1, true)) then - table.insert(results, cardData) - addedCards[cardData.metadata.id] = true + table.insert(results, cardData) + addedCards[cardData.metadata.id] = true end end end 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 @@ -381,9 +380,9 @@ 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 weaknessesInPlay = {} local allObjects = getAllObjects() for _, object in ipairs(allObjects) do if object.type == "Deck" then @@ -395,7 +394,7 @@ function buildAvailableWeaknesses() end end - local availableWeaknesses = { } + local availableWeaknesses = {} for _, weaknessId in ipairs(basicWeaknessList) do if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1 @@ -427,7 +426,7 @@ end function isBasicWeakness(cardMetadata) return cardMetadata ~= nil - and cardMetadata.weakness - and cardMetadata.basicWeaknessCount ~= nil - and cardMetadata.basicWeaknessCount > 0 + and cardMetadata.weakness + and cardMetadata.basicWeaknessCount ~= nil + and cardMetadata.basicWeaknessCount > 0 end diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index e43e693a..f2f4cc3b 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -28,7 +28,7 @@ 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. - ---@return string: ID of the selected weakness. + ---@return string: ID of the selected weakness AllCardsBagApi.getRandomWeaknessId = function() return getAllCardsBag().call("getRandomWeaknessId") end @@ -46,8 +46,8 @@ do 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)