Merge branch 'main' into playermat-xml

This commit is contained in:
Chr1Z93 2024-07-08 14:01:41 +02:00
commit a452043483
50 changed files with 4257 additions and 414 deletions

View File

@ -3,7 +3,6 @@
"ComponentTags_path": "ComponentTags.json",
"CustomUIAssets_path": "CustomUIAssets.json",
"DecalPallet_path": "DecalPallet.json",
"Decals": [],
"GameComplexity": "",
"GameMode": "Arkham Horror LCG - Super Complete Edition",
"GameType": "",
@ -84,6 +83,7 @@
"Trash.5f896a",
"Trash.147e80",
"Trash.f7b6c8",
"PatchNotes.f47225",
"RulesReference.d99993",
"LatestFAQ.faqfaq",
"Doomtokens.16724b",
@ -207,7 +207,6 @@
"Tokencache_Curse.16a9a7",
"Tokencache_Frost.b2b7be",
"PhysicsDetector.b300d8",
"ArkhamSCE390-06302024-Page1.bd6b3e",
"Neutral.834ad5",
"Neutral.a84ae4",
"Neutral.762df8",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/Analysis\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -58,5 +58,5 @@
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
"XmlUI": "\u003cInclude src=\"playercards/FamilyInheritance.xml\"/\u003e"
}

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/Strong-Armed\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -33,7 +33,7 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScript": "require(\"playercards/cards/ThirdTimesaCharm\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",

View File

@ -1,137 +0,0 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"ColorDiffuse": {
"b": 1,
"g": 1,
"r": 1
},
"Description": "Thanks for downloading Arkham SCE 3.9.0!\n\n- Added confirmation dialog for discard hotkey (e.g. for locations)\r\n- Added new action / ability tokens\r\n- Added automated discarding for Patrice\r\n- Added new option to enable all card helpers\r\n- Added new option to load class-specific playermats\r",
"DragSelectable": true,
"GMNotes": "",
"GUID": "bd6b3e",
"Grid": true,
"GridProjection": false,
"Hands": false,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Notecard",
"Nickname": "Arkham SCE 3.9.0 - 06/30/2024 - Page 1",
"Snap": true,
"States": {
"2": {
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"ColorDiffuse": {
"b": 1,
"g": 1,
"r": 1
},
"Description": "\n- Performed a small clean up of the bottom corners of the table\r\n- Updated 'Additional Cards Bag' / 'Player Card Panel' with better handling for fan-made cards\r\n- Updated Clean Up Helper, Drawing Tool, Hand Helper and Search Assistant\r\n- Updated Token Arranger",
"DragSelectable": true,
"GMNotes": "",
"GUID": "522604",
"Grid": true,
"GridProjection": false,
"Hands": false,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Notecard",
"Nickname": "Arkham SCE 3.9.0 - 06/30/2024 - Page 2",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": -27,
"posY": 1.551,
"posZ": -56.165,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
"scaleX": 3,
"scaleY": 1,
"scaleZ": 3
},
"Value": 0,
"XmlUI": ""
},
"3": {
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"ColorDiffuse": {
"b": 1,
"g": 1,
"r": 1
},
"Description": "\n- Implemented menu to redraw tokens for specific cards like Heavy Furs and Wendy Adams\r\n- Bugfix for attempting to draw an encounter card while there is no deck\r\n- Bugfix for Navigation Overlay: now checks if playmat is occupied\r\n- Bugfix for Phase Tracker broadcasting\r\n- Performance improvements",
"DragSelectable": true,
"GMNotes": "",
"GUID": "522877",
"Grid": true,
"GridProjection": false,
"Hands": false,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Notecard",
"Nickname": "Arkham SCE 3.9.0 - 06/30/2024 - Page 3",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": -27,
"posY": 1.551,
"posZ": -56.165,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
"scaleX": 3,
"scaleY": 1,
"scaleZ": 3
},
"Value": 0,
"XmlUI": ""
}
},
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": -27,
"posY": 1.551,
"posZ": -56.165,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
"scaleX": 3,
"scaleY": 1,
"scaleZ": 3
},
"Value": 0,
"XmlUI": ""
}

View File

@ -1 +1 @@
{"acknowledgedUpgradeVersions":[],"chaosTokensGUID":[],"optionPanel":{"cardLanguage":"en","changePlayAreaImage":false,"enableCardHelpers":false,"playAreaConnectionColor":{"a":1,"b":0.4,"g":0.4,"r":0.4},"playAreaConnections":true,"playAreaSnapTags":true,"showAttachmentHelper":false,"showCleanUpHelper":false,"showCYOA":false,"showDisplacementTool":false,"showDrawButton":false,"showHandHelper":false,"showSearchAssistant":false,"showTitleSplash":true,"useClassTexture":true,"useClueClickers":false,"useResourceCounters":"disabled","useSnapTags":true}}
{"acknowledgedUpgradeVersions":[],"chaosTokensGUID":[],"optionPanel":{"cardLanguage":"en","changePlayAreaImage":false,"enableCardHelpers":true,"playAreaConnectionColor":{"a":1,"b":0.4,"g":0.4,"r":0.4},"playAreaConnections":true,"playAreaSnapTags":true,"showAttachmentHelper":false,"showCleanUpHelper":false,"showCYOA":false,"showDisplacementTool":false,"showDrawButton":false,"showHandHelper":false,"showSearchAssistant":false,"showTitleSplash":true,"useClassTexture":true,"useClueClickers":false,"useResourceCounters":"disabled","useSnapTags":true}}

View File

@ -0,0 +1,113 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"AttachedDecals": [
{
"CustomDecal": {
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/2501268517218943111/803E57A7B3E9765DF342050EE6C71D69473A7388/",
"Name": "Image #1",
"Size": 1
},
"Transform": {
"posX": -0.93,
"posY": 0.105,
"posZ": 0.66,
"rotX": 90,
"rotY": 180,
"rotZ": 0,
"scaleX": 0.6,
"scaleY": 0.6,
"scaleZ": 1
}
},
{
"CustomDecal": {
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/2037357792052848566/5DA900C430E97D3DFF2C9B8A3DB1CB2271791FC7/",
"Name": "Image #2",
"Size": 1
},
"Transform": {
"posX": -1.05,
"posY": 0.105,
"posZ": -0.567,
"rotX": 90,
"rotY": 205,
"rotZ": 0,
"scaleX": 0.3,
"scaleY": 0.3,
"scaleZ": 1
}
},
{
"CustomDecal": {
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/2501268517219098388/0936FEE03B410319658B5E05DB5D486CEDDE98F5/",
"Name": "Image #3",
"Size": 1
},
"Transform": {
"posX": 0,
"posY": 0.105,
"posZ": -0.81,
"rotX": 90,
"rotY": 180,
"rotZ": 0,
"scaleX": 2.4,
"scaleY": 0.009,
"scaleZ": 1
}
}
],
"Autoraise": true,
"ColorDiffuse": {
"b": 1,
"g": 1,
"r": 1
},
"CustomImage": {
"CustomTile": {
"Stackable": false,
"Stretch": true,
"Thickness": 0.1,
"Type": 0
},
"ImageScalar": 1,
"ImageSecondaryURL": "http://sfwallpaper.com/images/parchment-paper-wallpaper-10.jpg",
"ImageURL": "http://sfwallpaper.com/images/parchment-paper-wallpaper-10.jpg",
"WidthScale": 0
},
"Description": "",
"DragSelectable": true,
"GMNotes": "",
"GUID": "f47225",
"Grid": true,
"GridProjection": false,
"Hands": false,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScriptState_path": "PatchNotes.f47225.luascriptstate",
"LuaScript_path": "PatchNotes.f47225.ttslua",
"MeasureMovement": false,
"Name": "Custom_Tile",
"Nickname": "Patch Notes",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": -27,
"posY": 1.481,
"posZ": -56.165,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 7.5,
"scaleY": 1,
"scaleZ": 7.5
},
"Value": 0,
"XmlUI": ""
}

