758 lines
26 KiB
Plaintext
758 lines
26 KiB
Plaintext
require("playercards/PlayerCardPanelData")
|
|
local spawnBag = require("playercards/SpawnBag")
|
|
local arkhamDb = require("arkhamdb/ArkhamDb")
|
|
|
|
-- Size and position information for the three rows of class buttons
|
|
local CIRCLE_BUTTON_SIZE = 250
|
|
local CLASS_BUTTONS_X_OFFSET = 0.1325
|
|
local INVESTIGATOR_ROW_START = Vector(0.125, 0.1, -0.447)
|
|
local LEVEL_ZERO_ROW_START = Vector(0.125, 0.1, -0.007)
|
|
local UPGRADED_ROW_START = Vector(0.125, 0.1, 0.333)
|
|
|
|
-- Size and position information for the two blocks of other buttons
|
|
local MISC_BUTTONS_X_OFFSET = 0.155
|
|
local WEAKNESS_ROW_START = Vector(0.157, 0.1, 0.666)
|
|
local OTHER_ROW_START = Vector(0.605, 0.1, 0.666)
|
|
|
|
-- Size and position information for the Cycle (box) buttons
|
|
local CYCLE_BUTTON_SIZE = 468
|
|
local CYCLE_BUTTON_START = Vector(-0.716, 0.1, -0.39)
|
|
local CYCLE_COLUMN_COUNT = 3
|
|
local CYCLE_BUTTONS_X_OFFSET = 0.267
|
|
local CYCLE_BUTTONS_Z_OFFSET = 0.2665
|
|
|
|
local ALL_CARDS_BAG_GUID = "15bb07"
|
|
|
|
local STARTER_DECK_MODE_SELECTED_COLOR = { 0.2, 0.2, 0.2, 0.8 }
|
|
local TRANSPARENT = { 0, 0, 0, 0 }
|
|
local STARTER_DECK_MODE_STARTERS = "starters"
|
|
local STARTER_DECK_MODE_CARDS_ONLY = "cards"
|
|
|
|
local FACE_UP_ROTATION = { x = 0, y = 270, z = 0}
|
|
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180}
|
|
|
|
-- ---------- IMPORTANT ----------
|
|
-- Coordinates defined below are in global dimensions relative to the panel - DO NOT USE THESE
|
|
-- DIRECTLY. Call scalePositions() before use, and reference the variables below
|
|
|
|
-- Layout width for a single card, in global coordinate space
|
|
local CARD_WIDTH = 2.3
|
|
|
|
-- Coordinates to begin laying out cards. These vary based on the cards that are being placed by
|
|
-- considering the width of the cards, number of cards, and desired spread intervals.
|
|
-- IMPORTANT! Because of the mix of global card sizes and relative-to-scale positions, the X and Y
|
|
-- coordinates on these provide global disances while the Z is local.
|
|
local START_POSITIONS = {
|
|
classCards = Vector(CARD_WIDTH * 9.5, 2, 1.4),
|
|
investigator = Vector(6 * 2.5, 2, 1.3),
|
|
cycle = Vector(CARD_WIDTH * 9.5, 2, 2.4),
|
|
other = Vector(CARD_WIDTH * 9.5, 2, 1.4),
|
|
randomWeakness = Vector(0, 2, 1.4),
|
|
-- Because the card spread is handled by the SpawnBag, we don't know (programatically) where this
|
|
-- should be placed. If more customizable cards are added it will need to be moved.
|
|
summonedServitor = Vector(CARD_WIDTH * -6.5, 2, 1.7),
|
|
}
|
|
|
|
-- Shifts to move rows of cards, and groups of rows, as different groupings are laid out
|
|
local CARD_ROW_OFFSET = 3.7
|
|
local CARD_GROUP_OFFSET = 2
|
|
|
|
-- 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(0, 0, 11)
|
|
local INVESTIGATOR_POSITION_SHIFT_COL = Vector(-6, 0, 0)
|
|
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(0, 0, 2.55)
|
|
local INVESTIGATOR_SIGNATURE_OFFSET = Vector(0, 0, 5.75)
|
|
|
|
-- USE THESE! Positions and offset shifts accounting for the scale of the panel
|
|
local startPositions
|
|
local cardRowOffset
|
|
local cardGroupOffset
|
|
local investigatorPositionShiftRow
|
|
local investigatorPositionShiftCol
|
|
local investigatorCardOffset
|
|
local investigatorSignatureOffset
|
|
|
|
local CLASS_LIST = { "Guardian", "Seeker", "Rogue", "Mystic", "Survivor", "Neutral" }
|
|
local CYCLE_LIST = {
|
|
"Core",
|
|
"The Dunwich Legacy",
|
|
"The Path to Carcosa",
|
|
"The Forgotten Age",
|
|
"The Circle Undone",
|
|
"The Dream-Eaters",
|
|
"The Innsmouth Conspiracy",
|
|
"Edge of the Earth",
|
|
"The Scarlet Keys",
|
|
"Investigator Packs"
|
|
}
|
|
|
|
local excludedNonBasicWeaknesses
|
|
|
|
local starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
|
|
local helpVisibleToPlayers = { }
|
|
|
|
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
|
|
buildExcludedWeaknessList()
|
|
createButtons()
|
|
end
|
|
|
|
-- Build a list of non-basic weaknesses which should be excluded from the last weakness set,
|
|
-- including all signature cards and evolved weaknesses.
|
|
function buildExcludedWeaknessList()
|
|
excludedNonBasicWeaknesses = { }
|
|
for _, investigator in pairs(INVESTIGATORS) do
|
|
for _, signatureId in ipairs(investigator.signatures) do
|
|
excludedNonBasicWeaknesses[signatureId] = true
|
|
end
|
|
end
|
|
for _, weaknessId in ipairs(EVOLVED_WEAKNESSES) do
|
|
excludedNonBasicWeaknesses[weaknessId] = true
|
|
end
|
|
end
|
|
|
|
function createButtons()
|
|
createHelpButton()
|
|
createInvestigatorButtons()
|
|
createLevelZeroButtons()
|
|
createUpgradedButtons()
|
|
createWeaknessButtons()
|
|
createOtherButtons()
|
|
createCycleButtons()
|
|
createClearButton()
|
|
-- Create investigator mode buttons last so the indexes are set when we need to update them
|
|
createInvestigatorModeButtons()
|
|
end
|
|
|
|
function createHelpButton()
|
|
self.createButton({
|
|
function_owner = self,
|
|
click_function = "toggleHelp",
|
|
position = Vector(0.845, 0.1, -0.855),
|
|
rotation = Vector(0, 0, 0),
|
|
height = 180,
|
|
width = 180,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
})
|
|
end
|
|
|
|
function createInvestigatorButtons()
|
|
local invButtonParams = {
|
|
function_owner = self,
|
|
rotation = Vector(0, 0, 0),
|
|
height = CIRCLE_BUTTON_SIZE,
|
|
width = CIRCLE_BUTTON_SIZE,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
}
|
|
local buttonPos = INVESTIGATOR_ROW_START:copy()
|
|
for _, class in ipairs(CLASS_LIST) do
|
|
invButtonParams.click_function = "spawnInvestigators" .. class
|
|
invButtonParams.position = buttonPos
|
|
self.createButton(invButtonParams)
|
|
buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
|
|
self.setVar(invButtonParams.click_function, function(_, _, _) spawnInvestigatorGroup(class) end)
|
|
end
|
|
end
|
|
|
|
function createLevelZeroButtons()
|
|
local l0ButtonParams = {
|
|
function_owner = self,
|
|
rotation = Vector(0, 0, 0),
|
|
height = CIRCLE_BUTTON_SIZE,
|
|
width = CIRCLE_BUTTON_SIZE,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
}
|
|
local buttonPos = LEVEL_ZERO_ROW_START:copy()
|
|
for _, class in ipairs(CLASS_LIST) do
|
|
l0ButtonParams.click_function = "spawnBasic" .. class
|
|
l0ButtonParams.position = buttonPos
|
|
self.createButton(l0ButtonParams)
|
|
buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
|
|
self.setVar(l0ButtonParams.click_function, function(_, _, _) spawnClassCards(class, false) end)
|
|
end
|
|
end
|
|
|
|
function createUpgradedButtons()
|
|
local upgradedButtonParams = {
|
|
function_owner = self,
|
|
rotation = Vector(0, 0, 0),
|
|
height = CIRCLE_BUTTON_SIZE,
|
|
width = CIRCLE_BUTTON_SIZE,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
}
|
|
local buttonPos = UPGRADED_ROW_START:copy()
|
|
for _, class in ipairs(CLASS_LIST) do
|
|
upgradedButtonParams.click_function = "spawnUpgraded" .. class
|
|
upgradedButtonParams.position = buttonPos
|
|
self.createButton(upgradedButtonParams)
|
|
buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
|
|
self.setVar(upgradedButtonParams.click_function, function(_, _, _) spawnClassCards(class, true) end)
|
|
end
|
|
end
|
|
|
|
function createWeaknessButtons()
|
|
local weaknessButtonParams = {
|
|
function_owner = self,
|
|
rotation = Vector(0, 0, 0),
|
|
height = CIRCLE_BUTTON_SIZE,
|
|
width = CIRCLE_BUTTON_SIZE,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
}
|
|
local buttonPos = WEAKNESS_ROW_START:copy()
|
|
weaknessButtonParams.click_function = "spawnWeaknesses"
|
|
weaknessButtonParams.tooltip = "All Weaknesses"
|
|
weaknessButtonParams.position = buttonPos
|
|
self.createButton(weaknessButtonParams)
|
|
buttonPos.x = buttonPos.x + MISC_BUTTONS_X_OFFSET
|
|
weaknessButtonParams.click_function = "spawnRandomWeakness"
|
|
weaknessButtonParams.tooltip = "Random Basic Weakness"
|
|
weaknessButtonParams.position = buttonPos
|
|
self.createButton(weaknessButtonParams)
|
|
end
|
|
|
|
function createOtherButtons()
|
|
local otherButtonParams = {
|
|
function_owner = self,
|
|
rotation = Vector(0, 0, 0),
|
|
height = CIRCLE_BUTTON_SIZE,
|
|
width = CIRCLE_BUTTON_SIZE,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
}
|
|
local buttonPos = OTHER_ROW_START:copy()
|
|
otherButtonParams.click_function = "spawnBonded"
|
|
otherButtonParams.tooltip = "Bonded Cards"
|
|
otherButtonParams.position = buttonPos
|
|
self.createButton(otherButtonParams)
|
|
buttonPos.x = buttonPos.x + MISC_BUTTONS_X_OFFSET
|
|
otherButtonParams.click_function = "spawnUpgradeSheets"
|
|
otherButtonParams.tooltip = "Customization Upgrade Sheets"
|
|
otherButtonParams.position = buttonPos
|
|
self.createButton(otherButtonParams)
|
|
end
|
|
|
|
function createCycleButtons()
|
|
local cycleButtonParams = {
|
|
function_owner = self,
|
|
rotation = Vector(0, 0, 0),
|
|
height = CYCLE_BUTTON_SIZE,
|
|
width = CYCLE_BUTTON_SIZE,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
}
|
|
local buttonPos = CYCLE_BUTTON_START:copy()
|
|
local rowCount = 0
|
|
local colCount = 0
|
|
for _, cycle in ipairs(CYCLE_LIST) do
|
|
cycleButtonParams.click_function = "spawnCycle" .. cycle
|
|
cycleButtonParams.position = buttonPos
|
|
cycleButtonParams.tooltip = cycle
|
|
self.createButton(cycleButtonParams)
|
|
self.setVar(cycleButtonParams.click_function, function(_, _, _) spawnCycle(cycle) end)
|
|
colCount = colCount + 1
|
|
-- If we've reached the end of a row, shift down and back to the first column
|
|
if colCount >= CYCLE_COLUMN_COUNT then
|
|
buttonPos = CYCLE_BUTTON_START:copy()
|
|
rowCount = rowCount + 1
|
|
colCount = 0
|
|
buttonPos.z = buttonPos.z + CYCLE_BUTTONS_Z_OFFSET * rowCount
|
|
if rowCount == 3 then
|
|
-- Account for centered button on the final row
|
|
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
|
|
end
|
|
else
|
|
buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
|
|
end
|
|
end
|
|
end
|
|
|
|
function createClearButton()
|
|
self.createButton({
|
|
function_owner = self,
|
|
click_function = "deleteAll",
|
|
position = Vector(0, 0.1, 0.852),
|
|
rotation = Vector(0, 0, 0),
|
|
height = 170,
|
|
width = 750,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = TRANSPARENT,
|
|
})
|
|
end
|
|
|
|
function createInvestigatorModeButtons()
|
|
local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
|
|
|
|
self.createButton({
|
|
function_owner = self,
|
|
click_function = "setCardsOnlyMode",
|
|
position = Vector(0.251, 0.1, -0.322),
|
|
rotation = Vector(0, 0, 0),
|
|
height = 170,
|
|
width = 760,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = starterMode and TRANSPARENT or STARTER_DECK_MODE_SELECTED_COLOR
|
|
})
|
|
self.createButton({
|
|
function_owner = self,
|
|
click_function = "setStarterDeckMode",
|
|
position = Vector(0.66, 0.1, -0.322),
|
|
rotation = Vector(0, 0, 0),
|
|
height = 170,
|
|
width = 760,
|
|
scale = Vector(0.25, 1, 0.25),
|
|
color = starterMode and STARTER_DECK_MODE_SELECTED_COLOR or TRANSPARENT
|
|
})
|
|
local checkX = starterMode and 0.52 or 0.11
|
|
self.createButton({
|
|
function_owner = self,
|
|
label = "✓",
|
|
click_function = "doNothing",
|
|
position = Vector(checkX, 0.11, -0.317),
|
|
rotation = Vector(0, 0, 0),
|
|
height = 0,
|
|
width = 0,
|
|
scale = Vector(0.3, 1, 0.3),
|
|
font_color = { 0, 0, 0 },
|
|
color = { 1, 1, 1 }
|
|
})
|
|
end
|
|
|
|
function toggleHelp(_, playerColor, _)
|
|
if helpVisibleToPlayers[playerColor] then
|
|
helpVisibleToPlayers[playerColor] = nil
|
|
else
|
|
helpVisibleToPlayers[playerColor] = true
|
|
end
|
|
updateHelpVisibility()
|
|
end
|
|
|
|
function updateHelpVisibility()
|
|
local visibility = ""
|
|
for player, _ in pairs(helpVisibleToPlayers) do
|
|
if string.len(visibility) > 0 then
|
|
visibility = visibility .. "|" .. player
|
|
else
|
|
visibility = player
|
|
end
|
|
end
|
|
self.UI.setAttribute("helpText", "visibility", visibility)
|
|
self.UI.setAttribute("helpPanel", "visibility", visibility)
|
|
self.UI.setAttribute("helpPanel", "active", string.len(visibility) > 0)
|
|
end
|
|
|
|
function setStarterDeckMode()
|
|
starterDeckMode = STARTER_DECK_MODE_STARTERS
|
|
updateStarterModeButtons()
|
|
end
|
|
|
|
function setCardsOnlyMode()
|
|
starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
|
|
updateStarterModeButtons()
|
|
end
|
|
|
|
function updateStarterModeButtons()
|
|
local buttonCount = #self.getButtons()
|
|
-- Buttons are 0-indexed, so the last three are -1, -2, and -3 from the size
|
|
self.removeButton(buttonCount - 1)
|
|
self.removeButton(buttonCount - 2)
|
|
self.removeButton(buttonCount - 3)
|
|
createInvestigatorModeButtons()
|
|
end
|
|
|
|
-- Clears the table and updates positions based on scale. Should be called before ANY card
|
|
-- placement
|
|
function prepareToPlaceCards()
|
|
deleteAll()
|
|
scalePositions()
|
|
end
|
|
|
|
-- Updates the positions based on the current object scale to ensure the relative layout functions
|
|
-- properly at different scales.
|
|
function scalePositions()
|
|
-- Assume scaling is consistent in X and Z dimensions
|
|
local scale = 1 / self.getScale().x
|
|
startPositions = { }
|
|
for key, pos in pairs(START_POSITIONS) do
|
|
-- Because a scaled object means a different global size, using global distance for Z results in
|
|
-- the cards being closer or farther depending on the scale. Leave the Z values and only scale
|
|
-- X and Y
|
|
startPositions[key] = Vector(pos)
|
|
startPositions[key].x = startPositions[key].x * scale
|
|
startPositions[key].y = startPositions[key].y * scale
|
|
end
|
|
cardRowOffset = CARD_ROW_OFFSET * scale
|
|
cardGroupOffset = CARD_GROUP_OFFSET * scale
|
|
investigatorPositionShiftRow = Vector(INVESTIGATOR_POSITION_SHIFT_ROW):scale(scale)
|
|
investigatorPositionShiftCol = Vector(INVESTIGATOR_POSITION_SHIFT_COL):scale(scale)
|
|
investigatorCardOffset = Vector(INVESTIGATOR_CARD_OFFSET):scale(scale)
|
|
investigatorSignatureOffset = Vector(INVESTIGATOR_SIGNATURE_OFFSET):scale(scale)
|
|
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 spawnInvestigatorGroup(groupName)
|
|
local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
|
|
prepareToPlaceCards()
|
|
Wait.frames(function()
|
|
if starterMode 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)
|
|
if INVESTIGATOR_GROUPS[groupName] == nil then
|
|
printToAll("No " .. groupName .. " data yet")
|
|
return
|
|
end
|
|
|
|
local col = 1
|
|
local row = 1
|
|
local investigatorCount = #INVESTIGATOR_GROUPS[groupName]
|
|
local position = getInvestigatorRowStartPos(investigatorCount, row)
|
|
|
|
for i, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do
|
|
for _, spawnSpec in ipairs(buildInvestigatorSpawnSpec(
|
|
investigatorName, INVESTIGATORS[investigatorName], position, false)) do
|
|
spawnBag.spawn(spawnSpec)
|
|
end
|
|
position:add(investigatorPositionShiftCol)
|
|
col = col + 1
|
|
if col > INVESTIGATOR_MAX_COLS then
|
|
col = 1
|
|
row = row + 1
|
|
position = getInvestigatorRowStartPos(investigatorCount, row)
|
|
end
|
|
end
|
|
end
|
|
|
|
function getInvestigatorRowStartPos(investigatorCount, row)
|
|
local rowStart = Vector(startPositions.investigator)
|
|
rowStart:add(Vector(
|
|
investigatorPositionShiftRow.x * (row - 1),
|
|
investigatorPositionShiftRow.y * (row - 1),
|
|
investigatorPositionShiftRow.z * (row - 1)))
|
|
local investigatorsInRow =
|
|
math.min(investigatorCount - INVESTIGATOR_MAX_COLS * (row - 1), INVESTIGATOR_MAX_COLS)
|
|
rowStart:add(Vector(
|
|
investigatorPositionShiftCol.x * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2,
|
|
investigatorPositionShiftCol.y * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2,
|
|
investigatorPositionShiftCol.z * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2))
|
|
|
|
return rowStart
|
|
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(investigatorSignatureOffset)
|
|
local spawns = buildCommonSpawnSpec(investigatorName, investigatorData, position)
|
|
table.insert(spawns, {
|
|
name = investigatorName.."signatures",
|
|
cards = investigatorData.signatures,
|
|
globalPos = self.positionToWorld(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(investigatorCardOffset)
|
|
return {
|
|
{
|
|
name = investigatorName.."minicards",
|
|
cards = oneCardOnly and { investigatorData.minicards[1] } or investigatorData.minicards,
|
|
globalPos = self.positionToWorld(position),
|
|
rotation = FACE_UP_ROTATION,
|
|
},
|
|
{
|
|
name = investigatorName.."cards",
|
|
cards = oneCardOnly and { investigatorData.cards[1] } or investigatorData.cards,
|
|
globalPos = self.positionToWorld(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 col = 1
|
|
local row = 1
|
|
local investigatorCount = #INVESTIGATOR_GROUPS[groupName]
|
|
local position = getInvestigatorRowStartPos(investigatorCount, row)
|
|
for _, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do
|
|
spawnStarterDeck(investigatorName, INVESTIGATORS[investigatorName], position)
|
|
position:add(investigatorPositionShiftCol)
|
|
col = col + 1
|
|
if col > INVESTIGATOR_MAX_COLS then
|
|
col = 1
|
|
row = row + 1
|
|
position = getInvestigatorRowStartPos(investigatorCount, row)
|
|
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(investigatorSignatureOffset)
|
|
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 = self.positionToWorld(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)
|
|
prepareToPlaceCards()
|
|
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
|
|
local groupPos = Vector(startPositions.classCards)
|
|
if #skillList > 0 then
|
|
spawnBag.spawn({
|
|
name = cardClass .. (isUpgraded and "upgraded" or "basic"),
|
|
cards = skillList,
|
|
globalPos = self.positionToWorld(groupPos),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
groupPos.z = groupPos.z + math.ceil(#skillList / 20) * cardRowOffset + cardGroupOffset
|
|
end
|
|
if #eventList > 0 then
|
|
spawnBag.spawn({
|
|
name = cardClass .. "event" .. (isUpgraded and "upgraded" or "basic"),
|
|
cards = eventList,
|
|
globalPos = self.positionToWorld(groupPos),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
groupPos.z = groupPos.z + math.ceil(#eventList / 20) * cardRowOffset + cardGroupOffset
|
|
end
|
|
if #assetList > 0 then
|
|
spawnBag.spawn({
|
|
name = cardClass .. "asset" .. (isUpgraded and "upgraded" or "basic"),
|
|
cards = assetList,
|
|
globalPos = self.positionToWorld(groupPos),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
end
|
|
end
|
|
|
|
-- Spawns the investigator sets and all cards for the given cycle
|
|
---@param cycle String Name of a cycle, should match the standard used in card metadata
|
|
function spawnCycle(cycle)
|
|
prepareToPlaceCards()
|
|
spawnInvestigators(cycle)
|
|
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 cycleCardList = allCardsBag.call("getCardsByCycle", cycle)
|
|
local copiedList = { }
|
|
for i, id in ipairs(cycleCardList) do
|
|
copiedList[i] = id
|
|
end
|
|
spawnBag.spawn({
|
|
name = "cycle"..cycle,
|
|
cards = copiedList,
|
|
globalPos = self.positionToWorld(startPositions.cycle),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
end
|
|
|
|
function spawnBonded()
|
|
prepareToPlaceCards()
|
|
spawnBag.spawn({
|
|
name = "bonded",
|
|
cards = BONDED_CARD_LIST,
|
|
globalPos = self.positionToWorld(startPositions.classCards),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
end
|
|
|
|
function spawnUpgradeSheets()
|
|
prepareToPlaceCards()
|
|
spawnBag.spawn({
|
|
name = "upgradeSheets",
|
|
cards = UPGRADE_SHEET_LIST,
|
|
globalPos = self.positionToWorld(startPositions.classCards),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
spawnBag.spawn({
|
|
name = "servitor",
|
|
cards = { "09080-m" },
|
|
globalPos = self.positionToWorld(startPositions.summonedServitor),
|
|
rotation = FACE_UP_ROTATION,
|
|
})
|
|
end
|
|
|
|
-- Clears the current cards, and places all basic weaknesses on the table.
|
|
function spawnWeaknesses()
|
|
prepareToPlaceCards()
|
|
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("getUniqueWeaknesses")
|
|
local basicWeaknessList = { }
|
|
local otherWeaknessList = { }
|
|
for i, id in ipairs(weaknessIdList) do
|
|
local cardMetadata = allCardsBag.call("getCardById", { id = id }).metadata
|
|
if cardMetadata.basicWeaknessCount ~= nil and cardMetadata.basicWeaknessCount > 0 then
|
|
table.insert(basicWeaknessList, id)
|
|
elseif excludedNonBasicWeaknesses[id] == nil then
|
|
table.insert(otherWeaknessList, id)
|
|
end
|
|
end
|
|
local groupPos = Vector(startPositions.classCards)
|
|
spawnBag.spawn({
|
|
name = "basicWeaknesses",
|
|
cards = basicWeaknessList,
|
|
globalPos = self.positionToWorld(groupPos),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
groupPos.z = groupPos.z + math.ceil(#basicWeaknessList / 20) * cardRowOffset + cardGroupOffset
|
|
spawnBag.spawn({
|
|
name = "evolvedWeaknesses",
|
|
cards = EVOLVED_WEAKNESSES,
|
|
globalPos = self.positionToWorld(groupPos),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
groupPos.z = groupPos.z + math.ceil(#EVOLVED_WEAKNESSES / 20) * cardRowOffset + cardGroupOffset
|
|
spawnBag.spawn({
|
|
name = "otherWeaknesses",
|
|
cards = otherWeaknessList,
|
|
globalPos = self.positionToWorld(groupPos),
|
|
rotation = FACE_UP_ROTATION,
|
|
spread = true,
|
|
spreadCols = 20
|
|
})
|
|
end
|
|
|
|
function spawnRandomWeakness()
|
|
prepareToPlaceCards()
|
|
local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
|
|
local weaknessId = allCardsBag.call("getRandomWeaknessId")
|
|
if (weaknessId == nil) then
|
|
broadcastToAll("All basic weaknesses are in play!", {0.9, 0.2, 0.2})
|
|
return
|
|
end
|
|
spawnBag.spawn({
|
|
name = "randomWeakness",
|
|
cards = { weaknessId },
|
|
globalPos = self.positionToWorld(startPositions.randomWeakness),
|
|
rotation = FACE_UP_ROTATION,
|
|
})
|
|
end
|