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 @@