Create a unified panel for groups of player cards.

The will replace the investigator boxes, class card spread tokens, and weakness container.  It will (in the future) replace the bonded and upgrade sheet bags.

This is a code-only submit while we wait on final images for the new panel.
This commit is contained in:
Buhallin 2022-12-17 01:24:19 -08:00
parent 83ff3eb65b
commit a2fb1058c6
No known key found for this signature in database
GPG Key ID: DB3C362823852294
6 changed files with 649 additions and 31 deletions

View File

@ -67,13 +67,13 @@ do
local deck = Request.start(deckUri, function(status)
if string.find(status.text, "<!DOCTYPE html>") then
printToAll("Private deck ID " .. deckId .. " is not shared", playerColor)
internal.maybePrint("Private deck ID " .. deckId .. " is not shared", playerColor)
return false, table.concat({ "Private deck ", deckId, " is not shared" })
end
local json = JSON.decode(status.text)
if not json then
printToAll("Deck ID " .. deckId .. " not found", playerColor)
internal.maybePrint("Deck ID " .. deckId .. " not found", playerColor)
return false, "Deck not found!"
end
@ -101,9 +101,9 @@ do
if (adbCardInfo.xp ~= nil and adbCardInfo.xp > 0) then
cardName = cardName .. " (" .. adbCardInfo.xp .. ")"
end
printToAll("Card not found: " .. cardName .. ", ArkhamDB ID " .. cardId, playerColor)
internal.maybePrint("Card not found: " .. cardName .. ", ArkhamDB ID " .. cardId, playerColor)
else
printToAll("Card not found in ArkhamDB, ID " .. cardId, playerColor)
internal.maybePrint("Card not found in ArkhamDB, ID " .. cardId, playerColor)
end
end)
end
@ -132,7 +132,7 @@ do
return
end
printToAll(table.concat({ "Found decklist: ", deck.name }), playerColor)
internal.maybePrint(table.concat({ "Found decklist: ", deck.name }), playerColor)
log(table.concat({ "-", deck.name, "-" }))
for k, v in pairs(deck) do
@ -185,7 +185,7 @@ do
local weaknessId = allCardsBag.call("getRandomWeaknessId")
slots[weaknessId] = 1
slots[RANDOM_WEAKNESS_ID] = nil
printToAll("Random basic weakness added to deck", playerColor)
internal.maybePrint("Random basic weakness added to deck", playerColor)
end
end
@ -249,7 +249,7 @@ do
if investigatorCount ~= nil then
slots["09006"] = investigatorCount
else
printToAll("Something went wrong with the load, adding 4 copies of On the Mend", playerColor)
internal.maybePrint("Something went wrong with the load, adding 4 copies of On the Mend", playerColor)
slots["09006"] = 4
end
end
@ -294,7 +294,7 @@ do
local tabooCard = allCardsBag.call("getCardById", { id = cardId .. "-t" })
if tabooCard == nil then
local basicCard = allCardsBag.call("getCardById", { id = cardId })
printToAll("Taboo version for " .. basicCard.data.Nickname .. " is not available. Using standard version", playerColor)
internal.maybePrint("Taboo version for " .. basicCard.data.Nickname .. " is not available. Using standard version", playerColor)
else
slots[cardId .. "-t"] = slots[cardId]
slots[cardId] = nil
@ -304,6 +304,12 @@ do
end
end
internal.maybePrint = function(message, playerColor)
if playerColor ~= "None" then
printToAll(message, playerColor)
end
end
-- Gets the ArkhamDB config info from the configuration object.
---@return Table. Configuration data
internal.getConfiguration = function()
@ -411,7 +417,7 @@ do
on_success(results, table.unpack(parameters))
elseif on_error == nil then
for _, request in ipairs(errors) do
printToAll(table.concat({ "[ERROR]", request.uri, ":", request.error_message }))
internal.maybePrint(table.concat({ "[ERROR]", request.uri, ":", request.error_message }))
end
else
on_error(requests, table.unpack(parameters))

