Merge branch 'main' into hand-helper
This commit is contained in:
commit
11b3b332c5
@ -20,6 +20,7 @@
|
|||||||
"Note": "",
|
"Note": "",
|
||||||
"ObjectStates_order": [
|
"ObjectStates_order": [
|
||||||
"GUIDReferenceHandler.123456",
|
"GUIDReferenceHandler.123456",
|
||||||
|
"GameKeyHandler.fce69c",
|
||||||
"TokenSpawnTracker.e3ffc9",
|
"TokenSpawnTracker.e3ffc9",
|
||||||
"HandTrigger.5fe087",
|
"HandTrigger.5fe087",
|
||||||
"HandTrigger.be2f17",
|
"HandTrigger.be2f17",
|
||||||
@ -196,7 +197,6 @@
|
|||||||
"Fan-MadeExpansionOverview.de7cae",
|
"Fan-MadeExpansionOverview.de7cae",
|
||||||
"OptionPanelSource.830bd0",
|
"OptionPanelSource.830bd0",
|
||||||
"SoundCube.3c988f",
|
"SoundCube.3c988f",
|
||||||
"GameKeyHandler.fce69c",
|
|
||||||
"TokenSpawningReference.f8b3a7",
|
"TokenSpawningReference.f8b3a7",
|
||||||
"3DText.d628cc",
|
"3DText.d628cc",
|
||||||
"NavigationOverlayHandler.797ede",
|
"NavigationOverlayHandler.797ede",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"1": {
|
"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",
|
"color": "Grey",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "Basic Intro",
|
"title": "Basic Intro",
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"IgnoreFoW": false,
|
"IgnoreFoW": false,
|
||||||
"LayoutGroupSortIndex": 0,
|
"LayoutGroupSortIndex": 0,
|
||||||
"Locked": false,
|
"Locked": false,
|
||||||
"LuaScript": "do_not_ready = true",
|
"LuaScript": "",
|
||||||
"LuaScriptState": "",
|
"LuaScriptState": "",
|
||||||
"MeasureMovement": false,
|
"MeasureMovement": false,
|
||||||
"Name": "Card",
|
"Name": "Card",
|
||||||
@ -43,6 +43,7 @@
|
|||||||
"Sticky": true,
|
"Sticky": true,
|
||||||
"Tags": [
|
"Tags": [
|
||||||
"Asset",
|
"Asset",
|
||||||
|
"DoNotReady",
|
||||||
"PlayerCard"
|
"PlayerCard"
|
||||||
],
|
],
|
||||||
"Tooltip": true,
|
"Tooltip": true,
|
||||||
|
@ -53,9 +53,9 @@
|
|||||||
"rotX": 0,
|
"rotX": 0,
|
||||||
"rotY": 180,
|
"rotY": 180,
|
||||||
"rotZ": 0,
|
"rotZ": 0,
|
||||||
"scaleX": 1,
|
"scaleX": 1.15,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"scaleZ": 1
|
"scaleZ": 1.15
|
||||||
},
|
},
|
||||||
"Value": 0,
|
"Value": 0,
|
||||||
"XmlUI": ""
|
"XmlUI": ""
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
"LayoutGroupSortIndex": 0,
|
"LayoutGroupSortIndex": 0,
|
||||||
"Locked": true,
|
"Locked": true,
|
||||||
"LuaScript": "require(\"util/ConnectionDrawingTool\")",
|
"LuaScript": "require(\"util/ConnectionDrawingTool\")",
|
||||||
"LuaScriptState": "{\"e8e04b\":[]}",
|
"LuaScriptState": "{\"connections\":[]}",
|
||||||
"MeasureMovement": false,
|
"MeasureMovement": false,
|
||||||
"Name": "Custom_Token",
|
"Name": "Custom_Token",
|
||||||
"Nickname": "Drawing Tool",
|
"Nickname": "Drawing Tool",
|
||||||
|
@ -22,6 +22,13 @@
|
|||||||
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/2342503777940937086/92256BDF101E6272AD1E3F5F0043D311DF708F03/",
|
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/2342503777940937086/92256BDF101E6272AD1E3F5F0043D311DF708F03/",
|
||||||
"WidthScale": 0
|
"WidthScale": 0
|
||||||
},
|
},
|
||||||
|
"CustomUIAssets": [
|
||||||
|
{
|
||||||
|
"Name": "OtherCards",
|
||||||
|
"Type": 0,
|
||||||
|
"URL": "http://cloud-3.steamusercontent.com/ugc/2446096169989812196/B5C491331EB348C261F561DC7A19968ECF9FC74A/"
|
||||||
|
}
|
||||||
|
],
|
||||||
"Description": "",
|
"Description": "",
|
||||||
"DragSelectable": true,
|
"DragSelectable": true,
|
||||||
"GMNotes": "",
|
"GMNotes": "",
|
||||||
@ -53,5 +60,5 @@
|
|||||||
"scaleZ": 10
|
"scaleZ": 10
|
||||||
},
|
},
|
||||||
"Value": 0,
|
"Value": 0,
|
||||||
"XmlUI": "\u003cInclude src=\"playercards/PlayerCardPanel.xml\"/\u003e"
|
"XmlUI": ""
|
||||||
}
|
}
|
||||||
|
@ -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]}}
|
||||||
|
@ -161,12 +161,12 @@ function restoreCampaignData(importData, coin)
|
|||||||
deckImporterApi.setUiState(importData["decks"])
|
deckImporterApi.setUiState(importData["decks"])
|
||||||
end
|
end
|
||||||
|
|
||||||
-- maybe set campaign guide page
|
-- maybe set campaign guide page (unless it was on the first page)
|
||||||
if importData["guide"] then
|
if importData["guide"] and importData["guide"] ~= 0 then
|
||||||
local campaignGuide = findUniqueObjectWithTag("CampaignGuide")
|
local campaignGuide = findUniqueObjectWithTag("CampaignGuide")
|
||||||
if campaignGuide then
|
if campaignGuide then
|
||||||
Wait.condition(
|
Wait.condition(
|
||||||
-- Called after the condition function returns true
|
-- Called after the condition function returns true
|
||||||
function() printToAll("Campaign Guide import successful!") end,
|
function() printToAll("Campaign Guide import successful!") end,
|
||||||
-- Condition function that is called continuously until it returns true or timeout is reached
|
-- Condition function that is called continuously until it returns true or timeout is reached
|
||||||
function() return campaignGuide.Book.setPage(importData["guide"]) end,
|
function() return campaignGuide.Book.setPage(importData["guide"]) end,
|
||||||
@ -190,6 +190,13 @@ function restoreCampaignData(importData, coin)
|
|||||||
playAreaApi.updateSurface(importData["playarea"])
|
playAreaApi.updateSurface(importData["playarea"])
|
||||||
playAreaApi.setInvestigatorCount(importData["clueCount"])
|
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()
|
coin.destruct()
|
||||||
broadcastToAll("Campaign successfully imported!", "Green")
|
broadcastToAll("Campaign successfully imported!", "Green")
|
||||||
end
|
end
|
||||||
@ -265,6 +272,13 @@ function createCampaignToken(_, playerColor, _)
|
|||||||
table.insert(campaignTokenData.ContainedObjects, indexData)
|
table.insert(campaignTokenData.ContainedObjects, indexData)
|
||||||
end
|
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
|
-- finish the data for the campaign token
|
||||||
campaignTokenData.GMNotes = JSON.encode(campaignData)
|
campaignTokenData.GMNotes = JSON.encode(campaignData)
|
||||||
campaignTokenData.Nickname = campaignBox.getName() .. os.date(" %b %d") .. " Save"
|
campaignTokenData.Nickname = campaignBox.getName() .. os.date(" %b %d") .. " Save"
|
||||||
|
@ -11,7 +11,8 @@ local phaseImages = {
|
|||||||
"http://cloud-3.steamusercontent.com/ugc/982233321870237261/C287CAED2423970F33E72D6C7415CBEC6794C533/"
|
"http://cloud-3.steamusercontent.com/ugc/982233321870237261/C287CAED2423970F33E72D6C7415CBEC6794C533/"
|
||||||
}
|
}
|
||||||
|
|
||||||
local phaseId, broadcastChange
|
-- these are intentionally global for remote updating
|
||||||
|
-- phaseId, broadcastChange
|
||||||
|
|
||||||
function onSave()
|
function onSave()
|
||||||
return JSON.encode({
|
return JSON.encode({
|
||||||
|
@ -12,16 +12,18 @@ buttonParameters.height = 325
|
|||||||
|
|
||||||
local inputParameters = {}
|
local inputParameters = {}
|
||||||
inputParameters.function_owner = self
|
inputParameters.function_owner = self
|
||||||
inputParameters.font_size = 100
|
inputParameters.font_size = 200
|
||||||
inputParameters.width = 250
|
inputParameters.width = 500
|
||||||
inputParameters.height = inputParameters.font_size + 23
|
inputParameters.height = inputParameters.font_size + 46
|
||||||
inputParameters.alignment = 3
|
inputParameters.alignment = 3
|
||||||
inputParameters.validation = 2
|
inputParameters.validation = 2
|
||||||
inputParameters.tab = 2
|
inputParameters.tab = 2
|
||||||
|
inputParameters.scale = { 0.5, 0.5, 0.5 }
|
||||||
|
|
||||||
local percentageLabel = {}
|
local percentageLabel = {}
|
||||||
percentageLabel.function_owner = self
|
percentageLabel.function_owner = self
|
||||||
percentageLabel.click_function = "none"
|
percentageLabel.click_function = "none"
|
||||||
|
percentageLabel.font_size = 200
|
||||||
percentageLabel.width = 0
|
percentageLabel.width = 0
|
||||||
percentageLabel.height = 0
|
percentageLabel.height = 0
|
||||||
|
|
||||||
@ -69,13 +71,13 @@ function onLoad(savedData)
|
|||||||
end
|
end
|
||||||
|
|
||||||
createButtonsAndInputs()
|
createButtonsAndInputs()
|
||||||
|
|
||||||
-- maybe trigger layout() to draw percentage buttons
|
-- maybe trigger layout() to draw percentage buttons
|
||||||
local objList = getObjectsWithTag("tempToken")
|
local objList = getObjectsWithTag("tempToken")
|
||||||
if #objList > 0 then
|
if #objList > 0 then
|
||||||
Wait.time(layout, 0.5)
|
Wait.time(layout, 0.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- context menu items
|
-- context menu items
|
||||||
self.addContextMenuItem("Load default values", function()
|
self.addContextMenuItem("Load default values", function()
|
||||||
loadDefaultValues()
|
loadDefaultValues()
|
||||||
@ -153,10 +155,10 @@ function loadDefaultValues()
|
|||||||
["Tablet"] = { -3, 5},
|
["Tablet"] = { -3, 5},
|
||||||
["Elder Thing"] = { -4, 6},
|
["Elder Thing"] = { -4, 6},
|
||||||
["Auto-fail"] = { -100, 7},
|
["Auto-fail"] = { -100, 7},
|
||||||
["Bless"] = { 101, 8},
|
["Bless"] = { 110, 8},
|
||||||
["Curse"] = { -101, 9},
|
["Curse"] = { -110, 9},
|
||||||
["Frost"] = { -99, 10},
|
["Frost"] = { -105, 10},
|
||||||
[""] = { 0, 11}
|
[""] = { 0, 11}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -195,9 +197,11 @@ function createButtonsAndInputs()
|
|||||||
click_function = "layout",
|
click_function = "layout",
|
||||||
tooltip = "Left-Click: Update!\nRight-Click: Hide Tokens!",
|
tooltip = "Left-Click: Update!\nRight-Click: Hide Tokens!",
|
||||||
position = { 0.725, 0.1, 2.025 },
|
position = { 0.725, 0.1, 2.025 },
|
||||||
|
scale = { 0.5, 0.5, 0.5 },
|
||||||
color = { 1, 1, 1 },
|
color = { 1, 1, 1 },
|
||||||
width = 675,
|
font_size = 200,
|
||||||
height = 175
|
width = 1350,
|
||||||
|
height = 325
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -238,14 +242,14 @@ function deleteCopiedTokens()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- creates buttons as labels as display for percentage values
|
-- creates buttons as labels as display for percentage values
|
||||||
function createPercentageButton(tokenCount, valueCount, tokenName)
|
function createPercentageButton(tokenCount, rowCount, tokenName)
|
||||||
local startPos = Vector(2.3, -0.04, 0.875 * valueCount)
|
local startPos = Vector(2.3, -0.04, 0.875 * rowCount)
|
||||||
|
|
||||||
if percentage == "cumulative" then
|
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)
|
percentageLabel.position = startPos - Vector(0, 0, 2.85)
|
||||||
else
|
else
|
||||||
percentageLabel.scale = { 2, 2, 2 }
|
percentageLabel.scale = { 1, 1, 1 }
|
||||||
percentageLabel.position = startPos - Vector(0, 0, 2.675)
|
percentageLabel.position = startPos - Vector(0, 0, 2.675)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -254,8 +258,8 @@ function createPercentageButton(tokenCount, valueCount, tokenName)
|
|||||||
percentageLabel.font_color = { 0.35, 0.71, 0.85 }
|
percentageLabel.font_color = { 0.35, 0.71, 0.85 }
|
||||||
elseif tokenName == "Auto-fail" then
|
elseif tokenName == "Auto-fail" then
|
||||||
percentageLabel.font_color = { 0.86, 0.1, 0.1 }
|
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
|
elseif string.match(tokenName, "%a") ~= nil then
|
||||||
|
-- tokenName contains letters (e.g. symbol token)
|
||||||
percentageLabel.font_color = { 0.68, 0.53, 0.86 }
|
percentageLabel.font_color = { 0.68, 0.53, 0.86 }
|
||||||
else
|
else
|
||||||
percentageLabel.font_color = { 0.85, 0.67, 0.33 }
|
percentageLabel.font_color = { 0.85, 0.67, 0.33 }
|
||||||
@ -302,12 +306,14 @@ function layout(_, _, isRightClick)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- clone tokens from chaos bag (default position above trash can)
|
-- 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
|
-- optionally get the data for tokens in play
|
||||||
if includeDrawnTokens then
|
if includeDrawnTokens then
|
||||||
for _, token in pairs(chaosBagApi.getTokensInPlay()) do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -335,8 +341,15 @@ function layout(_, _, isRightClick)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- sort table by value (symbols last if same value)
|
-- handling for near empty chaos bag
|
||||||
table.sort(data, tokenValueComparator)
|
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
|
-- laying out the tokens
|
||||||
local pos = self.getPosition() + Vector(3.55, -0.05, -3.95)
|
local pos = self.getPosition() + Vector(3.55, -0.05, -3.95)
|
||||||
@ -346,21 +359,21 @@ function layout(_, _, isRightClick)
|
|||||||
local rotation = self.getRotation()
|
local rotation = self.getRotation()
|
||||||
local currentValue = data[1].value
|
local currentValue = data[1].value
|
||||||
local tokenCount = { row = 0, sum = 0, total = #data }
|
local tokenCount = { row = 0, sum = 0, total = #data }
|
||||||
local valueCount = 1
|
local rowCount = 1
|
||||||
local tokenName = false
|
local tokenName
|
||||||
|
|
||||||
for i, item in ipairs(data) do
|
for _, item in ipairs(data) do
|
||||||
-- this is true for the first token in a new row
|
-- this is true for the first token in a new row
|
||||||
if item.value ~= currentValue then
|
if item.value ~= currentValue then
|
||||||
if percentage then
|
if percentage then
|
||||||
tokenCount.sum = tokenCount.sum + tokenCount.row
|
tokenCount.sum = tokenCount.sum + tokenCount.row
|
||||||
createPercentageButton(tokenCount, valueCount, tokenName)
|
createPercentageButton(tokenCount, rowCount, tokenName)
|
||||||
end
|
end
|
||||||
|
|
||||||
location.x = location.x - 1.75
|
location.x = location.x - 1.75
|
||||||
location.z = pos.z
|
location.z = pos.z
|
||||||
currentValue = item.value
|
currentValue = item.value
|
||||||
valueCount = valueCount + 1
|
rowCount = rowCount + 1
|
||||||
tokenCount.row = 0
|
tokenCount.row = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -377,21 +390,17 @@ function layout(_, _, isRightClick)
|
|||||||
-- this is repeated to create the button for the last token
|
-- this is repeated to create the button for the last token
|
||||||
if percentage then
|
if percentage then
|
||||||
tokenCount.sum = tokenCount.sum + tokenCount.row
|
tokenCount.sum = tokenCount.sum + tokenCount.row
|
||||||
createPercentageButton(tokenCount, valueCount, tokenName)
|
createPercentageButton(tokenCount, rowCount, tokenName)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- introducing a small delay to limit update calls
|
-- small delay to limit update calls
|
||||||
Wait.time(function() updating = false end, 0.1)
|
Wait.time(function() updating = false end, 0.1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- called from outside to set default values for tokens
|
-- called from outside to set default values for tokens
|
||||||
function onTokenDataChanged(parameters)
|
function onTokenDataChanged(parameters)
|
||||||
local tokenData = parameters.tokenData or {}
|
|
||||||
local currentScenario = parameters.currentScenario or ""
|
|
||||||
local useFrontData = parameters.useFrontData
|
|
||||||
|
|
||||||
-- update token precedence
|
-- update token precedence
|
||||||
for key, table in pairs(tokenData) do
|
for key, table in pairs(parameters.tokenData or {}) do
|
||||||
local modifier = table.modifier
|
local modifier = table.modifier
|
||||||
if modifier == -999 then modifier = 0 end
|
if modifier == -999 then modifier = 0 end
|
||||||
tokenPrecedence[key][1] = modifier
|
tokenPrecedence[key][1] = modifier
|
||||||
|
@ -13,7 +13,7 @@ do
|
|||||||
---@class Request
|
---@class Request
|
||||||
local 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()
|
ArkhamDb.initialize = function()
|
||||||
configuration = internal.getConfiguration()
|
configuration = internal.getConfiguration()
|
||||||
Request.start({ configuration.api_uri, configuration.taboo }, function(status)
|
Request.start({ configuration.api_uri, configuration.taboo }, function(status)
|
||||||
@ -71,13 +71,14 @@ do
|
|||||||
|
|
||||||
local deck = Request.start(deckUri, function(status)
|
local deck = Request.start(deckUri, function(status)
|
||||||
if string.find(status.text, "<!DOCTYPE html>") then
|
if string.find(status.text, "<!DOCTYPE html>") 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"
|
return false, "Private deck " .. deckId .. " is not shared"
|
||||||
end
|
end
|
||||||
local json = JSON.decode(status.text)
|
|
||||||
|
local json = JSON.decode(internal.fixUtf16String(status.text))
|
||||||
|
|
||||||
if not json then
|
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!"
|
return false, "Deck not found!"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -177,8 +178,8 @@ do
|
|||||||
local randomWeaknessAmount = slots[RANDOM_WEAKNESS_ID] or 0
|
local randomWeaknessAmount = slots[RANDOM_WEAKNESS_ID] or 0
|
||||||
slots[RANDOM_WEAKNESS_ID] = nil
|
slots[RANDOM_WEAKNESS_ID] = nil
|
||||||
|
|
||||||
if randomWeaknessAmount ~= 0 then
|
if randomWeaknessAmount > 0 then
|
||||||
for i=1, randomWeaknessAmount do
|
for i = 1, randomWeaknessAmount do
|
||||||
local weaknessId = allCardsBagApi.getRandomWeaknessId()
|
local weaknessId = allCardsBagApi.getRandomWeaknessId()
|
||||||
slots[weaknessId] = (slots[weaknessId] or 0) + 1
|
slots[weaknessId] = (slots[weaknessId] or 0) + 1
|
||||||
end
|
end
|
||||||
|
@ -150,11 +150,8 @@ end
|
|||||||
|
|
||||||
-- Event handlers for deck ID change
|
-- Event handlers for deck ID change
|
||||||
function redDeckChanged(_, _, inputValue) redDeckId = inputValue end
|
function redDeckChanged(_, _, inputValue) redDeckId = inputValue end
|
||||||
|
|
||||||
function orangeDeckChanged(_, _, inputValue) orangeDeckId = inputValue end
|
function orangeDeckChanged(_, _, inputValue) orangeDeckId = inputValue end
|
||||||
|
|
||||||
function whiteDeckChanged(_, _, inputValue) whiteDeckId = inputValue end
|
function whiteDeckChanged(_, _, inputValue) whiteDeckId = inputValue end
|
||||||
|
|
||||||
function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end
|
function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end
|
||||||
|
|
||||||
-- Event handlers for toggle buttons
|
-- Event handlers for toggle buttons
|
||||||
@ -174,14 +171,7 @@ function loadInvestigatorsChanged()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function loadDecks()
|
function loadDecks()
|
||||||
-- testLoadLotsOfDecks()
|
if not allCardsBagApi.isIndexReady() then return end
|
||||||
-- 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 (redDeckId ~= nil and redDeckId ~= "") then
|
if (redDeckId ~= nil and redDeckId ~= "") then
|
||||||
buildDeck("Red", redDeckId)
|
buildDeck("Red", redDeckId)
|
||||||
end
|
end
|
||||||
|
@ -135,11 +135,17 @@ function discardObject(playerColor, hoveredObject)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- warning for locations since these are usually not meant to be discarded
|
-- These should probably not be discarded normally. Ask player for confirmation.
|
||||||
if hoveredObject.hasTag("Location") then
|
if (hoveredObject.type == "Deck") or hoveredObject.hasTag("Location") then
|
||||||
broadcastToAll("Watch out: A location was discarded.", "Yellow")
|
local suspect = (hoveredObject.type == "Deck") and "Deck" or "Location"
|
||||||
|
Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?", function () performDiscard(playerColor, hoveredObject) end)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
performDiscard(playerColor, hoveredObject)
|
||||||
|
end
|
||||||
|
|
||||||
|
function performDiscard(playerColor, hoveredObject)
|
||||||
-- initialize list of objects to discard
|
-- initialize list of objects to discard
|
||||||
local discardTheseObjects = { hoveredObject }
|
local discardTheseObjects = { hoveredObject }
|
||||||
|
|
||||||
@ -168,7 +174,7 @@ function discardTopDeck(playerColor, hoveredObject)
|
|||||||
else
|
else
|
||||||
takenCard = hoveredObject
|
takenCard = hoveredObject
|
||||||
end
|
end
|
||||||
Wait.frames(function() discardObject(playerColor, takenCard) end, 1)
|
Wait.frames(function() performDiscard(playerColor, takenCard) end, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- helper function to get the player to trigger the discard function for
|
-- helper function to get the player to trigger the discard function for
|
||||||
|
@ -222,6 +222,11 @@ function onObjectNumberTyped(hoveredObject, playerColor, number)
|
|||||||
end
|
end
|
||||||
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
|
-- chaos token drawing
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
local cardIdIndex = { }
|
local guidReferenceApi = require("core/GUIDReferenceApi")
|
||||||
local classAndLevelIndex = { }
|
|
||||||
local basicWeaknessList = { }
|
local cardIdIndex = {}
|
||||||
local uniqueWeaknessList = { }
|
local classAndLevelIndex = {}
|
||||||
local cycleIndex = { }
|
local basicWeaknessList = {}
|
||||||
|
local uniqueWeaknessList = {}
|
||||||
|
local cycleIndex = {}
|
||||||
|
|
||||||
local indexingDone = false
|
local indexingDone = false
|
||||||
|
local otherCardsDetected = false
|
||||||
|
|
||||||
function onLoad()
|
function onLoad()
|
||||||
self.addContextMenuItem("Rebuild Index", startIndexBuild)
|
self.addContextMenuItem("Rebuild Index", startIndexBuild)
|
||||||
@ -12,13 +15,13 @@ function onLoad()
|
|||||||
Wait.frames(startIndexBuild, 30)
|
Wait.frames(startIndexBuild, 30)
|
||||||
end
|
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
|
-- 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
|
-- 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.
|
-- later, and we should rebuild the index to integrate the hotfix bag.
|
||||||
function rebuildIndexForHotfix()
|
function rebuildIndexForHotfix()
|
||||||
if (indexingDone) then
|
if indexingDone then
|
||||||
startIndexBuild()
|
startIndexBuild()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -26,23 +29,23 @@ end
|
|||||||
-- Resets all current bag indexes
|
-- Resets all current bag indexes
|
||||||
function clearIndexes()
|
function clearIndexes()
|
||||||
indexingDone = false
|
indexingDone = false
|
||||||
cardIdIndex = { }
|
cardIdIndex = {}
|
||||||
classAndLevelIndex = { }
|
classAndLevelIndex = {}
|
||||||
classAndLevelIndex["Guardian-upgrade"] = { }
|
classAndLevelIndex["Guardian-upgrade"] = {}
|
||||||
classAndLevelIndex["Seeker-upgrade"] = { }
|
classAndLevelIndex["Seeker-upgrade"] = {}
|
||||||
classAndLevelIndex["Mystic-upgrade"] = { }
|
classAndLevelIndex["Mystic-upgrade"] = {}
|
||||||
classAndLevelIndex["Survivor-upgrade"] = { }
|
classAndLevelIndex["Survivor-upgrade"] = {}
|
||||||
classAndLevelIndex["Rogue-upgrade"] = { }
|
classAndLevelIndex["Rogue-upgrade"] = {}
|
||||||
classAndLevelIndex["Neutral-upgrade"] = { }
|
classAndLevelIndex["Neutral-upgrade"] = {}
|
||||||
classAndLevelIndex["Guardian-level0"] = { }
|
classAndLevelIndex["Guardian-level0"] = {}
|
||||||
classAndLevelIndex["Seeker-level0"] = { }
|
classAndLevelIndex["Seeker-level0"] = {}
|
||||||
classAndLevelIndex["Mystic-level0"] = { }
|
classAndLevelIndex["Mystic-level0"] = {}
|
||||||
classAndLevelIndex["Survivor-level0"] = { }
|
classAndLevelIndex["Survivor-level0"] = {}
|
||||||
classAndLevelIndex["Rogue-level0"] = { }
|
classAndLevelIndex["Rogue-level0"] = {}
|
||||||
classAndLevelIndex["Neutral-level0"] = { }
|
classAndLevelIndex["Neutral-level0"] = {}
|
||||||
cycleIndex = { }
|
cycleIndex = {}
|
||||||
basicWeaknessList = { }
|
basicWeaknessList = {}
|
||||||
uniqueWeaknessList = { }
|
uniqueWeaknessList = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clears the bag indexes and starts the coroutine to rebuild the indexes
|
-- Clears the bag indexes and starts the coroutine to rebuild the indexes
|
||||||
@ -63,6 +66,7 @@ end
|
|||||||
function buildIndex()
|
function buildIndex()
|
||||||
local cardCount = 0
|
local cardCount = 0
|
||||||
indexingDone = false
|
indexingDone = false
|
||||||
|
otherCardsDetected = false
|
||||||
|
|
||||||
-- process the allcardsbag itself
|
-- process the allcardsbag itself
|
||||||
for _, cardData in ipairs(self.getData().ContainedObjects) do
|
for _, cardData in ipairs(self.getData().ContainedObjects) do
|
||||||
@ -84,8 +88,8 @@ function buildIndex()
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, cardData in ipairs(hotfixData.ContainedObjects) do
|
for _, cardData in ipairs(hotfixData.ContainedObjects) do
|
||||||
-- process containers
|
|
||||||
if cardData.ContainedObjects then
|
if cardData.ContainedObjects then
|
||||||
|
-- process containers
|
||||||
for _, deepCardData in ipairs(cardData.ContainedObjects) do
|
for _, deepCardData in ipairs(cardData.ContainedObjects) do
|
||||||
addCardToIndex(deepCardData)
|
addCardToIndex(deepCardData)
|
||||||
cardCount = cardCount + 1
|
cardCount = cardCount + 1
|
||||||
@ -94,8 +98,8 @@ function buildIndex()
|
|||||||
coroutine.yield(0)
|
coroutine.yield(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- process single cards
|
|
||||||
else
|
else
|
||||||
|
-- process single cards
|
||||||
addCardToIndex(cardData)
|
addCardToIndex(cardData)
|
||||||
cardCount = cardCount + 1
|
cardCount = cardCount + 1
|
||||||
if cardCount > 19 then
|
if cardCount > 19 then
|
||||||
@ -108,6 +112,7 @@ function buildIndex()
|
|||||||
end
|
end
|
||||||
|
|
||||||
buildSupplementalIndexes()
|
buildSupplementalIndexes()
|
||||||
|
updatePlayerCardPanel()
|
||||||
indexingDone = true
|
indexingDone = true
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
@ -116,8 +121,19 @@ end
|
|||||||
---@param cardData table TTS object data for the card
|
---@param cardData table TTS object data for the card
|
||||||
function addCardToIndex(cardData)
|
function addCardToIndex(cardData)
|
||||||
-- using the more efficient 'json.parse()' to speed this process up
|
-- using the more efficient 'json.parse()' to speed this process up
|
||||||
local cardMetadata = json.parse(cardData.GMNotes)
|
local status, cardMetadata = pcall(function() return json.parse(cardData.GMNotes) end)
|
||||||
if not cardMetadata then return end
|
|
||||||
|
-- if an error happens, fallback to the regular parser
|
||||||
|
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 == nil then
|
||||||
|
log("Error parsing " .. cardData.Nickname)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- use the ZoopGuid as fallback if no id present
|
-- use the ZoopGuid as fallback if no id present
|
||||||
cardMetadata.id = cardMetadata.id or cardMetadata.TtsZoopGuid
|
cardMetadata.id = cardMetadata.id or cardMetadata.TtsZoopGuid
|
||||||
@ -131,37 +147,35 @@ function addCardToIndex(cardData)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Creates the supplemental indexes for classes, weaknesses etc.
|
||||||
function buildSupplementalIndexes()
|
function buildSupplementalIndexes()
|
||||||
for cardId, card in pairs(cardIdIndex) do
|
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 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
|
-- Add card to the basic weakness list, if appropriate. Some weaknesses have multiple copies, and are added multiple times
|
||||||
if cardMetadata.weakness then
|
if card.metadata.weakness then
|
||||||
table.insert(uniqueWeaknessList, cardMetadata.id)
|
table.insert(uniqueWeaknessList, card.metadata.id)
|
||||||
if cardMetadata.basicWeaknessCount ~= nil then
|
if card.metadata.basicWeaknessCount ~= nil then
|
||||||
for i = 1, cardMetadata.basicWeaknessCount do
|
for i = 1, card.metadata.basicWeaknessCount do
|
||||||
table.insert(basicWeaknessList, cardMetadata.id)
|
table.insert(basicWeaknessList, card.metadata.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Excludes signature cards (which have no class or level)
|
-- Excludes signature cards (which have no class or level)
|
||||||
if cardMetadata.class ~= nil and cardMetadata.level ~= nil then
|
if card.metadata.class ~= nil and card.metadata.level ~= nil then
|
||||||
local upgradeKey
|
local upgradeKey = "-level0"
|
||||||
if cardMetadata.level > 0 then
|
if card.metadata.level > 0 then
|
||||||
upgradeKey = "-upgrade"
|
upgradeKey = "-upgrade"
|
||||||
else
|
|
||||||
upgradeKey = "-level0"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- parse classes (separated by "|") and add the card to the appropriate class and level indices
|
-- parse classes (separated by "|") and add the card to the appropriate class and level indices
|
||||||
for str in cardMetadata.class:gmatch("([^|]+)") do
|
for str in card.metadata.class:gmatch("([^|]+)") do
|
||||||
table.insert(classAndLevelIndex[str .. upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex[str .. upgradeKey], card.metadata.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add to cycle index
|
-- add to cycle index
|
||||||
local cycleName = cardMetadata.cycle
|
local cycleName = card.metadata.cycle
|
||||||
if cycleName ~= nil then
|
if cycleName ~= nil then
|
||||||
cycleName = string.lower(cycleName)
|
cycleName = string.lower(cycleName)
|
||||||
|
|
||||||
@ -170,12 +184,17 @@ function buildSupplementalIndexes()
|
|||||||
|
|
||||||
-- override cycle name for night of the zealot
|
-- override cycle name for night of the zealot
|
||||||
cycleName = cycleName:gsub("the night of the zealot", "core")
|
cycleName = cycleName:gsub("the night of the zealot", "core")
|
||||||
|
else
|
||||||
if cycleIndex[cycleName] == nil then
|
-- track cards without defined cycle (should only be fan-made cards)
|
||||||
cycleIndex[cycleName] = { }
|
cycleName = "other"
|
||||||
end
|
otherCardsDetected = true
|
||||||
table.insert(cycleIndex[cycleName], cardMetadata.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- maybe initialize table
|
||||||
|
if cycleIndex[cycleName] == nil then
|
||||||
|
cycleIndex[cycleName] = {}
|
||||||
|
end
|
||||||
|
table.insert(cycleIndex[cycleName], card.metadata.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -209,109 +228,173 @@ function cardComparator(id1, id2)
|
|||||||
end
|
end
|
||||||
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()
|
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
|
return indexingDone
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Returns a specific card from the bag, based on ArkhamDB ID
|
-- Returns a specific card from the bag, based on ArkhamDB ID
|
||||||
-- Params table:
|
---@param params table ID of the card to retrieve
|
||||||
-- id: String ID of the card to retrieve
|
---@return table: If the indexes are still being constructed, returns an empty table.
|
||||||
-- Return: If the indexes are still being constructed, an empty table is
|
-- Otherwise, a single table with the following fields
|
||||||
-- returned. Otherwise, a single table with the following fields
|
-- data: TTS object data, suitable for spawning the card
|
||||||
-- cardData: TTS object data, suitable for spawning the card
|
-- metadata: Table of parsed metadata
|
||||||
-- cardMetadata: Table of parsed metadata
|
|
||||||
function getCardById(params)
|
function getCardById(params)
|
||||||
if (not indexingDone) then
|
if not isIndexReady() then return {} end
|
||||||
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
|
|
||||||
return { }
|
|
||||||
end
|
|
||||||
return cardIdIndex[params.id]
|
return cardIdIndex[params.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
|
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
|
||||||
-- Params table:
|
---@param params table
|
||||||
-- class: String class to retrieve ("Guardian", "Seeker", etc)
|
-- class: String class to retrieve ("Guardian", "Seeker", etc)
|
||||||
-- isUpgraded: true for upgraded cards (Level 1-5), false for Level 0
|
-- isUpgraded: true for upgraded cards (Level 1-5), false for Level 0
|
||||||
-- Return: If the indexes are still being constructed, returns an empty table.
|
---@return table: If the indexes are still being constructed, returns an empty table.
|
||||||
-- Otherwise, a list of tables, each with the following fields
|
-- Otherwise, a list of tables, each with the following fields
|
||||||
-- cardData: TTS object data, suitable for spawning the card
|
-- data: TTS object data, suitable for spawning the card
|
||||||
-- cardMetadata: Table of parsed metadata
|
-- metadata: Table of parsed metadata
|
||||||
function getCardsByClassAndLevel(params)
|
function getCardsByClassAndLevel(params)
|
||||||
if (not indexingDone) then
|
if not isIndexReady() then return {} end
|
||||||
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
|
|
||||||
return { }
|
local upgradeKey = "-level0"
|
||||||
end
|
if params.upgraded then
|
||||||
local upgradeKey
|
|
||||||
if (params.upgraded) then
|
|
||||||
upgradeKey = "-upgrade"
|
upgradeKey = "-upgrade"
|
||||||
|
end
|
||||||
|
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
|
||||||
|
-- data: TTS object data, suitable for spawning the card
|
||||||
|
-- metadata: Table of parsed metadata
|
||||||
|
function getCardsByCycle(params)
|
||||||
|
if not isIndexReady() then return {} end
|
||||||
|
|
||||||
|
if not params.sortByMetadata then
|
||||||
|
return cycleIndex[string.lower(params.cycle)]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- sort list by metadata (useful for custom cards without proper IDs)
|
||||||
|
local cardList = {}
|
||||||
|
for _, id in ipairs(cycleIndex[string.lower(params.cycle)]) do
|
||||||
|
table.insert(cardList, id)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(cardList, metadataSortFunction)
|
||||||
|
return cardList
|
||||||
|
end
|
||||||
|
|
||||||
|
-- sorts cards by metadata: class, type, level, name and then description
|
||||||
|
function metadataSortFunction(id1, id2)
|
||||||
|
local card1 = cardIdIndex[id1]
|
||||||
|
local card2 = cardIdIndex[id2]
|
||||||
|
|
||||||
|
-- extract class per card
|
||||||
|
local classValue1 = getClassValueFromString(card1.metadata.class)
|
||||||
|
local classValue2 = getClassValueFromString(card2.metadata.class)
|
||||||
|
|
||||||
|
-- conversion tables to simplify type sorting
|
||||||
|
local typeConversion = {
|
||||||
|
Asset = 1,
|
||||||
|
Event = 2,
|
||||||
|
Skill = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if classValue1 ~= classValue2 then
|
||||||
|
return classValue1 < classValue2
|
||||||
|
elseif typeConversion[card1.metadata.type] ~= typeConversion[card2.metadata.type] then
|
||||||
|
return typeConversion[card1.metadata.type] < typeConversion[card2.metadata.type]
|
||||||
|
elseif card1.metadata.level ~= card2.metadata.level then
|
||||||
|
return card1.metadata.level < card2.metadata.level
|
||||||
|
elseif card1.data.Nickname ~= card2.data.Nickname then
|
||||||
|
return card1.data.Nickname < card2.data.Nickname
|
||||||
else
|
else
|
||||||
upgradeKey = "-level0"
|
return card1.data.Description < card2.data.Description
|
||||||
end
|
end
|
||||||
return classAndLevelIndex[params.class..upgradeKey];
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function getCardsByCycle(cycleName)
|
-- helper function to calculate the class value for sorting from the "|" separated string
|
||||||
if (not indexingDone) then
|
function getClassValueFromString(s)
|
||||||
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
|
local classValueList = {
|
||||||
return { }
|
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
|
end
|
||||||
return cycleIndex[string.lower(cycleName)]
|
return classValue
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Searches the bag for cards which match the given name and returns a list. Note that this is
|
-- 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.
|
-- an O(n) search without index support. It may be slow.
|
||||||
-- Parameter array must contain these fields to define the search:
|
-- Parameter array must contain these fields to define the search:
|
||||||
-- name String or string fragment to search for names
|
-- name: String or string fragment to search for names
|
||||||
-- exact Whether the name match should be exact
|
-- exact: Whether the name match should be exact
|
||||||
function getCardsByName(params)
|
function getCardsByName(params)
|
||||||
local name = params.name
|
local name = params.name
|
||||||
local exact = params.exact
|
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
|
-- 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
|
for _, cardData in pairs(cardIdIndex) do
|
||||||
if (not addedCards[cardData.metadata.id]) then
|
if (not addedCards[cardData.metadata.id]) then
|
||||||
if (exact and (string.lower(cardData.data.Nickname) == string.lower(name)))
|
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
|
or (not exact and string.find(string.lower(cardData.data.Nickname), string.lower(name), 1, true)) then
|
||||||
table.insert(results, cardData)
|
table.insert(results, cardData)
|
||||||
addedCards[cardData.metadata.id] = true
|
addedCards[cardData.metadata.id] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return results
|
return results
|
||||||
end
|
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
|
||||||
-- it will be removed from the list and cannot be selected again until a reload
|
-- removed from the list and cannot be selected again until a reload occurs or the indexes
|
||||||
-- occurs or the indexes are rebuilt, which will refresh the list to include all
|
-- are rebuilt, which will refresh the list to include all weaknesses.
|
||||||
-- weaknesses.
|
---@return string: ID of the selected weakness
|
||||||
-- Return: String ID of the selected weakness.
|
|
||||||
function getRandomWeaknessId()
|
function getRandomWeaknessId()
|
||||||
local availableWeaknesses = buildAvailableWeaknesses()
|
local availableWeaknesses = buildAvailableWeaknesses()
|
||||||
if (#availableWeaknesses > 0) then
|
if #availableWeaknesses > 0 then
|
||||||
return availableWeaknesses[math.random(#availableWeaknesses)]
|
return availableWeaknesses[math.random(#availableWeaknesses)]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Constructs a list of available basic weaknesses by starting with the full pool of basic
|
-- Constructs a list of available basic weaknesses by starting with the full pool of basic
|
||||||
-- weaknesses then removing any which are currently in the play or deck construction areas
|
-- weaknesses then removing any which are currently in the play or deck construction areas
|
||||||
-- 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()
|
function buildAvailableWeaknesses()
|
||||||
local weaknessesInPlay = { }
|
local weaknessesInPlay = {}
|
||||||
local allObjects = getAllObjects()
|
local allObjects = getAllObjects()
|
||||||
for _, object in ipairs(allObjects) do
|
for _, object in ipairs(allObjects) do
|
||||||
if (object.name == "Deck") then
|
if object.type == "Deck" then
|
||||||
for _, cardData in ipairs(object.getData().ContainedObjects) do
|
for _, cardData in ipairs(object.getData().ContainedObjects) do
|
||||||
local cardMetadata = JSON.decode(cardData.GMNotes)
|
incrementWeaknessCount(weaknessesInPlay, JSON.decode(cardData.GMNotes))
|
||||||
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
|
|
||||||
end
|
end
|
||||||
elseif (object.name == "Card") then
|
elseif object.type == "Card" then
|
||||||
local cardMetadata = JSON.decode(object.getGMNotes())
|
incrementWeaknessCount(weaknessesInPlay, JSON.decode(object.getGMNotes()))
|
||||||
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local availableWeaknesses = { }
|
local availableWeaknesses = {}
|
||||||
for _, weaknessId in ipairs(basicWeaknessList) do
|
for _, weaknessId in ipairs(basicWeaknessList) do
|
||||||
if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then
|
if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then
|
||||||
weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1
|
weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1
|
||||||
@ -332,8 +415,8 @@ end
|
|||||||
|
|
||||||
-- Helper function that adds one to the table entry for the number of weaknesses in play
|
-- Helper function that adds one to the table entry for the number of weaknesses in play
|
||||||
function incrementWeaknessCount(table, cardMetadata)
|
function incrementWeaknessCount(table, cardMetadata)
|
||||||
if (isBasicWeakness(cardMetadata)) then
|
if isBasicWeakness(cardMetadata) then
|
||||||
if (table[cardMetadata.id] == nil) then
|
if table[cardMetadata.id] == nil then
|
||||||
table[cardMetadata.id] = 1
|
table[cardMetadata.id] = 1
|
||||||
else
|
else
|
||||||
table[cardMetadata.id] = table[cardMetadata.id] + 1
|
table[cardMetadata.id] = table[cardMetadata.id] + 1
|
||||||
@ -343,7 +426,7 @@ end
|
|||||||
|
|
||||||
function isBasicWeakness(cardMetadata)
|
function isBasicWeakness(cardMetadata)
|
||||||
return cardMetadata ~= nil
|
return cardMetadata ~= nil
|
||||||
and cardMetadata.weakness
|
and cardMetadata.weakness
|
||||||
and cardMetadata.basicWeaknessCount ~= nil
|
and cardMetadata.basicWeaknessCount ~= nil
|
||||||
and cardMetadata.basicWeaknessCount > 0
|
and cardMetadata.basicWeaknessCount > 0
|
||||||
end
|
end
|
||||||
|
@ -6,22 +6,29 @@ do
|
|||||||
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "AllCardsBag")
|
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "AllCardsBag")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Returns a specific card from the bag, based on ArkhamDB ID
|
-- internal function to create a copy of the table to avoid operating on variables owned by different objects
|
||||||
---@param id table String ID of the card to retrieve
|
local function returnCopyOfList(data)
|
||||||
---@return table table
|
local copiedList = {}
|
||||||
-- If the indexes are still being constructed, an empty table is
|
for _, id in ipairs(data) do
|
||||||
-- returned. Otherwise, a single table with the following fields
|
table.insert(copiedList, id)
|
||||||
-- cardData: TTS object data, suitable for spawning the card
|
end
|
||||||
-- cardMetadata: Table of parsed metadata
|
return copiedList
|
||||||
AllCardsBagApi.getCardById = function(id)
|
|
||||||
return getAllCardsBag().call("getCardById", {id = id})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Gets a random basic weakness from the bag. Once a given ID has been returned
|
-- Returns a specific card from the bag, based on ArkhamDB ID
|
||||||
-- it will be removed from the list and cannot be selected again until a reload
|
---@param id string ID of the card to retrieve
|
||||||
-- occurs or the indexes are rebuilt, which will refresh the list to include all
|
---@return table: If the indexes are still being constructed, returns an empty table.
|
||||||
-- weaknesses.
|
-- Otherwise, a single table with the following fields
|
||||||
---@return string: ID of the selected weakness.
|
-- data: TTS object data, suitable for spawning the card
|
||||||
|
-- metadata: Table of parsed metadata
|
||||||
|
AllCardsBagApi.getCardById = function(id)
|
||||||
|
return getAllCardsBag().call("getCardById", { id = id })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Gets a random basic weakness from the bag. Once a given ID has been returned it
|
||||||
|
-- will be removed from the list and cannot be selected again until a reload occurs
|
||||||
|
-- or the indexes are rebuilt, which will refresh the list to include all weaknesses.
|
||||||
|
---@return string: ID of the selected weakness
|
||||||
AllCardsBagApi.getRandomWeaknessId = function()
|
AllCardsBagApi.getRandomWeaknessId = function()
|
||||||
return getAllCardsBag().call("getRandomWeaknessId")
|
return getAllCardsBag().call("getRandomWeaknessId")
|
||||||
end
|
end
|
||||||
@ -30,21 +37,21 @@ do
|
|||||||
return getAllCardsBag().call("isIndexReady")
|
return getAllCardsBag().call("isIndexReady")
|
||||||
end
|
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
|
-- 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
|
-- 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.
|
-- later, and we should rebuild the index to integrate the hotfix bag.
|
||||||
AllCardsBagApi.rebuildIndexForHotfix = function()
|
AllCardsBagApi.rebuildIndexForHotfix = function()
|
||||||
return getAllCardsBag().call("rebuildIndexForHotfix")
|
getAllCardsBag().call("rebuildIndexForHotfix")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Searches the bag for cards which match the given name and returns a list. Note that this is
|
-- Searches the bag for cards which match the given name and returns a list.
|
||||||
-- an O(n) search without index support. It may be slow.
|
-- 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 name string or string fragment to search for names
|
||||||
---@param exact boolean Whether the name match should be exact
|
---@param exact boolean Whether the name match should be exact
|
||||||
AllCardsBagApi.getCardsByName = function(name, exact)
|
AllCardsBagApi.getCardsByName = function(name, exact)
|
||||||
return getAllCardsBag().call("getCardsByName", {name = name, exact = exact})
|
return returnCopyOfList(getAllCardsBag().call("getCardsByName", { name = name, exact = exact }))
|
||||||
end
|
end
|
||||||
|
|
||||||
AllCardsBagApi.isBagPresent = function()
|
AllCardsBagApi.isBagPresent = function()
|
||||||
@ -53,22 +60,29 @@ do
|
|||||||
|
|
||||||
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
|
-- 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 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.
|
---@return table: If the indexes are still being constructed, returns an empty table.
|
||||||
-- Otherwise, a list of tables, each with the following fields
|
-- Otherwise, a list of tables, each with the following fields
|
||||||
-- cardData: TTS object data, suitable for spawning the card
|
-- data: TTS object data, suitable for spawning the card
|
||||||
-- cardMetadata: Table of parsed metadata
|
-- metadata: Table of parsed metadata
|
||||||
AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded)
|
AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded)
|
||||||
return getAllCardsBag().call("getCardsByClassAndLevel", {class = class, upgraded = upgraded})
|
return returnCopyOfList(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded }))
|
||||||
end
|
end
|
||||||
|
|
||||||
AllCardsBagApi.getCardsByCycle = function(cycle)
|
-- Returns a list of cards from the bag matching a cycle
|
||||||
return getAllCardsBag().call("getCardsByCycle", cycle)
|
---@param cycle string Cycle to retrieve ("The Scarlet Keys" etc.)
|
||||||
|
---@param sortByMetadata boolean If true, sorts the table by metadata instead of ID
|
||||||
|
---@return table: If the indexes are still being constructed, returns an empty table.
|
||||||
|
-- Otherwise, a list of tables, each with the following fields
|
||||||
|
-- data: TTS object data, suitable for spawning the card
|
||||||
|
-- metadata: Table of parsed metadata
|
||||||
|
AllCardsBagApi.getCardsByCycle = function(cycle, sortByMetadata)
|
||||||
|
return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", { cycle = cycle, sortByMetadata = sortByMetadata }))
|
||||||
end
|
end
|
||||||
|
|
||||||
AllCardsBagApi.getUniqueWeaknesses = function()
|
AllCardsBagApi.getUniqueWeaknesses = function()
|
||||||
return getAllCardsBag().call("getUniqueWeaknesses")
|
return returnCopyOfList(getAllCardsBag().call("getUniqueWeaknesses"))
|
||||||
end
|
end
|
||||||
|
|
||||||
return AllCardsBagApi
|
return AllCardsBagApi
|
||||||
end
|
end
|
||||||
|
@ -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 STARTER_DECK_MODE_SELECTED_COLOR = { 0.2, 0.2, 0.2, 0.8 }
|
||||||
local TRANSPARENT = { 0, 0, 0, 0 }
|
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_UP_ROTATION = { x = 0, y = 270, z = 0 }
|
||||||
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180}
|
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180 }
|
||||||
|
|
||||||
-- ---------- IMPORTANT ----------
|
-- ---------- IMPORTANT ----------
|
||||||
-- Coordinates defined below are in global dimensions relative to the panel - DO NOT USE THESE
|
-- 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
|
-- Layout width for a single card, in global coordinate space
|
||||||
local CARD_WIDTH = 2.3
|
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.
|
-- 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.
|
-- coordinates on these provide global disances while the Z is local.
|
||||||
local START_POSITIONS = {
|
local START_POSITIONS = {
|
||||||
classCards = Vector(CARD_WIDTH * 9.5, 2, 1.4),
|
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),
|
other = Vector(CARD_WIDTH * 9.5, 2, 1.4),
|
||||||
randomWeakness = Vector(0, 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
|
-- 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)
|
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_POSITION_SHIFT_COL = Vector(-6, 0, 0)
|
||||||
local INVESTIGATOR_MAX_COLS = 6
|
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
|
-- decks use SIGNATURE_OFFSET
|
||||||
local INVESTIGATOR_CARD_OFFSET = Vector(0, 0, 2.55)
|
local INVESTIGATOR_CARD_OFFSET = Vector(0, 0, 2.55)
|
||||||
local INVESTIGATOR_SIGNATURE_OFFSET = Vector(0, 0, 5.75)
|
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 startPositions
|
||||||
local cardRowOffset
|
local cardRowOffset
|
||||||
local cardGroupOffset
|
local cardGroupOffset
|
||||||
@ -78,7 +76,14 @@ local investigatorPositionShiftCol
|
|||||||
local investigatorCardOffset
|
local investigatorCardOffset
|
||||||
local investigatorSignatureOffset
|
local investigatorSignatureOffset
|
||||||
|
|
||||||
local CLASS_LIST = { "Guardian", "Seeker", "Rogue", "Mystic", "Survivor", "Neutral" }
|
local CLASS_LIST = {
|
||||||
|
"Guardian",
|
||||||
|
"Seeker",
|
||||||
|
"Rogue",
|
||||||
|
"Mystic",
|
||||||
|
"Survivor",
|
||||||
|
"Neutral"
|
||||||
|
}
|
||||||
local CYCLE_LIST = {
|
local CYCLE_LIST = {
|
||||||
"Core",
|
"Core",
|
||||||
"The Dunwich Legacy",
|
"The Dunwich Legacy",
|
||||||
@ -94,9 +99,8 @@ local CYCLE_LIST = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local excludedNonBasicWeaknesses
|
local excludedNonBasicWeaknesses
|
||||||
|
local spawnStarterDecks = false
|
||||||
local starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
|
local helpVisibleToPlayers = {}
|
||||||
local helpVisibleToPlayers = { }
|
|
||||||
|
|
||||||
function onSave()
|
function onSave()
|
||||||
return JSON.encode({ spawnBagState = spawnBag.getStateForSave() })
|
return JSON.encode({ spawnBagState = spawnBag.getStateForSave() })
|
||||||
@ -104,7 +108,7 @@ end
|
|||||||
|
|
||||||
function onLoad(savedData)
|
function onLoad(savedData)
|
||||||
if savedData and savedData ~= "" then
|
if savedData and savedData ~= "" then
|
||||||
local saveState = JSON.decode(savedData) or { }
|
local saveState = JSON.decode(savedData) or {}
|
||||||
if saveState.spawnBagState ~= nil then
|
if saveState.spawnBagState ~= nil then
|
||||||
spawnBag.loadFromSave(saveState.spawnBagState)
|
spawnBag.loadFromSave(saveState.spawnBagState)
|
||||||
end
|
end
|
||||||
@ -117,7 +121,7 @@ end
|
|||||||
-- Build a list of non-basic weaknesses which should be excluded from the last weakness set,
|
-- Build a list of non-basic weaknesses which should be excluded from the last weakness set,
|
||||||
-- including all signature cards and evolved weaknesses.
|
-- including all signature cards and evolved weaknesses.
|
||||||
function buildExcludedWeaknessList()
|
function buildExcludedWeaknessList()
|
||||||
excludedNonBasicWeaknesses = { }
|
excludedNonBasicWeaknesses = {}
|
||||||
for _, investigator in pairs(INVESTIGATORS) do
|
for _, investigator in pairs(INVESTIGATORS) do
|
||||||
for _, signatureId in ipairs(investigator.signatures) do
|
for _, signatureId in ipairs(investigator.signatures) do
|
||||||
excludedNonBasicWeaknesses[signatureId] = true
|
excludedNonBasicWeaknesses[signatureId] = true
|
||||||
@ -274,9 +278,8 @@ function createCycleButtons()
|
|||||||
if rowCount == 3 then
|
if rowCount == 3 then
|
||||||
-- Account for two centered buttons on the final row
|
-- Account for two centered buttons on the final row
|
||||||
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET / 2
|
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET / 2
|
||||||
--[[ Account for centered button on the final row
|
-- Account for centered button on the final row
|
||||||
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
|
-- buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
|
||||||
]]
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
|
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
|
||||||
@ -297,8 +300,6 @@ function createClearButton()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function createInvestigatorModeButtons()
|
function createInvestigatorModeButtons()
|
||||||
local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
|
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
function_owner = self,
|
function_owner = self,
|
||||||
click_function = "setCardsOnlyMode",
|
click_function = "setCardsOnlyMode",
|
||||||
@ -306,18 +307,18 @@ function createInvestigatorModeButtons()
|
|||||||
height = 170,
|
height = 170,
|
||||||
width = 760,
|
width = 760,
|
||||||
scale = Vector(0.25, 1, 0.25),
|
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({
|
self.createButton({
|
||||||
function_owner = self,
|
function_owner = self,
|
||||||
click_function = "setStarterDeckMode",
|
click_function = "setspawnStarterDecks",
|
||||||
position = Vector(0.66, 0.1, -0.322),
|
position = Vector(0.66, 0.1, -0.322),
|
||||||
height = 170,
|
height = 170,
|
||||||
width = 760,
|
width = 760,
|
||||||
scale = Vector(0.25, 1, 0.25),
|
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({
|
self.createButton({
|
||||||
function_owner = self,
|
function_owner = self,
|
||||||
label = "✓",
|
label = "✓",
|
||||||
@ -325,12 +326,77 @@ function createInvestigatorModeButtons()
|
|||||||
position = Vector(checkX, 0.11, -0.317),
|
position = Vector(checkX, 0.11, -0.317),
|
||||||
height = 0,
|
height = 0,
|
||||||
width = 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 },
|
font_color = { 0, 0, 0 },
|
||||||
color = { 1, 1, 1 }
|
color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
end
|
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, _)
|
function toggleHelp(_, playerColor, _)
|
||||||
if helpVisibleToPlayers[playerColor] then
|
if helpVisibleToPlayers[playerColor] then
|
||||||
helpVisibleToPlayers[playerColor] = nil
|
helpVisibleToPlayers[playerColor] = nil
|
||||||
@ -354,13 +420,13 @@ function updateHelpVisibility()
|
|||||||
self.UI.setAttribute("helpPanel", "active", string.len(visibility) > 0)
|
self.UI.setAttribute("helpPanel", "active", string.len(visibility) > 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
function setStarterDeckMode()
|
function setspawnStarterDecks()
|
||||||
starterDeckMode = STARTER_DECK_MODE_STARTERS
|
spawnStarterDecks = true
|
||||||
updateStarterModeButtons()
|
updateStarterModeButtons()
|
||||||
end
|
end
|
||||||
|
|
||||||
function setCardsOnlyMode()
|
function setCardsOnlyMode()
|
||||||
starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
|
spawnStarterDecks = false
|
||||||
updateStarterModeButtons()
|
updateStarterModeButtons()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -384,7 +450,7 @@ end
|
|||||||
function scalePositions()
|
function scalePositions()
|
||||||
-- Assume scaling is consistent in X and Z dimensions
|
-- Assume scaling is consistent in X and Z dimensions
|
||||||
local scale = 1 / self.getScale().x
|
local scale = 1 / self.getScale().x
|
||||||
startPositions = { }
|
startPositions = {}
|
||||||
for key, pos in pairs(START_POSITIONS) do
|
for key, pos in pairs(START_POSITIONS) do
|
||||||
-- Because a scaled object means a different global size, using global distance for Z results in
|
-- 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
|
-- the cards being closer or farther depending on the scale. Leave the Z values and only scale X and Y
|
||||||
@ -405,14 +471,12 @@ function deleteAll()
|
|||||||
spawnBag.recall(true)
|
spawnBag.recall(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Spawn an investigator group, based on the current UI setting for either investigators or starter
|
-- Spawn an investigator group, based on the current UI setting for either investigators or starter decks
|
||||||
-- decks.
|
|
||||||
---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData
|
---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData
|
||||||
function spawnInvestigatorGroup(groupName)
|
function spawnInvestigatorGroup(groupName)
|
||||||
local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
|
|
||||||
prepareToPlaceCards()
|
prepareToPlaceCards()
|
||||||
Wait.frames(function()
|
Wait.frames(function()
|
||||||
if starterMode then
|
if spawnStarterDecks then
|
||||||
spawnStarters(groupName)
|
spawnStarters(groupName)
|
||||||
else
|
else
|
||||||
spawnInvestigators(groupName)
|
spawnInvestigators(groupName)
|
||||||
@ -420,12 +484,12 @@ function spawnInvestigatorGroup(groupName)
|
|||||||
end, 2)
|
end, 2)
|
||||||
end
|
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.
|
-- investigator cards and minicards as well as the signature cards.
|
||||||
---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData
|
---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData
|
||||||
function spawnInvestigators(groupName)
|
function spawnInvestigators(groupName)
|
||||||
if INVESTIGATOR_GROUPS[groupName] == nil then
|
if INVESTIGATOR_GROUPS[groupName] == nil then
|
||||||
printToAll("No " .. groupName .. " data yet")
|
printToAll("No investigator data for " .. groupName .. " yet")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -434,7 +498,7 @@ function spawnInvestigators(groupName)
|
|||||||
local investigatorCount = #INVESTIGATOR_GROUPS[groupName]
|
local investigatorCount = #INVESTIGATOR_GROUPS[groupName]
|
||||||
local position = getInvestigatorRowStartPos(investigatorCount, row)
|
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
|
for _, spawnSpec in ipairs(buildInvestigatorSpawnSpec(investigatorName, INVESTIGATORS[investigatorName], position)) do
|
||||||
spawnBag.spawn(spawnSpec)
|
spawnBag.spawn(spawnSpec)
|
||||||
end
|
end
|
||||||
@ -451,14 +515,14 @@ end
|
|||||||
function getInvestigatorRowStartPos(investigatorCount, row)
|
function getInvestigatorRowStartPos(investigatorCount, row)
|
||||||
local rowStart = Vector(startPositions.investigator)
|
local rowStart = Vector(startPositions.investigator)
|
||||||
rowStart:add(Vector(
|
rowStart:add(Vector(
|
||||||
investigatorPositionShiftRow.x * (row - 1),
|
investigatorPositionShiftRow.x * (row - 1),
|
||||||
investigatorPositionShiftRow.y * (row - 1),
|
investigatorPositionShiftRow.y * (row - 1),
|
||||||
investigatorPositionShiftRow.z * (row - 1)))
|
investigatorPositionShiftRow.z * (row - 1)))
|
||||||
local investigatorsInRow = math.min(investigatorCount - INVESTIGATOR_MAX_COLS * (row - 1), INVESTIGATOR_MAX_COLS)
|
local investigatorsInRow = math.min(investigatorCount - INVESTIGATOR_MAX_COLS * (row - 1), INVESTIGATOR_MAX_COLS)
|
||||||
rowStart:add(Vector(
|
rowStart:add(Vector(
|
||||||
investigatorPositionShiftCol.x * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2,
|
investigatorPositionShiftCol.x * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2,
|
||||||
investigatorPositionShiftCol.y * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2,
|
investigatorPositionShiftCol.y * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2,
|
||||||
investigatorPositionShiftCol.z * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2))
|
investigatorPositionShiftCol.z * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2))
|
||||||
return rowStart
|
return rowStart
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -470,23 +534,23 @@ function buildInvestigatorSpawnSpec(investigatorName, investigatorData, position
|
|||||||
local sigPos = Vector(position):add(investigatorSignatureOffset)
|
local sigPos = Vector(position):add(investigatorSignatureOffset)
|
||||||
local spawns = buildCommonSpawnSpec(investigatorName, investigatorData, position)
|
local spawns = buildCommonSpawnSpec(investigatorName, investigatorData, position)
|
||||||
table.insert(spawns, {
|
table.insert(spawns, {
|
||||||
name = investigatorName .. "signatures",
|
name = investigatorName .. "signatures",
|
||||||
cards = investigatorData.signatures,
|
cards = investigatorData.signatures,
|
||||||
globalPos = self.positionToWorld(sigPos),
|
globalPos = self.positionToWorld(sigPos),
|
||||||
rotation = FACE_UP_ROTATION
|
rotation = FACE_UP_ROTATION
|
||||||
})
|
})
|
||||||
|
|
||||||
return spawns
|
return spawns
|
||||||
end
|
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
|
-- shared, and will only differ in whether they spawn the full stack of possible investigator and
|
||||||
-- minicards, or only the first of each.
|
-- minicards, or only the first of each.
|
||||||
---@param investigatorName string Name of the investigator, matching a key in InvestigatorPanelData
|
---@param investigatorName string Name of the investigator, matching a key in InvestigatorPanelData
|
||||||
---@param investigatorData table Spawn definition for the investigator, retrieved from INVESTIGATORS
|
---@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 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
|
---@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)
|
function buildCommonSpawnSpec(investigatorName, investigatorData, position, oneCardOnly)
|
||||||
local cardPos = Vector(position):add(investigatorCardOffset)
|
local cardPos = Vector(position):add(investigatorCardOffset)
|
||||||
return {
|
return {
|
||||||
@ -533,23 +597,24 @@ function spawnStarterDeck(investigatorName, investigatorData, position)
|
|||||||
end
|
end
|
||||||
local deckPos = Vector(position):add(investigatorSignatureOffset)
|
local deckPos = Vector(position):add(investigatorSignatureOffset)
|
||||||
arkhamDb.getDecklist("None", investigatorData.starterDeck, true, false, false, function(slots)
|
arkhamDb.getDecklist("None", investigatorData.starterDeck, true, false, false, function(slots)
|
||||||
local cardIdList = { }
|
local cardIdList = {}
|
||||||
for id, count in pairs(slots) do
|
for id, count in pairs(slots) do
|
||||||
for i = 1, count do
|
for i = 1, count do
|
||||||
table.insert(cardIdList, id)
|
table.insert(cardIdList, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
spawnBag.spawn({
|
spawnBag.spawn({
|
||||||
name = investigatorName.."starter",
|
name = investigatorName .. "starter",
|
||||||
cards = cardIdList,
|
cards = cardIdList,
|
||||||
globalPos = self.positionToWorld(deckPos),
|
globalPos = self.positionToWorld(deckPos),
|
||||||
rotation = FACE_DOWN_ROTATION
|
rotation = FACE_DOWN_ROTATION
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clears the currently placed cards, then places cards for the given class and level spread
|
-- 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 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)
|
function spawnClassCards(cardClass, isUpgraded)
|
||||||
prepareToPlaceCards()
|
prepareToPlaceCards()
|
||||||
Wait.frames(function() placeClassCards(cardClass, isUpgraded) end, 2)
|
Wait.frames(function() placeClassCards(cardClass, isUpgraded) end, 2)
|
||||||
@ -557,18 +622,15 @@ end
|
|||||||
|
|
||||||
-- Spawn the class cards.
|
-- Spawn the class cards.
|
||||||
---@param cardClass string Class to place ("Guardian", "Seeker", etc)
|
---@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)
|
function placeClassCards(cardClass, isUpgraded)
|
||||||
local indexReady = allCardsBagApi.isIndexReady()
|
if not allCardsBagApi.isIndexReady() then return end
|
||||||
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 cardIdList = allCardsBagApi.getCardsByClassAndLevel(cardClass, isUpgraded)
|
local cardIdList = allCardsBagApi.getCardsByClassAndLevel(cardClass, isUpgraded)
|
||||||
|
|
||||||
local skillList = { }
|
local skillList = {}
|
||||||
local eventList = { }
|
local eventList = {}
|
||||||
local assetList = { }
|
local assetList = {}
|
||||||
for _, cardId in ipairs(cardIdList) do
|
for _, cardId in ipairs(cardIdList) do
|
||||||
local cardMetadata = allCardsBagApi.getCardById(cardId).metadata
|
local cardMetadata = allCardsBagApi.getCardById(cardId).metadata
|
||||||
if (cardMetadata.type == "Skill") then
|
if (cardMetadata.type == "Skill") then
|
||||||
@ -617,21 +679,20 @@ end
|
|||||||
-- Spawns the investigator sets and all cards for the given cycle
|
-- 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
|
---@param cycle string Name of a cycle, should match the standard used in card metadata
|
||||||
function spawnCycle(cycle)
|
function spawnCycle(cycle)
|
||||||
|
if not allCardsBagApi.isIndexReady() then return end
|
||||||
|
|
||||||
prepareToPlaceCards()
|
prepareToPlaceCards()
|
||||||
spawnInvestigators(cycle)
|
spawnInvestigators(cycle)
|
||||||
local indexReady = allCardsBagApi.isIndexReady()
|
|
||||||
if (not indexReady) then
|
-- sort custom cards
|
||||||
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
|
local sortByMetadata = false
|
||||||
return
|
if cycle == "Other" then
|
||||||
end
|
sortByMetadata = true
|
||||||
local cycleCardList = allCardsBagApi.getCardsByCycle(cycle)
|
|
||||||
local copiedList = { }
|
|
||||||
for i, id in ipairs(cycleCardList) do
|
|
||||||
copiedList[i] = id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
spawnBag.spawn({
|
spawnBag.spawn({
|
||||||
name = "cycle"..cycle,
|
name = "cycle" .. cycle,
|
||||||
cards = copiedList,
|
cards = allCardsBagApi.getCardsByCycle(cycle, sortByMetadata),
|
||||||
globalPos = self.positionToWorld(startPositions.cycle),
|
globalPos = self.positionToWorld(startPositions.cycle),
|
||||||
rotation = FACE_UP_ROTATION,
|
rotation = FACE_UP_ROTATION,
|
||||||
spread = true,
|
spread = true,
|
||||||
@ -671,16 +732,13 @@ end
|
|||||||
|
|
||||||
-- Clears the current cards, and places all basic weaknesses on the table.
|
-- Clears the current cards, and places all basic weaknesses on the table.
|
||||||
function spawnWeaknesses()
|
function spawnWeaknesses()
|
||||||
|
if not allCardsBagApi.isIndexReady() then return end
|
||||||
|
|
||||||
prepareToPlaceCards()
|
prepareToPlaceCards()
|
||||||
local indexReady = allCardsBagApi.isIndexReady()
|
|
||||||
if (not indexReady) then
|
local basicWeaknessList = {}
|
||||||
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
|
local otherWeaknessList = {}
|
||||||
return
|
for _, id in ipairs(allCardsBagApi.getUniqueWeaknesses()) do
|
||||||
end
|
|
||||||
local weaknessIdList = allCardsBagApi.getUniqueWeaknesses()
|
|
||||||
local basicWeaknessList = { }
|
|
||||||
local otherWeaknessList = { }
|
|
||||||
for i, id in ipairs(weaknessIdList) do
|
|
||||||
local cardMetadata = allCardsBagApi.getCardById(id).metadata
|
local cardMetadata = allCardsBagApi.getCardById(id).metadata
|
||||||
if cardMetadata.basicWeaknessCount ~= nil and cardMetadata.basicWeaknessCount > 0 then
|
if cardMetadata.basicWeaknessCount ~= nil and cardMetadata.basicWeaknessCount > 0 then
|
||||||
table.insert(basicWeaknessList, id)
|
table.insert(basicWeaknessList, id)
|
||||||
@ -721,7 +779,7 @@ function spawnRandomWeakness()
|
|||||||
prepareToPlaceCards()
|
prepareToPlaceCards()
|
||||||
local weaknessId = allCardsBagApi.getRandomWeaknessId()
|
local weaknessId = allCardsBagApi.getRandomWeaknessId()
|
||||||
if (weaknessId == nil) then
|
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
|
return
|
||||||
end
|
end
|
||||||
spawnBag.spawn({
|
spawnBag.spawn({
|
||||||
|
@ -16,7 +16,7 @@ Spawner = { }
|
|||||||
---@param sort boolean True if this list of cards should be sorted before spawning
|
---@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.
|
---@param callback? function Callback to be called after the card/deck spawns.
|
||||||
Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
|
Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
|
||||||
if (sort) then
|
if sort then
|
||||||
table.sort(cardList, Spawner.cardComparator)
|
table.sort(cardList, Spawner.cardComparator)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
|
|||||||
local investigatorCards = { }
|
local investigatorCards = { }
|
||||||
|
|
||||||
for _, card in ipairs(cardList) do
|
for _, card in ipairs(cardList) do
|
||||||
if (card.metadata.type == "Investigator") then
|
if card.metadata.type == "Investigator" then
|
||||||
table.insert(investigatorCards, card)
|
table.insert(investigatorCards, card)
|
||||||
elseif (card.metadata.type == "Minicard") then
|
elseif card.metadata.type == "Minicard" then
|
||||||
table.insert(miniCards, card)
|
table.insert(miniCards, card)
|
||||||
else
|
else
|
||||||
table.insert(standardCards, card)
|
table.insert(standardCards, card)
|
||||||
@ -46,7 +46,7 @@ Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Spawner.spawnCardSpread = function(cardList, startPos, maxCols, rot, sort, callback)
|
Spawner.spawnCardSpread = function(cardList, startPos, maxCols, rot, sort, callback)
|
||||||
if (sort) then
|
if sort then
|
||||||
table.sort(cardList, Spawner.cardComparator)
|
table.sort(cardList, Spawner.cardComparator)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ end
|
|||||||
---@return string id >= startId
|
---@return string id >= startId
|
||||||
Spawner.findNextAvailableId = function(objectTable, startId)
|
Spawner.findNextAvailableId = function(objectTable, startId)
|
||||||
local id = startId
|
local id = startId
|
||||||
while (objectTable[id] ~= nil) do
|
while objectTable[id] ~= nil do
|
||||||
id = tostring(tonumber(id) + 1)
|
id = tostring(tonumber(id) + 1)
|
||||||
end
|
end
|
||||||
return id
|
return id
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
require("playercards/PlayerCardSpawner")
|
require("playercards/PlayerCardSpawner")
|
||||||
|
|
||||||
-- Allows spawning of defined lists of cards which will be created from the template in the All
|
-- 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
|
-- 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
|
-- 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.
|
-- spawned objects. Objects moved out of this area will not be cleaned up.
|
||||||
--
|
--
|
||||||
-- SpawnSpec: Spawning requires a spawn specification with the following structure:
|
-- 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
|
-- but each requires a separate name
|
||||||
-- cards: A list of card IDs to be spawned
|
-- 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 }
|
-- 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
|
-- 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: 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 moving to the right. globalPos will define the location of the first card, each
|
||||||
-- after that will be moved a predefined distance
|
-- after that will be moved a predefined distance
|
||||||
-- spreadCols: Optional integer. If spread is true, specifies the maximum columns cards will be
|
-- 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
|
-- 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)
|
-- cards will be in a single row (however long that may be)
|
||||||
-- }
|
-- }
|
||||||
-- See BondedBag.ttslua for an example
|
-- See BondedBag.ttslua for an example
|
||||||
do
|
do
|
||||||
local allCardsBagApi = require("playercards/AllCardsBagApi")
|
local allCardsBagApi = require("playercards/AllCardsBagApi")
|
||||||
|
|
||||||
local SpawnBag = { }
|
local SpawnBag = {}
|
||||||
local internal = { }
|
local internal = {}
|
||||||
|
|
||||||
-- To assist debugging, will draw a box around the recall zone when it's set up
|
-- To assist debugging, will draw a box around the recall zone when it's set up
|
||||||
local SHOW_RECALL_ZONE = false
|
local SHOW_RECALL_ZONE = false
|
||||||
@ -36,8 +36,8 @@ do
|
|||||||
local RECALL_BUFFER_Z = 0.5
|
local RECALL_BUFFER_Z = 0.5
|
||||||
|
|
||||||
-- In order to mimic the behavior of the previous memory buttons we use a temporary bag when
|
-- 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
|
-- 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
|
-- this object. Once all placed cards are recalled bag to this bag, it will be destroyed
|
||||||
local RECALL_BAG = {
|
local RECALL_BAG = {
|
||||||
Name = "Bag",
|
Name = "Bag",
|
||||||
Transform = {
|
Transform = {
|
||||||
@ -58,8 +58,8 @@ do
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- Tracks what has been placed by this "bag" so they can be recalled
|
-- Tracks what has been placed by this "bag" so they can be recalled
|
||||||
local placedSpecs = { }
|
local placedSpecs = {}
|
||||||
local placedObjectGuids = { }
|
local placedObjectGuids = {}
|
||||||
local recallZone = nil
|
local recallZone = nil
|
||||||
|
|
||||||
-- Loads a table of saved state, extracted during the parent object's onLoad
|
-- Loads a table of saved state, extracted during the parent object's onLoad
|
||||||
@ -81,24 +81,16 @@ do
|
|||||||
-- Places the given spawnSpec on the table. See comment at the start of the file for spawnSpec table data and examples
|
-- 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)
|
SpawnBag.spawn = function(spawnSpec)
|
||||||
-- Limit to one placement at a time
|
-- Limit to one placement at a time
|
||||||
if (placedSpecs[spawnSpec.name]) then
|
if placedSpecs[spawnSpec.name] or spawnSpec == nil then return end
|
||||||
return
|
|
||||||
end
|
local cardsToSpawn = {}
|
||||||
if (spawnSpec == nil) then
|
for _, cardId in ipairs(spawnSpec.cards) do
|
||||||
-- TODO: error here
|
local card = allCardsBagApi.getCardById(cardId)
|
||||||
return
|
if card ~= nil then
|
||||||
end
|
table.insert(cardsToSpawn, card)
|
||||||
local cardsToSpawn = { }
|
|
||||||
local cardList = spawnSpec.cards
|
|
||||||
for _, cardId in ipairs(cardList) do
|
|
||||||
local cardData = allCardsBagApi.getCardById(cardId)
|
|
||||||
if (cardData ~= nil) then
|
|
||||||
table.insert(cardsToSpawn, cardData)
|
|
||||||
else
|
|
||||||
-- TODO: error here
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if (spawnSpec.spread) then
|
if spawnSpec.spread then
|
||||||
Spawner.spawnCardSpread(cardsToSpawn, spawnSpec.globalPos, spawnSpec.spreadCols or 9999, spawnSpec.rotation, false, internal.recordPlacedObject)
|
Spawner.spawnCardSpread(cardsToSpawn, spawnSpec.globalPos, spawnSpec.spreadCols or 9999, spawnSpec.rotation, false, internal.recordPlacedObject)
|
||||||
else
|
else
|
||||||
-- TTS decks come out in reverse order of the cards, reverse the list so the input order stays
|
-- TTS decks come out in reverse order of the cards, reverse the list so the input order stays
|
||||||
@ -120,14 +112,13 @@ do
|
|||||||
internal.recallSpawned()
|
internal.recallSpawned()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- We've recalled everything we can, some cards may have been moved out of the
|
-- We've recalled everything we can, some cards may have been moved out of the card area. Just reset at this point.
|
||||||
-- card area. Just reset at this point.
|
placedSpecs = {}
|
||||||
placedSpecs = { }
|
placedObjectGuids = {}
|
||||||
placedObjectGuids = { }
|
|
||||||
recallZone = nil
|
recallZone = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Deleted all spawned cards.
|
-- Delete all spawned cards
|
||||||
internal.deleteSpawned = function()
|
internal.deleteSpawned = function()
|
||||||
for guid, _ in pairs(placedObjectGuids) do
|
for guid, _ in pairs(placedObjectGuids) do
|
||||||
local obj = getObjectFromGUID(guid)
|
local obj = getObjectFromGUID(guid)
|
||||||
@ -140,9 +131,9 @@ do
|
|||||||
end
|
end
|
||||||
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()
|
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
|
for guid, _ in pairs(placedObjectGuids) do
|
||||||
local obj = getObjectFromGUID(guid)
|
local obj = getObjectFromGUID(guid)
|
||||||
if (obj ~= nil) then
|
if (obj ~= nil) then
|
||||||
@ -156,71 +147,70 @@ do
|
|||||||
trash.destruct()
|
trash.destruct()
|
||||||
end
|
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)
|
internal.recordPlacedObject = function(spawned)
|
||||||
placedObjectGuids[spawned.getGUID()] = true
|
placedObjectGuids[spawned.getGUID()] = true
|
||||||
internal.expandRecallZone(spawned)
|
internal.expandRecallZone(spawned)
|
||||||
end
|
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
|
-- be maintained as the bounding box of the extreme object positions, plus a small amount of buffer
|
||||||
internal.expandRecallZone = function(spawnedCard)
|
internal.expandRecallZone = function(spawnedCard)
|
||||||
local pos = spawnedCard.getPosition()
|
local pos = spawnedCard.getPosition()
|
||||||
if (recallZone == nil) then
|
if (recallZone == nil) then
|
||||||
-- First card out of the bag, initialize surrounding that
|
-- 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.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 }
|
recallZone.lowerRight = { x = pos.x - RECALL_BUFFER_X, z = pos.z - RECALL_BUFFER_Z }
|
||||||
return
|
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
|
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 y = 1.5
|
||||||
local thick = 0.05
|
local thick = 0.05
|
||||||
Global.setVectorLines({
|
Global.setVectorLines({
|
||||||
{
|
{
|
||||||
points = { {recallZone.upperLeft.x,y,recallZone.upperLeft.z}, {recallZone.upperLeft.x,y,recallZone.lowerRight.z} },
|
points = { { recallZone.upperLeft.x, y, recallZone.upperLeft.z }, { recallZone.upperLeft.x, y, recallZone.lowerRight.z } },
|
||||||
color = {1,0,0},
|
color = { 1, 0, 0 },
|
||||||
thickness = thick,
|
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} },
|
points = { { recallZone.upperLeft.x, y, recallZone.lowerRight.z }, { recallZone.lowerRight.x, y, recallZone.lowerRight.z } },
|
||||||
color = {1,0,0},
|
color = { 1, 0, 0 },
|
||||||
thickness = thick,
|
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} },
|
points = { { recallZone.lowerRight.x, y, recallZone.lowerRight.z }, { recallZone.lowerRight.x, y, recallZone.upperLeft.z } },
|
||||||
color = {1,0,0},
|
color = { 1, 0, 0 },
|
||||||
thickness = thick,
|
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} },
|
points = { { recallZone.lowerRight.x, y, recallZone.upperLeft.z }, { recallZone.upperLeft.x, y, recallZone.upperLeft.z } },
|
||||||
color = {1,0,0},
|
color = { 1, 0, 0 },
|
||||||
thickness = thick,
|
thickness = thick,
|
||||||
rotation = {0,0,0}
|
rotation = { 0, 0, 0 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
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.
|
-- will return true so that everything can be easily cleaned up.
|
||||||
internal.isInRecallZone = function(obj)
|
internal.isInRecallZone = function(obj)
|
||||||
if (recallZone == nil) then
|
if (recallZone == nil) then
|
||||||
@ -228,11 +218,11 @@ do
|
|||||||
end
|
end
|
||||||
local pos = obj.getPosition()
|
local pos = obj.getPosition()
|
||||||
return (pos.x < recallZone.upperLeft.x and pos.x > recallZone.lowerRight.x
|
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
|
end
|
||||||
|
|
||||||
internal.reverseList = function(list)
|
internal.reverseList = function(list)
|
||||||
local reversed = { }
|
local reversed = {}
|
||||||
for i = 1, #list do
|
for i = 1, #list do
|
||||||
reversed[i] = list[#list - i + 1]
|
reversed[i] = list[#list - i + 1]
|
||||||
end
|
end
|
||||||
|
@ -73,8 +73,8 @@ local slotNameToChar = {
|
|||||||
["Tarot"] = "A"
|
["Tarot"] = "A"
|
||||||
}
|
}
|
||||||
|
|
||||||
-- slot symbol for the respective slot (from top left to bottom right)
|
-- slot symbol for the respective slot (from top left to bottom right) - intentionally global!
|
||||||
local slotData = {}
|
slotData = {}
|
||||||
local defaultSlotData = {
|
local defaultSlotData = {
|
||||||
-- 1st row
|
-- 1st row
|
||||||
"any", "any", "any", "Tarot", "Hand (left)", "Hand (right)", "Ally",
|
"any", "any", "any", "Tarot", "Hand (left)", "Hand (right)", "Ally",
|
||||||
@ -303,7 +303,7 @@ function doUpkeep(_, clickedByColor, isRightClick)
|
|||||||
obj.flip()
|
obj.flip()
|
||||||
elseif obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
|
elseif obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
|
||||||
local cardMetadata = JSON.decode(obj.getGMNotes()) or {}
|
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 cardRotation = round(obj.getRotation().y, 0) - rot.y
|
||||||
local yRotDiff = 0
|
local yRotDiff = 0
|
||||||
|
|
||||||
|
@ -55,6 +55,25 @@ do
|
|||||||
end
|
end
|
||||||
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
|
-- 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")
|
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
|
||||||
PlaymatApi.getDeckAreaObjects = function(matColor)
|
PlaymatApi.getDeckAreaObjects = function(matColor)
|
||||||
@ -258,6 +277,14 @@ do
|
|||||||
end
|
end
|
||||||
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
|
-- 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 matColor string Color of the playmat - White, Orange, Green, Red or All
|
||||||
---@param filter string Name of the filte function (see util/SearchLib)
|
---@param filter string Name of the filte function (see util/SearchLib)
|
||||||
|
@ -1,87 +1,98 @@
|
|||||||
local lines = {}
|
local connections = {}
|
||||||
|
|
||||||
-- save "lines" to be able to remove them after loading
|
|
||||||
function onSave()
|
function onSave()
|
||||||
return JSON.encode(lines)
|
return JSON.encode({ connections = connections })
|
||||||
end
|
end
|
||||||
|
|
||||||
function onLoad(savedData)
|
function onLoad(savedData)
|
||||||
if savedData and savedData ~= "" then
|
if savedData and savedData ~= "" then
|
||||||
lines = JSON.decode(savedData) or {}
|
local loadedData = JSON.decode(savedData) or {}
|
||||||
|
connections = loadedData.connections
|
||||||
|
processLines()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
addHotkey("Drawing Tool: Reset", function() connections = {} processLines() end)
|
||||||
|
addHotkey("Drawing Tool: Redraw", processLines)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create timer when numpad 0 is pressed
|
function onScriptingButtonDown(index, playerColor)
|
||||||
function onScriptingButtonDown(index, player_color)
|
|
||||||
if index ~= 10 then return end
|
if index ~= 10 then return end
|
||||||
TimerID = Wait.time(function() draw_from(Player[player_color]) end, 1)
|
|
||||||
|
Timer.create {
|
||||||
|
identifier = playerColor .. "_draw_from",
|
||||||
|
function_name = "draw_from",
|
||||||
|
parameters = { player = Player[playerColor] },
|
||||||
|
delay = 1
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- called for long press of numpad 0, draws lines from hovered object to selected objects
|
function draw_from(params)
|
||||||
function draw_from(player)
|
local source = params.player.getHoverObject()
|
||||||
local source = player.getHoverObject()
|
|
||||||
if not source then return end
|
if not source then return end
|
||||||
|
|
||||||
for _, item in ipairs(player.getSelectedObjects()) do
|
for _, item in ipairs(params.player.getSelectedObjects()) do
|
||||||
if item.getGUID() ~= source.getGUID() then
|
if item ~= source then
|
||||||
if item.getGUID() > source.getGUID() then
|
if item.getGUID() > source.getGUID() then
|
||||||
draw_with_pair(item, source)
|
addPair(item, source)
|
||||||
else
|
else
|
||||||
draw_with_pair(source, item)
|
addPair(source, item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
process_lines()
|
processLines()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- general drawing of all lines between selected objects
|
function onScriptingButtonUp(index, playerColor)
|
||||||
function onScriptingButtonUp(index, player_color)
|
|
||||||
if index ~= 10 then return end
|
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
|
|
||||||
|
|
||||||
local items = Player[player_color].getSelectedObjects()
|
-- returns true only if there is a timer to cancel. If this is false then we've waited longer than a second.
|
||||||
if #items < 2 then
|
if not Timer.destroy(playerColor .. "_draw_from") then return end
|
||||||
broadcastToColor("You must have at least two items selected (currently: " .. #items .. ").", player_color, "Red")
|
|
||||||
return
|
local items = Player[playerColor].getSelectedObjects()
|
||||||
end
|
if #items < 2 then return end
|
||||||
|
|
||||||
table.sort(items, function(a, b) return a.getGUID() > b.getGUID() end)
|
table.sort(items, function(a, b) return a.getGUID() > b.getGUID() end)
|
||||||
|
|
||||||
for f = 1, #items - 1 do
|
for i = 1, #items do
|
||||||
for s = f + 1, #items do
|
local first = items[i]
|
||||||
draw_with_pair(items[f], items[s])
|
|
||||||
|
for j = i, #items do
|
||||||
|
local second = items[j]
|
||||||
|
addPair(first, second)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
process_lines()
|
processLines()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- adds two objects to table of vector lines
|
function addPair(first, second)
|
||||||
function draw_with_pair(first, second)
|
local first_guid = first.getGUID()
|
||||||
local guid_first = first.getGUID()
|
local second_guid = second.getGUID()
|
||||||
local guid_second = second.getGUID()
|
|
||||||
|
|
||||||
if Global.getVectorLines() == nil then lines = {} end
|
if not connections[first_guid] then connections[first_guid] = {} end
|
||||||
if not lines[guid_first] then lines[guid_first] = {} end
|
connections[first_guid][second_guid] = not connections[first_guid][second_guid]
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- updates the global vector lines based on "lines"
|
function processLines()
|
||||||
function process_lines()
|
local lines = {}
|
||||||
local drawing = {}
|
|
||||||
|
|
||||||
for _, first in pairs(lines) do
|
for source_guid, target_guids in pairs(connections) do
|
||||||
for _, data in pairs(first) do
|
local source = getObjectFromGUID(source_guid)
|
||||||
table.insert(drawing, data)
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
Global.setVectorLines(drawing)
|
Global.setVectorLines(lines)
|
||||||
end
|
end
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<Panel
|
|
||||||
active="false"
|
|
||||||
id="helpPanel"
|
|
||||||
position="-165 -60 -2"
|
|
||||||
rotation="0 0 180"
|
|
||||||
height="55"
|
|
||||||
width="107"
|
|
||||||
color="#00000099">
|
|
||||||
<Text
|
|
||||||
id="helpText"
|
|
||||||
rectAlignment="MiddleCenter"
|
|
||||||
height="490"
|
|
||||||
width="1000"
|
|
||||||
scale="0.1 0.1 1"
|
|
||||||
fontSize="66"
|
|
||||||
color="#F5F5F5"
|
|
||||||
backgroundColor="#FF0000"
|
|
||||||
alignment="UpperLeft"
|
|
||||||
horizontalOverflow="wrap">
|
|
||||||
• Select a group to place cards
|
|
||||||
• Copy the cards you want for your deck
|
|
||||||
• Select a new group to clear the placed cards and see new ones
|
|
||||||
• Clear to remove all cards</Text>
|
|
||||||
</Panel>
|
|
Loading…
x
Reference in New Issue
Block a user