Merge pull request #377 from argonui/encounter-deck
Encounter card drawing / discard: moved code and fixed TTS merge bug
This commit is contained in:
commit
454eb80330
@ -34,7 +34,6 @@ local NOT_INTERACTABLE = {
|
||||
|
||||
local chaosTokens = {}
|
||||
local chaosTokensLastMat = nil
|
||||
local IS_RESHUFFLING = false
|
||||
local bagSearchers = {}
|
||||
local MAT_COLORS = {"White", "Orange", "Green", "Red"}
|
||||
local hideTitleSplashWaitFunctionId = nil
|
||||
@ -143,14 +142,6 @@ function onLoad(savedData)
|
||||
math.randomseed(os.time())
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- encounter card drawing
|
||||
---------------------------------------------------------
|
||||
|
||||
function isDeck(x) return x.tag == 'Deck' end
|
||||
|
||||
function isCardOrDeck(x) return x.tag == 'Card' or x.tag == 'Deck' end
|
||||
|
||||
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
|
||||
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
|
||||
-- chaos bag during search operations to avoid this.
|
||||
@ -179,82 +170,6 @@ function tryObjectEnterContainer(container, object)
|
||||
return true
|
||||
end
|
||||
|
||||
function drawEncountercard(params)
|
||||
local position = params[1]
|
||||
local rotation = params[2]
|
||||
local alwaysFaceUp = params[3]
|
||||
local card
|
||||
local items = findInRadiusBy(ENCOUNTER_DECK_POS, 4, isCardOrDeck)
|
||||
if #items > 0 then
|
||||
for _, v in ipairs(items) do
|
||||
if v.tag == 'Deck' then
|
||||
card = v.takeObject({index = 0})
|
||||
break
|
||||
end
|
||||
end
|
||||
-- we didn't find the deck so just pull the first thing we did find
|
||||
if card == nil then card = items[1] end
|
||||
actualEncounterCardDraw(card, params)
|
||||
else
|
||||
-- nothing here, time to reshuffle
|
||||
reshuffleEncounterDeck(params)
|
||||
end
|
||||
end
|
||||
|
||||
function actualEncounterCardDraw(card, params)
|
||||
local position = params[1]
|
||||
local rotation = params[2]
|
||||
local alwaysFaceUp = params[3]
|
||||
local faceUpRotation = 0
|
||||
if not alwaysFaceUp then
|
||||
local metadata = JSON.decode(card.getGMNotes()) or {}
|
||||
if metadata.hidden or getObjectFromGUID(DATA_HELPER_GUID).call('checkHiddenCard', card.getName()) then
|
||||
faceUpRotation = 180
|
||||
end
|
||||
end
|
||||
card.setPositionSmooth(position, false, false)
|
||||
card.setRotationSmooth({0, rotation.y, faceUpRotation}, false, false)
|
||||
end
|
||||
|
||||
function reshuffleEncounterDeck(params)
|
||||
-- finishes moving the deck back and draws a card
|
||||
local function move(deck)
|
||||
deck.setPositionSmooth({ENCOUNTER_DECK_POS[1], ENCOUNTER_DECK_POS[2] + 2, ENCOUNTER_DECK_POS[3]}, false, true)
|
||||
actualEncounterCardDraw(deck.takeObject({index=0}), params)
|
||||
Wait.time(function() IS_RESHUFFLING = false end, 1)
|
||||
end
|
||||
-- bail out if we're mid reshuffle
|
||||
if IS_RESHUFFLING then return end
|
||||
local discarded = findInRadiusBy(ENCOUNTER_DECK_DISCARD_POSITION, 4, isDeck)
|
||||
if #discarded > 0 then
|
||||
IS_RESHUFFLING = true
|
||||
local deck = discarded[1]
|
||||
if not deck.is_face_down then deck.flip() end
|
||||
deck.shuffle()
|
||||
Wait.time(|| move(deck), 0.3)
|
||||
else
|
||||
printToAll("Couldn't find encounter discard pile to reshuffle.", {1, 0, 0})
|
||||
end
|
||||
end
|
||||
|
||||
function findInRadiusBy(pos, radius, filter)
|
||||
local objList = Physics.cast({
|
||||
origin = pos,
|
||||
direction = {0, 1, 0},
|
||||
type = 2,
|
||||
size = {radius, radius, radius},
|
||||
max_distance = 0
|
||||
})
|
||||
|
||||
local filteredList = {}
|
||||
for _, obj in ipairs(objList) do
|
||||
if filter and filter(obj.hit_object) then
|
||||
table.insert(filteredList, obj.hit_object)
|
||||
end
|
||||
end
|
||||
return filteredList
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- chaos token drawing
|
||||
---------------------------------------------------------
|
||||
|
@ -12,9 +12,16 @@ local ENCOUNTER_DISCARD_AREA = {
|
||||
lowerRight = { x = 1.58, z = 0.38 },
|
||||
}
|
||||
|
||||
local currentScenario
|
||||
local useFrontData
|
||||
local tokenData
|
||||
-- global position of encounter deck and discard pile
|
||||
local ENCOUNTER_DECK_POS = { x = -3.93, y = 1, z = 5.76 }
|
||||
local ENCOUNTER_DISCARD_POSITION = { x = -3.85, y = 1, z = 10.38 }
|
||||
local isReshuffling = false
|
||||
|
||||
-- scenario metadata
|
||||
local currentScenario, useFrontData, tokenData
|
||||
|
||||
-- GUID of data helper
|
||||
local DATA_HELPER_GUID = "708279"
|
||||
|
||||
local TRASHCAN
|
||||
local TRASHCAN_GUID = "70b9f6"
|
||||
@ -117,6 +124,68 @@ function returnTokenData()
|
||||
}
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- encounter card drawing
|
||||
---------------------------------------------------------
|
||||
|
||||
-- 'params' contains the position, rotation and a boolean to force a faceup draw
|
||||
function drawEncounterCard(params)
|
||||
local card
|
||||
local items = searchArea(ENCOUNTER_DECK_POS, { 3, 1, 4 }, isCardOrDeck)
|
||||
if #items > 0 then
|
||||
for _, j in ipairs(items) do
|
||||
local v = j.hit_object
|
||||
if v.tag == 'Deck' then
|
||||
card = v.takeObject({ index = 0 })
|
||||
break
|
||||
end
|
||||
end
|
||||
-- we didn't find the deck so just pull the first thing we did find
|
||||
if card == nil then card = items[1].hit_object end
|
||||
actualEncounterCardDraw(card, params)
|
||||
else
|
||||
-- nothing here, time to reshuffle
|
||||
reshuffleEncounterDeck(params)
|
||||
end
|
||||
end
|
||||
|
||||
function actualEncounterCardDraw(card, params)
|
||||
local faceUpRotation = 0
|
||||
if not params.alwaysFaceUp then
|
||||
local metadata = JSON.decode(card.getGMNotes()) or {}
|
||||
if metadata.hidden or getObjectFromGUID(DATA_HELPER_GUID).call('checkHiddenCard', card.getName()) then
|
||||
faceUpRotation = 180
|
||||
end
|
||||
end
|
||||
card.setPositionSmooth(params.pos, false, false)
|
||||
card.setRotationSmooth({ 0, params.rotY, faceUpRotation }, false, false)
|
||||
end
|
||||
|
||||
function reshuffleEncounterDeck(params)
|
||||
-- flag to avoid multiple calls
|
||||
if isReshuffling then return end
|
||||
isReshuffling = true
|
||||
|
||||
-- shuffle and flip deck, draw card after completion
|
||||
local discarded = searchArea(ENCOUNTER_DISCARD_POSITION, { 3, 1, 4 }, isDeck)
|
||||
if #discarded > 0 then
|
||||
local deck = discarded[1].hit_object
|
||||
if not deck.is_face_down then deck.flip() end
|
||||
deck.shuffle()
|
||||
deck.setPositionSmooth(Vector(ENCOUNTER_DECK_POS) + Vector(0, 2, 0), false, true)
|
||||
Wait.time(function() actualEncounterCardDraw(deck.takeObject({ index = 0 }), params) end, 0.5)
|
||||
else
|
||||
printToAll("Couldn't find encounter discard pile to reshuffle.", { 1, 0, 0 })
|
||||
end
|
||||
|
||||
-- disable flag
|
||||
Wait.time(function() isReshuffling = false end, 1)
|
||||
end
|
||||
|
||||
---------------------------------------------------------
|
||||
-- helper functions
|
||||
---------------------------------------------------------
|
||||
|
||||
-- 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
|
||||
@ -124,16 +193,15 @@ end
|
||||
---@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
|
||||
and point.z < bounds.upperLeft.z
|
||||
and point.z > bounds.lowerRight.z)
|
||||
and point.x > bounds.lowerRight.x
|
||||
and point.z < bounds.upperLeft.z
|
||||
and point.z > bounds.lowerRight.z)
|
||||
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
|
||||
|
||||
if obj.getGUID() ~= "4ee1f2" and -- table
|
||||
obj ~= self and
|
||||
obj.type ~= "Deck" and
|
||||
@ -146,13 +214,31 @@ function removeTokensFromObject(object)
|
||||
end
|
||||
end
|
||||
|
||||
function searchArea(origin, size)
|
||||
return Physics.cast({
|
||||
-- searches an area and optionally filters the result
|
||||
function searchArea(origin, size, filter)
|
||||
local objList = Physics.cast({
|
||||
origin = origin,
|
||||
direction = {0, 1, 0},
|
||||
direction = { 0, 1, 0 },
|
||||
orientation = self.getRotation(),
|
||||
type = 3,
|
||||
size = size,
|
||||
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
|
||||
end
|
||||
return filteredList
|
||||
else
|
||||
return objList
|
||||
end
|
||||
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
|
||||
|
@ -7,5 +7,14 @@ do
|
||||
return getObjectFromGUID(MYTHOS_AREA_GUID).call("returnTokenData")
|
||||
end
|
||||
|
||||
-- draw an encounter card to the requested position/rotation
|
||||
MythosAreaApi.drawEncounterCard = function(pos, rotY, alwaysFaceUp)
|
||||
getObjectFromGUID(MYTHOS_AREA_GUID).call("drawEncounterCard", {
|
||||
pos = pos,
|
||||
rotY = rotY,
|
||||
alwaysFaceUp = alwaysFaceUp
|
||||
})
|
||||
end
|
||||
|
||||
return MythosAreaApi
|
||||
end
|
||||
|
@ -1,6 +1,8 @@
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
local tokenChecker = require("core/token/TokenChecker")
|
||||
local chaosBagApi = require("chaosbag/ChaosBagApi")
|
||||
local mythosAreaApi = require("core/MythosAreaApi")
|
||||
local navigationOverlayApi = require("core/NavigationOverlayApi")
|
||||
local tokenChecker = require("core/token/TokenChecker")
|
||||
local tokenManager = require("core/token/TokenManager")
|
||||
|
||||
-- set true to enable debug logging and show Physics.cast()
|
||||
local DEBUG = false
|
||||
@ -49,17 +51,17 @@ local THREAT_AREA = {
|
||||
}
|
||||
}
|
||||
|
||||
local DRAW_DECK_POSITION = { x = -1.82, y = 1, z = 0 }
|
||||
local DISCARD_PILE_POSITION = { x = -1.82, y = 1.5, z = 0.61 }
|
||||
-- local position of draw and discard pile
|
||||
local DRAW_DECK_POSITION = { x = -1.82, y = 0, z = 0 }
|
||||
local DISCARD_PILE_POSITION = { x = -1.82, y = 0, z = 0.61 }
|
||||
|
||||
local TRASHCAN
|
||||
local STAT_TRACKER
|
||||
local RESOURCE_COUNTER
|
||||
|
||||
local chaosBagApi = require("chaosbag/ChaosBagApi")
|
||||
-- global position of encounter discard pile
|
||||
local ENCOUNTER_DISCARD_POSITION = { x = -3.85, y = 1.5, z = 10.38}
|
||||
|
||||
-- global variable so it can be reset by the Clean Up Helper
|
||||
activeInvestigatorId = "00000"
|
||||
|
||||
local TRASHCAN, STAT_TRACKER, RESOURCE_COUNTER
|
||||
local isDrawButtonVisible = false
|
||||
|
||||
-- global variable to report "Dream-Enhancing Serum" status
|
||||
@ -83,11 +85,11 @@ function onLoad(save_state)
|
||||
|
||||
-- button creation
|
||||
for i = 1, 6 do
|
||||
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], {-3.85, 3, 10.38}, i)
|
||||
makeDiscardButton(DISCARD_BUTTON_OFFSETS[i], i)
|
||||
end
|
||||
|
||||
self.createButton({
|
||||
click_function = "drawEncountercard",
|
||||
click_function = "drawEncounterCard",
|
||||
function_owner = self,
|
||||
position = {-1.84, 0, -0.65},
|
||||
rotation = {0, 80, 0},
|
||||
@ -147,18 +149,35 @@ function spawnDeckZone()
|
||||
})
|
||||
end
|
||||
|
||||
function searchArea(origin, size)
|
||||
return Physics.cast({
|
||||
-- searches an area and optionally filters the result
|
||||
function searchArea(origin, size, filter)
|
||||
local objList = Physics.cast({
|
||||
origin = origin,
|
||||
direction = {0, 1, 0},
|
||||
direction = { 0, 1, 0 },
|
||||
orientation = self.getRotation(),
|
||||
type = 3,
|
||||
size = size,
|
||||
max_distance = 1,
|
||||
debug = DEBUG
|
||||
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
|
||||
end
|
||||
return filteredList
|
||||
else
|
||||
return objList
|
||||
end
|
||||
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
|
||||
|
||||
-- Finds all objects on the playmat and associated set aside zone.
|
||||
function searchAroundSelf()
|
||||
local bounds = self.getBoundsNormalized()
|
||||
@ -170,9 +189,7 @@ function searchAroundSelf()
|
||||
-- table position of the playmat
|
||||
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
|
||||
|
||||
localCenter.x = localCenter.x + setAsideDirection * SEARCH_AROUND_SELF_X_BUFFER / 2 / self.getScale().x
|
||||
return searchArea(self.positionToWorld(localCenter), bounds.size)
|
||||
end
|
||||
|
||||
@ -196,19 +213,17 @@ end
|
||||
-- Discard buttons
|
||||
---------------------------------------------------------
|
||||
|
||||
-- builds a function that discards things in searchPosition to discardPosition
|
||||
-- builds a function that discards things in searchPosition
|
||||
-- stuff on the card/deck will be put into the local trashcan
|
||||
function makeDiscardHandlerFor(searchPosition, discardPosition)
|
||||
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
|
||||
if obj.hasTag("PlayerCard") then
|
||||
obj.setPositionSmooth(self.positionToWorld(DISCARD_PILE_POSITION), false, true)
|
||||
obj.setRotation(self.getRotation())
|
||||
placeOrMergeIntoDeck(obj, returnGlobalDiscardPosition(), self.getRotation())
|
||||
else
|
||||
obj.setPositionSmooth(discardPosition, false, true)
|
||||
obj.setRotation({0, -90, 0})
|
||||
placeOrMergeIntoDeck(obj, ENCOUNTER_DISCARD_POSITION, {x = 0, y = -90, z = 0})
|
||||
end
|
||||
-- put chaos tokens back into bag (e.g. Unrelenting)
|
||||
elseif tokenChecker.isChaosToken(obj) then
|
||||
@ -222,11 +237,52 @@ function makeDiscardHandlerFor(searchPosition, discardPosition)
|
||||
end
|
||||
end
|
||||
|
||||
-- build a discard button to discard from searchPosition to discardPosition (number must be unique)
|
||||
function makeDiscardButton(xValue, discardPosition, number)
|
||||
-- places a card/deck at a position or merges into an existing deck
|
||||
-- rotation is optional
|
||||
function placeOrMergeIntoDeck(obj, pos, rot)
|
||||
if not pos then return end
|
||||
|
||||
local offset = 0.5
|
||||
local deck, card, newPos
|
||||
|
||||
-- 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
|
||||
if match.type == 'Card' then
|
||||
card = match
|
||||
elseif match.type == 'Deck' then
|
||||
deck = match
|
||||
end
|
||||
end
|
||||
|
||||
-- update vertical component of new position
|
||||
if card or deck then
|
||||
local bounds = searchResult[1].hit_object.getBounds()
|
||||
newPos = Vector(pos):setAt("y", bounds.center.y + bounds.size.y / 2 + offset)
|
||||
else
|
||||
newPos = Vector(pos) + Vector(0, offset, 0)
|
||||
end
|
||||
|
||||
-- actual movement of the object
|
||||
if rot then
|
||||
obj.setRotationSmooth(rot, false, true)
|
||||
end
|
||||
obj.setPositionSmooth(newPos, false, true)
|
||||
|
||||
-- this avoids a TTS bug that merges unrelated cards that are not resting
|
||||
if deck then
|
||||
Wait.time(function() deck.putObject(obj) end, 0.3)
|
||||
elseif card then
|
||||
Wait.time(function() obj.setPosition(newPos) end, 0.3)
|
||||
end
|
||||
end
|
||||
|
||||
-- build a discard button to discard from searchPosition (number must be unique)
|
||||
function makeDiscardButton(xValue, number)
|
||||
local position = { xValue, 0.1, -0.94}
|
||||
local searchPosition = {-position[1], position[2], position[3] + 0.32}
|
||||
local handler = makeDiscardHandlerFor(searchPosition, discardPosition)
|
||||
local handler = makeDiscardHandlerFor(searchPosition)
|
||||
local handlerName = 'handler' .. number
|
||||
self.setVar(handlerName, handler)
|
||||
self.createButton({
|
||||
@ -479,7 +535,7 @@ function doDiscardOne()
|
||||
|
||||
-- get a random non-hidden card (from the "choices" table)
|
||||
local num = math.random(1, #choices)
|
||||
hand[choices[num]].setPosition(returnGlobalDiscardPosition())
|
||||
placeOrMergeIntoDeck(hand[choices[num]], returnGlobalDiscardPosition(), self.getRotation())
|
||||
broadcastToAll(playerColor .. " randomly discarded card " .. choices[num] .. "/" .. #hand .. ".", "White")
|
||||
end
|
||||
end
|
||||
@ -782,8 +838,10 @@ function drawChaosTokenButton(_, _, isRightClick)
|
||||
chaosBagApi.drawChaosToken(self, DRAWN_CHAOS_TOKEN_OFFSET, isRightClick)
|
||||
end
|
||||
|
||||
function drawEncountercard(_, _, isRightClick)
|
||||
Global.call("drawEncountercard", {self.positionToWorld(DRAWN_ENCOUNTER_CARD_OFFSET), self.getRotation(), isRightClick})
|
||||
function drawEncounterCard(_, _, isRightClick)
|
||||
local pos = self.positionToWorld(DRAWN_ENCOUNTER_CARD_OFFSET)
|
||||
local rotY = self.getRotation().y
|
||||
mythosAreaApi.drawEncounterCard(pos, rotY, isRightClick)
|
||||
end
|
||||
|
||||
function returnGlobalDiscardPosition()
|
||||
|
Loading…
Reference in New Issue
Block a user