Merge pull request #1 from argonui/initial231
Update source repository to SCED v2.3.1
This commit is contained in:
commit
32a86bd9b4
@ -1,287 +0,0 @@
|
|||||||
---
|
|
||||||
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
|
||||||
--- Created by Whimsical.
|
|
||||||
--- DateTime: 2021-08-22 6:36 a.m.
|
|
||||||
---
|
|
||||||
|
|
||||||
---@class CommandTableEntry
|
|
||||||
---@field public object TTSObject
|
|
||||||
---@field public runOn ArkhamImport_Command_RunDirectives
|
|
||||||
local CommandTableEntry = {}
|
|
||||||
|
|
||||||
---@type table<string, CommandTableEntry>
|
|
||||||
local commands = {}
|
|
||||||
|
|
||||||
---@type table<string, boolean>
|
|
||||||
local found_commands = {}
|
|
||||||
|
|
||||||
---@type table<string, any>
|
|
||||||
local command_state
|
|
||||||
|
|
||||||
local function load_commands()
|
|
||||||
local command_objects = getObjectsWithTag("import_command")
|
|
||||||
|
|
||||||
for _, object in ipairs(command_objects) do
|
|
||||||
commands[object:getVar("command_name")] = {
|
|
||||||
object = object,
|
|
||||||
runOn = object:getTable("runOn")
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
---@param message string
|
|
||||||
---@return ArkhamImport_CommandManager_InitializationResults
|
|
||||||
local function build_error(configuration, message)
|
|
||||||
return {
|
|
||||||
configuration = configuration,
|
|
||||||
is_successful = false,
|
|
||||||
error_message = message
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param source table<any, any>
|
|
||||||
---@param updates table<any, any>
|
|
||||||
local function merge_tables(source, updates)
|
|
||||||
for key, _ in pairs(source) do
|
|
||||||
local update = updates[key]
|
|
||||||
if update~=nil then
|
|
||||||
source[key] = update
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param instruction TTSObject
|
|
||||||
---@param initialization_state any
|
|
||||||
---@param arguments string[]
|
|
||||||
---@return ArkhamImport_CommandManager_InitializationResults|nil
|
|
||||||
local function run_instruction(instruction, initialization_state, arguments)
|
|
||||||
---@type ArkhamImport_Command_DescriptionInstructionResults
|
|
||||||
local result = instruction:call("do_instruction", {
|
|
||||||
configuration = initialization_state.configuration,
|
|
||||||
command_state = initialization_state.command_state,
|
|
||||||
arguments = arguments
|
|
||||||
})
|
|
||||||
|
|
||||||
if (not result) or type(result)~="table" then
|
|
||||||
return build_error(initialization_state.configuration, table.concat({"Command \"", instruction:getName(), "\" did not return a table from do_instruction call. Type \"", type(result), "\" was returned."}))
|
|
||||||
end
|
|
||||||
|
|
||||||
if not result.is_successful then
|
|
||||||
return build_error(result.configuration, result.error_message)
|
|
||||||
end
|
|
||||||
|
|
||||||
merge_tables(initialization_state, result)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param description string
|
|
||||||
---@param initialization_state table<string, any>
|
|
||||||
---@return ArkhamImport_CommandManager_InitializationResults|nil
|
|
||||||
local function initialize_instructions(description, initialization_state)
|
|
||||||
for _, instruction in ipairs(parse(description)) do
|
|
||||||
local command = commands[instruction.command]
|
|
||||||
|
|
||||||
if command==nil then
|
|
||||||
return build_error(initialization_state.configuration, table.concat({ "Could not find command \"", command, "\"."}))
|
|
||||||
end
|
|
||||||
|
|
||||||
found_commands[instruction.command] = true
|
|
||||||
|
|
||||||
if command.runOn.instructions then
|
|
||||||
local error = run_instruction(command.object, initialization_state, instruction.arguments)
|
|
||||||
if error then return error end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_CommandManager_InitializationArguments
|
|
||||||
---@return table<string, any>
|
|
||||||
local function create_initialize_state(parameters)
|
|
||||||
return {
|
|
||||||
configuration = parameters.configuration,
|
|
||||||
command_state = {}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_CommandManager_InitializationArguments
|
|
||||||
---@return ArkhamImport_CommandManager_InitializationResults
|
|
||||||
function initialize(parameters)
|
|
||||||
found_commands = {}
|
|
||||||
load_commands()
|
|
||||||
|
|
||||||
local initialization_state = create_initialize_state(parameters)
|
|
||||||
|
|
||||||
local error = initialize_instructions(parameters.description, initialization_state)
|
|
||||||
if error then return error end
|
|
||||||
|
|
||||||
command_state = initialization_state.command_state
|
|
||||||
|
|
||||||
return {
|
|
||||||
configuration = initialization_state.configuration,
|
|
||||||
is_successful = true
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_CommandManager_HandlerArguments
|
|
||||||
---@return table<string, any>
|
|
||||||
local function create_handler_state(parameters)
|
|
||||||
return {
|
|
||||||
card = parameters.card,
|
|
||||||
handled = false,
|
|
||||||
zone = parameters.zone,
|
|
||||||
command_state = command_state
|
|
||||||
},
|
|
||||||
{
|
|
||||||
configuration = parameters.configuration,
|
|
||||||
source_guid = parameters.source_guid
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param card ArkhamImportCard
|
|
||||||
---@param zone = string[]
|
|
||||||
---@param handled boolean
|
|
||||||
---@param error_message string
|
|
||||||
---@return ArkhamImport_CommandManager_HandlerResults
|
|
||||||
local function create_handler_error(card, zone, handled, error_message)
|
|
||||||
return {
|
|
||||||
handled = handled,
|
|
||||||
card = card,
|
|
||||||
zone = zone,
|
|
||||||
is_successful = false,
|
|
||||||
error_message = error_message
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param handler TTSObject
|
|
||||||
---@param handler_state table<string, any>
|
|
||||||
---@param handler_constants table<string, any>
|
|
||||||
---@return ArkhamImport_CommandManager_HandlerResults|nil
|
|
||||||
local function call_handler(handler, handler_state, handler_constants)
|
|
||||||
---@type ArkhamImport_CommandManager_HandlerResults
|
|
||||||
local results = handler:call("handle_card", {
|
|
||||||
configuration = handler_constants.configuration,
|
|
||||||
source_guid = handler_constants.source_guid,
|
|
||||||
card = handler_state.card,
|
|
||||||
zone = handler_state.zone,
|
|
||||||
command_state = handler_state.command_state,
|
|
||||||
})
|
|
||||||
|
|
||||||
if not results.is_successful then return create_handler_error(results.card, results.zone, results.handled, results.error_message) end
|
|
||||||
|
|
||||||
merge_tables(handler_state, results)
|
|
||||||
command_state = handler_state.command_state
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param handler_state table<string, any>
|
|
||||||
---@param handler_constants table<string, any>
|
|
||||||
---@return ArkhamImport_CommandManager_HandlerResults|nil
|
|
||||||
local function run_handlers(handler_state, handler_constants)
|
|
||||||
for command_name, _ in pairs(found_commands) do
|
|
||||||
local command = commands[command_name]
|
|
||||||
if command.runOn.handlers then
|
|
||||||
local error = call_handler(command.object, handler_state, handler_constants)
|
|
||||||
if error then return error end
|
|
||||||
|
|
||||||
if (handler_state.handled) then return end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_CommandManager_HandlerArguments
|
|
||||||
---@return ArkhamImport_CommandManager_HandlerResults
|
|
||||||
function handle(parameters)
|
|
||||||
local handler_state, handler_constants = create_handler_state(parameters)
|
|
||||||
|
|
||||||
local error = run_handlers(handler_state, handler_constants)
|
|
||||||
if error then return error end
|
|
||||||
|
|
||||||
return {
|
|
||||||
handled = handler_state.handled,
|
|
||||||
card = handler_state.card,
|
|
||||||
zone = handler_state.zone,
|
|
||||||
is_successful = true
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param description string
|
|
||||||
---@return ArkhamImportCommandParserResult[]
|
|
||||||
function parse(description)
|
|
||||||
local input = description
|
|
||||||
|
|
||||||
if #input<=4 then return {} end
|
|
||||||
|
|
||||||
---@type string
|
|
||||||
local current, l1, l2, l3 = "", "", "", ""
|
|
||||||
|
|
||||||
local concat = table.concat
|
|
||||||
|
|
||||||
local function advance()
|
|
||||||
current, l1, l2, l3 = l1, l2, l3, input:sub(1,1)
|
|
||||||
input = input:sub(2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function advance_all()
|
|
||||||
current, l1, l2, l3 = input:sub(1,1), input:sub(2,2), input:sub(3,3), input:sub(4,4)
|
|
||||||
input = input:sub(5)
|
|
||||||
end
|
|
||||||
|
|
||||||
advance_all()
|
|
||||||
|
|
||||||
---@type ArkhamImportCommandParserResult[]
|
|
||||||
local results = {}
|
|
||||||
|
|
||||||
---@type string
|
|
||||||
local command
|
|
||||||
|
|
||||||
---@type string[]
|
|
||||||
local arguments = {}
|
|
||||||
|
|
||||||
---@type string
|
|
||||||
local separator
|
|
||||||
|
|
||||||
---@type string[]
|
|
||||||
local result = {}
|
|
||||||
|
|
||||||
while #current>0 do
|
|
||||||
if current=="<" and l1=="?" and l2 == "?" then
|
|
||||||
command = nil
|
|
||||||
arguments = {}
|
|
||||||
separator = l3
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
advance_all()
|
|
||||||
elseif current == "?" and l1 == "?" and l2 == ">" then
|
|
||||||
if not command then
|
|
||||||
table.insert(results, {
|
|
||||||
command = concat(result),
|
|
||||||
arguments = {}
|
|
||||||
})
|
|
||||||
else
|
|
||||||
table.insert(arguments, concat(result))
|
|
||||||
table.insert(results, {
|
|
||||||
command = command,
|
|
||||||
arguments = arguments
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
separator = nil
|
|
||||||
current, l1, l2, l3 = l3, input:sub(1,1), input:sub(2,2), input:sub(3,3)
|
|
||||||
input = input:sub(4)
|
|
||||||
elseif current == separator then
|
|
||||||
if not command then
|
|
||||||
command = concat(result)
|
|
||||||
else
|
|
||||||
table.insert(arguments, concat(result))
|
|
||||||
end
|
|
||||||
result = {}
|
|
||||||
advance()
|
|
||||||
else
|
|
||||||
if separator~=nil then
|
|
||||||
table.insert(result, current)
|
|
||||||
end
|
|
||||||
advance()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return results
|
|
||||||
end
|
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,16 @@
|
|||||||
local INPUT_FIELD_HEIGHT = 340
|
local INPUT_FIELD_HEIGHT = 340
|
||||||
local INPUT_FIELD_WIDTH = 1500
|
local INPUT_FIELD_WIDTH = 1500
|
||||||
|
local FIELD_COLOR = { 0.9, 0.7, 0.5 }
|
||||||
|
|
||||||
local FIELD_COLOR = {0.9,0.7,0.5}
|
local PRIVATE_TOGGLE_LABELS = {}
|
||||||
|
|
||||||
local PRIVATE_TOGGLE_LABELS = { }
|
|
||||||
PRIVATE_TOGGLE_LABELS[true] = "Private"
|
PRIVATE_TOGGLE_LABELS[true] = "Private"
|
||||||
PRIVATE_TOGGLE_LABELS[false] = "Published"
|
PRIVATE_TOGGLE_LABELS[false] = "Published"
|
||||||
local UPGRADED_TOGGLE_LABELS = { }
|
|
||||||
|
local UPGRADED_TOGGLE_LABELS = {}
|
||||||
UPGRADED_TOGGLE_LABELS[true] = "Upgraded"
|
UPGRADED_TOGGLE_LABELS[true] = "Upgraded"
|
||||||
UPGRADED_TOGGLE_LABELS[false] = "Specific"
|
UPGRADED_TOGGLE_LABELS[false] = "Specific"
|
||||||
local LOAD_INVESTIGATOR_TOGGLE_LABELS = { }
|
|
||||||
|
local LOAD_INVESTIGATOR_TOGGLE_LABELS = {}
|
||||||
LOAD_INVESTIGATOR_TOGGLE_LABELS[true] = "Yes"
|
LOAD_INVESTIGATOR_TOGGLE_LABELS[true] = "Yes"
|
||||||
LOAD_INVESTIGATOR_TOGGLE_LABELS[false] = "No"
|
LOAD_INVESTIGATOR_TOGGLE_LABELS[false] = "No"
|
||||||
|
|
||||||
@ -17,14 +18,13 @@ local redDeckId = ""
|
|||||||
local orangeDeckId = ""
|
local orangeDeckId = ""
|
||||||
local whiteDeckId = ""
|
local whiteDeckId = ""
|
||||||
local greenDeckId = ""
|
local greenDeckId = ""
|
||||||
|
|
||||||
local privateDeck = true
|
local privateDeck = true
|
||||||
local loadNewestDeck = true
|
local loadNewestDeck = true
|
||||||
local loadInvestigators = false
|
local loadInvestigators = false
|
||||||
local loadingColor = ""
|
|
||||||
|
|
||||||
-- Returns a table with the full state of the UI, including options and deck
|
-- Returns a table with the full state of the UI, including options and deck IDs.
|
||||||
-- IDs. This can be used to persist via onSave(), or provide values for a load
|
-- This can be used to persist via onSave(), or provide values for a load operation
|
||||||
-- operation
|
|
||||||
-- Table values:
|
-- Table values:
|
||||||
-- redDeck: Deck ID to load for the red player
|
-- redDeck: Deck ID to load for the red player
|
||||||
-- orangeDeck: Deck ID to load for the orange player
|
-- orangeDeck: Deck ID to load for the orange player
|
||||||
@ -41,14 +41,13 @@ function getUiState()
|
|||||||
greenDeck = greenDeckId,
|
greenDeck = greenDeckId,
|
||||||
private = privateDeck,
|
private = privateDeck,
|
||||||
loadNewest = loadNewestDeck,
|
loadNewest = loadNewestDeck,
|
||||||
investigators = loadInvestigators,
|
investigators = loadInvestigators
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sets up the UI for the deck loader, populating fields from the given save
|
-- Sets up the UI for the deck loader, populating fields from the given save state table decoded from onLoad()
|
||||||
-- state table decoded from onLoad()
|
|
||||||
function initializeUi(savedUiState)
|
function initializeUi(savedUiState)
|
||||||
if (savedUiState ~= nil) then
|
if savedUiState ~= nil then
|
||||||
redDeckId = savedUiState.redDeck
|
redDeckId = savedUiState.redDeck
|
||||||
orangeDeckId = savedUiState.orangeDeck
|
orangeDeckId = savedUiState.orangeDeck
|
||||||
whiteDeckId = savedUiState.whiteDeck
|
whiteDeckId = savedUiState.whiteDeck
|
||||||
@ -56,14 +55,6 @@ function initializeUi(savedUiState)
|
|||||||
privateDeck = savedUiState.private
|
privateDeck = savedUiState.private
|
||||||
loadNewestDeck = savedUiState.loadNewest
|
loadNewestDeck = savedUiState.loadNewest
|
||||||
loadInvestigators = savedUiState.investigators
|
loadInvestigators = savedUiState.investigators
|
||||||
else
|
|
||||||
redDeckId = ""
|
|
||||||
orangeDeckId = ""
|
|
||||||
whiteDeckId = ""
|
|
||||||
greenDeckId = ""
|
|
||||||
privateDeck = true
|
|
||||||
loadNewestDeck = true
|
|
||||||
loadInvestigators = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
makeOptionToggles()
|
makeOptionToggles()
|
||||||
@ -72,59 +63,35 @@ function initializeUi(savedUiState)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function makeOptionToggles()
|
function makeOptionToggles()
|
||||||
-- Creates the three option toggle buttons. Each toggle assumes its index as
|
-- common parameters
|
||||||
-- part of the toggle logic. IF YOU CHANGE THE ORDER OF THESE FIELDS YOU MUST
|
|
||||||
-- CHANGE THE EVENT HANDLERS
|
|
||||||
makePublicPrivateToggle()
|
|
||||||
makeLoadUpgradedToggle()
|
|
||||||
makeLoadInvestigatorsToggle()
|
|
||||||
end
|
|
||||||
|
|
||||||
function makePublicPrivateToggle()
|
|
||||||
local checkbox_parameters = {}
|
local checkbox_parameters = {}
|
||||||
|
checkbox_parameters.function_owner = self
|
||||||
|
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
||||||
|
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
||||||
|
checkbox_parameters.scale = { 0.1, 0.1, 0.1 }
|
||||||
|
checkbox_parameters.font_size = 240
|
||||||
|
checkbox_parameters.hover_color = { 0.4, 0.6, 0.8 }
|
||||||
|
checkbox_parameters.color = FIELD_COLOR
|
||||||
|
|
||||||
|
-- public / private deck
|
||||||
checkbox_parameters.click_function = "publicPrivateChanged"
|
checkbox_parameters.click_function = "publicPrivateChanged"
|
||||||
checkbox_parameters.function_owner = self
|
checkbox_parameters.position = { 0.25, 0.1, -0.102 }
|
||||||
checkbox_parameters.position = {0.25,0.1,-0.102}
|
checkbox_parameters.tooltip = "Published or private deck?\n\nPLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!"
|
||||||
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
|
||||||
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
|
||||||
checkbox_parameters.tooltip = "Published or private deck.\n\n*****PLEASE USE A PRIVATE DECK IF JUST FOR TTS TO AVOID FLOODING ARKHAMDB PUBLISHED DECK LISTS!"
|
|
||||||
checkbox_parameters.label = PRIVATE_TOGGLE_LABELS[privateDeck]
|
checkbox_parameters.label = PRIVATE_TOGGLE_LABELS[privateDeck]
|
||||||
checkbox_parameters.font_size = 240
|
|
||||||
checkbox_parameters.scale = {0.1,0.1,0.1}
|
|
||||||
checkbox_parameters.color = FIELD_COLOR
|
|
||||||
checkbox_parameters.hover_color = {0.4,0.6,0.8}
|
|
||||||
self.createButton(checkbox_parameters)
|
self.createButton(checkbox_parameters)
|
||||||
end
|
|
||||||
|
|
||||||
function makeLoadUpgradedToggle()
|
-- load upgraded?
|
||||||
local checkbox_parameters = {}
|
|
||||||
checkbox_parameters.click_function = "loadUpgradedChanged"
|
checkbox_parameters.click_function = "loadUpgradedChanged"
|
||||||
checkbox_parameters.function_owner = self
|
checkbox_parameters.position = { 0.25, 0.1, -0.01 }
|
||||||
checkbox_parameters.position = {0.25,0.1,-0.01}
|
checkbox_parameters.tooltip = "Load newest upgrade or exact deck?"
|
||||||
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
|
||||||
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
|
||||||
checkbox_parameters.tooltip = "Load newest upgrade, or exact deck"
|
|
||||||
checkbox_parameters.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck]
|
checkbox_parameters.label = UPGRADED_TOGGLE_LABELS[loadNewestDeck]
|
||||||
checkbox_parameters.font_size = 240
|
|
||||||
checkbox_parameters.scale = {0.1,0.1,0.1}
|
|
||||||
checkbox_parameters.color = FIELD_COLOR
|
|
||||||
checkbox_parameters.hover_color = {0.4,0.6,0.8}
|
|
||||||
self.createButton(checkbox_parameters)
|
self.createButton(checkbox_parameters)
|
||||||
end
|
|
||||||
|
|
||||||
function makeLoadInvestigatorsToggle()
|
-- load investigators?
|
||||||
local checkbox_parameters = {}
|
|
||||||
checkbox_parameters.click_function = "loadInvestigatorsChanged"
|
checkbox_parameters.click_function = "loadInvestigatorsChanged"
|
||||||
checkbox_parameters.function_owner = self
|
checkbox_parameters.position = { 0.25, 0.1, 0.081 }
|
||||||
checkbox_parameters.position = {0.25,0.1,0.081}
|
|
||||||
checkbox_parameters.width = INPUT_FIELD_WIDTH
|
|
||||||
checkbox_parameters.height = INPUT_FIELD_HEIGHT
|
|
||||||
checkbox_parameters.tooltip = "Spawn investigator cards?"
|
checkbox_parameters.tooltip = "Spawn investigator cards?"
|
||||||
checkbox_parameters.label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators]
|
checkbox_parameters.label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators]
|
||||||
checkbox_parameters.font_size = 240
|
|
||||||
checkbox_parameters.scale = {0.1,0.1,0.1}
|
|
||||||
checkbox_parameters.color = FIELD_COLOR
|
|
||||||
checkbox_parameters.hover_color = {0.4,0.6,0.8}
|
|
||||||
self.createButton(checkbox_parameters)
|
self.createButton(checkbox_parameters)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -133,103 +100,74 @@ function makeDeckIdFields()
|
|||||||
local input_parameters = {}
|
local input_parameters = {}
|
||||||
-- Parameters common to all entry fields
|
-- Parameters common to all entry fields
|
||||||
input_parameters.function_owner = self
|
input_parameters.function_owner = self
|
||||||
input_parameters.scale = {0.1,0.1,0.1}
|
input_parameters.scale = { 0.1, 0.1, 0.1 }
|
||||||
input_parameters.width = INPUT_FIELD_WIDTH
|
input_parameters.width = INPUT_FIELD_WIDTH
|
||||||
input_parameters.height = INPUT_FIELD_HEIGHT
|
input_parameters.height = INPUT_FIELD_HEIGHT
|
||||||
input_parameters.font_size = 320
|
input_parameters.font_size = 320
|
||||||
input_parameters.tooltip = "Deck ID from ArkhamDB URL of the deck\nPublic URL: 'https://arkhamdb.com/decklist/view/101/knowledge-overwhelming-solo-deck-1.0' = '101'\nPrivate URL: 'https://arkhamdb.com/deck/view/102' = '102'"
|
input_parameters.tooltip = "Deck ID from ArkhamDB URL of the deck\nPublic URL: 'https://arkhamdb.com/decklist/view/101/knowledge-overwhelming-solo-deck-1.0' = '101'\nPrivate URL: 'https://arkhamdb.com/deck/view/102' = '102'"
|
||||||
input_parameters.alignment = 3 -- Center
|
input_parameters.alignment = 3 -- Center
|
||||||
input_parameters.color = FIELD_COLOR
|
input_parameters.color = FIELD_COLOR
|
||||||
input_parameters.font_color = {0, 0, 0}
|
input_parameters.font_color = { 0, 0, 0 }
|
||||||
input_parameters.validation = 2 -- Integer
|
input_parameters.validation = 2 -- Integer
|
||||||
|
|
||||||
-- Green
|
-- Green
|
||||||
input_parameters.input_function = "greenDeckChanged"
|
input_parameters.input_function = "greenDeckChanged"
|
||||||
input_parameters.position = {-0.166,0.1,0.385}
|
input_parameters.position = { -0.166, 0.1, 0.385 }
|
||||||
input_parameters.value=greenDeckId
|
input_parameters.value = greenDeckId
|
||||||
self.createInput(input_parameters)
|
self.createInput(input_parameters)
|
||||||
-- Red
|
-- Red
|
||||||
input_parameters.input_function = "redDeckChanged"
|
input_parameters.input_function = "redDeckChanged"
|
||||||
input_parameters.position = {0.171,0.1,0.385}
|
input_parameters.position = { 0.171, 0.1, 0.385 }
|
||||||
input_parameters.value=redDeckId
|
input_parameters.value = redDeckId
|
||||||
self.createInput(input_parameters)
|
self.createInput(input_parameters)
|
||||||
-- White
|
-- White
|
||||||
input_parameters.input_function = "whiteDeckChanged"
|
input_parameters.input_function = "whiteDeckChanged"
|
||||||
input_parameters.position = {-0.166,0.1,0.474}
|
input_parameters.position = { -0.166, 0.1, 0.474 }
|
||||||
input_parameters.value=whiteDeckId
|
input_parameters.value = whiteDeckId
|
||||||
self.createInput(input_parameters)
|
self.createInput(input_parameters)
|
||||||
-- Orange
|
-- Orange
|
||||||
input_parameters.input_function = "orangeDeckChanged"
|
input_parameters.input_function = "orangeDeckChanged"
|
||||||
input_parameters.position = {0.171,0.1,0.474}
|
input_parameters.position = { 0.171, 0.1, 0.474 }
|
||||||
input_parameters.value=orangeDeckId
|
input_parameters.value = orangeDeckId
|
||||||
self.createInput(input_parameters)
|
self.createInput(input_parameters)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Create the Build All button. This is a transparent button which covers the
|
-- Create the Build All button. This is a transparent button which covers the Build All portion of the background graphic
|
||||||
-- Build All portion of the background graphic
|
|
||||||
function makeBuildButton()
|
function makeBuildButton()
|
||||||
local button_parameters = {}
|
local button_parameters = {}
|
||||||
button_parameters.click_function = "loadDecks"
|
button_parameters.click_function = "loadDecks"
|
||||||
button_parameters.function_owner = self
|
button_parameters.function_owner = self
|
||||||
button_parameters.position = {0,0.1,0.71}
|
button_parameters.position = { 0, 0.1, 0.71 }
|
||||||
button_parameters.width = 320
|
button_parameters.width = 320
|
||||||
button_parameters.height = 30
|
button_parameters.height = 30
|
||||||
button_parameters.color = {0, 0, 0, 0}
|
button_parameters.color = { 0, 0, 0, 0 }
|
||||||
button_parameters.tooltip = "Click to build all four decks!"
|
button_parameters.tooltip = "Click to build all four decks!"
|
||||||
self.createButton(button_parameters)
|
self.createButton(button_parameters)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Event handler for the Public/Private toggle. Changes the local value and the
|
-- Event handlers for deck ID change
|
||||||
-- labels to toggle the button
|
function redDeckChanged(_, _, inputValue) redDeckId = inputValue end
|
||||||
|
|
||||||
|
function orangeDeckChanged(_, _, inputValue) orangeDeckId = inputValue end
|
||||||
|
|
||||||
|
function whiteDeckChanged(_, _, inputValue) whiteDeckId = inputValue end
|
||||||
|
|
||||||
|
function greenDeckChanged(_, _, inputValue) greenDeckId = inputValue end
|
||||||
|
|
||||||
|
-- Event handlers for toggle buttons
|
||||||
function publicPrivateChanged()
|
function publicPrivateChanged()
|
||||||
-- editButton uses parameters.index which is 0-indexed
|
|
||||||
privateDeck = not privateDeck
|
privateDeck = not privateDeck
|
||||||
self.editButton {
|
self.editButton { index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] }
|
||||||
index = 0,
|
|
||||||
label = PRIVATE_TOGGLE_LABELS[privateDeck],
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Event handler for the Upgraded toggle. Changes the local value and the
|
|
||||||
-- labels to toggle the button
|
|
||||||
function loadUpgradedChanged()
|
function loadUpgradedChanged()
|
||||||
-- editButton uses parameters.index which is 0-indexed
|
|
||||||
loadNewestDeck = not loadNewestDeck
|
loadNewestDeck = not loadNewestDeck
|
||||||
self.editButton {
|
self.editButton { index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] }
|
||||||
index = 1,
|
|
||||||
label = UPGRADED_TOGGLE_LABELS[loadNewestDeck],
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Event handler for the load investigator cards toggle. Changes the local
|
|
||||||
-- value and the labels to toggle the button
|
|
||||||
function loadInvestigatorsChanged()
|
function loadInvestigatorsChanged()
|
||||||
-- editButton uses parameters.index which is 0-indexed
|
|
||||||
loadInvestigators = not loadInvestigators
|
loadInvestigators = not loadInvestigators
|
||||||
self.editButton {
|
self.editButton { index = 2, label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] }
|
||||||
index = 2,
|
|
||||||
label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators],
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Event handler for deck ID change
|
|
||||||
function redDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
||||||
redDeckId = inputValue
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Event handler for deck ID change
|
|
||||||
function orangeDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
||||||
orangeDeckId = inputValue
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Event handler for deck ID change
|
|
||||||
function whiteDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
||||||
whiteDeckId = inputValue
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Event handler for deck ID change
|
|
||||||
function greenDeckChanged(objectInputTyped, playerColorTyped, inputValue, selected)
|
|
||||||
greenDeckId = inputValue
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function loadDecks()
|
function loadDecks()
|
||||||
|
@ -1,542 +0,0 @@
|
|||||||
---
|
|
||||||
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
|
||||||
--- Created by Whimsical.
|
|
||||||
--- DateTime: 2021-08-19 6:38 a.m.
|
|
||||||
---
|
|
||||||
|
|
||||||
---@type ArkhamImportConfiguration
|
|
||||||
|
|
||||||
local tags = { configuration = "import_configuration_provider" }
|
|
||||||
|
|
||||||
local Priority = {
|
|
||||||
ERROR = 0,
|
|
||||||
WARNING = 1,
|
|
||||||
INFO = 2,
|
|
||||||
DEBUG = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
---@type fun(text: string)
|
|
||||||
local print_fun = print
|
|
||||||
local print_priority = Priority.DEBUG
|
|
||||||
|
|
||||||
---@param priority number
|
|
||||||
---@return string
|
|
||||||
function Priority.get_label(priority)
|
|
||||||
if priority==0 then return "ERROR"
|
|
||||||
elseif priority==1 then return "WARNING"
|
|
||||||
elseif priority==2 then return "INFO"
|
|
||||||
elseif priority==3 then return "DEBUG"
|
|
||||||
else error(table.concat({"Priority", priority, "not found"}, " ")) return ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param message string
|
|
||||||
---@param priority number
|
|
||||||
local function debug_print(message, priority)
|
|
||||||
if (print_priority >= priority) then
|
|
||||||
print_fun("[" .. Priority.get_label(priority) .. "] " .. message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param str string
|
|
||||||
---@return string
|
|
||||||
local function fix_utf16_string(str)
|
|
||||||
return str:gsub("\\u(%w%w%w%w)", function (match)
|
|
||||||
return string.char(tonumber(match,16))
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--Forward declaration
|
|
||||||
---@type Request
|
|
||||||
local Request = {}
|
|
||||||
|
|
||||||
---@type table<string, ArkhamImportTaboo>
|
|
||||||
local taboo_list = {}
|
|
||||||
|
|
||||||
---@type number
|
|
||||||
local deck_type_button_index
|
|
||||||
|
|
||||||
local is_private_deck = true
|
|
||||||
|
|
||||||
function on_decktype_checkbox_clicked()
|
|
||||||
self:editButton {
|
|
||||||
label = is_private_deck and "Published" or "Private",
|
|
||||||
index = deck_type_button_index
|
|
||||||
}
|
|
||||||
is_private_deck = not is_private_deck
|
|
||||||
end
|
|
||||||
|
|
||||||
---@return ArkhamImportConfiguration
|
|
||||||
local function get_configuration()
|
|
||||||
local configuration = getObjectsWithTag(tags.configuration)[1]:getTable("configuration")
|
|
||||||
print_priority = configuration.priority
|
|
||||||
return configuration
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
local function initialize(_, configuration)
|
|
||||||
local builder = getObjectFromGUID(configuration.ui_builder_guid)
|
|
||||||
|
|
||||||
deck_type_button_index = builder:call("create_ui", {
|
|
||||||
target_guid = self:getGUID(),
|
|
||||||
debug_deck_id = configuration.debug_deck_id,
|
|
||||||
checkbox_toggle_callback_name = "on_decktype_checkbox_clicked",
|
|
||||||
build_deck_callback_name = "build_deck"
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onLoad()
|
|
||||||
Wait.frames(function ()
|
|
||||||
local configuration = get_configuration()
|
|
||||||
local taboo = Request.start({configuration.api_uri, configuration.taboo}, function (status)
|
|
||||||
local json = JSON.decode(fix_utf16_string(status.text))
|
|
||||||
for _, taboo in pairs(json) do
|
|
||||||
---@type <string, boolean>
|
|
||||||
local cards = {}
|
|
||||||
|
|
||||||
for _, card in pairs(JSON.decode(taboo.cards)) do
|
|
||||||
cards[card.code] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
taboo_list[taboo.id] = {
|
|
||||||
date = taboo.date_start,
|
|
||||||
cards = cards
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return true, nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
taboo:with(initialize, configuration)
|
|
||||||
end, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param status WebRequestStatus
|
|
||||||
---@param number number
|
|
||||||
---@param is_bonded boolean
|
|
||||||
---@return boolean, ArkhamImportCard
|
|
||||||
local function on_card_request(status, number, is_bonded)
|
|
||||||
local text = fix_utf16_string(status.text)
|
|
||||||
|
|
||||||
---@type ArkhamImportCard
|
|
||||||
local card = JSON.decode(text)
|
|
||||||
card.count = number
|
|
||||||
card.is_bonded = is_bonded
|
|
||||||
|
|
||||||
return true, card
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
---@param card_code string
|
|
||||||
---@param count number
|
|
||||||
---@param is_bonded boolean
|
|
||||||
---@return Request
|
|
||||||
local function add_card(configuration, card_code, count, is_bonded)
|
|
||||||
local api, card_path = configuration.api_uri, configuration.cards
|
|
||||||
local request = Request.start({api, card_path, card_code}, on_card_request, nil, count, is_bonded)
|
|
||||||
return request
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param source TTSObject
|
|
||||||
---@param count number
|
|
||||||
---@param zones ArkhamImportZone[]
|
|
||||||
---@param keep_card boolean
|
|
||||||
---@return fun(card: TTSObject)
|
|
||||||
local function position_card(source, count, zones, keep_card)
|
|
||||||
---@param card TTSObject
|
|
||||||
return function (card)
|
|
||||||
|
|
||||||
for n = 1, count do
|
|
||||||
local zone = zones[n]
|
|
||||||
|
|
||||||
local destination = zone.is_absolute and zone.position or self:positionToWorld(zone.position)
|
|
||||||
local rotation = self:getRotation() + Vector(0, 0, zone.is_facedown and 180 or 0)
|
|
||||||
card:clone {
|
|
||||||
position = destination,
|
|
||||||
rotation = rotation
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if keep_card then source:putObject(card) else card:destruct() end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param source TTSObject
|
|
||||||
---@param target_name string
|
|
||||||
---@param target_subname string
|
|
||||||
---@param count number
|
|
||||||
---@param zone ArkhamImportZone[]
|
|
||||||
local function process_card(source, target_name, target_subname, count, zone)
|
|
||||||
for _, card in ipairs(source:getObjects()) do
|
|
||||||
if (card.name == target_name and (not target_subname or card.description==target_subname)) then
|
|
||||||
source:takeObject {
|
|
||||||
position = {0, 1.5, 0},
|
|
||||||
index = card.index,
|
|
||||||
smooth = false,
|
|
||||||
callback_function = position_card(source, count, zone, true)
|
|
||||||
}
|
|
||||||
debug_print(table.concat({ "Added", count, "of", target_name}, " "), Priority.DEBUG)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
debug_print(table.concat({ "Card not found:", target_name}, " "), Priority.WARNING)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param source TTSObject
|
|
||||||
---@param zones ArkhamImportZone[]
|
|
||||||
local function random_weakness(source, zones)
|
|
||||||
source:shuffle()
|
|
||||||
|
|
||||||
local card = source:takeObject {
|
|
||||||
position = {0, 1.5, 0},
|
|
||||||
index = 0,
|
|
||||||
smooth = false,
|
|
||||||
callback_function = position_card(source, 1, zones, false),
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcastToAll("Drew random basic weakness: " .. card:getName())
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
---@param card_id string
|
|
||||||
---@param used_bindings table<string, boolean>
|
|
||||||
---@param requests Request[]
|
|
||||||
local function process_bindings(configuration, card_id, used_bindings, requests)
|
|
||||||
local bondedCards = configuration.bonded_cards[card_id]
|
|
||||||
|
|
||||||
if not bondedCards then return end
|
|
||||||
|
|
||||||
if bondedCards.code then bondedCards = {bondedCards} end
|
|
||||||
|
|
||||||
for _, bond in ipairs(bondedCards) do
|
|
||||||
if not used_bindings[bond.code] then
|
|
||||||
used_bindings[bond.code] = true
|
|
||||||
local result = add_card(configuration, bond.code, bond.count, true)
|
|
||||||
|
|
||||||
table.insert(requests, result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
---@param slots table<string, number>
|
|
||||||
---@return Request[]
|
|
||||||
local function load_cards(configuration, slots)
|
|
||||||
---@type Request[]
|
|
||||||
local requests = {}
|
|
||||||
|
|
||||||
---@type <string, boolean>
|
|
||||||
local used_bindings = {} -- Bonded cards that we've already processed
|
|
||||||
for card_id, number in pairs(slots) do
|
|
||||||
table.insert(requests, add_card(configuration, card_id, number, false))
|
|
||||||
|
|
||||||
process_bindings(configuration, card_id, used_bindings, requests)
|
|
||||||
end
|
|
||||||
|
|
||||||
return requests
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type string[]
|
|
||||||
local parallel_component = {"", " (Parallel Back)", " (Parallel Front)", " (Parallel)"}
|
|
||||||
|
|
||||||
---@param discriminators table<string, string>
|
|
||||||
---@param card ArkhamImportCard
|
|
||||||
---@param taboo ArkhamImportTaboo
|
|
||||||
---@param meta table<string, any>
|
|
||||||
---@return string, string|nil
|
|
||||||
local function get_card_selector(discriminators, card, taboo, meta)
|
|
||||||
local discriminator = discriminators[card.code]
|
|
||||||
|
|
||||||
if card.type_code == "investigator" then
|
|
||||||
local parallel = (meta.alternate_front and 2 or 0) + (meta.alternate_back and 1 or 0)
|
|
||||||
|
|
||||||
return table.concat {card.real_name, parallel_component[parallel]}, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local xp_component = ""
|
|
||||||
if ((tonumber(card.xp) or 0) > 0) then
|
|
||||||
xp_component = table.concat {" (", card.xp, ")"}
|
|
||||||
end
|
|
||||||
|
|
||||||
local taboo_component = ""
|
|
||||||
local cards = taboo.cards or {}
|
|
||||||
if (cards[card.code]) then
|
|
||||||
taboo_component = " (Taboo)"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local target_name = table.concat({ card.real_name, xp_component, taboo_component })
|
|
||||||
local target_subname = discriminator or card.subname
|
|
||||||
|
|
||||||
return target_name, target_subname
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param zone string
|
|
||||||
---@param count number
|
|
||||||
---@return string[]
|
|
||||||
local function fill_zone(zone, count)
|
|
||||||
local result = {}
|
|
||||||
for n=1,count do
|
|
||||||
result[n] = zone
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param card ArkhamImportCard
|
|
||||||
---@param zone string[]
|
|
||||||
---@param override string[]
|
|
||||||
---@return string[]
|
|
||||||
local function get_zone_id(card, zone, override)
|
|
||||||
local result = {}
|
|
||||||
for n=1,card.count do
|
|
||||||
result[n] = zone[n]
|
|
||||||
or override[n]
|
|
||||||
or (card.is_bonded and "bonded")
|
|
||||||
or (card.permanent and "permanent")
|
|
||||||
or (card.subtype_name and card.subtype_name:find("Weakness") and "weakness")
|
|
||||||
or (card.type_code == "investigator" and "investigator")
|
|
||||||
or "default"
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param cards ArkhamImportCard[]
|
|
||||||
---@param deck ArkhamImportDeck
|
|
||||||
---@param command_manager TTSObject
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
local function on_cards_ready(cards, deck, command_manager, configuration)
|
|
||||||
local card_bag = getObjectFromGUID(configuration.card_bag_guid)
|
|
||||||
local weakness_bag = getObjectFromGUID(configuration.weaknesses_bag_guid)
|
|
||||||
local investigator_bag = getObjectFromGUID(configuration.investigator_bag_guid)
|
|
||||||
local minicard_bag = getObjectFromGUID(configuration.minicard_bag_guid)
|
|
||||||
|
|
||||||
local taboo = taboo_list[deck.taboo_id] or {}
|
|
||||||
|
|
||||||
local meta = deck.meta and JSON.decode(deck.meta) or {}
|
|
||||||
|
|
||||||
for _, card in ipairs(cards) do
|
|
||||||
---@type ArkhamImport_Command_HandlerArguments
|
|
||||||
local parameters = {
|
|
||||||
configuration = configuration,
|
|
||||||
source_guid = self:getGUID(),
|
|
||||||
zone = {},
|
|
||||||
card = card,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@type ArkhamImport_CommandManager_HandlerResults
|
|
||||||
local command_result = command_manager:call("handle", parameters)
|
|
||||||
|
|
||||||
if not command_result.is_successful then
|
|
||||||
debug_print(command_result.error_message, Priority.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local card = command_result.card
|
|
||||||
|
|
||||||
if not command_result.handled then
|
|
||||||
local target_name, target_subname = get_card_selector(configuration.discriminators, card, taboo, meta)
|
|
||||||
local override = configuration.default_zone_overrides[card.code]
|
|
||||||
|
|
||||||
if type(override)=="string" then override = fill_zone(override, card.count) end
|
|
||||||
|
|
||||||
local zone = get_zone_id(card, command_result.zone, configuration.default_zone_overrides[card.code] or {})
|
|
||||||
|
|
||||||
local spawn_zones = {}
|
|
||||||
|
|
||||||
local zones = configuration.zones
|
|
||||||
for index, zone in ipairs(zone) do
|
|
||||||
spawn_zones[index] = zones[zone]
|
|
||||||
end
|
|
||||||
|
|
||||||
if card.real_name == "Random Basic Weakness" then
|
|
||||||
random_weakness(weakness_bag, spawn_zones)
|
|
||||||
elseif card.type_code == "investigator" then
|
|
||||||
process_card(investigator_bag, target_name, nil, card.count, spawn_zones)
|
|
||||||
process_card(minicard_bag, card.real_name, nil, card.count, spawn_zones)
|
|
||||||
else
|
|
||||||
process_card(card_bag, target_name, target_subname, card.count, spawn_zones)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param deck ArkhamImportDeck
|
|
||||||
---@param configuration ArkhamImportConfiguration
|
|
||||||
local function on_deck_result(deck, configuration)
|
|
||||||
debug_print(table.concat({ "Found decklist: ", deck.name}), Priority.INFO)
|
|
||||||
|
|
||||||
debug_print(table.concat({"-", deck.name, "-"}), Priority.DEBUG)
|
|
||||||
for k,v in pairs(deck) do
|
|
||||||
if type(v)=="table" then
|
|
||||||
debug_print(table.concat {k, ": <table>"}, Priority.DEBUG)
|
|
||||||
else
|
|
||||||
debug_print(table.concat {k, ": ", tostring(v)}, Priority.DEBUG)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
debug_print("", Priority.DEBUG)
|
|
||||||
|
|
||||||
local investigator_id = deck.investigator_code
|
|
||||||
|
|
||||||
local slots = deck.slots
|
|
||||||
slots[investigator_id] = 1
|
|
||||||
|
|
||||||
---@type ArkhamImportCard[]
|
|
||||||
local requests = load_cards(configuration, deck.slots)
|
|
||||||
|
|
||||||
local command_manager = getObjectFromGUID(configuration.command_manager_guid)
|
|
||||||
|
|
||||||
---@type ArkhamImport_CommandManager_InitializationArguments
|
|
||||||
local parameters = {
|
|
||||||
configuration = configuration,
|
|
||||||
description = deck.description_md,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@type ArkhamImport_CommandManager_InitializationResults
|
|
||||||
local results = command_manager:call("initialize", parameters)
|
|
||||||
|
|
||||||
if not results.is_successful then
|
|
||||||
debug_print(results.error_message, Priority.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
Request.with_all(requests, on_cards_ready, nil, deck, command_manager, results.configuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
function build_deck()
|
|
||||||
local configuration = get_configuration()
|
|
||||||
local deck_id = self:getInputs()[1].value
|
|
||||||
local deck_uri = { configuration.api_uri, is_private_deck and configuration.private_deck or configuration.public_deck, deck_id }
|
|
||||||
|
|
||||||
local deck = Request.start(deck_uri, function (status)
|
|
||||||
if string.find(status.text, "<!DOCTYPE html>") then
|
|
||||||
return false, table.concat({ "Private deck ", deck_id, " is not shared"})
|
|
||||||
end
|
|
||||||
|
|
||||||
local json = JSON.decode(status.text)
|
|
||||||
|
|
||||||
if not json then
|
|
||||||
return false, "Deck not found!"
|
|
||||||
end
|
|
||||||
|
|
||||||
return true, JSON.decode(status.text)
|
|
||||||
end)
|
|
||||||
|
|
||||||
deck:with(on_deck_result, configuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type Request
|
|
||||||
Request = {
|
|
||||||
is_done = false,
|
|
||||||
is_successful = false
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Creates a new instance of a Request. Should not be directly called. Instead use Request.start and Request.deferred.
|
|
||||||
---@param uri string
|
|
||||||
---@param configure fun(request: Request, status: WebRequestStatus)
|
|
||||||
---@return Request
|
|
||||||
function Request:new(uri, configure)
|
|
||||||
local this = {}
|
|
||||||
|
|
||||||
setmetatable(this, self)
|
|
||||||
self.__index = self
|
|
||||||
|
|
||||||
if type(uri)=="table" then
|
|
||||||
uri = table.concat(uri, "/")
|
|
||||||
end
|
|
||||||
|
|
||||||
this.uri = uri
|
|
||||||
|
|
||||||
WebRequest.get(uri, function(status)
|
|
||||||
configure(this, status)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return this
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Creates a new request. on_success should set the request's is_done, is_successful, and content variables.
|
|
||||||
--- Deferred should be used when you don't want to set is_done immediately (such as if you want to wait for another request to finish)
|
|
||||||
---@param uri string
|
|
||||||
---@param on_success fun(request: Request, status: WebRequestStatus, vararg any)
|
|
||||||
---@param on_error fun(status: WebRequestStatus)|nil
|
|
||||||
---@vararg any[]
|
|
||||||
---@return Request
|
|
||||||
function Request.deferred(uri, on_success, on_error, ...)
|
|
||||||
local parameters = table.pack(...)
|
|
||||||
return Request:new(uri, function (request, status)
|
|
||||||
if (status.is_done) then
|
|
||||||
if (status.is_error) then
|
|
||||||
request.error_message = on_error and on_error(status, table.unpack(parameters)) or status.error
|
|
||||||
request.is_successful = false
|
|
||||||
request.is_done = true
|
|
||||||
else
|
|
||||||
on_success(request, status)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Creates a new request. on_success should return weather the resultant data is as expected, and the processed content of the request.
|
|
||||||
---@param uri string
|
|
||||||
---@param on_success fun(status: WebRequestStatus, vararg any): boolean, any
|
|
||||||
---@param on_error nil|fun(status: WebRequestStatus, vararg any): string
|
|
||||||
---@vararg any[]
|
|
||||||
---@return Request
|
|
||||||
function Request.start(uri, on_success, on_error, ...)
|
|
||||||
local parameters = table.pack(...)
|
|
||||||
return Request.deferred(uri, function(request, status)
|
|
||||||
local result, message = on_success(status, table.unpack(parameters))
|
|
||||||
if not result then request.error_message = message else request.content = message end
|
|
||||||
request.is_successful = result
|
|
||||||
request.is_done = true
|
|
||||||
end, on_error, table.unpack(parameters))
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param requests Request[]
|
|
||||||
---@param on_success fun(content: any[], vararg any[])
|
|
||||||
---@param on_error fun(requests: Request[], vararg any[])|nil
|
|
||||||
---@vararg any
|
|
||||||
function Request.with_all(requests, on_success, on_error, ...)
|
|
||||||
local parameters = table.pack(...)
|
|
||||||
|
|
||||||
Wait.condition(function ()
|
|
||||||
---@type any[]
|
|
||||||
local results = {}
|
|
||||||
|
|
||||||
---@type Request[]
|
|
||||||
local errors = {}
|
|
||||||
|
|
||||||
for _, request in ipairs(requests) do
|
|
||||||
if request.is_successful then
|
|
||||||
table.insert(results, request.content)
|
|
||||||
else
|
|
||||||
table.insert(errors, request)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (#errors<=0) then
|
|
||||||
on_success(results, table.unpack(parameters))
|
|
||||||
elseif on_error ==nil then
|
|
||||||
for _, request in ipairs(errors) do
|
|
||||||
debug_print(table.concat({ "[ERROR]", request.uri, ":", request.error_message }), Priority.ERROR)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
on_error(requests, table.unpack(parameters))
|
|
||||||
end
|
|
||||||
end, function ()
|
|
||||||
for _, request in ipairs(requests) do
|
|
||||||
if not request.is_done then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param callback fun(content: any, vararg any)
|
|
||||||
function Request:with(callback, ...)
|
|
||||||
local arguments = table.pack(...)
|
|
||||||
Wait.condition(function ()
|
|
||||||
if self.is_successful then
|
|
||||||
callback(self.content, table.unpack(arguments))
|
|
||||||
end
|
|
||||||
end, function () return self.is_done
|
|
||||||
end)
|
|
||||||
end
|
|
@ -1,70 +0,0 @@
|
|||||||
---
|
|
||||||
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
|
||||||
--- Created by Whimsical.
|
|
||||||
--- DateTime: 2021-08-24 6:02 p.m.
|
|
||||||
---
|
|
||||||
|
|
||||||
command_name = "move"
|
|
||||||
|
|
||||||
---@type ArkhamImport_Command_RunDirectives
|
|
||||||
runOn = {
|
|
||||||
instructions = true,
|
|
||||||
handlers = true
|
|
||||||
}
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_Command_DescriptionInstructionArguments
|
|
||||||
---@return ArkhamImport_Command_DescriptionInstructionResults
|
|
||||||
function do_instruction(parameters)
|
|
||||||
local args = parameters.arguments
|
|
||||||
|
|
||||||
if (#args~=2 and #args~=3) then
|
|
||||||
return { is_successful = false, error_message = "Move Command requires 2 or 3 arguments. " .. #args .. " were provided." }
|
|
||||||
end
|
|
||||||
|
|
||||||
local card_id = args[1]
|
|
||||||
local new_zone = args[2]
|
|
||||||
local count = tonumber(args[3]) or 3
|
|
||||||
|
|
||||||
if not parameters.configuration.zones[new_zone] then
|
|
||||||
return { is_successful = false, error_message = "Move Command: Zone \"" .. new_zone .. "\" was not found." }
|
|
||||||
end
|
|
||||||
|
|
||||||
local state = parameters.command_state["move"]
|
|
||||||
|
|
||||||
if not state then
|
|
||||||
state = {}
|
|
||||||
parameters.command_state["move"] = state
|
|
||||||
end
|
|
||||||
|
|
||||||
local card_data = state[card_id]
|
|
||||||
|
|
||||||
if not card_data then
|
|
||||||
card_data = {
|
|
||||||
zone = {},
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
state[card_id] = card_data
|
|
||||||
end
|
|
||||||
|
|
||||||
local zone = card_data.zone
|
|
||||||
local offset = card_data.offset
|
|
||||||
|
|
||||||
for index=offset,offset+count do
|
|
||||||
zone[index] = new_zone
|
|
||||||
end
|
|
||||||
|
|
||||||
return { command_state = parameters.command_state, is_successful = true }
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_Command_HandlerArguments
|
|
||||||
---@return ArkhamImport_Command_HandlerResults
|
|
||||||
function handle_card(parameters)
|
|
||||||
local state = parameters.command_state["move"] or {}
|
|
||||||
|
|
||||||
local card_data = state[parameters.card.code]
|
|
||||||
|
|
||||||
if not card_data then return { is_successful = true} end
|
|
||||||
|
|
||||||
return { zone = card_data.zone, is_successful = true }
|
|
||||||
end
|
|
@ -1,91 +0,0 @@
|
|||||||
---
|
|
||||||
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
|
||||||
--- Created by Whimsical.
|
|
||||||
--- DateTime: 2021-08-24 6:11 p.m.
|
|
||||||
---
|
|
||||||
|
|
||||||
command_name = "proxy-card"
|
|
||||||
|
|
||||||
---@type ArkhamImport_Command_RunDirectives
|
|
||||||
runOn = {
|
|
||||||
instructions = true,
|
|
||||||
handlers = true
|
|
||||||
}
|
|
||||||
|
|
||||||
local back_image_default = "https://images-ext-2.discordapp.net/external/QY_dmo_UnAHEi1pgWwaRr1-HSB8AtrAv0W74Mh_Z6vg/https/i.imgur.com/EcbhVuh.jpg"
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_Command_DescriptionInstructionArguments
|
|
||||||
---@return ArkhamImport_Command_DescriptionInstructionResults
|
|
||||||
function do_instruction(parameters)
|
|
||||||
local args = parameters.arguments
|
|
||||||
if (#args<4 or #args>6) then
|
|
||||||
return {
|
|
||||||
is_successful = false,
|
|
||||||
error_message = "Move Command requires between 4 or 6 arguments. " .. #args .. " were provided."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if not parameters.command_state["proxy-card"] then
|
|
||||||
parameters.command_state["proxy-card"] = {}
|
|
||||||
parameters.command_state["proxy-card-offset"] = 0.1
|
|
||||||
end
|
|
||||||
|
|
||||||
parameters.command_state["proxy-card"][args[1]] = {
|
|
||||||
name = args[2],
|
|
||||||
subtitle = args[3],
|
|
||||||
image_uri = args[4],
|
|
||||||
zone = args[5] or "default",
|
|
||||||
back_image_uri = args[6] or back_image_default
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
command_state = parameters.command_state,
|
|
||||||
is_successful = true
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_Command_HandlerArguments
|
|
||||||
---@return ArkhamImport_Command_HandlerResults
|
|
||||||
function handle_card(parameters)
|
|
||||||
local state = parameters.command_state["proxy-card"] or {}
|
|
||||||
|
|
||||||
local card_data = state[parameters.card.code]
|
|
||||||
|
|
||||||
if not card_data then return { is_successful = true } end
|
|
||||||
|
|
||||||
local offset = parameters.command_state["proxy-card-offset"]
|
|
||||||
parameters.command_state["proxy-card-offset"] = offset + 0.1
|
|
||||||
|
|
||||||
local zone = parameters.configuration.zones[card_data.zone]
|
|
||||||
|
|
||||||
if not zone then
|
|
||||||
return {
|
|
||||||
is_successful = false,
|
|
||||||
error_message = "Proxy Card [" .. tostring(parameters.card.code) .. "]: Zone \"" .. tostring(card_data.zone) .. "\" was not found."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local source = getObjectFromGUID(parameters.source_guid)
|
|
||||||
local position = zone.is_absolute and zone.position or source:positionToWorld(zone.position)
|
|
||||||
|
|
||||||
for _=1, parameters.card.count do
|
|
||||||
local new = spawnObject {
|
|
||||||
type = "CardCustom",
|
|
||||||
position = position + Vector(0, offset, 0),
|
|
||||||
rotation = source:getRotation() + Vector(0, 0, zone.is_facedown and 180 or 0),
|
|
||||||
---@param card TTSObject
|
|
||||||
callback_function = function (card)
|
|
||||||
card:setName(card_data.name)
|
|
||||||
card:setDescription(card_data.subtitle)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
new:setCustomObject {
|
|
||||||
type = 0,
|
|
||||||
face = card_data.image_uri,
|
|
||||||
back = card_data.back_image_uri
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return { handled = true, is_successful = true }
|
|
||||||
end
|
|
@ -1,96 +0,0 @@
|
|||||||
---
|
|
||||||
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
|
||||||
--- Created by Whimsical.
|
|
||||||
--- DateTime: 2021-08-25 2:32 p.m.
|
|
||||||
---
|
|
||||||
|
|
||||||
command_name = "proxy-investigator"
|
|
||||||
|
|
||||||
---@type ArkhamImport_Command_RunDirectives
|
|
||||||
runOn = {
|
|
||||||
instructions = true,
|
|
||||||
handlers = true
|
|
||||||
}
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_Command_DescriptionInstructionArguments
|
|
||||||
---@return ArkhamImport_Command_DescriptionInstructionResults
|
|
||||||
function do_instruction(parameters)
|
|
||||||
local args = parameters.arguments
|
|
||||||
|
|
||||||
if (#args~=6 and #args~=7) then
|
|
||||||
return {
|
|
||||||
is_successful = false,
|
|
||||||
error_message = "Proxy Investigator command requires either 7 or 8 arguments. " .. #args .. " were provided."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
parameters.command_state["proxy-investigator"] = {
|
|
||||||
name = args[1],
|
|
||||||
subtitle = args[2],
|
|
||||||
front_uri = args[3],
|
|
||||||
back_uri = args[4],
|
|
||||||
mini_front_uri = args[5],
|
|
||||||
mini_back_uri = args[6],
|
|
||||||
zone = args[7] or "investigator"
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
command_state = parameters.command_state,
|
|
||||||
is_successful = true
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param source TTSObject
|
|
||||||
---@param name string
|
|
||||||
---@param subtitle string
|
|
||||||
---@param offset number
|
|
||||||
---@param zone ArkhamImportZone
|
|
||||||
---@param front string
|
|
||||||
---@param back string
|
|
||||||
---@param use_minicard_scaling boolean
|
|
||||||
local function create_card(source, name, subtitle, offset, zone, front, back, use_minicard_scaling)
|
|
||||||
local position = zone.is_absolute and zone.position or source:positionToWorld(zone.position)
|
|
||||||
|
|
||||||
local card = spawnObject {
|
|
||||||
type = "CardCustom",
|
|
||||||
position = position + Vector(0, offset, 0),
|
|
||||||
rotation = source:getRotation() + Vector(0, 0, zone.is_facedown and 180 or 0),
|
|
||||||
scale = use_minicard_scaling and Vector(0.6, 1, 0.6) or Vector(1,1,1),
|
|
||||||
callback_function = function (card) card:setName(name) card:setDescription(subtitle) end
|
|
||||||
}
|
|
||||||
|
|
||||||
card:setCustomObject {
|
|
||||||
type = 0,
|
|
||||||
face = front,
|
|
||||||
back = back
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param parameters ArkhamImport_Command_HandlerArguments
|
|
||||||
---@return ArkhamImport_Command_HandlerResults
|
|
||||||
function handle_card(parameters)
|
|
||||||
if parameters.card.type_code ~= "investigator" then return {is_successful = true } end
|
|
||||||
|
|
||||||
local card_data = parameters.command_state["proxy-investigator"] or {}
|
|
||||||
|
|
||||||
if not card_data then return { is_successful = true } end
|
|
||||||
|
|
||||||
local zone = parameters.configuration.zones[card_data.zone]
|
|
||||||
|
|
||||||
if not zone then
|
|
||||||
return {
|
|
||||||
is_successful = false,
|
|
||||||
command_state = parameters.command_state,
|
|
||||||
error_message = "Proxy Investigator [" .. tostring(parameters.card.code) .. "]: Zone \"" .. tostring(card_data.zone) .. "\" was not found."
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local source = getObjectFromGUID(parameters.source_guid)
|
|
||||||
|
|
||||||
for _=1, parameters.card.count do
|
|
||||||
create_card(source, card_data.name, card_data.subtitle, 10, zone, card_data.front_uri, card_data.back_uri, false)
|
|
||||||
create_card(source, card_data.name, card_data.subtitle, 20, zone, card_data.mini_front_uri, card_data.mini_back_uri, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { handled = true, is_successful = true}
|
|
||||||
end
|
|
@ -16,6 +16,10 @@ end
|
|||||||
function buttonClick_draw()
|
function buttonClick_draw()
|
||||||
local allCardsBag = getObjectFromGUID(allCardsBagGuid)
|
local allCardsBag = getObjectFromGUID(allCardsBagGuid)
|
||||||
local weaknessId = allCardsBag.call("getRandomWeaknessId")
|
local weaknessId = allCardsBag.call("getRandomWeaknessId")
|
||||||
|
if (weaknessId == nil) then
|
||||||
|
broadcastToAll("All basic weaknesses are in play!", {0.9, 0.2, 0.2})
|
||||||
|
return
|
||||||
|
end
|
||||||
local card = allCardsBag.call("getCardById", { id = weaknessId })
|
local card = allCardsBag.call("getCardById", { id = weaknessId })
|
||||||
spawnObjectData({
|
spawnObjectData({
|
||||||
data = card.data,
|
data = card.data,
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
-- Sets up and returns coordinates for all possible spawn zones. Because Lua
|
|
||||||
-- assigns tables by reference and there is no built-in function to copy a
|
|
||||||
-- table this is relatively brute force.
|
|
||||||
--
|
|
||||||
-- Positions are all relative to the player mat, and most are consistent. The
|
|
||||||
-- exception are the SetAside# zones, which are placed to the left of the mat
|
|
||||||
-- for White/Green, and the right of the mat for Orange/Red.
|
|
||||||
--
|
|
||||||
-- Valid Zones:
|
|
||||||
-- Investigator: Investigator card area.
|
|
||||||
-- Minicard: Placement for the investigator's minicard. This is just above the
|
|
||||||
-- player mat, vertically in line with the investigator card area.
|
|
||||||
-- Deck, Discard: Standard locations for the deck and discard piles.
|
|
||||||
-- BlankTop, Tarot, Hand1, Hand2, Ally, BlankBottom, Accessory, Arcane1,
|
|
||||||
-- Arcane2, Body: Asset slot positions on the player mat.
|
|
||||||
-- Threat[1-4]: Threat area slots. Threat[1-3] correspond to the named threat
|
|
||||||
-- area slots, and Threat4 is the blank threat area slot.
|
|
||||||
-- SetAside[1-6]: Areas outside the player mat, to the right for Red/Orange and
|
|
||||||
-- the left for White/Green. SetAside[1-3] are a column closest to the
|
|
||||||
-- player mat, with 1 at the top of the mat and 3 at the bottom.
|
|
||||||
-- SetAside[4-6] are a column farther away from the mat, with 4 at the top
|
|
||||||
-- and 6 at the bottom.
|
|
||||||
do
|
|
||||||
local playerMatGuids = { }
|
|
||||||
playerMatGuids["Red"] = "0840d5"
|
|
||||||
playerMatGuids["Orange"] = "bd0ff4"
|
|
||||||
playerMatGuids["White"] = "8b081b"
|
|
||||||
playerMatGuids["Green"] = "383d8b"
|
|
||||||
|
|
||||||
local Zones = { }
|
|
||||||
|
|
||||||
local commonZones = { }
|
|
||||||
commonZones["Investigator"] = {-0.7833852, 0, 0.0001343352}
|
|
||||||
commonZones["Minicard"] = {-0.7833852, 0, -1.187242}
|
|
||||||
commonZones["Deck"] = {-1.414127, 0, -0.006668129}
|
|
||||||
commonZones["Discard"] = {-1.422189,0,0.643440}
|
|
||||||
commonZones["Ally"] = {-0.236577,0,0.023543}
|
|
||||||
commonZones["Body"] = {-0.257249,0,0.553170}
|
|
||||||
commonZones["Hand1"] = {0.600493,0,0.037291}
|
|
||||||
commonZones["Hand2"] = {0.206867,0,0.025540}
|
|
||||||
commonZones["Arcane1"] = {0.585817,0,0.567969}
|
|
||||||
commonZones["Arcane2"] = {0.197267,0,0.562296}
|
|
||||||
commonZones["Tarot"] = {0.980616,0,0.047756}
|
|
||||||
commonZones["Accessory"] = {0.976689,0,0.569344}
|
|
||||||
commonZones["BlankTop"] = {1.364696,0,0.062552}
|
|
||||||
commonZones["BlankBottom"] = {1.349999,0,0.585419}
|
|
||||||
commonZones["Threat1"] = {-0.835423,0,-0.633271}
|
|
||||||
commonZones["Threat2"] = {-0.384615,0,-0.633493}
|
|
||||||
commonZones["Threat3"] = {0.071090,0,-0.633717}
|
|
||||||
commonZones["Threat4"] = {0.520816,0,-0.633936}
|
|
||||||
|
|
||||||
Zones["White"] = { }
|
|
||||||
Zones["White"]["Investigator"] = commonZones["Investigator"]
|
|
||||||
Zones["White"]["Minicard"] = commonZones["Minicard"]
|
|
||||||
Zones["White"]["Deck"] = commonZones["Deck"]
|
|
||||||
Zones["White"]["Discard"] = commonZones["Discard"]
|
|
||||||
Zones["White"]["Ally"] = commonZones["Ally"]
|
|
||||||
Zones["White"]["Body"] = commonZones["Body"]
|
|
||||||
Zones["White"]["Hand1"] = commonZones["Hand1"]
|
|
||||||
Zones["White"]["Hand2"] = commonZones["Hand2"]
|
|
||||||
Zones["White"]["Arcane1"] = commonZones["Arcane1"]
|
|
||||||
Zones["White"]["Arcane2"] = commonZones["Arcane2"]
|
|
||||||
Zones["White"]["Tarot"] = commonZones["Tarot"]
|
|
||||||
Zones["White"]["Accessory"] = commonZones["Accessory"]
|
|
||||||
Zones["White"]["BlankTop"] = commonZones["BlankTop"]
|
|
||||||
Zones["White"]["BlankBottom"] = commonZones["BlankBottom"]
|
|
||||||
Zones["White"]["Threat1"] = commonZones["Threat1"]
|
|
||||||
Zones["White"]["Threat2"] = commonZones["Threat2"]
|
|
||||||
Zones["White"]["Threat3"] = commonZones["Threat3"]
|
|
||||||
Zones["White"]["Threat4"] = commonZones["Threat4"]
|
|
||||||
Zones["White"]["SetAside1"] = {2.004500,0,-0.520315}
|
|
||||||
Zones["White"]["SetAside2"] = {2.004500,0,0.042552}
|
|
||||||
Zones["White"]["SetAside3"] = {2.004500,0,0.605419}
|
|
||||||
Zones["White"]["UnderSetAside3"] = {2.154500,0,0.805419}
|
|
||||||
Zones["White"]["SetAside4"] = {2.434500,0,-0.520315}
|
|
||||||
Zones["White"]["SetAside5"] = {2.434500,0,0.042552}
|
|
||||||
Zones["White"]["SetAside6"] = {2.434500,0,0.605419}
|
|
||||||
Zones["White"]["UnderSetAside6"] = {2.584500,0,0.805419}
|
|
||||||
Zones["Orange"] = { }
|
|
||||||
Zones["Orange"]["Investigator"] = commonZones["Investigator"]
|
|
||||||
Zones["Orange"]["Minicard"] = commonZones["Minicard"]
|
|
||||||
Zones["Orange"]["Deck"] = commonZones["Deck"]
|
|
||||||
Zones["Orange"]["Discard"] = commonZones["Discard"]
|
|
||||||
Zones["Orange"]["Ally"] = commonZones["Ally"]
|
|
||||||
Zones["Orange"]["Body"] = commonZones["Body"]
|
|
||||||
Zones["Orange"]["Hand1"] = commonZones["Hand1"]
|
|
||||||
Zones["Orange"]["Hand2"] = commonZones["Hand2"]
|
|
||||||
Zones["Orange"]["Arcane1"] = commonZones["Arcane1"]
|
|
||||||
Zones["Orange"]["Arcane2"] = commonZones["Arcane2"]
|
|
||||||
Zones["Orange"]["Tarot"] = commonZones["Tarot"]
|
|
||||||
Zones["Orange"]["Accessory"] = commonZones["Accessory"]
|
|
||||||
Zones["Orange"]["BlankTop"] = commonZones["BlankTop"]
|
|
||||||
Zones["Orange"]["BlankBottom"] = commonZones["BlankBottom"]
|
|
||||||
Zones["Orange"]["Threat1"] = commonZones["Threat1"]
|
|
||||||
Zones["Orange"]["Threat2"] = commonZones["Threat2"]
|
|
||||||
Zones["Orange"]["Threat3"] = commonZones["Threat3"]
|
|
||||||
Zones["Orange"]["Threat4"] = commonZones["Threat4"]
|
|
||||||
Zones["Orange"]["SetAside1"] = {-2.004500,0,-0.520315}
|
|
||||||
Zones["Orange"]["SetAside2"] = {-2.004500,0,0.042552}
|
|
||||||
Zones["Orange"]["SetAside3"] = {-2.004500,0,0.605419}
|
|
||||||
Zones["Orange"]["UnderSetAside3"] = {-2.154500,0,0.80419}
|
|
||||||
Zones["Orange"]["SetAside4"] = {-2.434500,0,-0.520315}
|
|
||||||
Zones["Orange"]["SetAside5"] = {-2.434500,0,0.042552}
|
|
||||||
Zones["Orange"]["SetAside6"] = {-2.434500,0,0.605419}
|
|
||||||
Zones["Orange"]["UnderSetAside6"] = {-2.584500,0,0.80419}
|
|
||||||
|
|
||||||
-- Green positions are the same as White, and Red the same as orange, so we
|
|
||||||
-- can just point these at the White/Orange definitions
|
|
||||||
Zones["Red"] = Zones["Orange"]
|
|
||||||
Zones["Green"] = Zones["White"]
|
|
||||||
|
|
||||||
-- Returns the zone name where the specified card should be placed, based on
|
|
||||||
-- its metadata.
|
|
||||||
-- Param cardMetadata: Table of card metadata. Metadata fields type and
|
|
||||||
-- permanent are required; all others are optional.
|
|
||||||
-- Return: Zone name such as "Deck", "SetAside1", etc. See Zones object
|
|
||||||
-- documentation for a list of valid zones.
|
|
||||||
function Zones.getDefaultCardZone(cardMetadata)
|
|
||||||
if (cardMetadata.type == "Investigator") then
|
|
||||||
return "Investigator"
|
|
||||||
elseif (cardMetadata.type == "Minicard") then
|
|
||||||
return "Minicard"
|
|
||||||
elseif (cardMetadata.permanent) then
|
|
||||||
return "SetAside1"
|
|
||||||
elseif (cardMetadata.bonded_to ~= nil) then
|
|
||||||
return "SetAside2"
|
|
||||||
else
|
|
||||||
return "Deck"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Gets the global position for the given zone on the specified player mat.
|
|
||||||
-- Param playerColor: Color name of the player mat to get the zone position
|
|
||||||
-- for (e.g. "Red")
|
|
||||||
-- Param zoneName: Name of the zone to get the position for. See Zones object
|
|
||||||
-- documentation for a list of valid zones.
|
|
||||||
-- Return: Global position table, or nil if an invalid player color or zone
|
|
||||||
-- is specified
|
|
||||||
function Zones.getZonePosition(playerColor, zoneName)
|
|
||||||
if (playerColor ~= "Red"
|
|
||||||
and playerColor ~= "Orange"
|
|
||||||
and playerColor ~= "White"
|
|
||||||
and playerColor ~= "Green") then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return getObjectFromGUID(playerMatGuids[playerColor]).positionToWorld(Zones[playerColor][zoneName])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Return the global rotation for a card on the given player mat, based on its
|
|
||||||
-- metadata.
|
|
||||||
-- Param playerColor: Color name of the player mat to get the rotation
|
|
||||||
-- for (e.g. "Red")
|
|
||||||
-- Param cardMetadata: Table of card metadata. Metadata fields type and
|
|
||||||
-- permanent are required; all others are optional.
|
|
||||||
-- Return: Global rotation vector for the given card. This will include the
|
|
||||||
-- Y rotation to orient the card on the given player mat as well as a
|
|
||||||
-- Z rotation to place the card face up or face down.
|
|
||||||
function Zones.getDefaultCardRotation(playerColor, zone)
|
|
||||||
local deckRotation = getObjectFromGUID(playerMatGuids[playerColor]).getRotation()
|
|
||||||
|
|
||||||
if (zone == "Investigator") then
|
|
||||||
deckRotation = deckRotation + Vector(0, 270, 0)
|
|
||||||
elseif (zone == "Deck") then
|
|
||||||
deckRotation = deckRotation + Vector(0, 0, 180)
|
|
||||||
end
|
|
||||||
|
|
||||||
return deckRotation
|
|
||||||
end
|
|
||||||
|
|
||||||
return Zones
|
|
||||||
end
|
|
@ -0,0 +1,76 @@
|
|||||||
|
-- set true to enable debug logging
|
||||||
|
DEBUG = false
|
||||||
|
|
||||||
|
function log(message)
|
||||||
|
if DEBUG then
|
||||||
|
print(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Known locations and clues. We check this to determine if we should
|
||||||
|
atttempt to spawn clues, first we look for <LOCATION_NAME>_<GUID> and if
|
||||||
|
we find nothing we look for <LOCATION_NAME>
|
||||||
|
format is [location_guid -> clueCount]
|
||||||
|
]]
|
||||||
|
LOCATIONS_DATA_JSON = [[
|
||||||
|
{
|
||||||
|
"San Francisco": {"type": "fixed", "value": 1, "clueSide": "back"},
|
||||||
|
" Arkham": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Buenos Aires": {"type": "fixed", "value": 2, "clueSide": "back"},
|
||||||
|
" London": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Rome": {"type": "perPlayer", "value": 3, "clueSide": "front"},
|
||||||
|
"Istanbul": {"type": "perPlayer", "value": 4, "clueSide": "front"},
|
||||||
|
"Tokyo_123abc": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Tokyo_456efg": {"type": "perPlayer", "value": 4, "clueSide": "back"},
|
||||||
|
"Tokyo": {"type": "fixed", "value": 2, "clueSide": "back"},
|
||||||
|
"Shanghai_123": {"type": "fixed", "value": 12, "clueSide": "front"},
|
||||||
|
"Sydney": {"type": "fixed", "value": 0, "clueSide": "front"}
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
PLAYER_CARD_DATA_JSON = [[
|
||||||
|
{
|
||||||
|
"Tool Belt (0)": {
|
||||||
|
"tokenType": "resource",
|
||||||
|
"tokenCount": 2
|
||||||
|
},
|
||||||
|
"Tool Belt (3)": {
|
||||||
|
"tokenType": "resource",
|
||||||
|
"tokenCount": 4
|
||||||
|
},
|
||||||
|
"Yithian Rifle": {
|
||||||
|
"tokenType": "resource",
|
||||||
|
"tokenCount": 3
|
||||||
|
},
|
||||||
|
"xxx": {
|
||||||
|
"tokenType": "resource",
|
||||||
|
"tokenCount": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
HIDDEN_CARD_DATA = {
|
||||||
|
"Unpleasant Card (Doom)",
|
||||||
|
"Unpleasant Card (Gloom)",
|
||||||
|
"The Case of the Scarlet DOOOOOM!"
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCATIONS_DATA = JSON.decode(LOCATIONS_DATA_JSON)
|
||||||
|
PLAYER_CARD_DATA = JSON.decode(PLAYER_CARD_DATA_JSON)
|
||||||
|
|
||||||
|
function onload(save_state)
|
||||||
|
local playArea = getObjectFromGUID('721ba2')
|
||||||
|
playArea.call("updateLocations", {self.getGUID()})
|
||||||
|
local playerMatWhite = getObjectFromGUID('8b081b')
|
||||||
|
playerMatWhite.call("updatePlayerCards", {self.getGUID()})
|
||||||
|
local playerMatOrange = getObjectFromGUID('bd0ff4')
|
||||||
|
playerMatOrange.call("updatePlayerCards", {self.getGUID()})
|
||||||
|
local playerMatGreen = getObjectFromGUID('383d8b')
|
||||||
|
playerMatGreen.call("updatePlayerCards", {self.getGUID()})
|
||||||
|
local playerMatRed = getObjectFromGUID('0840d5')
|
||||||
|
playerMatRed.call("updatePlayerCards", {self.getGUID()})
|
||||||
|
local dataHelper = getObjectFromGUID('708279')
|
||||||
|
dataHelper.call("updateHiddenCards", {self.getGUID()})
|
||||||
|
end
|
@ -636,6 +636,82 @@ LOCATIONS_DATA_JSON = [[
|
|||||||
"Wine Cellar_b882f3": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
"Wine Cellar_b882f3": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
"Hidden Passageway": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
"Hidden Passageway": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
|
||||||
|
"Frozen Shores": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Treacherous Path": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Precarious Ice Sheet": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Broad Snowdrifts": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Icy Wastes": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Rocky Crags": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Snow Graves": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Icebreaker Landing": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Frigid Cave": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Barrier Camp": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
"Remnants of Lake's Camp": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
"Crystalline Cavern": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
|
||||||
|
"Prison of Memories": {"type": "perPlayer", "value": 3, "clueSide": "front"},
|
||||||
|
"Base Camp": {"type": "perPlayer", "value": 3, "clueSide": "front"},
|
||||||
|
"Deck of the Theodosia": {"type": "perPlayer", "value": 3, "clueSide": "front"},
|
||||||
|
"University Halls": {"type": "perPlayer", "value": 3, "clueSide": "front"},
|
||||||
|
"Hedge Maze": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Deserted Station": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Hedge Maze": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Coastal Waters": {"type": "perPlayer", "value": 4, "clueSide": "front"},
|
||||||
|
"Elder Chamber": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Riverview Theatre": {"type": "perPlayer", "value": 4, "clueSide": "front"},
|
||||||
|
"Standing Stones": {"type": "perPlayer", "value": 4, "clueSide": "front"},
|
||||||
|
"Airfield": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Alaskan Wilds": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Cluttered Dormitory": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Dyer's Classroom": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Infirmary_80c56d": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Dr. Kensler's Office": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Moʻai Statues": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"Ottoman Front": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
"The Black Stone": {"type": "perPlayer", "value": 2, "clueSide": "front"},
|
||||||
|
|
||||||
|
"The Summit": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
"Mountainside": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Mountainside_0dd2ac": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Mountainside_62fb7b": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Mountainside_0a512e": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Mountainside_163ba8": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
|
||||||
|
"Hidden Tunnel": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"City Landscape": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"City Landscape_ec2d80": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"City Landscape_d84841": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
|
||||||
|
|
||||||
|
"Ancient Facility": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Ancient Facility_97d88d": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Ancient Facility_0ff8d1": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Ancient Facility_42f1ad": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Ancient Facility_710850": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Ancient Facility_27771a": {"type": "perPlayer", "value": 0, "clueSide": "back"},
|
||||||
|
"Ancient Facility_f9fc4d": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Ancient Facility_baf524": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Ancient Facility_c70271": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"The Gate of Y'quaa": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Mist-Pylon": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Mist-Pylon_9ca053": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Mist-Pylon_c320b1": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
"Mist-Pylon_cc2b13": {"type": "perPlayer", "value": 4, "clueSide": "back"},
|
||||||
|
|
||||||
|
"River Docks": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Miskatonic University_cf8d9e": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Miskatonic University_b6c3a5": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Miskatonic University_fb6a7c": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Arkham Gazette": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Arkham Advertiser": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"O'Malley's Watch Shop": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
"Tick-Tock Club_e1116a": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Tick-Tock Club": {"type": "perPlayer", "value": 3, "clueSide": "back"},
|
||||||
|
|
||||||
|
"Childhood Home": {"type": "perPlayer", "value": 2, "clueSide": "back"},
|
||||||
|
"Ye Olde Magick Shoppe": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
"Corrigan Industries": {"type": "perPlayer", "value": 1, "clueSide": "back"},
|
||||||
|
|
||||||
"XXXX": {"type": "fixed", "value": 2, "clueSide": "back"},
|
"XXXX": {"type": "fixed", "value": 2, "clueSide": "back"},
|
||||||
"xxx": {"type": "perPlayer", "value": 2, "clueSide": "back"}
|
"xxx": {"type": "perPlayer", "value": 2, "clueSide": "back"}
|
||||||
}
|
}
|
||||||
@ -645,712 +721,12 @@ Player cards with token counts and types
|
|||||||
]]
|
]]
|
||||||
PLAYER_CARD_DATA_JSON = [[
|
PLAYER_CARD_DATA_JSON = [[
|
||||||
{
|
{
|
||||||
"Flashlight": {
|
"xxx": {
|
||||||
"tokenType": "resource",
|
"tokenType": "resource",
|
||||||
"tokenCount": 3
|
"tokenCount": 3
|
||||||
},
|
},
|
||||||
"Shrivelling": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Shrivelling (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Grotesque Statue (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Forbidden Knowledge": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
".45 Automatic": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Shotgun (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Liquid Courage": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Song of the Dead (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Cover Up": {
|
|
||||||
"tokenType": "clue",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Roland's .38 Special": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"First Aid": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Scrying": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".41 Derringer": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Painkillers": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Smoking Pipe": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Clarity of Mind": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Rite of Seeking": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"M1918 BAR (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 8
|
|
||||||
},
|
|
||||||
"Ornate Bow (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 1
|
|
||||||
},
|
|
||||||
".41 Derringer (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Suggestion (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Chicago Typewriter (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Lupara (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"First Aid (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Springfield M1903 (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Springfield M1903 (4) (Taboo)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".32 Colt": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"Venturer": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Lockpicks (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Finn's Trusty .38": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".45 Automatic (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Lightning Gun (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Strange Solution (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Strange Solution (4):Acidic Ichor": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Strange Solution (4):Empowering Elixir": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Arcane Insight (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Archaic Glyphs (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"In the Know (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Rite of Seeking (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Alchemical Transmutation": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Scrying (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Shrivelling (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Mists of R'lyeh": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Mists of R'lyeh (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Colt Vest Pocket": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Old Hunting Rifle (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Thermos": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Feed the Mind (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Seal of the Seventh Sign (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 7
|
|
||||||
},
|
|
||||||
"Flamethrower (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Flamethrower (5) (Taboo)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Pnakotic Manuscripts (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Kerosene (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Shards of the Void (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Try and Try Again (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Arcane Initiate": {
|
|
||||||
"tokenType": "doom",
|
|
||||||
"tokenCount": 1
|
|
||||||
},
|
|
||||||
"Detective's Colt 1911s": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Extra Ammunition (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Rite of Seeking (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Arcane Initiate (3)": {
|
|
||||||
"tokenType": "doom",
|
|
||||||
"tokenCount": 1
|
|
||||||
},
|
|
||||||
"Clarity of Mind (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Fingerprint Kit": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Truth from Fiction": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Enchanted Blade": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Tennessee Sour Mash": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Scroll of Secrets": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Scroll of Secrets (Taboo)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".45 Thompson": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Mr. \"Rook\"": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Mr. \"Rook\" (Taboo)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Scroll of Secrets (3):Seeker": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Scroll of Secrets (3) (Taboo):Seeker": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Scroll of Secrets (3):Mystic": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Scroll of Secrets (3) (Taboo):Mystic": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Enchanted Blade (3):Guardian": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Enchanted Blade (3):Mystic": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
".45 Thompson (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Esoteric Atlas (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Tennessee Sour Mash (3):Rogue": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Tennessee Sour Mash (3):Survivor": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Mk 1 Grenades (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Dayana Esperence (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Pendant of the Queen": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".32 Colt (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"Alchemical Transmutation (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Suggestion (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Gate Box": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Tony's .38 Long Colt": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Gregory Gry": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 9
|
|
||||||
},
|
|
||||||
"Scroll of Prophecies": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Healing Words": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Otherworld Codex (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".35 Winchester": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
".35 Winchester (Taboo)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Old Book of Lore (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Sawed-Off Shotgun (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Mind's Eye (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Colt Vest Pocket (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Mists of R'lyeh (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"The Chthonian Stone (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Flesh Ward": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Physical Training (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Encyclopedia": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Feed the Mind": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Forbidden Tome": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Esoteric Atlas (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"The Necronomicon (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"The Necronomicon (5) (Taboo)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"Mauser C96": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Liquid Courage (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Mauser C96 (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Beretta M1918 (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Scrying Mirror": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Azure Flame": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Clairvoyance": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Ineffable Truth": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Grotesque Statue (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Azure Flame (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Clairvoyance (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Ineffable Truth (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Arcane Studies (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Azure Flame (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Clairvoyance (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Ineffable Truth (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".18 Derringer": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Grimm's Fairy Tales": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Old Keyring": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
".18 Derringer (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Chainsaw (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Becky": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Book of Psalms": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Cryptographic Cipher": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
".25 Automatic": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Obfuscation": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Eldritch Sophist": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Armageddon": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Eye of Chaos": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Shroud of Shadows": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Guided by the Unseen (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Eye of Chaos (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Shroud of Shadows (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Armageddon (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Hyperawareness (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Hard Knocks (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Dig Deep (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
".25 Automatic (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Shrine of the Moirai (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Archive of Conduits": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Archive of Conduits (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Eon Chart (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Eon Chart (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Brand of Cthugha (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"Brand of Cthugha (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 9
|
|
||||||
},
|
|
||||||
"True Magick (5)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 1
|
|
||||||
},
|
|
||||||
"Healing Words (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Close the Circle (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 1
|
|
||||||
},
|
|
||||||
"Bangle of Jinxes (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 1
|
|
||||||
},
|
|
||||||
"Jury-Rig": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Bandages": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Schoffner's Catalogue": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 5
|
|
||||||
},
|
|
||||||
"Antiquary (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Crafty (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Bruiser (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Sleuth (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Prophetic (3)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 2
|
|
||||||
},
|
|
||||||
"Earthly Serenity (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"Earthly Serenity (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Enchanted Bow (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Blur (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Blur (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Professor William Webb (2)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Professor William Webb": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 3
|
|
||||||
},
|
|
||||||
"Divination (4)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 6
|
|
||||||
},
|
|
||||||
"Divination (1)": {
|
|
||||||
"tokenType": "resource",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
"Cover Up:Advanced": {
|
|
||||||
"tokenType": "clue",
|
|
||||||
"tokenCount": 4
|
|
||||||
},
|
|
||||||
|
|
||||||
"xxx": {
|
"yyy": {
|
||||||
"tokenType": "resource",
|
"tokenType": "resource",
|
||||||
"tokenCount": 3
|
"tokenCount": 3
|
||||||
}
|
}
|
||||||
@ -1648,6 +1024,21 @@ modeData = {
|
|||||||
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } }
|
standalone = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'tablet', 'elder', 'elder', 'red', 'blue' } }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
-----------------Edge of the Earth
|
||||||
|
['Edge of the Earth'] = {
|
||||||
|
easy = { token = { 'p1', 'p1', 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } },
|
||||||
|
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } },
|
||||||
|
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } },
|
||||||
|
expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm7', 'frost', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'red', 'blue' } }
|
||||||
|
},
|
||||||
|
|
||||||
|
['City of the Elder Things'] = {
|
||||||
|
easy = { token = { 'p1', 'p1', 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
expert = { token = { '0', 'm1', 'm2', 'm2', 'm3', 'm4', 'm4', 'm5', 'm7', 'frost', 'frost', 'frost', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
|
||||||
|
},
|
||||||
|
|
||||||
-----------------The Side Missions
|
-----------------The Side Missions
|
||||||
--official
|
--official
|
||||||
['Curse of the Rougarou'] = {
|
['Curse of the Rougarou'] = {
|
||||||
@ -1704,6 +1095,20 @@ modeData = {
|
|||||||
expert = { token = { '0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
|
expert = { token = { '0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
['Machinations'] = {
|
||||||
|
easy = { token = { 'p1', 'p1', 'p1', '0', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
hard = { token = { '0', '0', 'm1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm6', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'elder', 'red', 'blue' } },
|
||||||
|
expert = { token = { '0', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm8', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'elder', 'red', 'blue' } }
|
||||||
|
},
|
||||||
|
|
||||||
|
['Red Tide'] = {
|
||||||
|
easy = { token = { 'p1', 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
normal = { token = { 'p1', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
hard = { token = { '0', '0', 'm1', 'm1', 'm2', 'm2', 'm3', 'm4', 'm5', 'm6', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
expert = { token = { '0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'skull', 'skull', 'cultist', 'cultist', 'tablet', 'elder', 'red', 'blue' } }
|
||||||
|
},
|
||||||
|
|
||||||
--fan-made
|
--fan-made
|
||||||
['Carnevale of Spiders'] = {
|
['Carnevale of Spiders'] = {
|
||||||
normal = { token = { 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm6', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
normal = { token = { 'p1', '0', '0', '0', 'm1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm6', 'skull', 'skull', 'skull', 'cultist', 'tablet', 'elder', 'red', 'blue' } },
|
||||||
|
132
src/core/DoomInPlayCounter.ttslua
Normal file
132
src/core/DoomInPlayCounter.ttslua
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
MIN_VALUE = -99
|
||||||
|
MAX_VALUE = 999
|
||||||
|
|
||||||
|
function onload(saved_data)
|
||||||
|
light_mode = false
|
||||||
|
val = 0
|
||||||
|
|
||||||
|
if saved_data ~= "" then
|
||||||
|
local loaded_data = JSON.decode(saved_data)
|
||||||
|
light_mode = loaded_data[1]
|
||||||
|
val = loaded_data[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
createAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateSave()
|
||||||
|
local data_to_save = {light_mode, val}
|
||||||
|
saved_data = JSON.encode(data_to_save)
|
||||||
|
self.script_state = saved_data
|
||||||
|
end
|
||||||
|
|
||||||
|
function createAll()
|
||||||
|
s_color = {0.5, 0.5, 0.5, 95}
|
||||||
|
|
||||||
|
if light_mode then
|
||||||
|
f_color = {1,1,1,95}
|
||||||
|
else
|
||||||
|
f_color = {0,0,0,100}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.createButton({
|
||||||
|
label=tostring(val),
|
||||||
|
click_function="add_subtract",
|
||||||
|
function_owner=self,
|
||||||
|
position={0,0.05,0},
|
||||||
|
height=600,
|
||||||
|
width=1000,
|
||||||
|
alignment = 3,
|
||||||
|
scale={x=1.5, y=1.5, z=1.5},
|
||||||
|
font_size=600,
|
||||||
|
font_color=f_color,
|
||||||
|
color={0,0,0,0}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if light_mode then
|
||||||
|
lightButtonText = "[ Set dark ]"
|
||||||
|
else
|
||||||
|
lightButtonText = "[ Set light ]"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function removeAll()
|
||||||
|
self.removeInput(0)
|
||||||
|
self.removeInput(1)
|
||||||
|
self.removeButton(0)
|
||||||
|
self.removeButton(1)
|
||||||
|
self.removeButton(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function reloadAll()
|
||||||
|
removeAll()
|
||||||
|
createAll()
|
||||||
|
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
|
||||||
|
function swap_fcolor(_obj, _color, alt_click)
|
||||||
|
light_mode = not light_mode
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function swap_align(_obj, _color, alt_click)
|
||||||
|
center_mode = not center_mode
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function editName(_obj, _string, value)
|
||||||
|
self.setName(value)
|
||||||
|
setTooltips()
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_subtract(_obj, _color, alt_click)
|
||||||
|
mod = alt_click and -1 or 1
|
||||||
|
new_value = math.min(math.max(val + mod, MIN_VALUE), MAX_VALUE)
|
||||||
|
if val ~= new_value then
|
||||||
|
val = new_value
|
||||||
|
updateVal()
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateVal()
|
||||||
|
|
||||||
|
self.editButton({
|
||||||
|
index = 0,
|
||||||
|
label = tostring(val),
|
||||||
|
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function reset_val()
|
||||||
|
val = 0
|
||||||
|
updateVal()
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
|
||||||
|
function setTooltips()
|
||||||
|
self.editInput({
|
||||||
|
index = 0,
|
||||||
|
value = self.getName(),
|
||||||
|
tooltip = ttText
|
||||||
|
})
|
||||||
|
self.editButton({
|
||||||
|
index = 0,
|
||||||
|
value = tostring(val),
|
||||||
|
tooltip = ttText
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function null()
|
||||||
|
end
|
||||||
|
|
||||||
|
function keepSample(_obj, _string, value)
|
||||||
|
reloadAll()
|
||||||
|
end
|
@ -37,7 +37,9 @@ function onload()
|
|||||||
TOKEN_DATA = {
|
TOKEN_DATA = {
|
||||||
clue = {image = tokenplayerone.clue, scale = {0.15, 0.15, 0.15}},
|
clue = {image = tokenplayerone.clue, scale = {0.15, 0.15, 0.15}},
|
||||||
resource = {image = tokenplayerone.resource, scale = {0.17, 0.17, 0.17}},
|
resource = {image = tokenplayerone.resource, scale = {0.17, 0.17, 0.17}},
|
||||||
doom = {image = tokenplayerone.doom, scale = {0.17, 0.17, 0.17}}
|
doom = {image = tokenplayerone.doom, scale = {0.17, 0.17, 0.17}},
|
||||||
|
damage = {image = tokenplayerone.damageone, scale = {0.17, 0.17, 0.17}},
|
||||||
|
horror = {image = tokenplayerone.horrorone, scale = {0.17, 0.17, 0.17}}
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectFromGUID("6161b4").interactable=false
|
getObjectFromGUID("6161b4").interactable=false
|
||||||
@ -49,6 +51,7 @@ function onload()
|
|||||||
getObjectFromGUID("9487a4").interactable=false
|
getObjectFromGUID("9487a4").interactable=false
|
||||||
getObjectFromGUID("91dd9b").interactable=false
|
getObjectFromGUID("91dd9b").interactable=false
|
||||||
getObjectFromGUID("f182ee").interactable=false
|
getObjectFromGUID("f182ee").interactable=false
|
||||||
|
getObjectFromGUID("7bff34").interactable=false
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -134,6 +137,8 @@ PULLS = {
|
|||||||
["http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/"] = 0,
|
["http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/"] = 0,
|
||||||
-- elder sign
|
-- elder sign
|
||||||
["https://i.imgur.com/nEmqjmj.png"] = 0,
|
["https://i.imgur.com/nEmqjmj.png"] = 0,
|
||||||
|
-- frost
|
||||||
|
["http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/"] = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
IMAGE_TOKEN_MAP = {
|
IMAGE_TOKEN_MAP = {
|
||||||
@ -172,7 +177,9 @@ IMAGE_TOKEN_MAP = {
|
|||||||
-- bless
|
-- bless
|
||||||
["http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/"] = "Bless",
|
["http://cloud-3.steamusercontent.com/ugc/1655601092778627699/339FB716CB25CA6025C338F13AFDFD9AC6FA8356/"] = "Bless",
|
||||||
-- curse
|
-- curse
|
||||||
["http://cloud-3.steamusercontent.com/ugc/1655601092778636039/2A25BD38E8C44701D80DD96BF0121DA21843672E/"] = "Curse"
|
["http://cloud-3.steamusercontent.com/ugc/1655601092778636039/2A25BD38E8C44701D80DD96BF0121DA21843672E/"] = "Curse",
|
||||||
|
-- frost
|
||||||
|
["http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/"] = "Frost"
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetStats()
|
function resetStats()
|
||||||
@ -686,6 +693,7 @@ function getImageUrl(id)
|
|||||||
if id == 'elder' then return 'https://i.imgur.com/ttnspKt.png' end
|
if id == 'elder' then return 'https://i.imgur.com/ttnspKt.png' end
|
||||||
if id == 'red' then return 'https://i.imgur.com/lns4fhz.png' end
|
if id == 'red' then return 'https://i.imgur.com/lns4fhz.png' end
|
||||||
if id == 'blue' then return 'https://i.imgur.com/nEmqjmj.png' end
|
if id == 'blue' then return 'https://i.imgur.com/nEmqjmj.png' end
|
||||||
|
if id == 'frost' then return 'http://cloud-3.steamusercontent.com/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/' end
|
||||||
return ''
|
return ''
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -766,3 +774,199 @@ function updateRandomSeed()
|
|||||||
math.randomseed(os.time())
|
math.randomseed(os.time())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Content Importing
|
||||||
|
|
||||||
|
|
||||||
|
--- Loadable Items test
|
||||||
|
|
||||||
|
local source_repo = 'https://raw.githubusercontent.com/seth-sced/loadable-objects/main'
|
||||||
|
local list_url = 'library.json'
|
||||||
|
local library = nil
|
||||||
|
|
||||||
|
local request_obj
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
function get_source_repo()
|
||||||
|
return source_repo
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
function onClick_toggleUi(player, window)
|
||||||
|
toggle_ui(window)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick_refreshList()
|
||||||
|
local request = WebRequest.get(get_source_repo() .. '/' .. list_url, completed_list_update)
|
||||||
|
request_obj = request
|
||||||
|
startLuaCoroutine(Global, 'my_coroutine')
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick_select(player, params)
|
||||||
|
params = JSON.decode(urldecode(params))
|
||||||
|
local url = get_source_repo() .. '/' .. params.url
|
||||||
|
local request = WebRequest.get(url, function (request) complete_obj_download(request, params) end )
|
||||||
|
request_obj = request
|
||||||
|
startLuaCoroutine(Global, 'my_coroutine')
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick_load()
|
||||||
|
UI.show('progress_display')
|
||||||
|
UI.hide('load_button')
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick_cancel()
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
function toggle_ui(title)
|
||||||
|
UI.hide('load_ui')
|
||||||
|
if UI.getValue('title') == title or title == 'Hidden' then
|
||||||
|
UI.setValue('title', 'Hidden')
|
||||||
|
else
|
||||||
|
UI.setValue('title', title)
|
||||||
|
update_window_content(title)
|
||||||
|
UI.show('load_ui')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function my_coroutine()
|
||||||
|
while request_obj do
|
||||||
|
UI.setAttribute('download_progress', 'percentage', request_obj.download_progress * 100)
|
||||||
|
coroutine.yield(0)
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function update_list(objects)
|
||||||
|
local ui = UI.getXmlTable()
|
||||||
|
local update_height = find_tag_with_id(ui, 'ui_update_height')
|
||||||
|
local update_children = find_tag_with_id(update_height.children, 'ui_update_point')
|
||||||
|
|
||||||
|
update_children.children = {}
|
||||||
|
|
||||||
|
for i,v in ipairs(objects) do
|
||||||
|
local s = JSON.encode(v);
|
||||||
|
--print(s)
|
||||||
|
table.insert(update_children.children,
|
||||||
|
{
|
||||||
|
tag = 'Text',
|
||||||
|
value = v.name,
|
||||||
|
attributes = { onClick = 'onClick_select('.. urlencode(JSON.encode(v)) ..')',
|
||||||
|
alignment = 'MiddleLeft' }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
update_height.attributes.height = #(update_children.children) * 24
|
||||||
|
UI.setXmlTable(ui)
|
||||||
|
end
|
||||||
|
|
||||||
|
function update_window_content(new_title)
|
||||||
|
if not library then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if new_title == 'Campaigns' then
|
||||||
|
update_list(library.campaigns)
|
||||||
|
elseif new_title == 'Standalone Scenarios' then
|
||||||
|
update_list(library.scenarios)
|
||||||
|
elseif new_title == 'Investigators' then
|
||||||
|
update_list(library.investigators)
|
||||||
|
elseif new_title == 'Community Content' then
|
||||||
|
update_list(library.community)
|
||||||
|
elseif new_title == 'Extras' then
|
||||||
|
update_list(library.extras)
|
||||||
|
else
|
||||||
|
update_list({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function complete_obj_download(request, params)
|
||||||
|
assert(request.is_done)
|
||||||
|
if request.is_error or request.response_code ~= 200 then
|
||||||
|
print('error: ' .. request.error)
|
||||||
|
else
|
||||||
|
if pcall(function ()
|
||||||
|
local replaced_object
|
||||||
|
pcall(function ()
|
||||||
|
if params.replace then
|
||||||
|
replaced_object = getObjectFromGUID(params.replace)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
local json = request.text
|
||||||
|
if replaced_object then
|
||||||
|
local pos = replaced_object.getPosition()
|
||||||
|
local rot = replaced_object.getRotation()
|
||||||
|
destroyObject(replaced_object)
|
||||||
|
Wait.frames(function () spawnObjectJSON({json = json, position = pos, rotation = rot}) end, 1)
|
||||||
|
else
|
||||||
|
spawnObjectJSON({json = json})
|
||||||
|
end
|
||||||
|
end) then
|
||||||
|
print('Object loaded.')
|
||||||
|
else
|
||||||
|
print('Error loading object.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
request_obj = nil
|
||||||
|
UI.setAttribute('download_progress', 'percentage', 100)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- the download button on the placeholder objects calls this to directly initiate a download
|
||||||
|
function placeholder_download(params)
|
||||||
|
-- params is a table with url and guid of replacement object, which happens to match what onClick_select wants
|
||||||
|
onClick_select(nil, JSON.encode(params))
|
||||||
|
end
|
||||||
|
|
||||||
|
function completed_list_update(request)
|
||||||
|
assert(request.is_done)
|
||||||
|
if request.is_error or request.response_code ~= 200 then
|
||||||
|
print('error: ' .. request.error)
|
||||||
|
else
|
||||||
|
local json_response = nil
|
||||||
|
if pcall(function () json_response = JSON.decode(request.text) end) then
|
||||||
|
library = json_response
|
||||||
|
update_window_content(UI.getValue('title'))
|
||||||
|
else
|
||||||
|
print('error parsing downloaded library')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
request_obj = nil
|
||||||
|
UI.setAttribute('download_progress', 'percentage', 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
function find_tag_with_id(ui, id)
|
||||||
|
for i,obj in ipairs(ui) do
|
||||||
|
if obj.attributes and obj.attributes.id and obj.attributes.id == id then
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
if obj.children then
|
||||||
|
local result = find_tag_with_id(obj.children, id)
|
||||||
|
if result then return result end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function urlencode(str)
|
||||||
|
str = string.gsub(str, "([^A-Za-z0-9-_.~])",
|
||||||
|
function (c) return string.format("%%%02X", string.byte(c)) end)
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
function urldecode(str)
|
||||||
|
str = string.gsub(str, "%%(%x%x)",
|
||||||
|
function (h) return string.char(tonumber(h, 16)) end)
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
@ -1,199 +1,116 @@
|
|||||||
|
-- Playmat Image Swapper
|
||||||
|
-- updated by: Chr1Z
|
||||||
function onSave()
|
-- original by: -
|
||||||
saved_data = JSON.encode({tid=tableImageData, cd=checkData})
|
-- description: changes the big playmats image
|
||||||
--saved_data = ""
|
information = {
|
||||||
return saved_data
|
version = "1.1",
|
||||||
end
|
last_updated = "10.10.2022"
|
||||||
|
|
||||||
function onload(saved_data)
|
|
||||||
--Loads the tracking for if the game has started yet
|
|
||||||
if saved_data ~= "" then
|
|
||||||
local loaded_data = JSON.decode(saved_data)
|
|
||||||
tableImageData = loaded_data.tid
|
|
||||||
checkData = loaded_data.cd
|
|
||||||
else
|
|
||||||
tableImageData = {}
|
|
||||||
checkData = {move=false, scale=false}
|
|
||||||
end
|
|
||||||
|
|
||||||
--Disables interactable status of objects with GUID in list
|
|
||||||
for _, guid in ipairs(ref_noninteractable) do
|
|
||||||
local obj = getObjectFromGUID(guid)
|
|
||||||
if obj then obj.interactable = false end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
obj_surface = getObjectFromGUID("721ba2")
|
|
||||||
|
|
||||||
|
|
||||||
controlActive = false
|
|
||||||
createOpenCloseButton()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Activation/deactivation of control panel
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Activated by clicking on
|
|
||||||
function click_toggleControl(_, color)
|
|
||||||
if permissionCheck(color) then
|
|
||||||
if not controlActive then
|
|
||||||
--Activate control panel
|
|
||||||
controlActive = true
|
|
||||||
self.clearButtons()
|
|
||||||
createOpenCloseButton()
|
|
||||||
createSurfaceInput()
|
|
||||||
createSurfaceButtons()
|
|
||||||
|
|
||||||
else
|
|
||||||
--Deactivate control panel
|
|
||||||
controlActive = false
|
|
||||||
self.clearButtons()
|
|
||||||
self.clearInputs()
|
|
||||||
createOpenCloseButton()
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Table surface control
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Changes table surface
|
|
||||||
function click_applySurface(_, color)
|
|
||||||
if permissionCheck(color) then
|
|
||||||
updateSurface()
|
|
||||||
broadcastToAll("New Playmat Image Applied", {0.2,0.9,0.2})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--Updates surface from the values in the input field
|
|
||||||
function updateSurface()
|
|
||||||
local customInfo = obj_surface.getCustomObject()
|
|
||||||
customInfo.image = self.getInputs()[1].value
|
|
||||||
obj_surface.setCustomObject(customInfo)
|
|
||||||
obj_surface = obj_surface.reload()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Information gathering
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Checks if a color is promoted or host
|
|
||||||
function permissionCheck(color)
|
|
||||||
if Player[color].host==true or Player[color].promoted==true then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--Locates a string saved within memory file
|
|
||||||
function findInImageDataIndex(...)
|
|
||||||
for _, str in ipairs({...}) do
|
|
||||||
for i, v in ipairs(tableImageData) do
|
|
||||||
if v.url == str or v.name == str then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--Round number (num) to the Nth decimal (dec)
|
|
||||||
function round(num, dec)
|
|
||||||
local mult = 10^(dec or 0)
|
|
||||||
return math.floor(num * mult + 0.5) / mult
|
|
||||||
end
|
|
||||||
|
|
||||||
--Locates a button with a helper function
|
|
||||||
function findButton(obj, func)
|
|
||||||
if func==nil then error("No func supplied to findButton") end
|
|
||||||
for _, v in ipairs(obj.getButtons()) do
|
|
||||||
if func(v) then
|
|
||||||
return v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Creation of buttons/inputs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function createOpenCloseButton()
|
|
||||||
local tooltip = "Open Playmat Panel"
|
|
||||||
if controlActive then
|
|
||||||
tooltip = "Close Playmat Panel"
|
|
||||||
end
|
|
||||||
self.createButton({
|
|
||||||
click_function="click_toggleControl", function_owner=self,
|
|
||||||
position={0,0,0}, rotation={-45,0,0}, height=1500, width=1500,
|
|
||||||
color={1,1,1,0}, tooltip=tooltip
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function createSurfaceInput()
|
|
||||||
local currentURL = obj_surface.getCustomObject().diffuse
|
|
||||||
local nickname = ""
|
|
||||||
if findInImageDataIndex(currentURL) ~= nil then
|
|
||||||
nickname = tableImageData[findInImageDataIndex(currentURL)].name
|
|
||||||
end
|
|
||||||
|
|
||||||
self.createInput({
|
|
||||||
label="URL", input_function="none", function_owner=self,
|
|
||||||
alignment=3, position={0,0.15,3}, height=224, width=4000,
|
|
||||||
font_size=200, tooltip="Enter URL for playmat image",
|
|
||||||
value=currentURL
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function createSurfaceButtons()
|
|
||||||
--Label
|
|
||||||
self.createButton({
|
|
||||||
label="Playmat Image Swapper", click_function="none",
|
|
||||||
position={0,0.15,2.2}, height=0, width=0, font_size=300, font_color={1,1,1}
|
|
||||||
})
|
|
||||||
--Functional
|
|
||||||
self.createButton({
|
|
||||||
label="Apply Image\nTo Playmat", click_function="click_applySurface",
|
|
||||||
function_owner=self, tooltip="Apply URL as playmat image",
|
|
||||||
position={0,0.15,4}, height=440, width=1400, font_size=200,
|
|
||||||
})
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Data tables
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ref_noninteractable = {
|
|
||||||
"afc863","c8edca","393bf7","12c65e","f938a2","9f95fd","35b95f",
|
|
||||||
"5af8f2","4ee1f2","bd69bd"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ref_playerColor = {
|
defaultURL = "http://cloud-3.steamusercontent.com/ugc/998015670465071049/FFAE162920D67CF38045EFBD3B85AD0F916147B2/"
|
||||||
"White", "Brown", "Red", "Orange", "Yellow",
|
|
||||||
"Green", "Teal", "Blue", "Purple", "Pink", "Black"
|
|
||||||
}
|
|
||||||
|
|
||||||
--Dummy function, absorbs unwanted triggers
|
-- parameters for open/close button for reusing
|
||||||
|
BUTTON_PARAMETERS = {}
|
||||||
|
BUTTON_PARAMETERS.function_owner = self
|
||||||
|
BUTTON_PARAMETERS.click_function = "click_toggleControl"
|
||||||
|
BUTTON_PARAMETERS.height = 1500
|
||||||
|
BUTTON_PARAMETERS.width = 1500
|
||||||
|
BUTTON_PARAMETERS.color = { 1, 1, 1, 0 }
|
||||||
|
|
||||||
|
function onload()
|
||||||
|
controlActive = false
|
||||||
|
createOpenCloseButton()
|
||||||
|
|
||||||
|
self.addContextMenuItem("More Information", function()
|
||||||
|
printToAll("------------------------------", "White")
|
||||||
|
printToAll("Playmat Image Swapper v" .. information["version"] .. " by Chr1Z", "Orange")
|
||||||
|
printToAll("last updated: " .. information["last_updated"], "White")
|
||||||
|
printToAll("Original made by unknown", "White")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- click function for main button
|
||||||
|
function click_toggleControl()
|
||||||
|
self.clearButtons()
|
||||||
|
self.clearInputs()
|
||||||
|
|
||||||
|
controlActive = not controlActive
|
||||||
|
createOpenCloseButton()
|
||||||
|
|
||||||
|
if not controlActive then return end
|
||||||
|
|
||||||
|
-- creates the label, input box and apply button
|
||||||
|
self.createButton({
|
||||||
|
function_owner = self,
|
||||||
|
label = "Playmat Image Swapper",
|
||||||
|
tooltip = "",
|
||||||
|
click_function = "none",
|
||||||
|
position = { 0, 0.15, 2.2 },
|
||||||
|
height = 0,
|
||||||
|
width = 0,
|
||||||
|
font_size = 300,
|
||||||
|
font_color = { 1, 1, 1 }
|
||||||
|
})
|
||||||
|
|
||||||
|
self.createInput({
|
||||||
|
function_owner = self,
|
||||||
|
label = "URL",
|
||||||
|
tooltip = "Enter URL for playmat image",
|
||||||
|
input_function = "none",
|
||||||
|
alignment = 3,
|
||||||
|
position = { 0, 0.15, 3 },
|
||||||
|
height = 323,
|
||||||
|
width = 4000,
|
||||||
|
font_size = 300
|
||||||
|
})
|
||||||
|
|
||||||
|
self.createButton({
|
||||||
|
function_owner = self,
|
||||||
|
label = "Apply Image\nTo Playmat",
|
||||||
|
tooltip = "Left-Click: Apply URL\nRight-Click: Reset to default image",
|
||||||
|
click_function = "click_applySurface",
|
||||||
|
position = { 0, 0.15, 4.1 },
|
||||||
|
height = 460,
|
||||||
|
width = 1400,
|
||||||
|
font_size = 200
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- click function for apply button
|
||||||
|
function click_applySurface(_, _, isRightClick)
|
||||||
|
if isRightClick then
|
||||||
|
updateSurface(defaultURL)
|
||||||
|
else
|
||||||
|
updateSurface(self.getInputs()[1].value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- input function for the input box
|
||||||
function none() end
|
function none() end
|
||||||
|
|
||||||
|
-- main function (can be called by other objects)
|
||||||
|
function updateSurface(newURL)
|
||||||
|
local obj_surface = getObjectFromGUID("721ba2")
|
||||||
|
local customInfo = obj_surface.getCustomObject()
|
||||||
|
|
||||||
|
if newURL ~= "" and newURL ~= nil and newURL ~= defaultURL then
|
||||||
|
customInfo.image = newURL
|
||||||
|
broadcastToAll("New Playmat Image Applied", { 0.2, 0.9, 0.2 })
|
||||||
|
else
|
||||||
|
customInfo.image = defaultURL
|
||||||
|
broadcastToAll("Default Playmat Image Applied", { 0.2, 0.9, 0.2 })
|
||||||
|
end
|
||||||
|
|
||||||
|
obj_surface.setCustomObject(customInfo)
|
||||||
|
obj_surface = obj_surface.reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- creates the main button
|
||||||
|
function createOpenCloseButton()
|
||||||
|
if controlActive then
|
||||||
|
BUTTON_PARAMETERS.tooltip = "Close Playmat Panel"
|
||||||
|
else
|
||||||
|
BUTTON_PARAMETERS.tooltip = "Open Playmat Panel"
|
||||||
|
end
|
||||||
|
self.createButton(BUTTON_PARAMETERS)
|
||||||
|
end
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
|
|
||||||
|
-- Position to check for weaknesses. Everything with X and Z less
|
||||||
|
-- than these values (down and right on the table) will be checked
|
||||||
|
local WEAKNESS_CHECK_X = 15
|
||||||
|
local WEAKNESS_CHECK_Z = 37
|
||||||
|
|
||||||
local cardIdIndex = { }
|
local cardIdIndex = { }
|
||||||
local classAndLevelIndex = { }
|
local classAndLevelIndex = { }
|
||||||
local basicWeaknessList = { }
|
local basicWeaknessList = { }
|
||||||
@ -114,15 +119,18 @@ function buildSupplementalIndexes()
|
|||||||
for cardId, card in pairs(cardIdIndex) do
|
for cardId, card in pairs(cardIdIndex) do
|
||||||
local cardData = card.data
|
local cardData = card.data
|
||||||
local cardMetadata = card.metadata
|
local cardMetadata = card.metadata
|
||||||
-- Add card to the basic weakness list, if appropriate. Some weaknesses have
|
-- If the ID key and the metadata ID don't match this is a duplicate card created by an
|
||||||
-- multiple copies, and are added multiple times
|
-- alternate_id, and we should skip it
|
||||||
if (cardMetadata.weakness and cardMetadata.basicWeaknessCount ~= nil) then
|
if (cardId == cardMetadata.id) then
|
||||||
for i = 1, cardMetadata.basicWeaknessCount do
|
-- Add card to the basic weakness list, if appropriate. Some weaknesses have
|
||||||
table.insert(basicWeaknessList, cardMetadata.id)
|
-- multiple copies, and are added multiple times
|
||||||
|
if (cardMetadata.weakness and cardMetadata.basicWeaknessCount ~= nil) then
|
||||||
|
for i = 1, cardMetadata.basicWeaknessCount do
|
||||||
|
table.insert(basicWeaknessList, cardMetadata.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Add the card to the appropriate class and level indexes
|
-- Add the card to the appropriate class and level indexes
|
||||||
local isGuardian = false
|
local isGuardian = false
|
||||||
local isSeeker = false
|
local isSeeker = false
|
||||||
local isMystic = false
|
local isMystic = false
|
||||||
@ -130,35 +138,38 @@ function buildSupplementalIndexes()
|
|||||||
local isSurvivor = false
|
local isSurvivor = false
|
||||||
local isNeutral = false
|
local isNeutral = false
|
||||||
local upgradeKey
|
local upgradeKey
|
||||||
|
-- Excludes signature cards (which have no class or level) and alternate
|
||||||
|
-- ID entries
|
||||||
if (cardMetadata.class ~= nil and cardMetadata.level ~= nil) then
|
if (cardMetadata.class ~= nil and cardMetadata.level ~= nil) then
|
||||||
isGuardian = string.match(cardMetadata.class, "Guardian")
|
isGuardian = string.match(cardMetadata.class, "Guardian")
|
||||||
isSeeker = string.match(cardMetadata.class, "Seeker")
|
isSeeker = string.match(cardMetadata.class, "Seeker")
|
||||||
isMystic = string.match(cardMetadata.class, "Mystic")
|
isMystic = string.match(cardMetadata.class, "Mystic")
|
||||||
isRogue = string.match(cardMetadata.class, "Rogue")
|
isRogue = string.match(cardMetadata.class, "Rogue")
|
||||||
isSurvivor = string.match(cardMetadata.class, "Survivor")
|
isSurvivor = string.match(cardMetadata.class, "Survivor")
|
||||||
isNeutral = string.match(cardMetadata.class, "Neutral")
|
isNeutral = string.match(cardMetadata.class, "Neutral")
|
||||||
if (cardMetadata.level > 0) then
|
if (cardMetadata.level > 0) then
|
||||||
upgradeKey = "-upgrade"
|
upgradeKey = "-upgrade"
|
||||||
else
|
else
|
||||||
upgradeKey = "-level0"
|
upgradeKey = "-level0"
|
||||||
end
|
end
|
||||||
if (isGuardian) then
|
if (isGuardian) then
|
||||||
table.insert(classAndLevelIndex["Guardian"..upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex["Guardian"..upgradeKey], cardMetadata.id)
|
||||||
end
|
end
|
||||||
if (isSeeker) then
|
if (isSeeker) then
|
||||||
table.insert(classAndLevelIndex["Seeker"..upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex["Seeker"..upgradeKey], cardMetadata.id)
|
||||||
end
|
end
|
||||||
if (isMystic) then
|
if (isMystic) then
|
||||||
table.insert(classAndLevelIndex["Mystic"..upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex["Mystic"..upgradeKey], cardMetadata.id)
|
||||||
end
|
end
|
||||||
if (isRogue) then
|
if (isRogue) then
|
||||||
table.insert(classAndLevelIndex["Rogue"..upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex["Rogue"..upgradeKey], cardMetadata.id)
|
||||||
end
|
end
|
||||||
if (isSurvivor) then
|
if (isSurvivor) then
|
||||||
table.insert(classAndLevelIndex["Survivor"..upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex["Survivor"..upgradeKey], cardMetadata.id)
|
||||||
end
|
end
|
||||||
if (isNeutral) then
|
if (isNeutral) then
|
||||||
table.insert(classAndLevelIndex["Neutral"..upgradeKey], cardMetadata.id)
|
table.insert(classAndLevelIndex["Neutral"..upgradeKey], cardMetadata.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -172,6 +183,7 @@ end
|
|||||||
function cardComparator(id1, id2)
|
function cardComparator(id1, id2)
|
||||||
local card1 = cardIdIndex[id1]
|
local card1 = cardIdIndex[id1]
|
||||||
local card2 = cardIdIndex[id2]
|
local card2 = cardIdIndex[id2]
|
||||||
|
|
||||||
if (card1.metadata.level ~= card2.metadata.level) then
|
if (card1.metadata.level ~= card2.metadata.level) then
|
||||||
return card1.metadata.level < card2.metadata.level
|
return card1.metadata.level < card2.metadata.level
|
||||||
end
|
end
|
||||||
@ -222,19 +234,92 @@ function getCardsByClassAndLevel(params)
|
|||||||
return classAndLevelIndex[params.class..upgradeKey];
|
return classAndLevelIndex[params.class..upgradeKey];
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Searches the bag for cards which match the given name and returns a list. Note that this is
|
||||||
|
-- an O(n) search without index support. It may be slow.
|
||||||
|
-- Parameter array must contain these fields to define the search:
|
||||||
|
-- name String or string fragment to search for names
|
||||||
|
-- exact Whether the name match should be exact
|
||||||
|
function getCardsByName(params)
|
||||||
|
local name = params.name
|
||||||
|
local exact = params.exact
|
||||||
|
local results = { }
|
||||||
|
-- Track cards (by ID) that we've added to avoid duplicates that may come from alternate IDs
|
||||||
|
local addedCards = { }
|
||||||
|
for _, cardData in pairs(cardIdIndex) do
|
||||||
|
if (not addedCards[cardData.metadata.id]) then
|
||||||
|
if (exact and (string.lower(cardData.data.Nickname) == string.lower(name)))
|
||||||
|
or (not exact and string.find(string.lower(cardData.data.Nickname), string.lower(name), 1, true)) then
|
||||||
|
table.insert(results, cardData)
|
||||||
|
addedCards[cardData.metadata.id] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
-- Gets a random basic weakness from the bag. Once a given ID has been returned
|
-- Gets a random basic weakness from the bag. Once a given ID has been returned
|
||||||
-- it will be removed from the list and cannot be selected again until a reload
|
-- it will be removed from the list and cannot be selected again until a reload
|
||||||
-- occurs or the indexes are rebuilt, which will refresh the list to include all
|
-- occurs or the indexes are rebuilt, which will refresh the list to include all
|
||||||
-- weaknesses.
|
-- weaknesses.
|
||||||
-- Return: String ID of the selected weakness.
|
-- Return: String ID of the selected weakness.
|
||||||
function getRandomWeaknessId()
|
function getRandomWeaknessId()
|
||||||
local pickedIndex = math.random(#basicWeaknessList)
|
local availableWeaknesses = buildAvailableWeaknesses()
|
||||||
local weaknessId = basicWeaknessList[pickedIndex]
|
if (#availableWeaknesses > 0) then
|
||||||
if (#basicWeaknessList > 1) then
|
return availableWeaknesses[math.random(#availableWeaknesses)]
|
||||||
table.remove(basicWeaknessList, pickedIndex)
|
end
|
||||||
else
|
end
|
||||||
broadcastToAll("All weaknesses have been drawn!", {0.9, 0.2, 0.2})
|
|
||||||
|
-- Constructs a list of available basic weaknesses by starting with the full pool of basic
|
||||||
|
-- weaknesses then removing any which are currently in the play or deck construction areas
|
||||||
|
-- Return: Table array of weakness IDs which are valid to choose from
|
||||||
|
function buildAvailableWeaknesses()
|
||||||
|
local weaknessesInPlay = { }
|
||||||
|
local allObjects = getAllObjects()
|
||||||
|
for _, object in ipairs(allObjects) do
|
||||||
|
if (object.name == "Deck" and isInPlayArea(object)) then
|
||||||
|
for _, cardData in ipairs(object.getData().ContainedObjects) do
|
||||||
|
local cardMetadata = JSON.decode(cardData.GMNotes)
|
||||||
|
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
|
||||||
|
end
|
||||||
|
elseif (object.name == "Card" and isInPlayArea(object)) then
|
||||||
|
local cardMetadata = JSON.decode(object.getGMNotes())
|
||||||
|
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return weaknessId
|
local availableWeaknesses = { }
|
||||||
|
for _, weaknessId in ipairs(basicWeaknessList) do
|
||||||
|
if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then
|
||||||
|
weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1
|
||||||
|
else
|
||||||
|
table.insert(availableWeaknesses, weaknessId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return availableWeaknesses
|
||||||
|
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
|
||||||
|
if (table[cardMetadata.id] == nil) then
|
||||||
|
table[cardMetadata.id] = 1
|
||||||
|
else
|
||||||
|
table[cardMetadata.id] = table[cardMetadata.id] + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function isInPlayArea(object)
|
||||||
|
if (object == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local position = object.getPosition()
|
||||||
|
return position.x < WEAKNESS_CHECK_X
|
||||||
|
and position.z < WEAKNESS_CHECK_Z
|
||||||
|
end
|
||||||
|
function isBasicWeakness(cardMetadata)
|
||||||
|
return cardMetadata ~= nil
|
||||||
|
and cardMetadata.weakness
|
||||||
|
and cardMetadata.basicWeaknessCount ~= nil
|
||||||
|
and cardMetadata.basicWeaknessCount > 0
|
||||||
end
|
end
|
||||||
|
136
src/playercards/CardSearch.ttslua
Normal file
136
src/playercards/CardSearch.ttslua
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
-- Search-A-Card
|
||||||
|
-- made by: Chr1Z
|
||||||
|
-- description: spawns the card with the specified name
|
||||||
|
information = {
|
||||||
|
version = "1.1",
|
||||||
|
last_updated = "10.10.2022"
|
||||||
|
}
|
||||||
|
|
||||||
|
local BUTTON_PARAMETERS = {}
|
||||||
|
BUTTON_PARAMETERS.function_owner = self
|
||||||
|
BUTTON_PARAMETERS.height = 200
|
||||||
|
BUTTON_PARAMETERS.width = 1200
|
||||||
|
BUTTON_PARAMETERS.font_size = 75
|
||||||
|
|
||||||
|
-- save selected options
|
||||||
|
function onSave() return JSON.encode({ spawnAll, searchExact }) end
|
||||||
|
|
||||||
|
function onLoad(saved_data)
|
||||||
|
-- loading saved data
|
||||||
|
local loaded_data = JSON.decode(saved_data)
|
||||||
|
spawnAll = loaded_data[1] or false
|
||||||
|
searchExact = loaded_data[2] or false
|
||||||
|
|
||||||
|
allCardsBag = getObjectFromGUID("15bb07")
|
||||||
|
INPUT_BOX = ""
|
||||||
|
|
||||||
|
self.createInput({
|
||||||
|
input_function = "input_func",
|
||||||
|
function_owner = self,
|
||||||
|
label = "Click to enter card name",
|
||||||
|
alignment = 2,
|
||||||
|
position = { x = 0, y = 0.05, z = -1.6 },
|
||||||
|
width = 1200,
|
||||||
|
height = 130,
|
||||||
|
font_size = 107
|
||||||
|
})
|
||||||
|
|
||||||
|
-- index 0: button for spawn mode
|
||||||
|
BUTTON_PARAMETERS.click_function = "search"
|
||||||
|
BUTTON_PARAMETERS.label = "Spawn matching card(s)!"
|
||||||
|
BUTTON_PARAMETERS.position = { x = 0, y = 0.05, z = 1.15 }
|
||||||
|
self.createButton(BUTTON_PARAMETERS)
|
||||||
|
|
||||||
|
-- index 1: button for spawn mode
|
||||||
|
if spawnAll then
|
||||||
|
BUTTON_PARAMETERS.label = "Mode: Spawn all matching cards "
|
||||||
|
else
|
||||||
|
BUTTON_PARAMETERS.label = "Mode: Spawn first matching card"
|
||||||
|
end
|
||||||
|
|
||||||
|
BUTTON_PARAMETERS.click_function = "spawnMode"
|
||||||
|
BUTTON_PARAMETERS.position.z = 1.55
|
||||||
|
self.createButton(BUTTON_PARAMETERS)
|
||||||
|
|
||||||
|
-- index 2: button for search mode
|
||||||
|
if searchExact then
|
||||||
|
BUTTON_PARAMETERS.label = "Mode: Name matches search term"
|
||||||
|
else
|
||||||
|
BUTTON_PARAMETERS.label = "Mode: Name contains search term"
|
||||||
|
end
|
||||||
|
|
||||||
|
BUTTON_PARAMETERS.click_function = "searchMode"
|
||||||
|
BUTTON_PARAMETERS.position.z = 1.95
|
||||||
|
self.createButton(BUTTON_PARAMETERS)
|
||||||
|
|
||||||
|
self.addContextMenuItem("More Information", function()
|
||||||
|
printToAll("------------------------------", "White")
|
||||||
|
printToAll("Search-A-Card v" .. information["version"] .. " by Chr1Z", "Orange")
|
||||||
|
printToAll("last updated: " .. information["last_updated"], "White")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- main function
|
||||||
|
function search()
|
||||||
|
if INPUT_BOX == nil or string.len(INPUT_BOX) == 0 then
|
||||||
|
printToAll("Please enter a search string.", "Yellow")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.len(INPUT_BOX) < 4 then
|
||||||
|
printToAll("Please enter a longer search string.", "Yellow")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if allCardsBag == nil then
|
||||||
|
printToAll("Player card bag couldn't be found.", "Red")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cardList = allCardsBag.call("getCardsByName", { name = INPUT_BOX, exact = searchExact })
|
||||||
|
if cardList == nil or #cardList == 0 then
|
||||||
|
printToAll("No match found.", "Red")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- search all objects in bag
|
||||||
|
local spawnCount = 0
|
||||||
|
for i, card in ipairs(cardList) do
|
||||||
|
local pos = self.positionToWorld(Vector(0, 0.5 + spawnCount * 0.15, -0.225))
|
||||||
|
local rot = self.getRotation()
|
||||||
|
spawnObjectData({
|
||||||
|
data = card.data,
|
||||||
|
position = pos,
|
||||||
|
rotation = rot,
|
||||||
|
})
|
||||||
|
if not spawnAll then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function input_func(_, _, input, stillEditing)
|
||||||
|
if not stillEditing then INPUT_BOX = input end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- toggle spawn mode
|
||||||
|
function spawnMode()
|
||||||
|
spawnAll = not spawnAll
|
||||||
|
|
||||||
|
if spawnAll then
|
||||||
|
self.editButton({ index = 1, label = "Mode: Spawn all matching cards " })
|
||||||
|
else
|
||||||
|
self.editButton({ index = 1, label = "Mode: Spawn first matching card" })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- toggle search mode
|
||||||
|
function searchMode()
|
||||||
|
searchExact = not searchExact
|
||||||
|
|
||||||
|
if searchExact then
|
||||||
|
self.editButton({ index = 2, label = "Mode: Name matches search term" })
|
||||||
|
else
|
||||||
|
self.editButton({ index = 2, label = "Mode: Name contains search term" })
|
||||||
|
end
|
||||||
|
end
|
@ -16,6 +16,10 @@ end
|
|||||||
function buttonClick_draw()
|
function buttonClick_draw()
|
||||||
local allCardsBag = getObjectFromGUID(allCardsBagGuid)
|
local allCardsBag = getObjectFromGUID(allCardsBagGuid)
|
||||||
local weaknessId = allCardsBag.call("getRandomWeaknessId")
|
local weaknessId = allCardsBag.call("getRandomWeaknessId")
|
||||||
|
if (weaknessId == nil) then
|
||||||
|
broadcastToAll("All basic weaknesses are in play!", {0.9, 0.2, 0.2})
|
||||||
|
return
|
||||||
|
end
|
||||||
local card = allCardsBag.call("getCardById", { id = weaknessId })
|
local card = allCardsBag.call("getCardById", { id = weaknessId })
|
||||||
spawnObjectData({
|
spawnObjectData({
|
||||||
data = card.data,
|
data = card.data,
|
||||||
|
@ -1,34 +1,4 @@
|
|||||||
-- set true to enable debug logging
|
local activeInvestigatorId = nil
|
||||||
DEBUG = false
|
|
||||||
-- we use this to turn off collision handling (for clue spawning)
|
|
||||||
-- until after load is complete (probably a better way to do this)
|
|
||||||
COLLISION_ENABLED = false
|
|
||||||
-- position offsets, adjust these to reposition things relative to mat [x,y,z]
|
|
||||||
DRAWN_ENCOUNTER_CARD_OFFSET = {0.98, 0.5, -0.635}
|
|
||||||
DRAWN_CHAOS_TOKEN_OFFSET = {-1.2, 0.5, -0.45}
|
|
||||||
DISCARD_BUTTON_OFFSETS = {
|
|
||||||
{-0.98, 0.2, -0.945},
|
|
||||||
{-0.525, 0.2, -0.945},
|
|
||||||
{-0.07, 0.2, -0.945},
|
|
||||||
{0.39, 0.2, -0.945},
|
|
||||||
{0.84, 0.2, -0.945},
|
|
||||||
}
|
|
||||||
-- draw deck and discard zone
|
|
||||||
DECK_POSITION = { x=-1.4, y=0, z=0.3 }
|
|
||||||
DECK_ZONE_SCALE = { x=3, y=5, z=8 }
|
|
||||||
DRAW_DECK_POSITION = { x=-18.9, y=2.5, z=-26.7 }
|
|
||||||
|
|
||||||
-- play zone
|
|
||||||
PLAYER_COLOR = "Red"
|
|
||||||
PLAY_ZONE_POSITION = { x=-25, y=4, z=-27 }
|
|
||||||
PLAY_ZONE_ROTATION = { x=0, y=180, z=0 }
|
|
||||||
PLAY_ZONE_SCALE = { x=30, y=5, z=15 }
|
|
||||||
|
|
||||||
RESOURCE_COUNTER_GUID = "a4b60d"
|
|
||||||
|
|
||||||
-- the position of the global discard pile
|
|
||||||
-- TODO: delegate to global for any auto discard actions
|
|
||||||
DISCARD_POSITION = {-3.85, 3, 10.38}
|
|
||||||
|
|
||||||
function log(message)
|
function log(message)
|
||||||
if DEBUG then
|
if DEBUG then
|
||||||
@ -66,6 +36,7 @@ function makeDiscardButton(position, searchPosition, discardPosition, number)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function onload(save_state)
|
function onload(save_state)
|
||||||
|
|
||||||
self.interactable = DEBUG
|
self.interactable = DEBUG
|
||||||
DATA_HELPER = getObjectFromGUID('708279')
|
DATA_HELPER = getObjectFromGUID('708279')
|
||||||
PLAYER_CARDS = DATA_HELPER.getTable('PLAYER_CARD_DATA')
|
PLAYER_CARDS = DATA_HELPER.getTable('PLAYER_CARD_DATA')
|
||||||
@ -73,15 +44,16 @@ function onload(save_state)
|
|||||||
|
|
||||||
-- positions of encounter card slots
|
-- positions of encounter card slots
|
||||||
local encounterSlots = {
|
local encounterSlots = {
|
||||||
{1, 0, -0.7},
|
{1.365, 0, -0.7},
|
||||||
{0.55, 0, -0.7},
|
{0.91, 0, -0.7},
|
||||||
{0.1, 0, -0.7},
|
{0.455, 0, -0.7},
|
||||||
{-0.35, 0, -0.7},
|
{0, 0, -0.7},
|
||||||
{-0.8, 0, -0.7}
|
{-0.455, 0, -0.7},
|
||||||
|
{-0.91, 0, -0.7},
|
||||||
}
|
}
|
||||||
|
|
||||||
local i = 1
|
local i = 1
|
||||||
while i <= 5 do
|
while i <= 6 do
|
||||||
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], encounterSlots[i], DISCARD_POSITION, i)
|
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], encounterSlots[i], DISCARD_POSITION, i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
end
|
||||||
@ -90,7 +62,7 @@ function onload(save_state)
|
|||||||
label = " ",
|
label = " ",
|
||||||
click_function = "drawEncountercard",
|
click_function = "drawEncountercard",
|
||||||
function_owner = self,
|
function_owner = self,
|
||||||
position = {-1.45,0,-0.7},
|
position = {-1.88,0,-0.7},
|
||||||
rotation = {0,-15,0},
|
rotation = {0,-15,0},
|
||||||
width = 170,
|
width = 170,
|
||||||
height = 255,
|
height = 255,
|
||||||
@ -101,7 +73,7 @@ function onload(save_state)
|
|||||||
label=" ",
|
label=" ",
|
||||||
click_function = "drawChaostokenButton",
|
click_function = "drawChaostokenButton",
|
||||||
function_owner = self,
|
function_owner = self,
|
||||||
position = {1.48,0.0,-0.74},
|
position = {1.84,0.0,-0.74},
|
||||||
rotation = {0,-45,0},
|
rotation = {0,-45,0},
|
||||||
width = 125,
|
width = 125,
|
||||||
height = 125,
|
height = 125,
|
||||||
@ -112,24 +84,13 @@ function onload(save_state)
|
|||||||
label="Upkeep",
|
label="Upkeep",
|
||||||
click_function = "doUpkeep",
|
click_function = "doUpkeep",
|
||||||
function_owner = self,
|
function_owner = self,
|
||||||
position = {1.48,0.1,-0.44},
|
position = {1.84,0.1,-0.44},
|
||||||
scale = {0.12, 0.12, 0.12},
|
scale = {0.12, 0.12, 0.12},
|
||||||
width = 800,
|
width = 800,
|
||||||
height = 280,
|
height = 280,
|
||||||
font_size = 180
|
font_size = 180
|
||||||
})
|
})
|
||||||
|
|
||||||
-- self.createButton({
|
|
||||||
-- label="Draw 1",
|
|
||||||
-- click_function = "doDrawOne",
|
|
||||||
-- function_owner = self,
|
|
||||||
-- position = {1.48,0.1,-0.36},
|
|
||||||
-- scale = {0.12, 0.12, 0.12},
|
|
||||||
-- width = 800,
|
|
||||||
-- height = 280,
|
|
||||||
-- font_size = 180
|
|
||||||
-- })
|
|
||||||
|
|
||||||
local state = JSON.decode(save_state)
|
local state = JSON.decode(save_state)
|
||||||
if state ~= nil then
|
if state ~= nil then
|
||||||
if state.playerColor ~= nil then
|
if state.playerColor ~= nil then
|
||||||
@ -157,34 +118,31 @@ function setMessageColor(color)
|
|||||||
messageColor = Player[PLAYER_COLOR].seated and PLAYER_COLOR or color
|
messageColor = Player[PLAYER_COLOR].seated and PLAYER_COLOR or color
|
||||||
end
|
end
|
||||||
|
|
||||||
function getDrawDiscardDecks(zone)
|
-- get the draw deck and discard pile objects
|
||||||
-- get the draw deck and discard pile objects
|
function getDrawDiscardDecks()
|
||||||
drawDeck = nil
|
drawDeck = nil
|
||||||
discardPile = nil
|
discardPile = nil
|
||||||
|
topCard = nil
|
||||||
|
|
||||||
|
local zone = getObjectFromGUID(zoneID)
|
||||||
|
if zone == nil then return end
|
||||||
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
for i,object in ipairs(zone.getObjects()) do
|
||||||
if object.tag == "Deck" or object.tag == "Card" then
|
if object.tag == "Deck" or object.tag == "Card" then
|
||||||
if object.is_face_down then
|
local relativePos = self.positionToLocal(object.getPosition())
|
||||||
drawDeck = object
|
if relativePos.z > 0.5 then
|
||||||
else
|
|
||||||
discardPile = object
|
discardPile = object
|
||||||
|
else
|
||||||
|
if investigator == "Norman Withers" and object.tag == "Card" and not object.is_face_down then
|
||||||
|
topCard = object
|
||||||
|
else
|
||||||
|
drawDeck = object
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function checkDeckThenDrawOne()
|
|
||||||
-- draw 1 card, shuffling the discard pile if necessary
|
|
||||||
if drawDeck == nil then
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(1), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
else
|
|
||||||
drawCards(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function doUpkeep(obj, color, alt_click)
|
function doUpkeep(obj, color, alt_click)
|
||||||
-- right-click binds to new player color
|
-- right-click binds to new player color
|
||||||
if alt_click then
|
if alt_click then
|
||||||
@ -206,70 +164,93 @@ function doUpkeep(obj, color, alt_click)
|
|||||||
|
|
||||||
local y = PLAY_ZONE_ROTATION.y
|
local y = PLAY_ZONE_ROTATION.y
|
||||||
|
|
||||||
local investigator = nil
|
investigator = nil
|
||||||
|
local miniId = nil
|
||||||
|
local forcedLearning = false
|
||||||
for i,v in ipairs(objs) do
|
for i,v in ipairs(objs) do
|
||||||
local obj = v.hit_object
|
local obj = v.hit_object
|
||||||
local props = obj.getCustomObject()
|
local props = obj.getCustomObject() or {}
|
||||||
if obj.tag == "Card" and not obj.is_face_down and not doNotReady(obj) then
|
if obj.tag == "Card" and not obj.is_face_down and not doNotReady(obj) then
|
||||||
if props ~= nil and props.unique_back then
|
local notes = JSON.decode(obj.getGMNotes()) or {}
|
||||||
local name = obj.getName()
|
local name = obj.getName()
|
||||||
|
if notes.type == "Investigator" and notes.id ~= nil then
|
||||||
|
miniId = string.match(notes.id, "%d%d%d%d%d%d-") .. "-m"
|
||||||
|
end
|
||||||
|
if notes.type == "Investigator" or props.unique_back then
|
||||||
if string.match(name, "Jenny Barnes") ~= nil then
|
if string.match(name, "Jenny Barnes") ~= nil then
|
||||||
investigator = "Jenny Barnes"
|
investigator = "Jenny Barnes"
|
||||||
elseif name == "Patrice Hathaway" then
|
elseif name == "Patrice Hathaway" then
|
||||||
investigator = name
|
investigator = name
|
||||||
|
elseif string.match(name, "Norman Withers") ~= nil then
|
||||||
|
investigator = "Norman Withers"
|
||||||
end
|
end
|
||||||
|
elseif name == "Forced Learning" then
|
||||||
|
forcedLearning = true
|
||||||
else
|
else
|
||||||
local r = obj.getRotation()
|
local r = obj.getRotation()
|
||||||
if (r.y - y > 10) or (y - r.y > 10) then
|
if (r.y - y > 10) or (y - r.y > 10) then
|
||||||
obj.setRotation(PLAY_ZONE_ROTATION)
|
obj.setRotation(PLAY_ZONE_ROTATION)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif obj.tag == "Board" and obj.getDescription() == "Action token" then
|
elseif obj.getDescription() == "Action Token" then
|
||||||
if obj.is_face_down then obj.flip() end
|
if obj.is_face_down then obj.flip() end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- flip investigator mini-card if found
|
||||||
|
if miniId ~= nil then
|
||||||
|
objs = getObjects()
|
||||||
|
for i,obj in ipairs(objs) do
|
||||||
|
if obj.tag == "Card" then
|
||||||
|
local notes = JSON.decode(obj.getGMNotes())
|
||||||
|
if notes ~= nil and notes.type == "Minicard" and notes.id == miniId then
|
||||||
|
if obj.is_face_down then
|
||||||
|
obj.flip()
|
||||||
|
end
|
||||||
|
goto done
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::done::
|
||||||
|
|
||||||
|
-- flip summoned servitor mini-cards (To-Do: don't flip all of them)
|
||||||
|
for i, obj in ipairs(getObjects()) do
|
||||||
|
if obj.tag == "Card" then
|
||||||
|
local notes = JSON.decode(obj.getGMNotes())
|
||||||
|
if notes ~= nil and notes.type == "Minicard" and notes.id == "09080-m" then
|
||||||
|
if obj.is_face_down then
|
||||||
|
obj.flip()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- gain resource
|
-- gain resource
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
||||||
if investigator == "Jenny Barnes" then
|
if investigator == "Jenny Barnes" then
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
||||||
printToColor("Taking 2 resources (Jenny)", messageColor)
|
printToColor("Gaining 2 resources (Jenny)", messageColor)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- special draw for Patrice Hathaway (shuffle discards if necessary)
|
-- special draw for Patrice Hathaway (shuffle discards if necessary)
|
||||||
if investigator == "Patrice Hathaway" then
|
if investigator == "Patrice Hathaway" then
|
||||||
patriceDraw()
|
patriceDraw()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
-- special draw for Forced Learning
|
||||||
checkDeckThenDrawOne()
|
if forcedLearning then
|
||||||
end
|
forcedLearningDraw()
|
||||||
|
|
||||||
function doDrawOne(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Draw 1 button bound to " .. color, color)
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
drawCardsWithReshuffle(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function doDrawOne(obj, color)
|
||||||
setMessageColor(color)
|
setMessageColor(color)
|
||||||
|
drawCardsWithReshuffle(1)
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function doNotReady(card)
|
function doNotReady(card)
|
||||||
@ -280,13 +261,67 @@ function doNotReady(card)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- draw X cards (shuffle discards if necessary)
|
||||||
|
function drawCardsWithReshuffle(numCards)
|
||||||
|
if type(numCards) ~= "number" then numCards = 1 end
|
||||||
|
|
||||||
|
getDrawDiscardDecks()
|
||||||
|
|
||||||
|
if investigator == "Norman Withers" then
|
||||||
|
local harbinger = false
|
||||||
|
if topCard ~= nil and topCard.getName() == "The Harbinger" then
|
||||||
|
harbinger = true
|
||||||
|
else
|
||||||
|
if drawDeck ~= nil and not drawDeck.is_face_down then
|
||||||
|
local cards = drawDeck.getObjects()
|
||||||
|
local bottomCard = cards[#cards]
|
||||||
|
if bottomCard.name == "The Harbinger" then
|
||||||
|
harbinger = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if harbinger then
|
||||||
|
printToColor("The Harbinger is on top of your deck, not drawing cards", messageColor)
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
if topCard ~= nil then
|
||||||
|
topCard.deal(numCards, PLAYER_COLOR)
|
||||||
|
numCards = numCards - 1
|
||||||
|
if numCards == 0 then return end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local deckSize
|
||||||
|
if drawDeck == nil then
|
||||||
|
deckSize = 0
|
||||||
|
elseif drawDeck.tag == "Deck" then
|
||||||
|
deckSize = #drawDeck.getObjects()
|
||||||
|
else
|
||||||
|
deckSize = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if deckSize >= numCards then
|
||||||
|
drawCards(numCards)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
drawCards(deckSize)
|
||||||
|
if discardPile ~= nil then
|
||||||
|
shuffleDiscardIntoDeck()
|
||||||
|
Wait.time(|| drawCards(numCards - deckSize), 1)
|
||||||
|
end
|
||||||
|
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
||||||
|
end
|
||||||
|
|
||||||
function drawCards(numCards)
|
function drawCards(numCards)
|
||||||
if drawDeck == nil then return end
|
if drawDeck == nil then return end
|
||||||
drawDeck.deal(numCards, PLAYER_COLOR)
|
drawDeck.deal(numCards, PLAYER_COLOR)
|
||||||
end
|
end
|
||||||
|
|
||||||
function shuffleDiscardIntoDeck()
|
function shuffleDiscardIntoDeck()
|
||||||
discardPile.flip()
|
if not discardPile.is_face_down then discardPile.flip() end
|
||||||
discardPile.shuffle()
|
discardPile.shuffle()
|
||||||
discardPile.setPositionSmooth(DRAW_DECK_POSITION, false, false)
|
discardPile.setPositionSmooth(DRAW_DECK_POSITION, false, false)
|
||||||
drawDeck = discardPile
|
drawDeck = discardPile
|
||||||
@ -297,27 +332,13 @@ function patriceDraw()
|
|||||||
local handSize = #Player[PLAYER_COLOR].getHandObjects()
|
local handSize = #Player[PLAYER_COLOR].getHandObjects()
|
||||||
if handSize >= 5 then return end
|
if handSize >= 5 then return end
|
||||||
local cardsToDraw = 5 - handSize
|
local cardsToDraw = 5 - handSize
|
||||||
local deckSize
|
|
||||||
printToColor("Drawing " .. cardsToDraw .. " cards (Patrice)", messageColor)
|
printToColor("Drawing " .. cardsToDraw .. " cards (Patrice)", messageColor)
|
||||||
if drawDeck == nil then
|
drawCardsWithReshuffle(cardsToDraw)
|
||||||
deckSize = 0
|
end
|
||||||
elseif drawDeck.tag == "Deck" then
|
|
||||||
deckSize = #drawDeck.getObjects()
|
|
||||||
else
|
|
||||||
deckSize = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if deckSize >= cardsToDraw then
|
function forcedLearningDraw()
|
||||||
drawCards(cardsToDraw)
|
printToColor("Drawing 2 cards, discard 1 (Forced Learning)", messageColor)
|
||||||
return
|
drawCardsWithReshuffle(2)
|
||||||
end
|
|
||||||
|
|
||||||
drawCards(deckSize)
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(cardsToDraw - deckSize), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function checkDeckZoneExists()
|
function checkDeckZoneExists()
|
||||||
@ -368,6 +389,9 @@ end
|
|||||||
|
|
||||||
-- spawn a group of tokens of the given type on the object
|
-- spawn a group of tokens of the given type on the object
|
||||||
function spawnTokenGroup(object, tokenType, tokenCount)
|
function spawnTokenGroup(object, tokenType, tokenCount)
|
||||||
|
if (tokenCount < 1 or tokenCount > 12) then
|
||||||
|
return
|
||||||
|
end
|
||||||
local offsets = PLAYER_CARD_TOKEN_OFFSETS[tokenCount]
|
local offsets = PLAYER_CARD_TOKEN_OFFSETS[tokenCount]
|
||||||
if offsets == nil then
|
if offsets == nil then
|
||||||
error("couldn't find offsets for " .. tokenCount .. ' tokens')
|
error("couldn't find offsets for " .. tokenCount .. ' tokens')
|
||||||
@ -392,8 +416,10 @@ function shouldSpawnTokens(object)
|
|||||||
-- we assume we shouldn't spawn tokens if in doubt, this should
|
-- we assume we shouldn't spawn tokens if in doubt, this should
|
||||||
-- only ever happen on load and in that case prevents respawns
|
-- only ever happen on load and in that case prevents respawns
|
||||||
local spawned = DATA_HELPER.call('getSpawnedPlayerCardGuid', {object.getGUID()})
|
local spawned = DATA_HELPER.call('getSpawnedPlayerCardGuid', {object.getGUID()})
|
||||||
local canSpawn = getPlayerCardData(object)
|
local hasDataHelperData = getPlayerCardData(object)
|
||||||
return not spawned and canSpawn
|
local cardMetadata = JSON.decode(object.getGMNotes()) or {}
|
||||||
|
local hasUses = cardMetadata.uses ~= nil
|
||||||
|
return not spawned and (hasDataHelperData or hasUses)
|
||||||
end
|
end
|
||||||
|
|
||||||
function markSpawned(object)
|
function markSpawned(object)
|
||||||
@ -404,12 +430,32 @@ function markSpawned(object)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function spawnTokensFor(object)
|
function spawnTokensFor(object)
|
||||||
local data = getPlayerCardData(object)
|
local cardMetadata = JSON.decode(object.getGMNotes()) or {}
|
||||||
if data == nil then
|
local token = nil
|
||||||
error('attempt to spawn tokens for ' .. object.getName() .. ': no token data')
|
local type = nil
|
||||||
|
local tokenCount = 0
|
||||||
|
if (cardMetadata.uses ~= nil) then
|
||||||
|
for i, useInfo in ipairs(cardMetadata.uses) do
|
||||||
|
token = useInfo.token
|
||||||
|
type = useInfo.type
|
||||||
|
tokenCount = useInfo.count
|
||||||
|
if (activeInvestigatorId == "03004" and useInfo.type == "Charge") then
|
||||||
|
tokenCount = tokenCount + 1
|
||||||
|
end
|
||||||
|
log("Spawning tokens for "..object.getName()..'['..object.getDescription()..']: '..tokenCount.."x "..token)
|
||||||
|
spawnTokenGroup(object, token, tokenCount)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local data = getPlayerCardData(object)
|
||||||
|
if data == nil then
|
||||||
|
error('attempt to spawn tokens for ' .. object.getName() .. ': no token data')
|
||||||
|
end
|
||||||
|
token = data['tokenType']
|
||||||
|
tokenCount = data['tokenCount']
|
||||||
|
log(object.getName() .. '[' .. object.getDescription() .. ']' .. ' : ' .. data['tokenType'] .. ' : ' .. data['tokenCount'])
|
||||||
|
log("Spawning tokens for "..object.getName()..'['..object.getDescription()..']: '..tokenCount.."x "..token)
|
||||||
|
spawnTokenGroup(object, token, tokenCount)
|
||||||
end
|
end
|
||||||
log(object.getName() .. '[' .. object.getDescription() .. ']' .. ' : ' .. data['tokenType'] .. ' : ' .. data['tokenCount'])
|
|
||||||
spawnTokenGroup(object, data['tokenType'], data['tokenCount'])
|
|
||||||
markSpawned(object)
|
markSpawned(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -438,6 +484,9 @@ function unmarkSpawned(guid, force)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function onCollisionEnter(collision_info)
|
function onCollisionEnter(collision_info)
|
||||||
|
if (collision_info.collision_object.name == "Card") then
|
||||||
|
maybeUpdateActiveInvestigator(collision_info.collision_object)
|
||||||
|
end
|
||||||
if not COLLISION_ENABLED then
|
if not COLLISION_ENABLED then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -456,6 +505,28 @@ function onCollisionEnter(collision_info)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function maybeUpdateActiveInvestigator(card)
|
||||||
|
local cardMetadata = JSON.decode(card.getGMNotes()) or {}
|
||||||
|
if (cardMetadata.type == "Investigator") then
|
||||||
|
activeInvestigatorId = cardMetadata.id
|
||||||
|
updateStatToken(willpowerTokenGuid, cardMetadata.willpowerIcons)
|
||||||
|
updateStatToken(intellectTokenGuid, cardMetadata.intellectIcons)
|
||||||
|
updateStatToken(combatTokenGuid, cardMetadata.combatIcons)
|
||||||
|
updateStatToken(agilityTokenGuid, cardMetadata.agilityIcons)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateStatToken(tokenGuid, val)
|
||||||
|
local statToken = getObjectFromGUID(tokenGuid)
|
||||||
|
if (statToken == nil) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
statToken.call("reset_val")
|
||||||
|
for i = 1, val do
|
||||||
|
statToken.call("add_subtract", { alt_click = false })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- functions delegated to Global
|
-- functions delegated to Global
|
||||||
function drawChaostokenButton(object, player, isRightClick)
|
function drawChaostokenButton(object, player, isRightClick)
|
||||||
-- local toPosition = self.positionToWorld(DRAWN_CHAOS_TOKEN_OFFSET)
|
-- local toPosition = self.positionToWorld(DRAWN_CHAOS_TOKEN_OFFSET)
|
@ -1,480 +0,0 @@
|
|||||||
-- set true to enable debug logging
|
|
||||||
DEBUG = false
|
|
||||||
-- we use this to turn off collision handling (for clue spawning)
|
|
||||||
-- until after load is complete (probably a better way to do this)
|
|
||||||
COLLISION_ENABLED = false
|
|
||||||
-- position offsets, adjust these to reposition things relative to mat [x,y,z]
|
|
||||||
DRAWN_ENCOUNTER_CARD_OFFSET = {0.98, 0.5, -0.635}
|
|
||||||
DRAWN_CHAOS_TOKEN_OFFSET = {-1.2, 0.5, -0.45}
|
|
||||||
DISCARD_BUTTON_OFFSETS = {
|
|
||||||
{-0.98, 0.2, -0.945},
|
|
||||||
{-0.525, 0.2, -0.945},
|
|
||||||
{-0.07, 0.2, -0.945},
|
|
||||||
{0.39, 0.2, -0.945},
|
|
||||||
{0.84, 0.2, -0.945},
|
|
||||||
}
|
|
||||||
-- draw deck and discard zone
|
|
||||||
DECK_POSITION = { x=-1.4, y=0, z=0.3 }
|
|
||||||
DECK_ZONE_SCALE = { x=3, y=5, z=8 }
|
|
||||||
DRAW_DECK_POSITION = { x=-37, y=2.5, z=26.5 }
|
|
||||||
|
|
||||||
-- play zone
|
|
||||||
PLAYER_COLOR = "Green"
|
|
||||||
PLAY_ZONE_POSITION = { x=-25, y=4, z=27 }
|
|
||||||
PLAY_ZONE_ROTATION = { x=0, y=0, z=0 }
|
|
||||||
PLAY_ZONE_SCALE = { x=30, y=5, z=15 }
|
|
||||||
|
|
||||||
RESOURCE_COUNTER_GUID = "cd15ac"
|
|
||||||
|
|
||||||
-- the position of the global discard pile
|
|
||||||
-- TODO: delegate to global for any auto discard actions
|
|
||||||
DISCARD_POSITION = {-3.85, 3, 10.38}
|
|
||||||
|
|
||||||
function log(message)
|
|
||||||
if DEBUG then
|
|
||||||
print(message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- builds a function that discards things in searchPostion to discardPostition
|
|
||||||
function makeDiscardHandlerFor(searchPosition, discardPosition)
|
|
||||||
return function (_)
|
|
||||||
local discardItemList = findObjectsAtPosition(searchPosition)
|
|
||||||
for _, obj in ipairs(discardItemList) do
|
|
||||||
obj.setPositionSmooth(discardPosition, false, true)
|
|
||||||
obj.setRotation({0, -90, 0})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- build a discard button at position to discard from searchPosition to discardPosition
|
|
||||||
-- number must be unique
|
|
||||||
function makeDiscardButton(position, searchPosition, discardPosition, number)
|
|
||||||
local handler = makeDiscardHandlerFor(searchPosition, discardPosition)
|
|
||||||
local handlerName = 'handler' .. number
|
|
||||||
self.setVar(handlerName, handler)
|
|
||||||
self.createButton({
|
|
||||||
label = "Discard",
|
|
||||||
click_function= handlerName,
|
|
||||||
function_owner= self,
|
|
||||||
position = position,
|
|
||||||
scale = {0.12, 0.12, 0.12},
|
|
||||||
width = 800,
|
|
||||||
height = 280,
|
|
||||||
font_size = 180,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onload(save_state)
|
|
||||||
self.interactable = DEBUG
|
|
||||||
DATA_HELPER = getObjectFromGUID('708279')
|
|
||||||
PLAYER_CARDS = DATA_HELPER.getTable('PLAYER_CARD_DATA')
|
|
||||||
PLAYER_CARD_TOKEN_OFFSETS = DATA_HELPER.getTable('PLAYER_CARD_TOKEN_OFFSETS')
|
|
||||||
|
|
||||||
-- positions of encounter card slots
|
|
||||||
local encounterSlots = {
|
|
||||||
{1, 0, -0.7},
|
|
||||||
{0.55, 0, -0.7},
|
|
||||||
{0.1, 0, -0.7},
|
|
||||||
{-0.35, 0, -0.7},
|
|
||||||
{-0.8, 0, -0.7}
|
|
||||||
}
|
|
||||||
|
|
||||||
local i = 1
|
|
||||||
while i <= 5 do
|
|
||||||
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], encounterSlots[i], DISCARD_POSITION, i)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label = " ",
|
|
||||||
click_function = "drawEncountercard",
|
|
||||||
function_owner = self,
|
|
||||||
position = {-1.45,0,-0.7},
|
|
||||||
rotation = {0,-15,0},
|
|
||||||
width = 170,
|
|
||||||
height = 255,
|
|
||||||
font_size = 50
|
|
||||||
})
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label=" ",
|
|
||||||
click_function = "drawChaostokenButton",
|
|
||||||
function_owner = self,
|
|
||||||
position = {1.48,0.0,-0.74},
|
|
||||||
rotation = {0,-45,0},
|
|
||||||
width = 125,
|
|
||||||
height = 125,
|
|
||||||
font_size = 50
|
|
||||||
})
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label="Upkeep",
|
|
||||||
click_function = "doUpkeep",
|
|
||||||
function_owner = self,
|
|
||||||
position = {1.48,0.1,-0.44},
|
|
||||||
scale = {0.12, 0.12, 0.12},
|
|
||||||
width = 800,
|
|
||||||
height = 280,
|
|
||||||
font_size = 180
|
|
||||||
})
|
|
||||||
|
|
||||||
-- self.createButton({
|
|
||||||
-- label="Draw 1",
|
|
||||||
-- click_function = "doDrawOne",
|
|
||||||
-- function_owner = self,
|
|
||||||
-- position = {1.48,0.1,-0.36},
|
|
||||||
-- scale = {0.12, 0.12, 0.12},
|
|
||||||
-- width = 800,
|
|
||||||
-- height = 280,
|
|
||||||
-- font_size = 180
|
|
||||||
-- })
|
|
||||||
|
|
||||||
local state = JSON.decode(save_state)
|
|
||||||
if state ~= nil then
|
|
||||||
if state.playerColor ~= nil then
|
|
||||||
PLAYER_COLOR = state.playerColor
|
|
||||||
end
|
|
||||||
if state.zoneID ~= nil then
|
|
||||||
zoneID = state.zoneID
|
|
||||||
Wait.time(checkDeckZoneExists, 30)
|
|
||||||
else
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
|
|
||||||
COLLISION_ENABLED = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function onSave()
|
|
||||||
return JSON.encode({ zoneID=zoneID, playerColor=PLAYER_COLOR })
|
|
||||||
end
|
|
||||||
|
|
||||||
function setMessageColor(color)
|
|
||||||
-- send messages to player who clicked button if no seated player found
|
|
||||||
messageColor = Player[PLAYER_COLOR].seated and PLAYER_COLOR or color
|
|
||||||
end
|
|
||||||
|
|
||||||
function getDrawDiscardDecks(zone)
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
drawDeck = nil
|
|
||||||
discardPile = nil
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
|
||||||
if object.tag == "Deck" or object.tag == "Card" then
|
|
||||||
if object.is_face_down then
|
|
||||||
drawDeck = object
|
|
||||||
else
|
|
||||||
discardPile = object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkDeckThenDrawOne()
|
|
||||||
-- draw 1 card, shuffling the discard pile if necessary
|
|
||||||
if drawDeck == nil then
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(1), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
else
|
|
||||||
drawCards(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function doUpkeep(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Upkeep button bound to " .. color, color)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
setMessageColor(color)
|
|
||||||
|
|
||||||
-- unexhaust cards in play zone
|
|
||||||
local objs = Physics.cast({
|
|
||||||
origin = PLAY_ZONE_POSITION,
|
|
||||||
direction = { x=0, y=1, z=0 },
|
|
||||||
type = 3,
|
|
||||||
size = PLAY_ZONE_SCALE,
|
|
||||||
orientation = PLAY_ZONE_ROTATION
|
|
||||||
})
|
|
||||||
|
|
||||||
local y = PLAY_ZONE_ROTATION.y
|
|
||||||
|
|
||||||
local investigator = nil
|
|
||||||
for i,v in ipairs(objs) do
|
|
||||||
local obj = v.hit_object
|
|
||||||
local props = obj.getCustomObject()
|
|
||||||
if obj.tag == "Card" and not obj.is_face_down and not doNotReady(obj) then
|
|
||||||
if props ~= nil and props.unique_back then
|
|
||||||
local name = obj.getName()
|
|
||||||
if string.match(name, "Jenny Barnes") ~= nil then
|
|
||||||
investigator = "Jenny Barnes"
|
|
||||||
elseif name == "Patrice Hathaway" then
|
|
||||||
investigator = name
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local r = obj.getRotation()
|
|
||||||
if (r.y - y > 10) or (y - r.y > 10) then
|
|
||||||
obj.setRotation(PLAY_ZONE_ROTATION)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif obj.tag == "Board" and obj.getDescription() == "Action token" then
|
|
||||||
if obj.is_face_down then obj.flip() end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- gain resource
|
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
|
||||||
if investigator == "Jenny Barnes" then
|
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
|
||||||
printToColor("Taking 2 resources (Jenny)", messageColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- special draw for Patrice Hathaway (shuffle discards if necessary)
|
|
||||||
if investigator == "Patrice Hathaway" then
|
|
||||||
patriceDraw()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
|
||||||
|
|
||||||
function doDrawOne(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Draw 1 button bound to " .. color, color)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
setMessageColor(color)
|
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
|
||||||
|
|
||||||
function doNotReady(card)
|
|
||||||
if card.getVar("do_not_ready") == true then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawCards(numCards)
|
|
||||||
if drawDeck == nil then return end
|
|
||||||
drawDeck.deal(numCards, PLAYER_COLOR)
|
|
||||||
end
|
|
||||||
|
|
||||||
function shuffleDiscardIntoDeck()
|
|
||||||
discardPile.flip()
|
|
||||||
discardPile.shuffle()
|
|
||||||
discardPile.setPositionSmooth(DRAW_DECK_POSITION, false, false)
|
|
||||||
drawDeck = discardPile
|
|
||||||
discardPile = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function patriceDraw()
|
|
||||||
local handSize = #Player[PLAYER_COLOR].getHandObjects()
|
|
||||||
if handSize >= 5 then return end
|
|
||||||
local cardsToDraw = 5 - handSize
|
|
||||||
local deckSize
|
|
||||||
printToColor("Drawing " .. cardsToDraw .. " cards (Patrice)", messageColor)
|
|
||||||
if drawDeck == nil then
|
|
||||||
deckSize = 0
|
|
||||||
elseif drawDeck.tag == "Deck" then
|
|
||||||
deckSize = #drawDeck.getObjects()
|
|
||||||
else
|
|
||||||
deckSize = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if deckSize >= cardsToDraw then
|
|
||||||
drawCards(cardsToDraw)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
drawCards(deckSize)
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(cardsToDraw - deckSize), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkDeckZoneExists()
|
|
||||||
if getObjectFromGUID(zoneID) ~= nil then return end
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnDeckZone()
|
|
||||||
local pos = self.positionToWorld(DECK_POSITION)
|
|
||||||
local zoneProps = {
|
|
||||||
position = pos,
|
|
||||||
scale = DECK_ZONE_SCALE,
|
|
||||||
type = 'ScriptingTrigger',
|
|
||||||
callback = 'zoneCallback',
|
|
||||||
callback_owner = self,
|
|
||||||
rotation = self.getRotation()
|
|
||||||
}
|
|
||||||
spawnObject(zoneProps)
|
|
||||||
end
|
|
||||||
|
|
||||||
function zoneCallback(zone)
|
|
||||||
zoneID = zone.getGUID()
|
|
||||||
end
|
|
||||||
|
|
||||||
function findObjectsAtPosition(localPos)
|
|
||||||
local globalPos = self.positionToWorld(localPos)
|
|
||||||
local objList = Physics.cast({
|
|
||||||
origin=globalPos, --Where the cast takes place
|
|
||||||
direction={0,1,0}, --Which direction it moves (up is shown)
|
|
||||||
type=2, --Type. 2 is "sphere"
|
|
||||||
size={2,2,2}, --How large that sphere is
|
|
||||||
max_distance=1, --How far it moves. Just a little bit
|
|
||||||
debug=false --If it displays the sphere when casting.
|
|
||||||
})
|
|
||||||
local decksAndCards = {}
|
|
||||||
for _, obj in ipairs(objList) do
|
|
||||||
if obj.hit_object.tag == "Deck" or obj.hit_object.tag == "Card" then
|
|
||||||
table.insert(decksAndCards, obj.hit_object)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return decksAndCards
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnTokenOn(object, offsets, tokenType)
|
|
||||||
local tokenPosition = object.positionToWorld(offsets)
|
|
||||||
spawnToken(tokenPosition, tokenType)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- spawn a group of tokens of the given type on the object
|
|
||||||
function spawnTokenGroup(object, tokenType, tokenCount)
|
|
||||||
local offsets = PLAYER_CARD_TOKEN_OFFSETS[tokenCount]
|
|
||||||
if offsets == nil then
|
|
||||||
error("couldn't find offsets for " .. tokenCount .. ' tokens')
|
|
||||||
end
|
|
||||||
local i = 0
|
|
||||||
while i < tokenCount do
|
|
||||||
local offset = offsets[i + 1]
|
|
||||||
spawnTokenOn(object, offset, tokenType)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function buildPlayerCardKey(object)
|
|
||||||
return object.getName() .. ':' .. object.getDescription()
|
|
||||||
end
|
|
||||||
|
|
||||||
function getPlayerCardData(object)
|
|
||||||
return PLAYER_CARDS[buildPlayerCardKey(object)] or PLAYER_CARDS[object.getName()]
|
|
||||||
end
|
|
||||||
|
|
||||||
function shouldSpawnTokens(object)
|
|
||||||
-- we assume we shouldn't spawn tokens if in doubt, this should
|
|
||||||
-- only ever happen on load and in that case prevents respawns
|
|
||||||
local spawned = DATA_HELPER.call('getSpawnedPlayerCardGuid', {object.getGUID()})
|
|
||||||
local canSpawn = getPlayerCardData(object)
|
|
||||||
return not spawned and canSpawn
|
|
||||||
end
|
|
||||||
|
|
||||||
function markSpawned(object)
|
|
||||||
local saved = DATA_HELPER.call('setSpawnedPlayerCardGuid', {object.getGUID(), true})
|
|
||||||
if not saved then
|
|
||||||
error('attempt to mark player card spawned before data loaded')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnTokensFor(object)
|
|
||||||
local data = getPlayerCardData(object)
|
|
||||||
if data == nil then
|
|
||||||
error('attempt to spawn tokens for ' .. object.getName() .. ': no token data')
|
|
||||||
end
|
|
||||||
log(object.getName() .. '[' .. object.getDescription() .. ']' .. ' : ' .. data['tokenType'] .. ' : ' .. data['tokenCount'])
|
|
||||||
spawnTokenGroup(object, data['tokenType'], data['tokenCount'])
|
|
||||||
markSpawned(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
function resetSpawnState()
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
|
||||||
if object.tag == "Card" then
|
|
||||||
local guid = object.getGUID()
|
|
||||||
if guid ~= nil then unmarkSpawned(guid, true) end
|
|
||||||
elseif object.tag == "Deck" then
|
|
||||||
local cards = object.getObjects()
|
|
||||||
if (cards ~= nil) then
|
|
||||||
for i,v in ipairs(cards) do
|
|
||||||
if v.guid ~= nil then unmarkSpawned(v.guid) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function unmarkSpawned(guid, force)
|
|
||||||
if not force and getObjectFromGUID(guid) ~= nil then return end
|
|
||||||
DATA_HELPER.call('setSpawnedPlayerCardGuid', {guid, false})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onCollisionEnter(collision_info)
|
|
||||||
if not COLLISION_ENABLED then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local object = collision_info.collision_object
|
|
||||||
Wait.time(resetSpawnState, 1)
|
|
||||||
-- anything to the left of this is legal to spawn
|
|
||||||
local discardSpawnBoundary = self.positionToWorld({-1.2, 0, 0})
|
|
||||||
local boundaryLocalToCard = object.positionToLocal(discardSpawnBoundary)
|
|
||||||
if boundaryLocalToCard.x > 0 then
|
|
||||||
log('not checking for token spawn, boundary relative is ' .. boundaryLocalToCard.x)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not object.is_face_down and shouldSpawnTokens(object) then
|
|
||||||
spawnTokensFor(object)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- functions delegated to Global
|
|
||||||
function drawChaostokenButton(object, player, isRightClick)
|
|
||||||
-- local toPosition = self.positionToWorld(DRAWN_CHAOS_TOKEN_OFFSET)
|
|
||||||
Global.call("drawChaostoken", {self, DRAWN_CHAOS_TOKEN_OFFSET, isRightClick})
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawEncountercard(object, player, isRightClick)
|
|
||||||
local toPosition = self.positionToWorld(DRAWN_ENCOUNTER_CARD_OFFSET)
|
|
||||||
Global.call("drawEncountercard", {toPosition, self.getRotation(), isRightClick})
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnToken(position, tokenType)
|
|
||||||
Global.call('spawnToken', {position, tokenType})
|
|
||||||
end
|
|
||||||
|
|
||||||
function updatePlayerCards(args)
|
|
||||||
local custom_data_helper = getObjectFromGUID(args[1])
|
|
||||||
data_player_cards = custom_data_helper.getTable("PLAYER_CARD_DATA")
|
|
||||||
for k, v in pairs(data_player_cards) do
|
|
||||||
PLAYER_CARDS[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,480 +0,0 @@
|
|||||||
-- set true to enable debug logging
|
|
||||||
DEBUG = false
|
|
||||||
-- we use this to turn off collision handling (for clue spawning)
|
|
||||||
-- until after load is complete (probably a better way to do this)
|
|
||||||
COLLISION_ENABLED = false
|
|
||||||
-- position offsets, adjust these to reposition things relative to mat [x,y,z]
|
|
||||||
DRAWN_ENCOUNTER_CARD_OFFSET = {0.98, 0.5, -0.635}
|
|
||||||
DRAWN_CHAOS_TOKEN_OFFSET = {-1.2, 0.5, -0.45}
|
|
||||||
DISCARD_BUTTON_OFFSETS = {
|
|
||||||
{-0.98, 0.2, -0.945},
|
|
||||||
{-0.525, 0.2, -0.945},
|
|
||||||
{-0.07, 0.2, -0.945},
|
|
||||||
{0.39, 0.2, -0.945},
|
|
||||||
{0.84, 0.2, -0.945},
|
|
||||||
}
|
|
||||||
-- draw deck and discard zone
|
|
||||||
DECK_POSITION = { x=-1.4, y=0, z=0.3 }
|
|
||||||
DECK_ZONE_SCALE = { x=3, y=5, z=8 }
|
|
||||||
DRAW_DECK_POSITION = { x=-55, y=2.5, z=-22.7 }
|
|
||||||
|
|
||||||
-- play zone
|
|
||||||
PLAYER_COLOR = "Orange"
|
|
||||||
PLAY_ZONE_POSITION = { x=-54.53, y=4.10, z=-20.94}
|
|
||||||
PLAY_ZONE_ROTATION = { x=0, y=270, z=0 }
|
|
||||||
PLAY_ZONE_SCALE = { x=36.96, y=5.10, z=14.70}
|
|
||||||
|
|
||||||
RESOURCE_COUNTER_GUID = "816d84"
|
|
||||||
|
|
||||||
-- the position of the global discard pile
|
|
||||||
-- TODO: delegate to global for any auto discard actions
|
|
||||||
DISCARD_POSITION = {-3.85, 3, 10.38}
|
|
||||||
|
|
||||||
function log(message)
|
|
||||||
if DEBUG then
|
|
||||||
print(message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- builds a function that discards things in searchPostion to discardPostition
|
|
||||||
function makeDiscardHandlerFor(searchPosition, discardPosition)
|
|
||||||
return function (_)
|
|
||||||
local discardItemList = findObjectsAtPosition(searchPosition)
|
|
||||||
for _, obj in ipairs(discardItemList) do
|
|
||||||
obj.setPositionSmooth(discardPosition, false, true)
|
|
||||||
obj.setRotation({0, -90, 0})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- build a discard button at position to discard from searchPosition to discardPosition
|
|
||||||
-- number must be unique
|
|
||||||
function makeDiscardButton(position, searchPosition, discardPosition, number)
|
|
||||||
local handler = makeDiscardHandlerFor(searchPosition, discardPosition)
|
|
||||||
local handlerName = 'handler' .. number
|
|
||||||
self.setVar(handlerName, handler)
|
|
||||||
self.createButton({
|
|
||||||
label = "Discard",
|
|
||||||
click_function= handlerName,
|
|
||||||
function_owner= self,
|
|
||||||
position = position,
|
|
||||||
scale = {0.12, 0.12, 0.12},
|
|
||||||
width = 800,
|
|
||||||
height = 280,
|
|
||||||
font_size = 180,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onload(save_state)
|
|
||||||
self.interactable = DEBUG
|
|
||||||
DATA_HELPER = getObjectFromGUID('708279')
|
|
||||||
PLAYER_CARDS = DATA_HELPER.getTable('PLAYER_CARD_DATA')
|
|
||||||
PLAYER_CARD_TOKEN_OFFSETS = DATA_HELPER.getTable('PLAYER_CARD_TOKEN_OFFSETS')
|
|
||||||
|
|
||||||
-- positions of encounter card slots
|
|
||||||
local encounterSlots = {
|
|
||||||
{1, 0, -0.7},
|
|
||||||
{0.55, 0, -0.7},
|
|
||||||
{0.1, 0, -0.7},
|
|
||||||
{-0.35, 0, -0.7},
|
|
||||||
{-0.8, 0, -0.7}
|
|
||||||
}
|
|
||||||
|
|
||||||
local i = 1
|
|
||||||
while i <= 5 do
|
|
||||||
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], encounterSlots[i], DISCARD_POSITION, i)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label = " ",
|
|
||||||
click_function = "drawEncountercard",
|
|
||||||
function_owner = self,
|
|
||||||
position = {-1.45,0,-0.7},
|
|
||||||
rotation = {0,-15,0},
|
|
||||||
width = 170,
|
|
||||||
height = 255,
|
|
||||||
font_size = 50
|
|
||||||
})
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label=" ",
|
|
||||||
click_function = "drawChaostokenButton",
|
|
||||||
function_owner = self,
|
|
||||||
position = {1.48,0.0,-0.74},
|
|
||||||
rotation = {0,-45,0},
|
|
||||||
width = 125,
|
|
||||||
height = 125,
|
|
||||||
font_size = 50
|
|
||||||
})
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label="Upkeep",
|
|
||||||
click_function = "doUpkeep",
|
|
||||||
function_owner = self,
|
|
||||||
position = {1.48,0.1,-0.44},
|
|
||||||
scale = {0.12, 0.12, 0.12},
|
|
||||||
width = 800,
|
|
||||||
height = 280,
|
|
||||||
font_size = 180
|
|
||||||
})
|
|
||||||
|
|
||||||
-- self.createButton({
|
|
||||||
-- label="Draw 1",
|
|
||||||
-- click_function = "doDrawOne",
|
|
||||||
-- function_owner = self,
|
|
||||||
-- position = {1.48,0.1,-0.36},
|
|
||||||
-- scale = {0.12, 0.12, 0.12},
|
|
||||||
-- width = 800,
|
|
||||||
-- height = 280,
|
|
||||||
-- font_size = 180
|
|
||||||
-- })
|
|
||||||
|
|
||||||
local state = JSON.decode(save_state)
|
|
||||||
if state ~= nil then
|
|
||||||
if state.playerColor ~= nil then
|
|
||||||
PLAYER_COLOR = state.playerColor
|
|
||||||
end
|
|
||||||
if state.zoneID ~= nil then
|
|
||||||
zoneID = state.zoneID
|
|
||||||
Wait.time(checkDeckZoneExists, 30)
|
|
||||||
else
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
|
|
||||||
COLLISION_ENABLED = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function onSave()
|
|
||||||
return JSON.encode({ zoneID=zoneID, playerColor=PLAYER_COLOR })
|
|
||||||
end
|
|
||||||
|
|
||||||
function setMessageColor(color)
|
|
||||||
-- send messages to player who clicked button if no seated player found
|
|
||||||
messageColor = Player[PLAYER_COLOR].seated and PLAYER_COLOR or color
|
|
||||||
end
|
|
||||||
|
|
||||||
function getDrawDiscardDecks(zone)
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
drawDeck = nil
|
|
||||||
discardPile = nil
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
|
||||||
if object.tag == "Deck" or object.tag == "Card" then
|
|
||||||
if object.is_face_down then
|
|
||||||
drawDeck = object
|
|
||||||
else
|
|
||||||
discardPile = object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkDeckThenDrawOne()
|
|
||||||
-- draw 1 card, shuffling the discard pile if necessary
|
|
||||||
if drawDeck == nil then
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(1), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
else
|
|
||||||
drawCards(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function doUpkeep(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Upkeep button bound to " .. color, color)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
setMessageColor(color)
|
|
||||||
|
|
||||||
-- unexhaust cards in play zone
|
|
||||||
local objs = Physics.cast({
|
|
||||||
origin = PLAY_ZONE_POSITION,
|
|
||||||
direction = { x=0, y=1, z=0 },
|
|
||||||
type = 3,
|
|
||||||
size = PLAY_ZONE_SCALE,
|
|
||||||
orientation = PLAY_ZONE_ROTATION
|
|
||||||
})
|
|
||||||
|
|
||||||
local y = PLAY_ZONE_ROTATION.y
|
|
||||||
|
|
||||||
local investigator = nil
|
|
||||||
for i,v in ipairs(objs) do
|
|
||||||
local obj = v.hit_object
|
|
||||||
local props = obj.getCustomObject()
|
|
||||||
if obj.tag == "Card" and not obj.is_face_down and not doNotReady(obj) then
|
|
||||||
if props ~= nil and props.unique_back then
|
|
||||||
local name = obj.getName()
|
|
||||||
if string.match(name, "Jenny Barnes") ~= nil then
|
|
||||||
investigator = "Jenny Barnes"
|
|
||||||
elseif name == "Patrice Hathaway" then
|
|
||||||
investigator = name
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local r = obj.getRotation()
|
|
||||||
if (r.y - y > 10) or (y - r.y > 10) then
|
|
||||||
obj.setRotation(PLAY_ZONE_ROTATION)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif obj.tag == "Board" and obj.getDescription() == "Action token" then
|
|
||||||
if obj.is_face_down then obj.flip() end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- gain resource
|
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
|
||||||
if investigator == "Jenny Barnes" then
|
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
|
||||||
printToColor("Taking 2 resources (Jenny)", messageColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- special draw for Patrice Hathaway (shuffle discards if necessary)
|
|
||||||
if investigator == "Patrice Hathaway" then
|
|
||||||
patriceDraw()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
|
||||||
|
|
||||||
function doDrawOne(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Draw 1 button bound to " .. color, color)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
setMessageColor(color)
|
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
|
||||||
|
|
||||||
function doNotReady(card)
|
|
||||||
if card.getVar("do_not_ready") == true then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawCards(numCards)
|
|
||||||
if drawDeck == nil then return end
|
|
||||||
drawDeck.deal(numCards, PLAYER_COLOR)
|
|
||||||
end
|
|
||||||
|
|
||||||
function shuffleDiscardIntoDeck()
|
|
||||||
discardPile.flip()
|
|
||||||
discardPile.shuffle()
|
|
||||||
discardPile.setPositionSmooth(DRAW_DECK_POSITION, false, false)
|
|
||||||
drawDeck = discardPile
|
|
||||||
discardPile = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function patriceDraw()
|
|
||||||
local handSize = #Player[PLAYER_COLOR].getHandObjects()
|
|
||||||
if handSize >= 5 then return end
|
|
||||||
local cardsToDraw = 5 - handSize
|
|
||||||
local deckSize
|
|
||||||
printToColor("Drawing " .. cardsToDraw .. " cards (Patrice)", messageColor)
|
|
||||||
if drawDeck == nil then
|
|
||||||
deckSize = 0
|
|
||||||
elseif drawDeck.tag == "Deck" then
|
|
||||||
deckSize = #drawDeck.getObjects()
|
|
||||||
else
|
|
||||||
deckSize = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if deckSize >= cardsToDraw then
|
|
||||||
drawCards(cardsToDraw)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
drawCards(deckSize)
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(cardsToDraw - deckSize), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkDeckZoneExists()
|
|
||||||
if getObjectFromGUID(zoneID) ~= nil then return end
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnDeckZone()
|
|
||||||
local pos = self.positionToWorld(DECK_POSITION)
|
|
||||||
local zoneProps = {
|
|
||||||
position = pos,
|
|
||||||
scale = DECK_ZONE_SCALE,
|
|
||||||
type = 'ScriptingTrigger',
|
|
||||||
callback = 'zoneCallback',
|
|
||||||
callback_owner = self,
|
|
||||||
rotation = self.getRotation()
|
|
||||||
}
|
|
||||||
spawnObject(zoneProps)
|
|
||||||
end
|
|
||||||
|
|
||||||
function zoneCallback(zone)
|
|
||||||
zoneID = zone.getGUID()
|
|
||||||
end
|
|
||||||
|
|
||||||
function findObjectsAtPosition(localPos)
|
|
||||||
local globalPos = self.positionToWorld(localPos)
|
|
||||||
local objList = Physics.cast({
|
|
||||||
origin=globalPos, --Where the cast takes place
|
|
||||||
direction={0,1,0}, --Which direction it moves (up is shown)
|
|
||||||
type=2, --Type. 2 is "sphere"
|
|
||||||
size={2,2,2}, --How large that sphere is
|
|
||||||
max_distance=1, --How far it moves. Just a little bit
|
|
||||||
debug=false --If it displays the sphere when casting.
|
|
||||||
})
|
|
||||||
local decksAndCards = {}
|
|
||||||
for _, obj in ipairs(objList) do
|
|
||||||
if obj.hit_object.tag == "Deck" or obj.hit_object.tag == "Card" then
|
|
||||||
table.insert(decksAndCards, obj.hit_object)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return decksAndCards
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnTokenOn(object, offsets, tokenType)
|
|
||||||
local tokenPosition = object.positionToWorld(offsets)
|
|
||||||
spawnToken(tokenPosition, tokenType)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- spawn a group of tokens of the given type on the object
|
|
||||||
function spawnTokenGroup(object, tokenType, tokenCount)
|
|
||||||
local offsets = PLAYER_CARD_TOKEN_OFFSETS[tokenCount]
|
|
||||||
if offsets == nil then
|
|
||||||
error("couldn't find offsets for " .. tokenCount .. ' tokens')
|
|
||||||
end
|
|
||||||
local i = 0
|
|
||||||
while i < tokenCount do
|
|
||||||
local offset = offsets[i + 1]
|
|
||||||
spawnTokenOn(object, offset, tokenType)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function buildPlayerCardKey(object)
|
|
||||||
return object.getName() .. ':' .. object.getDescription()
|
|
||||||
end
|
|
||||||
|
|
||||||
function getPlayerCardData(object)
|
|
||||||
return PLAYER_CARDS[buildPlayerCardKey(object)] or PLAYER_CARDS[object.getName()]
|
|
||||||
end
|
|
||||||
|
|
||||||
function shouldSpawnTokens(object)
|
|
||||||
-- we assume we shouldn't spawn tokens if in doubt, this should
|
|
||||||
-- only ever happen on load and in that case prevents respawns
|
|
||||||
local spawned = DATA_HELPER.call('getSpawnedPlayerCardGuid', {object.getGUID()})
|
|
||||||
local canSpawn = getPlayerCardData(object)
|
|
||||||
return not spawned and canSpawn
|
|
||||||
end
|
|
||||||
|
|
||||||
function markSpawned(object)
|
|
||||||
local saved = DATA_HELPER.call('setSpawnedPlayerCardGuid', {object.getGUID(), true})
|
|
||||||
if not saved then
|
|
||||||
error('attempt to mark player card spawned before data loaded')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnTokensFor(object)
|
|
||||||
local data = getPlayerCardData(object)
|
|
||||||
if data == nil then
|
|
||||||
error('attempt to spawn tokens for ' .. object.getName() .. ': no token data')
|
|
||||||
end
|
|
||||||
log(object.getName() .. '[' .. object.getDescription() .. ']' .. ' : ' .. data['tokenType'] .. ' : ' .. data['tokenCount'])
|
|
||||||
spawnTokenGroup(object, data['tokenType'], data['tokenCount'])
|
|
||||||
markSpawned(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
function resetSpawnState()
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
|
||||||
if object.tag == "Card" then
|
|
||||||
local guid = object.getGUID()
|
|
||||||
if guid ~= nil then unmarkSpawned(guid, true) end
|
|
||||||
elseif object.tag == "Deck" then
|
|
||||||
local cards = object.getObjects()
|
|
||||||
if (cards ~= nil) then
|
|
||||||
for i,v in ipairs(cards) do
|
|
||||||
if v.guid ~= nil then unmarkSpawned(v.guid) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function unmarkSpawned(guid, force)
|
|
||||||
if not force and getObjectFromGUID(guid) ~= nil then return end
|
|
||||||
DATA_HELPER.call('setSpawnedPlayerCardGuid', {guid, false})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onCollisionEnter(collision_info)
|
|
||||||
if not COLLISION_ENABLED then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local object = collision_info.collision_object
|
|
||||||
Wait.time(resetSpawnState, 1)
|
|
||||||
-- anything to the left of this is legal to spawn
|
|
||||||
local discardSpawnBoundary = self.positionToWorld({-1.2, 0, 0})
|
|
||||||
local boundaryLocalToCard = object.positionToLocal(discardSpawnBoundary)
|
|
||||||
if boundaryLocalToCard.x > 0 then
|
|
||||||
log('not checking for token spawn, boundary relative is ' .. boundaryLocalToCard.x)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not object.is_face_down and shouldSpawnTokens(object) then
|
|
||||||
spawnTokensFor(object)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- functions delegated to Global
|
|
||||||
function drawChaostokenButton(object, player, isRightClick)
|
|
||||||
-- local toPosition = self.positionToWorld(DRAWN_CHAOS_TOKEN_OFFSET)
|
|
||||||
Global.call("drawChaostoken", {self, DRAWN_CHAOS_TOKEN_OFFSET, isRightClick})
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawEncountercard(object, player, isRightClick)
|
|
||||||
local toPosition = self.positionToWorld(DRAWN_ENCOUNTER_CARD_OFFSET)
|
|
||||||
Global.call("drawEncountercard", {toPosition, self.getRotation(), isRightClick})
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnToken(position, tokenType)
|
|
||||||
Global.call('spawnToken', {position, tokenType})
|
|
||||||
end
|
|
||||||
|
|
||||||
function updatePlayerCards(args)
|
|
||||||
local custom_data_helper = getObjectFromGUID(args[1])
|
|
||||||
data_player_cards = custom_data_helper.getTable("PLAYER_CARD_DATA")
|
|
||||||
for k, v in pairs(data_player_cards) do
|
|
||||||
PLAYER_CARDS[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,480 +0,0 @@
|
|||||||
-- set true to enable debug logging
|
|
||||||
DEBUG = false
|
|
||||||
-- we use this to turn off collision handling (for clue spawning)
|
|
||||||
-- until after load is complete (probably a better way to do this)
|
|
||||||
COLLISION_ENABLED = false
|
|
||||||
-- position offsets, adjust these to reposition things relative to mat [x,y,z]
|
|
||||||
DRAWN_ENCOUNTER_CARD_OFFSET = {0.98, 0.5, -0.635}
|
|
||||||
DRAWN_CHAOS_TOKEN_OFFSET = {-1.2, 0.5, -0.45}
|
|
||||||
DISCARD_BUTTON_OFFSETS = {
|
|
||||||
{-0.98, 0.2, -0.945},
|
|
||||||
{-0.525, 0.2, -0.945},
|
|
||||||
{-0.07, 0.2, -0.945},
|
|
||||||
{0.39, 0.2, -0.945},
|
|
||||||
{0.84, 0.2, -0.945},
|
|
||||||
}
|
|
||||||
-- draw deck and discard zone
|
|
||||||
DECK_POSITION = { x=-1.4, y=0, z=0.3 }
|
|
||||||
DECK_ZONE_SCALE = { x=3, y=5, z=8 }
|
|
||||||
DRAW_DECK_POSITION = { x=-55, y=2.5, z=4.5 }
|
|
||||||
|
|
||||||
-- play zone
|
|
||||||
PLAYER_COLOR = "White"
|
|
||||||
PLAY_ZONE_POSITION = { x=-54.42, y=4.10, z=20.96}
|
|
||||||
PLAY_ZONE_ROTATION = { x=0, y=270, z=0 }
|
|
||||||
PLAY_ZONE_SCALE = { x=36.63, y=5.10, z=14.59}
|
|
||||||
|
|
||||||
RESOURCE_COUNTER_GUID = "4406f0"
|
|
||||||
|
|
||||||
-- the position of the global discard pile
|
|
||||||
-- TODO: delegate to global for any auto discard actions
|
|
||||||
DISCARD_POSITION = {-3.85, 3, 10.38}
|
|
||||||
|
|
||||||
function log(message)
|
|
||||||
if DEBUG then
|
|
||||||
print(message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- builds a function that discards things in searchPostion to discardPostition
|
|
||||||
function makeDiscardHandlerFor(searchPosition, discardPosition)
|
|
||||||
return function (_)
|
|
||||||
local discardItemList = findObjectsAtPosition(searchPosition)
|
|
||||||
for _, obj in ipairs(discardItemList) do
|
|
||||||
obj.setPositionSmooth(discardPosition, false, true)
|
|
||||||
obj.setRotation({0, -90, 0})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- build a discard button at position to discard from searchPosition to discardPosition
|
|
||||||
-- number must be unique
|
|
||||||
function makeDiscardButton(position, searchPosition, discardPosition, number)
|
|
||||||
local handler = makeDiscardHandlerFor(searchPosition, discardPosition)
|
|
||||||
local handlerName = 'handler' .. number
|
|
||||||
self.setVar(handlerName, handler)
|
|
||||||
self.createButton({
|
|
||||||
label = "Discard",
|
|
||||||
click_function= handlerName,
|
|
||||||
function_owner= self,
|
|
||||||
position = position,
|
|
||||||
scale = {0.12, 0.12, 0.12},
|
|
||||||
width = 800,
|
|
||||||
height = 280,
|
|
||||||
font_size = 180,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onload(save_state)
|
|
||||||
self.interactable = DEBUG
|
|
||||||
DATA_HELPER = getObjectFromGUID('708279')
|
|
||||||
PLAYER_CARDS = DATA_HELPER.getTable('PLAYER_CARD_DATA')
|
|
||||||
PLAYER_CARD_TOKEN_OFFSETS = DATA_HELPER.getTable('PLAYER_CARD_TOKEN_OFFSETS')
|
|
||||||
|
|
||||||
-- positions of encounter card slots
|
|
||||||
local encounterSlots = {
|
|
||||||
{1, 0, -0.7},
|
|
||||||
{0.55, 0, -0.7},
|
|
||||||
{0.1, 0, -0.7},
|
|
||||||
{-0.35, 0, -0.7},
|
|
||||||
{-0.8, 0, -0.7}
|
|
||||||
}
|
|
||||||
|
|
||||||
local i = 1
|
|
||||||
while i <= 5 do
|
|
||||||
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], encounterSlots[i], DISCARD_POSITION, i)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label = " ",
|
|
||||||
click_function = "drawEncountercard",
|
|
||||||
function_owner = self,
|
|
||||||
position = {-1.45,0,-0.7},
|
|
||||||
rotation = {0,-15,0},
|
|
||||||
width = 170,
|
|
||||||
height = 255,
|
|
||||||
font_size = 50
|
|
||||||
})
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label=" ",
|
|
||||||
click_function = "drawChaostokenButton",
|
|
||||||
function_owner = self,
|
|
||||||
position = {1.48,0.0,-0.74},
|
|
||||||
rotation = {0,-45,0},
|
|
||||||
width = 125,
|
|
||||||
height = 125,
|
|
||||||
font_size = 50
|
|
||||||
})
|
|
||||||
|
|
||||||
self.createButton({
|
|
||||||
label="Upkeep",
|
|
||||||
click_function = "doUpkeep",
|
|
||||||
function_owner = self,
|
|
||||||
position = {1.48,0.1,-0.44},
|
|
||||||
scale = {0.12, 0.12, 0.12},
|
|
||||||
width = 800,
|
|
||||||
height = 280,
|
|
||||||
font_size = 180
|
|
||||||
})
|
|
||||||
|
|
||||||
-- self.createButton({
|
|
||||||
-- label="Draw 1",
|
|
||||||
-- click_function = "doDrawOne",
|
|
||||||
-- function_owner = self,
|
|
||||||
-- position = {1.48,0.1,-0.36},
|
|
||||||
-- scale = {0.12, 0.12, 0.12},
|
|
||||||
-- width = 800,
|
|
||||||
-- height = 280,
|
|
||||||
-- font_size = 180
|
|
||||||
-- })
|
|
||||||
|
|
||||||
local state = JSON.decode(save_state)
|
|
||||||
if state ~= nil then
|
|
||||||
if state.playerColor ~= nil then
|
|
||||||
PLAYER_COLOR = state.playerColor
|
|
||||||
end
|
|
||||||
if state.zoneID ~= nil then
|
|
||||||
zoneID = state.zoneID
|
|
||||||
Wait.time(checkDeckZoneExists, 30)
|
|
||||||
else
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
|
|
||||||
COLLISION_ENABLED = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function onSave()
|
|
||||||
return JSON.encode({ zoneID=zoneID, playerColor=PLAYER_COLOR })
|
|
||||||
end
|
|
||||||
|
|
||||||
function setMessageColor(color)
|
|
||||||
-- send messages to player who clicked button if no seated player found
|
|
||||||
messageColor = Player[PLAYER_COLOR].seated and PLAYER_COLOR or color
|
|
||||||
end
|
|
||||||
|
|
||||||
function getDrawDiscardDecks(zone)
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
drawDeck = nil
|
|
||||||
discardPile = nil
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
|
||||||
if object.tag == "Deck" or object.tag == "Card" then
|
|
||||||
if object.is_face_down then
|
|
||||||
drawDeck = object
|
|
||||||
else
|
|
||||||
discardPile = object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkDeckThenDrawOne()
|
|
||||||
-- draw 1 card, shuffling the discard pile if necessary
|
|
||||||
if drawDeck == nil then
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(1), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
else
|
|
||||||
drawCards(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function doUpkeep(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Upkeep button bound to " .. color, color)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
setMessageColor(color)
|
|
||||||
|
|
||||||
-- unexhaust cards in play zone
|
|
||||||
local objs = Physics.cast({
|
|
||||||
origin = PLAY_ZONE_POSITION,
|
|
||||||
direction = { x=0, y=1, z=0 },
|
|
||||||
type = 3,
|
|
||||||
size = PLAY_ZONE_SCALE,
|
|
||||||
orientation = PLAY_ZONE_ROTATION
|
|
||||||
})
|
|
||||||
|
|
||||||
local y = PLAY_ZONE_ROTATION.y
|
|
||||||
|
|
||||||
local investigator = nil
|
|
||||||
for i,v in ipairs(objs) do
|
|
||||||
local obj = v.hit_object
|
|
||||||
local props = obj.getCustomObject()
|
|
||||||
if obj.tag == "Card" and not obj.is_face_down and not doNotReady(obj) then
|
|
||||||
if props ~= nil and props.unique_back then
|
|
||||||
local name = obj.getName()
|
|
||||||
if string.match(name, "Jenny Barnes") ~= nil then
|
|
||||||
investigator = "Jenny Barnes"
|
|
||||||
elseif name == "Patrice Hathaway" then
|
|
||||||
investigator = name
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local r = obj.getRotation()
|
|
||||||
if (r.y - y > 10) or (y - r.y > 10) then
|
|
||||||
obj.setRotation(PLAY_ZONE_ROTATION)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif obj.tag == "Board" and obj.getDescription() == "Action token" then
|
|
||||||
if obj.is_face_down then obj.flip() end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- gain resource
|
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
|
||||||
if investigator == "Jenny Barnes" then
|
|
||||||
getObjectFromGUID(RESOURCE_COUNTER_GUID).call("add_subtract")
|
|
||||||
printToColor("Taking 2 resources (Jenny)", messageColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- special draw for Patrice Hathaway (shuffle discards if necessary)
|
|
||||||
if investigator == "Patrice Hathaway" then
|
|
||||||
patriceDraw()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
|
||||||
|
|
||||||
function doDrawOne(obj, color, alt_click)
|
|
||||||
-- right-click binds to new player color
|
|
||||||
if alt_click then
|
|
||||||
PLAYER_COLOR = color
|
|
||||||
printToColor("Draw 1 button bound to " .. color, color)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
setMessageColor(color)
|
|
||||||
|
|
||||||
-- get the draw deck and discard pile objects
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
getDrawDiscardDecks(zone)
|
|
||||||
|
|
||||||
-- draw 1 card (shuffle discards if necessary)
|
|
||||||
checkDeckThenDrawOne()
|
|
||||||
end
|
|
||||||
|
|
||||||
function doNotReady(card)
|
|
||||||
if card.getVar("do_not_ready") == true then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawCards(numCards)
|
|
||||||
if drawDeck == nil then return end
|
|
||||||
drawDeck.deal(numCards, PLAYER_COLOR)
|
|
||||||
end
|
|
||||||
|
|
||||||
function shuffleDiscardIntoDeck()
|
|
||||||
discardPile.flip()
|
|
||||||
discardPile.shuffle()
|
|
||||||
discardPile.setPositionSmooth(DRAW_DECK_POSITION, false, false)
|
|
||||||
drawDeck = discardPile
|
|
||||||
discardPile = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function patriceDraw()
|
|
||||||
local handSize = #Player[PLAYER_COLOR].getHandObjects()
|
|
||||||
if handSize >= 5 then return end
|
|
||||||
local cardsToDraw = 5 - handSize
|
|
||||||
local deckSize
|
|
||||||
printToColor("Drawing " .. cardsToDraw .. " cards (Patrice)", messageColor)
|
|
||||||
if drawDeck == nil then
|
|
||||||
deckSize = 0
|
|
||||||
elseif drawDeck.tag == "Deck" then
|
|
||||||
deckSize = #drawDeck.getObjects()
|
|
||||||
else
|
|
||||||
deckSize = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if deckSize >= cardsToDraw then
|
|
||||||
drawCards(cardsToDraw)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
drawCards(deckSize)
|
|
||||||
if discardPile ~= nil then
|
|
||||||
shuffleDiscardIntoDeck()
|
|
||||||
Wait.time(|| drawCards(cardsToDraw - deckSize), 1)
|
|
||||||
end
|
|
||||||
printToColor("Take 1 horror (drawing card from empty deck)", messageColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkDeckZoneExists()
|
|
||||||
if getObjectFromGUID(zoneID) ~= nil then return end
|
|
||||||
spawnDeckZone()
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnDeckZone()
|
|
||||||
local pos = self.positionToWorld(DECK_POSITION)
|
|
||||||
local zoneProps = {
|
|
||||||
position = pos,
|
|
||||||
scale = DECK_ZONE_SCALE,
|
|
||||||
type = 'ScriptingTrigger',
|
|
||||||
callback = 'zoneCallback',
|
|
||||||
callback_owner = self,
|
|
||||||
rotation = self.getRotation()
|
|
||||||
}
|
|
||||||
spawnObject(zoneProps)
|
|
||||||
end
|
|
||||||
|
|
||||||
function zoneCallback(zone)
|
|
||||||
zoneID = zone.getGUID()
|
|
||||||
end
|
|
||||||
|
|
||||||
function findObjectsAtPosition(localPos)
|
|
||||||
local globalPos = self.positionToWorld(localPos)
|
|
||||||
local objList = Physics.cast({
|
|
||||||
origin=globalPos, --Where the cast takes place
|
|
||||||
direction={0,1,0}, --Which direction it moves (up is shown)
|
|
||||||
type=2, --Type. 2 is "sphere"
|
|
||||||
size={2,2,2}, --How large that sphere is
|
|
||||||
max_distance=1, --How far it moves. Just a little bit
|
|
||||||
debug=false --If it displays the sphere when casting.
|
|
||||||
})
|
|
||||||
local decksAndCards = {}
|
|
||||||
for _, obj in ipairs(objList) do
|
|
||||||
if obj.hit_object.tag == "Deck" or obj.hit_object.tag == "Card" then
|
|
||||||
table.insert(decksAndCards, obj.hit_object)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return decksAndCards
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnTokenOn(object, offsets, tokenType)
|
|
||||||
local tokenPosition = object.positionToWorld(offsets)
|
|
||||||
spawnToken(tokenPosition, tokenType)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- spawn a group of tokens of the given type on the object
|
|
||||||
function spawnTokenGroup(object, tokenType, tokenCount)
|
|
||||||
local offsets = PLAYER_CARD_TOKEN_OFFSETS[tokenCount]
|
|
||||||
if offsets == nil then
|
|
||||||
error("couldn't find offsets for " .. tokenCount .. ' tokens')
|
|
||||||
end
|
|
||||||
local i = 0
|
|
||||||
while i < tokenCount do
|
|
||||||
local offset = offsets[i + 1]
|
|
||||||
spawnTokenOn(object, offset, tokenType)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function buildPlayerCardKey(object)
|
|
||||||
return object.getName() .. ':' .. object.getDescription()
|
|
||||||
end
|
|
||||||
|
|
||||||
function getPlayerCardData(object)
|
|
||||||
return PLAYER_CARDS[buildPlayerCardKey(object)] or PLAYER_CARDS[object.getName()]
|
|
||||||
end
|
|
||||||
|
|
||||||
function shouldSpawnTokens(object)
|
|
||||||
-- we assume we shouldn't spawn tokens if in doubt, this should
|
|
||||||
-- only ever happen on load and in that case prevents respawns
|
|
||||||
local spawned = DATA_HELPER.call('getSpawnedPlayerCardGuid', {object.getGUID()})
|
|
||||||
local canSpawn = getPlayerCardData(object)
|
|
||||||
return not spawned and canSpawn
|
|
||||||
end
|
|
||||||
|
|
||||||
function markSpawned(object)
|
|
||||||
local saved = DATA_HELPER.call('setSpawnedPlayerCardGuid', {object.getGUID(), true})
|
|
||||||
if not saved then
|
|
||||||
error('attempt to mark player card spawned before data loaded')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnTokensFor(object)
|
|
||||||
local data = getPlayerCardData(object)
|
|
||||||
if data == nil then
|
|
||||||
error('attempt to spawn tokens for ' .. object.getName() .. ': no token data')
|
|
||||||
end
|
|
||||||
log(object.getName() .. '[' .. object.getDescription() .. ']' .. ' : ' .. data['tokenType'] .. ' : ' .. data['tokenCount'])
|
|
||||||
spawnTokenGroup(object, data['tokenType'], data['tokenCount'])
|
|
||||||
markSpawned(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
function resetSpawnState()
|
|
||||||
local zone = getObjectFromGUID(zoneID)
|
|
||||||
if zone == nil then return end
|
|
||||||
|
|
||||||
for i,object in ipairs(zone.getObjects()) do
|
|
||||||
if object.tag == "Card" then
|
|
||||||
local guid = object.getGUID()
|
|
||||||
if guid ~= nil then unmarkSpawned(guid, true) end
|
|
||||||
elseif object.tag == "Deck" then
|
|
||||||
local cards = object.getObjects()
|
|
||||||
if (cards ~= nil) then
|
|
||||||
for i,v in ipairs(cards) do
|
|
||||||
if v.guid ~= nil then unmarkSpawned(v.guid) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function unmarkSpawned(guid, force)
|
|
||||||
if not force and getObjectFromGUID(guid) ~= nil then return end
|
|
||||||
DATA_HELPER.call('setSpawnedPlayerCardGuid', {guid, false})
|
|
||||||
end
|
|
||||||
|
|
||||||
function onCollisionEnter(collision_info)
|
|
||||||
if not COLLISION_ENABLED then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local object = collision_info.collision_object
|
|
||||||
Wait.time(resetSpawnState, 1)
|
|
||||||
-- anything to the left of this is legal to spawn
|
|
||||||
local discardSpawnBoundary = self.positionToWorld({-1.2, 0, 0})
|
|
||||||
local boundaryLocalToCard = object.positionToLocal(discardSpawnBoundary)
|
|
||||||
if boundaryLocalToCard.x > 0 then
|
|
||||||
log('not checking for token spawn, boundary relative is ' .. boundaryLocalToCard.x)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not object.is_face_down and shouldSpawnTokens(object) then
|
|
||||||
spawnTokensFor(object)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- functions delegated to Global
|
|
||||||
function drawChaostokenButton(object, player, isRightClick)
|
|
||||||
-- local toPosition = self.positionToWorld(DRAWN_CHAOS_TOKEN_OFFSET)
|
|
||||||
Global.call("drawChaostoken", {self, DRAWN_CHAOS_TOKEN_OFFSET, isRightClick})
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawEncountercard(object, player, isRightClick)
|
|
||||||
local toPosition = self.positionToWorld(DRAWN_ENCOUNTER_CARD_OFFSET)
|
|
||||||
Global.call("drawEncountercard", {toPosition, self.getRotation(), isRightClick})
|
|
||||||
end
|
|
||||||
|
|
||||||
function spawnToken(position, tokenType)
|
|
||||||
Global.call('spawnToken', {position, tokenType})
|
|
||||||
end
|
|
||||||
|
|
||||||
function updatePlayerCards(args)
|
|
||||||
local custom_data_helper = getObjectFromGUID(args[1])
|
|
||||||
data_player_cards = custom_data_helper.getTable("PLAYER_CARD_DATA")
|
|
||||||
for k, v in pairs(data_player_cards) do
|
|
||||||
PLAYER_CARDS[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
132
src/playermat/SkillToken.ttslua
Normal file
132
src/playermat/SkillToken.ttslua
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
MIN_VALUE = -99
|
||||||
|
MAX_VALUE = 999
|
||||||
|
|
||||||
|
function onload(saved_data)
|
||||||
|
light_mode = false
|
||||||
|
val = 0
|
||||||
|
|
||||||
|
if saved_data ~= "" then
|
||||||
|
local loaded_data = JSON.decode(saved_data)
|
||||||
|
light_mode = loaded_data[1]
|
||||||
|
val = loaded_data[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
createAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateSave()
|
||||||
|
local data_to_save = {light_mode, val}
|
||||||
|
saved_data = JSON.encode(data_to_save)
|
||||||
|
self.script_state = saved_data
|
||||||
|
end
|
||||||
|
|
||||||
|
function createAll()
|
||||||
|
s_color = {0.5, 0.5, 0.5, 95}
|
||||||
|
|
||||||
|
if light_mode then
|
||||||
|
f_color = {1,1,1,95}
|
||||||
|
else
|
||||||
|
f_color = {0,0,0,100}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.createButton({
|
||||||
|
label=tostring(val),
|
||||||
|
click_function="add_subtract",
|
||||||
|
function_owner=self,
|
||||||
|
position={0,0.05,0},
|
||||||
|
height=600,
|
||||||
|
width=1000,
|
||||||
|
alignment = 3,
|
||||||
|
scale={x=1.5, y=1.5, z=1.5},
|
||||||
|
font_size=600,
|
||||||
|
font_color=f_color,
|
||||||
|
color={0,0,0,0}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if light_mode then
|
||||||
|
lightButtonText = "[ Set dark ]"
|
||||||
|
else
|
||||||
|
lightButtonText = "[ Set light ]"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function removeAll()
|
||||||
|
self.removeInput(0)
|
||||||
|
self.removeInput(1)
|
||||||
|
self.removeButton(0)
|
||||||
|
self.removeButton(1)
|
||||||
|
self.removeButton(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function reloadAll()
|
||||||
|
removeAll()
|
||||||
|
createAll()
|
||||||
|
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
|
||||||
|
function swap_fcolor(_obj, _color, alt_click)
|
||||||
|
light_mode = not light_mode
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function swap_align(_obj, _color, alt_click)
|
||||||
|
center_mode = not center_mode
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function editName(_obj, _string, value)
|
||||||
|
self.setName(value)
|
||||||
|
setTooltips()
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_subtract(_obj, _color, alt_click)
|
||||||
|
mod = alt_click and -1 or 1
|
||||||
|
new_value = math.min(math.max(val + mod, MIN_VALUE), MAX_VALUE)
|
||||||
|
if val ~= new_value then
|
||||||
|
val = new_value
|
||||||
|
updateVal()
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateVal()
|
||||||
|
|
||||||
|
self.editButton({
|
||||||
|
index = 0,
|
||||||
|
label = tostring(val),
|
||||||
|
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function reset_val()
|
||||||
|
val = 0
|
||||||
|
updateVal()
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
|
||||||
|
function setTooltips()
|
||||||
|
self.editInput({
|
||||||
|
index = 0,
|
||||||
|
value = self.getName(),
|
||||||
|
tooltip = ttText
|
||||||
|
})
|
||||||
|
self.editButton({
|
||||||
|
index = 0,
|
||||||
|
value = tostring(val),
|
||||||
|
tooltip = ttText
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function null()
|
||||||
|
end
|
||||||
|
|
||||||
|
function keepSample(_obj, _string, value)
|
||||||
|
reloadAll()
|
||||||
|
end
|
164
src/playermat/Zones.ttslua
Normal file
164
src/playermat/Zones.ttslua
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
-- Sets up and returns coordinates for all possible spawn zones. Because Lua assigns tables by reference
|
||||||
|
-- and there is no built-in function to copy a table this is relatively brute force.
|
||||||
|
--
|
||||||
|
-- Positions are all relative to the player mat, and most are consistent. The
|
||||||
|
-- exception are the SetAside# zones, which are placed to the left of the mat
|
||||||
|
-- for White/Green, and the right of the mat for Orange/Red.
|
||||||
|
--
|
||||||
|
-- Investigator: Investigator card area.
|
||||||
|
-- Minicard: Placement for the investigator's minicard, just above the player mat
|
||||||
|
-- Deck, Discard: Standard locations for the deck and discard piles.
|
||||||
|
-- BlankTop: used for assets that start in play (e.g. Duke)
|
||||||
|
-- Tarot, Hand1, Hand2, Ally, BlankBottom, Accessory, Arcane1, Arcane2, Body: Asset slot positions
|
||||||
|
-- Threat[1-4]: Threat area slots. Threat[1-3] correspond to the named threat area slots, and Threat4 is the blank threat area slot.
|
||||||
|
-- SetAside[1-3]: Column closest to the player mat, with 1 at the top and 3 at the bottom.
|
||||||
|
-- SetAside[4-6]: Column farther away from the mat, with 4 at the top and 6 at the bottom.
|
||||||
|
-- SetAside1: Permanent cards
|
||||||
|
-- SetAside2: Bonded cards
|
||||||
|
-- SetAside3: Ancestral Knowledge / Underworld Market
|
||||||
|
-- SetAside4: Upgrade sheets for customizable cards
|
||||||
|
-- SetAside5: Hunch Deck for Joe Diamond
|
||||||
|
-- SetAside6: currently unused
|
||||||
|
do
|
||||||
|
|
||||||
|
local playerMatGuids = {}
|
||||||
|
playerMatGuids["Red"] = "0840d5"
|
||||||
|
playerMatGuids["Orange"] = "bd0ff4"
|
||||||
|
playerMatGuids["White"] = "8b081b"
|
||||||
|
playerMatGuids["Green"] = "383d8b"
|
||||||
|
|
||||||
|
commonZones = {}
|
||||||
|
commonZones["Investigator"] = { -1.17702, 0, 0.00209 }
|
||||||
|
commonZones["Minicard"] = { -0.4668214, 0, -1.222326 }
|
||||||
|
commonZones["Deck"] = { -1.822724, 0, -0.02940192 }
|
||||||
|
commonZones["Discard"] = { -1.822451, 0, 0.6092291 }
|
||||||
|
commonZones["Ally"] = { -0.6157398, 0, 0.02435675 }
|
||||||
|
commonZones["Body"] = { -0.6306521, 0, 0.553170 }
|
||||||
|
commonZones["Hand1"] = { 0.2155387, 0, 0.04257287 }
|
||||||
|
commonZones["Hand2"] = { -0.1803701, 0, 0.03745948 }
|
||||||
|
commonZones["Arcane1"] = { 0.2124223, 0, 0.5596902 }
|
||||||
|
commonZones["Arcane2"] = { -0.1711275, 0, 0.5567944 }
|
||||||
|
commonZones["Tarot"] = { 0.6016169, 0, 0.03273106 }
|
||||||
|
commonZones["Accessory"] = { 0.6049907, 0, 0.5546234 }
|
||||||
|
commonZones["BlankTop"] = { 1.758446, 0, 0.03965336 }
|
||||||
|
commonZones["BlankBottom"] = { 1.754469, 0, 0.5634764 }
|
||||||
|
commonZones["Threat1"] = { -0.9116555, 0, -0.6446251 }
|
||||||
|
commonZones["Threat2"] = { -0.4544126, 0, -0.6428719 }
|
||||||
|
commonZones["Threat3"] = { 0.002246313, 0, -0.6430681 }
|
||||||
|
commonZones["Threat4"] = { 0.4590618, 0, -0.6432732 }
|
||||||
|
|
||||||
|
Zones = {}
|
||||||
|
Zones["White"] = {}
|
||||||
|
Zones["White"]["Investigator"] = commonZones["Investigator"]
|
||||||
|
Zones["White"]["Minicard"] = commonZones["Minicard"]
|
||||||
|
Zones["White"]["Deck"] = commonZones["Deck"]
|
||||||
|
Zones["White"]["Discard"] = commonZones["Discard"]
|
||||||
|
Zones["White"]["Ally"] = commonZones["Ally"]
|
||||||
|
Zones["White"]["Body"] = commonZones["Body"]
|
||||||
|
Zones["White"]["Hand1"] = commonZones["Hand1"]
|
||||||
|
Zones["White"]["Hand2"] = commonZones["Hand2"]
|
||||||
|
Zones["White"]["Arcane1"] = commonZones["Arcane1"]
|
||||||
|
Zones["White"]["Arcane2"] = commonZones["Arcane2"]
|
||||||
|
Zones["White"]["Tarot"] = commonZones["Tarot"]
|
||||||
|
Zones["White"]["Accessory"] = commonZones["Accessory"]
|
||||||
|
Zones["White"]["BlankTop"] = commonZones["BlankTop"]
|
||||||
|
Zones["White"]["BlankBottom"] = commonZones["BlankBottom"]
|
||||||
|
Zones["White"]["Threat1"] = commonZones["Threat1"]
|
||||||
|
Zones["White"]["Threat2"] = commonZones["Threat2"]
|
||||||
|
Zones["White"]["Threat3"] = commonZones["Threat3"]
|
||||||
|
Zones["White"]["Threat4"] = commonZones["Threat4"]
|
||||||
|
Zones["White"]["SetAside1"] = { 2.345893, 0, -0.520315 }
|
||||||
|
Zones["White"]["SetAside2"] = { 2.345893, 0, 0.042552 }
|
||||||
|
Zones["White"]["SetAside3"] = { 2.345893, 0, 0.605419 }
|
||||||
|
Zones["White"]["UnderSetAside3"] = { 2.495893, 0, 0.805419 }
|
||||||
|
Zones["White"]["SetAside4"] = { 2.775893, 0, -0.520315 }
|
||||||
|
Zones["White"]["SetAside5"] = { 2.775893, 0, 0.042552 }
|
||||||
|
Zones["White"]["SetAside6"] = { 2.775893, 0, 0.605419 }
|
||||||
|
Zones["White"]["UnderSetAside6"] = { 2.925893, 0, 0.805419 }
|
||||||
|
|
||||||
|
Zones["Orange"] = {}
|
||||||
|
Zones["Orange"]["Investigator"] = commonZones["Investigator"]
|
||||||
|
Zones["Orange"]["Minicard"] = commonZones["Minicard"]
|
||||||
|
Zones["Orange"]["Deck"] = commonZones["Deck"]
|
||||||
|
Zones["Orange"]["Discard"] = commonZones["Discard"]
|
||||||
|
Zones["Orange"]["Ally"] = commonZones["Ally"]
|
||||||
|
Zones["Orange"]["Body"] = commonZones["Body"]
|
||||||
|
Zones["Orange"]["Hand1"] = commonZones["Hand1"]
|
||||||
|
Zones["Orange"]["Hand2"] = commonZones["Hand2"]
|
||||||
|
Zones["Orange"]["Arcane1"] = commonZones["Arcane1"]
|
||||||
|
Zones["Orange"]["Arcane2"] = commonZones["Arcane2"]
|
||||||
|
Zones["Orange"]["Tarot"] = commonZones["Tarot"]
|
||||||
|
Zones["Orange"]["Accessory"] = commonZones["Accessory"]
|
||||||
|
Zones["Orange"]["BlankTop"] = commonZones["BlankTop"]
|
||||||
|
Zones["Orange"]["BlankBottom"] = commonZones["BlankBottom"]
|
||||||
|
Zones["Orange"]["Threat1"] = commonZones["Threat1"]
|
||||||
|
Zones["Orange"]["Threat2"] = commonZones["Threat2"]
|
||||||
|
Zones["Orange"]["Threat3"] = commonZones["Threat3"]
|
||||||
|
Zones["Orange"]["Threat4"] = commonZones["Threat4"]
|
||||||
|
Zones["Orange"]["SetAside1"] = { -2.350362, 0, -0.520315 }
|
||||||
|
Zones["Orange"]["SetAside2"] = { -2.350362, 0, 0.042552 }
|
||||||
|
Zones["Orange"]["SetAside3"] = { -2.350362, 0, 0.605419 }
|
||||||
|
Zones["Orange"]["UnderSetAside3"] = { -2.500362, 0, 0.80419 }
|
||||||
|
Zones["Orange"]["SetAside4"] = { -2.7803627, 0, -0.520315 }
|
||||||
|
Zones["Orange"]["SetAside5"] = { -2.7803627, 0, 0.042552 }
|
||||||
|
Zones["Orange"]["SetAside6"] = { -2.7803627, 0, 0.605419 }
|
||||||
|
Zones["Orange"]["UnderSetAside6"] = { -2.9303627, 0, 0.80419 }
|
||||||
|
|
||||||
|
-- Green positions are the same as White and Red the same as Orange
|
||||||
|
Zones["Red"] = Zones["Orange"]
|
||||||
|
Zones["Green"] = Zones["White"]
|
||||||
|
|
||||||
|
-- Returns the zone name where the specified card should be placed, based on its metadata.
|
||||||
|
---@param cardMetadata: Table of card metadata. Metadata fields type and permanent are required; all others are optional.
|
||||||
|
---@return: Zone name such as "Deck", "SetAside1", etc. See Zones object documentation for a list of valid zones.
|
||||||
|
function Zones.getDefaultCardZone(cardMetadata)
|
||||||
|
if cardMetadata.type == "Investigator" then
|
||||||
|
return "Investigator"
|
||||||
|
elseif cardMetadata.type == "Minicard" then
|
||||||
|
return "Minicard"
|
||||||
|
elseif cardMetadata.permanent then
|
||||||
|
return "SetAside1"
|
||||||
|
-- TODO: Figure out how to handled bonded information which isn't her now that we split the file
|
||||||
|
-- elseif bondedList[cardMetadata.id] then
|
||||||
|
-- return "SetAside2"
|
||||||
|
-- SetAside3 is used for Ancestral Knowledge / Underworld Market
|
||||||
|
-- SetAside4 is used for upgrade sheets
|
||||||
|
else
|
||||||
|
return "Deck"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Gets the global position for the given zone on the specified player mat.
|
||||||
|
---@param playerColor: Color name of the player mat to get the zone position for (e.g. "Red")
|
||||||
|
---@param zoneName: Name of the zone to get the position for. See Zones object documentation for a list of valid zones.
|
||||||
|
---@return: Global position table, or nil if an invalid player color or zone is specified
|
||||||
|
function Zones.getZonePosition(playerColor, zoneName)
|
||||||
|
if (playerColor ~= "Red"
|
||||||
|
and playerColor ~= "Orange"
|
||||||
|
and playerColor ~= "White"
|
||||||
|
and playerColor ~= "Green") then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return getObjectFromGUID(playerMatGuids[playerColor]).positionToWorld(Zones[playerColor][zoneName])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return the global rotation for a card on the given player mat, based on its metadata.
|
||||||
|
---@param playerColor: Color name of the player mat to get the rotation for (e.g. "Red")
|
||||||
|
---@param cardMetadata: Table of card metadata. Metadata fields type and permanent are required; all others are optional.
|
||||||
|
---@return: Global rotation vector for the given card. This will include the
|
||||||
|
-- Y rotation to orient the card on the given player mat as well as a
|
||||||
|
-- Z rotation to place the card face up or face down.
|
||||||
|
function Zones.getDefaultCardRotation(playerColor, zone)
|
||||||
|
local deckRotation = getObjectFromGUID(playerMatGuids[playerColor]).getRotation()
|
||||||
|
|
||||||
|
if zone == "Investigator" then
|
||||||
|
deckRotation = deckRotation + Vector(0, 270, 0)
|
||||||
|
elseif zone == "Deck" then
|
||||||
|
deckRotation = deckRotation + Vector(0, 0, 180)
|
||||||
|
end
|
||||||
|
|
||||||
|
return deckRotation
|
||||||
|
end
|
||||||
|
|
||||||
|
return Zones
|
||||||
|
end
|
@ -1,5 +1,5 @@
|
|||||||
BLESS_COLOR = { r=0.3, g=0.25, b=0.09 }
|
BLESS_COLOR = { r = 0.3, g = 0.25, b = 0.09 }
|
||||||
CURSE_COLOR = { r=0.2, g=0.08, b=0.24 }
|
CURSE_COLOR = { r = 0.2, g = 0.08, b = 0.24 }
|
||||||
MIN_VALUE = 1
|
MIN_VALUE = 1
|
||||||
MAX_VALUE = 10
|
MAX_VALUE = 10
|
||||||
IMAGE_URL = {
|
IMAGE_URL = {
|
||||||
@ -9,120 +9,122 @@ IMAGE_URL = {
|
|||||||
|
|
||||||
function onload()
|
function onload()
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Add",
|
label = "Add",
|
||||||
click_function="addBlessToken",
|
click_function = "addBlessToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={-2.3,0.1,-0.5},
|
position = { -2.3, 0.1, -0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=300,
|
width = 300,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=BLESS_COLOR
|
color = BLESS_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Remove",
|
label = "Remove",
|
||||||
click_function="removeBlessToken",
|
click_function = "removeBlessToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={-0.9,0.1,-0.5},
|
position = { -0.9, 0.1, -0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=450,
|
width = 450,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=BLESS_COLOR
|
color = BLESS_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Take",
|
label = "Take",
|
||||||
click_function="takeBlessToken",
|
click_function = "takeBlessToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={0.7,0.1,-0.5},
|
position = { 0.7, 0.1, -0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=350,
|
width = 350,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=BLESS_COLOR
|
color = BLESS_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Return",
|
label = "Return",
|
||||||
click_function="returnBlessToken",
|
click_function = "returnBlessToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={2.1,0.1,-0.5},
|
position = { 2.1, 0.1, -0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=400,
|
width = 400,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=BLESS_COLOR
|
color = BLESS_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Add",
|
label = "Add",
|
||||||
click_function="addCurseToken",
|
click_function = "addCurseToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={-2.3,0.1,0.5},
|
position = { -2.3, 0.1, 0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=300,
|
width = 300,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=CURSE_COLOR
|
color = CURSE_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Remove",
|
label = "Remove",
|
||||||
click_function="removeCurseToken",
|
click_function = "removeCurseToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={-0.9,0.1,0.5},
|
position = { -0.9, 0.1, 0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=450,
|
width = 450,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=CURSE_COLOR
|
color = CURSE_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Take",
|
label = "Take",
|
||||||
click_function="takeCurseToken",
|
click_function = "takeCurseToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={0.7,0.1,0.5},
|
position = { 0.7, 0.1, 0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=350,
|
width = 350,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=CURSE_COLOR
|
color = CURSE_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Return",
|
label = "Return",
|
||||||
click_function="returnCurseToken",
|
click_function = "returnCurseToken",
|
||||||
function_owner=self,
|
function_owner = self,
|
||||||
position={2.1,0.1,0.5},
|
position = { 2.1, 0.1, 0.5 },
|
||||||
height=150,
|
height = 150,
|
||||||
width=400,
|
width = 400,
|
||||||
scale={x=1.75, y=1.75, z=1.75},
|
scale = { x = 1.75, y = 1.75, z = 1.75 },
|
||||||
font_size=100,
|
font_size = 100,
|
||||||
font_color={ r=1, g=1, b=1 },
|
font_color = { r = 1, g = 1, b = 1 },
|
||||||
color=CURSE_COLOR
|
color = CURSE_COLOR
|
||||||
})
|
})
|
||||||
|
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Reset", click_function="doReset", function_owner=self,
|
label = "Reset", click_function = "doReset", function_owner = self,
|
||||||
position={0,0.3,1.8}, rotation={0,0,0}, height=350, width=800,
|
position = { 0, 0, 1.8 }, rotation = { 0, 0, 0 }, height = 350, width = 800,
|
||||||
font_size=250, color={0,0,0}, font_color={1,1,1}
|
font_size = 250, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
|
|
||||||
numInPlay = { Bless=0, Curse=0 }
|
numInPlay = { Bless = 0, Curse = 0 }
|
||||||
tokensTaken = { Bless={}, Curse={} }
|
tokensTaken = { Bless = {}, Curse = {} }
|
||||||
|
sealedTokens = {}
|
||||||
Wait.time(initializeState, 1)
|
Wait.time(initializeState, 1)
|
||||||
|
|
||||||
addHotkey("Bless Curse Status", printStatus, false)
|
addHotkey("Bless Curse Status", printStatus, false)
|
||||||
|
addHotkey("Wendy's Menu", addMenuOptions, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
function initializeState()
|
function initializeState()
|
||||||
@ -131,7 +133,7 @@ function initializeState()
|
|||||||
local chaosbag = getChaosBag()
|
local chaosbag = getChaosBag()
|
||||||
if chaosbag == nil then return end
|
if chaosbag == nil then return end
|
||||||
local tokens = {}
|
local tokens = {}
|
||||||
for i,v in ipairs(chaosbag.getObjects()) do
|
for i, v in ipairs(chaosbag.getObjects()) do
|
||||||
if v.name == "Bless" then
|
if v.name == "Bless" then
|
||||||
numInPlay.Bless = numInPlay.Bless + 1
|
numInPlay.Bless = numInPlay.Bless + 1
|
||||||
elseif v.name == "Curse" then
|
elseif v.name == "Curse" then
|
||||||
@ -140,22 +142,17 @@ function initializeState()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- find tokens in the play area
|
-- find tokens in the play area
|
||||||
local objs = Physics.cast({
|
local objs = getObjects()
|
||||||
origin = { x=-33, y=0, z=0.5 },
|
for i, obj in ipairs(objs) do
|
||||||
direction = { x=0, y=1, z=0 },
|
local pos = obj.getPosition()
|
||||||
type = 3,
|
if (pos.x > -110 and pos.x < 44 and pos.z > -77 and pos.z < 77) then
|
||||||
size = { x=77, y=5, z=77 },
|
if obj.getName() == "Bless" then
|
||||||
orientation = { x=0, y=90, z=0 }
|
table.insert(tokensTaken.Bless, obj.getGUID())
|
||||||
})
|
numInPlay.Bless = numInPlay.Bless + 1
|
||||||
|
elseif obj.getName() == "Curse" then
|
||||||
for i,v in ipairs(objs) do
|
table.insert(tokensTaken.Curse, obj.getGUID())
|
||||||
local obj = v.hit_object
|
numInPlay.Curse = numInPlay.Curse + 1
|
||||||
if obj.getName() == "Bless" then
|
end
|
||||||
table.insert(tokensTaken.Bless, obj.getGUID())
|
|
||||||
numInPlay.Bless = numInPlay.Bless + 1
|
|
||||||
elseif obj.getName() == "Curse" then
|
|
||||||
table.insert(tokensTaken.Curse, obj.getGUID())
|
|
||||||
numInPlay.Curse = numInPlay.Curse + 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -174,8 +171,8 @@ end
|
|||||||
|
|
||||||
function doReset(_obj, _color, alt_click)
|
function doReset(_obj, _color, alt_click)
|
||||||
playerColor = _color
|
playerColor = _color
|
||||||
numInPlay = { Bless=0, Curse=0 }
|
numInPlay = { Bless = 0, Curse = 0 }
|
||||||
tokensTaken = { Bless={}, Curse={} }
|
tokensTaken = { Bless = {}, Curse = {} }
|
||||||
initializeState()
|
initializeState()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -202,8 +199,8 @@ function spawnToken()
|
|||||||
local url = IMAGE_URL[mode]
|
local url = IMAGE_URL[mode]
|
||||||
local obj = spawnObject({
|
local obj = spawnObject({
|
||||||
type = 'Custom_Tile',
|
type = 'Custom_Tile',
|
||||||
position = {pos.x, pos.y + 3, pos.z},
|
position = { pos.x, pos.y + 3, pos.z },
|
||||||
rotation = {x = 0, y = 260, z = 0},
|
rotation = { x = 0, y = 260, z = 0 },
|
||||||
callback_function = spawn_callback
|
callback_function = spawn_callback
|
||||||
})
|
})
|
||||||
obj.setCustomObject({
|
obj.setCustomObject({
|
||||||
@ -211,7 +208,7 @@ function spawnToken()
|
|||||||
image = url,
|
image = url,
|
||||||
thickness = 0.10,
|
thickness = 0.10,
|
||||||
})
|
})
|
||||||
obj.scale {0.81, 1, 0.81}
|
obj.scale { 0.81, 1, 0.81 }
|
||||||
return obj
|
return obj
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -242,8 +239,14 @@ function takeToken(type, _color, remove)
|
|||||||
playerColor = _color
|
playerColor = _color
|
||||||
local chaosbag = getChaosBag()
|
local chaosbag = getChaosBag()
|
||||||
if chaosbag == nil then return end
|
if chaosbag == nil then return end
|
||||||
|
if not remove and not SEAL_CARD_MESSAGE then
|
||||||
|
broadcastToColor("Are you trying to seal a token on a card? Return " ..
|
||||||
|
"this one, then try right-clicking on the card for seal options.",
|
||||||
|
_color)
|
||||||
|
SEAL_CARD_MESSAGE = true
|
||||||
|
end
|
||||||
local tokens = {}
|
local tokens = {}
|
||||||
for i,v in ipairs(chaosbag.getObjects()) do
|
for i, v in ipairs(chaosbag.getObjects()) do
|
||||||
if v.name == type then
|
if v.name == type then
|
||||||
table.insert(tokens, v.guid)
|
table.insert(tokens, v.guid)
|
||||||
end
|
end
|
||||||
@ -260,9 +263,13 @@ function takeToken(type, _color, remove)
|
|||||||
end
|
end
|
||||||
local guid = table.remove(tokens)
|
local guid = table.remove(tokens)
|
||||||
mode = type
|
mode = type
|
||||||
|
local position = Vector({ pos.x - 2, pos.y, pos.z + 2.5 })
|
||||||
|
if type == "Curse" then
|
||||||
|
position = position + Vector({ 0, 0, -5 })
|
||||||
|
end
|
||||||
chaosbag.takeObject({
|
chaosbag.takeObject({
|
||||||
guid = guid,
|
guid = guid,
|
||||||
position = {pos.x-2, pos.y, pos.z},
|
position = position,
|
||||||
smooth = false,
|
smooth = false,
|
||||||
callback_function = callback
|
callback_function = callback
|
||||||
})
|
})
|
||||||
@ -314,7 +321,7 @@ end
|
|||||||
function getChaosBag()
|
function getChaosBag()
|
||||||
local items = getObjectFromGUID("83ef06").getObjects()
|
local items = getObjectFromGUID("83ef06").getObjects()
|
||||||
local chaosbag = nil
|
local chaosbag = nil
|
||||||
for i,v in ipairs(items) do
|
for i, v in ipairs(items) do
|
||||||
if v.getDescription() == "Chaos Bag" then
|
if v.getDescription() == "Chaos Bag" then
|
||||||
chaosbag = getObjectFromGUID(v.getGUID())
|
chaosbag = getObjectFromGUID(v.getGUID())
|
||||||
break
|
break
|
||||||
@ -334,3 +341,104 @@ function getTokenCount()
|
|||||||
return "(" .. (numInPlay[mode] - #tokensTaken[mode]) .. "/" ..
|
return "(" .. (numInPlay[mode] - #tokensTaken[mode]) .. "/" ..
|
||||||
#tokensTaken[mode] .. ")"
|
#tokensTaken[mode] .. ")"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function addMenuOptions(playerColor, hoveredObject, pointerPosition, isKeyUp)
|
||||||
|
local manager = self
|
||||||
|
if hoveredObject == nil or hoveredObject.getVar("MENU_ADDED") == true then return end
|
||||||
|
if hoveredObject.tag ~= "Card" then
|
||||||
|
broadcastToColor("Right-click seal options can only be added to cards", playerColor)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
hoveredObject.addContextMenuItem("Seal Bless", function(color)
|
||||||
|
manager.call("sealToken", {
|
||||||
|
type = "Bless",
|
||||||
|
playerColor = color,
|
||||||
|
enemy = hoveredObject
|
||||||
|
})
|
||||||
|
end, true)
|
||||||
|
hoveredObject.addContextMenuItem("Release Bless", function(color)
|
||||||
|
manager.call("releaseToken", {
|
||||||
|
type = "Bless",
|
||||||
|
playerColor = color,
|
||||||
|
enemy = hoveredObject
|
||||||
|
})
|
||||||
|
end, true)
|
||||||
|
hoveredObject.addContextMenuItem("Seal Curse", function(color)
|
||||||
|
manager.call("sealToken", {
|
||||||
|
type = "Curse",
|
||||||
|
playerColor = color,
|
||||||
|
enemy = hoveredObject
|
||||||
|
})
|
||||||
|
end, true)
|
||||||
|
hoveredObject.addContextMenuItem("Release Curse", function(color)
|
||||||
|
manager.call("releaseToken", {
|
||||||
|
type = "Curse",
|
||||||
|
playerColor = color,
|
||||||
|
enemy = hoveredObject
|
||||||
|
})
|
||||||
|
end, true)
|
||||||
|
broadcastToColor("Right-click seal options added to " .. hoveredObject.getName(), playerColor)
|
||||||
|
hoveredObject.setVar("MENU_ADDED", true)
|
||||||
|
sealedTokens[hoveredObject.getGUID()] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function sealToken(params)
|
||||||
|
playerColor = params.playerColor
|
||||||
|
local chaosbag = getChaosBag()
|
||||||
|
if chaosbag == nil then return end
|
||||||
|
local pos = params.enemy.getPosition()
|
||||||
|
local manager = self
|
||||||
|
|
||||||
|
for i, token in ipairs(chaosbag.getObjects()) do
|
||||||
|
if token.name == params.type then
|
||||||
|
chaosbag.takeObject({
|
||||||
|
position = { pos.x, pos.y + 1, pos.z },
|
||||||
|
index = i - 1,
|
||||||
|
smooth = false,
|
||||||
|
callback_function = function(obj)
|
||||||
|
Wait.frames(function()
|
||||||
|
local mSealedTokens = manager.getVar("sealedTokens")
|
||||||
|
local tokens = mSealedTokens[params.enemy.getGUID()]
|
||||||
|
table.insert(tokens, obj)
|
||||||
|
manager.setVar("sealedTokens", mSealedTokens)
|
||||||
|
local guid = obj.getGUID()
|
||||||
|
local tokensTaken = manager.getVar("tokensTaken")
|
||||||
|
table.insert(tokensTaken[params.type], guid)
|
||||||
|
manager.setVar("tokensTaken", tokensTaken)
|
||||||
|
manager.setVar("mode", params.type)
|
||||||
|
printToColor("Sealing " .. params.type .. " token " .. manager.call("getTokenCount"),
|
||||||
|
params.playerColor)
|
||||||
|
end
|
||||||
|
, 1)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
printToColor(params.type .. " token not found in bag", playerColor)
|
||||||
|
end
|
||||||
|
|
||||||
|
function releaseToken(params)
|
||||||
|
playerColor = params.playerColor
|
||||||
|
local chaosbag = getChaosBag()
|
||||||
|
if chaosbag == nil then return end
|
||||||
|
local tokens = sealedTokens[params.enemy.getGUID()]
|
||||||
|
if tokens == nil or #tokens == 0 then return end
|
||||||
|
mode = params.type
|
||||||
|
|
||||||
|
for i, token in ipairs(tokens) do
|
||||||
|
if token ~= nil and token.getName() == params.type then
|
||||||
|
local guid = token.getGUID()
|
||||||
|
chaosbag.putObject(token)
|
||||||
|
for j, v in ipairs(tokensTaken[mode]) do
|
||||||
|
if v == guid then
|
||||||
|
table.remove(tokensTaken[mode], j)
|
||||||
|
table.remove(tokens, i)
|
||||||
|
printToColor("Releasing " .. mode .. " token" .. getTokenCount(), params.playerColor)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
printToColor(params.type .. " token not sealed on " .. params.enemy.getName(), params.playerColor)
|
||||||
|
end
|
||||||
|
103
src/util/ChaosBagStatTracker.ttslua
Normal file
103
src/util/ChaosBagStatTracker.ttslua
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
function onload(saved_data)
|
||||||
|
light_mode = false
|
||||||
|
|
||||||
|
if saved_data ~= "" then
|
||||||
|
local loaded_data = JSON.decode(saved_data)
|
||||||
|
light_mode = loaded_data[1]
|
||||||
|
end
|
||||||
|
createAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- functions delegated to Global
|
||||||
|
function printStats(object, player, isRightClick)
|
||||||
|
-- local toPosition = self.positionToWorld(DRAWN_CHAOS_TOKEN_OFFSET)
|
||||||
|
if isRightClick then
|
||||||
|
Global.call("resetStats")
|
||||||
|
else
|
||||||
|
Global.call("printStats")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateSave()
|
||||||
|
local data_to_save = {light_mode }
|
||||||
|
saved_data = JSON.encode(data_to_save)
|
||||||
|
self.script_state = saved_data
|
||||||
|
end
|
||||||
|
|
||||||
|
function createAll()
|
||||||
|
s_color = {0.5, 0.5, 0.5, 95}
|
||||||
|
|
||||||
|
if light_mode then
|
||||||
|
f_color = {1,1,1,95}
|
||||||
|
else
|
||||||
|
f_color = {0,0,0,100}
|
||||||
|
end
|
||||||
|
|
||||||
|
self.createButton({
|
||||||
|
click_function="printStats",
|
||||||
|
function_owner=self,
|
||||||
|
position={0,0.05,0},
|
||||||
|
height=600,
|
||||||
|
width=1000,
|
||||||
|
alignment = 3,
|
||||||
|
tooltip = "Left Click to print stats. Right Click to reset them.",
|
||||||
|
scale={x=1.5, y=1.5, z=1.5},
|
||||||
|
font_size=600,
|
||||||
|
font_color=f_color,
|
||||||
|
color={0,0,0,0}
|
||||||
|
})
|
||||||
|
|
||||||
|
if light_mode then
|
||||||
|
lightButtonText = "[ Set dark ]"
|
||||||
|
else
|
||||||
|
lightButtonText = "[ Set light ]"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function removeAll()
|
||||||
|
self.removeInput(0)
|
||||||
|
self.removeInput(1)
|
||||||
|
self.removeButton(0)
|
||||||
|
self.removeButton(1)
|
||||||
|
self.removeButton(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function reloadAll()
|
||||||
|
removeAll()
|
||||||
|
createAll()
|
||||||
|
updateSave()
|
||||||
|
end
|
||||||
|
|
||||||
|
function swap_fcolor(_obj, _color, alt_click)
|
||||||
|
light_mode = not light_mode
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function swap_align(_obj, _color, alt_click)
|
||||||
|
center_mode = not center_mode
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function editName(_obj, _string, value)
|
||||||
|
self.setName(value)
|
||||||
|
setTooltips()
|
||||||
|
end
|
||||||
|
|
||||||
|
function setTooltips()
|
||||||
|
self.editInput({
|
||||||
|
index = 0,
|
||||||
|
value = self.getName(),
|
||||||
|
tooltip = "Left click to show stats. Right click to reset them."
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function keepSample(_obj, _string, value)
|
||||||
|
reloadAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
function onDestroy()
|
||||||
|
if timerID and type(timerID) == 'object' then
|
||||||
|
Timer.destroy(timerID)
|
||||||
|
end
|
||||||
|
end
|
@ -1,5 +1,5 @@
|
|||||||
function updateSave()
|
function updateSave()
|
||||||
local data_to_save = {["ml"]=memoryList}
|
local data_to_save = { ["ml"] = memoryList }
|
||||||
saved_data = JSON.encode(data_to_save)
|
saved_data = JSON.encode(data_to_save)
|
||||||
self.script_state = saved_data
|
self.script_state = saved_data
|
||||||
end
|
end
|
||||||
@ -21,16 +21,20 @@ function onload(saved_data)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--Beginning Setup
|
--Beginning Setup
|
||||||
|
|
||||||
|
|
||||||
--Make setup button
|
--Make setup button
|
||||||
function createSetupButton()
|
function createSetupButton()
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Setup", click_function="buttonClick_setup", function_owner=self,
|
label = "Setup",
|
||||||
position={0,5,-2}, rotation={0,0,0}, height=250, width=600,
|
click_function = "buttonClick_setup",
|
||||||
font_size=150, color={0,0,0}, font_color={1,1,1}
|
function_owner = self,
|
||||||
|
position = { 0, 5, -2 },
|
||||||
|
rotation = { 0, 0, 0 },
|
||||||
|
height = 250,
|
||||||
|
width = 600,
|
||||||
|
font_size = 150,
|
||||||
|
color = { 0, 0, 0 },
|
||||||
|
font_color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -50,7 +54,7 @@ function createButtonsOnAllObjects()
|
|||||||
if obj ~= self then
|
if obj ~= self then
|
||||||
local dummyIndex = howManyButtons
|
local dummyIndex = howManyButtons
|
||||||
--On a normal bag, the button positions aren't the same size as the bag.
|
--On a normal bag, the button positions aren't the same size as the bag.
|
||||||
globalScaleFactor = 1.25 * 1/self.getScale().x
|
globalScaleFactor = 1.25 * 1 / self.getScale().x
|
||||||
--Super sweet math to set button positions
|
--Super sweet math to set button positions
|
||||||
local selfPos = self.getPosition()
|
local selfPos = self.getPosition()
|
||||||
local objPos = obj.getPosition()
|
local objPos = obj.getPosition()
|
||||||
@ -67,9 +71,9 @@ function createButtonsOnAllObjects()
|
|||||||
local func = function() buttonClick_selection(dummyIndex, obj) end
|
local func = function() buttonClick_selection(dummyIndex, obj) end
|
||||||
self.setVar(funcName, func)
|
self.setVar(funcName, func)
|
||||||
self.createButton({
|
self.createButton({
|
||||||
click_function=funcName, function_owner=self,
|
click_function = funcName, function_owner = self,
|
||||||
position=objPos, rotation=rot, height=1000, width=1000,
|
position = objPos, rotation = rot, height = 1000, width = 1000,
|
||||||
color={0.75,0.25,0.25,0.6},
|
color = { 0.75, 0.25, 0.25, 0.6 },
|
||||||
})
|
})
|
||||||
howManyButtons = howManyButtons + 1
|
howManyButtons = howManyButtons + 1
|
||||||
end
|
end
|
||||||
@ -79,43 +83,40 @@ end
|
|||||||
--Creates submit and cancel buttons
|
--Creates submit and cancel buttons
|
||||||
function createSetupActionButtons()
|
function createSetupActionButtons()
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Cancel", click_function="buttonClick_cancel", function_owner=self,
|
label = "Cancel", click_function = "buttonClick_cancel", function_owner = self,
|
||||||
position={1.5,5,2}, rotation={0,0,0}, height=350, width=1100,
|
position = { 1.5, 5, 2 }, rotation = { 0, 0, 0 }, height = 350, width = 1100,
|
||||||
font_size=250, color={0,0,0}, font_color={1,1,1}
|
font_size = 250, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Submit", click_function="buttonClick_submit", function_owner=self,
|
label = "Submit", click_function = "buttonClick_submit", function_owner = self,
|
||||||
position={-1.2,5,2}, rotation={0,0,0}, height=350, width=1100,
|
position = { -1.2, 5, 2 }, rotation = { 0, 0, 0 }, height = 350, width = 1100,
|
||||||
font_size=250, color={0,0,0}, font_color={1,1,1}
|
font_size = 250, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Reset", click_function="buttonClick_reset", function_owner=self,
|
label = "Reset", click_function = "buttonClick_reset", function_owner = self,
|
||||||
position={-3.5,5,2}, rotation={0,0,0}, height=350, width=800,
|
position = { -3.5, 5, 2 }, rotation = { 0, 0, 0 }, height = 350, width = 800,
|
||||||
font_size=250, color={0,0,0}, font_color={1,1,1}
|
font_size = 250, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--During Setup
|
--During Setup
|
||||||
|
|
||||||
|
|
||||||
--Checks or unchecks buttons
|
--Checks or unchecks buttons
|
||||||
function buttonClick_selection(index, obj)
|
function buttonClick_selection(index, obj)
|
||||||
local color = {0,1,0,0.6}
|
local color = { 0, 1, 0, 0.6 }
|
||||||
if memoryList[obj.getGUID()] == nil then
|
if memoryList[obj.getGUID()] == nil then
|
||||||
self.editButton({index=index, color=color})
|
self.editButton({ index = index, color = color })
|
||||||
--Adding pos/rot to memory table
|
--Adding pos/rot to memory table
|
||||||
local pos, rot = obj.getPosition(), obj.getRotation()
|
local pos, rot = obj.getPosition(), obj.getRotation()
|
||||||
--I need to add it like this or it won't save due to indexing issue
|
--I need to add it like this or it won't save due to indexing issue
|
||||||
memoryList[obj.getGUID()] = {
|
memoryList[obj.getGUID()] = {
|
||||||
pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)},
|
pos = { x = round(pos.x, 4), y = round(pos.y, 4), z = round(pos.z, 4) },
|
||||||
rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)},
|
rot = { x = round(rot.x, 4), y = round(rot.y, 4), z = round(rot.z, 4) },
|
||||||
lock=obj.getLock()
|
lock = obj.getLock()
|
||||||
}
|
}
|
||||||
obj.highlightOn({0,1,0})
|
obj.highlightOn({ 0, 1, 0 })
|
||||||
else
|
else
|
||||||
color = {0.75,0.25,0.25,0.6}
|
color = { 0.75, 0.25, 0.25, 0.6 }
|
||||||
self.editButton({index=index, color=color})
|
self.editButton({ index = index, color = color })
|
||||||
memoryList[obj.getGUID()] = nil
|
memoryList[obj.getGUID()] = nil
|
||||||
obj.highlightOff()
|
obj.highlightOff()
|
||||||
end
|
end
|
||||||
@ -131,13 +132,13 @@ function buttonClick_cancel()
|
|||||||
createMemoryActionButtons()
|
createMemoryActionButtons()
|
||||||
end
|
end
|
||||||
removeAllHighlights()
|
removeAllHighlights()
|
||||||
broadcastToAll("Selection Canceled", {1,1,1})
|
broadcastToAll("Selection Canceled", { 1, 1, 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
--Saves selections
|
--Saves selections
|
||||||
function buttonClick_submit()
|
function buttonClick_submit()
|
||||||
if next(memoryList) == nil then
|
if next(memoryList) == nil then
|
||||||
broadcastToAll("You cannot submit without any selections.", {0.75, 0.25, 0.25})
|
broadcastToAll("You cannot submit without any selections.", { 0.75, 0.25, 0.25 })
|
||||||
else
|
else
|
||||||
self.clearButtons()
|
self.clearButtons()
|
||||||
createMemoryActionButtons()
|
createMemoryActionButtons()
|
||||||
@ -147,7 +148,7 @@ function buttonClick_submit()
|
|||||||
local obj = getObjectFromGUID(guid)
|
local obj = getObjectFromGUID(guid)
|
||||||
if obj ~= nil then obj.highlightOff() end
|
if obj ~= nil then obj.highlightOff() end
|
||||||
end
|
end
|
||||||
broadcastToAll(count.." Objects Saved", {1,1,1})
|
broadcastToAll(count .. " Objects Saved", { 1, 1, 1 })
|
||||||
updateSave()
|
updateSave()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -158,31 +159,55 @@ function buttonClick_reset()
|
|||||||
self.clearButtons()
|
self.clearButtons()
|
||||||
createSetupButton()
|
createSetupButton()
|
||||||
removeAllHighlights()
|
removeAllHighlights()
|
||||||
broadcastToAll("Tool Reset", {1,1,1})
|
broadcastToAll("Tool Reset", { 1, 1, 1 })
|
||||||
updateSave()
|
updateSave()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--After Setup
|
--After Setup
|
||||||
|
|
||||||
|
|
||||||
--Creates recall and place buttons
|
--Creates recall and place buttons
|
||||||
function createMemoryActionButtons()
|
function createMemoryActionButtons()
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Clicker", click_function="buttonClick_place", function_owner=self,
|
label = "Clicker", click_function = "buttonClick_place", function_owner = self,
|
||||||
position={4.2,1,0}, rotation={0,0,0}, height=500, width=1100,
|
position = { 4.2, 1, 0 }, rotation = { 0, 0, 0 }, height = 500, width = 1100,
|
||||||
font_size=350, color={0,0,0}, font_color={1,1,1}
|
font_size = 350, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
})
|
})
|
||||||
self.createButton({
|
self.createButton({
|
||||||
label="Counter", click_function="buttonClick_recall", function_owner=self,
|
label = "Counter", click_function = "buttonClick_recall", function_owner = self,
|
||||||
position={-4.2,1,-0.1}, rotation={0,0,0}, height=500, width=1300,
|
position = { -4.2, 1, -0.1 }, rotation = { 0, 0, 0 }, height = 500, width = 1300,
|
||||||
|
font_size = 350, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
|
})
|
||||||
|
self.createButton({
|
||||||
|
label = "Add Draw 1 Buttons", click_function = "addDraw1Buttons", function_owner = self,
|
||||||
|
position = { 0, 1, -2.5 }, rotation = { 0, 0, 0 }, height = 500, width = 2600,
|
||||||
|
font_size = 250, color = { 0, 0, 0 }, font_color = { 1, 1, 1 }
|
||||||
|
})
|
||||||
|
--[[
|
||||||
|
self.createButton({
|
||||||
|
label="Setup", click_function="buttonClick_setup", function_owner=self,
|
||||||
|
position={-6,1,0}, rotation={0,90,0}, height=500, width=1200,
|
||||||
font_size=350, color={0,0,0}, font_color={1,1,1}
|
font_size=350, color={0,0,0}, font_color={1,1,1}
|
||||||
})
|
})
|
||||||
-- self.createButton({
|
--]]
|
||||||
-- label="Setup", click_function="buttonClick_setup", function_owner=self,
|
end
|
||||||
-- position={-6,1,0}, rotation={0,90,0}, height=500, width=1200,
|
|
||||||
-- font_size=350, color={0,0,0}, font_color={1,1,1}
|
function addDraw1Buttons()
|
||||||
-- })
|
if ADD_BUTTONS_DISABLED then return end
|
||||||
|
|
||||||
|
local mats = { "8b081b", "bd0ff4", "383d8b", "0840d5" }
|
||||||
|
for i, guid in ipairs(mats) do
|
||||||
|
local mat = getObjectFromGUID(guid)
|
||||||
|
mat.createButton({
|
||||||
|
label = "Draw 1",
|
||||||
|
click_function = "doDrawOne",
|
||||||
|
function_owner = mat,
|
||||||
|
position = { 1.84, 0.1, -0.36 },
|
||||||
|
scale = { 0.12, 0.12, 0.12 },
|
||||||
|
width = 800,
|
||||||
|
height = 280,
|
||||||
|
font_size = 180
|
||||||
|
})
|
||||||
|
end
|
||||||
|
ADD_BUTTONS_DISABLED = true
|
||||||
end
|
end
|
||||||
|
|
||||||
--Sends objects from bag/table to their saved position/rotation
|
--Sends objects from bag/table to their saved position/rotation
|
||||||
@ -200,7 +225,7 @@ function buttonClick_place()
|
|||||||
for _, bagObj in ipairs(bagObjList) do
|
for _, bagObj in ipairs(bagObjList) do
|
||||||
if bagObj.guid == guid then
|
if bagObj.guid == guid then
|
||||||
local item = self.takeObject({
|
local item = self.takeObject({
|
||||||
guid=guid, position=entry.pos, rotation=entry.rot,
|
guid = guid, position = entry.pos, rotation = entry.rot,
|
||||||
})
|
})
|
||||||
item.setLock(entry.lock)
|
item.setLock(entry.lock)
|
||||||
break
|
break
|
||||||
@ -208,7 +233,7 @@ function buttonClick_place()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
broadcastToAll("Objects Placed", {1,1,1})
|
broadcastToAll("Objects Placed", { 1, 1, 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
--Recalls objects to bag from table
|
--Recalls objects to bag from table
|
||||||
@ -217,31 +242,27 @@ function buttonClick_recall()
|
|||||||
local obj = getObjectFromGUID(guid)
|
local obj = getObjectFromGUID(guid)
|
||||||
if obj ~= nil then self.putObject(obj) end
|
if obj ~= nil then self.putObject(obj) end
|
||||||
end
|
end
|
||||||
broadcastToAll("Objects Recalled", {1,1,1})
|
broadcastToAll("Objects Recalled", { 1, 1, 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--Utility functions
|
--Utility functions
|
||||||
|
|
||||||
|
|
||||||
--Find delta (difference) between 2 x/y/z coordinates
|
--Find delta (difference) between 2 x/y/z coordinates
|
||||||
function findOffsetDistance(p1, p2, obj)
|
function findOffsetDistance(p1, p2, obj)
|
||||||
local deltaPos = {}
|
local deltaPos = {}
|
||||||
local bounds = obj.getBounds()
|
local bounds = obj.getBounds()
|
||||||
deltaPos.x = (p2.x-p1.x)
|
deltaPos.x = (p2.x - p1.x)
|
||||||
deltaPos.y = (p2.y-p1.y) + (bounds.size.y - bounds.offset.y)
|
deltaPos.y = (p2.y - p1.y) + (bounds.size.y - bounds.offset.y)
|
||||||
deltaPos.z = (p2.z-p1.z)
|
deltaPos.z = (p2.z - p1.z)
|
||||||
return deltaPos
|
return deltaPos
|
||||||
end
|
end
|
||||||
|
|
||||||
--Used to rotate a set of coordinates by an angle
|
--Used to rotate a set of coordinates by an angle
|
||||||
function rotateLocalCoordinates(desiredPos, obj)
|
function rotateLocalCoordinates(desiredPos, obj)
|
||||||
local objPos, objRot = obj.getPosition(), obj.getRotation()
|
local objPos, objRot = obj.getPosition(), obj.getRotation()
|
||||||
local angle = math.rad(objRot.y)
|
local angle = math.rad(objRot.y)
|
||||||
local x = desiredPos.x * math.cos(angle) - desiredPos.z * math.sin(angle)
|
local x = desiredPos.x * math.cos(angle) - desiredPos.z * math.sin(angle)
|
||||||
local z = desiredPos.x * math.sin(angle) + desiredPos.z * math.cos(angle)
|
local z = desiredPos.x * math.sin(angle) + desiredPos.z * math.cos(angle)
|
||||||
--return {x=objPos.x+x, y=objPos.y+desiredPos.y, z=objPos.z+z}
|
return { x = x, y = desiredPos.y, z = z }
|
||||||
return {x=x, y=desiredPos.y, z=z}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--Coroutine delay, in seconds
|
--Coroutine delay, in seconds
|
||||||
@ -268,6 +289,6 @@ end
|
|||||||
|
|
||||||
--Round number (num) to the Nth decimal (dec)
|
--Round number (num) to the Nth decimal (dec)
|
||||||
function round(num, dec)
|
function round(num, dec)
|
||||||
local mult = 10^(dec or 0)
|
local mult = 10 ^ (dec or 0)
|
||||||
return math.floor(num * mult + 0.5) / mult
|
return math.floor(num * mult + 0.5) / mult
|
||||||
end
|
end
|
||||||
|
98
src/util/ConnectionDrawingTool.ttslua
Normal file
98
src/util/ConnectionDrawingTool.ttslua
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
-- Drawing Tool
|
||||||
|
-- created by: Chr1Z
|
||||||
|
-- original by: Whimsical
|
||||||
|
-- description: draws lines between selected objects
|
||||||
|
information = {
|
||||||
|
version = "1.1",
|
||||||
|
last_updated = "10.10.2022"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- save "lines" to be able to remove them after loading
|
||||||
|
function onSave() return JSON.encode(lines) end
|
||||||
|
|
||||||
|
-- load data and add context menu
|
||||||
|
function onload(saved_data)
|
||||||
|
lines = JSON.decode(saved_data) or {}
|
||||||
|
|
||||||
|
self.addContextMenuItem("More Information", function()
|
||||||
|
printToAll("------------------------------", "White")
|
||||||
|
printToAll("Drawing Tool v" .. information["version"] .. " by Chr1Z", "Orange")
|
||||||
|
printToAll("last updated: " .. information["last_updated"], "White")
|
||||||
|
printToAll("original concept by Whimsical", "White")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create timer when numpad 0 is pressed
|
||||||
|
function onScriptingButtonDown(index, player_color)
|
||||||
|
if index ~= 10 then return end
|
||||||
|
TimerID = Wait.time(function() draw_from(Player[player_color]) end, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- called for long press of numpad 0, draws lines from hovered object to selected objects
|
||||||
|
function draw_from(player)
|
||||||
|
local source = player.getHoverObject()
|
||||||
|
if not source then return end
|
||||||
|
|
||||||
|
for _, item in ipairs(player.getSelectedObjects()) do
|
||||||
|
if item.getGUID() ~= source.getGUID() then
|
||||||
|
if item.getGUID() > source.getGUID() then
|
||||||
|
draw_with_pair(item, source)
|
||||||
|
else
|
||||||
|
draw_with_pair(source, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
process_lines()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- general drawing of all lines between selected objects
|
||||||
|
function onScriptingButtonUp(index, player_color)
|
||||||
|
if index ~= 10 then return end
|
||||||
|
-- returns true only if there is a timer to cancel. If this is false then we've waited longer than a second.
|
||||||
|
if not Wait.stop(TimerID) then return end
|
||||||
|
|
||||||
|
local items = Player[player_color].getSelectedObjects()
|
||||||
|
if #items < 2 then
|
||||||
|
broadcastToColor("You must have at least two items selected (currently: " .. #items .. ").", player_color, "Red")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(items, function(a, b) return a.getGUID() > b.getGUID() end)
|
||||||
|
|
||||||
|
for f = 1, #items - 1 do
|
||||||
|
for s = f + 1, #items do
|
||||||
|
draw_with_pair(items[f], items[s])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
process_lines()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- adds two objects to table of vector lines
|
||||||
|
function draw_with_pair(first, second)
|
||||||
|
local guid_first = first.getGUID()
|
||||||
|
local guid_second = second.getGUID()
|
||||||
|
|
||||||
|
if Global.getVectorLines() == nil then lines = {} end
|
||||||
|
if not lines[guid_first] then lines[guid_first] = {} end
|
||||||
|
|
||||||
|
if lines[guid_first][guid_second] then
|
||||||
|
lines[guid_first][guid_second] = nil
|
||||||
|
else
|
||||||
|
lines[guid_first][guid_second] = { points = { first.getPosition(), second.getPosition() }, color = "White" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- updates the global vector lines based on "lines"
|
||||||
|
function process_lines()
|
||||||
|
local drawing = {}
|
||||||
|
|
||||||
|
for _, first in pairs(lines) do
|
||||||
|
for _, data in pairs(first) do
|
||||||
|
table.insert(drawing, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Global.setVectorLines(drawing)
|
||||||
|
end
|
32
src/util/DeckCutter.ttslua
Normal file
32
src/util/DeckCutter.ttslua
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
||||||
|
--- Created by Whimsical.
|
||||||
|
--- DateTime: 2020-09-26 5:50 p.m.
|
||||||
|
---
|
||||||
|
|
||||||
|
---@param index number
|
||||||
|
---@param player_color string
|
||||||
|
function onScriptingButtonDown(index, player_color)
|
||||||
|
if not (index>=1 and index<=3) then return end
|
||||||
|
|
||||||
|
local count = index * 3
|
||||||
|
|
||||||
|
---@type Player
|
||||||
|
local player = Player[player_color]
|
||||||
|
local object = player:getHoverObject()
|
||||||
|
|
||||||
|
|
||||||
|
if (not object) then return end
|
||||||
|
if (object.tag~="Deck") then return end
|
||||||
|
if (count >= object:getQuantity()) then return end
|
||||||
|
|
||||||
|
for _ =1, count do
|
||||||
|
local target_position = object:positionToWorld(Vector(0, 0, -3.5))
|
||||||
|
|
||||||
|
object:takeObject {
|
||||||
|
index = 0,
|
||||||
|
position = target_position,
|
||||||
|
smooth = false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -1,71 +0,0 @@
|
|||||||
function onload(save_state)
|
|
||||||
val = 0
|
|
||||||
playerColor = "Orange"
|
|
||||||
if save_state ~= nil then
|
|
||||||
local obj = JSON.decode(save_state)
|
|
||||||
if obj ~= nil and obj.playerColor ~= nil then
|
|
||||||
playerColor = obj.playerColor
|
|
||||||
end
|
|
||||||
end
|
|
||||||
des = false
|
|
||||||
loopId = Wait.time(|| updateValue(), 1, -1)
|
|
||||||
self.addContextMenuItem("Bind to my color", bindColor)
|
|
||||||
end
|
|
||||||
|
|
||||||
function bindColor(player_color)
|
|
||||||
playerColor = player_color
|
|
||||||
self.setName(player_color .. " Hand Size Counter")
|
|
||||||
end
|
|
||||||
|
|
||||||
function onSave()
|
|
||||||
return JSON.encode({ playerColor = playerColor })
|
|
||||||
end
|
|
||||||
|
|
||||||
function onHover(player_color)
|
|
||||||
if not (player_color == playerColor) then return end
|
|
||||||
Wait.stop(loopId)
|
|
||||||
des = not des
|
|
||||||
updateValue()
|
|
||||||
des = not des
|
|
||||||
loopId = Wait.time(|| updateValue(), 1, -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function updateDES(player, value, id)
|
|
||||||
if (value == "True") then des = true
|
|
||||||
else des = false
|
|
||||||
end
|
|
||||||
updateValue()
|
|
||||||
end
|
|
||||||
|
|
||||||
function updateValue()
|
|
||||||
local hand = Player[playerColor].getHandObjects()
|
|
||||||
local size = 0
|
|
||||||
|
|
||||||
if (des) then
|
|
||||||
self.UI.setAttribute("handSize", "color", "#00FF00")
|
|
||||||
-- count by name for Dream Enhancing Serum
|
|
||||||
local cardHash = {}
|
|
||||||
for key, obj in pairs(hand) do
|
|
||||||
if obj != nil and obj.tag == "Card" then
|
|
||||||
local name = obj.getName()
|
|
||||||
local title, xp = string.match(name, '(.+)(%s%(%d+%))')
|
|
||||||
if title ~= nil then name = title end
|
|
||||||
cardHash[name] = obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for key, obj in pairs(cardHash) do
|
|
||||||
size = size + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.UI.setAttribute("handSize", "color", "#FFFFFF")
|
|
||||||
-- otherwise count individually
|
|
||||||
for key, obj in pairs(hand) do
|
|
||||||
if obj != nil and obj.tag == "Card" then
|
|
||||||
size = size + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
val = size
|
|
||||||
self.UI.setValue("handSize", val)
|
|
||||||
end
|
|
@ -1,13 +0,0 @@
|
|||||||
<Defaults>
|
|
||||||
<Text color="#FFFFFF" fontSize="72"/>
|
|
||||||
<Toggle textColor="#FFFFFF"/>
|
|
||||||
</Defaults>
|
|
||||||
|
|
||||||
<VerticalLayout width="150" height="200" position="0 0 -15" rotation="180 180 0">
|
|
||||||
<Row>
|
|
||||||
<Text id="handSize" width="100" height="150" alignment="LowerCenter">?</Text>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Toggle id="des" width="100" height="50" onValueChanged="updateDES">DES</Toggle>
|
|
||||||
</Row>
|
|
||||||
</VerticalLayout>
|
|
Loading…
Reference in New Issue
Block a user