diff --git a/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json b/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json index 24b5e802..3ee91e70 100644 --- a/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json +++ b/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json @@ -22,7 +22,7 @@ "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1704036721123215146/E44A3B99EACF310E49E94977151A03C9A3DC7F17/", "WidthScale": 0 }, - "Description": "- Displays the hand size (total or by title for \"Dream Enhancing Serum\"), hover over it to briefly toggle counting method\n\n- Adds a context menu to \"Short Supply\" for the 1st turn\n\n- Allows you to randomly discard a card from your hand\n\nSee context menu for additional information.", + "Description": "Displays the hand size (total or by title for \"Dream Enhancing Serum\"), hover over it to briefly toggle counting method.\n\nAllows you to randomly discard a card from your hand.", "DragSelectable": true, "GMNotes": "", "GUID": "450688", @@ -34,7 +34,7 @@ "LayoutGroupSortIndex": 0, "Locked": false, "LuaScript": "require(\"accessories/HandHelper\")", - "LuaScriptState": "[\"Green\",false]", + "LuaScriptState": "", "MeasureMovement": false, "Name": "Custom_Tile", "Nickname": "Hand Helper", diff --git a/src/accessories/HandHelper.ttslua b/src/accessories/HandHelper.ttslua index d79dd093..21e2e586 100644 --- a/src/accessories/HandHelper.ttslua +++ b/src/accessories/HandHelper.ttslua @@ -1,194 +1,114 @@ local playmatAPI = require("playermat/PlaymatApi") +local matColor, handColor, loopId, hovering -local buttonParamaters = {} -buttonParamaters.function_owner = self +function onLoad() + local buttonParamaters = {} + buttonParamaters.function_owner = self --- saving "playerColor" and "des" -function onSave() return JSON.encode({ playerColor, des}) end + -- index 0: button as hand size label + buttonParamaters.hover_color = "White" + buttonParamaters.click_function = "none" + buttonParamaters.position = { 0, 0.11, -0.4 } + buttonParamaters.height = 0 + buttonParamaters.width = 0 + buttonParamaters.font_size = 500 + buttonParamaters.font_color = "White" + self.createButton(buttonParamaters) -function onLoad(saved_data) - -- loading saved data - local loaded_data = JSON.decode(saved_data) - playerColor = loaded_data[1] or Player.getAvailableColors()[1] - des = loaded_data[2] or false + -- index 1: button to toggle "des" + buttonParamaters.label = "DES: ✗" + buttonParamaters.click_function = "none" + buttonParamaters.position = { 0, 0.11, 0.25 } + buttonParamaters.height = 0 + buttonParamaters.width = 0 + buttonParamaters.font_size = 120 + self.createButton(buttonParamaters) - -- index 0: button as hand size label - buttonParamaters.hover_color = "White" - buttonParamaters.click_function = "none" - buttonParamaters.position = { 0, 0.11, -0.4 } - buttonParamaters.height = 0 - buttonParamaters.width = 0 - buttonParamaters.font_size = 500 - buttonParamaters.font_color = "White" - self.createButton(buttonParamaters) + -- index 2: button to discard a card + buttonParamaters.label = "discard random card" + buttonParamaters.click_function = "discardRandom" + buttonParamaters.position = { 0, 0.11, 0.7 } + buttonParamaters.height = 175 + buttonParamaters.width = 900 + buttonParamaters.font_size = 90 + buttonParamaters.font_color = "Black" + self.createButton(buttonParamaters) - -- index 1: button to toggle "des" - buttonParamaters.label = "DES: " .. (des and "✓" or "✗") - buttonParamaters.click_function = "toggleDES" - buttonParamaters.position = { 0.475, 0.11, 0.25 } - buttonParamaters.height = 175 - buttonParamaters.width = 440 - buttonParamaters.font_size = 90 - buttonParamaters.font_color = "Black" - self.createButton(buttonParamaters) + updateColors() - -- index 2: button to discard a card - buttonParamaters.label = "discard random card" - buttonParamaters.click_function = "discardRandom" - buttonParamaters.position = { 0, 0.11, 0.7 } - buttonParamaters.width = 900 - self.createButton(buttonParamaters) - - -- index 3: button to select color - buttonParamaters.label = playerColor - buttonParamaters.color = playerColor - buttonParamaters.hover_color = playerColor - buttonParamaters.click_function = "changeColor" - buttonParamaters.tooltip = "change color" - buttonParamaters.position = { -0.475, 0.11, 0.25 } - buttonParamaters.width = 440 - self.createButton(buttonParamaters) - - -- start loop to update card count - loopId = Wait.time(||updateValue(), 1, -1) - - -- context menu to quickly bind color - self.addContextMenuItem("Bind to my color", function(color) - changeColor(_, _, _, color) - end) - - -- context menu to display additional information - self.addContextMenuItem("More Information", function() - printToAll("------------------------------", "White") - printToAll("Hand Helper by Chr1Z", "Orange") - printToAll("original by Tikatoy", "White") - printToAll("Note: 'Hidden' cards can't be randomly discarded.", "Yellow") - printToAll("Set them aside beforehand!", "Yellow") - end) - - -- initialize the pseudo random number generator - math.randomseed(os.time()) + -- start loop to update card count + loopId = Wait.time(updateValue, 1, -1) end +-- updates colors when object is dropped somewhere +function onDrop() updateColors() end + +-- toggles counting method briefly function onObjectHover(hover_color, obj) - -- only continue if correct player hovers over "self" - if obj ~= self or hover_color ~= playerColor then return end + -- only continue if correct player hovers over "self" + if obj ~= self or hover_color ~= handColor or hovering then return end - -- stop loop, toggle "des" and displayed value briefly, then start new loop - Wait.stop(loopId) - des = not des - updateValue() - des = not des - loopId = Wait.time(||updateValue(), 1, -1) + -- toggle this flag so this doesn't get executed multiple times during the delay + hovering = true + + -- stop loop, toggle "des" and displayed value briefly, then start new loop after 2s + Wait.stop(loopId) + updateValue(true) + Wait.time(function() + loopId = Wait.time(updateValue, 1, -1) + hovering = false + end, 1) end --- toggle "des" and update button label -function toggleDES() - des = not des - self.editButton({index = 1, label = "DES: " .. (des and "✓" or "✗")}) - updateValue() +-- updates the matcolor and handcolor variable +function updateColors() + matColor = playmatAPI.getMatColorByPosition(self.getPosition()) + handColor = playmatAPI.getHandColor(matColor) + self.setName(handColor .. " Hand Helper") end -- count cards in hand (by name for DES) -function updateValue() - if not playerExists(playerColor) then return end +function updateValue(toggle) + -- update colors if handColor doesn't own a handzone + if Player[handColor].getHandCount() == 0 then + updateColors() + end - local hand = Player[playerColor].getHandObjects() - local size = 0 + -- if there is still no handzone, then end here + if Player[handColor].getHandCount() == 0 then return end - if des then - local cardHash = {} - for _, obj in pairs(hand) do - if obj.tag == "Card" then - local name = obj.getName() - local title = string.match(name, '(.+)(%s%(%d+%))') or name - cardHash[title] = obj - end - end - for _, obj in pairs(cardHash) do - size = size + 1 - end - else - for _, obj in pairs(hand) do - if obj.tag == "Card" then size = size + 1 end - end + -- get state of "Dream-Enhancing Serum" from playermat and update button label + local des = playmatAPI.isDES(matColor) + if toggle then des = not des end + self.editButton({ index = 1, label = "DES: " .. (des and "✓" or "✗") }) + + -- count cards in hand + local hand = Player[handColor].getHandObjects() + local size = 0 + + if des then + local cardHash = {} + for _, obj in pairs(hand) do + if obj.tag == "Card" then + local name = obj.getName() + local title = string.match(name, '(.+)(%s%(%d+%))') or name + cardHash[title] = true + end end - -- change button label and color - self.editButton({index = 0, font_color = des and "Green" or "White", label = size}) -end - --- allows change of color via external call -function externalColorChange(newColor) - changeColor(_, _, _, newColor) -end - --- get index of current color and move up one step (or down for right-click) -function changeColor(_, _, isRightClick, color) - if color then - playerColor = color - else - local COLORS = Player.getAvailableColors() - local pos = indexOf(COLORS, playerColor) - - if isRightClick then - if pos == nil or pos == 1 then pos = #COLORS - else pos = pos - 1 end - else - if pos == nil or pos == #COLORS then pos = 1 - else pos = pos + 1 end - end - - -- update playerColor - playerColor = COLORS[pos] + for _, title in pairs(cardHash) do + size = size + 1 end + else + for _, obj in pairs(hand) do + if obj.tag == "Card" then size = size + 1 end + end + end - -- update "change color" button (note: remove and create instantly updates hover_color) - buttonParamaters.label = playerColor - buttonParamaters.color = playerColor - buttonParamaters.hover_color = playerColor - self.removeButton(3) - self.createButton(buttonParamaters) + -- update button label and color + self.editButton({ index = 0, font_color = des and "Green" or "White", label = size }) end ---------------------------------------------------------- --- discards a random card from hand ---------------------------------------------------------- - +-- discards a random non-hidden card from hand function discardRandom() - if not playerExists(playerColor) then return end - - -- error handling: hand is empty - local hand = Player[playerColor].getHandObjects() - if #hand == 0 then - broadcastToAll("Cannot discard from empty hand!", "Red") - else - local searchPos = Player[playerColor].getHandTransform().position - - local discardPos = playmatAPI.getDiscardPosition(playmatAPI.getMatColorByPosition(searchPos)) - if discardPos == nil then - broadcastToAll("Couldn't retrieve discard position from playermat!", "Red") - return - end - - local num = math.random(1, #hand) - hand[num].setPosition(discardPos) - broadcastToAll(playerColor .. " randomly discarded card " .. num .. "/" .. #hand .. ".", "White") - end -end - ---------------------------------------------------------- --- helper functions ---------------------------------------------------------- - --- helper to search array -function indexOf(array, value) - for i, v in ipairs(array) do - if v == value then return i end - end -end - --- helper to check if player exists -function playerExists(color) - local COLORS = Player.getAvailableColors() - return indexOf(COLORS, color) and true or false + playmatAPI.doDiscardOne(matColor) end diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua index 0234bbd5..ec157987 100644 --- a/src/core/Global.ttslua +++ b/src/core/Global.ttslua @@ -856,10 +856,10 @@ function applyOptionPanelChange(id, state) -- option: Show hand helper for each player elseif id == "showHandHelper" then - optionPanel[id][1] = spawnOrRemoveHelper(state, "Hand Helper", {-50.85, 1.6, 7.32}, {0, 270, 0}, "White") - optionPanel[id][2] = spawnOrRemoveHelper(state, "Hand Helper", {-50.85, 1.6, -24.88}, {0, 270, 0}, "Orange") - optionPanel[id][3] = spawnOrRemoveHelper(state, "Hand Helper", {-39.13, 1.6, 22.45}, {0, 000, 0}, "Green") - optionPanel[id][4] = spawnOrRemoveHelper(state, "Hand Helper", {-21.57, 1.6, -22.45}, {0, 180, 0}, "Red") + optionPanel[id][1] = spawnOrRemoveHelper(state, "Hand Helper", {-50.85, 1.6, 7.32}, {0, 270, 0}) + optionPanel[id][2] = spawnOrRemoveHelper(state, "Hand Helper", {-50.85, 1.6, -24.88}, {0, 270, 0}) + optionPanel[id][3] = spawnOrRemoveHelper(state, "Hand Helper", {-39.13, 1.6, 22.45}, {0, 000, 0}) + optionPanel[id][4] = spawnOrRemoveHelper(state, "Hand Helper", {-21.57, 1.6, -22.45}, {0, 180, 0}) -- option: Show search assistant for each player elseif id == "showSearchAssistant" then @@ -899,12 +899,11 @@ end ---@param name String Name of the helper object ---@param position Vector Position of the object (where it will spawn) ---@param rotation Vector Rotation of the object for spawning (default: {0, 270, 0}) ----@param color String This is only needed for correctly setting the color of the "Hand Helper" ---@return. GUID of the spawnedObj (or nil if object was removed) -function spawnOrRemoveHelper(state, name, position, rotation, color) +function spawnOrRemoveHelper(state, name, position, rotation) if state then Player.getPlayers()[1].pingTable(position) - return spawnHelperObject(name, position, rotation, color).getGUID() + return spawnHelperObject(name, position, rotation).getGUID() else return removeHelperObject(name) end @@ -913,7 +912,7 @@ end -- copies the specified tool (by name) from the barrel ---@param name String Name of the object that should be copied ---@param position Table Desired position of the object -function spawnHelperObject(name, position, rotation, color) +function spawnHelperObject(name, position, rotation) local barrel = getObjectFromGUID(BARREL_GUID) -- error handling for missing barrel @@ -925,9 +924,7 @@ function spawnHelperObject(name, position, rotation, color) local spawnTable = { position = position, callback_function = function(object) - if name == "Hand Helper" then - Wait.time(function() object.call("externalColorChange", color) end, 0.1) - elseif name == "Token Arranger" then + if name == "Token Arranger" then Wait.time(function() object.call("layout") end, 0.1) end end diff --git a/src/playermat/Playmat.ttslua b/src/playermat/Playmat.ttslua index 0a7b7cc3..0893bf38 100644 --- a/src/playermat/Playmat.ttslua +++ b/src/playermat/Playmat.ttslua @@ -5,7 +5,7 @@ local tokenChecker = require("core/token/TokenChecker") local DEBUG = false -- we use this to turn off collision handling until onLoad() is complete -local COLLISION_ENABLED = false +local collisionEnabled = false -- position offsets relative to mat [x, y, z] local DRAWN_ENCOUNTER_CARD_OFFSET = {1.365, 0.5, -0.635} @@ -62,6 +62,9 @@ local RESOURCE_COUNTER activeInvestigatorId = "00000" local isDrawButtonVisible = false +-- global variable to report "Dream-Enhancing Serum" status +isDES = false + function onSave() return JSON.encode({ zoneID = zoneID, @@ -122,7 +125,9 @@ function onLoad(save_state) showDrawButton(isDrawButtonVisible) if getObjectFromGUID(zoneID) == nil then spawnDeckZone() end - COLLISION_ENABLED = true + collisionEnabled = true + + math.randomseed(os.time()) end --------------------------------------------------------- @@ -409,6 +414,51 @@ function shuffleDiscardIntoDeck() discardPile = nil end +-- discard a random non-hidden card from hand +function doDiscardOne() + local handColor = getHandColor() + local hand = Player[handColor].getHandObjects() + if #hand == 0 then + broadcastToAll("Cannot discard from empty hand!", "Red") + else + local choices = {} + for i = 1, #hand do + local notes = JSON.decode(hand[i].getGMNotes()) + if notes ~= nil then + if notes.hidden ~= true then + table.insert(choices, i) + end + else + table.insert(choices, i) + end + end + + if #choices == 0 then + broadcastToAll("Hidden cards can't be randomly discarded.", "Orange") + return + end + + -- get a random non-hidden card (from the "choices" table) + local num = math.random(1, #choices) + hand[choices[num]].setPosition(returnGlobalDiscardPosition()) + broadcastToAll(handColor .. " randomly discarded card " .. choices[num] .. "/" .. #hand .. ".", "White") + end +end + +-- gets the hand color of the closest seated player (by roughly cutting the table into quarters) +function getHandColor() + for _, handColor in ipairs(Player.getAvailableColors()) do + local handPosition = Player[handColor].getHandTransform().position + + if (PLAYER_COLOR == "White" and handPosition.x < -42 and handPosition.z > 0) + or (PLAYER_COLOR == "Orange" and handPosition.x < -42 and handPosition.z < 0) + or (PLAYER_COLOR == "Green" and handPosition.x > -42 and handPosition.z > 0) + or (PLAYER_COLOR == "Red" and handPosition.x > -42 and handPosition.z < 0) then + return handColor + end + end +end + --------------------------------------------------------- -- playmat token spawning --------------------------------------------------------- @@ -493,9 +543,14 @@ function spawnTokensFor(object) end function onCollisionEnter(collision_info) - if not COLLISION_ENABLED then return end local object = collision_info.collision_object + -- detect if "Dream-Enhancing Serum" is placed + if object.getName() == "Dream-Enhancing Serum" then isDES = true end + + -- only continue if loading is completed + if not collisionEnabled then return end + -- only continue for cards if object.name ~= "Card" and object.name ~= "CardCustom" then return end @@ -509,6 +564,11 @@ function onCollisionEnter(collision_info) end end +-- detect if "Dream-Enhancing Serum" is removed +function onCollisionExit(collision_info) + if collision_info.collision_object.getName() == "Dream-Enhancing Serum" then isDES = false end +end + function shouldSpawnTokens(card) if card.is_face_down then return false diff --git a/src/playermat/PlaymatApi.ttslua b/src/playermat/PlaymatApi.ttslua index 9070a00b..a04893fc 100644 --- a/src/playermat/PlaymatApi.ttslua +++ b/src/playermat/PlaymatApi.ttslua @@ -41,6 +41,20 @@ do end end + -- Returns the color of the player's hand that is seated next to the playermat + ---@param matColor String Color of the playermat + PlaymatApi.getHandColor = function(matColor) + local mat = getObjectFromGUID(MAT_IDS[matColor]) + return mat.call("getHandColor") + end + + -- Returns if there is the card"Dream-Enhancing Serum" on the requested playermat + ---@param matColor String Color of the playermat + PlaymatApi.isDES = function(matColor) + local mat = getObjectFromGUID(MAT_IDS[matColor]) + return mat.getVar("isDES") + end + -- Returns the draw deck of the requested playmat ---@param matColor String Color of the playermat PlaymatApi.getDrawDeck = function(matColor) @@ -116,6 +130,13 @@ do end end + -- Discard a non-hidden card from the corresponding player's hand + PlaymatApi.doDiscardOne = function(matColor) + for _, mat in ipairs(internal.getMatForColor(matColor)) do + mat.call("doDiscardOne") + end + end + -- Convenience function to look up a mat's object by color, or get all mats. ---@param matColor String for one of the active player colors - White, Orange, Green, Red. Also -- accepts "All" as a special value which will return all four mats.