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:
parent
83ff3eb65b
commit
a2fb1058c6
@ -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))
|
||||
|
@ -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
|
||||
|
509
src/playercards/PlayerCardPanel.ttslua
Normal file
509
src/playercards/PlayerCardPanel.ttslua
Normal 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
|
44
src/playercards/PlayerCardPanelData.ttslua
Normal file
44
src/playercards/PlayerCardPanelData.ttslua
Normal 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"]
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user