--- --- Generated by EmmyLua(https://github.com/EmmyLua) --- Created by Whimsical. --- DateTime: 2021-08-19 6:38 a.m. --- ---@type ArkhamImportConfiguration local tags = { configuration = "import_configuration_provider" } local Priority = { ERROR = 0, WARNING = 1, INFO = 2, DEBUG = 3 } ---@type fun(text: string) local print_fun = print local print_priority = Priority.DEBUG ---@param priority number ---@return string function Priority.get_label(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 debug_print(message, priority) if (print_priority >= priority) then print_fun("[" .. Priority.get_label(priority) .. "] " .. message) end end ---@param str string ---@return string local function fix_utf16_string(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 local taboo_list = {} ---@type number local deck_type_button_index local is_private_deck = true function on_decktype_checkbox_clicked() self:editButton { label = is_private_deck and "Published" or "Private", index = deck_type_button_index } is_private_deck = not is_private_deck end ---@return ArkhamImportConfiguration local function get_configuration() local configuration = getObjectsWithTag(tags.configuration)[1]:getTable("configuration") print_priority = configuration.priority return configuration end ---@param configuration ArkhamImportConfiguration local function initialize(_, configuration) local builder = getObjectFromGUID(configuration.ui_builder_guid) deck_type_button_index = builder:call("create_ui", { target_guid = self:getGUID(), debug_deck_id = configuration.debug_deck_id, checkbox_toggle_callback_name = "on_decktype_checkbox_clicked", build_deck_callback_name = "build_deck" }) end function onLoad() Wait.frames(function () local configuration = get_configuration() local taboo = Request.start({configuration.api_uri, configuration.taboo}, function (status) local json = JSON.decode(fix_utf16_string(status.text)) for _, taboo in pairs(json) do ---@type local cards = {} for _, card in pairs(JSON.decode(taboo.cards)) do cards[card.code] = true end taboo_list[taboo.id] = { date = taboo.date_start, cards = cards } end return true, nil end) taboo:with(initialize, configuration) end, 1) end ---@param status WebRequestStatus ---@param number number ---@param is_bonded boolean ---@return boolean, ArkhamImportCard local function on_card_request(status, number, is_bonded) local text = fix_utf16_string(status.text) ---@type ArkhamImportCard local card = JSON.decode(text) card.count = number card.is_bonded = is_bonded return true, card end ---@param configuration ArkhamImportConfiguration ---@param card_code string ---@param count number ---@param is_bonded boolean ---@return Request local function add_card(configuration, card_code, count, is_bonded) local api, card_path = configuration.api_uri, configuration.cards local request = Request.start({api, card_path, card_code}, on_card_request, nil, count, is_bonded) return request end ---@param source TTSObject ---@param count number ---@param zones ArkhamImportZone[] ---@param keep_card boolean ---@return fun(card: TTSObject) local function position_card(source, count, zones, keep_card) ---@param card TTSObject return function (card) for n = 1, count do local zone = zones[n] local destination = zone.is_absolute and zone.position or self:positionToWorld(zone.position) local rotation = self:getRotation() + Vector(0, 0, zone.is_facedown and 180 or 0) card:clone { position = destination, rotation = rotation } end if keep_card then source:putObject(card) else card:destruct() end end end ---@param source TTSObject ---@param target_name string ---@param target_subname string ---@param count number ---@param zone ArkhamImportZone[] local function process_card(source, target_name, target_subname, count, zone) for _, card in ipairs(source:getObjects()) do if (card.name == target_name and (not target_subname or card.description==target_subname)) then source:takeObject { position = {0, 1.5, 0}, index = card.index, smooth = false, callback_function = position_card(source, count, zone, true) } debug_print(table.concat({ "Added", count, "of", target_name}, " "), Priority.DEBUG) return end end debug_print(table.concat({ "Card not found:", target_name}, " "), Priority.WARNING) end ---@param source TTSObject ---@param zones ArkhamImportZone[] local function random_weakness(source, zones) source:shuffle() local card = source:takeObject { position = {0, 1.5, 0}, index = 0, smooth = false, callback_function = position_card(source, 1, zones, false), } broadcastToAll("Drew random basic weakness: " .. card:getName()) end ---@param configuration ArkhamImportConfiguration ---@param card_id string ---@param used_bindings table ---@param requests Request[] local function process_bindings(configuration, card_id, used_bindings, requests) local bondedCards = configuration.bonded_cards[card_id] if not bondedCards then return end if bondedCards.code then bondedCards = {bondedCards} end for _, bond in ipairs(bondedCards) do if not used_bindings[bond.code] then used_bindings[bond.code] = true local result = add_card(configuration, bond.code, bond.count, true) table.insert(requests, result) end end end ---@param configuration ArkhamImportConfiguration ---@param slots table ---@return Request[] local function load_cards(configuration, slots) ---@type Request[] local requests = {} ---@type local used_bindings = {} -- Bonded cards that we've already processed for card_id, number in pairs(slots) do table.insert(requests, add_card(configuration, card_id, number, false)) process_bindings(configuration, card_id, used_bindings, requests) end return requests end ---@type string[] local parallel_component = {"", " (Parallel Back)", " (Parallel Front)", " (Parallel)"} ---@param discriminators table ---@param card ArkhamImportCard ---@param taboo ArkhamImportTaboo ---@param meta table ---@return string, string|nil local function get_card_selector(discriminators, card, taboo, meta) local discriminator = discriminators[card.code] if card.type_code == "investigator" then local parallel = (meta.alternate_front and 2 or 0) + (meta.alternate_back and 1 or 0) return table.concat {card.real_name, parallel_component[parallel]}, nil end local xp_component = "" if ((tonumber(card.xp) or 0) > 0) then xp_component = table.concat {" (", card.xp, ")"} end local taboo_component = "" local cards = taboo.cards or {} if (cards[card.code]) then taboo_component = " (Taboo)" end local target_name = table.concat({ card.real_name, xp_component, taboo_component }) local target_subname = discriminator or card.subname return target_name, target_subname end ---@param zone string ---@param count number ---@return string[] local function fill_zone(zone, count) local result = {} for n=1,count do result[n] = zone end return result end ---@param card ArkhamImportCard ---@param zone string[] ---@param override string[] ---@return string[] local function get_zone_id(card, zone, override) local result = {} for n=1,card.count do result[n] = zone[n] or override[n] or (card.is_bonded and "bonded") or (card.permanent and "permanent") or (card.subtype_name and card.subtype_name:find("Weakness") and "weakness") or (card.type_code == "investigator" and "investigator") or "default" end return result end ---@param cards ArkhamImportCard[] ---@param deck ArkhamImportDeck ---@param command_manager TTSObject ---@param configuration ArkhamImportConfiguration local function on_cards_ready(cards, deck, command_manager, configuration) local card_bag = getObjectFromGUID(configuration.card_bag_guid) local weakness_bag = getObjectFromGUID(configuration.weaknesses_bag_guid) local investigator_bag = getObjectFromGUID(configuration.investigator_bag_guid) local minicard_bag = getObjectFromGUID(configuration.minicard_bag_guid) local taboo = taboo_list[deck.taboo_id] or {} local meta = deck.meta and JSON.decode(deck.meta) or {} for _, card in ipairs(cards) do ---@type ArkhamImport_Command_HandlerArguments local parameters = { configuration = configuration, source_guid = self:getGUID(), zone = {}, card = card, } ---@type ArkhamImport_CommandManager_HandlerResults local command_result = command_manager:call("handle", parameters) if not command_result.is_successful then debug_print(command_result.error_message, Priority.ERROR) return end local card = command_result.card if not command_result.handled then local target_name, target_subname = get_card_selector(configuration.discriminators, card, taboo, meta) local override = configuration.default_zone_overrides[card.code] if type(override)=="string" then override = fill_zone(override, card.count) end local zone = get_zone_id(card, command_result.zone, configuration.default_zone_overrides[card.code] or {}) local spawn_zones = {} local zones = configuration.zones for index, zone in ipairs(zone) do spawn_zones[index] = zones[zone] end if card.real_name == "Random Basic Weakness" then random_weakness(weakness_bag, spawn_zones) elseif card.type_code == "investigator" then process_card(investigator_bag, target_name, nil, card.count, spawn_zones) process_card(minicard_bag, card.real_name, nil, card.count, spawn_zones) else process_card(card_bag, target_name, target_subname, card.count, spawn_zones) end end end end ---@param deck ArkhamImportDeck ---@param configuration ArkhamImportConfiguration local function on_deck_result(deck, configuration) debug_print(table.concat({ "Found decklist: ", deck.name}), Priority.INFO) debug_print(table.concat({"-", deck.name, "-"}), Priority.DEBUG) for k,v in pairs(deck) do if type(v)=="table" then debug_print(table.concat {k, ": "}, Priority.DEBUG) else debug_print(table.concat {k, ": ", tostring(v)}, Priority.DEBUG) end end debug_print("", Priority.DEBUG) local investigator_id = deck.investigator_code local slots = deck.slots slots[investigator_id] = 1 ---@type ArkhamImportCard[] local requests = load_cards(configuration, deck.slots) local command_manager = getObjectFromGUID(configuration.command_manager_guid) ---@type ArkhamImport_CommandManager_InitializationArguments local parameters = { configuration = configuration, description = deck.description_md, } ---@type ArkhamImport_CommandManager_InitializationResults local results = command_manager:call("initialize", parameters) if not results.is_successful then debug_print(results.error_message, Priority.ERROR) return end Request.with_all(requests, on_cards_ready, nil, deck, command_manager, results.configuration) end function build_deck() local configuration = get_configuration() local deck_id = self:getInputs()[1].value local deck_uri = { configuration.api_uri, is_private_deck and configuration.private_deck or configuration.public_deck, deck_id } local deck = Request.start(deck_uri, function (status) if string.find(status.text, "") then return false, table.concat({ "Private deck ", deck_id, " is not shared"}) end local json = JSON.decode(status.text) if not json then return false, "Deck not found!" end return true, JSON.decode(status.text) end) deck:with(on_deck_result, 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 debug_print(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