View File

@ -0,0 +1 @@
{"checks":[],"decals":[{"locked":false,"name":"Arkham SCE logo","pos":{"x":3.1,"y":2.2},"rotation":0,"scale":{"x":"2","y":"2"},"tooltip":"None","url":"http://cloud-3.steamusercontent.com/ugc/2501268517218943111/803E57A7B3E9765DF342050EE6C71D69473A7388/"},{"locked":false,"name":"Bootlegger Finn","pos":{"x":3.5,"y":-1.89},"rotation":"25","scale":{"x":"1","y":"1"},"tooltip":"None","url":"http://cloud-3.steamusercontent.com/ugc/2037357792052848566/5DA900C430E97D3DFF2C9B8A3DB1CB2271791FC7/"},{"locked":false,"name":"black bar","pos":{"x":0,"y":-2.7},"rotation":0,"scale":{"x":"8","y":"0.03"},"tooltip":"None","url":"http://cloud-3.steamusercontent.com/ugc/2501268517219098388/0936FEE03B410319658B5E05DB5D486CEDDE98F5/"}],"fields":[{"align":3,"array":{"x":"1","y":"1"},"counter":"False","distance":{"x":"1","y":"1"},"fieldColor":{"a":0,"b":1,"g":1,"r":1},"font":"200","locked":false,"name":"Patch Notes","pos":{"x":"0","y":-2.9},"role":"Normal Field","size":{"x":"3750","y":"250"},"textColor":{"a":1,"b":0,"g":0,"r":0},"tooltip":"None","value":["Arkham Horror LCG SCE 3.9.0 - 07/08/2024"]},{"align":2,"array":{"x":"1","y":1},"distance":{"x":"1","y":"1"},"fieldColor":{"a":0,"b":1,"g":1,"r":1},"font":"89","locked":false,"name":"Details","pos":{"x":"0","y":0.4},"role":"Nothing","size":{"x":"3750","y":"2750"},"textColor":{"a":1,"b":0,"g":0,"r":0},"tooltip":"None","value":["Thanks for downloading! We're happy to present you a rather big update this time :-)\n\nNew things\n- updated note card for patch notes (bless Marum for his awesome tool!)\n- automated discarding for Patrice\n- confirmation dialog for discard hotkey (e.g. for locations)\n- helpers for cards that redraw tokens and Kohaku\n- displaying of token count for cards that seal tokens\r\n- new action / ability tokens (replacing the old ones)\r\n- option to enable all card helpers (e.g. Heavy Furs)\r\n- option to load class-colored playermat backgrounds\n- coloring for player names in broadcasts\n- right-click option for RBW button on Player Card Panel to specify trait(s)\n\nUpdates\r\n- performed a small clean up of the bottom corners of the table\n- \"Numpad 9\" to rearranges present tokens (on top of adding a resource)\n- Scroll of Secrets context menu helper now displays player names instead of colors\r\n- Player Card Panel can display fan-made cards with a new \"custom\" cycle button)\n- updated Family Inheritance helper to a proper UI\n- \"Discard object\" gamekey works for selected objects\r\n- updated a bunch of tools like Clean Up Helper, Drawing Tool,\nHand Helper, Token Arranger and Search Assistant\n\nFixes\r\r\n- Bugfix for attempting to draw an encounter card while there is no deck\r\n- Bugfix for Navigation Overlay: now checks if playmat is occupied\r\n- Bugfix for Phase Tracker broadcasting\r\n- Performance and file size improvements (e.g. by adding download\nfunctions for CYOA campaign guides and Arkham Fantasy standees)"]}],"flip":"False","height":"0.1","locks":{"checks":false,"decals":false,"fields":false},"nudgeDistance":0.1,"scale":{"x":"0.3","y":"0.3"},"sheetLocked":true}

File diff suppressed because it is too large Load Diff

View File

@ -284,7 +284,6 @@ end
-- maybe ignore cards / decks on the tekelili helper
function maybeIgnoreTekeliliCards()
local tekeliliHelper = getTekeliliHelper()
if tekeliliHelper then
removeIgnoreLater = searchLib.onObject(tekeliliHelper, "isCardOrDeck")
for _, obj in ipairs(removeIgnoreLater) do
@ -295,10 +294,7 @@ end
-- clean up for play area
function tidyPlayareaCoroutine()
-- small wait to allow other operations to finish
for k = 1, 10 do
coroutine.yield(0)
end
coWaitFrames(10)
local trash = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
local playAreaZone = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayAreaZone")
@ -333,43 +329,46 @@ function tidyPlayerMatCoroutine()
if getOptionValue() then
for _, color in ipairs(COLORS) do
-- delay for animation purpose
for k = 1, 20 do
coroutine.yield(0)
end
-- get respective trash
local trash = guidReferenceApi.getObjectByOwnerAndType(color, "Trash")
if trash == nil then
printToAll("Trashcan for " .. color .. " playermat could not be found! Skipping this playermat.", "Yellow")
goto continue
end
else
coWaitFrames(20)
-- maybe store tekelili cards
if tekeliliHelper then
tekeliliHelper.call("storeTekelili", color)
end
-- maybe store tekelili cards
if tekeliliHelper then
tekeliliHelper.call("storeTekelili", color)
end
-- remove objects (with exceptions)
for _, obj in ipairs(playermatApi.searchAroundPlayermat(color)) do
maybeTrashObject(obj, trash)
end
-- parse playermat objects
for _, obj in ipairs(playermatApi.searchAroundPlayermat(color)) do
-- reset action tokens
if obj.hasTag("UniversalToken") and obj.is_face_down then
obj.flip()
end
-- reset "activeInvestigatorId" and "...class"
local mat = guidReferenceApi.getObjectByOwnerAndType(color, "Playermat")
mat.setVar("activeInvestigatorId", "00000")
mat.setVar("activeInvestigatorClass", "Neutral")
mat.call("updateTexture")
-- get rid of temporary tokens
if obj.hasTag("Temporary") then
trash.putObject(obj)
end
for k = 1, 5 do
coroutine.yield(0)
end
-- remove objects (with exceptions)
maybeTrashObject(obj, trash)
end
-- maybe respawn tekelili cards
if tekeliliHelper then
tekeliliHelper.call("spawnStoredTekelili", color)
-- reset "activeInvestigatorId" and "...class"
local mat = guidReferenceApi.getObjectByOwnerAndType(color, "Playermat")
mat.setVar("activeInvestigatorId", "00000")
mat.setVar("activeInvestigatorClass", "Neutral")
mat.call("updateTexture")
coWaitFrames(5)
-- maybe respawn tekelili cards
if tekeliliHelper then
tekeliliHelper.call("spawnStoredTekelili", color)
end
end
::continue::
end
end
@ -431,3 +430,10 @@ function getOptionValue()
return options["tidyPlayermats"]
end
end
-- pauses the current coroutine for 'frameCount' frames
function coWaitFrames(frameCount)
for k = 1, frameCount do
coroutine.yield(0)
end
end

View File

