diff --git a/config.json b/config.json
index 5e47eb16..0cd2293c 100644
--- a/config.json
+++ b/config.json
@@ -57,8 +57,8 @@
"Cluetokens.11e0cf",
"Doomtokens.b015d8",
"DoomCounter.85c4c6",
- "Custom_Tile.2eca7c",
- "Custom_Tile.fb09d4",
+ "RoundSequenceActionDescription.2eca7c",
+ "RoundSequenceActionDescription.fb09d4",
"3DText.65eb7e",
"InvestigatorCount.f182ee",
"ScriptingTrigger.c506bf",
@@ -96,7 +96,6 @@
"Resourcetokens.0168ae",
"Horrortokens.ae1a4e",
"DamageTokens.b0ef6c",
- "Connectionmarkers.b118af",
"Trash.4b8594",
"Trash.5f896a",
"Trash.147e80",
@@ -109,7 +108,6 @@
"Doomtokens.16fcd6",
"DamageTokens.93f4a0",
"Resourcetokens.fd617a",
- "Connectionmarkers.5dcccb",
"Cluetokens.31fa39",
"Horrortokens.c3ecf4",
"Doomtokens.47ffc3",
@@ -252,7 +250,8 @@
"TokenSpawnTracker.e3ffc9",
"TokenSource.124381",
"GameData.3dbe47",
- "SCEDTour.0e5aa8"
+ "SCEDTour.0e5aa8",
+ "PlayerCards.2d30ee"
],
"PlayArea": 1,
"PlayerCounts": [
diff --git a/modsettings/CustomUIAssets.json b/modsettings/CustomUIAssets.json
index cb69884e..da875443 100644
--- a/modsettings/CustomUIAssets.json
+++ b/modsettings/CustomUIAssets.json
@@ -107,12 +107,12 @@
{
"Name": "NextArrow",
"Type": 0,
- "URL": "https://i.imgur.com/gXRiKmu.png"
+ "URL": "https://i.imgur.com/MztSQis.png"
},
{
"Name": "Exit",
"Type": 0,
- "URL": "https://i.imgur.com/i0VMjPD.png"
+ "URL": "https://i.imgur.com/8qmTXwt.png"
},
{
"Name": "Inv-Mandy",
diff --git a/modsettings/SnapPoints.json b/modsettings/SnapPoints.json
index ec98ff16..11cad351 100644
--- a/modsettings/SnapPoints.json
+++ b/modsettings/SnapPoints.json
@@ -3817,54 +3817,6 @@
"z": 65.419
}
},
- {
- "Position": {
- "x": -22.602,
- "y": 1.635,
- "z": 22.44
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -25.571,
- "y": 1.639,
- "z": 22.44
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -28.485,
- "y": 1.642,
- "z": 22.44
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -31.454,
- "y": 1.645,
- "z": 22.44
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
{
"Position": {
"x": -50.852,
@@ -3985,106 +3937,6 @@
"z": 0
}
},
- {
- "Position": {
- "x": -28.509,
- "y": 1.64,
- "z": -22.42
- },
- "Rotation": {
- "x": 0,
- "y": 180,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -25.509,
- "y": 1.64,
- "z": -22.42
- },
- "Rotation": {
- "x": 0,
- "y": 180,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -22.606,
- "y": 1.64,
- "z": -22.42
- },
- "Rotation": {
- "x": 0,
- "y": 180,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -19.651,
- "y": 1.64,
- "z": -22.42
- },
- "Rotation": {
- "x": 0,
- "y": 180,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -34.383,
- "y": 1.651,
- "z": 22.439
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -16.691,
- "y": 1.64,
- "z": -22.417
- },
- "Rotation": {
- "x": 0,
- "y": 180,
- "z": 0
- }
- },
- {
- "Position": {
- "x": -34.26,
- "y": 1.651,
- "z": 23.6
- }
- },
- {
- "Position": {
- "x": -33.12,
- "y": 1.65,
- "z": 23.6
- }
- },
- {
- "Position": {
- "x": -16.78,
- "y": 1.64,
- "z": -23.58
- }
- },
- {
- "Position": {
- "x": -17.92,
- "y": 1.64,
- "z": -23.58
- }
- },
{
"Position": {
"x": -52.07,
@@ -4127,20 +3979,6 @@
"z": -21.55
}
},
- {
- "Position": {
- "x": -20.049,
- "y": 1.64,
- "z": -24.78
- }
- },
- {
- "Position": {
- "x": -30.997,
- "y": 1.647,
- "z": 24.81
- }
- },
{
"Position": {
"x": 45.336,
@@ -4203,5 +4041,285 @@
"y": 1.481,
"z": 0
}
+ },
+ {
+ "Position": {
+ "x": -24.91,
+ "y": 1.55,
+ "z": -24.84
+ }
+ },
+ {
+ "Position": {
+ "x": -35.77,
+ "y": 1.55,
+ "z": 24.86
+ }
+ },
+ {
+ "Position": {
+ "x": -37.9,
+ "y": 1.55,
+ "z": 23.72
+ }
+ },
+ {
+ "Position": {
+ "x": -39.04,
+ "y": 1.55,
+ "z": 23.72
+ }
+ },
+ {
+ "Position": {
+ "x": -22.78,
+ "y": 1.55,
+ "z": -23.72
+ }
+ },
+ {
+ "Position": {
+ "x": -21.64,
+ "y": 1.55,
+ "z": -23.72
+ }
+ },
+ {
+ "Position": {
+ "x": -21.502,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -24.462,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -27.407,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -30.365,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -33.332,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -36.216,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -39.14,
+ "y": 1.55,
+ "z": -22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 180,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -39.271,
+ "y": 1.55,
+ "z": 22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -36.213,
+ "y": 1.55,
+ "z": 22.439
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -33.287,
+ "y": 1.55,
+ "z": 22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -30.332,
+ "y": 1.55,
+ "z": 22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -27.388,
+ "y": 1.55,
+ "z": 22.44
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -24.473,
+ "y": 1.55,
+ "z": 22.504
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -21.541,
+ "y": 1.55,
+ "z": 22.504
+ },
+ "Rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": 21.5
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": 23.75
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": 26
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": 28.25
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": 30.5
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": -21.5
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": -23.75
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": -26
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": -28.25
+ }
+ },
+ {
+ "Position": {
+ "x": -45.4,
+ "y": 1.481,
+ "z": -30.5
+ }
}
]
diff --git a/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.json b/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.json
index 864d643d..74ebcdf6 100644
--- a/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.json
+++ b/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.json
@@ -33,8 +33,8 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
- "LuaScript": "",
"LuaScriptState": "",
+ "LuaScript_path": "AllPlayerCards.15bb07/ShortSupply.e5f541.ttslua",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Short Supply",
diff --git a/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.ttslua b/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.ttslua
new file mode 100644
index 00000000..548ea019
--- /dev/null
+++ b/objects/AllPlayerCards.15bb07/ShortSupply.e5f541.ttslua
@@ -0,0 +1,36 @@
+local playmatAPI = require("playermat/PlaymatApi")
+
+function onLoad()
+ self.addContextMenuItem("Discard 10 cards", shortSupply)
+end
+
+-- called by context menu entry
+function shortSupply(color)
+ local matColor = playmatAPI.getMatColorByPosition(self.getPosition())
+
+ -- get draw deck and discard position
+ local drawDeck = playmatAPI.getDrawDeck(matColor)
+ local discardPos = playmatAPI.getDiscardPosition(matColor)
+
+ -- error handling
+ if discardPos == nil then
+ broadcastToColor("Couldn't retrieve discard position from playermat!", color, "Red")
+ return
+ end
+
+ if drawDeck == nil then
+ broadcastToColor("Deck not found!", color, "Yellow")
+ return
+ elseif drawDeck.tag ~= "Deck" then
+ broadcastToColor("Deck only contains a single card!", color, "Yellow")
+ return
+ end
+
+ -- discard cards
+ broadcastToColor("Discarding top 10 cards for player color '" .. matColor .. "'.", color, "White")
+ discardPos.y = 0.5
+ for i = 1, 10 do
+ discardPos.y = discardPos.y + 0.05 * i
+ drawDeck.takeObject({ flip = true, position = discardPos })
+ end
+end
diff --git a/objects/ClueCounter.032300.json b/objects/ClueCounter.032300.json
index 066aa2e8..71211083 100644
--- a/objects/ClueCounter.032300.json
+++ b/objects/ClueCounter.032300.json
@@ -51,9 +51,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -32.193,
+ "posX": -36.867,
"posY": 1.52,
- "posZ": 30.977,
+ "posZ": 31.025,
"rotX": 0,
"rotY": 10,
"rotZ": 0,
diff --git a/objects/ClueCounter.1769ed.json b/objects/ClueCounter.1769ed.json
index 51153c45..914b6298 100644
--- a/objects/ClueCounter.1769ed.json
+++ b/objects/ClueCounter.1769ed.json
@@ -51,9 +51,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -59.426,
+ "posX": -59.402,
"posY": 1.52,
- "posZ": -22.721,
+ "posZ": -22.586,
"rotX": 0,
"rotY": 280,
"rotZ": 0,
diff --git a/objects/ClueCounter.37be78.json b/objects/ClueCounter.37be78.json
index 1026c19d..5b7fcb52 100644
--- a/objects/ClueCounter.37be78.json
+++ b/objects/ClueCounter.37be78.json
@@ -51,9 +51,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -18.87,
+ "posX": -23.89,
"posY": 1.52,
- "posZ": -30.977,
+ "posZ": -31.107,
"rotX": 0,
"rotY": 190,
"rotZ": 0,
diff --git a/objects/ClueCounter.d86b7c.json b/objects/ClueCounter.d86b7c.json
index ca1bedaf..8beb0fc8 100644
--- a/objects/ClueCounter.d86b7c.json
+++ b/objects/ClueCounter.d86b7c.json
@@ -51,9 +51,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -59.426,
+ "posX": -59.45,
"posY": 1.52,
- "posZ": 9.395,
+ "posZ": 9.589,
"rotX": 0,
"rotY": 280,
"rotZ": 0,
diff --git a/objects/Clues.4111de.json b/objects/Clues.4111de.json
index efe2d1a7..97c69558 100644
--- a/objects/Clues.4111de.json
+++ b/objects/Clues.4111de.json
@@ -45,7 +45,7 @@
],
"Tooltip": false,
"Transform": {
- "posX": -18.87,
+ "posX": -23.89,
"posY": 1.3,
"posZ": -30.977,
"rotX": 0,
diff --git a/objects/Clues.891403.json b/objects/Clues.891403.json
index d7dfcd22..32174201 100644
--- a/objects/Clues.891403.json
+++ b/objects/Clues.891403.json
@@ -45,7 +45,7 @@
],
"Tooltip": false,
"Transform": {
- "posX": -32.193,
+ "posX": -36.87,
"posY": 1.3,
"posZ": 30.977,
"rotX": 0,
diff --git a/objects/Cluetokens.3b2550.json b/objects/Cluetokens.3b2550.json
index e5cdc8d5..9724ad44 100644
--- a/objects/Cluetokens.3b2550.json
+++ b/objects/Cluetokens.3b2550.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -42.227,
- "posY": 1.611,
- "posZ": -31.182,
+ "posX": -45.4,
+ "posY": 1.561,
+ "posZ": -28.25,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
diff --git a/objects/Cluetokens.fae2f6.json b/objects/Cluetokens.fae2f6.json
index b5340f1f..0ce0296e 100644
--- a/objects/Cluetokens.fae2f6.json
+++ b/objects/Cluetokens.fae2f6.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -44.101,
- "posY": 1.632,
- "posZ": 31.207,
+ "posX": -45.4,
+ "posY": 1.561,
+ "posZ": 28.25,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
diff --git a/objects/Connectionmarkers.5dcccb.json b/objects/Connectionmarkers.5dcccb.json
deleted file mode 100644
index 603d2fad..00000000
--- a/objects/Connectionmarkers.5dcccb.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "AltLookAngle": {
- "x": 0,
- "y": 0,
- "z": 0
- },
- "Autoraise": true,
- "ColorDiffuse": {
- "b": 1,
- "g": 1,
- "r": 1
- },
- "ContainedObjects_order": [
- "Custom_Tile.7234af"
- ],
- "ContainedObjects_path": "Connectionmarkers.5dcccb",
- "CustomMesh": {
- "CastShadows": true,
- "ColliderURL": "",
- "Convex": true,
- "DiffuseURL": "http://cloud-3.steamusercontent.com/ugc/949588657208009702/1786DA3A72B61BF39ADE9577B177797450011602/",
- "MaterialIndex": 3,
- "MeshURL": "https://pastebin.com/raw/ALrYhQGb",
- "NormalURL": "",
- "TypeIndex": 7
- },
- "Description": "",
- "DragSelectable": true,
- "GMNotes": "",
- "GUID": "5dcccb",
- "Grid": true,
- "GridProjection": false,
- "Hands": false,
- "HideWhenFaceDown": false,
- "IgnoreFoW": false,
- "LayoutGroupSortIndex": 0,
- "Locked": true,
- "LuaScript": "",
- "LuaScriptState": "",
- "MaterialIndex": -1,
- "MeasureMovement": false,
- "MeshIndex": -1,
- "Name": "Custom_Model_Infinite_Bag",
- "Nickname": "Connection markers",
- "Snap": true,
- "Sticky": true,
- "Tooltip": true,
- "Transform": {
- "posX": -44.559,
- "posY": 1.636,
- "posZ": -26.724,
- "rotX": 0,
- "rotY": 180,
- "rotZ": 0,
- "scaleX": 0.8,
- "scaleY": 1,
- "scaleZ": 0.8
- },
- "Value": 0,
- "XmlUI": ""
-}
diff --git a/objects/Connectionmarkers.b118af.json b/objects/Connectionmarkers.b118af.json
deleted file mode 100644
index 35dc1b27..00000000
--- a/objects/Connectionmarkers.b118af.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "AltLookAngle": {
- "x": 0,
- "y": 0,
- "z": 0
- },
- "Autoraise": true,
- "ColorDiffuse": {
- "b": 1,
- "g": 1,
- "r": 1
- },
- "ContainedObjects_order": [
- "Custom_Tile.7234af"
- ],
- "ContainedObjects_path": "Connectionmarkers.b118af",
- "CustomMesh": {
- "CastShadows": true,
- "ColliderURL": "",
- "Convex": true,
- "DiffuseURL": "http://cloud-3.steamusercontent.com/ugc/949588657208009702/1786DA3A72B61BF39ADE9577B177797450011602/",
- "MaterialIndex": 3,
- "MeshURL": "https://pastebin.com/raw/ALrYhQGb",
- "NormalURL": "",
- "TypeIndex": 7
- },
- "Description": "",
- "DragSelectable": true,
- "GMNotes": "",
- "GUID": "b118af",
- "Grid": true,
- "GridProjection": false,
- "Hands": false,
- "HideWhenFaceDown": false,
- "IgnoreFoW": false,
- "LayoutGroupSortIndex": 0,
- "Locked": true,
- "LuaScript": "",
- "LuaScriptState": "",
- "MaterialIndex": -1,
- "MeasureMovement": false,
- "MeshIndex": -1,
- "Name": "Custom_Model_Infinite_Bag",
- "Nickname": "Connection markers",
- "Snap": true,
- "Sticky": true,
- "Tooltip": true,
- "Transform": {
- "posX": -41.769,
- "posY": 1.648,
- "posZ": 26.75,
- "rotX": 0,
- "rotY": 0,
- "rotZ": 0,
- "scaleX": 0.8,
- "scaleY": 1,
- "scaleZ": 0.8
- },
- "Value": 0,
- "XmlUI": ""
-}
diff --git a/objects/Custom_PDF.d99993.json b/objects/Custom_PDF.d99993.json
index 6b2360be..5f0bb689 100644
--- a/objects/Custom_PDF.d99993.json
+++ b/objects/Custom_PDF.d99993.json
@@ -36,9 +36,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -60.574,
- "posY": 1.249,
- "posZ": 70.866,
+ "posX": -61,
+ "posY": 1.27,
+ "posZ": 74,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
diff --git a/objects/Damage.1f5a0a.json b/objects/Damage.1f5a0a.json
index b97d7fd8..38534af1 100644
--- a/objects/Damage.1f5a0a.json
+++ b/objects/Damage.1f5a0a.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -32.31,
+ "posX": -37.182,
"posY": 1.52,
- "posZ": 29.006,
+ "posZ": 29.089,
"rotX": 0,
- "rotY": 11,
+ "rotY": 10,
"rotZ": 1,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Damage.591a45.json b/objects/Damage.591a45.json
index 41937170..b1c402b9 100644
--- a/objects/Damage.591a45.json
+++ b/objects/Damage.591a45.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -18.706,
+ "posX": -23.497,
"posY": 1.52,
- "posZ": -29.027,
+ "posZ": -29.078,
"rotX": 0,
- "rotY": 195,
+ "rotY": 190,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Damage.e64eec.json b/objects/Damage.e64eec.json
index 1ea4da6b..99f89b60 100644
--- a/objects/Damage.e64eec.json
+++ b/objects/Damage.e64eec.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -57.512,
+ "posX": -57.507,
"posY": 1.52,
- "posZ": -22.921,
+ "posZ": -22.894,
"rotX": 0,
- "rotY": 285,
+ "rotY": 280,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Damage.eb08d6.json b/objects/Damage.eb08d6.json
index 3e461e08..39eab22d 100644
--- a/objects/Damage.eb08d6.json
+++ b/objects/Damage.eb08d6.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -57.488,
+ "posX": -57.472,
"posY": 1.52,
- "posZ": 9.184,
+ "posZ": 9.273,
"rotX": 0,
- "rotY": 282,
+ "rotY": 280,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/DamageTokens.93f4a0.json b/objects/DamageTokens.93f4a0.json
index 0218c699..07d4c640 100644
--- a/objects/DamageTokens.93f4a0.json
+++ b/objects/DamageTokens.93f4a0.json
@@ -56,9 +56,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -44.558,
- "posY": 1.635,
- "posZ": -28.959,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": -23.75,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
diff --git a/objects/DamageTokens.b0ef6c.json b/objects/DamageTokens.b0ef6c.json
index 18b00688..a1581f63 100644
--- a/objects/DamageTokens.b0ef6c.json
+++ b/objects/DamageTokens.b0ef6c.json
@@ -56,9 +56,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -41.77,
- "posY": 1.648,
- "posZ": 28.985,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": 23.75,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
diff --git a/objects/Detailedphasereference.68fe54.json b/objects/Detailedphasereference.68fe54.json
index a4e53dbe..c1acfc78 100644
--- a/objects/Detailedphasereference.68fe54.json
+++ b/objects/Detailedphasereference.68fe54.json
@@ -43,9 +43,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -60.384,
- "posY": 1.325,
- "posZ": 86.713,
+ "posX": -61,
+ "posY": 1.27,
+ "posZ": 89,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
diff --git a/objects/Doomtokens.16724b.json b/objects/Doomtokens.16724b.json
index 485ed82f..7cfbffa0 100644
--- a/objects/Doomtokens.16724b.json
+++ b/objects/Doomtokens.16724b.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -41.77,
- "posY": 1.649,
- "posZ": 31.207,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": 30.5,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
diff --git a/objects/Doomtokens.16fcd6.json b/objects/Doomtokens.16fcd6.json
index e2d3d901..a078b6b6 100644
--- a/objects/Doomtokens.16fcd6.json
+++ b/objects/Doomtokens.16fcd6.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -44.558,
- "posY": 1.634,
- "posZ": -31.182,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": -30.5,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
diff --git a/objects/Fan-MadeAccessories.aa8b38.json b/objects/Fan-MadeAccessories.aa8b38.json
index be4f769a..56d31505 100644
--- a/objects/Fan-MadeAccessories.aa8b38.json
+++ b/objects/Fan-MadeAccessories.aa8b38.json
@@ -17,7 +17,7 @@
"ChaosBagManager.023240",
"TokenArranger.022907",
"CYOACampaignGuides.e87ea2",
- "AttachmentHelper.d45664",
+ "AttachmentHelper.7f4976",
"ArkhamFantasy-PixelArtMini-Cards.e17c9e",
"jaqenZannsNavigationOverlay.a8affa",
"DrawTokenButtonTooltipRenamer.cc77a8",
diff --git a/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976.json b/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976.json
new file mode 100644
index 00000000..fd21c75a
--- /dev/null
+++ b/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976.json
@@ -0,0 +1,51 @@
+{
+ "AltLookAngle": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "Autoraise": true,
+ "ColorDiffuse": {
+ "b": 1,
+ "g": 0.37256,
+ "r": 0.30589
+ },
+ "ContainedObjects_order": [
+ "AttachmentHelper.d45664"
+ ],
+ "ContainedObjects_path": "AttachmentHelper.7f4976",
+ "Description": "Provides card-sized bags that are useful for cards that are attached facedown (e.g. Backpack).",
+ "DragSelectable": true,
+ "GMNotes": "",
+ "GUID": "7f4976",
+ "Grid": true,
+ "GridProjection": false,
+ "Hands": false,
+ "HideWhenFaceDown": false,
+ "IgnoreFoW": false,
+ "LayoutGroupSortIndex": 0,
+ "Locked": false,
+ "LuaScript": "",
+ "LuaScriptState": "",
+ "MaterialIndex": -1,
+ "MeasureMovement": false,
+ "MeshIndex": -1,
+ "Name": "Infinite_Bag",
+ "Nickname": "Attachment Helper",
+ "Snap": true,
+ "Sticky": true,
+ "Tooltip": true,
+ "Transform": {
+ "posX": 27.677,
+ "posY": 4.472,
+ "posZ": -31.034,
+ "rotX": 0,
+ "rotY": 0,
+ "rotZ": 0,
+ "scaleX": 1,
+ "scaleY": 1,
+ "scaleZ": 1
+ },
+ "Value": 0,
+ "XmlUI": ""
+}
diff --git a/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.json b/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.json
new file mode 100644
index 00000000..629ef166
--- /dev/null
+++ b/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.json
@@ -0,0 +1,65 @@
+{
+ "AltLookAngle": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "Autoraise": true,
+ "Bag": {
+ "Order": 0
+ },
+ "ColorDiffuse": {
+ "b": 1,
+ "g": 1,
+ "r": 1
+ },
+ "CustomMesh": {
+ "CastShadows": true,
+ "ColliderURL": "http://cloud-3.steamusercontent.com/ugc/1754695414379239413/0B8E68F3B7311DCF2138FB701F78D1D93FBA4CAB/",
+ "Convex": true,
+ "DiffuseURL": "http://cloud-3.steamusercontent.com/ugc/1750192233783143973/D526236AAE16BDBB98D3F30E27BAFC1D3E21F4AC/",
+ "MaterialIndex": 1,
+ "MeshURL": "http://cloud-3.steamusercontent.com/ugc/1754695414379239413/0B8E68F3B7311DCF2138FB701F78D1D93FBA4CAB/",
+ "NormalURL": "",
+ "TypeIndex": 6
+ },
+ "Description": "Drop cards here to display name, cost and skill icons.\n\nSee context menu for options.",
+ "DragSelectable": true,
+ "GMNotes": "",
+ "GUID": "d45664",
+ "Grid": true,
+ "GridProjection": false,
+ "Hands": false,
+ "HideWhenFaceDown": false,
+ "IgnoreFoW": false,
+ "LayoutGroupSortIndex": 0,
+ "Locked": false,
+ "LuaScriptState": "[[],true,true]",
+ "LuaScript_path": "Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.ttslua",
+ "MaterialIndex": -1,
+ "MeasureMovement": false,
+ "MeshIndex": -1,
+ "Name": "Custom_Model_Bag",
+ "Nickname": "Attachment Helper",
+ "Number": 0,
+ "Snap": true,
+ "Sticky": true,
+ "Tags": [
+ "Asset",
+ "scesetup_memory_object"
+ ],
+ "Tooltip": true,
+ "Transform": {
+ "posX": 19.228,
+ "posY": 3.822,
+ "posZ": -19.636,
+ "rotX": 0,
+ "rotY": 270,
+ "rotZ": 359,
+ "scaleX": 0.8,
+ "scaleY": 1,
+ "scaleZ": 0.8
+ },
+ "Value": 0,
+ "XmlUI": ""
+}
diff --git a/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.ttslua b/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.ttslua
new file mode 100644
index 00000000..de2c31a1
--- /dev/null
+++ b/objects/Fan-MadeAccessories.aa8b38/AttachmentHelper.7f4976/AttachmentHelper.d45664.ttslua
@@ -0,0 +1,202 @@
+local OPTION_TEXT = {
+ "Ancestral Knowledge",
+ "Astronomical Atlas",
+ "Crystallizer of Dreams",
+ "Diana Stanley",
+ "Gloria Goldberg",
+ "Sefina Rousseau",
+ "Wooden Sledge"
+}
+
+local IMAGE_LIST = {
+ -- Ancestral Knowledge
+ "http://cloud-3.steamusercontent.com/ugc/1915746489207287888/2F9F6F211ED0F98E66C9D35D93221E4C7FB6DD3C/",
+ -- Astronomical Atlas
+ "http://cloud-3.steamusercontent.com/ugc/1754695853007989004/9153BC204FC707AE564ECFAC063A11CB8C2B5D1E/",
+ -- Crystallizer of Dreams
+ "http://cloud-3.steamusercontent.com/ugc/1915746489207280958/100F16441939E5E23818651D1EB5C209BF3125B9/",
+ -- Diana Stanley
+ "http://cloud-3.steamusercontent.com/ugc/1754695635919071208/1AB7222850201630826BFFBA8F2BD0065E2D572F/",
+ -- Gloria Goldberg
+ "http://cloud-3.steamusercontent.com/ugc/1754695635919102502/453D4426118C8A6DE2EA281184716E26CA924C84/",
+ -- Sefina Rousseau
+ "http://cloud-3.steamusercontent.com/ugc/1754695635919099826/3C3CBFFAADB2ACA9957C736491F470AE906CC953/",
+ -- Wooden Sledge
+ "http://cloud-3.steamusercontent.com/ugc/1750192233783143973/D526236AAE16BDBB98D3F30E27BAFC1D3E21F4AC/"
+}
+
+-- save state and options to restore onLoad
+function onSave() return JSON.encode({ cardsInBag, showCost, showIcons }) end
+
+-- load variables and create context menu
+function onLoad(savedData)
+ local loadedData = JSON.decode(savedData)
+ cardsInBag = loadedData[1] or {}
+ showCost = loadedData[2] or true
+ showIcons = loadedData[3] or true
+
+ recreateButtons()
+
+ self.addContextMenuItem("Select image", selectImage)
+ self.addContextMenuItem("Toggle cost", function(color)
+ showCost = not showCost
+ printToColor("Show cost of cards: " .. tostring(showCost), color, "White")
+ refresh()
+ end)
+
+ self.addContextMenuItem("Toggle skill icons", function(color)
+ showIcons = not showIcons
+ printToColor("Show skill icons of cards: " .. tostring(showIcons), color, "White")
+ refresh()
+ end)
+
+ self.addContextMenuItem("More Information", function()
+ printToAll("------------------------------", "White")
+ printToAll("Attachment Helper by Chr1Z", "Orange")
+ printToAll("original by bankey", "White")
+ end)
+end
+
+function selectImage(color)
+ Player[color].showOptionsDialog("Select image:", OPTION_TEXT, 1, function(_, option_index)
+ local customInfo = self.getCustomObject()
+ customInfo.diffuse = IMAGE_LIST[option_index]
+ self.setCustomObject(customInfo)
+ self.reload()
+ end)
+end
+
+-- called for every card that enters
+function onObjectEnterContainer(container, object)
+ if container == self then
+ if object.tag ~= "Card" then
+ broadcastToAll("The 'Attachment Helper' is meant to be used for single cards.", "White")
+ else
+ findCard(object.getGUID(), object.getName(), object.getGMNotes())
+ end
+ -- TODO: implement splitting of decks that get thrown in here
+ recreateButtons()
+ end
+end
+
+-- removes leaving cards from the "cardInBag" table
+function onObjectLeaveContainer(container, object)
+ if container == self then
+ local guid = object.getGUID()
+ local found = false
+ for i, card in ipairs(cardsInBag) do
+ if card.id == guid then
+ table.remove(cardsInBag, i)
+ found = true
+ break
+ end
+ end
+
+ if found ~= true then
+ local name = object.getName()
+ for i, card in ipairs(cardsInBag) do
+ if card.name == name then
+ table.remove(cardsInBag, i)
+ break
+ end
+ end
+ end
+ recreateButtons()
+ end
+end
+
+-- refreshes displayed buttons based on contained cards
+function refresh()
+ cardsInBag = {}
+ for _, object in ipairs(self.getObjects()) do
+ findCard(object.guid, object.name, object.gm_notes)
+ end
+ recreateButtons()
+end
+
+-- gets cost and icons for a card
+function findCard(guid, name, GMNotes)
+ local cost = ""
+ local icons = {}
+ local metadata = {}
+ local displayName = name
+
+ if displayName == nil or displayName == "" then displayName = "unnamed" end
+ if showCost or showIcons then metadata = JSON.decode(GMNotes) end
+
+ if showCost then
+ if GMNotes ~= "" then cost = metadata.cost end
+ if cost == nil or cost == "" then cost = "–" end
+ displayName = "[" .. cost .. "] " .. displayName
+ end
+
+ if showIcons then
+ if GMNotes ~= "" then
+ icons[1] = metadata.wildIcons
+ icons[2] = metadata.willpowerIcons
+ icons[3] = metadata.intellectIcons
+ icons[4] = metadata.combatIcons
+ icons[5] = metadata.agilityIcons
+ end
+
+ local IconTypes = { "Wild", "Willpower", "Intellect", "Combat", "Agility" }
+ local found = false
+ for i = 1, 5 do
+ if icons[i] ~= nil and icons[i] ~= "" then
+ if found == false then
+ displayName = displayName .. "\n" .. IconTypes[i] .. ": " .. icons[i]
+ found = true
+ else
+ displayName = displayName .. " " .. IconTypes[i] .. ": " .. icons[i]
+ end
+ end
+ end
+ end
+ table.insert(cardsInBag, { name = name, displayName = displayName, id = guid })
+end
+
+-- recreates buttons with up-to-date labels
+function recreateButtons()
+ self.clearButtons()
+ local verticalPosition = 1.65
+
+ for _, card in ipairs(cardsInBag) do
+ local id = card.id
+ local funcName = "removeCard" .. id
+ self.setVar(funcName, function() removeCard(id) end)
+ self.createButton({
+ label = card.displayName,
+ click_function = funcName,
+ function_owner = self,
+ position = { 0, 0, verticalPosition },
+ height = 200,
+ width = 1200,
+ font_size = string.len(card.displayName) > 20 and 75 or 100
+ })
+ verticalPosition = verticalPosition - 0.5
+ end
+
+ local countLabel = "Attachment\nHelper"
+ if #cardsInBag ~= 0 then countLabel = #cardsInBag end
+
+ self.createButton({
+ label = countLabel,
+ click_function = "none",
+ function_owner = self,
+ position = { 0, 0, -1.35 },
+ height = 0,
+ width = 0,
+ font_size = 225,
+ font_color = { 1, 1, 1 }
+ })
+end
+
+-- click-function for buttons to take a card out of the bag
+function removeCard(cardGUID)
+ self.takeObject({
+ guid = cardGUID,
+ rotation = self.getRotation(),
+ position = self.getPosition() + Vector(0, 0.25, 0),
+ callback_function = function(obj) obj.resting = true end
+ })
+end
diff --git a/objects/Fan-MadeAccessories.aa8b38/DisplacementTool.0f1374.json b/objects/Fan-MadeAccessories.aa8b38/DisplacementTool.0f1374.json
index c3fddf97..c8011877 100644
--- a/objects/Fan-MadeAccessories.aa8b38/DisplacementTool.0f1374.json
+++ b/objects/Fan-MadeAccessories.aa8b38/DisplacementTool.0f1374.json
@@ -41,6 +41,7 @@
"Snap": true,
"Sticky": true,
"Tags": [
+ "CleanUpHelper_ignore",
"displacement_excluded"
],
"Tooltip": true,
diff --git a/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json b/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json
index 9e917d47..c1137ca5 100644
--- a/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json
+++ b/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.json
@@ -40,6 +40,10 @@
"Nickname": "Hand Helper",
"Snap": true,
"Sticky": true,
+ "Tags": [
+ "CleanUpHelper_ignore",
+ "displacement_excluded"
+ ],
"Tooltip": true,
"Transform": {
"posX": 37.613,
diff --git a/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.ttslua b/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.ttslua
index c186124a..3204c68f 100644
--- a/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.ttslua
+++ b/objects/Fan-MadeAccessories.aa8b38/HandHelper.450688.ttslua
@@ -1,7 +1,7 @@
-MAT_GUIDS = { "8b081b", "bd0ff4", "383d8b", "0840d5" }
+local playmatAPI = require("playermat/PlaymatApi")
-local BUTTON_PARAMETERS = {}
-BUTTON_PARAMETERS.function_owner = self
+local buttonParamaters = {}
+buttonParamaters.function_owner = self
-- saving "playerColor" and "des"
function onSave() return JSON.encode({ playerColor, des}) end
@@ -13,41 +13,41 @@ function onLoad(saved_data)
des = loaded_data[2] or false
-- index 0: button as hand size label
- BUTTON_PARAMETERS.hover_color = "White"
- BUTTON_PARAMETERS.click_function = "none"
- BUTTON_PARAMETERS.position = { 0, 0.1, -0.4 }
- BUTTON_PARAMETERS.height = 0
- BUTTON_PARAMETERS.width = 0
- BUTTON_PARAMETERS.font_size = 500
- BUTTON_PARAMETERS.font_color = "White"
- self.createButton(BUTTON_PARAMETERS)
+ 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 1: button to toggle "des"
- BUTTON_PARAMETERS.label = "DES: " .. (des and "✓" or "✗")
- BUTTON_PARAMETERS.click_function = "toggleDES"
- BUTTON_PARAMETERS.position = { 0.475, 0.1, 0.25 }
- BUTTON_PARAMETERS.height = 175
- BUTTON_PARAMETERS.width = 440
- BUTTON_PARAMETERS.font_size = 90
- BUTTON_PARAMETERS.font_color = "Black"
- self.createButton(BUTTON_PARAMETERS)
+ 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)
-- index 2: button to discard a card
- BUTTON_PARAMETERS.label = "discard random card"
- BUTTON_PARAMETERS.click_function = "discardRandom"
- BUTTON_PARAMETERS.position = { 0, 0.1, 0.7 }
- BUTTON_PARAMETERS.width = 900
- self.createButton(BUTTON_PARAMETERS)
+ 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
- BUTTON_PARAMETERS.label = playerColor
- BUTTON_PARAMETERS.color = playerColor
- BUTTON_PARAMETERS.hover_color = playerColor
- BUTTON_PARAMETERS.click_function = "changeColor"
- BUTTON_PARAMETERS.tooltip = "change color"
- BUTTON_PARAMETERS.position = { -0.475, 0.1, 0.25 }
- BUTTON_PARAMETERS.width = 440
- self.createButton(BUTTON_PARAMETERS)
+ 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)
@@ -71,14 +71,6 @@ function onLoad(saved_data)
end
function onObjectHover(hover_color, obj)
- -- error handling
- if obj == nil then return end
-
- -- add context menu to "short supply"
- if obj.getName() == "Short Supply" then
- obj.addContextMenuItem("Discard 10 (" .. playerColor .. ")", shortSupply)
- end
-
-- only continue if correct player hovers over "self"
if obj ~= self or hover_color ~= playerColor then return end
@@ -151,11 +143,11 @@ function changeColor(_, _, isRightClick, color)
end
-- update "change color" button (note: remove and create instantly updates hover_color)
- BUTTON_PARAMETERS.label = playerColor
- BUTTON_PARAMETERS.color = playerColor
- BUTTON_PARAMETERS.hover_color = playerColor
+ buttonParamaters.label = playerColor
+ buttonParamaters.color = playerColor
+ buttonParamaters.hover_color = playerColor
self.removeButton(3)
- self.createButton(BUTTON_PARAMETERS)
+ self.createButton(buttonParamaters)
end
---------------------------------------------------------
@@ -169,7 +161,8 @@ function discardRandom()
if #hand == 0 then
broadcastToAll("Cannot discard from empty hand!", "Red")
else
- local mat = getPlayermat(playerColor)
+ local searchPos = Player[playerColor].getHandTransform().position
+ local mat = playmatAPI.getMatbyPosition(searchPos)
if mat == nil then return end
local discardPos = mat.getTable("DISCARD_PILE_POSITION")
@@ -184,38 +177,6 @@ function discardRandom()
end
end
----------------------------------------------------------
--- discards the top 10 cards of your deck
----------------------------------------------------------
-function shortSupply(color)
- local mat = getPlayermat(playerColor)
- if mat == nil then return end
-
- -- get draw deck and discard pile
- mat.call("getDrawDiscardDecks")
- drawDeck = mat.getVar("drawDeck")
- local discardPos = mat.getTable("DISCARD_PILE_POSITION")
- if discardPos == nil then
- broadcastToAll("Couldn't retrieve discard position from playermat!", "Red")
- return
- end
-
- if drawDeck == nil then
- broadcastToColor("Deck not found!", color, "Yellow")
- return
- elseif drawDeck.tag ~= "Deck" then
- broadcastToColor("Deck only contains a single card!", color, "Yellow")
- return
- end
-
- -- discard cards
- discardPos[2] = 0.5
- for i = 1, 10 do
- discardPos[2] = discardPos[2] + 0.05 * i
- drawDeck.takeObject({ flip = true; position = discardPos })
- end
-end
-
---------------------------------------------------------
-- helper functions
---------------------------------------------------------
@@ -232,27 +193,3 @@ function playerExists(color)
local COLORS = Player.getAvailableColors()
return indexOf(COLORS, color) and true or false
end
-
--- helper to find playermat based on hand position
-function getPlayermat(color)
- local pos = Player[playerColor].getHandTransform().position
- if pos.x < -30 then
- if pos.z > 0 then
- playerNumber = 1
- else
- playerNumber = 2
- end
- else
- if pos.z > 0 then
- playerNumber = 3
- else
- playerNumber = 4
- end
- end
-
- local mat = getObjectFromGUID(MAT_GUIDS[playerNumber])
- if mat == nil then
- broadcastToAll(playerColor .. " playermat could not be found!", "Yellow")
- end
- return mat
-end
diff --git a/objects/HandTrigger.0285cc.json b/objects/HandTrigger.0285cc.json
index 97fced68..d8eff7f5 100644
--- a/objects/HandTrigger.0285cc.json
+++ b/objects/HandTrigger.0285cc.json
@@ -32,7 +32,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -27.96,
+ "posX": -30.5,
"posY": 6,
"posZ": 36.053,
"rotX": 0,
diff --git a/objects/HandTrigger.5fe087.json b/objects/HandTrigger.5fe087.json
index cf44b919..0ea988e6 100644
--- a/objects/HandTrigger.5fe087.json
+++ b/objects/HandTrigger.5fe087.json
@@ -32,9 +32,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -65.72,
+ "posX": -65.7,
"posY": 6,
- "posZ": -13.61,
+ "posZ": -15.5,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
diff --git a/objects/HandTrigger.a70eee.json b/objects/HandTrigger.a70eee.json
index 6f58ee22..9fcb112a 100644
--- a/objects/HandTrigger.a70eee.json
+++ b/objects/HandTrigger.a70eee.json
@@ -32,9 +32,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -65.581,
+ "posX": -65.7,
"posY": 6,
- "posZ": 13.55,
+ "posZ": 15.5,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
diff --git a/objects/HandTrigger.be2f17.json b/objects/HandTrigger.be2f17.json
index 80a19e14..ac40365c 100644
--- a/objects/HandTrigger.be2f17.json
+++ b/objects/HandTrigger.be2f17.json
@@ -32,7 +32,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -27.96,
+ "posX": -30.5,
"posY": 6,
"posZ": -36.364,
"rotX": 0,
diff --git a/objects/Horror.0257d9.json b/objects/Horror.0257d9.json
index 6a9ab31a..f933c101 100644
--- a/objects/Horror.0257d9.json
+++ b/objects/Horror.0257d9.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -57.882,
+ "posX": -57.887,
"posY": 1.52,
- "posZ": -24.902,
+ "posZ": -24.928,
"rotX": 0,
- "rotY": 285,
+ "rotY": 280,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Horror.468e88.json b/objects/Horror.468e88.json
index cdda908d..b6a6cb0b 100644
--- a/objects/Horror.468e88.json
+++ b/objects/Horror.468e88.json
@@ -42,9 +42,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -57.837,
+ "posX": -57.83,
"posY": 1.52,
- "posZ": 7.19,
+ "posZ": 7.229,
"rotX": 0,
"rotY": 280,
"rotZ": 0,
diff --git a/objects/Horror.7b5729.json b/objects/Horror.7b5729.json
index ba06284c..973828d6 100644
--- a/objects/Horror.7b5729.json
+++ b/objects/Horror.7b5729.json
@@ -42,9 +42,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -34.234,
- "posY": 1.52,
- "posZ": 29.394,
+ "posX": -39.163,
+ "posY": 1.519,
+ "posZ": 29.487,
"rotX": 0,
"rotY": 10,
"rotZ": 1,
diff --git a/objects/Horror.beb964.json b/objects/Horror.beb964.json
index 39d68205..9564ddac 100644
--- a/objects/Horror.beb964.json
+++ b/objects/Horror.beb964.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -16.653,
+ "posX": -21.469,
"posY": 1.52,
- "posZ": -29.429,
+ "posZ": -29.42,
"rotX": 0,
- "rotY": 195,
+ "rotY": 190,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Horrortokens.7bd2a0.json b/objects/Horrortokens.7bd2a0.json
index 1f2cf8a1..4cc4205d 100644
--- a/objects/Horrortokens.7bd2a0.json
+++ b/objects/Horrortokens.7bd2a0.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -42.227,
- "posY": 1.632,
- "posZ": -28.959,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": -26,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
diff --git a/objects/Horrortokens.ae1a4e.json b/objects/Horrortokens.ae1a4e.json
index 01e6240f..a3630870 100644
--- a/objects/Horrortokens.ae1a4e.json
+++ b/objects/Horrortokens.ae1a4e.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -44.101,
- "posY": 1.652,
- "posZ": 28.985,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": 26,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
diff --git a/objects/InvestigatorSkillTracker.af7ed7.json b/objects/InvestigatorSkillTracker.af7ed7.json
index b750e89c..8e320a3d 100644
--- a/objects/InvestigatorSkillTracker.af7ed7.json
+++ b/objects/InvestigatorSkillTracker.af7ed7.json
@@ -46,7 +46,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -32.6,
+ "posX": -37.35,
"posY": 1.531,
"posZ": 19.35,
"rotX": 0,
diff --git a/objects/InvestigatorSkillTracker.b4a5f7.json b/objects/InvestigatorSkillTracker.b4a5f7.json
index c1b575f9..cbf2123d 100644
--- a/objects/InvestigatorSkillTracker.b4a5f7.json
+++ b/objects/InvestigatorSkillTracker.b4a5f7.json
@@ -46,7 +46,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -47.76,
+ "posX": -47.75,
"posY": 1.531,
"posZ": -23.1,
"rotX": 0,
diff --git a/objects/InvestigatorSkillTracker.e74881.json b/objects/InvestigatorSkillTracker.e74881.json
index d6643109..aad443c7 100644
--- a/objects/InvestigatorSkillTracker.e74881.json
+++ b/objects/InvestigatorSkillTracker.e74881.json
@@ -46,7 +46,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -18.6,
+ "posX": -23.35,
"posY": 1.531,
"posZ": -19.35,
"rotX": 0,
diff --git a/objects/LolaNeutral.460250.json b/objects/LolaNeutral.460250.json
index 21d24b63..9732283f 100644
--- a/objects/LolaNeutral.460250.json
+++ b/objects/LolaNeutral.460250.json
@@ -1157,7 +1157,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -19.17,
+ "posX": -23.92,
"posY": 1.55,
"posZ": -24.845,
"rotX": 0,
diff --git a/objects/LolaNeutral.7ffb43.json b/objects/LolaNeutral.7ffb43.json
index f87b6f78..1b8e0eee 100644
--- a/objects/LolaNeutral.7ffb43.json
+++ b/objects/LolaNeutral.7ffb43.json
@@ -1157,7 +1157,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -34.293,
+ "posX": -39.043,
"posY": 1.55,
"posZ": 24.864,
"rotX": 0,
diff --git a/objects/LolaNeutral.a1e2a3.json b/objects/LolaNeutral.a1e2a3.json
index cedc49b2..90ee3bcf 100644
--- a/objects/LolaNeutral.a1e2a3.json
+++ b/objects/LolaNeutral.a1e2a3.json
@@ -1157,7 +1157,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -18.025,
+ "posX": -22.776,
"posY": 1.55,
"posZ": -24.845,
"rotX": 0,
diff --git a/objects/LolaNeutral.a7c0f0.json b/objects/LolaNeutral.a7c0f0.json
index 9b55d8b9..9c7a4e9f 100644
--- a/objects/LolaNeutral.a7c0f0.json
+++ b/objects/LolaNeutral.a7c0f0.json
@@ -1157,9 +1157,9 @@
],
"Tooltip": true,
"Transform": {
- "posX": -20.049,
+ "posX": -24.91,
"posY": 1.55,
- "posZ": -24.78,
+ "posZ": -24.84,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
diff --git a/objects/LolaNeutral.b8409d.json b/objects/LolaNeutral.b8409d.json
index e01f8186..af6b8e3f 100644
--- a/objects/LolaNeutral.b8409d.json
+++ b/objects/LolaNeutral.b8409d.json
@@ -1157,7 +1157,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -32.011,
+ "posX": -36.761,
"posY": 1.55,
"posZ": 24.864,
"rotX": 0,
diff --git a/objects/LolaNeutral.bf7cc9.json b/objects/LolaNeutral.bf7cc9.json
index 4d221ec0..ccd541ff 100644
--- a/objects/LolaNeutral.bf7cc9.json
+++ b/objects/LolaNeutral.bf7cc9.json
@@ -1157,9 +1157,9 @@
],
"Tooltip": true,
"Transform": {
- "posX": -30.997,
+ "posX": -35.77,
"posY": 1.55,
- "posZ": 24.81,
+ "posZ": 24.86,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
diff --git a/objects/LolaNeutral.bfcaf4.json b/objects/LolaNeutral.bfcaf4.json
index b0b1021b..9990e4b2 100644
--- a/objects/LolaNeutral.bfcaf4.json
+++ b/objects/LolaNeutral.bfcaf4.json
@@ -1157,7 +1157,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -16.887,
+ "posX": -21.638,
"posY": 1.55,
"posZ": -24.845,
"rotX": 0,
diff --git a/objects/LolaNeutral.d7910b.json b/objects/LolaNeutral.d7910b.json
index 91f903e4..c937b1ad 100644
--- a/objects/LolaNeutral.d7910b.json
+++ b/objects/LolaNeutral.d7910b.json
@@ -1157,7 +1157,7 @@
],
"Tooltip": true,
"Transform": {
- "posX": -33.149,
+ "posX": -37.899,
"posY": 1.55,
"posZ": 24.864,
"rotX": 0,
diff --git a/objects/LuaScriptState.luascriptstate b/objects/LuaScriptState.luascriptstate
index 56e3e6ae..ce0cb518 100644
--- a/objects/LuaScriptState.luascriptstate
+++ b/objects/LuaScriptState.luascriptstate
@@ -1 +1 @@
-{"optionPanel":{"showAttachmentHelper":false,"showChaosBagManager":false,"showCleanUpHelper":false,"showCustomPlaymatImages":false,"showCYOA":false,"showDisplacementTool":false,"showDrawButton":false,"showHandHelper":[],"showNavigationOverlay":false,"showTokenArranger":false,"useClueClickers":false,"useSnapTags":true}}
+{"optionPanel":{"showAttachmentHelper":false,"showChaosBagManager":false,"showCleanUpHelper":false,"useClueClickers":false,"showCustomPlaymatImages":false,"showCYOA":false,"showDisplacementTool":false,"showDrawButton":false,"showHandHelper":[],"showNavigationOverlay":false,"useSnapTags":true,"showTitleSplash":true,"showTokenArranger":false}}
diff --git a/objects/PlayerCards.2d30ee.json b/objects/PlayerCards.2d30ee.json
new file mode 100644
index 00000000..8bea32fe
--- /dev/null
+++ b/objects/PlayerCards.2d30ee.json
@@ -0,0 +1,57 @@
+{
+ "AltLookAngle": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "Autoraise": true,
+ "ColorDiffuse": {
+ "b": 1,
+ "g": 1,
+ "r": 1
+ },
+ "CustomImage": {
+ "CustomTile": {
+ "Stackable": false,
+ "Stretch": true,
+ "Thickness": 0.1,
+ "Type": 3
+ },
+ "ImageScalar": 1,
+ "ImageSecondaryURL": "https://i.imgur.com/dISlnEk.jpg",
+ "ImageURL": "https://i.imgur.com/dISlnEk.jpg",
+ "WidthScale": 0
+ },
+ "Description": "",
+ "DragSelectable": true,
+ "GMNotes": "",
+ "GUID": "2d30ee",
+ "Grid": true,
+ "GridProjection": false,
+ "Hands": false,
+ "HideWhenFaceDown": false,
+ "IgnoreFoW": false,
+ "LayoutGroupSortIndex": 0,
+ "Locked": false,
+ "LuaScript": "require(\"playercards/PlayerCardPanel\")",
+ "LuaScriptState": "{\"spawnBagState\":{\"placed\":[],\"placedObjects\":[]}}",
+ "MeasureMovement": false,
+ "Name": "Custom_Tile",
+ "Nickname": "Player Cards",
+ "Snap": true,
+ "Sticky": true,
+ "Tooltip": false,
+ "Transform": {
+ "posX": -1.083,
+ "posY": 1.255,
+ "posZ": 69.985,
+ "rotX": 0,
+ "rotY": 270,
+ "rotZ": 0,
+ "scaleX": 9.39,
+ "scaleY": 1,
+ "scaleZ": 9.39
+ },
+ "Value": 0,
+ "XmlUI": ""
+}
diff --git a/objects/Playermat3Green.383d8b.json b/objects/Playermat3Green.383d8b.json
index dae537d8..bfe1d4af 100644
--- a/objects/Playermat3Green.383d8b.json
+++ b/objects/Playermat3Green.383d8b.json
@@ -145,30 +145,6 @@
"Asset"
]
},
- {
- "Position": {
- "x": 1.37,
- "y": 0.1,
- "z": -0.637
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "Position": {
- "x": 0.914,
- "y": 0.1,
- "z": -0.637
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
{
"Position": {
"x": -1.817,
@@ -282,7 +258,7 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -25.6,
+ "posX": -30.35,
"posY": 1.45,
"posZ": 26.6,
"rotX": 0,
diff --git a/objects/Playermat4Red.0840d5.json b/objects/Playermat4Red.0840d5.json
index bb8fbfac..52126c46 100644
--- a/objects/Playermat4Red.0840d5.json
+++ b/objects/Playermat4Red.0840d5.json
@@ -179,30 +179,6 @@
"z": 0
}
},
- {
- "Position": {
- "x": 0.911,
- "y": 0.1,
- "z": -0.641
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "Position": {
- "x": 1.367,
- "y": 0.1,
- "z": -0.641
- },
- "Rotation": {
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
{
"Position": {
"x": -0.982,
@@ -282,7 +258,7 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -25.6,
+ "posX": -30.35,
"posY": 1.45,
"posZ": -26.6,
"rotX": 0,
diff --git a/objects/Resources.4406f0.json b/objects/Resources.4406f0.json
index d3ca7508..d0ad1b7c 100644
--- a/objects/Resources.4406f0.json
+++ b/objects/Resources.4406f0.json
@@ -42,9 +42,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -59.774,
+ "posX": -59.817,
"posY": 1.52,
- "posZ": 7.535,
+ "posZ": 7.617,
"rotX": 0,
"rotY": 280,
"rotZ": 0,
diff --git a/objects/Resources.816d84.json b/objects/Resources.816d84.json
index d95532fe..de7dc10a 100644
--- a/objects/Resources.816d84.json
+++ b/objects/Resources.816d84.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -59.793,
+ "posX": -59.798,
"posY": 1.52,
- "posZ": -24.544,
+ "posZ": -24.571,
"rotX": 0,
- "rotY": 285,
+ "rotY": 280,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Resources.a4b60d.json b/objects/Resources.a4b60d.json
index bfbec567..16acd881 100644
--- a/objects/Resources.a4b60d.json
+++ b/objects/Resources.a4b60d.json
@@ -42,11 +42,11 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -17.037,
+ "posX": -21.914,
"posY": 1.52,
- "posZ": -31.384,
+ "posZ": -31.433,
"rotX": 0,
- "rotY": 195,
+ "rotY": 190,
"rotZ": 0,
"scaleX": 0.26,
"scaleY": 1,
diff --git a/objects/Resources.cd15ac.json b/objects/Resources.cd15ac.json
index d232153d..d87f1ff2 100644
--- a/objects/Resources.cd15ac.json
+++ b/objects/Resources.cd15ac.json
@@ -42,9 +42,9 @@
"Sticky": true,
"Tooltip": false,
"Transform": {
- "posX": -33.889,
+ "posX": -38.812,
"posY": 1.52,
- "posZ": 31.335,
+ "posZ": 31.434,
"rotX": 0,
"rotY": 10,
"rotZ": 0,
diff --git a/objects/Resourcetokens.0168ae.json b/objects/Resourcetokens.0168ae.json
index 27fe1446..b8a1b238 100644
--- a/objects/Resourcetokens.0168ae.json
+++ b/objects/Resourcetokens.0168ae.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -44.105,
- "posY": 1.651,
- "posZ": 26.75,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": 21.5,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
diff --git a/objects/Resourcetokens.fd617a.json b/objects/Resourcetokens.fd617a.json
index 5d83bd6b..5aacb91e 100644
--- a/objects/Resourcetokens.fd617a.json
+++ b/objects/Resourcetokens.fd617a.json
@@ -46,9 +46,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -42.223,
- "posY": 1.633,
- "posZ": -26.724,
+ "posX": -45.4,
+ "posY": 1.581,
+ "posZ": -21.5,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
diff --git a/objects/Custom_Tile.2eca7c.json b/objects/RoundSequenceActionDescription.2eca7c.json
similarity index 90%
rename from objects/Custom_Tile.2eca7c.json
rename to objects/RoundSequenceActionDescription.2eca7c.json
index 2aa56b96..0ccd6c89 100644
--- a/objects/Custom_Tile.2eca7c.json
+++ b/objects/RoundSequenceActionDescription.2eca7c.json
@@ -37,14 +37,14 @@
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Custom_Tile",
- "Nickname": "",
+ "Nickname": "Round Sequence / Action Description",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -43.21,
- "posY": 1.55,
- "posZ": 22.5,
+ "posX": -57,
+ "posY": 1.255,
+ "posZ": 48,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
diff --git a/objects/Custom_Tile.fb09d4.json b/objects/RoundSequenceActionDescription.fb09d4.json
similarity index 90%
rename from objects/Custom_Tile.fb09d4.json
rename to objects/RoundSequenceActionDescription.fb09d4.json
index b9c0f17f..c78e8b51 100644
--- a/objects/Custom_Tile.fb09d4.json
+++ b/objects/RoundSequenceActionDescription.fb09d4.json
@@ -37,14 +37,14 @@
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Custom_Tile",
- "Nickname": "",
+ "Nickname": "Round Sequence / Action Description",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -43.21,
- "posY": 1.55,
- "posZ": -22.5,
+ "posX": -65,
+ "posY": 1.355,
+ "posZ": 48,
"rotX": 0,
"rotY": 270,
"rotZ": 180,
diff --git a/objects/RulesIndex.91c83e.json b/objects/RulesIndex.91c83e.json
index bbbf924f..58e9b8fe 100644
--- a/objects/RulesIndex.91c83e.json
+++ b/objects/RulesIndex.91c83e.json
@@ -43,9 +43,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -60.748,
- "posY": 1.316,
- "posZ": 54.855,
+ "posX": -61,
+ "posY": 1.27,
+ "posZ": 59,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
diff --git a/objects/ScriptingTrigger.18538f.json b/objects/ScriptingTrigger.18538f.json
index 03a8eebc..8733e093 100644
--- a/objects/ScriptingTrigger.18538f.json
+++ b/objects/ScriptingTrigger.18538f.json
@@ -31,7 +31,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -14.05,
+ "posX": -18.8,
"posY": 1.481,
"posZ": -28.6,
"rotX": 0,
diff --git a/objects/ScriptingTrigger.57c22c.json b/objects/ScriptingTrigger.57c22c.json
index fde6e563..a152fd4c 100644
--- a/objects/ScriptingTrigger.57c22c.json
+++ b/objects/ScriptingTrigger.57c22c.json
@@ -31,7 +31,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -27.977,
+ "posX": -30.5,
"posY": 4.076,
"posZ": -37.889,
"rotX": 0,
diff --git a/objects/ScriptingTrigger.67ce9a.json b/objects/ScriptingTrigger.67ce9a.json
index 126a01bc..5a3529d7 100644
--- a/objects/ScriptingTrigger.67ce9a.json
+++ b/objects/ScriptingTrigger.67ce9a.json
@@ -31,7 +31,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -28.046,
+ "posX": -30.5,
"posY": 4.065,
"posZ": 37.669,
"rotX": 0,
diff --git a/objects/ScriptingTrigger.c506bf.json b/objects/ScriptingTrigger.c506bf.json
index 695081e6..19c5df83 100644
--- a/objects/ScriptingTrigger.c506bf.json
+++ b/objects/ScriptingTrigger.c506bf.json
@@ -33,7 +33,7 @@
"Transform": {
"posX": -66.804,
"posY": 4.135,
- "posZ": 13.565,
+ "posZ": 15.5,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
diff --git a/objects/ScriptingTrigger.cbc751.json b/objects/ScriptingTrigger.cbc751.json
index 83b2850d..679e59d0 100644
--- a/objects/ScriptingTrigger.cbc751.json
+++ b/objects/ScriptingTrigger.cbc751.json
@@ -33,7 +33,7 @@
"Transform": {
"posX": -66.963,
"posY": 4.117,
- "posZ": -13.277,
+ "posZ": -15.5,
"rotX": 0,
"rotY": 90,
"rotZ": 0,
diff --git a/objects/ScriptingTrigger.fb28e1.json b/objects/ScriptingTrigger.fb28e1.json
index f0d9ec82..24cb97b9 100644
--- a/objects/ScriptingTrigger.fb28e1.json
+++ b/objects/ScriptingTrigger.fb28e1.json
@@ -31,7 +31,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -37.15,
+ "posX": -41.9,
"posY": 1.468,
"posZ": 28.6,
"rotX": 0,
diff --git a/objects/TokenSource.124381.json b/objects/TokenSource.124381.json
index 0f953b34..9b468f66 100644
--- a/objects/TokenSource.124381.json
+++ b/objects/TokenSource.124381.json
@@ -18,7 +18,7 @@
"Damage.cd2a02",
"Horror.36be72",
"ClueDoom.a3fb6c",
- "Reesource.00d19a"
+ "Resource.00d19a"
],
"ContainedObjects_path": "TokenSource.124381",
"Description": "",
diff --git a/objects/TokenSource.124381/ClueDoom.a3fb6c.json b/objects/TokenSource.124381/ClueDoom.a3fb6c.json
index 48b61ac0..3e32ac21 100644
--- a/objects/TokenSource.124381/ClueDoom.a3fb6c.json
+++ b/objects/TokenSource.124381/ClueDoom.a3fb6c.json
@@ -40,7 +40,7 @@
"Nickname": "ClueDoom",
"Snap": false,
"Sticky": true,
- "Tooltip": false,
+ "Tooltip": true,
"Transform": {
"posX": 78.661,
"posY": 2.398,
diff --git a/objects/TokenSource.124381/ClueDoom.a40a48.json b/objects/TokenSource.124381/ClueDoom.a40a48.json
index 9720c15b..c501cd2f 100644
--- a/objects/TokenSource.124381/ClueDoom.a40a48.json
+++ b/objects/TokenSource.124381/ClueDoom.a40a48.json
@@ -40,7 +40,7 @@
"Nickname": "ClueDoom",
"Snap": false,
"Sticky": true,
- "Tooltip": false,
+ "Tooltip": true,
"Transform": {
"posX": 78.738,
"posY": 2.287,
diff --git a/objects/TokenSource.124381/Reesource.00d19a.json b/objects/TokenSource.124381/Resource.00d19a.json
similarity index 95%
rename from objects/TokenSource.124381/Reesource.00d19a.json
rename to objects/TokenSource.124381/Resource.00d19a.json
index 74817ed2..0d1c5508 100644
--- a/objects/TokenSource.124381/Reesource.00d19a.json
+++ b/objects/TokenSource.124381/Resource.00d19a.json
@@ -37,10 +37,10 @@
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Custom_Token",
- "Nickname": "Reesource",
+ "Nickname": "Resource",
"Snap": false,
"Sticky": true,
- "Tooltip": false,
+ "Tooltip": true,
"Transform": {
"posX": 78.848,
"posY": 2.273,
diff --git a/objects/Trash.4b8594.json b/objects/Trash.4b8594.json
index ac64bcfc..f93d73c2 100644
--- a/objects/Trash.4b8594.json
+++ b/objects/Trash.4b8594.json
@@ -46,7 +46,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -37.497,
+ "posX": -42.25,
"posY": 1.653,
"posZ": -19.3,
"rotX": 0,
diff --git a/objects/Trash.5f896a.json b/objects/Trash.5f896a.json
index f29269ec..9de436ed 100644
--- a/objects/Trash.5f896a.json
+++ b/objects/Trash.5f896a.json
@@ -46,7 +46,7 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
- "posX": -37.5,
+ "posX": -42.25,
"posY": 1.664,
"posZ": 19.3,
"rotX": 0,
diff --git a/src/arkhamdb/ArkhamDb.ttslua b/src/arkhamdb/ArkhamDb.ttslua
index 63ed2ffe..97f853f0 100644
--- a/src/arkhamdb/ArkhamDb.ttslua
+++ b/src/arkhamdb/ArkhamDb.ttslua
@@ -134,15 +134,6 @@ do
internal.maybePrint(table.concat({ "Found decklist: ", deck.name }), playerColor)
- log(table.concat({ "-", deck.name, "-" }))
- for k, v in pairs(deck) do
- if type(v) == "table" then
- log(table.concat { k, ":
" })
- else
- log(table.concat { k, ": ", tostring(v) })
- end
- end
-
-- Initialize deck slot table and perform common transformations. The order of these should not
-- be changed, as later steps may act on cards added in each. For example, a random weakness or
-- investigator may have bonded cards or taboo entries, and should be present
diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua
index cee8d2a8..d3fa9a8e 100644
--- a/src/core/Global.ttslua
+++ b/src/core/Global.ttslua
@@ -34,6 +34,7 @@ local chaosTokens = {}
local chaosTokensLastMat = nil
local IS_RESHUFFLING = false
local bagSearchers = {}
+local hideTitleSplashWaitFunctionId = nil
local playmatAPI = require("playermat/PlaymatApi")
---------------------------------------------------------
@@ -818,6 +819,10 @@ function applyOptionPanelChange(id, state)
-- update master clue counter
getObjectFromGUID("4a3aa4").setVar("useClickableCounters", state)
+ -- option: Show Title on placing scenarios
+ elseif id == "showTitleSplash" then
+ optionPanel[id] = state
+
-- option: Show token arranger
elseif id == "showTokenArranger" then
-- delete previously pulled out tokens
@@ -833,8 +838,8 @@ function applyOptionPanelChange(id, state)
elseif id == "showHandHelper" then
optionPanel[id][1] = spawnOrRemoveHelper(state, "Hand Helper", {-50.84, 1.6, 7.02}, {0, 270, 0}, "White")
optionPanel[id][2] = spawnOrRemoveHelper(state, "Hand Helper", {-50.90, 1.6, -25.10}, {0, 270, 0}, "Orange")
- optionPanel[id][3] = spawnOrRemoveHelper(state, "Hand Helper", {-34.38, 1.6, 22.44}, {0, 000, 0}, "Green")
- optionPanel[id][4] = spawnOrRemoveHelper(state, "Hand Helper", {-16.69, 1.6, -22.42}, {0, 180, 0}, "Red")
+ optionPanel[id][3] = spawnOrRemoveHelper(state, "Hand Helper", {-39.27, 1.6, 22.44}, {0, 000, 0}, "Green")
+ optionPanel[id][4] = spawnOrRemoveHelper(state, "Hand Helper", {-21.51, 1.6, -22.44}, {0, 180, 0}, "Red")
-- option: Show chaos bag manager
elseif id == "showChaosBagManager" then
@@ -951,7 +956,7 @@ function onClick_defaultSettings()
for id, _ in pairs(optionPanel) do
local state = false
-- override for settings that are enabled by default
- if id == "useSnapTags" then
+ if id == "useSnapTags" or id == "showTitleSplash" then
state = true
end
applyOptionPanelChange(id, state)
@@ -962,6 +967,7 @@ function onClick_defaultSettings()
useSnapTags = true,
showDrawButton = false,
useClueClickers = false,
+ showTitleSplash = true,
showTokenArranger = false,
showCleanUpHelper = false,
showHandHelper = {},
@@ -972,3 +978,25 @@ function onClick_defaultSettings()
-- update UI
updateOptionPanelState()
end
+
+-- splash scenario title on setup
+function titleSplash(scenarioName)
+ if optionPanel['showTitleSplash'] then
+
+ -- if there's any ongoing title being displayed, hide it and cancel the waiting function
+ if hideTitleSplashWaitFunctionId then
+ Wait.stop(hideTitleSplashWaitFunctionId)
+ hideTitleSplashWaitFunctionId = nil
+ UI.setAttribute('title_splash', 'active', false)
+ end
+
+ -- display scenario name and set a 4 seconds (2 seconds animation and 2 seconds on screen)
+ -- wait timer to hide the scenario name
+ UI.setValue('title_splash', scenarioName)
+ UI.show('title_splash')
+ hideTitleSplashWaitFunctionId = Wait.time(function()
+ UI.hide('title_splash')
+ hideTitleSplashWaitFunctionId = nil
+ end, 4)
+ end
+end
diff --git a/src/core/MythosArea.ttslua b/src/core/MythosArea.ttslua
index 84f962ab..94933d64 100644
--- a/src/core/MythosArea.ttslua
+++ b/src/core/MythosArea.ttslua
@@ -55,6 +55,7 @@ function resetTokensIfInDeckZone(container, object)
end
function fireScenarioChangedEvent()
+ Global.call('titleSplash', currentScenario)
playArea.onScenarioChanged(currentScenario)
end
diff --git a/src/core/PlayArea.ttslua b/src/core/PlayArea.ttslua
index dde1cfe0..3fa91628 100644
--- a/src/core/PlayArea.ttslua
+++ b/src/core/PlayArea.ttslua
@@ -5,10 +5,24 @@ local tokenManager = require("core/token/TokenManager")
---------------------------------------------------------
-- set true to enable debug logging
-DEBUG = false
+local DEBUG = false
+
+-- Location connection directional options
+local BIDIRECTIONAL = 0
+local ONE_WAY = 1
+
+-- Connector draw parameters
+local CONNECTION_THICKNESS = 0.015
+local CONNECTION_COLOR = { 0.4, 0.4, 0.4, 1 }
+local DIRECTIONAL_ARROW_DISTANCE = 3.5
+local ARROW_ARM_LENGTH = 0.9
+local ARROW_ANGLE = 25
+
+-- Height to draw the connector lines, places them just above the table and always below cards
+local CONNECTION_LINE_Y = 1.529
-- we use this to turn off collision handling until onLoad() is complete
-COLLISION_ENABLED = false
+local collisionEnabled = false
local SHIFT_OFFSETS = {
left = { x = 0.00, y = 0, z = 7.67 },
@@ -21,10 +35,23 @@ local SHIFT_EXCLUSION = {
["f182ee"] = true,
["721ba2"] = true
}
+local LOC_LINK_EXCLUDE_SCENARIOS = {
+ ["Devil Reef"] = true,
+ ["The Witching Hour"] = true,
+}
local INVESTIGATOR_COUNTER_GUID = "f182ee"
local PLAY_AREA_ZONE_GUID = "a2f932"
+local clueData = {}
+local spawnedLocationGUIDs = {}
+
+local locations = { }
+local locationConnections = { }
+local draggingGuids = { }
+
+local locationData
+
local currentScenario
---------------------------------------------------------
@@ -33,17 +60,19 @@ local currentScenario
function onSave()
return JSON.encode({
- currentScenario = currentScenario
+ trackedLocations = locations,
+ currentScenario = currentScenario,
})
end
function onLoad(saveState)
-- records locations we have spawned clues for
- local saveData = JSON.decode(saveState) or {}
- currentScenario = saveData.currentScenario
+ local save = JSON.decode(saveState) or { }
+ locations = save.trackedLocations or { }
+ currentScenario = save.currentScenario
self.interactable = DEBUG
- Wait.time(function() COLLISION_ENABLED = true end, 1)
+ Wait.time(function() collisionEnabled = true end, 1)
end
function log(message)
@@ -60,14 +89,15 @@ function updateLocations(args)
end
end
-function onCollisionEnter(collision_info)
- if not COLLISION_ENABLED then return end
-
+function onCollisionEnter(collisionInfo)
+ if not collisionEnabled then return end
-- check if we should spawn clues here and do so according to playercount
- local card = collision_info.collision_object
+ local card = collisionInfo.collision_object
if shouldSpawnTokens(card) then
tokenManager.spawnForCard(card)
end
+ draggingGuids[card.getGUID()] = nil
+ maybeTrackLocation(card)
end
function shouldSpawnTokens(card)
@@ -81,6 +111,267 @@ function shouldSpawnTokens(card)
or metadata.weakness
end
+function onCollisionExit(collisionInfo)
+ maybeUntrackLocation(collisionInfo.collision_object)
+end
+
+-- Destroyed objects don't trigger onCollisionExit(), so check on destruction to untrack as well
+function onObjectDestroy(object)
+ maybeUntrackLocation(object)
+end
+
+function onObjectPickUp(player, object)
+ -- onCollisionExit fires first, so we have to check the card to see if it's a location we should
+ -- be tracking
+ if showLocationLinks() and isInPlayArea(object) and object.getGMNotes() ~= nil and object.getGMNotes() ~= "" then
+ local pickedUpGuid = object.getGUID()
+ local metadata = JSON.decode(object.getGMNotes())
+ if (metadata.type == "Location") then
+ draggingGuids[pickedUpGuid] = metadata
+ rebuildConnectionList()
+ end
+ end
+end
+
+function onUpdate()
+ -- Due to the frequence of onUpdate calls, ensure that we only process any changes to the
+ -- connection list once, and only redraw once
+ local needsConnectionRebuild = false
+ local needsConnectionDraw = false
+ for guid, _ in pairs(draggingGuids) do
+ local obj = getObjectFromGUID(guid)
+ if obj == nil or not isInPlayArea(obj) then
+ draggingGuids[guid] = nil
+ needsConnectionRebuild = true
+ end
+ -- Even if the last location left the play area, need one last draw to clear the lines
+ needsConnectionDraw = true
+ end
+ if (needsConnectionRebuild) then
+ rebuildConnectionList()
+ end
+ if needsConnectionDraw then
+ drawConnections()
+ end
+end
+
+-- Checks the given card and adds it to the list of locations tracked for connection purposes.
+-- A card will be added to the tracking if it is a location in the play area (based on centerpoint).
+-- @param A card object, possibly a location.
+function maybeTrackLocation(card)
+ -- Collision checks for any part of the card overlap, but our other tracking is centerpoint
+ -- Ignore any collision where the centerpoint isn't in the area
+ if showLocationLinks() and isInPlayArea(card) then
+ local metadata = JSON.decode(card.getGMNotes()) or { }
+ if metadata.type == "Location" then
+ locations[card.getGUID()] = metadata
+ rebuildConnectionList()
+ drawConnections()
+ end
+ end
+end
+
+-- Stop tracking a location for connection drawing. This should be called for both collision exit
+-- and destruction, as a destroyed object does not trigger collision exit. An object can also be
+-- deleted mid-drag, but the ordering for drag events means we can't clear those here and those will
+-- be cleared in the next onUpdate() cycle.
+-- @param card Card to (maybe) stop tracking
+function maybeUntrackLocation(card)
+ -- Locked objects no longer collide (hence triggering an exit event) but are still in the play
+ -- area. If the object is now locked, don't remove it.
+ if locations[card.getGUID()] ~= nil and not card.locked then
+ locations[card.getGUID()] = nil
+ rebuildConnectionList()
+ drawConnections()
+ end
+end
+
+-- Builds a list of GUID to GUID connection information based on the currently tracked locations.
+-- This will update the connection information and store it in the locationConnections data member,
+-- but does not draw those connections. This should often be followed by a call to
+-- drawConnections()
+function rebuildConnectionList()
+ if not showLocationLinks() then
+ locationConnections = { }
+ return
+ end
+
+ local iconCardList = { }
+
+ -- Build a list of cards with each icon as their location ID
+ for cardId, metadata in pairs(draggingGuids) do
+ buildLocListByIcon(cardId, iconCardList)
+ end
+ for cardId, metadata in pairs(locations) do
+ buildLocListByIcon(cardId, iconCardList)
+ end
+
+ -- Pair up all the icons
+ locationConnections = { }
+ for cardId, metadata in pairs(draggingGuids) do
+ buildConnection(cardId, iconCardList)
+ end
+ for cardId, metadata in pairs(locations) do
+ if draggingGuids[cardId] == nil then
+ buildConnection(cardId, iconCardList)
+ end
+ end
+end
+
+-- Extracts the card's icon string into a list of individual location icons
+-- @param cardID GUID of the card to pull the icon data from
+-- @param iconCardList A table of icon->GUID list. Mutable, will be updated by this method
+function buildLocListByIcon(cardId, iconCardList)
+ local card = getObjectFromGUID(cardId)
+ local locData = getLocationData(card)
+ if locData ~= nil and locData.icons ~= nil then
+ for icon in string.gmatch(locData.icons, "%a+") do
+ if iconCardList[icon] == nil then
+ iconCardList[icon] = { }
+ end
+ table.insert(iconCardList[icon], card.getGUID())
+ end
+ end
+end
+
+-- Builds the connections for the given cardID by finding matching icons and adding them to the
+-- Playarea's locationConnections table.
+-- @param cardId GUID of the card to build the connections for
+-- @param iconCardList A table of icon->GUID List. Used to find matching icons for connections.
+function buildConnection(cardId, iconCardList)
+ local card = getObjectFromGUID(cardId)
+ local locData = getLocationData(card)
+ if locData ~= nil and locData.connections ~= nil then
+ locationConnections[card.getGUID()] = { }
+ for icon in string.gmatch(locData.connections, "%a+") do
+ if iconCardList[icon] ~= nil then
+ for _, connectedGuid in ipairs(iconCardList[icon]) do
+ -- If the reciprocal exists, convert it to BiDi, otherwise add as a one-way
+ if locationConnections[connectedGuid] ~= nil
+ and locationConnections[connectedGuid][card.getGUID()] ~= nil then
+ locationConnections[connectedGuid][card.getGUID()] = BIDIRECTIONAL
+ else
+ locationConnections[card.getGUID()][connectedGuid] = ONE_WAY
+ end
+ end
+ end
+ end
+ end
+end
+
+-- Helper method to extract the location metadata from a card based on whether it's front or back
+-- is showing.
+-- @param card Card object to extract data from
+-- @return Table with either the locationFront or locationBack metadata structure, or nil if the
+-- metadata doesn't exist
+function getLocationData(card)
+ if card == nil then
+ return nil
+ end
+ if card.is_face_down then
+ return JSON.decode(card.getGMNotes()).locationBack
+ else
+ return JSON.decode(card.getGMNotes()).locationFront
+ end
+end
+
+-- Draws the lines for connections currently in locationConnections.
+function drawConnections()
+ if not showLocationLinks() then
+ locationConnections = { }
+ return
+ end
+ local cardConnectionLines = { }
+
+ for originGuid, targetGuids in pairs(locationConnections) do
+ -- Objects should reliably exist at this point, but since this can be called during onUpdate the
+ -- object checks are conservative just to make sure.
+ local origin = getObjectFromGUID(originGuid)
+ if origin != nil then
+ for targetGuid, direction in pairs(targetGuids) do
+ local target = getObjectFromGUID(targetGuid)
+ if target != nil then
+ if direction == BIDIRECTIONAL then
+ addBidirectionalVector(origin, target, cardConnectionLines)
+ elseif direction == ONE_WAY then
+ addOneWayVector(origin, target, cardConnectionLines)
+ end
+ end
+ end
+ end
+ end
+ self.setVectorLines(cardConnectionLines)
+end
+
+-- Draws a bidirectional location connection between the two cards, adding the lines to do so to the
+-- given lines list.
+-- @param card1 One of the card objects to connect
+-- @param card2 The other card object to connect
+-- @param lines List of vector line elements. Mutable, will be updated to add this connector
+function addBidirectionalVector(card1, card2, lines)
+ local cardPos1 = card1.getPosition()
+ local cardPos2 = card2.getPosition()
+ cardPos1.y = CONNECTION_LINE_Y
+ cardPos2.y = CONNECTION_LINE_Y
+ local pos1 = self.positionToLocal(cardPos1)
+ local pos2 = self.positionToLocal(cardPos2)
+ table.insert(lines, {
+ points = { pos1, pos2 },
+ color = CONNECTION_COLOR,
+ thickness = CONNECTION_THICKNESS,
+ })
+end
+
+-- Draws a one-way location connection between the two cards, adding the lines to do so to the
+-- given lines list. Arrows will point towards the target card.
+-- @param origin Origin card in the connection
+-- @param target Target card object to connect
+-- @param lines List of vector line elements. Mutable, will be updated to add this connector
+function addOneWayVector(origin, target, lines)
+ -- Start with the BiDi then add the arrow lines to it
+ addBidirectionalVector(origin, target, lines)
+ local originPos = origin.getPosition()
+ local targetPos = target.getPosition()
+ originPos.y = CONNECTION_LINE_Y
+ targetPos.y = CONNECTION_LINE_Y
+
+ -- Calculate card distance to be closer for horizontal positions than vertical, since cards are
+ -- taller than they are wide
+ local heading = Vector(originPos):sub(targetPos):heading("y")
+ local distanceFromCard = DIRECTIONAL_ARROW_DISTANCE * 0.7 + DIRECTIONAL_ARROW_DISTANCE * 0.3 * math.abs(math.sin(math.rad(heading)))
+
+ -- Calculate the three possible arrow positions. These are offset by half the arrow length to
+ -- make them visually balanced by keeping the arrows centered, not tracking the point
+ local midpoint = Vector(originPos):add(targetPos):scale(Vector(0.5, 0.5, 0.5)):moveTowards(targetPos, ARROW_ARM_LENGTH / 2)
+ local closeToOrigin = Vector(originPos):moveTowards(targetPos, distanceFromCard + ARROW_ARM_LENGTH / 2)
+ local closeToTarget = Vector(targetPos):moveTowards(originPos, distanceFromCard - ARROW_ARM_LENGTH / 2)
+
+ if (originPos:distance(closeToOrigin) > originPos:distance(closeToTarget)) then
+ addArrowLines(midpoint, originPos, lines)
+ else
+ addArrowLines(closeToOrigin, originPos, lines)
+ addArrowLines(closeToTarget, originPos, lines)
+ end
+end
+
+-- Draws an arrowhead at the given position.
+-- @param arrowheadPosition Centerpoint of the arrowhead to draw (NOT the tip of the arrow)
+-- @param originPos Origin point of the connection, used to position the arrow arms
+-- @param lines List of vector line elements. Mutable, will be updated to add this arrow
+function addArrowLines(arrowheadPos, originPos, lines)
+ local arrowArm1 = Vector(arrowheadPos):moveTowards(originPos, ARROW_ARM_LENGTH):sub(arrowheadPos):rotateOver("y", -1 * ARROW_ANGLE):add(arrowheadPos)
+ local arrowArm2 = Vector(arrowheadPos):moveTowards(originPos, ARROW_ARM_LENGTH):sub(arrowheadPos):rotateOver("y", ARROW_ANGLE):add(arrowheadPos)
+
+ local head = self.positionToLocal(arrowheadPos)
+ local arm1 = self.positionToLocal(arrowArm1)
+ local arm2 = self.positionToLocal(arrowArm2)
+ table.insert(lines, {
+ points = { arm1, head, arm2},
+ color = CONNECTION_COLOR,
+ thickness = CONNECTION_THICKNESS
+ })
+end
+
-- Move all contents on the play area (cards, tokens, etc) one slot in the given direction. Certain
-- fixed objects will be ignored, as will anything the player has tagged with
-- 'displacement_excluded'
@@ -123,6 +414,20 @@ function getInvestigatorCount()
return investigatorCounter.getVar("val")
end
+-- Check to see if the given object is within the bounds of the play area, based solely on the X and
+-- Z coordinates, ignoring height
+-- @param object Object to check
+-- @return True if the object is inside the play area
+function isInPlayArea(object)
+ local bounds = self.getBounds()
+ local position = object.getPosition()
+ -- Corners are arbitrary since it's all global - c1 goes down both axes, c2 goes up
+ local c1 = { x = bounds.center.x - bounds.size.x / 2, z = bounds.center.z - bounds.size.z / 2}
+ local c2 = { x = bounds.center.x + bounds.size.x / 2, z = bounds.center.z + bounds.size.z / 2}
+
+ return position.x > c1.x and position.x < c2.x and position.z > c1.z and position.z < c2.z
+end
+
-- Reset the play area's tracking of which cards have had tokens spawned.
function resetSpawnedCards()
spawnedLocationGUIDs = {}
@@ -130,4 +435,11 @@ end
function onScenarioChanged(scenarioName)
currentScenario = scenarioName
+ if not showLocationLinks() then
+ broadcastToAll("Automatic location connections not available for this scenario")
+ end
+end
+
+function showLocationLinks()
+ return not LOC_LINK_EXCLUDE_SCENARIOS[currentScenario]
end
diff --git a/src/core/token/TokenManager.ttslua b/src/core/token/TokenManager.ttslua
index e5c62cb4..7a7916e1 100644
--- a/src/core/token/TokenManager.ttslua
+++ b/src/core/token/TokenManager.ttslua
@@ -248,6 +248,8 @@ do
else
rot.y = 270
end
+
+ tokenTemplate.Nickname = ""
return spawnObjectData({
data = tokenTemplate,
position = position,
diff --git a/src/core/tour/TourCard.ttslua b/src/core/tour/TourCard.ttslua
index 02d9ad73..3a77f414 100644
--- a/src/core/tour/TourCard.ttslua
+++ b/src/core/tour/TourCard.ttslua
@@ -6,8 +6,8 @@ tourCardTemplate = {
tag = "Panel",
attributes = {
id = "tourCard",
- height = 195,
- width = 300,
+ height = 215,
+ width = 330,
rotation = "0 0 0",
position = "0 300 30",
showAnimation = "FadeIn",
@@ -19,10 +19,10 @@ tourCardTemplate = {
tag = "Image",
attributes = {
id = "tourNarratorImageLeft",
- height=75,
- width=50,
+ height=120,
+ width=80,
rectAlignment="UpperLeft",
- offsetXY = "-50 0",
+ offsetXY = "-80 0",
-- Image will be set when the card is updated
}
},
@@ -31,10 +31,10 @@ tourCardTemplate = {
attributes = {
id = "tourNarratorImageRight",
active = false,
- height=75,
- width=50,
+ height=125,
+ width=80,
rectAlignment="UpperRight",
- offsetXY = "50 0"
+ offsetXY = "80 0"
-- Image will be set when the card is updated
}
},
@@ -43,8 +43,8 @@ tourCardTemplate = {
attributes = {
id = "tourSpeechBubble",
color = "#F5F5DC",
- height = 195,
- width = 300,
+ height = 215,
+ width = 330,
rectAlignment = "MiddleCenter",
image = "SpeechBubble",
},
@@ -53,35 +53,37 @@ tourCardTemplate = {
tag = "Text",
attributes = {
id = "tourText",
- height = 165,
- width = 230,
+ -- Everything on this is double-sized and scaled down to keep the text sharps
+ height = 370,
+ width = 520,
+ scale = "0.5 0.5 1",
rectAlignment = "UpperCenter",
offsetXY = "15 -15",
resizeTextForBestFit = true,
- resizeTextMinSize = 10,
- resizeTextMaxSize = 16,
+ resizeTextMinSize = 20,
+ resizeTextMaxSize = 32,
color = "#050505",
alignment = "UpperLeft",
horizontalOverflow = "wrap",
}
},
{
- tag = "Button",
+ tag = "Image",
attributes = {
id = "tourNext",
- height = 40,
- width = 40,
+ height = 45,
+ width = 45,
rectAlignment = "LowerRight",
offsetXY = "-5 -45",
image = "NextArrow"
},
},
{
- tag = "Button",
+ tag = "Image",
attributes = {
id = "tourStop",
- height = 40,
- width = 40,
+ height = 45,
+ width = 45,
rectAlignment = "LowerLeft",
offsetXY = "35 -45",
image = "Exit"
diff --git a/src/core/tour/TourManager.ttslua b/src/core/tour/TourManager.ttslua
index 34001fcc..d0a37183 100644
--- a/src/core/tour/TourManager.ttslua
+++ b/src/core/tour/TourManager.ttslua
@@ -36,10 +36,14 @@ do
east = "600 0 0",
west = "-600 0 0",
south = "0 -300 0",
- northwest = "-600 300 0",
+ -- Northwest is only used by the Mandy card, move it a little right than standard so it's
+ -- closer to the importer
+ northwest = "-500 300 0",
northeast = "600 300 0",
southwest = "-600 -300 0",
- southeast = "600 -300 0"
+ -- Used by the Diana and Wini cards referencing the bottom-right global controls, moved a little
+ -- closer to them
+ southeast = "730 -365 0"
}
-- Tracks the current state of the tours. Keyed by player color to keep each player's tour
@@ -264,7 +268,6 @@ do
---@param playerColor String. String color of the player to make this visible for
internal.setDeepVisibility = function(xmlUi, playerColor)
xmlUi.attributes.visibility = "" .. playerColor
- log(xmlUi.attributes.id)
if xmlUi.children ~= nil then
for _, child in ipairs(xmlUi.children) do
internal.setDeepVisibility(child, playerColor)
diff --git a/src/core/tour/TourScript.ttslua b/src/core/tour/TourScript.ttslua
index addc0d73..2f6fd65c 100644
--- a/src/core/tour/TourScript.ttslua
+++ b/src/core/tour/TourScript.ttslua
@@ -8,7 +8,7 @@ TOUR_SCRIPT = {
},
{
narrator = "Darrell",
- text = "Cameras can be tricky things. Best you leave handling it to the professionals during the tour. Don't try to move the camera until the tour is complete.\n\nOnce we're done, remmeber you can use the 'p' key to switch back to third-person mode, and the spacebar to reset the position.",
+ text = "Cameras can be tricky things. Best you leave handling it to the professionals during the tour. Don't try to move the camera until the tour is complete.\n\nOnce we're done, remember you can use the 'p' key to switch back to third-person mode, and the spacebar to reset the position.",
position = "center",
speakerSide = "right",
},
@@ -25,7 +25,7 @@ TOUR_SCRIPT = {
text = "To survive what's coming you'll need a deck. If it's safely hidden away on ArkhamDB you can load it here, and even find the newest version after an upgrade without changing the ID.\n\nNo need to publish all your decks, use 'Private' and you can see it. Just make sure to select 'Make your decks public' in ArkhamDB.",
showObj = "a28140",
distanceFromObj = -10,
- position = "west",
+ position = "northwest",
},
{
narrator = "Daniela",
@@ -83,7 +83,7 @@ TOUR_SCRIPT = {
},
{
narrator = "Preston",
- text = "I can afford to buy what I need, but for those less well-off we've provided an endless pool of tokens to track your game. Simply drag one out of the pools here.\n\nResources are my favorite, of course, but damage and horror are as inevitable as taxes, though I leave those to my bookkeeper. Those tokens can work like counters, use the number keys to change the value.",
+ text = "I can afford to buy what I need, but for those less well-off we've provided an endless pool of tokens to track your game. Simply drag one out of the pools here.\n\nResources are my favorite of course, but damage and horror are as inevitable as taxes. I leave those to my bookkeeper though. Those tokens can work like counters, use the number keys to change the value.",
showObj = "9fadf9",
position = "north",
skipCentering = true,
diff --git a/src/core/tour/TourStarter.ttslua b/src/core/tour/TourStarter.ttslua
index 62bd64e7..cc8a9ee7 100644
--- a/src/core/tour/TourStarter.ttslua
+++ b/src/core/tour/TourStarter.ttslua
@@ -26,7 +26,6 @@ function onLoad()
end
function startTour(_, playerColor, _)
- broadcastToColor("Starting the tour, please wait", playerColor)
tourManager.startTour(playerColor)
end
diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua
index a59a1cb6..a5954e2d 100644
--- a/src/playercards/AllCardsBag.ttslua
+++ b/src/playercards/AllCardsBag.ttslua
@@ -7,6 +7,8 @@ local WEAKNESS_CHECK_Z = 37
local cardIdIndex = { }
local classAndLevelIndex = { }
local basicWeaknessList = { }
+local uniqueWeaknessList = { }
+local cycleIndex = { }
local indexingDone = false
local allowRemoval = false
@@ -45,7 +47,9 @@ function clearIndexes()
classAndLevelIndex["Survivor-level0"] = { }
classAndLevelIndex["Rogue-level0"] = { }
classAndLevelIndex["Neutral-level0"] = { }
+ cycleIndex = { }
basicWeaknessList = { }
+ uniqueWeaknessList = { }
end
-- Clears the bag indexes and starts the coroutine to rebuild the indexes
@@ -121,27 +125,27 @@ function buildSupplementalIndexes()
local cardMetadata = card.metadata
-- If the ID key and the metadata ID don't match this is a duplicate card created by an
-- alternate_id, and we should skip it
- if (cardId == cardMetadata.id) then
+ if cardId == cardMetadata.id then
-- Add card to the basic weakness list, if appropriate. Some weaknesses have
-- multiple copies, and are added multiple times
- if (cardMetadata.weakness and cardMetadata.basicWeaknessCount ~= nil) then
+ if cardMetadata.weakness and cardMetadata.basicWeaknessCount ~= nil then
+ table.insert(uniqueWeaknessList, cardMetadata.id)
for i = 1, cardMetadata.basicWeaknessCount do
table.insert(basicWeaknessList, cardMetadata.id)
+ end
end
- end
- table.sort(basicWeaknessList, cardComparator)
- -- Add the card to the appropriate class and level indexes
- local isGuardian = false
- local isSeeker = false
- local isMystic = false
- local isRogue = false
- local isSurvivor = false
- local isNeutral = false
- local upgradeKey
- -- Excludes signature cards (which have no class or level) and alternate
- -- ID entries
- if (cardMetadata.class ~= nil and cardMetadata.level ~= nil) then
+ -- Add the card to the appropriate class and level indexes
+ local isGuardian = false
+ local isSeeker = false
+ local isMystic = false
+ local isRogue = false
+ local isSurvivor = false
+ local isNeutral = false
+ local upgradeKey
+ -- Excludes signature cards (which have no class or level) and alternate
+ -- ID entries
+ if (cardMetadata.class ~= nil and cardMetadata.level ~= nil) then
isGuardian = string.match(cardMetadata.class, "Guardian")
isSeeker = string.match(cardMetadata.class, "Seeker")
isMystic = string.match(cardMetadata.class, "Mystic")
@@ -171,12 +175,32 @@ function buildSupplementalIndexes()
if (isNeutral) then
table.insert(classAndLevelIndex["Neutral"..upgradeKey], cardMetadata.id)
end
+
+ local cycleName = cardMetadata.cycle
+ if cycleName ~= nil then
+ cycleName = string.lower(cycleName)
+ if string.match(cycleName, "return") then
+ cycleName = string.sub(cycleName, 11)
+ end
+ if cycleName == "the night of the zealot" then
+ cycleName = "core"
+ end
+ if cycleIndex[cycleName] == nil then
+ cycleIndex[cycleName] = { }
+ end
+ table.insert(cycleIndex[cycleName], cardMetadata.id)
+ end
end
end
end
for _, indexTable in pairs(classAndLevelIndex) do
table.sort(indexTable, cardComparator)
end
+ for _, indexTable in pairs(cycleIndex) do
+ table.sort(indexTable)
+ end
+ table.sort(basicWeaknessList, cardComparator)
+ table.sort(uniqueWeaknessList, cardComparator)
end
-- Comparison function used to sort the class card bag indexes. Sorts by card
@@ -235,6 +259,14 @@ function getCardsByClassAndLevel(params)
return classAndLevelIndex[params.class..upgradeKey];
end
+function getCardsByCycle(cycleName)
+ if (not indexingDone) then
+ broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
+ return { }
+ end
+ return cycleIndex[string.lower(cycleName)]
+end
+
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
-- Parameter array must contain these fields to define the search:
@@ -303,6 +335,10 @@ function getBasicWeaknesses()
return basicWeaknessList
end
+function getUniqueWeaknesses()
+ return uniqueWeaknessList
+end
+
-- Helper function that adds one to the table entry for the number of weaknesses in play
function incrementWeaknessCount(table, cardMetadata)
if (isBasicWeakness(cardMetadata)) then
@@ -322,6 +358,7 @@ function isInPlayArea(object)
return position.x < WEAKNESS_CHECK_X
and position.z < WEAKNESS_CHECK_Z
end
+
function isBasicWeakness(cardMetadata)
return cardMetadata ~= nil
and cardMetadata.weakness
diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua
index d6ad9f29..9b85aa2b 100644
--- a/src/playercards/PlayerCardPanel.ttslua
+++ b/src/playercards/PlayerCardPanel.ttslua
@@ -2,24 +2,49 @@ require("playercards/PlayerCardPanelData")
local spawnBag = require("playercards/spawnbag/SpawnBag")
local arkhamDb = require("arkhamdb/ArkhamDb")
--- TODO: Update when the real UI image is in place
-local BUTTON_WIDTH = 150
-local BUTTON_HEIGHT = 550
+-- Size and position information for the three rows of class buttons
+local CIRCLE_BUTTON_SIZE = 250
+local CLASS_BUTTONS_X_OFFSET = 0.1325
+local INVESTIGATOR_ROW_START = Vector(0.125, 0.1, -0.447)
+local LEVEL_ZERO_ROW_START = Vector(0.125, 0.1, -0.007)
+local UPGRADED_ROW_START = Vector(0.125, 0.1, 0.333)
+
+-- Size and position information for the two blocks of other buttons
+local MISC_BUTTONS_X_OFFSET = 0.155
+local WEAKNESS_ROW_START = Vector(0.157, 0.1, 0.666)
+local OTHER_ROW_START = Vector(0.605, 0.1, 0.666)
+
+-- Size and position information for the Cycle (box) buttons
+local CYCLE_BUTTON_SIZE = 468
+local CYCLE_BUTTON_START = Vector(-0.716, 0.1, -0.39)
+local CYCLE_COLUMN_COUNT = 3
+local CYCLE_BUTTONS_X_OFFSET = 0.267
+local CYCLE_BUTTONS_Z_OFFSET = 0.2665
local ALL_CARDS_BAG_GUID = "15bb07"
+local STARTER_DECK_MODE_SELECTED_COLOR = { 0.2, 0.2, 0.2, 0.8 }
+local TRANSPARENT = { 0, 0, 0, 0 }
+local STARTER_DECK_MODE_STARTERS = "starters"
+local STARTER_DECK_MODE_CARDS_ONLY = "cards"
+
local FACE_UP_ROTATION = { x = 0, y = 270, z = 0}
local FACE_DOWN_ROTATION = { x = 0, y = 270, z = 180}
--- Coordinates to begin laying out cards to match the reserved areas of the
--- table. Cards will lay out horizontally, then create additional rows
+-- Coordinates to begin laying out cards. These vary based on the cards that are being placed
local START_POSITIONS = {
- skill = Vector(58.384, 1.36, 92.4),
- event = Vector(53.229, 1.36, 92.4),
- asset = Vector(40.960, 1.36, 92.4),
- investigator = Vector(60, 1.36, 80)
+ classCards = Vector(58.384, 1.36, 92.4),
+ investigator = Vector(60, 1.36, 86),
+ cycle = Vector(48, 1.36, 92.4),
+ other = Vector(56, 1.36, 86),
+ summonedServitor = Vector(55.5, 1.36, 60.2),
+ randomWeakness = Vector(55, 1.36, 75)
}
+-- Shifts to move rows of cards, and groups of rows, as different groupings are laid out
+local CARD_ROW_OFFSET = 3.7
+local CARD_GROUP_OFFSET = 2
+
-- Position offsets for investigator decks in investigator mode, defines the spacing for how the
-- rows and columns are laid out
local INVESTIGATOR_POSITION_SHIFT_ROW = Vector(-11, 0, 0)
@@ -31,7 +56,21 @@ local INVESTIGATOR_MAX_COLS = 6
local INVESTIGATOR_CARD_OFFSET = Vector(-2.55, 0, 0)
local INVESTIGATOR_SIGNATURE_OFFSET = Vector(-5.75, 0, 0)
-local spawnStarterDecks = false
+local CLASS_LIST = { "Guardian", "Seeker", "Rogue", "Mystic", "Survivor", "Neutral" }
+local CYCLE_LIST = {
+ "Core",
+ "The Dunwich Legacy",
+ "The Path to Carcosa",
+ "The Forgotten Age",
+ "The Circle Undone",
+ "The Dream-Eaters",
+ "The Innsmouth Conspiracy",
+ "Edge of the Earth",
+ "The Scarlet Keys",
+ "Investigator Packs"
+}
+
+local starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
function onSave()
local saveState = {
@@ -48,232 +87,223 @@ function onLoad(savedData)
spawnBag.loadFromSave(saveState.spawnBagState)
end
end
+ createButtons()
+end
- self.createButton({
- label="Guardian", click_function="spawnInvestigatorsGuardian", function_owner=self,
- position={-0.3,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Seeker", click_function="spawnInvestigatorsSeeker", function_owner=self,
- position={0,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Mystic", click_function="spawnInvestigatorsMystic", function_owner=self,
- position={0.3,0.2,-0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Rogue", click_function="spawnInvestigatorsRogue", function_owner=self,
- position={-0.3,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Survivor", click_function="spawnSurvivor", function_owner=self,
- position={0,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Neutral", click_function="spawnNeutral", function_owner=self,
- position={0.3,0.2,-0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
+function createButtons()
+ createInvestigatorButtons()
+ createLevelZeroButtons()
+ createUpgradedButtons()
+ createWeaknessButtons()
+ createOtherButtons()
+ createCycleButtons()
+ createClearButton()
+ -- Create investigator mode buttons last so the indexes are set when we need to update them
+ createInvestigatorModeButtons()
+end
- self.createButton({
- label="Core", click_function="spawnCore", function_owner=self,
- position={-0.3,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Dunwich", click_function="spawnDunwich", function_owner=self,
- position={0,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Carcosa", click_function="spawnCarcosa", function_owner=self,
- position={0.3,0.2,-0.2}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Forgotten Age", click_function="spawnForgottenAge", function_owner=self,
- position={-0.3,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Circle Undone", click_function="spawnCircleUndone", function_owner=self,
- position={0,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Dream Eaters", click_function="spawnDreamEaters", function_owner=self,
- position={0.3,0.2,-0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Innsmouth", click_function="spawnInnsmouth", function_owner=self,
- position={-0.3,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="EotE", click_function="spawnEotE", function_owner=self,
- position={0,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Scarlet Keys", click_function="spawnScarletKeys", function_owner=self,
- position={0.3,0.2,0}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="InvPacks", click_function="spawnInvestigatorDecks", function_owner=self,
- position={-0.3,0.2,0.1}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Investigators", click_function="setInvestigators", function_owner=self,
- position={-0.15,0.2,-0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Starters", click_function="setStarters", function_owner=self,
- position={0.15,0.2,-0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
-
- self.createButton({
- label="L0 Guardian", click_function="spawnBasicGuardian", function_owner=self,
- position={-0.15,0.2,0.3}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Guardian", click_function="spawnUpgradedGuardian", function_owner=self,
- position={0.15,0.2,0.3}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Seeker", click_function="spawnBasicSeeker", function_owner=self,
- position={-0.15,0.2,0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Seeker", click_function="spawnUpgradedSeeker", function_owner=self,
- position={0.15,0.2,0.4}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Mystic", click_function="spawnBasicMystic", function_owner=self,
- position={-0.15,0.2,0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Mystic", click_function="spawnUpgradedGuardian", function_owner=self,
- position={0.15,0.2,0.5}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Rogue", click_function="spawnBasicRogue", function_owner=self,
- position={-0.15,0.2,0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Rogue", click_function="spawnUpgradedRogue", function_owner=self,
- position={0.15,0.2,0.6}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Survivor", click_function="spawnBasicSurvivor", function_owner=self,
- position={-0.15,0.2,0.7}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Survivor", click_function="spawnUpgradedSurvivor", function_owner=self,
- position={0.15,0.2,0.7}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L0 Neutral", click_function="spawnBasicNeutral", function_owner=self,
- position={-0.15,0.2,0.8}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="L1-5 Neutral", click_function="spawnUpgradedNeutral", function_owner=self,
- position={0.15,0.2,0.8}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Clear", click_function="deleteAll", function_owner=self,
- position={0.5,0.2,0.9}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- self.createButton({
- label="Weaknesses", click_function="spawnWeaknesses", function_owner=self,
- position={-0.5,0.2,0.9}, rotation={0,0,0}, height=BUTTON_WIDTH, width=BUTTON_HEIGHT,
- font_size=64, color={0,0,0}, font_color={1,1,1}, scale={0.25, 0.25, 0.25}
- })
- local classList = { "Guardian", "Seeker", "Mystic", "Rogue", "Survivor", "Neutral" }
- for _, className in ipairs(classList) do
- local funcName = "spawnInvestigators"..className
- self.setVar(funcName, function(_, _, _) spawnGroup(className) end)
- funcName = "spawnBasic"..className
- self.setVar(funcName, function(_, _, _) spawnClassCards(className, false) end)
- funcName = "spawnUpgraded"..className
- self.setVar(funcName, function(_, _, _) spawnClassCards(className, true) end)
+function createInvestigatorButtons()
+ local invButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = INVESTIGATOR_ROW_START:copy()
+ for _, class in ipairs(CLASS_LIST) do
+ invButtonParams.click_function = "spawnInvestigators" .. class
+ invButtonParams.position = buttonPos
+ self.createButton(invButtonParams)
+ buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
+ self.setVar(invButtonParams.click_function, function(_, _, _) spawnInvestigatorGroup(class) end)
end
end
--- TODO: Replace these with something less manual once the full data in in place so we know what
--- keys to use
-function placeCore()
- spawnGroup("Core")
+function createLevelZeroButtons()
+ local l0ButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = LEVEL_ZERO_ROW_START:copy()
+ for _, class in ipairs(CLASS_LIST) do
+ l0ButtonParams.click_function = "spawnBasic" .. class
+ l0ButtonParams.position = buttonPos
+ self.createButton(l0ButtonParams)
+ buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
+ self.setVar(l0ButtonParams.click_function, function(_, _, _) spawnClassCards(class, false) end)
+ end
end
-function placeDunwich()
- spawnGroup("Dunwich")
+function createUpgradedButtons()
+ local upgradedButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = UPGRADED_ROW_START:copy()
+ for _, class in ipairs(CLASS_LIST) do
+ upgradedButtonParams.click_function = "spawnUpgraded" .. class
+ upgradedButtonParams.position = buttonPos
+ self.createButton(upgradedButtonParams)
+ buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET
+ self.setVar(upgradedButtonParams.click_function, function(_, _, _) spawnClassCards(class, true) end)
+ end
end
-function placeCarcosa()
- spawnGroup("Carcosa")
+function createWeaknessButtons()
+ local weaknessButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = WEAKNESS_ROW_START:copy()
+ weaknessButtonParams.click_function = "spawnWeaknesses"
+ weaknessButtonParams.tooltip = "Basic Weaknesses"
+ weaknessButtonParams.position = buttonPos
+ self.createButton(weaknessButtonParams)
+ buttonPos.x = buttonPos.x + MISC_BUTTONS_X_OFFSET
+ weaknessButtonParams.click_function = "spawnRandomWeakness"
+ weaknessButtonParams.tooltip = "Random Weakness"
+ weaknessButtonParams.position = buttonPos
+ self.createButton(weaknessButtonParams)
end
-function placeForgottenAge()
- spawnGroup("ForgottenAge")
+function createOtherButtons()
+ local otherButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CIRCLE_BUTTON_SIZE,
+ width = CIRCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = OTHER_ROW_START:copy()
+ otherButtonParams.click_function = "spawnBonded"
+ otherButtonParams.tooltip = "Bonded Cards"
+ otherButtonParams.position = buttonPos
+ self.createButton(otherButtonParams)
+ buttonPos.x = buttonPos.x + MISC_BUTTONS_X_OFFSET
+ otherButtonParams.click_function = "spawnUpgradeSheets"
+ otherButtonParams.tooltip = "Customization Upgrade Sheets"
+ otherButtonParams.position = buttonPos
+ self.createButton(otherButtonParams)
end
-function placeCircleUndone()
- spawnGroup("CircleUndone")
+function createCycleButtons()
+ local cycleButtonParams = {
+ function_owner = self,
+ rotation = Vector(0, 0, 0),
+ height = CYCLE_BUTTON_SIZE,
+ width = CYCLE_BUTTON_SIZE,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ }
+ local buttonPos = CYCLE_BUTTON_START:copy()
+ local rowCount = 0
+ local colCount = 0
+ for _, cycle in ipairs(CYCLE_LIST) do
+ cycleButtonParams.click_function = "spawnCycle" .. cycle
+ cycleButtonParams.position = buttonPos
+ cycleButtonParams.tooltip = cycle
+ self.createButton(cycleButtonParams)
+ self.setVar(cycleButtonParams.click_function, function(_, _, _) spawnCycle(cycle) end)
+ colCount = colCount + 1
+ -- If we've reached the end of a row, shift down and back to the first column
+ if colCount >= CYCLE_COLUMN_COUNT then
+ buttonPos = CYCLE_BUTTON_START:copy()
+ rowCount = rowCount + 1
+ colCount = 0
+ buttonPos.z = buttonPos.z + CYCLE_BUTTONS_Z_OFFSET * rowCount
+ if rowCount == 3 then
+ -- Account for centered button on the final row
+ buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
+ end
+ else
+ buttonPos.x = buttonPos.x + CYCLE_BUTTONS_X_OFFSET
+ end
+ end
end
-function placeDreamEaters()
- spawnGroup("DreamEaters")
+function createClearButton()
+ self.createButton({
+ function_owner = self,
+ click_function = "deleteAll",
+ position = Vector(0, 0.1, 0.852),
+ rotation = Vector(0, 0, 0),
+ height = 170,
+ width = 750,
+ scale = Vector(0.25, 1, 0.25),
+ color = TRANSPARENT,
+ })
end
-function placeInnsmouth()
- spawnGroup("Innsmouth")
+function createInvestigatorModeButtons()
+ local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
+
+ self.createButton({
+ function_owner = self,
+ click_function = "setCardsOnlyMode",
+ position = Vector(0.251, 0.1, -0.322),
+ rotation = Vector(0, 0, 0),
+ height = 170,
+ width = 760,
+ scale = Vector(0.25, 1, 0.25),
+ color = starterMode and TRANSPARENT or STARTER_DECK_MODE_SELECTED_COLOR
+ })
+ self.createButton({
+ function_owner = self,
+ click_function = "setStarterDeckMode",
+ position = Vector(0.66, 0.1, -0.322),
+ rotation = Vector(0, 0, 0),
+ height = 170,
+ width = 760,
+ scale = Vector(0.25, 1, 0.25),
+ color = starterMode and STARTER_DECK_MODE_SELECTED_COLOR or TRANSPARENT
+ })
+ local checkX = starterMode and 0.52 or 0.11
+ self.createButton({
+ function_owner = self,
+ label = "✓",
+ click_function = "doNothing",
+ position = Vector(checkX, 0.11, -0.317),
+ rotation = Vector(0, 0, 0),
+ height = 0,
+ width = 0,
+ scale = Vector(0.3, 1, 0.3),
+ font_color = { 0, 0, 0 },
+ color = { 1, 1, 1 }
+ })
end
-function placeEotE()
- spawnGroup("EotE")
+function setStarterDeckMode()
+ starterDeckMode = STARTER_DECK_MODE_STARTERS
+ updateStarterModeButtons()
end
-function placeScarletKeys()
- spawnGroup("ScarletKeys")
+function setCardsOnlyMode()
+ starterDeckMode = STARTER_DECK_MODE_CARDS_ONLY
+ updateStarterModeButtons()
end
-function placeInvestigatorDecks()
- spawnGroup("InvestigatorDecks")
-end
-
--- UI handler to put the investigator spawn in investigator mode.
-function setInvestigators()
- spawnStarterDecks = false
- printToAll("Spawning investigator piles")
-end
-
--- UI handler to put the investigator spawn in starter deck mode.
-function setStarters()
- spawnStarterDecks = true
- printToAll("Spawning starter decks")
+function updateStarterModeButtons()
+ local buttonCount = #self.getButtons()
+ -- Buttons are 0-indexed, so the last three are -1, -2, and -3 from the size
+ self.removeButton(buttonCount - 1)
+ self.removeButton(buttonCount - 2)
+ self.removeButton(buttonCount - 3)
+ createInvestigatorModeButtons()
end
-- Deletes all cards currently placed on the table
@@ -284,10 +314,11 @@ end
-- Spawn an investigator group, based on the current UI setting for either investigators or starter
-- decks.
---@param groupName String. Name of the group to spawn, matching a key in InvestigatorPanelData
-function spawnGroup(groupName)
+function spawnInvestigatorGroup(groupName)
+ local starterMode = starterDeckMode == STARTER_DECK_MODE_STARTERS
spawnBag.recall(true)
Wait.frames(function()
- if spawnStarterDecks then
+ if starterMode then
spawnStarters(groupName)
else
spawnInvestigators(groupName)
@@ -359,13 +390,13 @@ function buildCommonSpawnSpec(investigatorName, investigatorData, position, oneC
return {
{
name = investigatorName.."minicards",
- cards = oneCardOnly and investigatorData.minicards[1] or investigatorData.minicards,
+ cards = oneCardOnly and { investigatorData.minicards[1] } or investigatorData.minicards,
globalPos = position,
rotation = FACE_UP_ROTATION,
},
{
name = investigatorName.."cards",
- cards = oneCardOnly and investigatorData.cards[1] or investigatorData.cards,
+ cards = oneCardOnly and { investigatorData.cards[1] } or investigatorData.cards,
globalPos = cardPos,
rotation = FACE_UP_ROTATION,
},
@@ -452,31 +483,34 @@ function placeClassCards(cardClass, isUpgraded)
table.insert(assetList, cardId)
end
end
+ local groupPos = Vector(START_POSITIONS.classCards)
if #skillList > 0 then
spawnBag.spawn({
name = cardClass .. (isUpgraded and "upgraded" or "basic"),
cards = skillList,
- globalPos = START_POSITIONS.skill,
+ globalPos = groupPos,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
+ groupPos.x = groupPos.x - math.ceil(#skillList / 20) * CARD_ROW_OFFSET - CARD_GROUP_OFFSET
end
if #eventList > 0 then
spawnBag.spawn({
name = cardClass .. "event" .. (isUpgraded and "upgraded" or "basic"),
cards = eventList,
- globalPos = START_POSITIONS.event,
+ globalPos = groupPos,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
+ groupPos.x = groupPos.x - math.ceil(#eventList / 20) * CARD_ROW_OFFSET - CARD_GROUP_OFFSET
end
if #assetList > 0 then
spawnBag.spawn({
name = cardClass .. "asset" .. (isUpgraded and "upgraded" or "basic"),
cards = assetList,
- globalPos = START_POSITIONS.asset,
+ globalPos = groupPos,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
@@ -484,26 +518,108 @@ function placeClassCards(cardClass, isUpgraded)
end
end
--- Clears the current cards, and places all basic weaknesses on the table.
-function spawnWeaknesses()
- spawnBag.recall(fast)
+-- Spawns the investigator sets and all cards for the given cycle
+---@param cycle String Name of a cycle, should match the standard used in card metadata
+function spawnCycle(cycle)
+ spawnBag.recall(true)
+ spawnInvestigators(cycle)
local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
local indexReady = allCardsBag.call("isIndexReady")
if (not indexReady) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return
end
- local weaknessIdList = allCardsBag.call("getBasicWeaknesses")
+ local cycleCardList = allCardsBag.call("getCardsByCycle", cycle)
local copiedList = { }
- for i, id in ipairs(weaknessIdList) do
+ for i, id in ipairs(cycleCardList) do
copiedList[i] = id
end
spawnBag.spawn({
- name = "weaknesses",
+ name = "cycle"..cycle,
cards = copiedList,
- globalPos = START_POSITIONS.asset,
+ globalPos = START_POSITIONS.cycle,
rotation = FACE_UP_ROTATION,
spread = true,
spreadCols = 20
})
end
+
+function spawnBonded()
+ spawnBag.recall(true)
+ spawnBag.spawn({
+ name = "bonded",
+ cards = BONDED_CARD_LIST,
+ globalPos = START_POSITIONS.classCards,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+end
+
+function spawnUpgradeSheets()
+ spawnBag.recall(true)
+ spawnBag.spawn({
+ name = "upgradeSheets",
+ cards = UPGRADE_SHEET_LIST,
+ globalPos = START_POSITIONS.classCards,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+ spawnBag.spawn({
+ name = "servitor",
+ cards = { "09080-m" },
+ globalPos = START_POSITIONS.summonedServitor,
+ rotation = FACE_UP_ROTATION,
+ })
+end
+
+-- Clears the current cards, and places all basic weaknesses on the table.
+function spawnWeaknesses()
+ spawnBag.recall(true)
+ local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
+ local indexReady = allCardsBag.call("isIndexReady")
+ if (not indexReady) then
+ broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
+ return
+ end
+ local weaknessIdList = allCardsBag.call("getUniqueWeaknesses")
+ local copiedList = { }
+ for i, id in ipairs(weaknessIdList) do
+ copiedList[i] = id
+ end
+ local groupPos = Vector(START_POSITIONS.classCards)
+ spawnBag.spawn({
+ name = "weaknesses",
+ cards = copiedList,
+ globalPos = groupPos,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+ groupPos.x = groupPos.x - math.ceil(#copiedList / 20) * CARD_ROW_OFFSET - CARD_GROUP_OFFSET
+ spawnBag.spawn({
+ name = "evolvedWeaknesses",
+ cards = EVOLVED_WEAKNESSES,
+ globalPos = groupPos,
+ rotation = FACE_UP_ROTATION,
+ spread = true,
+ spreadCols = 20
+ })
+end
+
+function spawnRandomWeakness()
+ spawnBag.recall(true)
+ local allCardsBag = getObjectFromGUID(ALL_CARDS_BAG_GUID)
+ local weaknessId = allCardsBag.call("getRandomWeaknessId")
+ if (weaknessId == nil) then
+ broadcastToAll("All basic weaknesses are in play!", {0.9, 0.2, 0.2})
+ return
+ end
+ spawnBag.spawn({
+ name = "randomWeakness",
+ cards = { weaknessId },
+ globalPos = START_POSITIONS.randomWeakness,
+ rotation = FACE_UP_ROTATION,
+ })
+end
diff --git a/src/playercards/PlayerCardPanelData.ttslua b/src/playercards/PlayerCardPanelData.ttslua
index 62e13e6e..6d186e55 100644
--- a/src/playercards/PlayerCardPanelData.ttslua
+++ b/src/playercards/PlayerCardPanelData.ttslua
@@ -1,3 +1,45 @@
+BONDED_CARD_LIST = {
+ "05314", -- Soothing Melody
+ "06277", -- Wish Eater
+ "06019", -- Bloodlust
+ "06022", -- Pendant of the Queen
+ "05317", -- Blood-rite
+ "06113", -- Essence of the Dream
+ "06028", -- Stars Are Right
+ "06025", -- Guardian of the Crystallizer
+ "06283", -- Unbound Beast
+ "06032", -- Zeal
+ "06031", -- Hope
+ "06033", -- Augur
+ "06331", -- Dream Parasite
+ "06015a", -- Dream-Gate
+}
+
+UPGRADE_SHEET_LIST = {
+ "09040-c", -- Alchemical Distillation
+ "09023-c", -- Custom Modifications
+ "09059-c", -- Damning Testimony
+ "09041-c", -- Emperical Hypothesis
+ "09060-c", -- Friends in Low Places
+ "09101-c", -- Grizzled
+ "09061-c", -- Honed Instinct
+ "09021-c", -- Hunter's Armor
+ "09119-c", -- Hyperphysical Shotcaster
+ "09079-c", -- Living Ink
+ "09100-c", -- Makeshift Trap
+ "09099-c", -- Pocket Multi Tool
+ "09081-c", -- Power Word
+ "09022-c", -- Runic Axe
+ "09080-c", -- Summoned Servitor
+ "09042-c", -- Raven's Quill
+}
+
+EVOLVED_WEAKNESSES = {
+ "04039",
+ "04041",
+ "04042",
+}
+
------------------ START INVESTIGATOR DATA DEFINITION ------------------
INVESTIGATOR_GROUPS = {
Guardian = {
@@ -13,10 +55,6 @@ INVESTIGATOR_GROUPS = {
"D2",
"R3",
"D3",
- "R4",
- "D4",
- "R5",
- "D5",
},
}
@@ -25,13 +63,13 @@ INVESTIGATORS["Roland Banks"] = {
cards = { "01001", "01001-promo", "01001-p", "01001-pf", "01001-pb", },
minicards = { "01001-m", "01001-promo-m", },
signatures = { "01006", "01007", "90030", "90031", },
- starterDeck = "1462",
+ starterDeck = "2624931",
}
INVESTIGATORS["Daisy Walker"] = {
cards = { "01002", "01002-p", "01002-pf", "01002-pb", },
minicards = { "01002-m", },
signatures = { "01008", "01009", "90002", "90003" },
- starterDeck = "42652",
+ starterDeck = "2624938",
}
------------------ END INVESTIGATOR DATA DEFINITION ------------------
INVESTIGATORS["R2"] = INVESTIGATORS["Roland Banks"]
diff --git a/src/playermat/Playmat.ttslua b/src/playermat/Playmat.ttslua
index d98e3a31..a879d59a 100644
--- a/src/playermat/Playmat.ttslua
+++ b/src/playermat/Playmat.ttslua
@@ -663,7 +663,7 @@ function clickableClues(showCounter)
local pos = self.positionToWorld({x = -1.12, y = 0.05, z = 0.7})
for i = 1, clueCount do
pos.y = pos.y + 0.045 * i
- TokenManager.spawnToken(pos, "clue", PLAY_ZONE_ROTATION)
+ tokenManager.spawnToken(pos, "clue", PLAY_ZONE_ROTATION)
end
end
end
diff --git a/src/playermat/PlaymatApi.ttslua b/src/playermat/PlaymatApi.ttslua
index ba6f1687..621c22de 100644
--- a/src/playermat/PlaymatApi.ttslua
+++ b/src/playermat/PlaymatApi.ttslua
@@ -3,26 +3,59 @@ do
local internal = { }
local MAT_IDS = {
- White = "8b081b",
+ White = "8b081b",
Orange = "bd0ff4",
- Green = "383d8b",
- Red = "0840d5"
+ Green = "383d8b",
+ Red = "0840d5"
}
local CLUE_COUNTER_GUIDS = {
- White = "37be78",
+ White = "37be78",
Orange = "1769ed",
- Green = "032300",
- Red = "d86b7c"
+ Green = "032300",
+ Red = "d86b7c"
}
local CLUE_CLICKER_GUIDS = {
- White = "db85d6",
+ White = "db85d6",
Orange = "3f22e5",
- Green = "891403",
- Red = "4111de"
+ Green = "891403",
+ Red = "4111de"
}
+ -- Returns the color of the by position requested playermat as string
+ ---@param startPos Table Position of the search, table get's roughly cut into 4 quarters to assign a playermat
+ PlaymatApi.getMatColorByPosition = function(startPos)
+ if startPos.x < -42 then
+ if startPos.z > 0 then
+ return "White"
+ else
+ return "Orange"
+ end
+ else
+ if startPos.z > 0 then
+ return "Green"
+ else
+ return "Red"
+ end
+ end
+ end
+
+ -- Returns the draw deck of the requested playmat
+ ---@param matColor String Color of the playermat
+ PlaymatApi.getDrawDeck = function(matColor)
+ local mat = getObjectFromGUID(MAT_IDS[matColor])
+ mat.call("getDrawDiscardDecks")
+ return mat.getVar("drawDeck")
+ end
+
+ -- Returns the position of the discard pile of the requested playmat
+ ---@param matColor String Color of the playermat
+ PlaymatApi.getDiscardPosition = function(matColor)
+ local mat = getObjectFromGUID(MAT_IDS[matColor])
+ return mat.getTable("DISCARD_PILE_POSITION")
+ end
+
-- Sets the requested playermat's snap points to limit snapping to matching card types or not. If
-- matchTypes is true, the main card slot snap points will only snap assets, while the
-- investigator area point will only snap Investigators. If matchTypes is false, snap points will
@@ -79,7 +112,7 @@ do
-- 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.
- ---@return Array of playermat objects. If a single mat is requested, will return a single-element
+ ---@return: Array of playermat objects. If a single mat is requested, will return a single-element
-- array to simplify processing by consumers.
internal.getMatForColor = function(matColor)
local targetMatGuid = MAT_IDS[matColor]
diff --git a/xml/Global.xml b/xml/Global.xml
index 6ba20511..dc15bced 100644
--- a/xml/Global.xml
+++ b/xml/Global.xml
@@ -104,4 +104,17 @@
+
+
+
+
diff --git a/xml/OptionPanel.xml b/xml/OptionPanel.xml
index 75f4aff5..d368c473 100644
--- a/xml/OptionPanel.xml
+++ b/xml/OptionPanel.xml
@@ -142,6 +142,20 @@
+
+
+
+
+
+ Fade in the name of the scenario for 2 seconds when placing down a scenario.
+
+ |
+
+
+ |
+
+