1195 lines
44 KiB
Plaintext
1195 lines
44 KiB
Plaintext
---
|
|
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
|
--- Created by Whimsical.
|
|
--- DateTime: 2021-08-19 6:38 a.m.
|
|
---
|
|
|
|
---@type ArkhamImportConfiguration
|
|
|
|
-- Begin LoaderUi included file
|
|
local INPUT_FIELD_HEIGHT = 340
|
|
local INPUT_FIELD_WIDTH = 1500
|
|
|
|
local FIELD_COLOR = {0.9,0.7,0.5}
|
|
|
|
local PRIVATE_TOGGLE_LABELS = { }
|
|
PRIVATE_TOGGLE_LABELS[true] = "Private"
|
|
PRIVATE_TOGGLE_LABELS[false] = "Published"
|
|
local UPGRADED_TOGGLE_LABELS = { }
|
|
UPGRADED_TOGGLE_LABELS[true] = "Upgraded"
|
|
UPGRADED_TOGGLE_LABELS[false] = "Specific"
|
|
local LOAD_INVESTIGATOR_TOGGLE_LABELS = { }
|
|
LOAD_INVESTIGATOR_TOGGLE_LABELS[true] = "Yes"
|
|
LOAD_INVESTIGATOR_TOGGLE_LABELS[false] = "No"
|
|
|
|
local redDeckId = ""
|
|
local orangeDeckId = ""
|
|
local whiteDeckId = ""
|
|
local greenDeckId = ""
|
|
local privateDeck = true
|
|
local loadNewestDeck = true
|
|
local loadInvestigators = false
|
|
local loadingColor = ""
|
|
|
|
-- Returns a table with the full state of the UI, including options and deck
|
|
-- IDs. This can be used to persist via onSave(), or provide values for a load
|
|
-- operation
|
|
-- Table values:
|
|
-- redDeck: Deck ID to load for the red player
|
|
-- orangeDeck: Deck ID to load for the orange player
|
|
-- whiteDeck: Deck ID to load for the white player
|
|
-- greenDeck: Deck ID to load for the green player
|
|
-- private: True to load a private deck, false to load a public deck
|
|
-- loadNewest: True if the most upgraded version of the deck should be loaded
|
|
-- investigators: True if investigator cards should be spawned
|
|
function getUiState()
|
|
return {
|
|
redDeck = redDeckId,
|
|
orangeDeck = orangeDeckId,
|
|
whiteDeck = whiteDeckId,
|
|
greenDeck = greenDeckId,
|
|
private = privateDeck,
|
|
loadNewest = loadNewestDeck,
|
|
investigators = loadInvestigators,
|
|
}
|
|
end
|
|
|
|
-- Sets up the UI for the deck loader, populating fields from the given save
|
|
-- state table decoded from onLoad()
|
|
function initializeUi(savedUiState)
|
|
if (savedUiState ~= nil) then
|
|
redDeckId = savedUiState.redDeck
|
|
orangeDeckId = savedUiState.orangeDeck
|
|
whiteDeckId = savedUiState.whiteDeck
|
|
greenDeckId = savedUiState.greenDeck
|
|
privateDeck = savedUiState.private
|
|
loadNewestDeck = savedUiState.loadNewest
|
|
loadInvestigators = savedUiState.investigators
|
|
else
|
|
redDeckId = ""
|
|
orangeDeckId = ""
|
|
whiteDeckId = ""
|
|
greenDeckId = ""
|
|
privateDeck = true
|
|
loadNewestDeck = true
|
|
loadInvestigators = true
|
|
end
|
|
|
|
makeOptionToggles()
|
|
makeDeckIdFields()
|
|
makeBuildButton()
|
|
end
|
|
|
|
function makeOptionToggles()
|
|
-- Creates the three option toggle buttons. Each toggle assumes its index as
|
|
-- part of the toggle logic. IF YOU CHANGE THE ORDER OF THESE FIELDS YOU MUST
|
|
-- CHANGE THE EVENT HANDLERS
|
|
makePublicPrivateToggle()
|
|
makeLoadUpgradedToggle()
|
|
makeLoadInvestigatorsToggle()
|
|
end
|
|
|
|
function makePublicPrivateToggle()
|
|
local checkbox_parameters = {}
|
|
checkbox_parameters.click_function = "publicPrivateChanged"
|
|
checkbox_parameters.function_owner = self
|
|
checkbox_parameters.position = {0.25,0.1,-0.102}
|
|
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
|
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
|
checkbox_parameters.tooltip = "Published or private deck.\n\n*****PLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!"
|
|
checkbox_parameters.label = PRIVATE_TOGGLE_LABELS[privateDeck]
|
|
checkbox_parameters.font_size = 240
|
|
checkbox_parameters.scale = {0.1,0.1,0.1}
|
|
checkbox_parameters.color = FIELD_COLOR
|
|
checkbox_parameters.hover_color = {0.4,0.6,0.8}
|
|
self.createButton(checkbox_parameters)
|
|
end
|
|
|
|
function makeLoadUpgradedToggle()
|
|
local checkbox_parameters = {}
|
|
checkbox_parameters.click_function = "loadUpgradedChanged"
|
|
checkbox_parameters.function_owner = self
|
|
checkbox_parameters.position = {0.25,0.1,-0.01}
|
|
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
|
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
|
checkbox_parameters.tooltip = "Load newest upgrade, or exact deck"
|
|
checkbox_parameters.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck]
|
|
checkbox_parameters.font_size = 240
|
|
checkbox_parameters.scale = {0.1,0.1,0.1}
|
|
checkbox_parameters.color = FIELD_COLOR
|
|
checkbox_parameters.hover_color = {0.4,0.6,0.8}
|
|
self.createButton(checkbox_parameters)
|
|
end
|
|
|
|
function makeLoadInvestigatorsToggle()
|
|
local checkbox_parameters = {}
|
|
checkbox_parameters.click_function = "loadInvestigatorsChanged"
|
|
checkbox_parameters.function_owner = self
|
|
checkbox_parameters.position = {0.25,0.1,0.081}
|
|
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
|
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
|
checkbox_parameters.tooltip = "Spawn investigator cards?"
|
|
checkbox_parameters.label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators]
|
|
checkbox_parameters.font_size = 240
|
|
checkbox_parameters.scale = {0.1,0.1,0.1}
|
|
checkbox_parameters.color = FIELD_COLOR
|
|
checkbox_parameters.hover_color = {0.4,0.6,0.8}
|
|
self.createButton(checkbox_parameters)
|
|
end
|
|
|
|
-- Create the four deck ID entry fields
|
|
function makeDeckIdFields()
|
|
local input_parameters = {}
|
|
-- Parameters common to all entry fields
|
|
input_parameters.function_owner = self
|
|
input_parameters.scale = {0.1,0.1,0.1}
|
|
input_parameters.width = INPUT_FIELD_WIDTH
|
|
input_parameters.height = INPUT_FIELD_HEIGHT
|
|
input_parameters.font_size = 320
|
|
input_parameters.tooltip = "Deck ID from ArkhamDB URL of the deck\nPublic URL: 'https://arkhamdb.com/decklist/view/101/knowledge-overwhelming-solo-deck-1.0' = '101'\nPrivate URL: 'https://arkhamdb.com/deck/view/102' = '102'"
|
|
input_parameters.alignment = 3 -- Center
|
|
input_parameters.color = FIELD_COLOR
|
|
input_parameters.font_color = {0, 0, 0}
|
|
input_parameters.validation = 2 -- Integer
|
|
|
|
-- Green
|
|
input_parameters.input_function = "greenDeckChanged"
|
|
input_parameters.position = {-0.166,0.1,0.385}
|
|
input_parameters.value=greenDeckId
|
|
self.createInput(input_parameters)
|
|
-- Red
|
|
input_parameters.input_function = "redDeckChanged"
|
|
input_parameters.position = {0.171,0.1,0.385}
|
|
input_parameters.value=redDeckId
|
|
self.createInput(input_parameters)
|
|
-- White
|
|
input_parameters.input_function = "whiteDeckChanged"
|
|
input_parameters.position = {-0.166,0.1,0.474}
|
|
input_parameters.value=whiteDeckId
|
|
self.createInput(input_parameters)
|
|
-- Orange
|
|
input_parameters.input_function = "orangeDeckChanged"
|
|
input_parameters.position = {0.171,0.1,0.474}
|
|
input_parameters.value=orangeDeckId
|
|
self.createInput(input_parameters)
|
|
end
|
|
|
|
-- Create the Build All button. This is a transparent button which covers the
|
|
-- Build All portion of the background graphic
|
|
function makeBuildButton()
|
|
local button_parameters = {}
|
|
button_parameters.click_function = "loadDecks"
|
|
button_parameters.function_owner = self
|
|
button_parameters.position = {0,0.1,0.71}
|
|
button_parameters.width = 320
|
|
button_parameters.height = 30
|
|
button_parameters.color = {0, 0, 0, 0}
|
|
button_parameters.tooltip = "Click to build all four decks!"
|
|
self.createButton(button_parameters)
|
|
end
|
|
|
|
-- Event handler for the Public/Private toggle. Changes the local value and the
|
|
-- labels to toggle the button
|
|
function publicPrivateChanged()
|
|
-- editButton uses parameters.index which is 0-indexed
|
|
privateDeck = not privateDeck
|
|
self.editButton {
|
|
index = 0,
|
|
label = PRIVATE_TOGGLE_LABELS[privateDeck],
|
|
}
|
|
end
|
|
|
|
-- Event handler for the Upgraded toggle. Changes the local value and the
|
|
-- labels to toggle the button
|
|
function loadUpgradedChanged()
|
|
-- editButton uses parameters.index which is 0-indexed
|
|
loadNewestDeck = not loadNewestDeck
|
|
self.editButton {
|
|
index = 1,
|
|
label = UPGRADED_TOGGLE_LABELS[loadNewestDeck],
|
|
}
|
|
end
|
|
|
|
-- Event handler for the load investigator cards toggle. Changes the local
|
|
-- value and the labels to toggle the button
|
|
function loadInvestigatorsChanged()
|
|
-- editButton uses parameters.index which is 0-indexed
|
|
loadInvestigators = not loadInvestigators
|
|
self.editButton {
|
|
index = 2,
|
|
label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators],
|
|
}
|
|
end
|
|
|
|
-- Event handler for deck ID change
|
|
function redDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
redDeckId = inputValue
|
|
end
|
|
|
|
-- Event handler for deck ID change
|
|
function orangeDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
orangeDeckId = inputValue
|
|
end
|
|
|
|
-- Event handler for deck ID change
|
|
function whiteDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
whiteDeckId = inputValue
|
|
end
|
|
|
|
-- Event handler for deck ID change
|
|
function greenDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
greenDeckId = inputValue
|
|
end
|
|
|
|
function loadDecks()
|
|
-- testLoadLotsOfDecks()
|
|
-- Method in DeckImporterMain, visible due to inclusion
|
|
|
|
-- TODO: Make this use the configuration ID for the all cards bag
|
|
local allCardsBag = getObjectFromGUID("15bb07")
|
|
local indexReady = allCardsBag.call("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
|
|
buildDeck("Red", redDeckId)
|
|
end
|
|
if (orangeDeckId ~= nil and orangeDeckId ~= "") then
|
|
buildDeck("Orange", orangeDeckId)
|
|
end
|
|
if (whiteDeckId ~= nil and whiteDeckId ~= "") then
|
|
buildDeck("White", whiteDeckId)
|
|
end
|
|
if (greenDeckId ~= nil and greenDeckId ~= "") then
|
|
buildDeck("Green", greenDeckId)
|
|
end
|
|
end
|
|
-- End LoaderUi included file
|
|
|
|
-- Begin Zones included file (with minor modifications)
|
|
|
|
-- Sets up and returns coordinates for all possible spawn zones. Because Lua
|
|
-- assigns tables by reference and there is no built-in function to copy a
|
|
-- table this is relatively brute force.
|
|
--
|
|
-- Positions are all relative to the player mat, and most are consistent. The
|
|
-- exception are the SetAside# zones, which are placed to the left of the mat
|
|
-- for White/Green, and the right of the mat for Orange/Red.
|
|
--
|
|
-- Valid Zones:
|
|
-- Investigator: Investigator card area.
|
|
-- Minicard: Placement for the investigator's minicard. This is just above the
|
|
-- player mat, vertically in line with the investigator card area.
|
|
-- Deck, Discard: Standard locations for the deck and discard piles.
|
|
-- BlankTop, Tarot, Hand1, Hand2, Ally, BlankBottom, Accessory, Arcane1,
|
|
-- Arcane2, Body: Asset slot positions on the player mat.
|
|
-- Threat[1-4]: Threat area slots. Threat[1-3] correspond to the named threat
|
|
-- area slots, and Threat4 is the blank threat area slot.
|
|
-- SetAside[1-6]: Areas outside the player mat, to the right for Red/Orange and
|
|
-- the left for White/Green. SetAside[1-3] are a column closest to the
|
|
-- player mat, with 1 at the top of the mat and 3 at the bottom.
|
|
-- SetAside[4-6] are a column farther away from the mat, with 4 at the top
|
|
-- and 6 at the bottom.
|
|
local playerMatGuids = { }
|
|
playerMatGuids["Red"] = "0840d5"
|
|
playerMatGuids["Orange"] = "bd0ff4"
|
|
playerMatGuids["White"] = "8b081b"
|
|
playerMatGuids["Green"] = "383d8b"
|
|
|
|
local Zones = { }
|
|
|
|
local commonZones = { }
|
|
commonZones["Investigator"] = {-0.7833852, 0, 0.0001343352}
|
|
commonZones["Minicard"] = {-0.7833852, 0, -1.187242}
|
|
commonZones["Deck"] = {-1.414127, 0, -0.006668129}
|
|
commonZones["Discard"] = {-1.422189,0,0.643440}
|
|
commonZones["Ally"] = {-0.236577,0,0.023543}
|
|
commonZones["Body"] = {-0.257249,0,0.553170}
|
|
commonZones["Hand1"] = {0.600493,0,0.037291}
|
|
commonZones["Hand2"] = {0.206867,0,0.025540}
|
|
commonZones["Arcane1"] = {0.585817,0,0.567969}
|
|
commonZones["Arcane2"] = {0.197267,0,0.562296}
|
|
commonZones["Tarot"] = {0.980616,0,0.047756}
|
|
commonZones["Accessory"] = {0.976689,0,0.569344}
|
|
commonZones["BlankTop"] = {1.364696,0,0.062552}
|
|
commonZones["BlankBottom"] = {1.349999,0,0.585419}
|
|
commonZones["Threat1"] = {-0.835423,0,-0.633271}
|
|
commonZones["Threat2"] = {-0.384615,0,-0.633493}
|
|
commonZones["Threat3"] = {0.071090,0,-0.633717}
|
|
commonZones["Threat4"] = {0.520816,0,-0.633936}
|
|
|
|
Zones["White"] = { }
|
|
Zones["White"]["Investigator"] = commonZones["Investigator"]
|
|
Zones["White"]["Minicard"] = commonZones["Minicard"]
|
|
Zones["White"]["Deck"] = commonZones["Deck"]
|
|
Zones["White"]["Discard"] = commonZones["Discard"]
|
|
Zones["White"]["Ally"] = commonZones["Ally"]
|
|
Zones["White"]["Body"] = commonZones["Body"]
|
|
Zones["White"]["Hand1"] = commonZones["Hand1"]
|
|
Zones["White"]["Hand2"] = commonZones["Hand2"]
|
|
Zones["White"]["Arcane1"] = commonZones["Arcane1"]
|
|
Zones["White"]["Arcane2"] = commonZones["Arcane2"]
|
|
Zones["White"]["Tarot"] = commonZones["Tarot"]
|
|
Zones["White"]["Accessory"] = commonZones["Accessory"]
|
|
Zones["White"]["BlankTop"] = commonZones["BlankTop"]
|
|
Zones["White"]["BlankBottom"] = commonZones["BlankBottom"]
|
|
Zones["White"]["Threat1"] = commonZones["Threat1"]
|
|
Zones["White"]["Threat2"] = commonZones["Threat2"]
|
|
Zones["White"]["Threat3"] = commonZones["Threat3"]
|
|
Zones["White"]["Threat4"] = commonZones["Threat4"]
|
|
Zones["White"]["SetAside1"] = {2.004500,0,-0.520315}
|
|
Zones["White"]["SetAside2"] = {2.004500,0,0.042552}
|
|
Zones["White"]["SetAside3"] = {2.004500,0,0.605419}
|
|
Zones["White"]["UnderSetAside3"] = {2.154500,0,0.805419}
|
|
Zones["White"]["SetAside4"] = {2.434500,0,-0.520315}
|
|
Zones["White"]["SetAside5"] = {2.434500,0,0.042552}
|
|
Zones["White"]["SetAside6"] = {2.434500,0,0.605419}
|
|
Zones["White"]["UnderSetAside6"] = {2.584500,0,0.805419}
|
|
Zones["Orange"] = { }
|
|
Zones["Orange"]["Investigator"] = commonZones["Investigator"]
|
|
Zones["Orange"]["Minicard"] = commonZones["Minicard"]
|
|
Zones["Orange"]["Deck"] = commonZones["Deck"]
|
|
Zones["Orange"]["Discard"] = commonZones["Discard"]
|
|
Zones["Orange"]["Ally"] = commonZones["Ally"]
|
|
Zones["Orange"]["Body"] = commonZones["Body"]
|
|
Zones["Orange"]["Hand1"] = commonZones["Hand1"]
|
|
Zones["Orange"]["Hand2"] = commonZones["Hand2"]
|
|
Zones["Orange"]["Arcane1"] = commonZones["Arcane1"]
|
|
Zones["Orange"]["Arcane2"] = commonZones["Arcane2"]
|
|
Zones["Orange"]["Tarot"] = commonZones["Tarot"]
|
|
Zones["Orange"]["Accessory"] = commonZones["Accessory"]
|
|
Zones["Orange"]["BlankTop"] = commonZones["BlankTop"]
|
|
Zones["Orange"]["BlankBottom"] = commonZones["BlankBottom"]
|
|
Zones["Orange"]["Threat1"] = commonZones["Threat1"]
|
|
Zones["Orange"]["Threat2"] = commonZones["Threat2"]
|
|
Zones["Orange"]["Threat3"] = commonZones["Threat3"]
|
|
Zones["Orange"]["Threat4"] = commonZones["Threat4"]
|
|
Zones["Orange"]["SetAside1"] = {-2.004500,0,-0.520315}
|
|
Zones["Orange"]["SetAside2"] = {-2.004500,0,0.042552}
|
|
Zones["Orange"]["SetAside3"] = {-2.004500,0,0.605419}
|
|
Zones["Orange"]["UnderSetAside3"] = {-2.154500,0,0.80419}
|
|
Zones["Orange"]["SetAside4"] = {-2.434500,0,-0.520315}
|
|
Zones["Orange"]["SetAside5"] = {-2.434500,0,0.042552}
|
|
Zones["Orange"]["SetAside6"] = {-2.434500,0,0.605419}
|
|
Zones["Orange"]["UnderSetAside6"] = {-2.584500,0,0.80419}
|
|
|
|
-- Green positions are the same as White, and Red the same as orange, so we
|
|
-- can just point these at the White/Orange definitions
|
|
Zones["Red"] = Zones["Orange"]
|
|
Zones["Green"] = Zones["White"]
|
|
|
|
-- Returns the zone name where the specified card should be placed, based on
|
|
-- its metadata.
|
|
-- Param cardMetadata: Table of card metadata. Metadata fields type and
|
|
-- permanent are required; all others are optional.
|
|
-- Return: Zone name such as "Deck", "SetAside1", etc. See Zones object
|
|
-- documentation for a list of valid zones.
|
|
function Zones.getDefaultCardZone(cardMetadata)
|
|
if (cardMetadata.type == "Investigator") then
|
|
return "Investigator"
|
|
elseif (cardMetadata.type == "Minicard") then
|
|
return "Minicard"
|
|
elseif (cardMetadata.permanent) then
|
|
return "SetAside1"
|
|
elseif (cardMetadata.bonded_to ~= nil) then
|
|
return "SetAside2"
|
|
else
|
|
return "Deck"
|
|
end
|
|
end
|
|
|
|
-- Gets the global position for the given zone on the specified player mat.
|
|
-- Param playerColor: Color name of the player mat to get the zone position
|
|
-- for (e.g. "Red")
|
|
-- Param zoneName: Name of the zone to get the position for. See Zones object
|
|
-- documentation for a list of valid zones.
|
|
-- Return: Global position table, or nil if an invalid player color or zone
|
|
-- is specified
|
|
function Zones.getZonePosition(playerColor, zoneName)
|
|
if (playerColor ~= "Red"
|
|
and playerColor ~= "Orange"
|
|
and playerColor ~= "White"
|
|
and playerColor ~= "Green") then
|
|
return nil
|
|
end
|
|
return getObjectFromGUID(playerMatGuids[playerColor]).positionToWorld(Zones[playerColor][zoneName])
|
|
end
|
|
|
|
-- Return the global rotation for a card on the given player mat, based on its
|
|
-- metadata.
|
|
-- Param playerColor: Color name of the player mat to get the rotation
|
|
-- for (e.g. "Red")
|
|
-- Param cardMetadata: Table of card metadata. Metadata fields type and
|
|
-- permanent are required; all others are optional.
|
|
-- Return: Global rotation vector for the given card. This will include the
|
|
-- Y rotation to orient the card on the given player mat as well as a
|
|
-- Z rotation to place the card face up or face down.
|
|
function Zones.getDefaultCardRotation(playerColor, zone)
|
|
local deckRotation = getObjectFromGUID(playerMatGuids[playerColor]).getRotation()
|
|
|
|
if (zone == "Investigator") then
|
|
deckRotation = deckRotation + Vector(0, 270, 0)
|
|
elseif (zone == "Deck") then
|
|
deckRotation = deckRotation + Vector(0, 0, 180)
|
|
end
|
|
|
|
return deckRotation
|
|
end
|
|
|
|
local RANDOM_WEAKNESS_ID = "01000"
|
|
|
|
local tags = { configuration = "import_configuration_provider" }
|
|
|
|
local Priority = {
|
|
ERROR = 0,
|
|
WARNING = 1,
|
|
INFO = 2,
|
|
DEBUG = 3
|
|
}
|
|
|
|
---@type fun(text: string)
|
|
local printFunction = printToAll
|
|
local printPriority = Priority.INFO
|
|
|
|
---@param priority number
|
|
---@return string
|
|
function Priority.getLabel(priority)
|
|
if priority==0 then return "ERROR"
|
|
elseif priority==1 then return "WARNING"
|
|
elseif priority==2 then return "INFO"
|
|
elseif priority==3 then return "DEBUG"
|
|
else error(table.concat({"Priority", priority, "not found"}, " ")) return ""
|
|
end
|
|
end
|
|
|
|
---@param message string
|
|
---@param priority number
|
|
local function debugPrint(message, priority, color)
|
|
if (color == nil) then
|
|
color = { 0.5, 0.5, 0.5 }
|
|
end
|
|
if (printPriority >= priority) then
|
|
printFunction("[" .. Priority.getLabel(priority) .. "] " .. message, color)
|
|
end
|
|
end
|
|
|
|
---@param str string
|
|
---@return string
|
|
local function fixUtf16String(str)
|
|
return str:gsub("\\u(%w%w%w%w)", function (match)
|
|
return string.char(tonumber(match,16))
|
|
end)
|
|
end
|
|
|
|
--Forward declaration
|
|
---@type Request
|
|
local Request = {}
|
|
|
|
---@type table<string, ArkhamImportTaboo>
|
|
local tabooList = {}
|
|
|
|
---@return ArkhamImportConfiguration
|
|
local function getConfiguration()
|
|
local configuration = getObjectsWithTag(tags.configuration)[1]:getTable("configuration")
|
|
printPriority = configuration.priority
|
|
return configuration
|
|
end
|
|
|
|
function onLoad(script_state)
|
|
local state = JSON.decode(script_state)
|
|
initializeUi(state)
|
|
math.randomseed(os.time())
|
|
|
|
local configuration = getConfiguration()
|
|
Request.start({configuration.api_uri, configuration.taboo}, function (status)
|
|
local json = JSON.decode(fixUtf16String(status.text))
|
|
for _, taboo in pairs(json) do
|
|
---@type <string, boolean>
|
|
local cards = {}
|
|
|
|
for _, card in pairs(JSON.decode(taboo.cards)) do
|
|
cards[card.code] = true
|
|
end
|
|
|
|
tabooList[taboo.id] = {
|
|
date = taboo.date_start,
|
|
cards = cards
|
|
}
|
|
end
|
|
return true, nil
|
|
end)
|
|
end
|
|
|
|
function onSave()
|
|
return JSON.encode(getUiState())
|
|
end
|
|
|
|
-- Callback when the deck information is received from ArkhamDB. Parses the
|
|
-- response then applies standard transformations to the deck such as adding
|
|
-- random weaknesses and checking for taboos. Once the deck is processed,
|
|
-- passes to loadCards to actually spawn the defined deck.
|
|
---@param deck ArkhamImportDeck
|
|
---@param playerColor String Color name of the player mat to place this deck
|
|
-- on (e.g. "Red")
|
|
---@param configuration ArkhamImportConfiguration
|
|
local function onDeckResult(deck, playerColor, configuration)
|
|
-- Load the next deck in the upgrade path if the option is enabled
|
|
if (getUiState().loadNewest and deck.next_deck ~= nil and deck.next_deck ~= "") then
|
|
buildDeck(playerColor, deck.next_deck)
|
|
return
|
|
end
|
|
|
|
debugPrint(table.concat({ "Found decklist: ", deck.name}), Priority.INFO, playerColor)
|
|
|
|
debugPrint(table.concat({"-", deck.name, "-"}), Priority.DEBUG)
|
|
for k,v in pairs(deck) do
|
|
if type(v)=="table" then
|
|
debugPrint(table.concat {k, ": <table>"}, Priority.DEBUG)
|
|
else
|
|
debugPrint(table.concat {k, ": ", tostring(v)}, Priority.DEBUG)
|
|
end
|
|
end
|
|
debugPrint("", Priority.DEBUG)
|
|
|
|
-- Initialize deck slot table and perform common transformations. The order
|
|
-- of these should not be changed, as later steps may act on cards added in
|
|
-- each. For example, a random weakness or investigator may have bonded
|
|
-- cards or taboo entries, and should be present
|
|
local slots = deck.slots
|
|
maybeDrawRandomWeakness(slots, playerColor, configuration)
|
|
maybeAddInvestigatorCards(deck, slots)
|
|
extractBondedCards(slots, configuration)
|
|
checkTaboos(deck.taboo_id, slots, playerColor, configuration)
|
|
|
|
local commandManager = getObjectFromGUID(configuration.command_manager_guid)
|
|
|
|
---@type ArkhamImport_CommandManager_InitializationArguments
|
|
local parameters = {
|
|
configuration = configuration,
|
|
description = deck.description_md,
|
|
}
|
|
|
|
---@type ArkhamImport_CommandManager_InitializationResults
|
|
local results = commandManager:call("initialize", parameters)
|
|
|
|
if not results.is_successful then
|
|
debugPrint(results.error_message, Priority.ERROR)
|
|
return
|
|
end
|
|
|
|
loadCards(slots, deck.investigator_code, playerColor, commandManager, configuration, results.configuration)
|
|
end
|
|
|
|
-- Checks to see if the slot list includes the random weakness ID. If it does,
|
|
-- removes it from the deck and replaces it with the ID of a random basic
|
|
-- weakness provided by the all cards bag
|
|
-- Param slots: The slot list for cards in this deck. Table key is the cardId,
|
|
-- value is the number of those cards which will be spawned
|
|
-- Param playerColor: Color name of the player this deck is being loaded for.
|
|
-- Used for broadcast if a weakness is added.
|
|
-- Param configuration: The API configuration object
|
|
function maybeDrawRandomWeakness(slots, playerColor, configuration)
|
|
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
|
local hasRandomWeakness = false
|
|
for cardId, cardCount in pairs(slots) do
|
|
if (cardId == RANDOM_WEAKNESS_ID) then
|
|
hasRandomWeakness = true
|
|
break
|
|
end
|
|
end
|
|
if (hasRandomWeakness) then
|
|
local weaknessId = allCardsBag.call("getRandomWeaknessId")
|
|
slots[weaknessId] = 1
|
|
slots[RANDOM_WEAKNESS_ID] = nil
|
|
debugPrint("Random basic weakness added to deck", Priority.INFO, playerColor)
|
|
end
|
|
|
|
end
|
|
|
|
-- If the UI indicates that investigator cards should be loaded, add both the
|
|
-- investigator (XXXXX) and minicard (XXXXX-m) slots with one copy each
|
|
-- Param deck: The processed ArkhamDB deck response
|
|
-- Param slots: The slot list for cards in this deck. Table key is the cardId,
|
|
-- value is the number of those cards which will be spawned
|
|
function maybeAddInvestigatorCards(deck, slots)
|
|
if (getUiState().investigators) then
|
|
local investigatorId = deck.investigator_code
|
|
slots[investigatorId.."-m"] = 1
|
|
local deckMeta = JSON.decode(deck.meta)
|
|
local parallelFront = deckMeta ~= nil and deckMeta.alternate_front ~= nil and deckMeta.alternate_front ~= ""
|
|
local parallelBack = deckMeta ~= nil and deckMeta.alternate_back ~= nil and deckMeta.alternate_back ~= ""
|
|
if (parallelFront and parallelBack) then
|
|
investigatorId = investigatorId.."-p"
|
|
elseif (parallelFront) then
|
|
|
|
local alternateNum = tonumber(deckMeta.alternate_front)
|
|
if (alternateNum >= 01501 and alternateNum <= 01506) then
|
|
investigatorId = investigatorId.."-r"
|
|
else
|
|
investigatorId = investigatorId.."-pf"
|
|
end
|
|
elseif (parallelBack) then
|
|
investigatorId = investigatorId.."-pb"
|
|
end
|
|
slots[investigatorId] = 1
|
|
end
|
|
end
|
|
|
|
-- Process the slot list and looks for any cards which are bonded to those in
|
|
-- the deck. Adds those cards to the slot list.
|
|
-- Param slots: The slot list for cards in this deck. Table key is the cardId,
|
|
-- value is the number of those cards which will be spawned
|
|
-- Param configuration: The API configuration object
|
|
function extractBondedCards(slots, configuration)
|
|
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
|
-- Create a list of bonded cards first so we don't modify slots while iterating
|
|
local bondedCards = { }
|
|
for cardId, cardCount in pairs(slots) do
|
|
local card = allCardsBag.call("getCardById", { id = cardId })
|
|
if (card ~= nil and card.metadata.bonded ~= nil) then
|
|
for _, bond in ipairs(card.metadata.bonded) do
|
|
bondedCards[bond.id] = bond.count
|
|
end
|
|
end
|
|
end
|
|
-- Add any bonded cards to the main slots list
|
|
for bondedId, bondedCount in pairs(bondedCards) do
|
|
slots[bondedId] = bondedCount
|
|
end
|
|
end
|
|
|
|
-- Check the deck for any cards on its taboo list. If they're found, replace
|
|
-- the entry in the slot with the Taboo id (i.e. "XXXX" becomes "XXXX-t")
|
|
-- Param tabooId: The deck's taboo ID, taken from the deck response taboo_id
|
|
-- field. May be nil, indicating that no taboo list should be used
|
|
-- Param slots: The slot list for cards in this deck. Table key is the cardId,
|
|
-- value is the number of those cards which will be spawned
|
|
function checkTaboos(tabooId, slots, playerColor, configuration)
|
|
if (tabooId) then
|
|
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
|
for cardId, _ in pairs(tabooList[tabooId].cards) do
|
|
if (slots[cardId] ~= nil) then
|
|
-- Make sure there's a taboo version of the card before we replace it
|
|
-- SCED only maintains the most recent taboo cards. If a deck is using
|
|
-- an older taboo list it's possible the card isn't a taboo any more
|
|
local tabooCard = allCardsBag.call("getCardById", { id = cardId.."-t" })
|
|
if (tabooCard == nil) then
|
|
local basicCard = allCardsBag.call("getCardById", { id = cardId })
|
|
debugPrint("Taboo version for "..basicCard.data.Nickname..
|
|
" is not available. Using standard version", Priority.WARNING, playerColor)
|
|
else
|
|
slots[cardId.."-t"] = slots[cardId]
|
|
slots[cardId] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Process the slot list, which defines the card Ids and counts of cards to
|
|
-- load. Spawn those cards at the appropriate zones, and report an error to the
|
|
-- user if any could not be loaded.
|
|
--
|
|
-- This method uses an encapsulated coroutine with yields to make the card
|
|
-- spawning cleaner.
|
|
--
|
|
-- Param slots: Key-Value table of cardId:count. cardId is the ArkhamDB ID of
|
|
-- the card to spawn, and count is the number which should be spawned
|
|
-- Param investigatorId: String ArkhamDB ID (code) for this deck's investigator.
|
|
-- Investigator cards should already be added to the slots list if they
|
|
-- should be spawned, but this value is separate to check for special
|
|
-- handling for certain investigators
|
|
-- Param playerColor String Color name of the player mat to place this deck
|
|
-- on (e.g. "Red")
|
|
-- Param commandManager
|
|
-- Param configuration: Loader configuration object
|
|
-- Param command_config:
|
|
function loadCards(slots, investigatorId, playerColor, commandManager, configuration, command_config)
|
|
function coinside()
|
|
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
|
local yPos = { }
|
|
local cardsToSpawn = { }
|
|
for cardId, cardCount in pairs(slots) do
|
|
local card = allCardsBag.call("getCardById", { id = cardId })
|
|
if (card ~= nil) then
|
|
local cardZone = Zones.getDefaultCardZone(card.metadata)
|
|
for i = 1, cardCount do
|
|
table.insert(cardsToSpawn, { data = card.data, metadata = card.metadata, zone = cardZone })
|
|
end
|
|
slots[cardId] = 0
|
|
end
|
|
end
|
|
|
|
-- TODO: Re-enable this later, as a command
|
|
--handleAltInvestigatorCard(cardsToSpawn, "promo", configuration)
|
|
|
|
table.sort(cardsToSpawn, cardComparator)
|
|
|
|
-- TODO: Process commands for the cardsToSpawn list
|
|
|
|
-- These should probably be commands, once the command handler is updated
|
|
handleStartsInPlay(cardsToSpawn)
|
|
handleAncestralKnowledge(cardsToSpawn)
|
|
handleHunchDeck(investigatorId, cardsToSpawn, playerColor)
|
|
|
|
-- Count the number of cards in each zone so we know if it's a deck or card.
|
|
-- TTS's Card vs. Deck distinction requires this since we can't spawn a deck
|
|
-- with only one card
|
|
local zoneCounts = getZoneCounts(cardsToSpawn)
|
|
local zoneDecks = { }
|
|
for zone, count in pairs(zoneCounts) do
|
|
if (count > 1) then
|
|
zoneDecks[zone] = buildDeckDataTemplate()
|
|
end
|
|
end
|
|
-- For each card in a deck zone, add it to that deck. Otherwise, spawn it
|
|
-- directly
|
|
for _, spawnCard in ipairs(cardsToSpawn) do
|
|
if (zoneDecks[spawnCard.zone] ~= nil) then
|
|
addCardToDeck(zoneDecks[spawnCard.zone], spawnCard.data)
|
|
else
|
|
local cardPos = Zones.getZonePosition(playerColor, spawnCard.zone)
|
|
cardPos.y = 2
|
|
spawnObjectData({
|
|
data = spawnCard.data,
|
|
position = cardPos,
|
|
rotation = Zones.getDefaultCardRotation(playerColor, spawnCard.zone)})
|
|
end
|
|
end
|
|
-- Spawn each of the decks
|
|
for zone, deck in pairs(zoneDecks) do
|
|
local deckPos = Zones.getZonePosition(playerColor, zone)
|
|
deckPos.y = 3
|
|
spawnObjectData({
|
|
data = deck,
|
|
position = deckPos,
|
|
rotation = Zones.getDefaultCardRotation(playerColor, zone)})
|
|
coroutine.yield(0)
|
|
end
|
|
|
|
-- Look for any cards which haven't been loaded
|
|
local hadError = false
|
|
for cardId, remainingCount in pairs(slots) do
|
|
if (remainingCount > 0) then
|
|
hadError = true
|
|
local request = Request.start({
|
|
configuration.api_uri,
|
|
configuration.cards,
|
|
cardId},
|
|
function(result)
|
|
local adbCardInfo = JSON.decode(fixUtf16String(result.text))
|
|
local cardName = adbCardInfo.real_name
|
|
if (cardName ~= nil) then
|
|
if (adbCardInfo.xp ~= nil and adbCardInfo.xp > 0) then
|
|
cardName = cardName.." ("..adbCardInfo.xp..")"
|
|
end
|
|
debugPrint("Card not found: "..cardName..", ArkhamDB ID "..cardId, Priority.ERROR, playerColor)
|
|
else
|
|
debugPrint("Card not found in ArkhamDB, ID "..cardId, Priority.ERROR, playerColor)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
if (not hadError) then
|
|
debugPrint("Deck loaded successfully!", Priority.INFO, playerColor)
|
|
end
|
|
return 1
|
|
end
|
|
startLuaCoroutine(self, "coinside")
|
|
end
|
|
|
|
-- Inserts a card into the given deck. This does three things:
|
|
-- 1. Add the card's data to ContainedObjects
|
|
-- 2. Add the card's ID (the TTS CardID, not the Arkham ID) to the deck's
|
|
-- ID list. Note that the deck's ID list is "DeckIDs" even though it
|
|
-- contains a list of card Ids
|
|
-- 3. Extract the card's CustomDeck table and add it to the deck. The deck's
|
|
-- "CustomDeck" field is a list of all CustomDecks used by cards within the
|
|
-- deck, keyed by the DeckID and referencing the custom deck table
|
|
-- Param deck: TTS deck data structure to add to
|
|
-- Param card: Data for the card to be inserted
|
|
function addCardToDeck(deck, cardData)
|
|
table.insert(deck.ContainedObjects, cardData)
|
|
table.insert(deck.DeckIDs, cardData.CardID)
|
|
for customDeckId, customDeckData in pairs(cardData.CustomDeck) do
|
|
deck.CustomDeck[customDeckId] = customDeckData
|
|
end
|
|
end
|
|
|
|
-- Count the number of cards in each zone
|
|
-- Param cards: Table of {cardData, cardMetadata, zone}
|
|
-- Return: Table of {zoneName=zoneCount}
|
|
function getZoneCounts(cards)
|
|
local counts = { }
|
|
for _, card in ipairs(cards) do
|
|
if (counts[card.zone] == nil) then
|
|
counts[card.zone] = 1
|
|
else
|
|
counts[card.zone] = counts[card.zone] + 1
|
|
end
|
|
end
|
|
|
|
return counts
|
|
end
|
|
|
|
-- Create an empty deck data table which can have cards added to it. This
|
|
-- creates a new table on each call without using metatables or previous
|
|
-- definitions because we can't be sure that TTS doesn't modify the structure
|
|
-- Return: Table containing the minimal TTS deck data structure
|
|
function buildDeckDataTemplate()
|
|
local deck = { }
|
|
deck.Name = "Deck"
|
|
|
|
-- Card data. DeckIDs and CustomDeck entries will be built from the cards
|
|
deck.ContainedObjects = { }
|
|
deck.DeckIDs = { }
|
|
deck.CustomDeck = { }
|
|
|
|
-- Transform is required, Position and Rotation will be overridden by the
|
|
-- spawn call so can be omitted here
|
|
deck.Transform = {
|
|
scaleX = 1,
|
|
scaleY = 1,
|
|
scaleZ = 1, }
|
|
|
|
return deck
|
|
end
|
|
|
|
-- Get the PBN (Permanent/Bonded/Normal) value from the given metadata.
|
|
-- Return: 1 for Permanent, 2 for Bonded, or 3 for Normal. The actual values
|
|
-- are irrelevant as they provide only grouping and the order between them
|
|
-- doesn't matter.
|
|
function getPbn(metadata)
|
|
if (metadata.permanent) then
|
|
return 1
|
|
elseif (metadata.bonded_to ~= nil) then
|
|
return 2
|
|
else -- Normal card
|
|
return 3
|
|
end
|
|
end
|
|
|
|
-- Comparison function used to sort the cards in a deck. Groups bonded or
|
|
-- permanent cards first, then sorts within theose types by name/subname.
|
|
-- Normal cards will sort in standard alphabetical order, while permanent/bonded
|
|
-- will be in reverse alphabetical order.
|
|
--
|
|
-- Since cards spawn in the order provided by this comparator, with the first
|
|
-- cards ending up at the bottom of a pile, this ordering will spawn in reverse
|
|
-- alphabetical order. This presents the cards in order for non-face-down
|
|
-- areas, and presents them in order when Searching the face-down deck.
|
|
function cardComparator(card1, card2)
|
|
local pbn1 = getPbn(card1.metadata)
|
|
local pbn2 = getPbn(card2.metadata)
|
|
if (pbn1 ~= pbn2) then
|
|
return pbn1 > pbn2
|
|
end
|
|
if (pbn1 == 3) then
|
|
if (card1.data.Nickname ~= card2.data.Nickname) then
|
|
return card1.data.Nickname < card2.data.Nickname
|
|
end
|
|
return card1.data.Description < card2.data.Description
|
|
else
|
|
if (card1.data.Nickname ~= card2.data.Nickname) then
|
|
return card1.data.Nickname > card2.data.Nickname
|
|
end
|
|
return card1.data.Description > card2.data.Description
|
|
end
|
|
end
|
|
|
|
-- Replace the investigator card and minicard with an alternate version. This
|
|
-- will find the relevant cards and look for IDs with <id>-<altVersionTag>, and
|
|
-- <id>-<altVersionTag>-m, and update the entries in cardList with the new card
|
|
-- data.
|
|
--
|
|
-- Param cardList: Deck list being created
|
|
-- Param altVersionTag: The tag for the different version, currently the only
|
|
-- alt versions are "promo", but will soon inclide "revised"
|
|
-- Param configuration: ArkhamDB configuration defniition, used for the card bag
|
|
function handleAltInvestigatorCard(cardList, altVersionTag, configuration)
|
|
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
|
for _, card in ipairs(cardList) do
|
|
if (card.metadata.type == "Investigator") then
|
|
local altInvestigator = allCardsBag.call("getCardById", { id = card.metadata.id.."-"..altVersionTag})
|
|
if (altInvestigator ~= nil) then
|
|
card.data = altInvestigator.data
|
|
card.metadata = altInvestigator.metadata
|
|
end
|
|
end
|
|
if (card.metadata.type == "Minicard") then
|
|
-- -promo comes before -m in the ID, so needs a little massaging
|
|
local investigatorId = string.sub(card.metadata.id, 1, 5)
|
|
local altMinicard = allCardsBag.call("getCardById", { id = investigatorId.."-"..altVersionTag.."-m"})
|
|
if (altMinicard ~= nil) then
|
|
card.data = altMinicard.data
|
|
card.metadata = altMinicard.metadata
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Place cards which start in play (Duke, Sophie) in the play area
|
|
function handleStartsInPlay(cardList)
|
|
for _, card in ipairs(cardList) do
|
|
-- 02014 = Duke (Ashcan Pete)
|
|
-- 03009 = Sophie (Mark Harrigan)
|
|
if (card.metadata.id == "02014" or card.metadata.id == "03009") then
|
|
card.zone = "BlankTop"
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Check to see if the deck list has Ancestral Knowledge. If it does, move 5
|
|
-- random skills to SetAside3
|
|
function handleAncestralKnowledge(cardList)
|
|
local hasAncestralKnowledge = false
|
|
local skillList = { }
|
|
-- Have to process the entire list to check for Ancestral Knowledge and get
|
|
-- all possible skills, so do both in one pass
|
|
for i, card in ipairs(cardList) do
|
|
if (card.metadata.id == "07303") then
|
|
-- Ancestral Knowledge found
|
|
hasAncestralKnowledge = true
|
|
card.zone = "SetAside3"
|
|
elseif (card.metadata.type == "Skill"
|
|
and card.metadata.bonded_to == nil
|
|
and not card.metadata.weakness) then
|
|
table.insert(skillList, i)
|
|
end
|
|
end
|
|
if (hasAncestralKnowledge) then
|
|
for i = 1,5 do
|
|
-- Move 5 random skills to SetAside3
|
|
local skillListIndex = math.random(#skillList)
|
|
cardList[skillList[skillListIndex]].zone = "UnderSetAside3"
|
|
table.remove(skillList, skillListIndex)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If the investigator is Joe Diamond, extract all Insight events to SetAside5
|
|
-- to build the Hunch Deck.
|
|
-- Param investigatorId: ID for the deck's investigator card. Passed separately
|
|
-- because the investigator may not be included in the cardList
|
|
-- Param cardList: Deck list being created
|
|
-- Param playerColor: Color this deck is being loaded for
|
|
function handleHunchDeck(investigatorId, cardList, playerColor)
|
|
if (investigatorId == "05002") then -- Joe Diamond
|
|
local insightList = { }
|
|
for i, card in ipairs(cardList) do
|
|
if (card.metadata.type == "Event"
|
|
and string.match(card.metadata.traits, "Insight")
|
|
and card.metadata.bonded_to == nil) then
|
|
table.insert(insightList, i)
|
|
end
|
|
end
|
|
-- Process insights to move them to the hunch deck. This is done in reverse
|
|
-- order because the sorting needs to be reversed (deck sorts for face down)
|
|
-- Performance here may be an issue, as table.remove() is an O(n) operation
|
|
-- which makes the full shift O(n^2). But keep it simple unless it becomes
|
|
-- a problem
|
|
for i = #insightList, 1, -1 do
|
|
local moving = cardList[insightList[i]]
|
|
moving.zone = "SetAside5"
|
|
table.remove(cardList, insightList[i])
|
|
table.insert(cardList, moving)
|
|
end
|
|
if (#insightList < 11) then
|
|
debugPrint("Joe's hunch deck must have 11 cards but the deck only has "..#insightList.." Insight events", Priority.INFO, playerColor)
|
|
elseif (#insightList > 11) then
|
|
debugPrint("Moved all "..#insightList.." Insight events to the hunch deck, reduce it to 11", Priority.INFO, playerColor)
|
|
else
|
|
debugPrint("Built Joe's hunch deck", Priority.INFO, playerColor)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Test method. Loads all decks which were submitted to ArkhamDB on a given
|
|
-- date window.
|
|
function testLoadLotsOfDecks()
|
|
local configuration = getConfiguration()
|
|
local numDays = 7
|
|
local day = os.time{year=2021, month=7, day=15} -- Start date here
|
|
for i=1,numDays do
|
|
local dateString = os.date("%Y-%m-%d", day)
|
|
local deckList = Request.start({
|
|
configuration.api_uri,
|
|
"decklists/by_date",
|
|
dateString,
|
|
},
|
|
function(result)
|
|
local json = JSON.decode(result.text)
|
|
for i, deckData in ipairs(json) do
|
|
buildDeck(getColorForTest(i), deckData.id)
|
|
end
|
|
end)
|
|
day = day + (60 * 60 * 24) -- Move forward by one day
|
|
end
|
|
end
|
|
|
|
-- Rotates the player mat based on index, to spread the card stacks during
|
|
-- a mass load
|
|
function getColorForTest(index)
|
|
if (index % 4 == 0) then
|
|
return "Red"
|
|
elseif (index % 4 == 1) then
|
|
return "Orange"
|
|
elseif (index % 4 == 2) then
|
|
return "White"
|
|
elseif (index % 4 == 3) then
|
|
return "Green"
|
|
end
|
|
end
|
|
|
|
-- Start the deck build process for the given player color and deck ID. This
|
|
-- will retrieve the deck from ArkhamDB, and pass to a callback for processing.
|
|
-- Param playerColor String Color name of the player mat to place this deck
|
|
-- on (e.g. "Red")
|
|
-- Param deckId: ArkhamDB deck id to be loaded
|
|
function buildDeck(playerColor, deckId)
|
|
local configuration = getConfiguration()
|
|
-- Get a simple card to see if the bag indexes are complete. If not, abort
|
|
-- the deck load. The called method will handle player notification.
|
|
local allCardsBag = getObjectFromGUID(configuration.card_bag_guid)
|
|
local checkCard = allCardsBag.call("getCardById", { id = "01001"})
|
|
if (checkCard ~= nil and checkCard.data == nil) then
|
|
return
|
|
end
|
|
|
|
local deckUri = { configuration.api_uri, getUiState().private and configuration.private_deck or configuration.public_deck, deckId }
|
|
|
|
local deck = Request.start(deckUri, function (status)
|
|
if string.find(status.text, "<!DOCTYPE html>") then
|
|
debugPrint("Private deck ID "..deckId.." is not shared", Priority.ERROR, playerColor)
|
|
return false, table.concat({ "Private deck ", deckId, " is not shared"})
|
|
end
|
|
local json = JSON.decode(status.text)
|
|
|
|
if not json then
|
|
debugPrint("Deck ID "..deckId.." not found", Priority.ERROR, playerColor)
|
|
return false, "Deck not found!"
|
|
end
|
|
|
|
return true, JSON.decode(status.text)
|
|
end)
|
|
|
|
deck:with(onDeckResult, playerColor, configuration)
|
|
end
|
|
|
|
---@type Request
|
|
Request = {
|
|
is_done = false,
|
|
is_successful = false
|
|
}
|
|
|
|
--- Creates a new instance of a Request. Should not be directly called. Instead use Request.start and Request.deferred.
|
|
---@param uri string
|
|
---@param configure fun(request: Request, status: WebRequestStatus)
|
|
---@return Request
|
|
function Request:new(uri, configure)
|
|
local this = {}
|
|
|
|
setmetatable(this, self)
|
|
self.__index = self
|
|
|
|
if type(uri)=="table" then
|
|
uri = table.concat(uri, "/")
|
|
end
|
|
|
|
this.uri = uri
|
|
|
|
WebRequest.get(uri, function(status)
|
|
configure(this, status)
|
|
end)
|
|
|
|
return this
|
|
end
|
|
|
|
--- Creates a new request. on_success should set the request's is_done, is_successful, and content variables.
|
|
--- Deferred should be used when you don't want to set is_done immediately (such as if you want to wait for another request to finish)
|
|
---@param uri string
|
|
---@param on_success fun(request: Request, status: WebRequestStatus, vararg any)
|
|
---@param on_error fun(status: WebRequestStatus)|nil
|
|
---@vararg any[]
|
|
---@return Request
|
|
function Request.deferred(uri, on_success, on_error, ...)
|
|
local parameters = table.pack(...)
|
|
return Request:new(uri, function (request, status)
|
|
if (status.is_done) then
|
|
if (status.is_error) then
|
|
request.error_message = on_error and on_error(status, table.unpack(parameters)) or status.error
|
|
request.is_successful = false
|
|
request.is_done = true
|
|
else
|
|
on_success(request, status)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
--- Creates a new request. on_success should return weather the resultant data is as expected, and the processed content of the request.
|
|
---@param uri string
|
|
---@param on_success fun(status: WebRequestStatus, vararg any): boolean, any
|
|
---@param on_error nil|fun(status: WebRequestStatus, vararg any): string
|
|
---@vararg any[]
|
|
---@return Request
|
|
function Request.start(uri, on_success, on_error, ...)
|
|
local parameters = table.pack(...)
|
|
return Request.deferred(uri, function(request, status)
|
|
local result, message = on_success(status, table.unpack(parameters))
|
|
if not result then request.error_message = message else request.content = message end
|
|
request.is_successful = result
|
|
request.is_done = true
|
|
end, on_error, table.unpack(parameters))
|
|
end
|
|
|
|
---@param requests Request[]
|
|
---@param on_success fun(content: any[], vararg any[])
|
|
---@param on_error fun(requests: Request[], vararg any[])|nil
|
|
---@vararg any
|
|
function Request.with_all(requests, on_success, on_error, ...)
|
|
local parameters = table.pack(...)
|
|
|
|
Wait.condition(function ()
|
|
---@type any[]
|
|
local results = {}
|
|
|
|
---@type Request[]
|
|
local errors = {}
|
|
|
|
for _, request in ipairs(requests) do
|
|
if request.is_successful then
|
|
table.insert(results, request.content)
|
|
else
|
|
table.insert(errors, request)
|
|
end
|
|
end
|
|
|
|
if (#errors<=0) then
|
|
on_success(results, table.unpack(parameters))
|
|
elseif on_error ==nil then
|
|
for _, request in ipairs(errors) do
|
|
debugPrint(table.concat({ "[ERROR]", request.uri, ":", request.error_message }), Priority.ERROR)
|
|
end
|
|
else
|
|
on_error(requests, table.unpack(parameters))
|
|
end
|
|
end, function ()
|
|
for _, request in ipairs(requests) do
|
|
if not request.is_done then return false end
|
|
end
|
|
return true
|
|
end)
|
|
end
|
|
|
|
---@param callback fun(content: any, vararg any)
|
|
function Request:with(callback, ...)
|
|
local arguments = table.pack(...)
|
|
Wait.condition(function ()
|
|
if self.is_successful then
|
|
callback(self.content, table.unpack(arguments))
|
|
end
|
|
end, function () return self.is_done
|
|
end)
|
|
end |