@ -9,6 +9,7 @@ function onLoad()
-- index 0: button as hand size label
buttonParamaters.hover_color = "White"
buttonParamaters.label = 0
buttonParamaters.click_function = "none"
buttonParamaters.position = Vector(0, 0.11, -0.4)
buttonParamaters.height = 0
@ -33,11 +34,14 @@ function onLoad()
buttonParamaters.font_color = "Black"
self.createButton(buttonParamaters)
updateColors()
-- make sure this part executes after the playermats are loaded
Wait.time(function()
updateColors()
-- start loop to update card count
playermatApi.checkForDES(matColor)
Wait.time(updateValue, 1, -1)
-- start loop to update card count
playermatApi.checkForDES(matColor)
Wait.time(updateValue, 1, -1)
end, 1)
end
-- updates colors when object is dropped somewhere
@ -70,6 +74,9 @@ function updateValue()
updateColors()
end
-- if one of the colors is undefined, then end here
if matColor == nil or handColor == nil then return end
-- if there is still no handzone, then end here
if Player[handColor].getHandCount() == 0 then return end

View File

@ -4,6 +4,7 @@ local searchLib = require("util/SearchLib")
-- forward declaration of variables that are used across functions
local matColor, handColor, setAsidePosition, setAsideRotation, drawDeckPosition, topCardDetected
local addedVectorLines, addedSnapPoint
local quickParameters = {}
quickParameters.function_owner = self
@ -34,6 +35,7 @@ inputParameters.validation = 2
function onLoad()
normalView()
self.max_typed_number = 9999
end
-- regular view with search box
@ -89,6 +91,10 @@ function updateSearchNumber(_, _, input)
inputParameters.value = tonumber(input)
end
function onNumberTyped(playerColor, number)
startSearch(playerColor, number)
end
-- starts the search with the number from the input field
function searchCustom(_, messageColor)
local number = inputParameters.value
@ -115,7 +121,7 @@ function startSearch(messageColor, number)
-- get bounds to know the height of the deck
local bounds = deckAreaObjects.draw.getBounds()
drawDeckPosition = bounds.center + Vector(0, bounds.size.y / 2 + 0.2, 0)
printToColor("Place target(s) of search on set aside hand.", messageColor, "Green")
printToColor("Place target(s) of search on set aside spot.", messageColor, "Green")
-- get playermat orientation
local offset = -15
@ -127,10 +133,28 @@ function startSearch(messageColor, number)
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 }
setAsideRotation = Vector(handData.rotation.x, handData.rotation.y + 180, 180)
-- place hand cards set aside
deckLib.placeOrMergeIntoDeck(handCards, setAsidePosition, setAsideRotation)
if #handCards > 0 then
deckLib.placeOrMergeIntoDeck(handCards, setAsidePosition, setAsideRotation)
end
-- add a temporary snap point for the set aside spot
addedSnapPoint = { position = setAsidePosition, rotation = setAsideRotation }
local snapPoints = Global.getSnapPoints() or {}
table.insert(snapPoints, addedSnapPoint)
Global.setSnapPoints(snapPoints)
-- add a temporary box for the set aside spot
local vectorLines = Global.getVectorLines() or {}
local boxSize = Vector(2.5, 0, 3.5)
addedVectorLines = generateBoxData(setAsidePosition, boxSize, setAsideRotation.y, handColor)
for _, line in ipairs(addedVectorLines) do
table.insert(vectorLines, line)
end
Global.setVectorLines(vectorLines)
-- handling for Norman Withers
if deckAreaObjects.topCard then
@ -182,4 +206,77 @@ function drawSetAsideCards()
end
obj.deal(count, handColor)
end
removeAddedSnapAndLines()
end
function removeAddedSnapAndLines()
local vectorLines = Global.getVectorLines() or {}
local snapPoints = Global.getSnapPoints() or {}
-- look for previously added data and remove it (iterate in reverse because we're removing entries)
for i = #vectorLines, 1, -1 do
for _, boxLine in ipairs(addedVectorLines) do
if vectorLines[i].points[1] == boxLine.points[1] and vectorLines[i].points[2] == boxLine.points[2] then
table.remove(vectorLines, i)
break
end
end
end
for i = #snapPoints, 1, -1 do
if snapPoints[i].position == addedSnapPoint.position then
table.remove(snapPoints, i)
break
end
end
Global.setVectorLines(vectorLines)
Global.setSnapPoints(snapPoints)
end
-- generates the lines data for a rectangular box
---@param center tts__Vector Center of the box
---@param size tts__Vector X and Z dimension of the box
---@param rotation number Rotation around the Y-axis for the box
---@param boxColor string Color for the box
---@return table lines Vector line data for the box
function generateBoxData(center, size, rotation, boxColor)
local halfWidth = size.x / 2
local halfDepth = size.z / 2
-- corners of the box in local coordinates
local corners = {
Vector(-halfWidth, 0, -halfDepth),
Vector(halfWidth, 0, -halfDepth),
Vector(halfWidth, 0, halfDepth),
Vector(-halfWidth, 0, halfDepth)
}
-- translate corners to global coordinates
for i, cornerVec in ipairs(corners) do
local rotatedCornerVec = cornerVec:rotateOver('y', rotation)
corners[i] = rotatedCornerVec + center
end
-- generate the lines data
local lines = {
{
points = { corners[1], corners[2] },
color = boxColor
},
{
points = { corners[2], corners[3] },
color = boxColor
},
{
points = { corners[3], corners[4] },
color = boxColor
},
{
points = { corners[4], corners[1] },
color = boxColor
}
}
return lines
end

View File

@ -4,7 +4,7 @@ do
-- respawns the chaos bag with a new state of tokens
---@param tokenList table List of chaos token ids
ChaosBagApi.setChaosBagState = function(tokenList)
return Global.call("setChaosBagState", tokenList)
Global.call("setChaosBagState", tokenList)
end
-- returns a Table List of chaos token ids in the current chaos bag
@ -31,30 +31,31 @@ do
-- returns all sealed tokens on cards to the chaos bag
---@param playerColor string Color of the player to show the broadcast to
ChaosBagApi.releaseAllSealedTokens = function(playerColor)
return Global.call("releaseAllSealedTokens", playerColor)
Global.call("releaseAllSealedTokens", playerColor)
end
-- returns all drawn tokens to the chaos bag
ChaosBagApi.returnChaosTokens = function()
return Global.call("returnChaosTokens")
Global.call("returnChaosTokens")
end
-- removes the specified chaos token from the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.removeChaosToken = function(id)
return Global.call("removeChaosToken", id)
Global.call("removeChaosToken", id)
end
-- returns a chaos token to the bag and calls all relevant functions
---@param token tts__Object Chaos token to return
ChaosBagApi.returnChaosTokenToBag = function(token)
return Global.call("returnChaosTokenToBag", token)
---@param fromBag boolean whether or not the token to return was in the middle of being drawn (true) or elsewhere (false)
ChaosBagApi.returnChaosTokenToBag = function(token, fromBag)
Global.call("returnChaosTokenToBag", { token = token, fromBag = fromBag })
end
-- spawns the specified chaos token and puts it into the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.spawnChaosToken = function(id)
return Global.call("spawnChaosToken", id)
Global.call("spawnChaosToken", id)
end
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens

View File

@ -176,6 +176,17 @@ function getOwnerOfObject(object)
end
end
-- check if the object is in a handzone
for owner, subtable in pairs(GuidReferences) do
for type, guid in pairs(subtable) do
for _, zone in ipairs(object.getZones()) do
if guid == zone.getGUID() then
return owner
end
end
end
end
-- check if it is on an owned object
local result = searchLib.belowPosition(object.getPosition())

View File

@ -1,11 +1,12 @@
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local mythosAreaApi = require("core/MythosAreaApi")
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")
local searchLib = require("util/SearchLib")
local victoryDisplayApi = require("core/VictoryDisplayApi")
local optionPanelApi = require("core/OptionPanelApi")
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenChecker = require("core/token/TokenChecker")
local victoryDisplayApi = require("core/VictoryDisplayApi")
function onLoad()
addHotkey("Add doom to agenda", addDoomToAgenda)
@ -75,15 +76,19 @@ function takeCardIntoThreatArea(playerColor, hoveredObject)
local mat = guidReferenceApi.getObjectByOwnerAndType(matColor, "Playermat")
-- do not continue if the threat area is already full
if playermatApi.getEncounterCardDrawPosition(matColor, false) == playermatApi.getEncounterCardDrawPosition(matColor, true) then
local threatAreaPos = playermatApi.getEncounterCardDrawPosition(matColor, false)
if threatAreaPos == playermatApi.getEncounterCardDrawPosition(matColor, true) then
broadcastToColor("Threat area is full.", playerColor, "Yellow")
return
end
-- initialize list of objects to move
local moveTheseObjects = {}
-- initialize list of objects to move (and store local positions)
local additionalObjects = {}
for _, obj in ipairs(searchLib.onObject(hoveredObject, "isTileOrToken")) do
table.insert(moveTheseObjects, obj)
local data = {}
data.object = obj
data.localPos = hoveredObject.positionToLocal(obj.getPosition())
table.insert(additionalObjects, data)
end
-- find out if the original card is on the green or red playermats
@ -97,29 +102,25 @@ function takeCardIntoThreatArea(playerColor, hoveredObject)
modifierY = -90
end
-- store local positions of objects
local localPositions = {}
for i, obj in ipairs(moveTheseObjects) do
local localPos = hoveredObject.positionToLocal(obj.getPosition())
localPositions[i] = localPos
end
-- move the main card
local pos = playermatApi.getEncounterCardDrawPosition(matColor, false)
hoveredObject.setPosition(pos)
hoveredObject.setRotation(hoveredObject.getRotation() - Vector(0, 270 - mat.getRotation().y - modifierY, 0))
local cardName = hoveredObject.getName()
if cardName == nil or cardName == "" then
cardName = "card(s)"
end
broadcastToAll("Placed " .. cardName .. " into threat area.", "White")
if cardName == "" then cardName = "card" end
broadcastToAll("Moved " .. cardName .. " to " .. getColoredName(playerColor) .. "'s threat area.", "White")
for i, obj in ipairs(moveTheseObjects) do
if not obj.locked then
local globalPos = hoveredObject.positionToWorld(localPositions[i])
obj.setPosition(globalPos)
obj.setRotation(obj.getRotation() - Vector(0, 270 - mat.getRotation().y - modifierY, 0))
-- get new rotation (rounded)
local cardRot = hoveredObject.getRotation()
local roundedRotY = roundToMultiple(cardRot.y, 45)
local deltaRotY = 270 - mat.getRotation().y - modifierY
local newCardRot = cardRot:setAt("y", roundedRotY - deltaRotY)
-- move the main card to threat area
hoveredObject.setRotation(newCardRot)
hoveredObject.setPosition(threatAreaPos)
-- move tokens/tiles (to new global position)
for _, data in ipairs(additionalObjects) do
if not data.object.locked then
data.object.setPosition(hoveredObject.positionToWorld(data.localPos))
data.object.setRotation(data.object.getRotation() - Vector(0, deltaRotY, 0))
end
end
end
@ -131,7 +132,7 @@ function discardObject(playerColor, hoveredObject)
if #selectedObjects > 0 then
discardGroup(playerColor, selectedObjects)
return
-- only continue if an unlocked card, deck or tile was hovered
-- only continue if an unlocked card, deck or tile was hovered
elseif hoveredObject == nil
or (hoveredObject.type ~= "Card" and hoveredObject.type ~= "Deck" and hoveredObject.type ~= "Tile")
or hoveredObject.locked then
@ -174,11 +175,11 @@ 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()
Wait.time(function()
if (selectedObjects[i].type == "Card" or selectedObjects[i].type ~= "Deck" or selectedObjects[i].type == "Tile") then
performDiscard(playerColor, selectedObjects[i])
performDiscard(playerColor, selectedObjects[i])
end
end, (count - i + 1) * 0.1)
end, (count - i + 1) * 0.1)
end
end
@ -265,7 +266,7 @@ function removeOneUse(playerColor, hoveredObject)
for _, obj in ipairs(searchLib.onObject(hoveredObject, "isTileOrToken")) do
if not obj.locked and obj.memo ~= "resourceCounter" then
-- check for matching object, otherwise use the first hit
if obj.memo == searchForType then
if obj.memo and obj.memo == searchForType then
targetObject = obj
break
elseif not targetObject then
@ -275,6 +276,15 @@ function removeOneUse(playerColor, hoveredObject)
end
end
-- release sealed token if card has one and no uses
if tokenChecker.isChaosToken(targetObject) and hoveredObject.hasTag("CardThatSeals") then
local func = hoveredObject.getVar("releaseOneToken") -- check if function exists
if func ~= nil then
hoveredObject.call("releaseOneToken", playerColor)
return
end
end
-- error handling
if not targetObject then
broadcastToColor("No tokens found!", playerColor, "Yellow")
@ -503,3 +513,7 @@ function getColoredName(playerColor)
-- add bb-code
return "[" .. Color.fromString(playerColor):toHex() .. "]" .. displayName .. "[-]"
end
function roundToMultiple(num, mult)
return math.floor((num + mult / 2) / mult) * mult
end

