c33d0386b7
Because of the mix of global and relative positioning this ended up being very complex. Tried to make sure the comments were thorough, but open to any improvement suggestions.
758 lines
26 KiB
Plaintext
758 lines
26 KiB
Plaintext
require("playercards/PlayerCardPanelData")
|
|
local spawnBag = require("playercards/spawnbag/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
|