View File

@ -129,6 +129,7 @@ function buildSupplementalIndexes()
table.insert(basicWeaknessList, cardMetadata.id)
end
end
table.sort(basicWeaknessList, cardComparator)
-- Add the card to the appropriate class and level indexes
local isGuardian = false
@ -298,6 +299,10 @@ function buildAvailableWeaknesses()
return availableWeaknesses
end
function getBasicWeaknesses()
return basicWeaknessList
end
-- Helper function that adds one to the table entry for the number of weaknesses in play
function incrementWeaknessCount(table, cardMetadata)
if (isBasicWeakness(cardMetadata)) then

View File

@ -0,0 +1,509 @@
require("playercards/PlayerCardPanelData")
local spawnBag = require("playercards/spawnbag/SpawnBag")
local arkhamDb = require("arkhamdb/ArkhamDb")
-- TODO: Update when the real UI image is in place
local BUTTON_WIDTH = 150
local BUTTON_HEIGHT = 550
local ALL_CARDS_BAG_GUID = "15bb07"
local FACE_UP_ROTATION = { x = 0, y = 270, z = 0}
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180}
-- Coordinates to begin laying out cards to match the reserved areas of the
-- table. Cards will lay out horizontally, then create additional rows
local START_POSITIONS = {
skill = Vector(58.384, 1.36, 92.4),
event = Vector(53.229, 1.36, 92.4),
asset = Vector(40.960, 1.36, 92.4),
investigator = Vector(60, 1.36, 80)
}
-- Position offsets for investigator decks in investigator mode, defines the spacing for how the
-- rows and columns are laid out
local INVESTIGATOR_POSITION_SHIFT_ROW = Vector(-11, 0, 0)
local INVESTIGATOR_POSITION_SHIFT_COL = Vector(0, 0, -6)
local INVESTIGATOR_MAX_COLS = 6
-- Positions relative to the minicard to place other stacks. Both signature card piles and starter
-- decks use SIGNATURE_OFFSET
local INVESTIGATOR_CARD_OFFSET = Vector(-2.55, 0, 0)
local INVESTIGATOR_SIGNATURE_OFFSET = Vector(-5.75, 0, 0)
local spawnStarterDecks = false
function onSave()
local saveState = {
spawnBagState = spawnBag.getStateForSave(),
}
return JSON.encode(saveState)
end
function onLoad(savedData)
arkhamDb.initialize()
if (savedData ~= nil) then
local saveState = JSON.decode(savedData) or { }
if (saveState.spawnBagState ~= nil) then
spawnBag.loadFromSave(saveState.spawnBagState)
end
end
self.createButton({
label="Guardian", click_function="spawnInvestigatorsGuardian", function_owner=self,
position={-0.3,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Seeker", click_function="spawnInvestigatorsSeeker", function_owner=self,
position={0,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Mystic", click_function="spawnInvestigatorsMystic", function_owner=self,
position={0.3,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Rogue", click_function="spawnInvestigatorsRogue", function_owner=self,
position={-0.3,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Survivor", click_function="spawnSurvivor", function_owner=self,
position={0,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Neutral", click_function="spawnNeutral", function_owner=self,
position={0.3,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Core", click_function="spawnCore", function_owner=self,
position={-0.3,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Dunwich", click_function="spawnDunwich", function_owner=self,
position={0,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Carcosa", click_function="spawnCarcosa", function_owner=self,
position={0.3,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Forgotten Age", click_function="spawnForgottenAge", function_owner=self,
position={-0.3,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Circle Undone", click_function="spawnCircleUndone", function_owner=self,
position={0,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Dream Eaters", click_function="spawnDreamEaters", function_owner=self,
position={0.3,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Innsmouth", click_function="spawnInnsmouth", function_owner=self,
position={-0.3,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="EotE", click_function="spawnEotE", function_owner=self,
position={0,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Scarlet Keys", click_function="spawnScarletKeys", function_owner=self,
position={0.3,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="InvPacks", click_function="spawnInvestigatorDecks", function_owner=self,
position={-0.3,0.2,0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Investigators", click_function="setInvestigators", function_owner=self,
position={-0.15,0.2,-0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Starters", click_function="setStarters", function_owner=self,
position={0.15,0.2,-0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L0 Guardian", click_function="spawnBasicGuardian", function_owner=self,
position={-0.15,0.2,0.3}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L1-5 Guardian", click_function="spawnUpgradedGuardian", function_owner=self,
position={0.15,0.2,0.3}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L0 Seeker", click_function="spawnBasicSeeker", function_owner=self,
position={-0.15,0.2,0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L1-5 Seeker", click_function="spawnUpgradedSeeker", function_owner=self,
position={0.15,0.2,0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L0 Mystic", click_function="spawnBasicMystic", function_owner=self,
position={-0.15,0.2,0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L1-5 Mystic", click_function="spawnUpgradedGuardian", function_owner=self,
position={0.15,0.2,0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L0 Rogue", click_function="spawnBasicRogue", function_owner=self,
position={-0.15,0.2,0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L1-5 Rogue", click_function="spawnUpgradedRogue", function_owner=self,
position={0.15,0.2,0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L0 Survivor", click_function="spawnBasicSurvivor", function_owner=self,
position={-0.15,0.2,0.7}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L1-5 Survivor", click_function="spawnUpgradedSurvivor", function_owner=self,
position={0.15,0.2,0.7}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L0 Neutral", click_function="spawnBasicNeutral", function_owner=self,
position={-0.15,0.2,0.8}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="L1-5 Neutral", click_function="spawnUpgradedNeutral", function_owner=self,
position={0.15,0.2,0.8}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Clear", click_function="deleteAll", function_owner=self,
position={0.5,0.2,0.9}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
self.createButton({
label="Weaknesses", click_function="spawnWeaknesses", function_owner=self,
position={-0.5,0.2,0.9}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
})
local classList = { "Guardian", "Seeker", "Mystic", "Rogue", "Survivor", "Neutral" }
for _, className in ipairs(classList) do
local funcName = "spawnInvestigators"..className
self.setVar(funcName, function(_, _, _) spawnGroup(className) end)
funcName = "spawnBasic"..className
self.setVar(funcName, function(_, _, _) spawnClassCards(className, false) end)
funcName = "spawnUpgraded"..className
self.setVar(funcName, function(_, _, _) spawnClassCards(className, true) end)
end
end
-- TODO: Replace these with something less manual once the full data in in place so we know what
-- keys to use
function placeCore()
spawnGroup("Core")
end
function placeDunwich()
spawnGroup("Dunwich")
end
function placeCarcosa()
spawnGroup("Carcosa")
end
function placeForgottenAge()
spawnGroup("ForgottenAge")
end
function placeCircleUndone()
spawnGroup("CircleUndone")
end
function placeDreamEaters()
spawnGroup("DreamEaters")
end
function placeInnsmouth()
spawnGroup("Innsmouth")
end
function placeEotE()
spawnGroup("EotE")
end
function placeScarletKeys()
spawnGroup("ScarletKeys")
end
function placeInvestigatorDecks()
spawnGroup("InvestigatorDecks")
end
-- UI handler to put the investigator spawn in investigator mode.
function setInvestigators()
spawnStarterDecks = false
printToAll("Spawning investigator piles")
end
-- UI handler to put the investigator spawn in starter deck mode.
function setStarters()
spawnStarterDecks = true
printToAll("Spawning starter decks")
end
-- Deletes all cards currently placed on the table
function deleteAll()
spawnBag.recall(true)
end
-- Spawn an investigator group, based on the current UI setting for either investigators or starter
-- decks.
---@param groupName String. Name of the group to spawn, matching a key in InvestigatorPanelData
function spawnGroup(groupName)
spawnBag.recall(true)
Wait.frames(function()
if spawnStarterDecks then
spawnStarters(groupName)
else
spawnInvestigators(groupName)
end
end, 2)
end
-- Spawn cards for all investigators in the given group. This creates piles for all defined
-- investigator cards and minicards as well as the signature cards.
---@param groupName String. Name of the group to spawn, matching a key in InvestigatorPanelData
function spawnInvestigators(groupName)
local position = Vector(START_POSITIONS.investigator)
local col = 1
local row = 1
if INVESTIGATOR_GROUPS[groupName] == nil then
printToAll("No " .. groupName .. " data yet")
return
end
for _, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do
for _, spawnSpec in ipairs(buildInvestigatorSpawnSpec(
investigatorName, INVESTIGATORS[investigatorName], position, false)) do
spawnBag.spawn(spawnSpec)
end
position:add(INVESTIGATOR_POSITION_SHIFT_COL)
col = col + 1
if col > INVESTIGATOR_MAX_COLS then
col = 1
position = Vector(START_POSITIONS.investigator)
position:add(Vector(
INVESTIGATOR_POSITION_SHIFT_ROW.x * row,
INVESTIGATOR_POSITION_SHIFT_ROW.z * row,
INVESTIGATOR_POSITION_SHIFT_ROW.z * row))
row = row + 1
end
end
end
-- Creates the spawn spec for the investigator's signature cards.
---@param investigatorName String. Name of the investigator, matching a key in
--- InvestigatorPanelData
---@param investigatorData Table. Spawn definition for the investigator, retrieved from
--- INVESTIGATORS
---@param position Vector. Where to spawn the minicard; investigagor cards will be placed below
function buildInvestigatorSpawnSpec(investigatorName, investigatorData, position)
local sigPos = Vector(position):add(INVESTIGATOR_SIGNATURE_OFFSET)
local spawns = buildCommonSpawnSpec(investigatorName, investigatorData, position)
table.insert(spawns, {
name = investigatorName.."signatures",
cards = investigatorData.signatures,
globalPos = sigPos,
rotation = FACE_UP_ROTATION,
})
return spawns
end
-- Builds the spawn specs for minicards and investigator cards. These are common enough to be
-- shared, and will only differ in whether they spawn the full stack of possible investigator and
-- minicards, or only the first of each.
---@param investigatorName String. Name of the investigator, matching a key in
--- InvestigatorPanelData
---@param investigatorData Table. Spawn definition for the investigator, retrieved from
--- INVESTIGATORS
---@param position Vector. Where to spawn the minicard; investigagor cards will be placed below
---@param oneCardOnly Boolean. If true, will spawn only the first card in the investigator card
--- and minicard lists. Otherwise, spawn them all in a deck
function buildCommonSpawnSpec(investigatorName, investigatorData, position, oneCardOnly)
local cardPos = Vector(position):add(INVESTIGATOR_CARD_OFFSET)
return {
{
name = investigatorName.."minicards",
cards = oneCardOnly and investigatorData.minicards[1] or investigatorData.minicards,
globalPos = position,
rotation = FACE_UP_ROTATION,
},
{
name = investigatorName.."cards",
cards = oneCardOnly and investigatorData.cards[1] or investigatorData.cards,
globalPos = cardPos,
rotation = FACE_UP_ROTATION,
},
}
end
-- Spawns all starter decks (single minicard and investigator card, plus the starter deck) for
-- investigators in the given group.
---@param groupName String. Name of the group to spawn, matching a key in InvestigatorPanelData
function spawnStarters(groupName)
local position = Vector(START_POSITIONS.investigator)
local col = 1
local row = 1
for _, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do
spawnStarterDeck(investigatorName, INVESTIGATORS[investigatorName], position)
position:add(INVESTIGATOR_POSITION_SHIFT_COL)
col = col + 1
if col > INVESTIGATOR_MAX_COLS then
col = 1
position = Vector(START_POSITIONS.investigator)
position:add(Vector(
INVESTIGATOR_POSITION_SHIFT_ROW.x * row,
INVESTIGATOR_POSITION_SHIFT_ROW.z * row,
INVESTIGATOR_POSITION_SHIFT_ROW.z * row))
row = row + 1
end
end
end
-- Spawns the defined starter deck for the given investigator's.
---@param investigatorName String. Name of the investigator, matching a key in
--- InvestigatorPanelData
function spawnStarterDeck(investigatorName, investigatorData, position)
for _, spawnSpec in ipairs(
buildCommonSpawnSpec(investigatorName, INVESTIGATORS[investigatorName], position, true)) do
spawnBag.spawn(spawnSpec)
end
local deckPos = Vector(position):add(INVESTIGATOR_SIGNATURE_OFFSET)
arkhamDb.getDecklist("None", investigatorData.starterDeck, true, false, false, function(slots)
local cardIdList = { }
for id, count in pairs(slots) do
for i = 1, count do
table.insert(cardIdList, id)
end
end
spawnBag.spawn({
name = investigatorName.."starter",
cards = cardIdList,
globalPos = deckPos,
rotation = FACE_DOWN_ROTATION
})
end)
end
-- Clears the currently placed cards, then places cards for the given class and level spread
---@param cardClass String. Class to place ("Guardian", "Seeker", etc)
---@param isUpgraded Boolean. If true, spawn the Level 1-5 cards. Otherwise, Level 0.
function spawnClassCards(cardClass, isUpgraded)
spawnBag.recall(true)
Wait.frames(function() placeClassCards(cardClass, isUpgraded) end, 2)
end
-- Spawn the class cards.
---@param cardClass String. Class to place ("Guardian", "Seeker", etc)
---@param isUpgraded Boolean. If true, spawn the Level 1-5 cards. Otherwise, Level 0.
function placeClassCards(cardClass, isUpgraded)
local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
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
local cardIdList = allCardsBag.call("getCardsByClassAndLevel", {class = cardClass, upgraded = isUpgraded})
local skillList = { }
local eventList = { }
local assetList = { }
for _, cardId in ipairs(cardIdList) do
local cardMetadata = allCardsBag.call("getCardById", { id = cardId }).metadata
if (cardMetadata.type == "Skill") then
table.insert(skillList, cardId)
elseif (cardMetadata.type == "Event") then
table.insert(eventList, cardId)
elseif (cardMetadata.type == "Asset") then
table.insert(assetList, cardId)
end
end
if #skillList > 0 then
spawnBag.spawn({
name = cardClass .. (isUpgraded and "upgraded" or "basic"),
cards = skillList,
globalPos = START_POSITIONS.skill,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
end
if #eventList > 0 then
spawnBag.spawn({
name = cardClass .. "event" .. (isUpgraded and "upgraded" or "basic"),
cards = eventList,
globalPos = START_POSITIONS.event,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
end
if #assetList > 0 then
spawnBag.spawn({
name = cardClass .. "asset" .. (isUpgraded and "upgraded" or "basic"),
cards = assetList,
globalPos = START_POSITIONS.asset,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
end
end
-- Clears the current cards, and places all basic weaknesses on the table.
function spawnWeaknesses()
spawnBag.recall(fast)
local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
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
local weaknessIdList = allCardsBag.call("getBasicWeaknesses")
local copiedList = { }
for i, id in ipairs(weaknessIdList) do
copiedList[i] = id
end
spawnBag.spawn({
name = "weaknesses",
cards = copiedList,
globalPos = START_POSITIONS.asset,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
end

View File

@ -0,0 +1,44 @@
------------------ START INVESTIGATOR DATA DEFINITION ------------------
INVESTIGATOR_GROUPS = {
Guardian = {
"Roland Banks",
},
Seeker = {
"Daisy Walker",
},
Core = {
"Roland Banks",
"Daisy Walker",
"R2",
"D2",
"R3",
"D3",
"R4",
"D4",
"R5",
"D5",
},
}
INVESTIGATORS = { }
INVESTIGATORS["Roland Banks"] = {
cards = { "01001", "01001-promo", "01001-p", "01001-pf", "01001-pb", },
minicards = { "01001-m", "01001-promo-m", },
signatures = { "01006", "01007", "90030", "90031", },
starterDeck = "1462",
}
INVESTIGATORS["Daisy Walker"] = {
cards = { "01002", "01002-p", "01002-pf", "01002-pb", },
minicards = { "01002-m", },
signatures = { "01008", "01009", "90002", "90003" },
starterDeck = "42652",
}
------------------ END INVESTIGATOR DATA DEFINITION ------------------
INVESTIGATORS["R2"] = INVESTIGATORS["Roland Banks"]
INVESTIGATORS["R3"] = INVESTIGATORS["Roland Banks"]
INVESTIGATORS["R4"] = INVESTIGATORS["Roland Banks"]
INVESTIGATORS["R5"] = INVESTIGATORS["Roland Banks"]
INVESTIGATORS["D2"] = INVESTIGATORS["Daisy Walker"]
INVESTIGATORS["D3"] = INVESTIGATORS["Daisy Walker"]
INVESTIGATORS["D4"] = INVESTIGATORS["Daisy Walker"]
INVESTIGATORS["D5"] = INVESTIGATORS["Daisy Walker"]

View File

@ -3,6 +3,9 @@
-- 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
local SPREAD_X_SHIFT = -3.66
Spawner = { }
@ -45,15 +48,22 @@ Spawner.spawnCards = function(cardList, pos, rot, sort, callback)
Spawner.spawn(miniCards, position, rot, callback)
end
Spawner.spawnCardSpread = function(cardList, startPos, rot, sort, callback)
Spawner.spawnCardSpread = function(cardList, startPos, maxCols, rot, sort, callback)
if (sort) then
table.sort(cardList, Spawner.cardComparator)
end
local position = { x = startPos.x, y = startPos.y, z = startPos.z }
local cardsInRow = 0
for _, card in ipairs(cardList) do
Spawner.spawn({ card }, position, rot, callback)
position.z = position.z + SPREAD_Z_SHIFT
cardsInRow = cardsInRow + 1
if cardsInRow >= maxCols then
position.z = startPos.z
position.x = position.x + SPREAD_X_SHIFT
cardsInRow = 0
end
end
end

View File

@ -14,13 +14,17 @@ require("playercards/PlayerCardSpawner")
-- 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
-- globalPos, this should be a valid Vector with x, y, and z defined
-- spread: Optional. If present and true, cards will be spawned next to each other in a spread
-- moving to the right. globalPos will define the location of the first card, each after that
-- will be moved a predefined distance
-- spread: Optional Boolean. If present and true, cards will be spawned next to each other in a
-- spread moving to the right. globalPos will define the location of the first card, each
-- after that will be moved a predefined distance
-- spreadCols: Optional integer. If spread is true, specifies the maximum columns cards will be
-- laid out in before starting a new row. If spread is true but spreadCols is not set, all
-- cards will be in a single row (however long that may be)
-- }
-- See BondedBag.ttslua for an example
do
local SpawnBag = { }
local internal = { }
-- To assist debugging, will draw a box around the recall zone when it's set up
local SHOW_RECALL_ZONE = false
@ -87,7 +91,8 @@ do
end
local cardsToSpawn = { }
local allCardsBag = getObjectFromGUID(ALL_CARDS_GUID)
for _, cardId in ipairs(spawnSpec.cards) do
local cardList = spawnSpec.cards
for _, cardId in ipairs(cardList) do
local cardData = allCardsBag.call("getCardById", { id = cardId })
if (cardData ~= nil) then
table.insert(cardsToSpawn, cardData)
@ -96,27 +101,27 @@ do
end
end
if (spawnSpec.spread) then
Spawner.spawnCardSpread(cardsToSpawn, spawnSpec.globalPos, spawnSpec.rotation, false, recordPlacedObject)
Spawner.spawnCardSpread(cardsToSpawn, spawnSpec.globalPos, spawnSpec.spreadCols or 9999, spawnSpec.rotation, false, internal.recordPlacedObject)
else
Spawner.spawnCards(cardsToSpawn, spawnSpec.globalPos, spawnSpec.rotation, false, recordPlacedObject)
-- TTS decks come out in reverse order of the cards, reverse the list so the input order stays
-- This only applies for decks; spreads are spawned by us in the order given
if spawnSpec.rotation.z != 180 then
cardsToSpawn = internal.reverseList(cardsToSpawn)
end
Spawner.spawnCards(cardsToSpawn, spawnSpec.globalPos, spawnSpec.rotation, false, internal.recordPlacedObject)
end
placedSpecs[spawnSpec.name] = true
end
-- Recalls all spawned objects to the bag, and clears the placedObjectGuids list
SpawnBag.recall = function()
local trash = spawnObjectData({data = RECALL_BAG, position = self.getPosition()})
for guid, _ in pairs(placedObjectGuids) do
local obj = getObjectFromGUID(guid)
if (obj ~= nil) then
if (isInRecallZone(obj)) then
trash.putObject(obj)
end
placedObjectGuids[guid] = nil
end
---@param fast Boolean. If true, cards will be deleted directly without faking the bag recall.
SpawnBag.recall = function(fast)
if fast then
internal.deleteSpawned()
else
internal.recallSpawned()
end
trash.destruct()
-- We've recalled everything we can, some cards may have been moved out of the
-- card area. Just reset at this point.
placedSpecs = { }
@ -124,16 +129,46 @@ do
recallZone = nil
end
-- Deleted all spawned cards.
internal.deleteSpawned = function()
for guid, _ in pairs(placedObjectGuids) do
local obj = getObjectFromGUID(guid)
if (obj ~= nil) then
if (internal.isInRecallZone(obj)) then
obj.destruct()
end
placedObjectGuids[guid] = nil
end
end
end
-- Recalls spawned cards with a fake bag that replicates the memory bag recall style.
internal.recallSpawned = function()
local trash = spawnObjectData({data = RECALL_BAG, position = self.getPosition()})
for guid, _ in pairs(placedObjectGuids) do
local obj = getObjectFromGUID(guid)
if (obj ~= nil) then
if (internal.isInRecallZone(obj)) then
trash.putObject(obj)
end
placedObjectGuids[guid] = nil
end
end
trash.destruct()
end
-- Callback for when an object has been spawned. Tracks the object for later recall and updates the
-- recall zone.
function recordPlacedObject(spawned)
internal.recordPlacedObject = function(spawned)
placedObjectGuids[spawned.getGUID()] = true
expandRecallZone(spawned)
internal.expandRecallZone(spawned)
end
-- 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
function expandRecallZone(spawnedCard)
internal.expandRecallZone = function(spawnedCard)
local pos = spawnedCard.getPosition()
if (recallZone == nil) then
-- First card out of the bag, initialize surrounding that
@ -189,7 +224,7 @@ do
-- 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.
function isInRecallZone(obj)
internal.isInRecallZone = function(obj)
if (recallZone == nil) then
return true
end
@ -198,5 +233,14 @@ do
and pos.z < recallZone.upperLeft.z and pos.z > recallZone.lowerRight.z)
end
internal.reverseList = function(list)
local reversed = { }
for i = 1, #list do
reversed[i] = list[#list - i + 1]
end
return reversed
end
return SpawnBag
end