View File

@ -160,8 +160,8 @@ function getRandomSeed()
return math.random(999)
end
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
-- chaos bag during search operations to avoid this.
function onObjectSearchStart(object, playerColor)
local chaosBag = findChaosBag()
@ -170,14 +170,15 @@ function onObjectSearchStart(object, playerColor)
end
end
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
-- container is being searched, a TTS bug can cause tokens to duplicate or vanish. We lock the
-- chaos bag during search operations to avoid this.
function onObjectSearchEnd(object, playerColor)
local chaosBag = findChaosBag()
if object == chaosBag then
bagSearchers[playerColor] = nil
end
Player[playerColor].clearSelectedObjects()
end
-- Pass object enter container events to the PlayArea to clear vector lines from dragged cards.
@ -207,15 +208,25 @@ function onObjectEnterZone(zone, object)
object.clearContextMenu()
object.call("shutOff")
end
if object.hasTag("CardThatSeals") then
local func = object.getVar("resetSealedTokens") -- check if function exists (it won't for older custom content)
if func ~= nil then
object.call("resetSealedTokens")
end
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
-- 1 frame delay to avoid error messages when exiting the game
Wait.frames(
function()
if zone.isDestroyed() or object.isDestroyed() then return end
if zone.type == "Hand" and object.hasTag("CardWithHelper") then
object.call("updateDisplay")
end
end, 1)
end
-- handle card drawing via number typing for multihanded gameplay
@ -224,15 +235,6 @@ function onObjectNumberTyped(hoveredObject, playerColor, number)
-- only continue for decks or cards
if hoveredObject.type ~= "Deck" and hoveredObject.type ~= "Card" then return end
-- check whether the hovered object is part of a players draw objects
for _, color in ipairs(playermatApi.getUsedMatColors()) do
local deckAreaObjects = playermatApi.getDeckAreaObjects(color)
if deckAreaObjects.topCard == hoveredObject or deckAreaObjects.draw == hoveredObject then
playermatApi.drawCardsWithReshuffle(color, number)
return true
end
end
-- check if this is a card with states (and then change state instead of drawing it)
local states = hoveredObject.getStates()
if states ~= nil and #states > 0 then
@ -242,6 +244,15 @@ function onObjectNumberTyped(hoveredObject, playerColor, number)
return true
end
end
-- check whether the hovered object is part of a players draw objects
for _, color in ipairs(playermatApi.getUsedMatColors()) do
local deckAreaObjects = playermatApi.getDeckAreaObjects(color)
if deckAreaObjects.topCard == hoveredObject or deckAreaObjects.draw == hoveredObject then
playermatApi.drawCardsWithReshuffle(color, number)
return true
end
end
end
-- TTS event, used to redraw the playermat slot symbols after a small delay to account for the custom font loading
@ -249,8 +260,9 @@ function onPlayerConnect()
Wait.time(function() playermatApi.redrawSlotSymbols("All") end, 0.2)
end
-- disable delete action (only applies to promoted players) and discard objects instead
function onPlayerAction(player, action, targets)
if action == Player.Action.Delete and player.admin == false then
if action == Player.Action.Delete and not player.admin then
for _, target in ipairs(targets) do
local matColor = playermatApi.getMatColorByPosition(target.getPosition())
local trash = guidReferenceApi.getObjectByOwnerAndType(matColor, "Trash")
@ -296,13 +308,13 @@ function returnChaosTokens()
end
-- returns a single chaos token to the bag and calls respective functions
function returnChaosTokenToBag(token)
local name = token.getName()
function returnChaosTokenToBag(params)
local name = params.token.getName()
local chaosBag = findChaosBag()
chaosBag.putObject(token)
chaosBag.putObject(params.token)
tokenArrangerApi.layout()
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.releasedToken(name, token.getGUID(), true)
blessCurseManagerApi.releasedToken(name, params.token.getGUID(), params.fromBag)
end
end
@ -413,7 +425,8 @@ function returnAndRedraw(_, tokenGUID)
-- perform the actual token replacing
trackChaosToken(tokenName, mat.getGUID(), true)
returnChaosTokenToBag(returnedToken)
local params = {token = returnedToken, fromBag = true}
returnChaosTokenToBag(params)
chaosTokens[indexOfReturnedToken] = drawChaosToken({
mat = mat,
@ -440,10 +453,13 @@ function returnAndRedraw(_, tokenGUID)
end
redrawData = {}
-- return a reference to the freshly drawn token
return chaosTokens[indexOfReturnedToken]
end
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens
-- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens
-- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the
-- contents of the bag should check this method before doing so.
-- This method will broadcast a message to all players if the bag is being searched.
---@return boolean: True if the bag is manipulated, false if it should be blocked.
@ -543,9 +559,9 @@ end
-- token spawning
---------------------------------------------------------
-- DEPRECATED. Use TokenManager instead.
-- DEPRECATED. Use TokenManager instead.
-- Spawns a single token.
---@param params table Array with arguments to the method. 1 = position, 2 = type, 3 = rotation
---@param params table Array with arguments to the method. 1 = position, 2 = type, 3 = rotation
function spawnToken(params)
return tokenManager.spawnToken(params[1], params[2], params[3])
end
@ -1364,7 +1380,8 @@ function contentDownloadCallback(request, params)
if pos then
spawnTable.position = pos
else
broadcastToAll("Please make space in the area below the tentacle stand in the upper middle of the table and try again.", "Red")
broadcastToAll(
"Please make space in the area below the tentacle stand in the upper middle of the table and try again.", "Red")
return
end
end
@ -1515,10 +1532,11 @@ function playermatRemovalSelected(player, selectedIndex, id)
if mat then
-- confirmation dialog about deletion
player.pingTable(mat.getPosition())
player.showConfirmDialog("Do you really want to remove " .. matColor .. "'s playermat and related objects? This can't be reversed.",
function()
removePlayermat(matColor)
end)
player.showConfirmDialog(
"Do you really want to remove " .. matColor .. "'s playermat and related objects? This can't be reversed.",
function()
removePlayermat(matColor)
end)
else
-- info dialog that it is already deleted
player.showInfoDialog(matColor .. "'s playermat has already been removed.")
@ -1593,6 +1611,7 @@ function applyOptionPanelChange(id, state)
local counter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "MasterClueCounter")
counter.setVar("useClickableCounters", state)
-- option: Enable card helpers
elseif id == "enableCardHelpers" then
toggleCardHelpers(state)
@ -1720,7 +1739,7 @@ function onClick_defaultSettings()
optionPanel = {
cardLanguage = "en",
changePlayAreaImage = false,
enableCardHelpers = false,
enableCardHelpers = true,
playAreaConnectionColor = { a = 1, b = 0.4, g = 0.4, r = 0.4 },
playAreaConnections = true,
playAreaSnapTags = true,

View File

@ -8,12 +8,12 @@ local tokenChecker = require("core/token/TokenChecker")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
local ENCOUNTER_DECK_AREA = {
upperLeft = { x = 0.9, z = 0.42 },
lowerRight = { x = 0.86, z = 0.38 }
upperLeft = { x = 1.05, z = 0.15 },
lowerRight = { x = 0.70, z = 0.59 }
}
local ENCOUNTER_DISCARD_AREA = {
upperLeft = { x = 1.62, z = 0.42 },
lowerRight = { x = 1.58, z = 0.38 }
upperLeft = { x = 1.77, z = 0.15 },
lowerRight = { x = 1.42, z = 0.59 }
}
-- global position of encounter deck and discard pile
@ -45,7 +45,7 @@ end
-- collison and container event handling
---------------------------------------------------------
-- TTS event handler. Handles scenario name event triggering and encounter card token resets.
-- TTS event handler. Handles scenario name event triggering and encounter card token resets.
function onCollisionEnter(collisionInfo)
if not collisionEnabled then return end
@ -240,18 +240,16 @@ end
function inArea(point, bounds)
return (point.x < bounds.upperLeft.x
and point.x > bounds.lowerRight.x
and point.z < bounds.upperLeft.z
and point.z > bounds.lowerRight.z)
and point.z > bounds.upperLeft.z
and point.z < bounds.lowerRight.z)
end
-- removes tokens from the provided card/deck
function removeTokensFromObject(object)
local TRASH = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
for _, obj in ipairs(searchLib.onObject(object)) do
for _, obj in ipairs(searchLib.onObject(object, "isTileOrToken")) do
if obj.getGUID() ~= "4ee1f2" and -- table
obj ~= self and
obj.type ~= "Deck" and
obj.type ~= "Card" and
obj.memo ~= nil and
obj.getLock() == false and
not tokenChecker.isChaosToken(obj) then

View File

@ -51,20 +51,21 @@ As a nice reminder the XML button takes on the Frost color and icon with the tex
> require...
----------------------------------------------------------]]
local isHelperEnabled = false
-- intentionally global
hasXML = true
isHelperEnabled = false
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
createHelperXML()
checkOptionPanel()
syncDisplayWithOptionPanel()
end
function createHelperXML()
@ -95,14 +96,6 @@ function createHelperXML()
self.UI.setXmlTable(xmlTable)
end
function shutOff()
self.UI.hide("Helper")
end
function initialize()
self.UI.show("Helper")
end
function triggerXMLTokenLabelCreation()
Global.call("activeRedrawEffect", {
VALID_TOKENS = VALID_TOKENS,

View File

@ -1,7 +1,16 @@
--[[ Library for cards that seal tokens
This file is used to add sealing option to cards' context menu.
NOTE: all cards are allowed to release a single token to enable Hallow and A Watchful Peace,
and to release all sealed tokens to allow for cards that might leave play with sealed tokens on them.
Valid options (set before requiring this file):
MAX_SEALED --@type: number (maximum number of tokens allowable by the card to be sealed)
- required for all cards
- if MAX_SEALED is more than 1, then an XML label is created for the topmost token indicating the number of sealed tokens
- gives an error if user tries to seal additional tokens on the card
- example usage: "The Chthonian Stone"
> MAX_SEALED = 1
UPDATE_ON_HOVER --@type: boolean
- automatically updates the context menu options when the card is hovered
- the "Read Bag" function reads the content of the chaos bag to update the context menu
@ -12,19 +21,16 @@ KEEP_OPEN --@type: boolean
- makes the context menu stay open after selecting an option
- example usage: "Unrelenting"
SHOW_SINGLE_RELEASE --@type: boolean
SHOW_MULTI_RELEASE --@type: number (maximum amount of tokens to release at once)
- enables an entry in the context menu
- this entry allows releasing a single token
- example usage: "Holy Spear" (to keep the other tokens and just release one)
SHOW_MULTI_RELEASE --@type: number (amount of tokens to release at once)
- enables an entry in the context menu
- this entry allows releasing of multiple tokens at once
- example usage: "Nephthys" (to release 3 bless tokens at once)
- this entry allows releasing of multiple tokens at once, to the maximum number
- does not fail if there are fewer than the maximum sealed
- example usage: "Nephthys" (to release up to 3 bless tokens at once)
SHOW_MULTI_RETURN --@type: number (amount of tokens to return to pool at once)
- enables an entry in the context menu
- this entry allows returning tokens to the token pool
- fails if not enough tokens are sealed
- example usage: "Nephthys" (to return 3 bless tokens at once)
SHOW_MULTI_SEAL --@type: number (amount of tokens to seal at once)
@ -58,6 +64,7 @@ Thus it should be implemented like this:
> ["+1"] = true,
> ["Elder Sign"] = true
> }
> MAX_SEALED = 1
> require...
----------------------------------------------------------
Example 2: Holy Spear
@ -68,8 +75,8 @@ Thus it should be implemented like this:
> VALID_TOKENS = {
> ["Bless"] = true
> }
> SHOW_SINGLE_RELEASE = true
> SHOW_MULTI_SEAL = 2
> MAX_SEALED = 10
> require...
----------------------------------------------------------]]
@ -83,6 +90,20 @@ local sealedTokens = {}
local ID_URL_MAP = {}
local tokensInBag = {}
-- XML background color for each token for label when stacked
local tokenColor = {
["Skull"] = "#4A0400E6",
["Cultist"] = "#173B0BE6",
["Tablet"] = "#1D2238E6",
["Elder Thing"] = "#4D2331E6",
["Auto-fail"] = "#9B0004E6",
["Bless"] = "#9D702CE6",
["Curse"] = "#633A84E6",
["Frost"] = "#404450E6",
["Elder Sign"] = "#50A8CEE6",
[""] = "#77674DE6"
}
function onSave() return JSON.encode(sealedTokens) end
function onLoad(savedData)
@ -94,13 +115,15 @@ end
-- builds the context menu
function generateContextMenu()
-- conditional single or multi release options
if SHOW_SINGLE_RELEASE then
self.addContextMenuItem("Release token", releaseOneToken)
elseif SHOW_MULTI_RELEASE then
self.addContextMenuItem("Release one token", releaseOneToken)
-- conditional release options
if MAX_SEALED > 1 then
self.addContextMenuItem("Release all tokens", releaseAllTokens)
end
if SHOW_MULTI_RELEASE then
self.addContextMenuItem("Release " .. SHOW_MULTI_RELEASE .. " token(s)", releaseMultipleTokens)
else
self.addContextMenuItem("Release token(s)", releaseAllTokens)
end
if RESOLVE_TOKEN then
@ -138,7 +161,7 @@ function generateContextMenu()
end
if allowed then
for i = 1, SHOW_MULTI_SEAL do
for i = SHOW_MULTI_SEAL, 1, -1 do
sealToken(map.name, playerColor)
end
else
@ -175,6 +198,10 @@ end
-- seals the named token on this card
function sealToken(name, playerColor)
if #sealedTokens >= MAX_SEALED then
printToColor("Cannot seal any more tokens on this card", playerColor, "Red")
return
end
if not chaosBagApi.canTouchChaosTokens() then return end
local chaosbag = chaosBagApi.findChaosBag()
for i, obj in ipairs(chaosbag.getObjects()) do
@ -191,6 +218,16 @@ function sealToken(name, playerColor)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.sealedToken(name, guid)
end
-- destroy XML on just covered token
if #sealedTokens > 1 then
local coveredToken = getObjectFromGUID(sealedTokens[#sealedTokens - 1])
if coveredToken ~= nil then
coveredToken.UI.setXml("")
else
table.remove(sealedTokens, #sealedTokens - 1)
end
end
updateStackSize()
end
})
return
@ -210,16 +247,22 @@ function releaseOneToken(playerColor)
end
end
-- release multiple tokens at once
-- release up to multiple tokens at once with no minimum
function releaseMultipleTokens(playerColor)
if SHOW_MULTI_RELEASE <= #sealedTokens then
for i = 1, SHOW_MULTI_RELEASE do
putTokenAway(table.remove(sealedTokens))
end
printToColor("Releasing " .. SHOW_MULTI_RELEASE .. " tokens", playerColor)
else
if #sealedTokens == 0 then
printToColor("Not enough tokens sealed.", playerColor)
return
end
local numRemoved = SHOW_MULTI_RELEASE
if #sealedTokens < SHOW_MULTI_RELEASE then
numRemoved = #sealedTokens
end
for i = 1, numRemoved do
putTokenAway(table.remove(sealedTokens))
end
printToColor("Releasing " .. numRemoved .. " tokens", playerColor)
end
-- releases all sealed tokens
@ -260,6 +303,7 @@ function putTokenAway(guid)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.releasedToken(name, guid)
end
updateStackSize()
end
-- returns the token to the pool (== removes it)
@ -272,6 +316,7 @@ function returnToken(guid)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.returnedToken(name, guid)
end
updateStackSize()
end
-- resolves sealed token as if it came from the chaos bag
@ -283,5 +328,41 @@ function resolveSealed()
local closestMatColor = playermatApi.getMatColorByPosition(self.getPosition())
local mat = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, "Playermat")
local guidToBeResolved = table.remove(sealedTokens)
local resolvedToken = getObjectFromGUID(guidToBeResolved)
resolvedToken.UI.setXml("")
updateStackSize()
chaosBagApi.drawChaosToken(mat, true, _, guidToBeResolved)
end
function updateStackSize()
if MAX_SEALED == 1 then return end
if #sealedTokens == 0 then return end
-- get topmost sealed token
local topToken = getObjectFromGUID(sealedTokens[#sealedTokens])
local name = topToken.getName()
topToken.UI.setXmlTable({
{
tag = "Panel",
attributes = {
height = 380,
width = 380,
rotation = "0 0 180",
scale = "0.2 0.2 1",
position = "0 0 -12",
color = tokenColor[name] or "#77674DE6"
},
children = {
tag = "Text",
attributes = {
fontSize = "380",
font = "font_teutonic-arkham",
color = "#ffffff",
outline = "#000000",
outlineSize = "8 -8",
text = "x" .. #sealedTokens
}
}
}
})
end

View File

@ -1,7 +1,23 @@
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function checkOptionPanel()
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
@ -33,10 +49,12 @@ function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end

View File

@ -0,0 +1,2 @@
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")

View File

@ -1,38 +1,40 @@
require("playercards/CardsWithHelper")
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playermatApi = require("playermat/PlayermatApi")
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
-- intentionally global
hasXML = true
isHelperEnabled = false
local updated, loopId
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
self.script_state = JSON.encode({
isHelperEnabled = isHelperEnabled,
loopId = loopId
})
end
function onLoad(savedData)
self.addTag("CardWithHelper")
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
loopId = loadedData.loopId
end
checkOptionPanel()
syncDisplayWithOptionPanel()
end
-- hide buttons and stop monitoring
function shutOff()
self.UI.hide("Helper")
Wait.stopAll()
updateSave()
if loopId then
Wait.stop(loopId)
loopId = nil
end
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()
loopId = Wait.time(maybeUpdateButtonState, 1, -1)
end
function resolveToken(player, _, tokenType)

View File

@ -2,5 +2,6 @@ VALID_TOKENS = {
["+1"] = true,
["Elder Sign"] = true
}
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -3,5 +3,6 @@ VALID_TOKENS = {
}
KEEP_OPEN = true
MAX_SEALED = 5
require("playercards/CardsThatSealTokens")

View File

@ -2,4 +2,6 @@ VALID_TOKENS = {
["Elder Sign"] = true
}
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -1,5 +1,9 @@
require("playercards/CardsWithHelper")
local playermatApi = require("playermat/PlayermatApi")
local playermatApi = require("playermat/PlayermatApi")
-- intentionally global
hasXML = false
isHelperEnabled = false
-- common button parameters
local buttonParameters = {}
@ -28,7 +32,6 @@ local customizableList = {
-- index of the currently selected button (0-indexed from the top)
local activeButtonIndex = -1
local isHelperEnabled = false
local hypothesisList = {}
function updateSave()
@ -39,17 +42,17 @@ function updateSave()
end
function onLoad(savedData)
self.addTag("CardWithHelper")
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
activeButtonIndex = loadedData.activeButtonIndex
end
checkOptionPanel()
if activeButtonIndex > 0 then
selectButton(activeButtonIndex)
end
syncDisplayWithOptionPanel()
end
function initialize()
@ -74,7 +77,7 @@ function selectButton(index)
end
end
-- Create buttons based on the button parameters
-- create buttons based on the button parameters
function createButtons()
self.clearButtons()
@ -88,7 +91,6 @@ 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

View File

@ -1,26 +1,24 @@
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager")
require("playercards/CardsWithHelper")
local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager")
-- intentionally global
hasXML = true
isHelperEnabled = false
local clickableResourceCounter = nil
local foundTokens = 0
local foundTokens = 0
function onLoad()
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)
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
syncDisplayWithOptionPanel()
end
function searchSelf()
@ -40,7 +38,7 @@ function searchSelf()
end
end
function add4(playerColor)
function add4()
searchSelf()
local newCount = foundTokens + 4
@ -51,7 +49,7 @@ function add4(playerColor)
end
end
function takeAll(playerColor)
function takeAll(player)
searchSelf()
local matColor = playermatApi.getMatColorByPosition(self.getPosition())
playermatApi.updateCounter(matColor, "ResourceCounter", _, foundTokens)
@ -59,14 +57,13 @@ function takeAll(playerColor)
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", 0)
end
printToColor("Moved " .. foundTokens .. " resource(s) to " .. matColor .. "'s resource pool.", playerColor)
printToColor("Moved " .. foundTokens .. " resource(s) to " .. matColor .. "'s resource pool.", player.color)
end
function loseAll(playerColor)
function loseAll(player)
searchSelf()
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", 0)
end
printToColor("Discarded " .. foundTokens .. " resource(s).", playerColor)
printToColor("Discarded " .. foundTokens .. " resource(s).", player.color)
end

View File

@ -2,8 +2,8 @@ VALID_TOKENS = {
["Curse"] = true
}
SHOW_SINGLE_RELEASE = true
KEEP_OPEN = true
MAX_SEALED = 3
RESOLVE_TOKEN = true
require("playercards/CardsThatSealTokens")

View File

@ -2,8 +2,8 @@ VALID_TOKENS = {
["Bless"] = true
}
SHOW_SINGLE_RELEASE = true
KEEP_OPEN = true
MAX_SEALED = 3
RESOLVE_TOKEN = true
require("playercards/CardsThatSealTokens")

View File

@ -2,7 +2,7 @@ VALID_TOKENS = {
["Curse"] = true
}
SHOW_SINGLE_RELEASE = true
MAX_SEALED = 10
KEEP_OPEN = true
require("playercards/CardsThatSealTokens")

View File

@ -2,7 +2,7 @@ VALID_TOKENS = {
["Bless"] = true
}
SHOW_SINGLE_RELEASE = true
SHOW_MULTI_SEAL = 2
MAX_SEALED = 10
require("playercards/CardsThatSealTokens")

View File

@ -5,8 +5,10 @@ local playermatApi = require("playermat/PlayermatApi")
local searchLib = require("util/SearchLib")
local tokenManager = require("core/token/TokenManager")
local isHelperEnabled = false
local updated
-- intentionally global
hasXML = true
isHelperEnabled = false
local updated, loopId
local xmlData = {
Action = { color = "#6D202CE6", onClick = "removeAndExtraAction" },
@ -16,31 +18,31 @@ local xmlData = {
}
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
self.script_state = JSON.encode({
isHelperEnabled = isHelperEnabled,
loopId = loopId
})
end
function onLoad(savedData)
self.addTag("CardWithHelper")
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
loopId = loadedData.loopId
end
checkOptionPanel()
syncDisplayWithOptionPanel()
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()
loopId = Wait.time(maybeUpdateButtonState, 1, -1)
end
function shutOff()
if loopId then
Wait.stop(loopId)
loopId = nil
end
end
function addTokenToBag(_, _, tokenType)

View File

@ -2,8 +2,9 @@ VALID_TOKENS = {
["Bless"] = true
}
SHOW_SINGLE_RELEASE = true
KEEP_OPEN = true
SHOW_MULTI_RELEASE = 3
SHOW_MULTI_RETURN = 3
MAX_SEALED = 10
require("playercards/CardsThatSealTokens")

View File

@ -5,5 +5,6 @@ INVALID_TOKENS = {
}
UPDATE_ON_HOVER = true
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -3,5 +3,6 @@ VALID_TOKENS = {
}
KEEP_OPEN = true
MAX_SEALED = 3
require("playercards/CardsThatSealTokens")

View File

@ -3,6 +3,6 @@ VALID_TOKENS = {
}
KEEP_OPEN = true
SHOW_SINGLE_RELEASE = true
MAX_SEALED = 5
require("playercards/CardsThatSealTokens")

View File

@ -2,4 +2,6 @@ VALID_TOKENS = {
["Auto-fail"] = true
}
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -2,4 +2,6 @@ VALID_TOKENS = {
["Elder Sign"] = true
}
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -2,6 +2,6 @@ VALID_TOKENS = {
["0"] = true
}
SHOW_SINGLE_RELEASE = true
MAX_SEALED = 4 -- Core Set is component-limited to 4 '0' tokens
require("playercards/CardsThatSealTokens")

View File

@ -3,6 +3,6 @@ VALID_TOKENS = {
}
KEEP_OPEN = true
SHOW_SINGLE_RELEASE = true
MAX_SEALED = 5
require("playercards/CardsThatSealTokens")

View File

@ -0,0 +1,2 @@
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")

View File

@ -5,4 +5,6 @@ VALID_TOKENS = {
["Elder Thing"] = true,
}
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -3,5 +3,6 @@ VALID_TOKENS = {
}
RESOLVE_TOKEN = true
MAX_SEALED = 1
require("playercards/CardsThatSealTokens")

View File

@ -0,0 +1,2 @@
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")

View File

@ -5,5 +5,6 @@ INVALID_TOKENS = {
UPDATE_ON_HOVER = true
KEEP_OPEN = true
MAX_SEALED = 3
require("playercards/CardsThatSealTokens")

View File

@ -1,5 +1,11 @@
require("playercards/CardsWithHelper")
local playermatApi = require("playermat/PlayermatApi")
local playermatApi = require("playermat/PlayermatApi")
-- intentionally global
hasXML = false
isHelperEnabled = false
local modValue, loopId
local buttonParameters = {
click_function = "shutOff",
@ -10,13 +16,20 @@ local buttonParameters = {
height = 175
}
local modValue
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
self.script_state = JSON.encode({
isHelperEnabled = isHelperEnabled,
loopId = loopId
})
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
loopId = loadedData.loopId
end
-- use metadata to detect level and adjust modValue accordingly
if JSON.decode(self.getGMNotes()).level == 0 then
modValue = 5
@ -24,25 +37,22 @@ function onLoad(savedData)
modValue = 4
end
self.addTag("CardWithHelper")
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
checkOptionPanel()
updateDisplay()
syncDisplayWithOptionPanel()
end
function initialize()
self.clearButtons()
self.createButton(buttonParameters)
updateButton()
Wait.time(updateButton, 2, -1)
loopId = Wait.time(updateButton, 2, -1)
end
function shutOff()
self.clearButtons()
Wait.stopAll()
if loopId then
Wait.stop(loopId)
loopId = nil
end
end
function updateButton()

View File

@ -76,12 +76,12 @@ local buttonParameters = {
-- table of texture URLs
local nameToTexture = {
Guardian = "http://cloud-3.steamusercontent.com/ugc/2501268517203536128/853B9CD08FC14A8B2A08C73D8ED77E0FE235CCCB/",
Mystic = "http://cloud-3.steamusercontent.com/ugc/2501268517203536470/11C99488B9CA9236059A5F02E4A852A7C77B42A6/",
Guardian = "http://cloud-3.steamusercontent.com/ugc/2501268517241599869/179119CA88170D9F5C87CD00D267E6F9F397D2F7/",
Mystic = "http://cloud-3.steamusercontent.com/ugc/2501268517241600113/F6473F92B3435C32A685BB4DC2A88C2504DDAC4F/",
Neutral = "http://cloud-3.steamusercontent.com/ugc/2462982115659543571/5D778EA4BC682DAE97E8F59A991BCF8CB3979B04/",
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/"
Rogue = "http://cloud-3.steamusercontent.com/ugc/2501268517241600395/00CFAFC13D7B6EACC147D22A40AF9FBBFFAF3136/",
Seeker = "http://cloud-3.steamusercontent.com/ugc/2501268517241600579/92DEB412D8D3A9C26D1795CEA0335480409C3E4B/",
Survivor = "http://cloud-3.steamusercontent.com/ugc/2501268517241600848/CEB685E9C8A4A3C18A4B677A519B49423B54E886/"
}
-- translation table for slot names to characters for special font
@ -141,6 +141,8 @@ function onLoad(savedData)
slotData = loadedData.slotData
end
updateMessageColor(playerColor)
self.interactable = false
-- get object references to owned objects
@ -271,7 +273,7 @@ function discardListOfObjects(objList)
end
elseif tokenChecker.isChaosToken(obj) then
-- put chaos tokens back into bag (e.g. Unrelenting)
chaosBagApi.returnChaosTokenToBag(obj)
chaosBagApi.returnChaosTokenToBag(obj, false)
elseif not obj.getLock() and not obj.hasTag("DontDiscard") then
-- don't touch locked objects (like the table etc.) or specific objects (like key tokens)
ownedObjects.Trash.putObject(obj)
@ -404,7 +406,7 @@ function doUpkeep(_, clickedByColor, isRightClick)
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
if card.type == "Card" and md ~= nil and (not md.weakness and not md.hidden and md.id ~= "52020") then
table.insert(cardsToDiscard, card)
end
end
@ -564,23 +566,46 @@ function doDiscardOne()
broadcastToColor("Cannot discard from empty hand!", messageColor, "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
local hiddenCards = {}
local missingMetadataCards = {}
for i, handObj in ipairs(hand) do
if handObj.type == "Card" then
-- get a name for the card or use the index if unnamed
local name = handObj.getName()
if name == "" then
name = "Card " .. i
end
-- check card for metadata
local md = JSON.decode(handObj.getGMNotes())
if md == nil then
table.insert(missingMetadataCards, name)
elseif md.hidden or md.id == "52020" then
table.insert(hiddenCards, name)
else
table.insert(choices, i)
end
else
table.insert(choices, i)
end
end
-- print message with hidden cards
if #hiddenCards > 0 then
local cardList = concatenateListOfStrings(hiddenCards)
printToColor("Excluded (hidden): " .. cardList, messageColor)
end
-- print message with missing metadata cards
if #missingMetadataCards > 0 then
local cardList = concatenateListOfStrings(missingMetadataCards)
printToColor("Excluded (missing data): " .. cardList, messageColor)
end
if #choices == 0 then
broadcastToColor("Hidden cards can't be randomly discarded.", messageColor, "Orange")
broadcastToColor("Didn't find any eligible cards for random discarding.", messageColor, "Orange")
return
end
-- get a random non-hidden card (from the "choices" table)
-- get a random eligible card (from the "choices" table)
local num = math.random(1, #choices)
deckLib.placeOrMergeIntoDeck(hand[choices[num]], returnGlobalDiscardPosition(), self.getRotation())
broadcastToAll(getColoredName(playerColor) .. " randomly discarded card "
@ -588,6 +613,19 @@ function doDiscardOne()
end
end
function concatenateListOfStrings(list)
local cardList
for _, cardName in ipairs(list) do
if not cardList then
cardList = ""
else
cardList = cardList .. ", "
end
cardList = cardList .. cardName
end
return cardList
end
-- checks if DES is present
function checkForDES()
hasDES = false
@ -1031,7 +1069,7 @@ function removeTokensFromObject(object)
for _, obj in ipairs(searchLib.onObject(object)) do
if tokenChecker.isChaosToken(obj) then
chaosBagApi.returnChaosTokenToBag(obj)
chaosBagApi.returnChaosTokenToBag(obj, false)
elseif obj.getGUID() ~= "4ee1f2" and -- table
obj ~= self and
obj.type ~= "Deck" and

View File

@ -30,7 +30,7 @@ do
max_distance = maxDistance or 0
})
-- filtering the result
-- filter the result for matching objects
local objList = {}
for _, v in ipairs(searchResult) do
if not filter or filterFunc(v.hit_object) then
@ -47,22 +47,22 @@ do
-- searches the area on an object
SearchLib.onObject = function(obj, filter)
pos = obj.getPosition()
size = obj.getBounds().size:setAt("y", 1)
local pos = obj.getPosition()
local size = obj.getBounds().size:setAt("y", 1)
return returnSearchResult(pos, _, size, filter)
end
-- searches the specified position (a single point)
SearchLib.atPosition = function(pos, filter)
size = { 0.1, 2, 0.1 }
local size = { 0.1, 2, 0.1 }
return returnSearchResult(pos, _, size, filter)
end
-- searches below the specified position (downwards until y = 0)
SearchLib.belowPosition = function(pos, filter)
size = { 0.1, 2, 0.1 }
direction = { 0, -1, 0 }
maxDistance = pos.y
local size = { 0.1, 2, 0.1 }
local direction = { 0, -1, 0 }
local maxDistance = pos.y
return returnSearchResult(pos, _, size, filter, direction, maxDistance)
end

View File

@ -0,0 +1,37 @@
<Defaults>
<Button padding="30 30 30 30"
font="font_teutonic-arkham"
textColor="white"
fontSize="235"
shadow="#405041B3"
shadowDistance="-15 15"/>
<TableLayout position="130 0 -22"
rotation="0 0 270"
height="460"
width="2600"
scale="0.1 0.1 1"
cellSpacing="80"
cellBackgroundColor="rgba(1,1,1,0)"/>
</Defaults>
<TableLayout id="Helper"
active="false">
<Row>
<Cell>
<Button onClick="loseAll"
color="#6D202C"
fontSize="195"
text="Discard all"/>
</Cell>
<Cell>
<Button onClick="takeAll"
color="#173B0B"
text="Move all"/>
</Cell>
<Cell>
<Button onClick="add4"
color="#77674D"
text="Place 4"/>
</Cell>
</Row>
</TableLayout>