diff --git a/config.json b/config.json
index 2c6d51e1..566b0050 100644
--- a/config.json
+++ b/config.json
@@ -27,6 +27,8 @@
"HandTrigger.be2f17",
"HandTrigger.0285cc",
"HandTrigger.a70eee",
+ "ScriptingTrigger.a2f932",
+ "FogOfWarTrigger.3aab97",
"TableLegBottomRight.afc863",
"TableLegBottomLeft.c8edca",
"TableLegTopLeft.393bf7",
@@ -97,7 +99,6 @@
"Damagetokens.480bda",
"Resourcetokens.9fadf9",
"Connectionmarkers.170f10",
- "FogOfWarTrigger.3aab97",
"ChaosTokenReserve.106418",
"ClueCounter.37be78",
"ClueCounter.1769ed",
@@ -127,7 +128,6 @@
"ChaosBag.fea079",
"DataHelper.708279",
"BlessCurseManager.5933fb",
- "ScriptingTrigger.a2f932",
"EdgeoftheEarth.895eaa",
"TheDream-Eaters.a16a1a",
"TheFeastofHemlockVale.c740af",
diff --git a/modsettings/CustomUIAssets.json b/modsettings/CustomUIAssets.json
index 37fe7bd3..dc84a999 100644
--- a/modsettings/CustomUIAssets.json
+++ b/modsettings/CustomUIAssets.json
@@ -30,12 +30,12 @@
"URL": "http://cloud-3.steamusercontent.com/ugc/2026086584372569912/5CB461AEAE2E59D3064D90A776EB86C46081EC78/"
},
{
- "Name": "option-on",
+ "Name": "option_on",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2462982115668997008/2178787B67B3C96F3419EDBAB8420E39893756BC/"
},
{
- "Name": "option-off",
+ "Name": "option_off",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2462982115668996901/D6438ECBB11DECC6DB9987589FF526FBAD4D2368/"
},
@@ -69,16 +69,6 @@
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2280574378889753733/F67B7B37FF7AA253B6D697E577DF54A3E76030C2/"
},
- {
- "Name": "option_on",
- "Type": 0,
- "URL": "http://cloud-3.steamusercontent.com/ugc/2024962321889555728/22ABD35CBB49A001F3A5318E4AFCFB22D24FEA39/"
- },
- {
- "Name": "option_off",
- "Type": 0,
- "URL": "http://cloud-3.steamusercontent.com/ugc/2024962321889555661/6643E5CC9160FF4624672C255D0DF7B313DA00A5/"
- },
{
"Name": "SpeechBubble",
"Type": 0,
@@ -244,6 +234,11 @@
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2510267932886739653/CB7AA2D73777EF5938A6E6CD664B2ABA52B6E20A/"
},
+ {
+ "Name": "token-eldersign",
+ "Type": 0,
+ "URL": "http://cloud-3.steamusercontent.com/ugc/2540675016035917168/C0D6F531A85FD94C2F54825DFC50781B5B499A1D/"
+ },
{
"Name": "token-custom-token",
"Type": 0,
diff --git a/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.json b/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.json
index 7c317a95..3cc17a73 100644
--- a/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.json
+++ b/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.json
@@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
- "LuaScript": "",
+ "LuaScript": "require(\"playercards/cards/KohakuNarukami\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
@@ -58,5 +58,5 @@
"scaleZ": 1.15
},
"Value": 0,
- "XmlUI": ""
+ "XmlUI": "\u003cInclude src=\"playercards/KohakuNarukami.xml\"/\u003e"
}
diff --git a/objects/OptionPanelSource.830bd0/CleanUpHelper.26cf4b.json b/objects/OptionPanelSource.830bd0/CleanUpHelper.26cf4b.json
index 572e54b7..3e49fb5d 100644
--- a/objects/OptionPanelSource.830bd0/CleanUpHelper.26cf4b.json
+++ b/objects/OptionPanelSource.830bd0/CleanUpHelper.26cf4b.json
@@ -31,12 +31,12 @@
{
"Name": "option_on",
"Type": 0,
- "URL": "http://cloud-3.steamusercontent.com/ugc/2024962321889555728/22ABD35CBB49A001F3A5318E4AFCFB22D24FEA39/"
+ "URL": "http://cloud-3.steamusercontent.com/ugc/2462982115668997008/2178787B67B3C96F3419EDBAB8420E39893756BC/"
},
{
"Name": "option_off",
"Type": 0,
- "URL": "http://cloud-3.steamusercontent.com/ugc/2024962321889555661/6643E5CC9160FF4624672C255D0DF7B313DA00A5/"
+ "URL": "http://cloud-3.steamusercontent.com/ugc/2462982115668996901/D6438ECBB11DECC6DB9987589FF526FBAD4D2368/"
}
],
"Description": "Cleans up the table for the next scenario during campaign play.\n\nThis includes moving cards and tokens into the trashcans, resetting counters and removing bless/curse tokens from the chaos bag.",
diff --git a/src/accessories/CleanUpHelper.ttslua b/src/accessories/CleanUpHelper.ttslua
index c8a784ef..c161432c 100644
--- a/src/accessories/CleanUpHelper.ttslua
+++ b/src/accessories/CleanUpHelper.ttslua
@@ -369,8 +369,8 @@ function tidyPlayerMatCoroutine()
if tekeliliHelper then
tekeliliHelper.call("spawnStoredTekelili", color)
end
+ ::continue::
end
- ::continue::
end
-- mythos area cleanup
diff --git a/src/accessories/PhaseTracker.ttslua b/src/accessories/PhaseTracker.ttslua
index e646a004..19af8ff9 100644
--- a/src/accessories/PhaseTracker.ttslua
+++ b/src/accessories/PhaseTracker.ttslua
@@ -42,10 +42,11 @@ function onLoad(savedData)
color = { r = 0, g = 0, b = 0, a = 0 }
})
- self.addContextMenuItem("toggle broadcasting", updateBroadcast)
+ self.addContextMenuItem("Toggle Broadcasting", updateBroadcast)
end
-function updateBroadcast()
+function updateBroadcast(playerColor)
+ Player[playerColor].clearSelectedObjects()
for _, tracker in ipairs(getObjectsWithTag("LinkedPhaseTracker")) do
tracker.setVar("broadcastChange", not broadcastChange)
end
diff --git a/src/accessories/SearchAssistant.ttslua b/src/accessories/SearchAssistant.ttslua
index 24872e73..3ecf8da7 100644
--- a/src/accessories/SearchAssistant.ttslua
+++ b/src/accessories/SearchAssistant.ttslua
@@ -124,22 +124,17 @@ function startSearch(messageColor, number)
end
-- get position and rotation for set aside cards
- local handData = Player[handColor].getHandTransform()
- local handCards = Player[handColor].getHandObjects()
- setAsidePosition = handData.position + offset * handData.right
- setAsideRotation = { handData.rotation.x, handData.rotation.y + 180, 180 }
+ local handData = Player[handColor].getHandTransform()
+ local handCards = Player[handColor].getHandObjects()
+ setAsidePosition = (handData.position + offset * handData.right):setAt("y", 1.5)
+ setAsideRotation = { handData.rotation.x, handData.rotation.y + 180, 180 }
- -- set y-value
- setAsidePosition.y = 1.5
-
- for i = #handCards, 1, -1 do
- handCards[i].setPosition(setAsidePosition + Vector(0, (#handCards - i) * 0.1, 0))
- handCards[i].setRotation(setAsideRotation)
- end
+ -- place hand cards set aside
+ deckLib.placeOrMergeIntoDeck(handCards, setAsidePosition, setAsideRotation)
-- handling for Norman Withers
if deckAreaObjects.topCard then
- deckAreaObjects.topCard.flip()
+ deckAreaObjects.topCard.setRotation(setAsideRotation)
topCardDetected = true
end
@@ -155,35 +150,36 @@ end
function endSearch(_, _, isRightClick)
local handCards = Player[handColor].getHandObjects()
- local j = 0
- for i = #handCards, 1, -1 do
- j = j + 1
- Wait.time(function() deckLib.placeOrMergeIntoDeck(handCards[i], drawDeckPosition, setAsideRotation) end, j * 0.1)
- end
+ -- place cards on deck
+ deckLib.placeOrMergeIntoDeck(handCards, drawDeckPosition, setAsideRotation)
-- draw set aside cards (from the ground!)
+ Wait.time(drawSetAsideCards, 0.5 + #handCards * 0.1)
+
+ normalView()
+
+ Wait.time(function()
+ -- maybe shuffle deck
+ if not isRightClick then
+ local deckAreaObjects = playermatApi.getDeckAreaObjects(matColor)
+ if deckAreaObjects.draw then
+ deckAreaObjects.draw.shuffle()
+ end
+ end
+
+ -- Norman Withers handling
+ if topCardDetected then
+ playermatApi.flipTopCardFromDeck(matColor)
+ end
+ end, 1 + #handCards * 0.1)
+end
+
+function drawSetAsideCards()
for _, obj in ipairs(searchLib.atPosition(setAsidePosition, "isCardOrDeck")) do
local count = 1
if obj.type == "Deck" then
count = #obj.getObjects()
end
- Wait.time(function() obj.deal(count, handColor) end, 1)
- end
-
- normalView()
-
- -- delay is to wait for cards to enter deck
- if not isRightClick then
- Wait.time(function()
- local deckAreaObjects = playermatApi.getDeckAreaObjects(matColor)
- if deckAreaObjects.draw then
- deckAreaObjects.draw.shuffle()
- end
- end, #handCards * 0.3 + 0.5)
- end
-
- -- Norman Withers handling
- if topCardDetected then
- Wait.time(function() playermatApi.flipTopCardFromDeck(matColor) end, #handCards * 0.3 + 0.75)
+ obj.deal(count, handColor)
end
end
diff --git a/src/accessories/UnderworldMarketHelper.ttslua b/src/accessories/UnderworldMarketHelper.ttslua
index 0373efbb..b24c7962 100644
--- a/src/accessories/UnderworldMarketHelper.ttslua
+++ b/src/accessories/UnderworldMarketHelper.ttslua
@@ -32,7 +32,7 @@ function onload(savedData)
isSetup = false
movingCards = false
- self.addContextMenuItem('Reset helper', resetHelper)
+ self.addContextMenuItem('Reset Helper', resetHelper)
if savedData and savedData ~= '' then
local loaded_data = JSON.decode(savedData)
@@ -242,7 +242,8 @@ function finish()
0.75)
end
-function resetHelper()
+function resetHelper(playerColor)
+ Player[playerColor].clearSelectedObjects()
for i, card in ipairs(self.getObjects()) do
self.takeObject({
index = 0,
@@ -269,7 +270,5 @@ function resetHelper()
isSetup = false
movingCards = false
- self.reset()
-
print('Underworld Market Helper: Helper has been reset.')
end
diff --git a/src/chaosbag/BlessCurseManager.ttslua b/src/chaosbag/BlessCurseManager.ttslua
index 0d8f50eb..945123e0 100644
--- a/src/chaosbag/BlessCurseManager.ttslua
+++ b/src/chaosbag/BlessCurseManager.ttslua
@@ -266,6 +266,19 @@ function updateDisplayAndBroadcast(type)
end
end
+function getBlessCurseInBag()
+ local numInBag = { Bless = 0, Curse = 0 }
+ local chaosBag = chaosBagApi.findChaosBag()
+
+ for _, v in ipairs(chaosBag.getObjects()) do
+ if v.name == "Bless" or v.name == "Curse" then
+ numInBag[v.name] = numInBag[v.name] + 1
+ end
+ end
+
+ return numInBag
+end
+
---------------------------------------------------------
-- main functions: add and remove
---------------------------------------------------------
diff --git a/src/chaosbag/BlessCurseManagerApi.ttslua b/src/chaosbag/BlessCurseManagerApi.ttslua
index 00290284..896a6c96 100644
--- a/src/chaosbag/BlessCurseManagerApi.ttslua
+++ b/src/chaosbag/BlessCurseManagerApi.ttslua
@@ -67,5 +67,9 @@ do
getManager().call("removeToken", type)
end
+ BlessCurseManagerApi.getBlessCurseInBag = function()
+ return getManager().call("getBlessCurseInBag", {})
+ end
+
return BlessCurseManagerApi
end
diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua
index 462f7216..1b3c74bc 100644
--- a/src/core/GameKeyHandler.ttslua
+++ b/src/core/GameKeyHandler.ttslua
@@ -1,5 +1,6 @@
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
+local mythosAreaApi = require("core/MythosAreaApi")
local navigationOverlayApi = require("core/NavigationOverlayApi")
local optionPanelApi = require("core/OptionPanelApi")
local playermatApi = require("playermat/PlayermatApi")
@@ -15,6 +16,7 @@ function onLoad()
addHotkey("Move card to Victory Display", moveCardToVictoryDisplay)
addHotkey("Place card into threat area", takeCardIntoThreatArea)
addHotkey("Remove a use", removeOneUse)
+ addHotkey("Reshuffle encounter deck", mythosAreaApi.reshuffleEncounterDeck)
addHotkey("Switch seat clockwise", switchSeatClockwise)
addHotkey("Switch seat counter-clockwise", switchSeatCounterClockwise)
addHotkey("Take clue from location", takeClueFromLocation)
@@ -122,10 +124,15 @@ function takeCardIntoThreatArea(playerColor, hoveredObject)
end
end
--- discard the hovered object to the respective trashcan and discard tokens on it if it was a card
+-- discard the hovered or selected objects to the respective trashcan and discard tokens on it if it was a card
function discardObject(playerColor, hoveredObject)
+ -- if more than one object is selected, discard them all, one at a time
+ local selectedObjects = Player[playerColor].getSelectedObjects()
+ if #selectedObjects > 0 then
+ discardGroup(playerColor, selectedObjects)
+ return
-- only continue if an unlocked card, deck or tile was hovered
- if hoveredObject == nil
+ elseif hoveredObject == nil
or (hoveredObject.type ~= "Card" and hoveredObject.type ~= "Deck" and hoveredObject.type ~= "Tile")
or hoveredObject.locked then
broadcastToColor("Hover a token/tile or a card/deck and try again.", playerColor, "Yellow")
@@ -133,11 +140,15 @@ function discardObject(playerColor, hoveredObject)
end
-- These should probably not be discarded normally. Ask player for confirmation.
- if hoveredObject.type == "Deck" or hoveredObject.hasTag("Location") then
- local suspect = (hoveredObject.type == "Deck") and "Deck" or "Location"
- Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?",
- function() performDiscard(playerColor, hoveredObject) end)
- return
+ local tokenData = mythosAreaApi.returnTokenData()
+ local scenarioName = tokenData.currentScenario
+ if scenarioName ~= "Lost in Time and Space" and scenarioName ~= "The Secret Name" then
+ if hoveredObject.type == "Deck" or hoveredObject.hasTag("Location") then
+ local suspect = (hoveredObject.type == "Deck") and "Deck" or "Location"
+ Player[playerColor].showConfirmDialog("Discard " .. suspect .. "?",
+ function() performDiscard(playerColor, hoveredObject) end)
+ return
+ end
end
performDiscard(playerColor, hoveredObject)
@@ -159,6 +170,18 @@ function performDiscard(playerColor, hoveredObject)
playermatApi.discardListOfObjects(discardForMatColor, discardTheseObjects)
end
+function discardGroup(playerColor, selectedObjects)
+ local count = #selectedObjects
+ -- discarding one at a time avoids an error with cards in the discard pile losing the 'hands' toggle and uses multiple mats
+ for i = count, 1, -1 do
+ Wait.time(function()
+ if (selectedObjects[i].type == "Card" or selectedObjects[i].type ~= "Deck" or selectedObjects[i].type == "Tile") then
+ performDiscard(playerColor, selectedObjects[i])
+ end
+ end, (count - i + 1) * 0.1)
+ end
+end
+
-- discard the top card of hovered deck, calling discardObject function
function discardTopDeck(playerColor, hoveredObject)
-- only continue if an unlocked card or deck was hovered
@@ -282,8 +305,7 @@ function removeOneUse(playerColor, hoveredObject)
end
end
- local playerName = Player[playerColor].steam_name
- broadcastToAll(playerName .. " removed a token: " .. tokenName, playerColor)
+ broadcastToAll(getColoredName(playerColor) .. " removed a token: " .. tokenName, playerColor)
local discardForMatColor = getColorToDiscardFor(hoveredObject, playerColor)
playermatApi.discardListOfObjects(discardForMatColor, { targetObject })
@@ -404,12 +426,10 @@ function takeClueFromLocation(playerColor, hoveredObject)
local clickableClues = optionPanelApi.getOptions()["useClueClickers"]
-- handling for calling this for a specific mat via hotkey
- local playerName, matColor, pos
+ local matColor, pos
if Player[playerColor] and Player[playerColor].seated then
- playerName = Player[playerColor].steam_name
matColor = playermatApi.getMatColor(playerColor)
else
- playerName = playerColor
matColor = playerColor
end
@@ -431,9 +451,9 @@ function takeClueFromLocation(playerColor, hoveredObject)
end
if cardName then
- broadcastToAll(playerName .. " took one clue from " .. cardName .. ".", "White")
+ broadcastToAll(getColoredName(playerColor) .. " took one clue from " .. cardName .. ".", "White")
else
- broadcastToAll(playerName .. " took one clue.", "White")
+ broadcastToAll(getColoredName(playerColor) .. " took one clue.", "White")
end
victoryDisplayApi.update()
@@ -472,3 +492,14 @@ function getFirstSeatedPlayer()
return color
end
end
+
+-- returns the colored steam name or color
+function getColoredName(playerColor)
+ local displayName = playerColor
+ if Player[playerColor].steam_name then
+ displayName = Player[playerColor].steam_name
+ end
+
+ -- add bb-code
+ return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]"
+end
diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua
index fe16e87f..91cbd68f 100644
--- a/src/core/Global.ttslua
+++ b/src/core/Global.ttslua
@@ -199,14 +199,20 @@ function onObjectEnterZone(zone, object)
local matcolor = playermatApi.getMatColorByPosition(object.getPosition())
local trash = guidReferenceApi.getObjectByOwnerAndType(matcolor, "Trash")
trash.putObject(object)
- elseif zone.type == "Hand" and object.hasTag("CardWithHelper") then
- object.clearContextMenu()
- object.call("shutOff")
+ elseif zone.type == "Hand" and object.type == "Card" then
+ if object.is_face_down then
+ object.flip()
+ end
+ if object.hasTag("CardWithHelper") then
+ object.clearContextMenu()
+ object.call("shutOff")
+ end
end
end
-- TTS event for objects that leave zones
function onObjectLeaveZone(zone, object)
+ if zone.isDestroyed() or object.isDestroyed() then return end
if zone.type == "Hand" and object.hasTag("CardWithHelper") then
object.call("updateDisplay")
end
@@ -238,6 +244,23 @@ function onObjectNumberTyped(hoveredObject, playerColor, number)
end
end
+-- TTS event, used to redraw the playermat slot symbols after a small delay to account for the custom font loading
+function onPlayerConnect()
+ Wait.time(function() playermatApi.redrawSlotSymbols("All") end, 0.2)
+end
+
+function onPlayerAction(player, action, targets)
+ if action == Player.Action.Delete and player.admin == false then
+ for _, target in ipairs(targets) do
+ local matColor = playermatApi.getMatColorByPosition(target.getPosition())
+ local trash = guidReferenceApi.getObjectByOwnerAndType(matColor, "Trash")
+ trash.putObject(target)
+ end
+ return false
+ end
+ return true
+end
+
---------------------------------------------------------
-- chaos token drawing
---------------------------------------------------------
@@ -1443,7 +1466,7 @@ function onClick_toggleOption(_, _, id)
local currentState = optionPanel[id]
local newState = not currentState
applyOptionPanelChange(id, newState)
- UI.setAttribute(id, "image", newState and "option-on" or "option-off")
+ UI.setAttribute(id, "image", newState and "option_on" or "option_off")
end
-- color selection for playArea
@@ -1537,9 +1560,9 @@ function updateOptionPanelState()
elseif (type(optionValue) == "boolean" and optionValue)
or (type(optionValue) == "string" and optionValue)
or (type(optionValue) == "table" and #optionValue ~= 0) then
- UI.setAttribute(id, "image", "option-on")
+ UI.setAttribute(id, "image", "option_on")
else
- UI.setAttribute(id, "image", "option-off")
+ UI.setAttribute(id, "image", "option_off")
end
end
end
diff --git a/src/core/MythosArea.ttslua b/src/core/MythosArea.ttslua
index 9ce728d3..a40d6813 100644
--- a/src/core/MythosArea.ttslua
+++ b/src/core/MythosArea.ttslua
@@ -78,7 +78,7 @@ function onCollisionEnter(collisionInfo)
local localPos = self.positionToLocal(object.getPosition())
if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then
- Wait.frames(function() tokenSpawnTrackerApi.resetTokensSpawned(object.getGUID()) end, 1)
+ Wait.frames(function() tokenSpawnTrackerApi.resetTokensSpawned(object) end, 1)
removeTokensFromObject(object)
end
end
@@ -100,7 +100,7 @@ end
function onObjectEnterContainer(container, object)
local localPos = self.positionToLocal(container.getPosition())
if inArea(localPos, ENCOUNTER_DECK_AREA) or inArea(localPos, ENCOUNTER_DISCARD_AREA) then
- tokenSpawnTrackerApi.resetTokensSpawned(object.getGUID())
+ tokenSpawnTrackerApi.resetTokensSpawned(object)
removeTokensFromObject(object)
end
end
diff --git a/src/core/UniversalActionAbilityToken.ttslua b/src/core/UniversalActionAbilityToken.ttslua
index b991697f..de55b40d 100644
--- a/src/core/UniversalActionAbilityToken.ttslua
+++ b/src/core/UniversalActionAbilityToken.ttslua
@@ -202,6 +202,7 @@ end
function addContextMenu()
self.addContextMenuItem("Change color", function(playerColor)
+ Player[playerColor].clearSelectedObjects()
local currentClassDisplayName = listOfClassDisplayNames[getIndexOfValue(listOfClasses, class)]
Player[playerColor].showOptionsDialog("Choose color", listOfClassDisplayNames, currentClassDisplayName,
function(_, selectedIndex)
@@ -210,6 +211,7 @@ function addContextMenu()
end)
self.addContextMenuItem("Change 1st symbol", function(playerColor)
+ Player[playerColor].clearSelectedObjects()
local symbolList = getSymbolList()
Player[playerColor].showOptionsDialog("Choose symbol", listOfSymbols, symbolList[1], function(newSymbol)
if newSymbol == "None" then
@@ -229,6 +231,7 @@ function addContextMenu()
end)
self.addContextMenuItem("Change 2nd symbol", function(playerColor)
+ Player[playerColor].clearSelectedObjects()
local symbolList = getSymbolList()
Player[playerColor].showOptionsDialog("Choose 2nd symbol", listOfSymbols, symbolList[2] or symbolList[1],
function(newSymbol)
diff --git a/src/core/VictoryDisplay.ttslua b/src/core/VictoryDisplay.ttslua
index 13c56694..4057fca6 100644
--- a/src/core/VictoryDisplay.ttslua
+++ b/src/core/VictoryDisplay.ttslua
@@ -1,7 +1,8 @@
-local searchLib = require("util/SearchLib")
local chaosBagApi = require("chaosbag/ChaosBagApi")
+local deckLib = require("util/DeckLib")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playAreaApi = require("core/PlayAreaApi")
+local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker")
local pendingCall = false
@@ -234,28 +235,30 @@ end
-- places the provided card in the first empty spot
function placeCard(card)
- local trash = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
+ local name = card.getName() or "Unnamed card"
- -- check snap point states
+ -- get sorted list of snap points to check slots
local snaps = self.getSnapPoints()
table.sort(snaps, function(a, b) return a.position.x > b.position.x end)
table.sort(snaps, function(a, b) return a.position.z < b.position.z end)
-- get first empty slot
- local fullSlots = {}
- local positions = {}
+ local emptyIndex, emptyPos
for i, snap in ipairs(snaps) do
- positions[i] = self.positionToWorld(snap.position)
- local searchResult = searchLib.atPosition(positions[i], "isCardOrDeck")
- fullSlots[i] = #searchResult > 0
+ local snapPos = self.positionToWorld(snap.position)
+ local searchResult = searchLib.atPosition(snapPos, "isCardOrDeck")
+ if #searchResult == 0 then
+ emptyPos = snapPos
+ emptyIndex = i
+ break
+ end
end
-- remove tokens from the card
- for _, obj in ipairs(searchLib.onObject(card)) do
- -- don't touch decks / cards
- if obj.type == "Deck" or obj.type == "Card" then
+ local trash = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
+ for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do
+ if tokenChecker.isChaosToken(obj) then
-- put chaos tokens back into bag
- elseif tokenChecker.isChaosToken(obj) then
local chaosBag = chaosBagApi.findChaosBag()
chaosBag.putObject(obj)
elseif obj.memo ~= nil and obj.getLock() == false then
@@ -263,18 +266,15 @@ function placeCard(card)
end
end
- -- place the card
- local name = card.getName() or "Unnamed card"
- for i = 1, 10 do
- if fullSlots[i] ~= true then
- local rot = { 0, 270, card.getRotation().z }
- card.setPositionSmooth(positions[i], false, true)
- card.setRotation(rot)
- broadcastToAll("Victory Display: " .. name .. " placed into slot " .. i .. ".", "Green")
- return
- end
- end
+ -- use the first snap position in case all slots are full
+ local pos = emptyPos or self.positionToWorld(snaps[1].position)
+ local rot = card.getRotation():setAt("x", 0):setAt("y", 270)
+ deckLib.placeOrMergeIntoDeck(card, pos, rot)
- broadcastToAll("Victory Display is full! " .. name .. " placed into slot 1.", "Orange")
- card.setPositionSmooth(positions[1], false, true)
+ -- give a feedback message
+ if emptyPos then
+ broadcastToAll("Victory Display: " .. name .. " placed into slot " .. emptyIndex .. ".", "Green")
+ else
+ broadcastToAll("Victory Display is full! " .. name .. " placed into slot 1.", "Orange")
+ end
end
diff --git a/src/core/token/TokenManager.ttslua b/src/core/token/TokenManager.ttslua
index 0f208129..afc89c32 100644
--- a/src/core/token/TokenManager.ttslua
+++ b/src/core/token/TokenManager.ttslua
@@ -211,10 +211,11 @@ do
if tokenType == "clue" then
offsets = internal.buildClueOffsets(card, tokenCount)
else
- -- only up to 12 offset tables defined (TODO: stack more than 12 tokens and generate offsets dynamically)
+ -- only up to 12 offset tables defined
if tokenCount > 12 then
- printToAll("Spawning maximum of 12 tokens.")
- tokenCount = 12
+ printToAll("Attempting to spawn " .. tokenCount .. " tokens. Spawning clickable counter instead.")
+ TokenManager.spawnResourceCounterToken(card, tokenCount)
+ return
end
for i = 1, tokenCount do
offsets[i] = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[tokenCount][i])
@@ -297,20 +298,13 @@ do
-- Checks a card for metadata to maybe replenish it
---@param card tts__Object Card object to be replenished
---@param uses table The already decoded metadata.uses (to avoid decoding again)
- ---@param mat tts__Object The playermat the card is placed on (for rotation and casting)
- TokenManager.maybeReplenishCard = function(card, uses, mat)
+ TokenManager.maybeReplenishCard = function(card, uses)
-- TODO: support for cards with multiple uses AND replenish (as of yet, no official card needs that)
if uses[1].count and uses[1].replenish then
- internal.replenishTokens(card, uses, mat)
+ internal.replenishTokens(card, uses)
end
end
- -- Delegate function to the token spawn tracker. Exists to avoid circular dependencies in some callers
- ---@param card tts__Object Card object to reset the tokens for
- TokenManager.resetTokensSpawned = function(card)
- tokenSpawnTrackerApi.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)
@@ -483,16 +477,16 @@ do
---@param card tts__Object Card object to be replenished
---@param uses table The already decoded metadata.uses (to avoid decoding again)
- ---@param mat tts__Object The playermat the card is placed on (for rotation and casting)
- internal.replenishTokens = function(card, uses, mat)
- -- get current amount of resource tokens on the card
+ internal.replenishTokens = function(card, uses)
+ -- get current amount of matching resource tokens on the card
local clickableResourceCounter = nil
local foundTokens = 0
+ local searchType = string.lower(uses[1].type)
for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do
local memo = obj.getMemo()
- if (stateTable[memo] or 0) > 0 then
+ if searchType == memo then
foundTokens = foundTokens + math.abs(obj.getQuantity())
obj.destruct()
elseif memo == "resourceCounter" then
diff --git a/src/core/token/TokenSpawnTracker.ttslua b/src/core/token/TokenSpawnTracker.ttslua
index 0340ae91..1061197f 100644
--- a/src/core/token/TokenSpawnTracker.ttslua
+++ b/src/core/token/TokenSpawnTracker.ttslua
@@ -22,8 +22,8 @@ function markTokensSpawned(cardGuid)
spawnedCardGuids[cardGuid] = true
end
-function resetTokensSpawned(cardGuid)
- spawnedCardGuids[cardGuid] = nil
+function resetTokensSpawned(card)
+ spawnedCardGuids[card.getGUID()] = nil
end
function resetAll() spawnedCardGuids = {} end
@@ -52,6 +52,6 @@ end
-- Listener to reset card token spawns when they enter a hand.
function onObjectEnterZone(zone, enterObject)
if zone.type == "Hand" and enterObject.type == "Card" then
- resetTokensSpawned(enterObject.getGUID())
+ resetTokensSpawned(enterObject)
end
end
diff --git a/src/core/token/TokenSpawnTrackerApi.ttslua b/src/core/token/TokenSpawnTrackerApi.ttslua
index 1ba4c261..e8176296 100644
--- a/src/core/token/TokenSpawnTrackerApi.ttslua
+++ b/src/core/token/TokenSpawnTrackerApi.ttslua
@@ -14,8 +14,8 @@ do
return getSpawnTracker().call("markTokensSpawned", cardGuid)
end
- TokenSpawnTracker.resetTokensSpawned = function(cardGuid)
- return getSpawnTracker().call("resetTokensSpawned", cardGuid)
+ TokenSpawnTracker.resetTokensSpawned = function(card)
+ return getSpawnTracker().call("resetTokensSpawned", card)
end
TokenSpawnTracker.resetAllAssetAndEvents = function()
diff --git a/src/playercards/CardsThatRedrawTokens.ttslua b/src/playercards/CardsThatRedrawTokens.ttslua
index 479dc55a..b975f8a2 100644
--- a/src/playercards/CardsThatRedrawTokens.ttslua
+++ b/src/playercards/CardsThatRedrawTokens.ttslua
@@ -63,9 +63,8 @@ function onLoad(savedData)
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
- checkOptionPanel()
createHelperXML()
- updateDisplay()
+ checkOptionPanel()
end
function createHelperXML()
diff --git a/src/playercards/CardsWithHelper.ttslua b/src/playercards/CardsWithHelper.ttslua
index b0f1cfd1..c92ffda9 100644
--- a/src/playercards/CardsWithHelper.ttslua
+++ b/src/playercards/CardsWithHelper.ttslua
@@ -5,6 +5,8 @@ function checkOptionPanel()
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
+ else
+ updateDisplay()
end
end
diff --git a/src/playercards/Tarotcard.ttslua b/src/playercards/Tarotcard.ttslua
index d1c1bbbc..e9abd0d0 100644
--- a/src/playercards/Tarotcard.ttslua
+++ b/src/playercards/Tarotcard.ttslua
@@ -5,7 +5,8 @@ function onLoad()
end
-- rotates the alt_view_angle
-function rotatePreview()
+function rotatePreview(playerColor)
+ Player[playerColor].clearSelectedObjects()
local angle = self.alt_view_angle
if angle.y == 0 then
angle.y = 180
@@ -16,7 +17,7 @@ function rotatePreview()
end
-- rotates this card and the preview
-function rotateSelfAndPreview()
+function rotateSelfAndPreview(playerColor)
self.setRotationSmooth(self.getRotation() + Vector(0, 180, 0))
- rotatePreview()
+ rotatePreview(playerColor)
end
diff --git a/src/playercards/cards/BookofLivingMyths.ttslua b/src/playercards/cards/BookofLivingMyths.ttslua
index 7f7f116b..776c9d8b 100644
--- a/src/playercards/cards/BookofLivingMyths.ttslua
+++ b/src/playercards/cards/BookofLivingMyths.ttslua
@@ -1,9 +1,11 @@
require("playercards/CardsWithHelper")
+local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playermatApi = require("playermat/PlayermatApi")
local isHelperEnabled = false
+local updated
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
@@ -16,7 +18,6 @@ function onLoad(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
checkOptionPanel()
- updateDisplay()
end
-- hide buttons and stop monitoring
@@ -35,6 +36,7 @@ function initialize()
end
function resolveToken(player, _, tokenType)
+ if not updated then return end
local matColor
if player.color == "Black" then
matColor = playermatApi.getMatColorByPosition(self.getPosition())
@@ -44,11 +46,13 @@ function resolveToken(player, _, tokenType)
local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
chaosBagApi.drawChaosToken(mat, true, tokenType)
+ updated = false
+ Wait.frames(maybeUpdateButtonState, 2)
end
-- count tokens in the bag and show appropriate buttons
function maybeUpdateButtonState()
- local numInBag = getBlessCurseInBag()
+ local numInBag = blessCurseManagerApi.getBlessCurseInBag()
local state = { Bless = false, Curse = false }
if numInBag.Bless >= numInBag.Curse and numInBag.Bless > 0 then
@@ -60,19 +64,7 @@ function maybeUpdateButtonState()
end
setUiState(state)
-end
-
-function getBlessCurseInBag()
- local numInBag = { Bless = 0, Curse = 0 }
- local chaosBag = chaosBagApi.findChaosBag()
-
- for _, v in ipairs(chaosBag.getObjects()) do
- if v.name == "Bless" or v.name == "Curse" then
- numInBag[v.name] = numInBag[v.name] + 1
- end
- end
-
- return numInBag
+ updated = true
end
function setUiState(params)
@@ -88,7 +80,7 @@ function setUiState(params)
end
function errorMessage()
- local numInBag = getBlessCurseInBag()
+ local numInBag = blessCurseManagerApi.getBlessCurseInBag()
if numInBag.Bless == 0 and numInBag.Curse == 0 then
broadcastToAll("There are no Bless or Curse tokens in the chaos bag.", "Red")
diff --git a/src/playercards/cards/EmpiricalHypothesis.ttslua b/src/playercards/cards/EmpiricalHypothesis.ttslua
index 21f1f986..d2b410d3 100644
--- a/src/playercards/cards/EmpiricalHypothesis.ttslua
+++ b/src/playercards/cards/EmpiricalHypothesis.ttslua
@@ -46,7 +46,6 @@ function onLoad(savedData)
activeButtonIndex = loadedData.activeButtonIndex
end
checkOptionPanel()
- updateDisplay()
if activeButtonIndex > 0 then
selectButton(activeButtonIndex)
@@ -89,6 +88,7 @@ function createButtons()
local upgradeSheet = findUpgradeSheet()
if upgradeSheet then
for i = 1, 4 do
+ log(4)
if upgradeSheet.call("isUpgradeActive", i) then
table.insert(hypothesisList, customizableList[i])
end
@@ -110,7 +110,7 @@ end
function findUpgradeSheet()
local matColor = playermatApi.getMatColorByPosition(self.getPosition())
- local result = playermatApi.searchAroundPlaymat(matColor, "isCard")
+ local result = playermatApi.searchAroundPlayermat(matColor, "isCard")
for j, card in ipairs(result) do
local metadata = JSON.decode(card.getGMNotes()) or {}
if metadata.id == "09041-c" then
diff --git a/src/playercards/cards/FamilyInheritance.ttslua b/src/playercards/cards/FamilyInheritance.ttslua
index 19ea08cc..e5b71240 100644
--- a/src/playercards/cards/FamilyInheritance.ttslua
+++ b/src/playercards/cards/FamilyInheritance.ttslua
@@ -6,9 +6,21 @@ local clickableResourceCounter = nil
local foundTokens = 0
function onLoad()
- self.addContextMenuItem("Add 4 resources", function(playerColor) add4(playerColor) end)
- self.addContextMenuItem("Take all resources", function(playerColor) takeAll(playerColor) end)
- self.addContextMenuItem("Discard all resources", function(playerColor) loseAll(playerColor) end)
+ self.addContextMenuItem("Add 4 resources",
+ function(playerColor)
+ Player[playerColor].clearSelectedObjects()
+ add4(playerColor)
+ end)
+ self.addContextMenuItem("Take all resources",
+ function(playerColor)
+ Player[playerColor].clearSelectedObjects()
+ takeAll(playerColor)
+ end)
+ self.addContextMenuItem("Discard all resources",
+ function(playerColor)
+ Player[playerColor].clearSelectedObjects()
+ loseAll(playerColor)
+ end)
end
function searchSelf()
@@ -35,12 +47,7 @@ function add4(playerColor)
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", newCount)
else
- if newCount > 12 then
- printToColor("Count increased to " .. newCount .. " resources. Spawning clickable counter instead.", playerColor)
- tokenManager.spawnResourceCounterToken(self, newCount)
- else
- tokenManager.spawnTokenGroup(self, "resource", newCount)
- end
+ tokenManager.spawnTokenGroup(self, "resource", newCount)
end
end
diff --git a/src/playercards/cards/KohakuNarukami.ttslua b/src/playercards/cards/KohakuNarukami.ttslua
new file mode 100644
index 00000000..5d478997
--- /dev/null
+++ b/src/playercards/cards/KohakuNarukami.ttslua
@@ -0,0 +1,147 @@
+require("playercards/CardsWithHelper")
+local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
+local guidReferenceApi = require("core/GUIDReferenceApi")
+local playermatApi = require("playermat/PlayermatApi")
+local searchLib = require("util/SearchLib")
+local tokenManager = require("core/token/TokenManager")
+
+local isHelperEnabled = false
+local updated
+
+local xmlData = {
+ Action = { color = "#6D202CE6", onClick = "removeAndExtraAction" },
+ Bless = { color = "#9D702CE6", onClick = "addTokenToBag" },
+ Curse = { color = "#633A84E6", onClick = "addTokenToBag" },
+ ElderSign = { color = "#50A8CEE6", onClick = "elderSignAbility" }
+}
+
+function updateSave()
+ self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
+end
+
+function onLoad(savedData)
+ self.addTag("CardWithHelper")
+ if savedData and savedData ~= "" then
+ local loadedData = JSON.decode(savedData)
+ isHelperEnabled = loadedData.isHelperEnabled
+ end
+ checkOptionPanel()
+end
+
+-- hide buttons and stop monitoring
+function shutOff()
+ self.UI.hide("Helper")
+ Wait.stopAll()
+ updateSave()
+end
+
+-- show buttons and begin monitoring chaos bag for curse and bless tokens
+function initialize()
+ self.UI.show("Helper")
+ maybeUpdateButtonState()
+ Wait.time(maybeUpdateButtonState, 1, -1)
+ updateSave()
+end
+
+function addTokenToBag(_, _, tokenType)
+ if not updated then return end
+ blessCurseManagerApi.addToken(tokenType)
+ updated = false
+ Wait.frames(maybeUpdateButtonState, 2)
+end
+
+function removeAndExtraAction()
+ if not updated then return end
+ blessCurseManagerApi.removeToken("Bless")
+ blessCurseManagerApi.removeToken("Bless")
+ blessCurseManagerApi.removeToken("Curse")
+ blessCurseManagerApi.removeToken("Curse")
+ updated = false
+ Wait.frames(maybeUpdateButtonState, 2)
+
+ local position = self.getPosition()
+ local matColor = playermatApi.getMatColorByPosition(position)
+ local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
+ local rotation = mat.getRotation()
+
+ -- find empty action token slots by checking snap points
+ local snaps = mat.getSnapPoints()
+
+ -- get first empty slot in the top row (so the second empty slot because of the ability token)
+ local emptyPos = position -- default to card position
+ for i, snap in ipairs(snaps) do
+ if i > 1 then
+ if snap.tags[1] == "UniversalToken" then
+ local snapPos = mat.positionToWorld(snap.position)
+ local searchResult = searchLib.atPosition(snapPos, "isUniversalToken")
+ if #searchResult == 0 then
+ emptyPos = snapPos
+ break
+ end
+ end
+ end
+ end
+
+ callback = function(spawned)
+ spawned.call("updateClassAndSymbol", { class = "Mystic", symbol = "Mystic" })
+ spawned.addTag("Temporary")
+ end
+ tokenManager.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callback)
+end
+
+function elderSignAbility()
+ blessCurseManagerApi.addToken("Bless")
+ blessCurseManagerApi.addToken("Curse")
+ updated = false
+ Wait.frames(maybeUpdateButtonState, 2)
+end
+
+-- count tokens in the bag and show appropriate buttons
+function maybeUpdateButtonState()
+ local numInBag = blessCurseManagerApi.getBlessCurseInBag()
+ local state = { Bless = false, Curse = false, Action = false, ElderSign = false }
+
+ if numInBag.Bless <= numInBag.Curse and numInBag.Bless < 10 then
+ state.Bless = true
+ state.ElderSign = true
+ end
+
+ if numInBag.Curse <= numInBag.Bless and numInBag.Curse < 10 then
+ state.Curse = true
+ state.ElderSign = true
+ end
+
+ if numInBag.Curse >= 2 and numInBag.Bless >= 2 then
+ state.Action = true
+ end
+
+ setUiState(state)
+ updated = true
+end
+
+function setUiState(params)
+ for buttonId, state in pairs(params) do
+ if state then
+ self.UI.setAttribute(buttonId, "color", xmlData[buttonId].color)
+ self.UI.setAttribute(buttonId, "onClick", xmlData[buttonId].onClick)
+ self.UI.setAttribute(buttonId, "textColor", "white")
+ else
+ self.UI.setAttribute(buttonId, "color", "#353535E6")
+ self.UI.setAttribute(buttonId, "onClick", "errorMessage")
+ self.UI.setAttribute(buttonId, "textColor", "#A0A0A0")
+ end
+ end
+end
+
+function errorMessage(_, _, triggeringButton)
+ local numInBag = blessCurseManagerApi.getBlessCurseInBag()
+ if triggeringButton == "Action" then
+ broadcastToAll("There are not enough Blesses and/or Curses in the chaos bag.", "Red")
+ elseif numInBag.Bless == 10 and numInBag.Curse == 10 then
+ broadcastToAll("No more tokens can be added to the chaos bag.", "Red")
+ elseif numInBag.Bless < numInBag.Curse then
+ broadcastToAll("There are more Bless tokens than Curse tokens in the chaos bag.", "Red")
+ else
+ broadcastToAll("There are more Curse tokens than Bless tokens in the chaos bag.", "Red")
+ end
+end
diff --git a/src/playercards/cards/NkosiMabati3.ttslua b/src/playercards/cards/NkosiMabati3.ttslua
index 187d302c..83d2e6b9 100644
--- a/src/playercards/cards/NkosiMabati3.ttslua
+++ b/src/playercards/cards/NkosiMabati3.ttslua
@@ -61,6 +61,7 @@ end
-- Create dialog window to choose sigil and create sigil-drawing button
function chooseSigil(playerColor)
+ Player[playerColor].clearSelectedObjects()
self.clearContextMenu()
self.addContextMenuItem("Clear Helper", deleteButtons)
@@ -80,7 +81,8 @@ function chooseSigil(playerColor)
end
-- Delete button and remove sigil
-function deleteButtons()
+function deleteButtons(playerColor)
+ Player[playerColor].clearSelectedObjects()
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", chooseSigil)
self.UI.setXml("")
diff --git a/src/playercards/cards/ScrollofSecrets.ttslua b/src/playercards/cards/ScrollofSecrets.ttslua
index 39c0a97e..544ef2c8 100644
--- a/src/playercards/cards/ScrollofSecrets.ttslua
+++ b/src/playercards/cards/ScrollofSecrets.ttslua
@@ -17,6 +17,7 @@ function onLoad()
end
function contextFunc(playerColor, amount)
+ Player[playerColor].clearSelectedObjects()
deckData = {}
local options = {}
diff --git a/src/playercards/cards/ShortSupply.ttslua b/src/playercards/cards/ShortSupply.ttslua
index 8074211b..cdca89b6 100644
--- a/src/playercards/cards/ShortSupply.ttslua
+++ b/src/playercards/cards/ShortSupply.ttslua
@@ -5,7 +5,8 @@ function onLoad()
end
-- called by context menu entry
-function shortSupply(color)
+function shortSupply(playerColor)
+ Player[playerColor].clearSelectedObjects()
local matColor = playermatApi.getMatColorByPosition(self.getPosition())
-- get draw deck and discard position
@@ -15,20 +16,20 @@ function shortSupply(color)
-- error handling
if discardPos == nil then
- broadcastToColor("Couldn't retrieve discard position from playermat!", color, "Red")
+ broadcastToColor("Couldn't retrieve discard position from playermat!", playerColor, "Red")
return
end
if drawDeck == nil then
- broadcastToColor("Deck not found!", color, "Yellow")
+ broadcastToColor("Deck not found!", playerColor, "Yellow")
return
elseif drawDeck.type ~= "Deck" then
- broadcastToColor("Deck only contains a single card!", color, "Yellow")
+ broadcastToColor("Deck only contains a single card!", playerColor, "Yellow")
return
end
-- discard cards, waiting 0.7 seconds between each discard to give players visiblity of the cards
- broadcastToColor("Discarding top 10 cards for player color '" .. matColor .. "'.", color, "White")
+ broadcastToColor("Discarding top 10 cards for player color '" .. matColor .. "'.", playerColor, "White")
for i = 1, 10 do
Wait.time(function() drawDeck.takeObject({ flip = true, position = { discardPos.x, 2 + 0.075 * i, discardPos.z } }) end, .7 * (i - 1))
end
diff --git a/src/playercards/cards/Tekeli-li.ttslua b/src/playercards/cards/Tekeli-li.ttslua
index acf90fae..6a54d793 100644
--- a/src/playercards/cards/Tekeli-li.ttslua
+++ b/src/playercards/cards/Tekeli-li.ttslua
@@ -7,7 +7,8 @@ function onLoad()
end
-- uses the tekeli-li helper to place this card at the bottom of the deck
-function returnSelf()
+function returnSelf(playerColor)
+ Player[playerColor].clearSelectedObjects()
local helper = getTekeliliHelper()
if helper == nil then
printToAll("Couldn't find Tekeli-li Helper!")
@@ -18,6 +19,7 @@ end
-- places this card below the deck of the player that triggered it
function placeBelowDeck(playerColor)
+ Player[playerColor].clearSelectedObjects()
local matColor = playermatApi.getMatColor(playerColor)
local deckPos = playermatApi.getDrawPosition(matColor)
local deckRot = playermatApi.returnRotation(matColor)
diff --git a/src/playermat/Playermat.ttslua b/src/playermat/Playermat.ttslua
index 6aeefc4f..307f9631 100644
--- a/src/playermat/Playermat.ttslua
+++ b/src/playermat/Playermat.ttslua
@@ -6,6 +6,7 @@ local navigationOverlayApi = require("core/NavigationOverlayApi")
local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker")
local tokenManager = require("core/token/TokenManager")
+local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
-- option panel data
local availableOptions = {
@@ -75,12 +76,12 @@ local buttonParameters = {
-- table of texture URLs
local nameToTexture = {
- Guardian = "http://cloud-3.steamusercontent.com/ugc/2444972799638881117/169F4520A94FB186B54E0F2BF4BAC809844C923E/",
- Mystic = "http://cloud-3.steamusercontent.com/ugc/2444972799638880413/B59966123EA41649EDCBD614167E590C8C105582/",
+ Guardian = "http://cloud-3.steamusercontent.com/ugc/2501268517203536128/853B9CD08FC14A8B2A08C73D8ED77E0FE235CCCB/",
+ Mystic = "http://cloud-3.steamusercontent.com/ugc/2501268517203536470/11C99488B9CA9236059A5F02E4A852A7C77B42A6/",
Neutral = "http://cloud-3.steamusercontent.com/ugc/2462982115659543571/5D778EA4BC682DAE97E8F59A991BCF8CB3979B04/",
- Rogue = "http://cloud-3.steamusercontent.com/ugc/2444972799638880905/CFC02BF5A6140B9B4B92312AD6DC74D8DD61180B/",
- Seeker = "http://cloud-3.steamusercontent.com/ugc/2444972799638881117/169F4520A94FB186B54E0F2BF4BAC809844C923E/",
- Survivor = "http://cloud-3.steamusercontent.com/ugc/2444972799638880687/EEDF8F8BC3266069FECB09775845BE2501310C17/"
+ Rogue = "http://cloud-3.steamusercontent.com/ugc/2501268517203536767/587791B327255DB8F953B27BB9E4DE602FA32B64/",
+ Seeker = "http://cloud-3.steamusercontent.com/ugc/2501268517203537098/EFD9FC4CCDB105EFFDFF7A57C915CD984865760D/",
+ Survivor = "http://cloud-3.steamusercontent.com/ugc/2501268517203537426/14EF780606D9A449F31A007226CB48D05AA820FF/"
}
-- translation table for slot names to characters for special font
@@ -325,7 +326,9 @@ function doUpkeep(_, clickedByColor, isRightClick)
local forcedLearning = false
local rot = self.getRotation()
for _, obj in ipairs(searchAroundSelf()) do
- if obj.hasTag("UniversalToken") == true and obj.is_face_down then
+ if obj.hasTag("Temporary") == true then
+ discardListOfObjects({ obj })
+ elseif obj.hasTag("UniversalToken") == true and obj.is_face_down then
obj.flip()
elseif obj.type == "Card" and not inArea(self.positionToLocal(obj.getPosition()), INVESTIGATOR_AREA) then
local cardMetadata = JSON.decode(obj.getGMNotes()) or {}
@@ -355,7 +358,7 @@ function doUpkeep(_, clickedByColor, isRightClick)
end
-- maybe replenish uses on certain cards (don't continue for cards on the deck (Norman) or in the discard pile)
- if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x < -1 then
+ if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 then
tokenManager.maybeReplenishCard(obj, cardMetadata.uses, self)
end
elseif obj.type == "Deck" and forcedLearning == false then
@@ -399,34 +402,29 @@ function doUpkeep(_, clickedByColor, isRightClick)
local handCards = Player[playerColor].getHandObjects()
local cardsToDiscard = {}
- for i = 1, #handCards do
- local metadata = JSON.decode(handCards[i].getGMNotes())
- if metadata ~= nil and (not metadata.weakness and not metadata.hidden) then
- table.insert(cardsToDiscard, handCards[i])
+ for _, card in ipairs(handCards) do
+ local md = JSON.decode(card.getGMNotes())
+ if md ~= nil and (not md.weakness and not md.hidden and md.type ~= "Enemy") then
+ table.insert(cardsToDiscard, card)
end
end
-- perform discarding 1 by 1
local pos = returnGlobalDiscardPosition()
- local count = #cardsToDiscard
- for i = count, 1, -1 do
- Wait.time(function() deckLib.placeOrMergeIntoDeck(cardsToDiscard[i], pos, rot) end, (count - i + 1) * 0.1)
- end
+ deckLib.placeOrMergeIntoDeck(cardsToDiscard, pos, self.getRotation())
- -- add some time if there are any cards to discard, if not, draw up to 5 immediately
- local k = 0
- if count > 0 then
- k = 0.7 + (count * 0.1)
- end
+ -- draw up to 5 cards
+ local cardsToDraw = 5 - #handCards + #cardsToDiscard
+ if cardsToDraw > 0 then
+ printToColor("Discarding " .. #cardsToDiscard .. " and drawing " .. cardsToDraw .. " card(s). (Patrice)", messageColor)
- Wait.time(function()
- local handSize = #Player[playerColor].getHandObjects()
- if handSize < 5 then
- local cardsToDraw = 5 - handSize
- printToColor("Drawing " .. cardsToDraw .. " cards (Patrice)", messageColor)
- drawCardsWithReshuffle(cardsToDraw)
+ -- add some time if there are any cards to discard
+ local k = 0
+ if #cardsToDiscard > 0 then
+ k = 0.8 + (#cardsToDiscard * 0.1)
end
- end, k)
+ Wait.time(function() drawCardsWithReshuffle(cardsToDraw) end, k)
+ end
end
elseif forcedLearning then
printToColor("Drawing 2 cards, discard 1 (Forced Learning)", messageColor)
@@ -585,9 +583,8 @@ function doDiscardOne()
-- get a random non-hidden card (from the "choices" table)
local num = math.random(1, #choices)
deckLib.placeOrMergeIntoDeck(hand[choices[num]], returnGlobalDiscardPosition(), self.getRotation())
-
- local playerName = Player[playerColor].steam_name or playerColor
- broadcastToAll(playerName .. " randomly discarded card " .. choices[num] .. "/" .. #hand .. ".", "White")
+ broadcastToAll(getColoredName(playerColor) .. " randomly discarded card "
+ .. choices[num] .. "/" .. #hand .. ".", "White")
end
end
@@ -974,7 +971,7 @@ function onCollisionEnter(collisionInfo)
local localCardPos = self.positionToLocal(object.getPosition())
if inArea(localCardPos, DECK_DISCARD_AREA) then
- tokenManager.resetTokensSpawned(object)
+ tokenSpawnTrackerApi.resetTokensSpawned(object)
removeTokensFromObject(object)
elseif shouldSpawnTokens(object) then
spawnTokensFor(object)
@@ -1018,7 +1015,7 @@ function onObjectEnterContainer(container, object)
local localCardPos = self.positionToLocal(object.getPosition())
if inArea(localCardPos, DECK_DISCARD_AREA) then
- tokenManager.resetTokensSpawned(object)
+ tokenSpawnTrackerApi.resetTokensSpawned(object)
removeTokensFromObject(object)
end
end
@@ -1397,3 +1394,14 @@ function updatePlayerCards(args)
local playerCardData = customDataHelper.getTable("PLAYER_CARD_DATA")
tokenManager.addPlayerCardData(playerCardData)
end
+
+-- returns the colored steam name or color
+function getColoredName(playerColor)
+ local displayName = playerColor
+ if Player[playerColor].steam_name then
+ displayName = Player[playerColor].steam_name
+ end
+
+ -- add bb-code
+ return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]"
+end
diff --git a/src/util/DeckLib.ttslua b/src/util/DeckLib.ttslua
index c818dead..ad48bcd1 100644
--- a/src/util/DeckLib.ttslua
+++ b/src/util/DeckLib.ttslua
@@ -2,23 +2,33 @@ do
local DeckLib = {}
local searchLib = require("util/SearchLib")
- -- places a card/deck at a position or merges into an existing deck
- ---@param obj tts__Object Object to move
+ -- places a card/deck at a position or merges into an existing deck below
+ ---@param objOrTable tts__Object|table Object or table of objects to move
---@param pos table New position for the object
---@param rot? table New rotation for the object
---@param below? boolean Should the object be placed below an existing deck?
- DeckLib.placeOrMergeIntoDeck = function(obj, pos, rot, below)
- if obj == nil or pos == nil then return end
+ DeckLib.placeOrMergeIntoDeck = function(objOrTable, pos, rot, below)
+ if objOrTable == nil or pos == nil then return end
+
+ -- handle 'objOrTable' parameter
+ local objects = {}
+ if type(objOrTable) == "table" then
+ objects = objOrTable
+ else
+ table.insert(objects, objOrTable)
+ end
-- search the new position for existing card/deck
local searchResult = searchLib.atPosition(pos, "isCardOrDeck")
+ local targetObj
-- get new position
local offset = 0.5
local newPos = Vector(pos) + Vector(0, offset, 0)
if #searchResult == 1 then
- local bounds = searchResult[1].getBounds()
+ targetObj = searchResult[1]
+ local bounds = targetObj.getBounds()
if below then
newPos = Vector(pos):setAt("y", bounds.center.y - bounds.size.y / 2)
else
@@ -26,25 +36,39 @@ do
end
end
- -- allow moving the objects smoothly out of the hand
- obj.use_hands = false
+ -- process objects in reverse order
+ for i = #objects, 1, -1 do
+ local obj = objects[i]
+ -- add a 0.1 delay for each object (for animation purposes)
+ Wait.time(function()
+ -- allow moving smoothly out of hand and temporarily lock it
+ obj.setLock(true)
+ obj.use_hands = false
- if rot then
- obj.setRotationSmooth(rot, false, true)
+ if rot then
+ obj.setRotationSmooth(rot, false, true)
+ end
+ obj.setPositionSmooth(newPos, false, true)
+
+ -- wait for object to finish movement (or 2 seconds)
+ Wait.condition(
+ function()
+ -- revert toggles
+ obj.setLock(false)
+ obj.use_hands = true
+
+ -- use putObject to avoid a TTS bug that merges unrelated cards that are not resting
+ if #searchResult == 1 and targetObj ~= obj and not targetObj.isDestroyed() and not obj.isDestroyed() then
+ targetObj = targetObj.putObject(obj)
+ else
+ targetObj = obj
+ end
+ end,
+ -- check state of the object (make sure it's not moving)
+ function() return obj.isDestroyed() or not obj.isSmoothMoving() end,
+ 2)
+ end, (#objects- i) * 0.1)
end
- obj.setPositionSmooth(newPos, false, true)
-
- -- continue if the card stops smooth moving
- Wait.condition(
- function()
- obj.use_hands = true
- -- this avoids a TTS bug that merges unrelated cards that are not resting
- if #searchResult == 1 and searchResult[1] ~= obj then
- -- call this with avoiding errors (physics is sometimes too fast so the object doesn't exist for the put)
- pcall(function() searchResult[1].putObject(obj) end)
- end
- end,
- function() return not obj.isSmoothMoving() end, 3)
end
return DeckLib
diff --git a/xml/Global/OptionPanel.xml b/xml/Global/OptionPanel.xml
index f5246678..6530366b 100644
--- a/xml/Global/OptionPanel.xml
+++ b/xml/Global/OptionPanel.xml
@@ -62,7 +62,7 @@