ah_sce_unpacked/unpacked/Custom_Token Search-A-Card 24051a.ttslua

516 lines
19 KiB
Plaintext
Raw Normal View History

2022-12-13 14:02:30 -05:00
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
2024-01-06 21:32:29 -05:00
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local GUIDReferenceApi = {}
local function getGuidHandler()
return getObjectFromGUID("123456")
end
-- returns all matching objects as a table with references
---@param owner String Parent object for this search
---@param type String Type of object to search for
GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)
return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type })
end
-- returns all matching objects as a table with references
---@param type String Type of object to search for
GUIDReferenceApi.getObjectsByType = function(type)
return getGuidHandler().call("getObjectsByType", type)
end
-- returns all matching objects as a table with references
---@param owner String Parent object for this search
GUIDReferenceApi.getObjectsByOwner = function(owner)
return getGuidHandler().call("getObjectsByOwner", owner)
end
return GUIDReferenceApi
end
end)
2022-12-13 14:02:30 -05:00
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/CardSearch")
end)
__bundle_register("playercards/CardSearch", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/PlayerCardSpawner")
2024-01-06 21:32:07 -05:00
local allCardsBagApi = require("playercards/AllCardsBagApi")
2022-12-13 14:02:30 -05:00
local buttonParameters = {}
buttonParameters.function_owner = self
buttonParameters.height = 200
buttonParameters.width = 1200
buttonParameters.font_size = 75
2022-10-19 19:07:47 -04:00
2022-12-13 14:02:30 -05:00
local BUTTON_LABELS = {}
2024-01-06 21:32:07 -05:00
2022-12-13 14:02:30 -05:00
BUTTON_LABELS["spawn"] = {}
BUTTON_LABELS["spawn"][true] = "Mode: Spawn all matching cards "
BUTTON_LABELS["spawn"][false] = "Mode: Spawn first matching card"
2024-01-06 21:32:07 -05:00
2022-12-13 14:02:30 -05:00
BUTTON_LABELS["search"] = {}
BUTTON_LABELS["search"][true] = "Mode: Name matches search term"
BUTTON_LABELS["search"][false] = "Mode: Name contains search term"
local inputParameters = {}
inputParameters.label = "Click to enter card name"
inputParameters.input_function = "input_func"
inputParameters.function_owner = self
inputParameters.alignment = 2
inputParameters.position = { 0, 0.05, -1.6 }
inputParameters.width = 1200
inputParameters.height = 130
inputParameters.font_size = 107
-- main code
2024-01-06 21:32:07 -05:00
function onSave()
return JSON.encode({ spawnAll, searchExact, inputParameters.value })
end
2022-10-19 19:07:47 -04:00
2024-01-06 21:32:07 -05:00
function onLoad(savedData)
local loadedData = JSON.decode(savedData)
spawnAll = loadedData[1] or false
searchExact = loadedData[2] or false
inputParameters.value = loadedData[3] or ""
2022-10-19 19:07:47 -04:00
-- index 0: button for spawn mode
2022-12-13 14:02:30 -05:00
buttonParameters.click_function = "search"
buttonParameters.label = "Spawn matching card(s)!"
buttonParameters.position = { 0, 0.06, 1.15 }
self.createButton(buttonParameters)
2022-10-19 19:07:47 -04:00
-- index 1: button for spawn mode
2022-12-13 14:02:30 -05:00
buttonParameters.click_function = "spawnMode"
buttonParameters.label = BUTTON_LABELS["spawn"][spawnAll]
buttonParameters.position[3] = buttonParameters.position[3] + 0.4
self.createButton(buttonParameters)
2022-10-19 19:07:47 -04:00
-- index 2: button for search mode
2022-12-13 14:02:30 -05:00
buttonParameters.click_function = "searchMode"
buttonParameters.label = BUTTON_LABELS["search"][searchExact]
buttonParameters.position[3] = buttonParameters.position[3] + 0.4
self.createButton(buttonParameters)
2022-10-19 19:07:47 -04:00
2022-12-13 14:02:30 -05:00
self.createInput(inputParameters)
2022-10-19 19:07:47 -04:00
end
2022-12-13 14:02:30 -05:00
function spawnMode()
spawnAll = not spawnAll
self.editButton({ index = 1, label = BUTTON_LABELS["spawn"][spawnAll] })
end
function searchMode()
searchExact = not searchExact
self.editButton({ index = 2, label = BUTTON_LABELS["search"][searchExact] })
end
-- if "Enter press" (\n) is found, start search and recreate input
function input_func(_, _, input, stillEditing)
if not stillEditing then
inputParameters.value = input
elseif string.find(input, "%\n") ~= nil then
inputParameters.value = input.gsub(input, "%\n", "")
search()
self.removeInput(0)
self.createInput(inputParameters)
end
end
2022-10-19 19:07:47 -04:00
function search()
2022-12-13 14:02:30 -05:00
if inputParameters.value == nil or string.len(inputParameters.value) == 0 then
2022-10-19 19:07:47 -04:00
printToAll("Please enter a search string.", "Yellow")
return
end
2022-12-13 14:02:30 -05:00
if string.len(inputParameters.value) < 3 then
2022-10-19 19:07:47 -04:00
printToAll("Please enter a longer search string.", "Yellow")
return
end
2024-01-06 21:32:07 -05:00
if not allCardsBagApi.isBagPresent() then
2022-10-19 19:07:47 -04:00
printToAll("Player card bag couldn't be found.", "Red")
return
end
2022-12-13 14:02:30 -05:00
-- search all objects in bag
2024-01-06 21:32:07 -05:00
local cardList = allCardsBagApi.getCardsByName(inputParameters.value, searchExact)
2022-10-19 19:07:47 -04:00
if cardList == nil or #cardList == 0 then
printToAll("No match found.", "Red")
return
end
2022-12-13 14:02:30 -05:00
if (#cardList > 100) then
printToAll("Matched more than 100 cards, please try a more specific search.", "Yellow")
return
end
2022-10-19 19:07:47 -04:00
2022-12-13 14:02:30 -05:00
-- sort table by name (reverse for multiple results, because bottom card spawns first)
table.sort(cardList, function(k1, k2) return spawnAll == (k1.data.Nickname > k2.data.Nickname) end)
local rot = self.getRotation()
local pos = self.positionToWorld(Vector(0, 2, -0.225))
Spawner.spawnCards(cardList, pos, rot, true)
end
end)
2024-01-06 21:32:07 -05:00
__bundle_register("playercards/AllCardsBagApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local AllCardsBagApi = {}
2024-01-06 21:32:29 -05:00
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getAllCardsBag()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "AllCardsBag")
end
2024-01-06 21:32:07 -05:00
-- Returns a specific card from the bag, based on ArkhamDB ID
2024-01-06 21:32:29 -05:00
---@param id table String ID of the card to retrieve
---@return table table
-- If the indexes are still being constructed, an empty table is
-- returned. Otherwise, a single table with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
2024-01-06 21:32:07 -05:00
AllCardsBagApi.getCardById = function(id)
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("getCardById", {id = id})
2024-01-06 21:32:07 -05:00
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.
2024-01-06 21:32:29 -05:00
---@return id String ID of the selected weakness.
2024-01-06 21:32:07 -05:00
AllCardsBagApi.getRandomWeaknessId = function()
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("getRandomWeaknessId")
2024-01-06 21:32:07 -05:00
end
AllCardsBagApi.isIndexReady = function()
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("isIndexReady")
2024-01-06 21:32:07 -05:00
end
-- Called by Hotfix bags when they load. If we are still loading indexes, then
-- the all cards and hotfix bags are being loaded together, and we can ignore
-- this call as the hotfix will be included in the initial indexing. If it is
-- called once indexing is complete it means the hotfix bag has been added
-- later, and we should rebuild the index to integrate the hotfix bag.
AllCardsBagApi.rebuildIndexForHotfix = function()
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("rebuildIndexForHotfix")
2024-01-06 21:32:07 -05:00
end
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
2024-01-06 21:32:29 -05:00
---@param name String or string fragment to search for names
---@param exact Boolean Whether the name match should be exact
2024-01-06 21:32:07 -05:00
AllCardsBagApi.getCardsByName = function(name, exact)
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("getCardsByName", {name = name, exact = exact})
2024-01-06 21:32:07 -05:00
end
AllCardsBagApi.isBagPresent = function()
2024-01-06 21:32:29 -05:00
return getAllCardsBag() and true
2024-01-06 21:32:07 -05:00
end
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
2024-01-06 21:32:29 -05:00
---@param class String class to retrieve ("Guardian", "Seeker", etc)
---@param upgraded Boolean true for upgraded cards (Level 1-5), false for Level 0
---@return: If the indexes are still being constructed, returns an empty table.
2024-01-06 21:32:07 -05:00
-- Otherwise, a list of tables, each with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded)
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("getCardsByClassAndLevel", {class = class, upgraded = upgraded})
2024-01-06 21:32:07 -05:00
end
AllCardsBagApi.getCardsByCycle = function(cycle)
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("getCardsByCycle", cycle)
2024-01-06 21:32:07 -05:00
end
AllCardsBagApi.getUniqueWeaknesses = function()
2024-01-06 21:32:29 -05:00
return getAllCardsBag().call("getUniqueWeaknesses")
2024-01-06 21:32:07 -05:00
end
return AllCardsBagApi
end
end)
2022-12-13 14:02:30 -05:00
__bundle_register("playercards/PlayerCardSpawner", function(require, _LOADED, __bundle_register, __bundle_modules)
-- Amount to shift for the next card (zShift) or next row of cards (xShift)
-- Note that the table rotation is weird, and the X axis is vertical while the
-- Z axis is horizontal
local SPREAD_Z_SHIFT = -2.3
2023-01-29 19:31:52 -05:00
local SPREAD_X_SHIFT = -3.66
2022-12-13 14:02:30 -05:00
Spawner = { }
-- Spawns a list of cards at the given position/rotation. This will separate cards by size -
-- investigator, standard, and mini, spawning them in that order with larger cards on bottom. If
-- there are different types, the provided callback will be called once for each type as it spawns
-- either a card or deck.
-- @param cardList: A list of Player Card data structures (data/metadata)
-- @param pos Position table where the cards should be spawned (global)
-- @param rot Rotation table for the orientation of the spawned cards (global)
-- @param sort Boolean, true if this list of cards should be sorted before spawning
-- @param callback Function, callback to be called after the card/deck spawns.
Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
if (sort) then
table.sort(cardList, Spawner.cardComparator)
end
local miniCards = { }
local standardCards = { }
local investigatorCards = { }
for _, card in ipairs(cardList) do
if (card.metadata.type == "Investigator") then
table.insert(investigatorCards, card)
elseif (card.metadata.type == "Minicard") then
table.insert(miniCards, card)
else
table.insert(standardCards, card)
2022-10-19 19:07:47 -04:00
end
2022-12-13 14:02:30 -05:00
end
-- Spawn each of the three types individually. Each Y position shift accounts for the thickness
-- of the spawned deck
local position = { x = pos.x, y = pos.y, z = pos.z }
2023-04-22 16:56:01 -04:00
Spawner.spawn(investigatorCards, position, { rot.x, rot.y - 90, rot.z }, callback)
2022-12-13 14:02:30 -05:00
position.y = position.y + (#investigatorCards + #standardCards) * 0.07
Spawner.spawn(standardCards, position, rot, callback)
position.y = position.y + (#standardCards + #miniCards) * 0.07
Spawner.spawn(miniCards, position, rot, callback)
2022-10-19 19:07:47 -04:00
end
2023-01-29 19:31:52 -05:00
Spawner.spawnCardSpread = function(cardList, startPos, maxCols, rot, sort, callback)
2022-12-13 14:02:30 -05:00
if (sort) then
table.sort(cardList, Spawner.cardComparator)
end
local position = { x = startPos.x, y = startPos.y, z = startPos.z }
2023-01-29 19:31:52 -05:00
-- Special handle the first row if we have less than a full single row, but only if there's a
-- reasonable max column count. Single-row spreads will send a large value for maxCols
if maxCols < 100 and #cardList < maxCols then
position.z = startPos.z + ((maxCols - #cardList) / 2 * SPREAD_Z_SHIFT)
end
local cardsInRow = 0
local rows = 0
2022-12-13 14:02:30 -05:00
for _, card in ipairs(cardList) do
Spawner.spawn({ card }, position, rot, callback)
position.z = position.z + SPREAD_Z_SHIFT
2023-01-29 19:31:52 -05:00
cardsInRow = cardsInRow + 1
if cardsInRow >= maxCols then
rows = rows + 1
local cardsForRow = #cardList - rows * maxCols
if cardsForRow > maxCols then
cardsForRow = maxCols
end
position.z = startPos.z + ((maxCols - cardsForRow) / 2 * SPREAD_Z_SHIFT)
position.x = position.x + SPREAD_X_SHIFT
cardsInRow = 0
end
2022-12-13 14:02:30 -05:00
end
2022-10-19 19:07:47 -04:00
end
2022-12-13 14:02:30 -05:00
-- Spawn a specific list of cards. This method is for internal use and should not be called
-- directly, use spawnCards instead.
2023-04-22 16:56:01 -04:00
---@param cardList: A list of Player Card data structures (data/metadata)
---@param pos table Position where the cards should be spawned (global)
---@param rot table Rotation for the orientation of the spawned cards (global)
---@param callback function callback to be called after the card/deck spawns.
2022-12-13 14:02:30 -05:00
Spawner.spawn = function(cardList, pos, rot, callback)
if (#cardList == 0) then
return
end
-- Spawn a single card directly
if (#cardList == 1) then
spawnObjectData({
data = cardList[1].data,
position = pos,
rotation = rot,
callback_function = callback,
})
return
end
-- For multiple cards, construct a deck and spawn that
local deck = Spawner.buildDeckDataTemplate()
-- Decks won't inherently scale to the cards in them. The card list being spawned should be all
-- the same type/size by this point, so use the first card to set the size
deck.Transform = {
scaleX = cardList[1].data.Transform.scaleX,
scaleY = 1,
scaleZ = cardList[1].data.Transform.scaleZ,
}
2023-04-22 16:56:01 -04:00
local sidewaysDeck = true
2022-12-13 14:02:30 -05:00
for _, spawnCard in ipairs(cardList) do
Spawner.addCardToDeck(deck, spawnCard.data)
2023-04-22 16:56:01 -04:00
-- set sidewaysDeck to false if any card is not a sideways card
sidewaysDeck = (sidewaysDeck and spawnCard.data.SidewaysCard)
end
-- set the alt view angle for sideway decks
if sidewaysDeck then
deck.AltLookAngle = { x = 0, y = 180, z = 90 }
2022-12-13 14:02:30 -05:00
end
spawnObjectData({
data = deck,
position = pos,
rotation = rot,
callback_function = callback,
})
end
2022-10-19 19:07:47 -04:00
2022-12-13 14:02:30 -05:00
-- 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
Spawner.addCardToDeck = function(deck, cardData)
for customDeckId, customDeckData in pairs(cardData.CustomDeck) do
if (deck.CustomDeck[customDeckId] == nil) then
-- CustomDeck not added to deck yet, add it
deck.CustomDeck[customDeckId] = customDeckData
elseif (deck.CustomDeck[customDeckId].FaceURL == customDeckData.FaceURL) then
-- CustomDeck for this card matches the current one for the deck, do nothing
2022-10-19 19:07:47 -04:00
else
2022-12-13 14:02:30 -05:00
-- CustomDeck data conflict
local newDeckId = nil
for deckId, customDeck in pairs(deck.CustomDeck) do
if (customDeckData.FaceURL == customDeck.FaceURL) then
newDeckId = deckId
end
end
if (newDeckId == nil) then
-- No non-conflicting custom deck for this card, add a new one
newDeckId = Spawner.findNextAvailableId(deck.CustomDeck, "1000")
deck.CustomDeck[newDeckId] = customDeckData
end
-- Update the card with the new CustomDeck info
cardData.CardID = newDeckId..string.sub(cardData.CardID, 5)
cardData.CustomDeck[customDeckId] = nil
cardData.CustomDeck[newDeckId] = customDeckData
break
2022-10-19 19:07:47 -04:00
end
2022-12-13 14:02:30 -05:00
end
table.insert(deck.ContainedObjects, cardData)
table.insert(deck.DeckIDs, cardData.CardID)
2022-10-19 19:07:47 -04:00
end
2022-12-13 14:02:30 -05:00
-- 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
Spawner.buildDeckDataTemplate = function()
local deck = {}
deck.Name = "Deck"
2022-10-19 19:07:47 -04:00
2022-12-13 14:02:30 -05:00
-- 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
-- Returns the first ID which does not exist in the given table, starting at startId and increasing
-- @param objectTable Table keyed by strings which are numbers
-- @param startId First possible ID.
-- @return String ID >= startId
Spawner.findNextAvailableId = function(objectTable, startId)
local id = startId
while (objectTable[id] ~= nil) do
id = tostring(tonumber(id) + 1)
end
return id
end
-- Get the PBCN (Permanent/Bonded/Customizable/Normal) value from the given metadata.
---@return: 1 for Permanent, 2 for Bonded or 4 for Normal. The actual values are
-- irrelevant as they provide only grouping and the order between them doesn't matter.
Spawner.getpbcn = function(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/customizable 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.
Spawner.cardComparator = function(card1, card2)
local pbcn1 = Spawner.getpbcn(card1.metadata)
local pbcn2 = Spawner.getpbcn(card2.metadata)
if pbcn1 ~= pbcn2 then
return pbcn1 > pbcn2
end
if pbcn1 == 3 then
if card1.data.Nickname ~= card2.data.Nickname then
return card1.data.Nickname < card2.data.Nickname
2022-10-19 19:07:47 -04:00
end
2022-12-13 14:02:30 -05:00
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
end)
return __bundle_require("__root")