|
|
|
@ -19,15 +19,15 @@ local DISCARD_BUTTON_OFFSETS = {-1.365, -0.91, -0.455, 0, 0.455, 0.91}
|
|
|
|
|
|
|
|
|
|
local SEARCH_AROUND_SELF_X_BUFFER = 8
|
|
|
|
|
|
|
|
|
|
-- defined areas for the function "inArea()""
|
|
|
|
|
-- defined areas for "inArea()" and "Physics.cast()"
|
|
|
|
|
local MAIN_PLAY_AREA = {
|
|
|
|
|
upperLeft = {
|
|
|
|
|
x = 1.98,
|
|
|
|
|
z = 0.736,
|
|
|
|
|
z = 0.736
|
|
|
|
|
},
|
|
|
|
|
lowerRight = {
|
|
|
|
|
x = -0.79,
|
|
|
|
|
z = -0.39,
|
|
|
|
|
z = -0.39
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
local INVESTIGATOR_AREA = {
|
|
|
|
@ -37,7 +37,7 @@ local INVESTIGATOR_AREA = {
|
|
|
|
|
},
|
|
|
|
|
lowerRight = {
|
|
|
|
|
x = -1.258,
|
|
|
|
|
z = -0.0805,
|
|
|
|
|
z = -0.0805
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
local THREAT_AREA = {
|
|
|
|
@ -47,7 +47,27 @@ local THREAT_AREA = {
|
|
|
|
|
},
|
|
|
|
|
lowerRight = {
|
|
|
|
|
x = -1.13,
|
|
|
|
|
z = -0.92,
|
|
|
|
|
z = -0.92
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
local DECK_DISCARD_AREA = {
|
|
|
|
|
upperLeft = {
|
|
|
|
|
x = -1.62,
|
|
|
|
|
z = 0.855
|
|
|
|
|
},
|
|
|
|
|
lowerRight = {
|
|
|
|
|
x = -2.02,
|
|
|
|
|
z = -0.245
|
|
|
|
|
},
|
|
|
|
|
center = {
|
|
|
|
|
x = -1.82,
|
|
|
|
|
y = 0.1,
|
|
|
|
|
z = 0.305
|
|
|
|
|
},
|
|
|
|
|
size = {
|
|
|
|
|
x = 0.4,
|
|
|
|
|
y = 0.1,
|
|
|
|
|
z = 1.1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -69,14 +89,13 @@ isDES = false
|
|
|
|
|
|
|
|
|
|
function onSave()
|
|
|
|
|
return JSON.encode({
|
|
|
|
|
zoneID = zoneID,
|
|
|
|
|
playerColor = playerColor,
|
|
|
|
|
activeInvestigatorId = activeInvestigatorId,
|
|
|
|
|
isDrawButtonVisible = isDrawButtonVisible
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function onLoad(save_state)
|
|
|
|
|
function onLoad(saveState)
|
|
|
|
|
self.interactable = DEBUG
|
|
|
|
|
|
|
|
|
|
TRASHCAN = getObjectFromGUID(TRASHCAN_GUID)
|
|
|
|
@ -118,9 +137,8 @@ function onLoad(save_state)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
-- save state loading
|
|
|
|
|
local state = JSON.decode(save_state)
|
|
|
|
|
local state = JSON.decode(saveState)
|
|
|
|
|
if state ~= nil then
|
|
|
|
|
zoneID = state.zoneID
|
|
|
|
|
playerColor = state.playerColor
|
|
|
|
|
activeInvestigatorId = state.activeInvestigatorId
|
|
|
|
|
isDrawButtonVisible = state.isDrawButtonVisible
|
|
|
|
@ -128,7 +146,6 @@ function onLoad(save_state)
|
|
|
|
|
|
|
|
|
|
showDrawButton(isDrawButtonVisible)
|
|
|
|
|
|
|
|
|
|
if getObjectFromGUID(zoneID) == nil then spawnDeckZone() end
|
|
|
|
|
collisionEnabled = true
|
|
|
|
|
|
|
|
|
|
math.randomseed(os.time())
|
|
|
|
@ -138,20 +155,9 @@ end
|
|
|
|
|
-- utility functions
|
|
|
|
|
---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
function spawnDeckZone()
|
|
|
|
|
spawnObject({
|
|
|
|
|
position = self.positionToWorld({-1.4, 0, 0.3 }),
|
|
|
|
|
scale = {3, 5, 8 },
|
|
|
|
|
type = 'ScriptingTrigger',
|
|
|
|
|
callback = function (zone) zoneID = zone.getGUID() end,
|
|
|
|
|
callback_owner = self,
|
|
|
|
|
rotation = self.getRotation()
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- searches an area and optionally filters the result
|
|
|
|
|
function searchArea(origin, size, filter)
|
|
|
|
|
local objList = Physics.cast({
|
|
|
|
|
local searchResult = Physics.cast({
|
|
|
|
|
origin = origin,
|
|
|
|
|
direction = { 0, 1, 0 },
|
|
|
|
|
orientation = self.getRotation(),
|
|
|
|
@ -160,26 +166,22 @@ function searchArea(origin, size, filter)
|
|
|
|
|
max_distance = 1
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if filter then
|
|
|
|
|
local filteredList = {}
|
|
|
|
|
for _, obj in ipairs(objList) do
|
|
|
|
|
if filter(obj.hit_object) then
|
|
|
|
|
table.insert(filteredList, obj)
|
|
|
|
|
end
|
|
|
|
|
local objList = {}
|
|
|
|
|
for _, v in ipairs(searchResult) do
|
|
|
|
|
if not filter or (filter and filter(v.hit_object)) then
|
|
|
|
|
table.insert(objList, v.hit_object)
|
|
|
|
|
end
|
|
|
|
|
return filteredList
|
|
|
|
|
else
|
|
|
|
|
return objList
|
|
|
|
|
end
|
|
|
|
|
return objList
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- filter functions for searchArea
|
|
|
|
|
function isDeck(x) return x.tag == 'Deck' end
|
|
|
|
|
|
|
|
|
|
function isCardOrDeck(x) return x.tag == 'Card' or x.tag == 'Deck' end
|
|
|
|
|
-- filter functions for searchArea()
|
|
|
|
|
function isCard(x) return x.type == 'Card' end
|
|
|
|
|
function isDeck(x) return x.type == 'Deck' end
|
|
|
|
|
function isCardOrDeck(x) return x.type == 'Card' or x.type == 'Deck' end
|
|
|
|
|
|
|
|
|
|
-- Finds all objects on the playmat and associated set aside zone.
|
|
|
|
|
function searchAroundSelf()
|
|
|
|
|
function searchAroundSelf(filter)
|
|
|
|
|
local bounds = self.getBoundsNormalized()
|
|
|
|
|
-- Increase the width to cover the set aside zone
|
|
|
|
|
bounds.size.x = bounds.size.x + SEARCH_AROUND_SELF_X_BUFFER
|
|
|
|
@ -190,19 +192,19 @@ function searchAroundSelf()
|
|
|
|
|
local setAsideDirection = bounds.center.z > 0 and 1 or -1
|
|
|
|
|
local localCenter = self.positionToLocal(bounds.center)
|
|
|
|
|
localCenter.x = localCenter.x + setAsideDirection * SEARCH_AROUND_SELF_X_BUFFER / 2 / self.getScale().x
|
|
|
|
|
return searchArea(self.positionToWorld(localCenter), bounds.size)
|
|
|
|
|
return searchArea(self.positionToWorld(localCenter), bounds.size, filter)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function findCardsAroundSelf()
|
|
|
|
|
local cards = { }
|
|
|
|
|
for _, collision in ipairs(searchAroundSelf()) do
|
|
|
|
|
local obj = collision.hit_object
|
|
|
|
|
if obj.name == "Card" or obj.name == "CardCustom" then
|
|
|
|
|
table.insert(cards, obj)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return cards
|
|
|
|
|
-- searches the area around the draw deck and discard pile
|
|
|
|
|
function searchDeckAndDiscardArea(filter)
|
|
|
|
|
local pos = self.positionToWorld(DECK_DISCARD_AREA.center)
|
|
|
|
|
local scale = self.getScale()
|
|
|
|
|
local size = {
|
|
|
|
|
x = DECK_DISCARD_AREA.size.x * scale.x,
|
|
|
|
|
y = DECK_DISCARD_AREA.size.y,
|
|
|
|
|
z = DECK_DISCARD_AREA.size.z * scale.z
|
|
|
|
|
}
|
|
|
|
|
return searchArea(pos, size, filter)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function doNotReady(card)
|
|
|
|
@ -215,11 +217,11 @@ end
|
|
|
|
|
|
|
|
|
|
-- builds a function that discards things in searchPosition
|
|
|
|
|
-- stuff on the card/deck will be put into the local trashcan
|
|
|
|
|
function makeDiscardHandlerFor(searchPosition, )
|
|
|
|
|
function makeDiscardHandlerFor(searchPosition)
|
|
|
|
|
return function ()
|
|
|
|
|
for _, hitObj in ipairs(findObjectsAtPosition(searchPosition)) do
|
|
|
|
|
local obj = hitObj.hit_object
|
|
|
|
|
if obj.tag == "Deck" or obj.tag == "Card" then
|
|
|
|
|
local origin = self.positionToWorld(searchPosition)
|
|
|
|
|
for _, obj in ipairs(searchArea(origin, {2, 1, 3.2})) do
|
|
|
|
|
if isCardOrDeck(obj) then
|
|
|
|
|
if obj.hasTag("PlayerCard") then
|
|
|
|
|
placeOrMergeIntoDeck(obj, returnGlobalDiscardPosition(), self.getRotation())
|
|
|
|
|
else
|
|
|
|
@ -248,7 +250,7 @@ function placeOrMergeIntoDeck(obj, pos, rot)
|
|
|
|
|
-- search the new position for existing card/deck
|
|
|
|
|
local searchResult = searchArea(pos, { 1, 1, 1 }, isCardOrDeck)
|
|
|
|
|
if #searchResult == 1 then
|
|
|
|
|
local match = searchResult[1].hit_object
|
|
|
|
|
local match = searchResult[1]
|
|
|
|
|
if match.type == 'Card' then
|
|
|
|
|
card = match
|
|
|
|
|
elseif match.type == 'Deck' then
|
|
|
|
@ -258,7 +260,7 @@ function placeOrMergeIntoDeck(obj, pos, rot)
|
|
|
|
|
|
|
|
|
|
-- update vertical component of new position
|
|
|
|
|
if card or deck then
|
|
|
|
|
local bounds = searchResult[1].hit_object.getBounds()
|
|
|
|
|
local bounds = searchResult[1].getBounds()
|
|
|
|
|
newPos = Vector(pos):setAt("y", bounds.center.y + bounds.size.y / 2 + offset)
|
|
|
|
|
else
|
|
|
|
|
newPos = Vector(pos) + Vector(0, offset, 0)
|
|
|
|
@ -297,18 +299,6 @@ function makeDiscardButton(xValue, number)
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function findObjectsAtPosition(localPos)
|
|
|
|
|
return Physics.cast({
|
|
|
|
|
origin = self.positionToWorld(localPos),
|
|
|
|
|
direction = {0, 1, 0},
|
|
|
|
|
orientation = {0, self.getRotation().y + 90, 0},
|
|
|
|
|
type = 3,
|
|
|
|
|
size = {3.2, 1, 2},
|
|
|
|
|
max_distance = 0,
|
|
|
|
|
debug = DEBUG
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------
|
|
|
|
|
-- Upkeep button
|
|
|
|
|
---------------------------------------------------------
|
|
|
|
@ -331,11 +321,10 @@ function doUpkeep(_, clickedByColor, isRightClick)
|
|
|
|
|
-- unexhaust cards in play zone, flip action tokens and find forcedLearning
|
|
|
|
|
local forcedLearning = false
|
|
|
|
|
local rot = self.getRotation()
|
|
|
|
|
for _, v in ipairs(searchAroundSelf()) do
|
|
|
|
|
local obj = v.hit_object
|
|
|
|
|
for _, obj in ipairs(searchAroundSelf()) do
|
|
|
|
|
if obj.getDescription() == "Action Token" and obj.is_face_down then
|
|
|
|
|
obj.flip()
|
|
|
|
|
elseif obj.tag == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
|
|
|
|
|
elseif obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
|
|
|
|
|
local cardMetadata = JSON.decode(obj.getGMNotes()) or {}
|
|
|
|
|
if not doNotReady(obj) then
|
|
|
|
|
local cardRotation = round(obj.getRotation().y, 0) - rot.y
|
|
|
|
@ -370,7 +359,7 @@ function doUpkeep(_, clickedByColor, isRightClick)
|
|
|
|
|
if activeInvestigatorId ~= nil then
|
|
|
|
|
local miniId = string.match(activeInvestigatorId, ".....") .. "-m"
|
|
|
|
|
for _, obj in ipairs(getObjects()) do
|
|
|
|
|
if obj.tag == "Card" and obj.is_face_down then
|
|
|
|
|
if obj.type == "Card" and obj.is_face_down then
|
|
|
|
|
local notes = JSON.decode(obj.getGMNotes())
|
|
|
|
|
if notes ~= nil and notes.type == "Minicard" and (notes.id == miniId or notes.id == "09080-m") then
|
|
|
|
|
obj.flip()
|
|
|
|
@ -480,19 +469,14 @@ function getDrawDiscardDecks()
|
|
|
|
|
discardPile = nil
|
|
|
|
|
topCard = nil
|
|
|
|
|
|
|
|
|
|
local zone = getObjectFromGUID(zoneID)
|
|
|
|
|
if zone == nil then return end
|
|
|
|
|
|
|
|
|
|
for _, object in ipairs(zone.getObjects()) do
|
|
|
|
|
if object.tag == "Deck" or object.tag == "Card" then
|
|
|
|
|
if self.positionToLocal(object.getPosition()).z > 0.5 then
|
|
|
|
|
discardPile = object
|
|
|
|
|
-- Norman Withers handling
|
|
|
|
|
elseif string.match(activeInvestigatorId, "%d%d%d%d%d") == "08004" and object.tag == "Card" and not object.is_face_down then
|
|
|
|
|
topCard = object
|
|
|
|
|
else
|
|
|
|
|
drawDeck = object
|
|
|
|
|
end
|
|
|
|
|
for _, object in ipairs(searchDeckAndDiscardArea(isCardOrDeck)) do
|
|
|
|
|
if self.positionToLocal(object.getPosition()).z > 0.5 then
|
|
|
|
|
discardPile = object
|
|
|
|
|
-- Norman Withers handling
|
|
|
|
|
elseif string.match(activeInvestigatorId, "%d%d%d%d%d") == "08004" and not object.is_face_down then
|
|
|
|
|
topCard = object
|
|
|
|
|
else
|
|
|
|
|
drawDeck = object
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
@ -619,7 +603,7 @@ end
|
|
|
|
|
-- called when a checkbox is added or removed in-game (which should be rare), and is bounded by the
|
|
|
|
|
-- number of customizable cards in play.
|
|
|
|
|
function syncAllCustomizableCards()
|
|
|
|
|
for _, card in ipairs(findCardsAroundSelf()) do
|
|
|
|
|
for _, card in ipairs(searchAroundSelf(isCard)) do
|
|
|
|
|
syncCustomizableMetadata(card)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
@ -629,7 +613,7 @@ function syncCustomizableMetadata(card)
|
|
|
|
|
if cardMetadata == nil or cardMetadata.customizations == nil then
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
for _, upgradeSheet in ipairs(findCardsAroundSelf()) do
|
|
|
|
|
for _, upgradeSheet in ipairs(searchAroundSelf(isCard)) do
|
|
|
|
|
local upgradeSheetMetadata = JSON.decode(upgradeSheet.getGMNotes()) or { }
|
|
|
|
|
if upgradeSheetMetadata.id == (cardMetadata.id .. "-c") then
|
|
|
|
|
for i, customization in ipairs(cardMetadata.customizations) do
|
|
|
|
@ -669,12 +653,13 @@ function onCollisionEnter(collision_info)
|
|
|
|
|
if not collisionEnabled then return end
|
|
|
|
|
|
|
|
|
|
-- only continue for cards
|
|
|
|
|
if object.name ~= "Card" and object.name ~= "CardCustom" then return end
|
|
|
|
|
if object.type ~= "Card" then return end
|
|
|
|
|
|
|
|
|
|
maybeUpdateActiveInvestigator(object)
|
|
|
|
|
syncCustomizableMetadata(object)
|
|
|
|
|
|
|
|
|
|
if isInDeckZone(object) then
|
|
|
|
|
local localCardPos = self.positionToLocal(object.getPosition())
|
|
|
|
|
if inArea(localCardPos, DECK_DISCARD_AREA) then
|
|
|
|
|
tokenManager.resetTokensSpawned(object)
|
|
|
|
|
removeTokensFromObject(object)
|
|
|
|
|
elseif shouldSpawnTokens(object) then
|
|
|
|
@ -724,33 +709,16 @@ function onObjectEnterContainer(container, object)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function resetTokensIfInDeckZone(container, object)
|
|
|
|
|
if isInDeckZone(container) then
|
|
|
|
|
local pos = self.positionToLocal(container.getPosition())
|
|
|
|
|
if inArea(pos, DECK_DISCARD_AREA) then
|
|
|
|
|
tokenManager.resetTokensSpawned(object)
|
|
|
|
|
removeTokensFromObject(container)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- checks if an object is in this mats deckzone
|
|
|
|
|
function isInDeckZone(checkObject)
|
|
|
|
|
local deckZone = getObjectFromGUID(zoneID)
|
|
|
|
|
if deckZone == nil then
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
for _, obj in ipairs(deckZone.getObjects()) do
|
|
|
|
|
if obj == checkObject then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- removes tokens from the provided card/deck
|
|
|
|
|
function removeTokensFromObject(object)
|
|
|
|
|
for _, v in ipairs(searchArea(object.getPosition(), { 3, 1, 4 })) do
|
|
|
|
|
local obj = v.hit_object
|
|
|
|
|
|
|
|
|
|
for _, obj in ipairs(searchArea(object.getPosition(), { 3, 1, 4 })) do
|
|
|
|
|
if obj.getGUID() ~= "4ee1f2" and -- table
|
|
|
|
|
obj ~= self and
|
|
|
|
|
obj.type ~= "Deck" and
|
|
|
|
@ -800,7 +768,6 @@ function maybeUpdateActiveInvestigator(card)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, obj in ipairs(search) do
|
|
|
|
|
local obj = obj.hit_object
|
|
|
|
|
if obj.getDescription() == "Action Token" and obj.getStateId() > 0 then
|
|
|
|
|
if obj.getScale().x < 0.4 then
|
|
|
|
|
smallToken = obj
|
|
|
|
@ -977,10 +944,10 @@ function setLimitSnapsByType(matchTypes)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Simple method to check if the given point is in a specified area. Local use only,
|
|
|
|
|
---@param point Vector. Point to check, only x and z values are relevant
|
|
|
|
|
---@param bounds Table. Defined area to see if the point is within. See MAIN_PLAY_AREA for sample
|
|
|
|
|
---@param point Vector Point to check, only x and z values are relevant
|
|
|
|
|
---@param bounds Table Defined area to see if the point is within. See MAIN_PLAY_AREA for sample
|
|
|
|
|
-- bounds definition.
|
|
|
|
|
---@return Boolean. True if the point is in the area defined by bounds
|
|
|
|
|
---@return Boolean True if the point is in the area defined by bounds
|
|
|
|
|
function inArea(point, bounds)
|
|
|
|
|
return (point.x < bounds.upperLeft.x
|
|
|
|
|
and point.x > bounds.lowerRight.x
|
|
|
|
|