-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
	local loadingPlaceholder = {[{}] = true}

	local register
	local modules = {}

	local require
	local loaded = {}

	register = function(name, body)
		if not modules[name] then
			modules[name] = body
		end
	end

	require = function(name)
		local loadedModule = loaded[name]

		if loadedModule then
			if loadedModule == loadingPlaceholder then
				return nil
			end
		else
			if not modules[name] then
				if not superRequire then
					local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
					error('Tried to require ' .. identifier .. ', but no such module has been registered')
				else
					return superRequire(name)
				end
			end

			loaded[name] = loadingPlaceholder
			loadedModule = modules[name](require, loaded, register, modules)
			loaded[name] = loadedModule
		end

		return loadedModule
	end

	return require, loaded, register, modules
end)(nil)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("util/TokenSpawner")
end)
__bundle_register("util/TokenSpawner", function(require, _LOADED, __bundle_register, __bundle_modules)
local tokenManager = require("core/token/TokenManager")

local TOKEN_INDEX = {}
TOKEN_INDEX[1] = false
TOKEN_INDEX[2] = false
TOKEN_INDEX[3] = "resourceCounter"
TOKEN_INDEX[4] = "damage"
TOKEN_INDEX[5] = "path"
TOKEN_INDEX[6] = "horror"
TOKEN_INDEX[7] = "doom"
TOKEN_INDEX[8] = "clue"
TOKEN_INDEX[9] = "resource"

---@param index number Index of the pressed key
---@param playerColor string Color of the triggering player
function onScriptingButtonDown(index, playerColor)
  local tokenType = TOKEN_INDEX[index]
  if not tokenType then return end

  local player = Player[playerColor]
  local rotation = { x = 0, y = player.getPointerRotation(), z = 0 }
  local position = player.getPointerPosition() + Vector(0, 0.2, 0)

  tokenManager.spawnToken(position, tokenType, rotation)
end
end)
__bundle_register("core/token/TokenManager", function(require, _LOADED, __bundle_register, __bundle_modules)
do
  local tokenSpawnTracker = require("core/token/TokenSpawnTrackerApi")
  local playArea = require("core/PlayAreaApi")

  local PLAYER_CARD_TOKEN_OFFSETS = {
    [1] = {
      Vector(0, 3, -0.2)
    },
    [2] = {
      Vector(0.4, 3, -0.2),
      Vector(-0.4, 3, -0.2)
    },
    [3] = {
      Vector(0, 3, -0.9),
      Vector(0.4, 3, -0.2),
      Vector(-0.4, 3, -0.2)
    },
    [4] = {
      Vector(0.4, 3, -0.9),
      Vector(-0.4, 3, -0.9),
      Vector(0.4, 3, -0.2),
      Vector(-0.4, 3, -0.2)
    },
    [5] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.4, 3, -0.2),
      Vector(-0.4, 3, -0.2)
    },
    [6] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2)
    },
    [7] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2),
      Vector(0, 3, 0.5)
    },
    [8] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2),
      Vector(-0.35, 3, 0.5),
      Vector(0.35, 3, 0.5)
    },
    [9] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2),
      Vector(0.7, 3, 0.5),
      Vector(0, 3, 0.5),
      Vector(-0.7, 3, 0.5)
    },
    [10] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2),
      Vector(0.7, 3, 0.5),
      Vector(0, 3, 0.5),
      Vector(-0.7, 3, 0.5),
      Vector(0, 3, 1.2)
    },
    [11] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2),
      Vector(0.7, 3, 0.5),
      Vector(0, 3, 0.5),
      Vector(-0.7, 3, 0.5),
      Vector(-0.35, 3, 1.2),
      Vector(0.35, 3, 1.2)
    },
    [12] = {
      Vector(0.7, 3, -0.9),
      Vector(0, 3, -0.9),
      Vector(-0.7, 3, -0.9),
      Vector(0.7, 3, -0.2),
      Vector(0, 3, -0.2),
      Vector(-0.7, 3, -0.2),
      Vector(0.7, 3, 0.5),
      Vector(0, 3, 0.5),
      Vector(-0.7, 3, 0.5),
      Vector(0.7, 3, 1.2),
      Vector(0, 3, 1.2),
      Vector(-0.7, 3, 1.2)
    }
  }

  -- Source for tokens
  local TOKEN_SOURCE_GUID = "124381"

  -- Table of data extracted from the token source bag, keyed by the Memo on each token which
  -- should match the token type keys ("resource", "clue", etc)
  local tokenTemplates

  local DATA_HELPER_GUID = "708279"

  local playerCardData
  local locationData

  local TokenManager = { }
  local internal = { }

  -- Spawns tokens for the card.  This function is built to just throw a card at it and let it do
  -- the work once a card has hit an area where it might spawn tokens.  It will check to see if
  -- the card has already spawned, find appropriate data from either the uses metadata or the Data
  -- Helper, and spawn the tokens.
  ---@param card Object Card to maybe spawn tokens for
  ---@param extraUses Table A table of <use type>=<count> which will modify the number of tokens
  ---    spawned for that type.  e.g. Akachi's playmat should pass "Charge"=1
  TokenManager.spawnForCard = function(card, extraUses)
    if tokenSpawnTracker.hasSpawnedTokens(card.getGUID()) then
      return
    end
    local metadata = JSON.decode(card.getGMNotes())
    if metadata ~= nil then
      internal.spawnTokensFromUses(card, extraUses)
    else
      internal.spawnTokensFromDataHelper(card)
    end
  end

  -- Spawns a set of tokens on the given card.
  ---@param card Object Card to spawn tokens on
  ---@param tokenType String type of token to spawn, valid values are "damage", "horror",
  --     "resource", "doom", or "clue"
  ---@param tokenCount Number How many tokens to spawn.  For damage or horror this value will be set to the
  --     spawned state object rather than spawning multiple tokens
  ---@param shiftDown Number An offset for the z-value of this group of tokens
  TokenManager.spawnTokenGroup = function(card, tokenType, tokenCount, shiftDown)
    local optionPanel = Global.getTable("optionPanel")

    if tokenType == "damage" or tokenType == "horror" then
      TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown)
    elseif tokenType == "resource" and optionPanel["useResourceCounters"] then
      TokenManager.spawnResourceCounterToken(card, tokenCount)
    else
      TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown)
    end
  end

  -- Spawns a single counter token and sets the value to tokenValue.  Used for damage and horror
  -- tokens.
  ---@param card Object Card to spawn tokens on
  ---@param tokenType String type of token to spawn, valid values are "damage" and "horror".  Other
  --     types should use spawnMultipleTokens()
  ---@param tokenValue Number Value to set the damage/horror to
  TokenManager.spawnCounterToken = function(card, tokenType, tokenValue, shiftDown)
    if tokenValue < 1 or tokenValue > 50 then return end

    local pos = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[1][1] + Vector(0, 0, shiftDown))
    local rot = card.getRotation()
    TokenManager.spawnToken(pos, tokenType, rot, function(spawned) spawned.setState(tokenValue) end)
  end

  TokenManager.spawnResourceCounterToken = function(card, tokenCount)
    local pos = card.positionToWorld(card.positionToLocal(card.getPosition()) + Vector(0, 0.2, -0.5))
    local rot = card.getRotation()
    TokenManager.spawnToken(pos, "resourceCounter", rot, function(spawned)
      spawned.call("updateVal", tokenCount)
    end)
  end

  -- Spawns a number of tokens.
  ---@param tokenType String type of token to spawn, valid values are resource", "doom", or "clue".
  --     Other types should use spawnCounterToken()
  ---@param tokenCount Number How many tokens to spawn
  ---@param shiftDown Number An offset for the z-value of this group of tokens
  TokenManager.spawnMultipleTokens = function(card, tokenType, tokenCount, shiftDown)
    if tokenCount < 1 or tokenCount > 12 then
      return
    end

    local offsets = {}
    if tokenType == "clue" then
      offsets = internal.buildClueOffsets(card, tokenCount)
    else
      for i = 1, tokenCount do
        offsets[i] = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[tokenCount][i])
        -- Fix the y-position for the spawn, since positionToWorld considers rotation which can
        -- have bad results for face up/down differences
        offsets[i].y = card.getPosition().y + 0.15
      end
    end

    if shiftDown ~= nil then
      -- Copy the offsets to make sure we don't change the static values
      local baseOffsets = offsets
      offsets = { }
      for i, baseOffset in ipairs(baseOffsets) do
        offsets[i] = baseOffset
        offsets[i][3] = offsets[i][3] + shiftDown
      end
    end

    if offsets == nil then
      error("couldn't find offsets for " .. tokenCount .. ' tokens')
      return
    end

    for i = 1, tokenCount do
      TokenManager.spawnToken(offsets[i], tokenType, card.getRotation())
    end
  end

  -- Spawns a single token at the given global position by copying it from the template bag.
  ---@param position Global position to spawn the token
  ---@param tokenType String type of token to spawn, valid values are "damage", "horror",
  --     "resource", "doom", or "clue"
  ---@param rotation Vector Rotation to be used for the new token.  Only the y-value will be used,
  --     x and z will use the default rotation from the source bag
  ---@param callback function A callback function triggered after the new token is spawned
  TokenManager.spawnToken = function(position, tokenType, rotation, callback)
    internal.initTokenTemplates()
    local loadTokenType = tokenType
    if tokenType == "clue" or tokenType == "doom" then
      loadTokenType = "clueDoom"
    end
    if tokenTemplates[loadTokenType] == nil then
      error("Unknown token type '" .. tokenType .. "'")
      return
    end
    local tokenTemplate = tokenTemplates[loadTokenType]

    -- Take ONLY the Y-value for rotation, so we don't flip the token coming out of the bag
    local rot = Vector(tokenTemplate.Transform.rotX,
        270,
        tokenTemplate.Transform.rotZ)
    if rotation ~= nil then
      rot.y = rotation.y
    end
    if tokenType == "doom" then
      rot.z = 180
    end

    tokenTemplate.Nickname = ""
    return spawnObjectData({
      data = tokenTemplate,
      position = position,
      rotation = rot,
      callback_function = callback
    })
  end

  -- Delegate function to the token spawn tracker.  Exists to avoid circular dependencies in some
  -- callers.
  ---@param card Object Card object to reset the tokens for
  TokenManager.resetTokensSpawned = function(card)
    tokenSpawnTracker.resetTokensSpawned(card.getGUID())
  end

  -- Pushes new player card data into the local copy of the Data Helper player data.
  ---@param dataTable Table Key/Value pairs following the DataHelper style
  TokenManager.addPlayerCardData = function(dataTable)
    internal.initDataHelperData()
    for k, v in pairs(dataTable) do
      playerCardData[k] = v
    end
  end

  -- Pushes new location data into the local copy of the Data Helper location data.
  ---@param dataTable Table Key/Value pairs following the DataHelper style
  TokenManager.addLocationData = function(dataTable)
    internal.initDataHelperData()
    for k, v in pairs(dataTable) do
      locationData[k] = v
    end
  end

  -- Checks to see if the given card has location data in the DataHelper
  ---@param card Object Card to check for data
  ---@return Boolean True if this card has data in the helper, false otherwise
  TokenManager.hasLocationData = function(card)
    internal.initDataHelperData()
    return internal.getLocationData(card) ~= nil
  end

  internal.initTokenTemplates = function()
    if tokenTemplates ~= nil then
      return
    end
    tokenTemplates = { }
    local tokenSource = getObjectFromGUID(TOKEN_SOURCE_GUID)
    for _, tokenTemplate in ipairs(tokenSource.getData().ContainedObjects) do
      local tokenName = tokenTemplate.Memo
      tokenTemplates[tokenName] = tokenTemplate
    end
  end

  -- Copies the data from the DataHelper.  Will only happen once.
  internal.initDataHelperData = function()
    if playerCardData ~= nil then
      return
    end
    local dataHelper = getObjectFromGUID(DATA_HELPER_GUID)
    playerCardData = dataHelper.getTable('PLAYER_CARD_DATA')
    locationData = dataHelper.getTable('LOCATIONS_DATA')
  end

  -- Spawn tokens for a card based on the uses metadata.  This will consider the face up/down state
  -- of the card for both locations and standard cards.
  ---@param card Object Card to maybe spawn tokens for
  ---@param extraUses Table A table of <use type>=<count> which will modify the number of tokens
  ---    spawned for that type.  e.g. Akachi's playmat should pass "Charge"=1
  internal.spawnTokensFromUses = function(card, extraUses)
    local uses = internal.getUses(card)
    if uses == nil then
      return
    end
    local type = nil
    local token = nil
    local tokenCount = 0
    -- Uses structure underwent a breaking change in 2.4.0, have to check to see if this is
    -- a single entry or an array.  This is ugly and duplicated, but impossible to replicate the
    -- multi-spawn vs. single spawn otherwise.  TODO: Clean this up when 2.4.0 has been out long
    -- enough that saved decks don't have old data
    if uses.count != nil then
      type = cardMetadata.uses.type
      token = cardMetadata.uses.token
      tokenCount = cardMetadata.uses.count
      if extraUses ~= nil and extraUses[type] ~= nil then
        tokenCount = tokenCount + extraUses[type]
      end
      log("Spawning single use tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
      TokenManager.spawnTokenGroup(card, token, tokenCount)
    else
      for i, useInfo in ipairs(uses) do
        type = useInfo.type
        token = useInfo.token
        tokenCount = (useInfo.count or 0)
            + (useInfo.countPerInvestigator or 0) * playArea.getInvestigatorCount()
        if extraUses ~= nil and extraUses[type] ~= nil then
          tokenCount = tokenCount + extraUses[type]
        end
        log("Spawning use array tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
        -- Shift each spawned group after the first down so they don't pile on each other
        TokenManager.spawnTokenGroup(card, token, tokenCount, (i - 1) * 0.8)
      end
    end
    tokenSpawnTracker.markTokensSpawned(card.getGUID())
  end

  -- Spawn tokens for a card based on the data helper data.  This will consider the face up/down state
  -- of the card for both locations and standard cards.
  ---@param card Object Card to maybe spawn tokens for
  internal.spawnTokensFromDataHelper = function(card)
    internal.initDataHelperData()
    local playerData = internal.getPlayerCardData(card)
    if playerData ~= nil then
      internal.spawnPlayerCardTokensFromDataHelper(card, playerData)
    end
    local locationData = internal.getLocationData(card)
    if locationData ~= nil then
      internal.spawnLocationTokensFromDataHelper(card, locationData)
    end
  end

  -- Spawn tokens for a player card using data retrieved from the Data Helper.
  ---@param card Object Card to maybe spawn tokens for
  ---@param playerData Table Player card data structure retrieved from the DataHelper.  Should be
  --     the right data for this card.
  internal.spawnPlayerCardTokensFromDataHelper = function(card, playerData)
    token = playerData.tokenType
    tokenCount = playerData.tokenCount
    log("Spawning data helper tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
    TokenManager.spawnTokenGroup(card, token, tokenCount)
    tokenSpawnTracker.markTokensSpawned(card.getGUID())
  end

  -- Spawn tokens for a location using data retrieved from the Data Helper.
  ---@param card Object Card to maybe spawn tokens for
  ---@param playerData Table Location data structure retrieved from the DataHelper.  Should be
  --     the right data for this card.
  internal.spawnLocationTokensFromDataHelper = function(card, locationData)
    local clueCount = internal.getClueCountFromData(card, locationData)
    if clueCount > 0 then
      TokenManager.spawnTokenGroup(card, "clue", clueCount)
      tokenSpawnTracker.markTokensSpawned(card.getGUID())
    end
  end

  internal.getPlayerCardData = function(card)
    return playerCardData[card.getName() .. ':' .. card.getDescription()]
        or playerCardData[card.getName()]
  end

  internal.getLocationData = function(card)
    return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()]
  end

  internal.getClueCountFromData = function(card, locationData)
    -- Return the number of clues to spawn on this location
    if locationData == nil then
      error('attempted to get clue for unexpected object: ' .. card.getName())
      return 0
    end

    log(card.getName() .. ' : ' .. locationData.type .. ' : ' .. locationData.value .. ' : ' .. locationData.clueSide)
    if ((card.is_face_down and locationData.clueSide == 'back')
        or (not card.is_face_down and locationData.clueSide == 'front')) then
      if locationData.type == 'fixed' then
        return locationData.value
      elseif locationData.type == 'perPlayer' then
        return locationData.value * playArea.getInvestigatorCount()
      end
      error('unexpected location type: ' .. locationData.type)
    end
    return 0
  end

  -- Gets the right uses structure for this card, based on metadata and face up/down state
  ---@param card Object Card to pull the uses from
  internal.getUses = function(card)
    local metadata = JSON.decode(card.getGMNotes()) or { }
    if metadata.type == "Location" then
      if card.is_face_down and metadata.locationBack ~= nil then
        return metadata.locationBack.uses
      elseif not card.is_face_down and metadata.locationFront ~= nil then
        return metadata.locationFront.uses
      end
    elseif not card.is_face_down then
      return metadata.uses
    end

    return nil
  end

  -- Dynamically create positions for clues on a card.
  ---@param card Object Card the clues will be placed on
  ---@param count Integer How many clues?
  ---@return Table Array of global positions to spawn the clues at
  internal.buildClueOffsets = function(card, count)
    local pos = card.getPosition()
    local cluePositions = { }
    for i = 1, count do
      local row = math.floor(1 + (i - 1) / 4)
      local column = (i - 1) % 4
      table.insert(cluePositions, Vector(pos.x + 1.5 - 0.55 * row, pos.y + 0.15, pos.z - 0.825 + 0.55 * column))
    end

    return cluePositions
  end

  return TokenManager

end
end)
__bundle_register("core/PlayAreaApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
  local PlayAreaApi = { }

  local PLAY_AREA_GUID = "721ba2"

  -- Returns the current value of the investigator counter from the playmat
  ---@return Integer.  Number of investigators currently set on the counter
  PlayAreaApi.getInvestigatorCount = function()
    return getObjectFromGUID(PLAY_AREA_GUID).call("getInvestigatorCount")
  end

  -- Move all contents on the play area (cards, tokens, etc) one slot in the given direction.  Certain
  -- fixed objects will be ignored, as will anything the player has tagged with
  -- 'displacement_excluded'
  ---@param playerColor Color of the player requesting the shift.  Used solely to send an error
  ---    message in the unlikely case that the scripting zone has been deleted
  PlayAreaApi.shiftContentsUp = function(playerColor)
    return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsUp", playerColor)
  end

  PlayAreaApi.shiftContentsDown = function(playerColor)
    return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsDown", playerColor)
  end

  PlayAreaApi.shiftContentsLeft = function(playerColor)
    return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsLeft", playerColor)
  end

  PlayAreaApi.shiftContentsRight = function(playerColor)
    return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsRight", playerColor)
  end

  -- Reset the play area's tracking of which cards have had tokens spawned.
  PlayAreaApi.resetSpawnedCards = function()
    return getObjectFromGUID(PLAY_AREA_GUID).call("resetSpawnedCards")
  end

  -- Event to be called when the current scenario has changed.
  ---@param scenarioName Name of the new scenario
  PlayAreaApi.onScenarioChanged = function(scenarioName)
    getObjectFromGUID(PLAY_AREA_GUID).call("onScenarioChanged", scenarioName)
  end

  -- Sets this playmat's snap points to limit snapping to locations or not.
  -- If matchTypes is false, snap points will be reset to snap all cards.
  ---@param matchTypes Boolean  Whether snap points should only snap for the matching card types.
  PlayAreaApi.setLimitSnapsByType = function(matchCardTypes)
    getObjectFromGUID(PLAY_AREA_GUID).call("setLimitSnapsByType", matchCardTypes)
  end

  -- Receiver for the Global tryObjectEnterContainer event.  Used to clear vector lines from dragged
  -- cards before they're destroyed by entering the container
  PlayAreaApi.tryObjectEnterContainer = function(container, object)
    getObjectFromGUID(PLAY_AREA_GUID).call("tryObjectEnterContainer",
        { container = container, object = object })
  end

  return PlayAreaApi
end
end)
__bundle_register("core/token/TokenSpawnTrackerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
  local TokenSpawnTracker = { }

  local SPAWN_TRACKER_GUID = "e3ffc9"

  TokenSpawnTracker.hasSpawnedTokens = function(cardGuid)
    return getObjectFromGUID(SPAWN_TRACKER_GUID).call("hasSpawnedTokens", cardGuid)
  end

  TokenSpawnTracker.markTokensSpawned = function(cardGuid)
    return getObjectFromGUID(SPAWN_TRACKER_GUID).call("markTokensSpawned", cardGuid)
  end

  TokenSpawnTracker.resetTokensSpawned = function(cardGuid)
    return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetTokensSpawned", cardGuid)
  end

  TokenSpawnTracker.resetAllAssetAndEvents = function()
    return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllAssetAndEvents")
  end

  TokenSpawnTracker.resetAllLocations = function()
    return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllLocations")
  end

  TokenSpawnTracker.resetAll = function()
    return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAll")
  end

  return TokenSpawnTracker
end
end)
return __bundle_require("__root")