Merge branch 'main' into nav-overlay

This commit is contained in:
Chr1Z93 2023-10-20 15:53:08 +02:00
commit 9dfd3d17cd
114 changed files with 2384 additions and 1404 deletions

View File

@ -19,6 +19,7 @@
"MusicPlayer_path": "MusicPlayer.json",
"Note": "",
"ObjectStates_order": [
"GUIDReferenceHandler.123456",
"HandTrigger.5fe087",
"HandTrigger.be2f17",
"HandTrigger.0285cc",
@ -101,7 +102,7 @@
"ClueCounter.d86b7c",
"MasterClueCounter.4a3aa4",
"LegacyAssets.7165a9",
"Playarea.721ba2",
"PlayArea.721ba2",
"BarkhamHorror.308439",
"ChaosBagStatTracker.766620",
"Blesstokens.afa06b",
@ -131,11 +132,11 @@
"EdgeoftheEarth.895eaa",
"TheDream-Eaters.a16a1a",
"ReturntoTheCircleUndone.757324",
"Playermat4Red.0840d5",
"Playermat3Green.383d8b",
"OtherDoominPlay.652ff3",
"Playermat1White.8b081b",
"Playermat2Orange.bd0ff4",
"Playermat3Green.383d8b",
"Playermat4Red.0840d5",
"Neutral.2691e1",
"Neutral.748245",
"Neutral.271b17",
@ -198,6 +199,7 @@
"TokenArranger.022907",
"ChaosBagManager.023240",
"ArkhamSCE330-1092023-Page1.964222",
"PlaceholderBoxDummy.a93466",
"SoulsofDarkness.a94e6b"
],
"PlayArea": 1,
@ -218,5 +220,5 @@
"Tags": [],
"Turns_path": "Turns.json",
"VersionNumber": "v13.2.2",
"XmlUI": "\u003cInclude src=\"Global.xml\"/\u003e"
"XmlUI": "\u003cInclude src=\"Global/Global.xml\"/\u003e"
}

View File

@ -12,10 +12,6 @@
"displayed": "LinkedPhaseTracker",
"normalized": "linkedphasetracker"
},
{
"displayed": "chaosBag",
"normalized": "chaosBag"
},
{
"displayed": "displacement_excluded",
"normalized": "displacement_excluded"
@ -60,10 +56,6 @@
"displayed": "chaosBag",
"normalized": "chaosbag"
},
{
"displayed": "arkham_setup_memory_object",
"normalized": "arkham_setup_memory_object"
},
{
"displayed": "ActionToken",
"normalized": "actiontoken"
@ -72,10 +64,6 @@
"displayed": "LargeBox",
"normalized": "largebox"
},
{
"displayed": "SoundCube",
"normalized": "soundcube"
},
{
"displayed": "CampaignBox",
"normalized": "campaignbox"
@ -83,10 +71,6 @@
{
"displayed": "CameraZoom_ignore",
"normalized": "camerazoom_ignore"
},
{
"displayed": "TokenArranger",
"normalized": "tokenarranger"
}
]
}

View File

@ -218,5 +218,20 @@
"Name": "FinnIcon",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2037357792052848566/5DA900C430E97D3DFF2C9B8A3DB1CB2271791FC7/"
},
{
"Name": "box-cover-mask-small",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2115061298536631564/F29C2ED9DD8431A1D1E21C7FFAFF1FFBC0AF0BF3/"
},
{
"Name": "box-cover-mask-big",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2115061298536631429/D075D2EECE6EE091AD3BEA5800DEF9C7B02B745B/"
},
{
"Name": "box-cover-mask-wide",
"Type": 0,
"URL": "http://cloud-3.steamusercontent.com/ugc/2115061298538827369/A20C2ECB8ECDC1B0AD8B2B38F68CA1C1F5E07D37/"
}
]

View File

@ -1557,7 +1557,14 @@
"StallforTime.7b6ed1",
"WrongPlaceRightTime.d5944e",
"SparrowMask.975d79",
"Pitchfork.45a724"
"Pitchfork.45a724",
"JimsTrumpet.7dfd5f",
"JimCulverParallel.72bf31",
"JimCulverParallelFront.c5fc80",
"VengefulShade.73bc8e",
"FinalRhapsody.561775",
"JimCulverParallelBack.aba863",
"TheBeyond.37ab47"
],
"ContainedObjects_path": "AllPlayerCards.15bb07",
"Description": "",

View File

@ -0,0 +1,8 @@
{
"id": "90051",
"type": "Treachery",
"class": "Neutral",
"traits": "Endtimes.",
"weakness": true,
"cycle": "The Dunwich Legacy"
}

View File

@ -0,0 +1,61 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 847001,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8470": {
"BackIsHidden": false,
"BackURL": "https://i.imgur.com/EcbhVuh.jpg/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/2149964195986880793/517FBB4FF8F72900B9E123DB865BCAD625F6506C/",
"NumHeight": 2,
"NumWidth": 2,
"Type": 0,
"UniqueBack": false
}
},
"Description": "Advanced",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/FinalRhapsody.561775.gmnotes",
"GUID": "561775",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": true,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Final Rhapsody",
"SidewaysCard": false,
"Snap": true,
"Sticky": true,
"Tags": [
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 78.419,
"posY": 3.193,
"posZ": 23.541,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 1,
"scaleY": 1,
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
}

View File

@ -0,0 +1,11 @@
{
"id": "02004-p",
"type": "Investigator",
"class": "Mystic",
"traits": "Performer. Cursed.",
"willpowerIcons": 4,
"intellectIcons": 3,
"combatIcons": 3,
"agilityIcons": 2,
"cycle": "The Dunwich Legacy"
}

View File

@ -0,0 +1,62 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 846700,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8467": {
"BackIsHidden": false,
"BackURL": "http://cloud-3.steamusercontent.com/ugc/2149964195987018702/54C63785F3AA474F635F58BC506C86A318432BD7/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/2149964195987018793/0AED4BF62C4FF3206778AD36FDB9C8E482CD3F9E/",
"NumHeight": 2,
"NumWidth": 4,
"Type": 0,
"UniqueBack": true
}
},
"Description": "The Musician",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/JimCulverParallel.72bf31.gmnotes",
"GUID": "72bf31",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Jim Culver (Parallel)",
"SidewaysCard": true,
"Snap": true,
"Sticky": true,
"Tags": [
"Investigator",
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 82.182,
"posY": 3.193,
"posZ": 26.386,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
"scaleX": 1.15,
"scaleY": 1,
"scaleZ": 1.15
},
"Value": 0,
"XmlUI": ""
}

View File

@ -0,0 +1,11 @@
{
"id": "02004-pb",
"type": "Investigator",
"class": "Mystic",
"traits": "Performer.",
"willpowerIcons": 4,
"intellectIcons": 3,
"combatIcons": 3,
"agilityIcons": 2,
"cycle": "The Dunwich Legacy"
}

View File

@ -0,0 +1,62 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 846805,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8468": {
"BackIsHidden": false,
"BackURL": "http://cloud-3.steamusercontent.com/ugc/2149964195987018702/54C63785F3AA474F635F58BC506C86A318432BD7/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/1656727981627737050/3CFF9E3825033909543AD1CF843361D9243538EE/",
"NumHeight": 2,
"NumWidth": 4,
"Type": 0,
"UniqueBack": true
}
},
"Description": "The Musician",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/JimCulverParallelBack.aba863.gmnotes",
"GUID": "aba863",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Jim Culver (Parallel Back)",
"SidewaysCard": true,
"Snap": true,
"Sticky": true,
"Tags": [
"Investigator",
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 82.206,
"posY": 3.193,
"posZ": 18.459,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
"scaleX": 1.15,
"scaleY": 1,
"scaleZ": 1.15
},
"Value": 0,
"XmlUI": ""
}

View File

@ -0,0 +1,11 @@
{
"id": "02004-pf",
"type": "Investigator",
"class": "Mystic",
"traits": "Performer. Cursed.",
"willpowerIcons": 4,
"intellectIcons": 3,
"combatIcons": 3,
"agilityIcons": 2,
"cycle": "The Dunwich Legacy"
}

View File

@ -0,0 +1,62 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 846905,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8469": {
"BackIsHidden": false,
"BackURL": "http://cloud-3.steamusercontent.com/ugc/1656727981627737648/F371339538812F68E38AAC0D520C525250DAC5C0/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/2149964195987018793/0AED4BF62C4FF3206778AD36FDB9C8E482CD3F9E/",
"NumHeight": 2,
"NumWidth": 4,
"Type": 0,
"UniqueBack": true
}
},
"Description": "The Musician",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/JimCulverParallelFront.c5fc80.gmnotes",
"GUID": "c5fc80",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Jim Culver (Parallel Front)",
"SidewaysCard": true,
"Snap": true,
"Sticky": true,
"Tags": [
"Investigator",
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 82.087,
"posY": 3.193,
"posZ": 22.484,
"rotX": 0,
"rotY": 180,
"rotZ": 0,
"scaleX": 1.15,
"scaleY": 1,
"scaleZ": 1.15
},
"Value": 0,
"XmlUI": ""
}

View File

@ -22,7 +22,7 @@
"UniqueBack": false
}
},
"Description": "",
"Description": "The Dead Listen",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/JimsTrumpet.03c6a7.gmnotes",
"GUID": "03c6a7",

View File

@ -0,0 +1,10 @@
{
"id": "90050",
"type": "Asset",
"class": "Neutral",
"cost": 2,
"traits": "Item. Instrument. Relic.",
"willpowerIcons": 2,
"wildIcons": 2,
"cycle": "The Dunwich Legacy"
}

View File

@ -0,0 +1,62 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 847000,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8470": {
"BackIsHidden": false,
"BackURL": "https://i.imgur.com/EcbhVuh.jpg/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/2149964195986880793/517FBB4FF8F72900B9E123DB865BCAD625F6506C/",
"NumHeight": 2,
"NumWidth": 2,
"Type": 0,
"UniqueBack": false
}
},
"Description": "The Dead Speak (Advanced)",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/JimsTrumpet.7dfd5f.gmnotes",
"GUID": "7dfd5f",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": true,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Jim's Trumpet",
"SidewaysCard": false,
"Snap": true,
"Sticky": true,
"Tags": [
"Asset",
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 78.511,
"posY": 3.229,
"posZ": 27.011,
"rotX": 359,
"rotY": 270,
"rotZ": 0,
"scaleX": 1,
"scaleY": 1,
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
}

View File

@ -0,0 +1,8 @@
{
"id": "90052",
"type": "Asset",
"class": "Neutral",
"permanent": true,
"startsInPlay": true,
"cycle": "Laid to Rest"
}

View File

@ -0,0 +1,62 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 847100,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8471": {
"BackIsHidden": true,
"BackURL": "http://cloud-3.steamusercontent.com/ugc/2149964195986881059/864F6EBBD2900ED6D145B8AF12F2F8C180375C83/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/2149964195986880989/13172BC914A0D729B4EFD9845FA9FDFCAB6F77F7/",
"NumHeight": 1,
"NumWidth": 1,
"Type": 0,
"UniqueBack": false
}
},
"Description": "Bleak Netherworld",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/TheBeyond.37ab47.gmnotes",
"GUID": "37ab47",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": true,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "CardCustom",
"Nickname": "The Beyond",
"SidewaysCard": false,
"Snap": true,
"Sticky": true,
"Tags": [
"Asset",
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 78.532,
"posY": 3.193,
"posZ": 17.888,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 1,
"scaleY": 1,
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
}

View File

@ -0,0 +1,8 @@
{
"id": "90053",
"type": "Enemy",
"class": "Neutral",
"traits": "Monster. Geist",
"weakness": true,
"cycle": "Laid to Rest"
}

View File

@ -0,0 +1,61 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"CardID": 847002,
"ColorDiffuse": {
"b": 0.71324,
"g": 0.71324,
"r": 0.71324
},
"CustomDeck": {
"8470": {
"BackIsHidden": false,
"BackURL": "https://i.imgur.com/EcbhVuh.jpg/",
"FaceURL": "http://cloud-3.steamusercontent.com/ugc/2149964195986880793/517FBB4FF8F72900B9E123DB865BCAD625F6506C/",
"NumHeight": 2,
"NumWidth": 2,
"Type": 0,
"UniqueBack": false
}
},
"Description": "",
"DragSelectable": true,
"GMNotes_path": "AllPlayerCards.15bb07/VengefulShade.73bc8e.gmnotes",
"GUID": "73bc8e",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": true,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Card",
"Nickname": "Vengeful Shade",
"SidewaysCard": false,
"Snap": true,
"Sticky": true,
"Tags": [
"PlayerCard"
],
"Tooltip": true,
"Transform": {
"posX": 78.455,
"posY": 3.193,
"posZ": 20.547,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 1,
"scaleY": 1,
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
}

View File

@ -2,7 +2,7 @@
"id": "01005-p",
"type": "Investigator",
"class": "Survivor",
"traits": "Drifter.",
"traits": "Drifter. Blessed. Cursed.",
"willpowerIcons": 4,
"intellectIcons": 3,
"combatIcons": 1,

View File

@ -2,7 +2,7 @@
"id": "01005-pf",
"type": "Investigator",
"class": "Survivor",
"traits": "Drifter.",
"traits": "Drifter. Blessed. Cursed.",
"willpowerIcons": 4,
"intellectIcons": 3,
"combatIcons": 1,

View File

@ -28,9 +28,6 @@
"Nickname": "Arkham Deck Cutter",
"Snap": true,
"Sticky": true,
"Tags": [
"arkham_setup_memory_object"
],
"Tooltip": true,
"Transform": {
"posX": 78,

View File

@ -67,6 +67,7 @@
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore",
"displacement_excluded"
],
"Tooltip": true,

View File

@ -44,6 +44,9 @@
"Nickname": "Clue tokens",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": true,
"Transform": {
"posX": 2.857,

View File

@ -83,4 +83,4 @@
},
"Value": 0,
"XmlUI": ""
}
}

View File

@ -49,6 +49,9 @@
"Nickname": "Cartoon Investigators",
"Snap": true,
"Sticky": true,
"Tags": [
"LargeBox"
],
"Tooltip": true,
"Transform": {
"posX": -23.615,
@ -61,9 +64,6 @@
"scaleY": 0.14,
"scaleZ": 1
},
"Tags": [
"LargeBox"
],
"Value": 0,
"XmlUI": ""
}

View File

@ -83,4 +83,4 @@
},
"Value": 0,
"XmlUI": ""
}
}

View File

@ -49,6 +49,9 @@
"Nickname": "The Ghosts Of Onigawa (Investigator Expansion)",
"Snap": true,
"Sticky": true,
"Tags": [
"LargeBox"
],
"Tooltip": true,
"Transform": {
"posX": -47.192,
@ -61,9 +64,6 @@
"scaleY": 0.14,
"scaleZ": 1
},
"Tags": [
"LargeBox"
],
"Value": 0,
"XmlUI": ""
}

View File

@ -83,4 +83,4 @@
},
"Value": 0,
"XmlUI": ""
}
}

View File

@ -49,6 +49,9 @@
"Nickname": "The Sands Of Memphis (Investigator Expansion)",
"Snap": true,
"Sticky": true,
"Tags": [
"LargeBox"
],
"Tooltip": true,
"Transform": {
"posX": -47.192,
@ -61,9 +64,6 @@
"scaleY": 0.14,
"scaleZ": 1
},
"Tags": [
"LargeBox"
],
"Value": 0,
"XmlUI": ""
}
}

View File

@ -44,6 +44,9 @@
"Nickname": "Connection markers",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": true,
"Transform": {
"posX": -51,

View File

@ -67,6 +67,7 @@
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore",
"displacement_excluded"
],
"Tooltip": true,

View File

@ -40,6 +40,9 @@
"Nickname": "Doom Counter",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": true,
"Transform": {
"posX": -5.3,

View File

@ -44,6 +44,9 @@
"Nickname": "Doom tokens",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": true,
"Transform": {
"posX": -55.48,

View File

@ -44,6 +44,9 @@
"Nickname": "Doom tokens",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": true,
"Transform": {
"posX": 2.761,

View File

@ -54,4 +54,4 @@
},
"Value": 0,
"XmlUI": ""
}
}

View File

@ -54,4 +54,4 @@
},
"Value": 0,
"XmlUI": ""
}
}

View File

@ -73,4 +73,4 @@
},
"Value": 0,
"XmlUI": ""
}
}

View File

@ -0,0 +1,45 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"ColorDiffuse": {
"b": 0.116,
"g": 0.116,
"r": 0.716
},
"Description": "This object handles GUID references to objects.",
"DragSelectable": true,
"GMNotes": "",
"GUID": "123456",
"Grid": true,
"GridProjection": false,
"Hands": false,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": true,
"LuaScript": "require(\"core/GUIDReferenceHandler\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "go_game_piece_white",
"Nickname": "GUID Reference Handler",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": 78,
"posY": 1.328,
"posZ": -8,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
"scaleX": 1,
"scaleY": 1,
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
}

View File

@ -19,7 +19,7 @@
},
"ImageScalar": 1,
"ImageSecondaryURL": "",
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/1758068501357164917/1D06F1DC4D6888B6F57124BD2AFE20D0B0DA15A8/",
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/784129913444610342/7903BA89870C1656A003FD69C79BFA99BD1AAC24/",
"WidthScale": 0
},
"Description": "Click to remove all clues from all investigators",
@ -37,9 +37,12 @@
"LuaScriptState": "false",
"MeasureMovement": false,
"Name": "Custom_Token",
"Nickname": "Master Clue Counter\n",
"Nickname": "Master Clue Counter",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": true,
"Transform": {
"posX": -5.3,

View File

@ -66,6 +66,9 @@
"Nickname": "Mythos Area",
"Snap": true,
"Sticky": true,
"Tags": [
"CleanUpHelper_ignore"
],
"Tooltip": false,
"Transform": {
"posX": -1.309,

View File

@ -14,11 +14,12 @@
"r": 1
},
"ContainedObjects_order": [
"BadBlood.451eaa",
"RedTideRising.5302f2",
"AllorNothing.72ab92",
"BadBlood.451eaa",
"BytheBook.cc7eb3",
"LaidtoRest.e2dd57",
"ReadorDie.9e73fa",
"BytheBook.cc7eb3"
"RedTideRising.5302f2"
],
"ContainedObjects_path": "ChallengeScenarios.9f6801",
"CustomMesh": {
@ -64,7 +65,7 @@
"Tooltip": true,
"Transform": {
"posX": -9,
"posY": 1.481,
"posY": 1.482,
"posZ": -76,
"rotX": 0,
"rotY": 270,

View File

@ -1 +1 @@
{"ml":{"451eaa":{"lock":false,"pos":{"x":12.2499580383301,"y":1.46560525894165,"z":3.98636198043823},"rot":{"x":359.920135498047,"y":269.999908447266,"z":0.016873624175787}},"5302f2":{"lock":false,"pos":{"x":12.2504663467407,"y":1.45853757858276,"z":-20.013650894165},"rot":{"x":359.920135498047,"y":270.00146484375,"z":0.0168716721236706}},"72ab92":{"lock":false,"pos":{"x":12.2520532608032,"y":1.4679582118988,"z":11.9863719940186},"rot":{"x":359.920135498047,"y":270,"z":0.0168737415224314}},"9e73fa":{"lock":false,"pos":{"x":12.2500581741333,"y":1.46089386940002,"z":-12.0136384963989},"rot":{"x":359.920135498047,"y":269.999847412109,"z":0.0168744903057814}},"cc7eb3":{"lock":false,"pos":{"x":12.2495565414429,"y":1.46325027942657,"z":-4.01364088058472},"rot":{"x":359.920135498047,"y":269.999908447266,"z":0.0168744102120399}}}}
{"ml":{"451eaa":{"lock":false,"pos":{"x":12.252,"y":1.4815,"z":11.986},"rot":{"x":0,"y":269.9999,"z":0}},"5302f2":{"lock":false,"pos":{"x":12.2505,"y":1.4815,"z":-20.0137},"rot":{"x":0,"y":270.0014,"z":0}},"72ab92":{"lock":false,"pos":{"x":12.25,"y":1.4815,"z":19.986},"rot":{"x":0,"y":269.9999,"z":0}},"9e73fa":{"lock":false,"pos":{"x":12.2501,"y":1.4815,"z":-12.0137},"rot":{"x":0,"y":269.9998,"z":0}},"cc7eb3":{"lock":false,"pos":{"x":12.25,"y":1.4815,"z":3.986},"rot":{"x":0,"y":269.9999,"z":0}},"e2dd57":{"lock":false,"pos":{"x":12.25,"y":1.4815,"z":-4.014},"rot":{"x":0,"y":270,"z":0}}}}

View File

@ -71,9 +71,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": 12.252,
"posY": 1.468,
"posZ": 11.986,
"posX": 12.25,
"posY": 1.481,
"posZ": 19.986,
"rotX": 0,
"rotY": 270,
"rotZ": 0,

View File

@ -71,9 +71,9 @@
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": 12.25,
"posY": 1.466,
"posZ": 3.986,
"posX": 12.252,
"posY": 1.481,
"posZ": 11.986,
"rotX": 0,
"rotY": 270,
"rotZ": 0,

View File

@ -72,8 +72,8 @@
"Tooltip": true,
"Transform": {
"posX": 12.25,
"posY": 1.463,
"posZ": -4.014,
"posY": 1.481,
"posZ": 3.986,
"rotX": 0,
"rotY": 270,
"rotZ": 0,

View File

@ -0,0 +1,86 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"AttachedDecals": [
{
"CustomDecal": {
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/959719855119695911/931B9829687A20F4DEADB36DA57B7E6D76792231/",
"Name": "dunwich_back",
"Size": 7.4
},
"Transform": {
"posX": 0,
"posY": 0,
"posZ": 0,
"rotX": 270,
"rotY": 0,
"rotZ": 0,
"scaleX": 2,
"scaleY": 2,
"scaleZ": 2
}
}
],
"Autoraise": true,
"ColorDiffuse": {
"a": 0.27451,
"b": 1,
"g": 1,
"r": 1
},
"CustomMesh": {
"CastShadows": true,
"ColliderURL": "",
"Convex": true,
"CustomShader": {
"FresnelStrength": 0,
"SpecularColor": {
"b": 1,
"g": 1,
"r": 1
},
"SpecularIntensity": 0,
"SpecularSharpness": 2
},
"DiffuseURL": "http://cloud-3.steamusercontent.com/ugc/2115061845788468343/B7611EC7DCD2008B87D6518EBEFF0AD36EFE5B54/",
"MaterialIndex": 3,
"MeshURL": "https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/advboxes/tuckbox_h_MSH.obj",
"NormalURL": "",
"TypeIndex": 0
},
"Description": "Challenge Scenario",
"DragSelectable": true,
"GMNotes": "scenarios/challenge_laid_to_rest.json",
"GUID": "e2dd57",
"Grid": true,
"GridProjection": false,
"Hands": true,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": false,
"LuaScript": "require(\"core/DownloadBox\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "Custom_Model",
"Nickname": "Laid to Rest",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": 12.25,
"posY": 1.481,
"posZ": -4.014,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 2.21,
"scaleY": 0.46,
"scaleZ": 2.42
},
"Value": 0,
"XmlUI": ""
}

View File

@ -72,7 +72,7 @@
"Tooltip": true,
"Transform": {
"posX": 12.25,
"posY": 1.461,
"posY": 1.481,
"posZ": -12.014,
"rotX": 0,
"rotY": 270,

View File

@ -72,7 +72,7 @@
"Tooltip": true,
"Transform": {
"posX": 12.25,
"posY": 1.459,
"posY": 1.481,
"posZ": -20.014,
"rotX": 0,
"rotY": 270,

View File

@ -0,0 +1,45 @@
{
"AltLookAngle": {
"x": 0,
"y": 0,
"z": 0
},
"Autoraise": true,
"ColorDiffuse": {
"b": 0.82353,
"g": 0.20157,
"r": 0
},
"Description": "This dummy is there to hold the up-to-date script file for placeholder boxes to be available for placeholder box spawning.",
"DragSelectable": true,
"GMNotes": "",
"GUID": "a93466",
"Grid": true,
"GridProjection": false,
"Hands": false,
"HideWhenFaceDown": false,
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": true,
"LuaScript": "require(\"core/DownloadBox\")",
"LuaScriptState": "",
"MeasureMovement": false,
"Name": "BlockRectangle",
"Nickname": "Placeholder Box Dummy",
"Snap": true,
"Sticky": true,
"Tooltip": true,
"Transform": {
"posX": 78,
"posY": 1.645,
"posZ": -33,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
"scaleX": 1,
"scaleY": 1,
"scaleZ": 1
},
"Value": 0,
"XmlUI": ""
}

View File

@ -974,7 +974,7 @@
"LuaScriptState": "{\"trackedLocations\":[]}",
"MeasureMovement": false,
"Name": "Custom_Token",
"Nickname": "Playarea",
"Nickname": "Play Area",
"Snap": true,
"Sticky": true,
"Tags": [

View File

@ -343,9 +343,10 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": true,
"LuaScript": "require(\"playermat/Playmat\")",
"LuaScriptState_path": "Playermat1White.8b081b.luascriptstate",
"LuaScript_path": "Playermat1White.8b081b.ttslua",
"MeasureMovement": false,
"Memo": "White",
"Name": "Custom_Tile",
"Nickname": "Playermat 1: White",
"Snap": true,

View File

@ -1,11 +0,0 @@
---------------------------------------------------------
-- specific setup (different for each playmat)
---------------------------------------------------------
TRASHCAN_GUID = "147e80"
STAT_TRACKER_GUID = "e598c2"
RESOURCE_COUNTER_GUID = "4406f0"
CLUE_COUNTER_GUID = "d86b7c"
CLUE_CLICKER_GUID = "db85d6"
require("playermat/Playmat")

View File

@ -343,9 +343,10 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": true,
"LuaScript": "require(\"playermat/Playmat\")",
"LuaScriptState_path": "Playermat2Orange.bd0ff4.luascriptstate",
"LuaScript_path": "Playermat2Orange.bd0ff4.ttslua",
"MeasureMovement": false,
"Memo": "Orange",
"Name": "Custom_Tile",
"Nickname": "Playermat 2: Orange",
"Snap": true,

View File

@ -1,11 +0,0 @@
---------------------------------------------------------
-- specific setup (different for each playmat)
---------------------------------------------------------
TRASHCAN_GUID = "f7b6c8"
STAT_TRACKER_GUID = "b4a5f7"
RESOURCE_COUNTER_GUID = "816d84"
CLUE_COUNTER_GUID = "1769ed"
CLUE_CLICKER_GUID = "3f22e5"
require("playermat/Playmat")

View File

@ -343,9 +343,10 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": true,
"LuaScript": "require(\"playermat/Playmat\")",
"LuaScriptState_path": "Playermat3Green.383d8b.luascriptstate",
"LuaScript_path": "Playermat3Green.383d8b.ttslua",
"MeasureMovement": false,
"Memo": "Green",
"Name": "Custom_Tile",
"Nickname": "Playermat 3: Green",
"Snap": true,

View File

@ -1,11 +0,0 @@
---------------------------------------------------------
-- specific setup (different for each playmat)
---------------------------------------------------------
TRASHCAN_GUID = "5f896a"
STAT_TRACKER_GUID = "af7ed7"
RESOURCE_COUNTER_GUID = "cd15ac"
CLUE_COUNTER_GUID = "032300"
CLUE_CLICKER_GUID = "891403"
require("playermat/Playmat")

View File

@ -343,9 +343,10 @@
"IgnoreFoW": false,
"LayoutGroupSortIndex": 0,
"Locked": true,
"LuaScript": "require(\"playermat/Playmat\")",
"LuaScriptState_path": "Playermat4Red.0840d5.luascriptstate",
"LuaScript_path": "Playermat4Red.0840d5.ttslua",
"MeasureMovement": false,
"Memo": "Red",
"Name": "Custom_Tile",
"Nickname": "Playermat 4: Red",
"Snap": true,

View File

@ -1,11 +0,0 @@
---------------------------------------------------------
-- specific setup (different for each playmat)
---------------------------------------------------------
TRASHCAN_GUID = "4b8594"
STAT_TRACKER_GUID = "e74881"
RESOURCE_COUNTER_GUID = "a4b60d"
CLUE_COUNTER_GUID = "37be78"
CLUE_CLICKER_GUID = "4111de"
require("playermat/Playmat")

View File

@ -14,7 +14,7 @@
"PDFPage": 0,
"PDFPageOffset": 0,
"PDFPassword": "",
"PDFUrl": "https://images-cdn.fantasyflightgames.com/filer_public/c4/b0/c4b0d66c-d79e-411b-bdb5-b5d8c457d4bc/ahc01_rules_reference_web.pdf"
"PDFUrl": "http://cloud-3.steamusercontent.com/ugc/2115061845793806189/6FC67F9AF9224452E2D8F25E63B88D702B21B0DC/"
},
"Description": "",
"DragSelectable": true,

View File

@ -35,9 +35,6 @@
"Nickname": "SoundCube",
"Snap": true,
"Sticky": true,
"Tags": [
"SoundCube"
],
"Tooltip": true,
"Transform": {
"posX": 78,

View File

@ -40,9 +40,6 @@
"Nickname": "Token Arranger",
"Snap": true,
"Sticky": true,
"Tags": [
"TokenArranger"
],
"Tooltip": true,
"Transform": {
"posX": -42.3,

View File

@ -1,11 +1,12 @@
local blessCurseApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local deckImporterApi = require("arkhamdb/DeckImporterApi")
local optionPanelApi = require("core/OptionPanelApi")
local playAreaApi = require("core/PlayAreaApi")
local blessCurseApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local deckImporterApi = require("arkhamdb/DeckImporterApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local optionPanelApi = require("core/OptionPanelApi")
local playAreaApi = require("core/PlayAreaApi")
local playmatApi = require("playermat/PlaymatApi")
local campaignTokenData = {
GUID = "51b1c9",
Name = "Custom_Model",
Transform = {
posX = -21.25,
@ -18,9 +19,7 @@ local campaignTokenData = {
scaleY = 2,
scaleZ = 2
},
Nickname = "Arkham Coin",
Description = "SCED Importer Token",
GMNotes = "",
Tags = {
"ImporterToken"
},
@ -33,56 +32,48 @@ local campaignTokenData = {
MaterialIndex = 2,
TypeIndex = 0,
CustomShader = {
SpecularColor = {
r = 0.7222887,
g = 0.507659256,
b = 0.339915335
},
SpecularIntensity = 0.4,
SpecularSharpness = 7.0,
FresnelStrength = 0.0
SpecularColor = {
r = 0.7222887,
g = 0.507659256,
b = 0.339915335
},
SpecularIntensity = 0.4,
SpecularSharpness = 7.0,
FresnelStrength = 0.0
},
CastShadows = true
}
}
local COLORS = { "White", "Orange", "Green", "Red" }
-- counter GUIDS (4x damage and 4x horror)
local DAMAGE_HORROR_GUIDS = {
"eb08d6"; "e64eec"; "1f5a0a"; "591a45";
"468e88"; "0257d9"; "7b5729"; "beb964";
}
local TOUR_GUID = "0e5aa8"
local campaignBoxGUID
function onLoad(save_state)
campaignBoxGUID = ""
function onLoad()
self.createButton({
click_function = "findCampaignFromToken",
function_owner = self,
label = "Import",
tooltip = "Load in a campaign save from a token!\n\n(Token can be anywhere on the table, but ensure there is only 1!)",
position = {x=-1, y=0.2, z=0},
tooltip = "Load in a campaign save from a token!\n\n(Token can be anywhere on the table, but ensure there is only 1!)",
position = { x = -1, y = 0.2, z = 0 },
font_size = 400,
width = 1400,
height = 600,
scale = {0.5, 1, 0.5},
scale = { 0.5, 1, 0.5 },
})
self.createButton({
click_function = "createCampaignToken",
function_owner = self,
label = "Export",
tooltip = "Create a campaign save token!\n\n(Ensure all chaos tokens have been unsealed!)",
position = {x=1, y=0.2, z=0},
position = { x = 1, y = 0.2, z = 0 },
font_size = 400,
width = 1400,
height = 600,
scale = {0.5, 1, 0.5},
scale = { 0.5, 1, 0.5 },
})
end
-- The main import functions. Due to timing concerns, has to be split up into several separate methods to allow for Wait conditions
---------------------------------------------------------
-- main import functions (split up to allow for Wait conditions)
---------------------------------------------------------
-- Identifies import token, determines campaign box and downloads it (if needed)
function findCampaignFromToken(_, _, _)
@ -91,11 +82,13 @@ function findCampaignFromToken(_, _, _)
if #coinObjects == 0 then
broadcastToAll("Could not find importer token", Color.Red)
elseif #coinObjects > 1 then
broadcastToAll("More than 1 importer token found. Please delete all but 1 importer token", Color.Yellow)
broadcastToAll("More than 1 importer token found. Please delete all but 1 importer token", Color.Yellow)
else
coin = coinObjects[1]
local importData = JSON.decode(coin.getGMNotes())
campaignBoxGUID = importData["box"]
local campaignBox = getObjectFromGUID(campaignBoxGUID)
if campaignBox.type == "Generic" then
campaignBox.call("buttonClick_download")
@ -110,24 +103,24 @@ function findCampaignFromToken(_, _, _)
end,
function()
local obj = getObjectFromGUID(campaignBoxGUID)
if obj == nil then
return false
if obj == nil then
return false
else
return obj.type == "Bag" and obj.getLuaScript() ~= ""
end
end,
2,
function() broadcastToAll("Error loading campaign box") end
)
)
end
end
-- After box has been downloaded, places content on table
function placeCampaignFromToken(importData)
getObjectFromGUID(campaignBoxGUID).call("buttonClick_place")
getObjectFromGUID(importData["box"]).call("buttonClick_place")
Wait.condition(
function() createCampaignFromToken(importData) end,
function() return findCampaignLog() ~= nil end,
function() return findUniqueObjectWithTag("CampaignLog") ~= nil end,
2,
function() broadcastToAll("Error placing campaign box") end
)
@ -135,82 +128,86 @@ end
-- After content is placed on table, conducts all the other import operations
function createCampaignFromToken(importData)
findCampaignLog().destruct()
--create campaign log
spawnObjectData({data = importData["log"]})
--create chaos bag
-- destroy existing campaign log and load saved campaign log
findUniqueObjectWithTag("CampaignLog").destruct()
spawnObjectData({ data = importData["log"] })
chaosBagApi.setChaosBagState(importData["bag"])
--populate trauma values
-- populate trauma values
if importData["trauma"] then
updateCounters(importData["trauma"])
setTrauma(importData["trauma"])
end
--populate ArkhamDB deck IDs
-- populate ArkhamDB deck IDs
if importData["decks"] then
deckImporterApi.setUiState(importData["decks"])
end
--set investigator count
playAreaApi.setInvestigatorCount(importData["clueCount"])
--set campaign guide page
local guide = findCampaignGuide()
-- set campaign guide page
local guide = findUniqueObjectWithTag("CampaignGuide")
if guide then
Wait.condition(
-- Called after the condition function returns true
function()
log("Campaign Guide import successful!")
end,
-- Condition function that is called continiously until returs true or timeout is reached
function()
guide.Book.setPage(importData["guide"])
return guide.Book.getPage() == importData["guide"]
end,
-- Called after the condition function returns true
function() log("Campaign Guide import successful!") end,
-- Condition function that is called continiously until returs true or timeout is reached
function() return guide.Book.setPage(importData["guide"]) end,
-- Amount of time in seconds until the Wait times out
1,
-- Called if the Wait times out
function()
log("Campaign Guide import failed!")
end
function() log("Campaign Guide import failed!") end
)
end
Wait.time(
function() optionPanelApi.loadSettings(importData["options"]) end,
0.5
)
getObjectFromGUID(TOUR_GUID).destruct()
Wait.time(function() optionPanelApi.loadSettings(importData["options"]) end, 0.5)
-- destroy Tour Starter token
local tourStarter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TourStarter")
tourStarter.destruct()
-- restore PlayArea image
playAreaApi.updateSurface(importData["playmat"])
broadcastToAll("Campaign successfully imported!", Color.Green)
end
-- Creates a campaign token with save data encoded into GM Notes based on the current state of the table
function createCampaignToken(_, playerColor, _)
-- clean up chaos tokens
blessCurseApi.removeAll(playerColor)
chaosBagApi.releaseAllSealedTokens(playerColor)
local campaignBoxGUID = ""
-- find active campaign
local campaignBox
for _, obj in ipairs(getObjectsWithTag("CampaignBox")) do
if obj.type == "Bag" and #obj.getObjects() == 0 then
if campaignBoxGUID ~= "" then
if not campaignBox then
campaignBox = obj
else
broadcastToAll("Multiple empty campaign box detected; delete all but one.", Color.Red)
return
end
campaignBoxGUID = obj.getGUID()
end
end
if campaignBoxGUID == "" then
if not campaignBox then
broadcastToAll("Campaign box with all placed objects not found!", Color.Red)
return
end
local campaignLog = findCampaignLog()
local campaignLog = findUniqueObjectWithTag("CampaignLog")
if campaignLog == nil then
broadcastToAll("Campaign log not found!", Color.Red)
return
end
local traumaValues = nil
local traumaValues = {
0, 0, 0, 0,
0, 0, 0, 0
}
local counterData = campaignLog.getVar("ref_buttonData")
if counterData ~= nil then
traumaValues = {}
printToAll("Trauma values found in campaign log!", "Green")
for i = 1, 10, 3 do
traumaValues[1 + (i - 1) / 3] = counterData.counter[i].value
@ -220,78 +217,49 @@ function createCampaignToken(_, playerColor, _)
printToAll("Trauma values could not be found in campaign log!", "Yellow")
printToAll("Default values for health and sanity loaded.", "Yellow")
end
local campaignGuide = findCampaignGuide()
local campaignGuide = findUniqueObjectWithTag("CampaignGuide")
if campaignGuide == nil then
broadcastToAll("Campaign guide not found!", Color.Red)
return
end
local campaignGuidePage = campaignGuide.Book.getPage()
local campaignData = {
box = campaignBoxGUID,
box = campaignBox.getGUID(),
log = campaignLog.getData(),
bag = chaosBagApi.getChaosBagState(),
trauma = traumaValues,
decks = deckImporterApi.getUiState(),
clueCount = playAreaApi.getInvestigatorCount(),
guide = campaignGuidePage,
guide = campaignGuide.Book.getPage(),
options = optionPanelApi.getOptions(),
playmat = playAreaApi.getSurface()
}
campaignTokenData.GMNotes = JSON.encode(campaignData)
campaignTokenData.Nickname = os.date("%b %d ") .. getObjectFromGUID(campaignBoxGUID).getName() .. " Save"
spawnObjectData({
data = campaignTokenData,
position = {-21.25, 1.68, 55.59}
})
campaignTokenData.Nickname = os.date("%b %d ") .. campaignBox.getName() .. " Save"
spawnObjectData({ data = campaignTokenData })
broadcastToAll("Campaign successfully exported! Save coin object to import on a fresh save", Color.Green)
end
---------------------------------------------------------
-- helper functions
---------------------------------------------------------
function findCampaignLog()
local campaignLog = getObjectsWithTag("CampaignLog")
if campaignLog then
if #campaignLog == 1 then
return campaignLog[1]
else
broadcastToAll("More than 1 campaign log detected; delete all but one.", Color.Red)
return nil
end
function findUniqueObjectWithTag(tag)
local objects = getObjectsWithTag(tag)
if not objects then return end
if #objects == 1 then
return objects[1]
else
broadcastToAll("More than 1 " .. tag .. " detected; delete all but one.", Color.Red)
return nil
end
end
function findCampaignGuide()
local campaignGuide = getObjectsWithTag("CampaignGuide")
if campaignGuide then
if #campaignGuide == 1 then
return campaignGuide[1]
else
broadcastToAll("More than 1 campaign guide detected; delete all but one.", Color.Red)
return nil
end
else
return nil
end
end
function updateCounters(tableOfNewValues)
if tonumber(tableOfNewValues) then
local value = tableOfNewValues
tableOfNewValues = {}
for i = 1, #DAMAGE_HORROR_GUIDS do
table.insert(tableOfNewValues, value)
end
end
for i, guid in ipairs(DAMAGE_HORROR_GUIDS) do
local TOKEN = getObjectFromGUID(guid)
if TOKEN ~= nil then
TOKEN.call("updateVal", tableOfNewValues[i])
else
printToAll(": No. " .. i .. " could not be found.", "Yellow")
end
function setTrauma(trauma)
for i = 1, 4 do
playmatApi.updateCounter(COLORS[i], "DamageCounter", trauma[i])
playmatApi.updateCounter(COLORS[i], "HorrorCounter", trauma[i + 4])
end
end

View File

@ -3,79 +3,24 @@
- puts everything on playmats and hands into respective trashcans
- use the IGNORE_TAG to exclude objects from tidying (default: "CleanUpHelper_Ignore")]]
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local playAreaApi = require("core/PlayAreaApi")
local playmatApi = require("playermat/PlaymatApi")
local soundCubeApi = require("core/SoundCubeApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
-- these objects will be ignored
local IGNORE_GUIDS = {
-- big playmat, change image panel and investigator counter
"b7b45b", "f182ee", "721ba2",
-- bless/curse manager
"afa06b", "bd0253", "5933fb",
-- stuff on agenda/act playmat
"85c4c6", "4a3aa4", "fea079", "b015d8", "11e0cf", "9f334f", "70b9f6", "0a5a29",
-- doom/location token bag
"47ffc3", "170f10",
-- table
"4ee1f2"
}
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playAreaApi = require("core/PlayAreaApi")
local playmatApi = require("playermat/PlaymatApi")
local soundCubeApi = require("core/SoundCubeApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
-- objects with this tag will be ignored
local IGNORE_TAG = "CleanUpHelper_ignore"
-- colors and order for following tables
local COLORS = { "White", "Orange", "Green", "Red", "Agenda" }
-- counter GUIDS (4x damage and 4x horror)
local DAMAGE_HORROR_GUIDS = {
"eb08d6", "e64eec", "1f5a0a", "591a45",
"468e88", "0257d9", "7b5729", "beb964",
}
local COLORS = { "White", "Orange", "Green", "Red", "Mythos" }
local campaignLog
local RESET_VALUES = {}
-- GUIDS of objects (in order of ownership relating to 'COLORS')
local PLAYERMAT_GUIDS = { "8b081b", "bd0ff4", "383d8b", "0840d5" }
local RESOURCE_GUIDS = { "4406f0", "816d84", "cd15ac", "a4b60d" }
local TRACKER_GUIDS = { "e598c2", "b4a5f7", "af7ed7", "e74881" }
local CLUE_GUIDS = { "d86b7c", "1769ed", "032300", "37be78" }
local CLUE_CLICKER_GUIDS = { "db85d6", "3f22e5", "891403", "4111de" }
local TRASHCAN_GUIDS = { "147e80", "f7b6c8", "5f896a", "4b8594", "70b9f6" }
-- values for physics.cast (4 entries for player zones, 5th entry for agenda/act deck, 6th for campaign log)
local PHYSICS_POSITION = {
{ -54.5, 2, 21 },
{ -54.5, 2, -21 },
{ -27.0, 2, 26 },
{ -27.0, 2, -26 },
{ -02.0, 2, 10 },
{ -00.0, 2, -27 }
}
local PHYSICS_ROTATION = {
270,
270,
0,
180,
270,
0
}
local PHYSICS_SCALE = {
{ 36.6, 1, 14.5 },
{ 36.6, 1, 14.5 },
{ 34.0, 1, 14.5 },
{ 34.0, 1, 14.5 },
{ 55.0, 1, 13.5 },
{ 05.0, 1, 05.0 }
}
local loadingFailedBefore = false
local optionsVisible = false
local options = {}
options["importTrauma"] = true
options["tidyPlayermats"] = true
@ -84,7 +29,6 @@ options["removeDrawnLines"] = false
local buttonParameters = {}
buttonParameters.function_owner = self
local loadingFailedBefore = false
---------------------------------------------------------
-- option loading and GUI setup
@ -131,14 +75,6 @@ function onLoad(savedData)
buttonParameters.position.z = 1.1
buttonParameters.width = 1550
self.createButton(buttonParameters)
-- create single table for ignoring
for _, v in ipairs(CLUE_GUIDS) do table.insert(IGNORE_GUIDS, v) end
for _, v in ipairs(CLUE_CLICKER_GUIDS) do table.insert(IGNORE_GUIDS, v) end
for _, v in ipairs(RESOURCE_GUIDS) do table.insert(IGNORE_GUIDS, v) end
for _, v in ipairs(TRASHCAN_GUIDS) do table.insert(IGNORE_GUIDS, v) end
for _, v in ipairs(PLAYERMAT_GUIDS) do table.insert(IGNORE_GUIDS, v) end
for _, v in ipairs(DAMAGE_HORROR_GUIDS) do table.insert(IGNORE_GUIDS, v) end
end
---------------------------------------------------------
@ -178,13 +114,8 @@ function cleanUp(_, color)
getTrauma()
-- delay to account for potential state change
Wait.time(function()
updateCounters(RESOURCE_GUIDS, 5, "Resource")
updateCounters(CLUE_CLICKER_GUIDS, 0, "Clue clicker")
updateCounters(DAMAGE_HORROR_GUIDS, RESET_VALUES, "Damage / Horror")
end, 0.2)
Wait.time(updateCounters, 0.2)
resetSkillTrackers()
resetDoomCounter()
blessCurseManagerApi.removeAll(color)
removeLines()
@ -201,39 +132,20 @@ end
-- modular functions, called by other functions
---------------------------------------------------------
function updateCounters(tableOfGUIDs, newValues, info)
-- instead of a table, this will be used if just a single value is provided
local singleValue = tonumber(newValues)
function updateCounters()
playmatApi.updateCounter("All", "ResourceCounter" , 5)
playmatApi.updateCounter("All", "ClickableClueCounter" , 0)
playmatApi.resetSkillTracker("All")
for i, guid in ipairs(tableOfGUIDs) do
local TOKEN = getObjectFromGUID(guid)
local newValue = singleValue or newValues[i]
if TOKEN ~= nil then
TOKEN.call("updateVal", newValue)
else
printToAll(info .. ": No. " .. i .. " could not be found.", "Yellow")
end
end
end
-- set investigator skill trackers to "1, 1, 1, 1"
function resetSkillTrackers()
for i, guid in ipairs(TRACKER_GUIDS) do
local obj = getObjectFromGUID(guid)
if obj ~= nil then
obj.call("updateStats", { 1, 1, 1, 1 })
else
printToAll("Skill tracker for " .. COLORS[i] .. " playmat could not be found.", "Yellow")
end
for i = 1, 4 do
playmatApi.updateCounter(COLORS[i], "DamageCounter", RESET_VALUES.Damage[i])
playmatApi.updateCounter(COLORS[i], "HorrorCounter", RESET_VALUES.Horror[i])
end
end
-- reset doom on agenda
function resetDoomCounter()
local doomCounter = getObjectFromGUID("85c4c6")
local doomCounter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DoomCounter")
if doomCounter ~= nil then
doomCounter.call("updateVal")
else
@ -241,19 +153,19 @@ function resetDoomCounter()
end
end
-- gets the GUID of a custom data helper (if present) and adds it to the ignore list
-- adds the ignore tag to the custom data helper
function ignoreCustomDataHelper()
local customDataHelper = playAreaApi.getCustomDataHelper()
if customDataHelper then
table.insert(IGNORE_GUIDS, customDataHelper.getGUID())
customDataHelper.addTag(IGNORE_TAG)
end
end
-- read values for trauma from campaign log if enabled
function getTrauma()
RESET_VALUES = {
0, 0, 0, 0,
0, 0, 0, 0
Damage = { 0, 0, 0, 0 },
Horror = { 0, 0, 0, 0 }
}
-- stop here if trauma import is disabled
@ -263,13 +175,12 @@ function getTrauma()
end
-- get campaign log
campaignLog = findObjects(6)[1]
campaignLog = getObjectsWithTag("CampaignLog")[1]
if campaignLog == nil then
printToAll("Campaign log not found in standard position!", "Yellow")
printToAll("Default values for health and sanity loaded.", "Yellow")
return
end
campaignLog = campaignLog.hit_object
loadTrauma()
end
@ -280,7 +191,14 @@ function loadTrauma()
if trauma ~= nil then
printToAll("Trauma values found in campaign log!", "Green")
RESET_VALUES = campaignLog.call("returnTrauma")
trauma = campaignLog.call("returnTrauma")
for i = 1, 8 do
if i < 5 then
RESET_VALUES.Damage[i] = trauma[i]
else
RESET_VALUES.Horror[i-4] = trauma[i]
end
end
loadingFailedBefore = false
elseif loadingFailedBefore then
printToAll("Trauma values could not be found in campaign log!", "Yellow")
@ -303,7 +221,7 @@ end
-- remove drawn lines
function removeLines()
if options["removeDrawnLines"] then
printToAll("Removing vector lines...", "White")
printToAll("Removing global vector lines...", "White")
Global.setVectorLines({})
end
end
@ -312,40 +230,40 @@ end
function discardHands()
if not options["tidyPlayermats"] then return end
for i = 1, 4 do
local trashcan = getObjectFromGUID(TRASHCAN_GUIDS[i])
if trashcan == nil then return end
local trash = guidReferenceApi.getObjectByOwnerAndType(COLORS[i], "Trash")
if trash == nil then return end
local hand = Player[playmatApi.getPlayerColor(COLORS[i])].getHandObjects()
for j = #hand, 1, -1 do
trashcan.putObject(hand[j])
trash.putObject(hand[j])
end
end
end
-- clean up for play area
function tidyPlayareaCoroutine()
local trashcan = getObjectFromGUID(TRASHCAN_GUIDS[5])
local PLAYMATZONE = getObjectFromGUID("a2f932")
local trash = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
local playAreaZone = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayAreaZone")
if PLAYMATZONE == nil then
if playAreaZone == nil then
printToAll("Scripting zone for main play area could not be found!", "Red")
elseif trashcan == nil then
elseif trash == nil then
printToAll("Trashcan for main play area could not be found!", "Red")
else
for _, obj in ipairs(PLAYMATZONE.getObjects()) do
for _, obj in ipairs(playAreaZone.getObjects()) do
-- ignore these elements
if not tableContains(IGNORE_GUIDS, obj.getGUID()) and obj.hasTag(IGNORE_TAG) == false then
if obj.hasTag(IGNORE_TAG) == false and checkMemo(obj) == false then
coroutine.yield(0)
trashcan.putObject(obj)
trash.putObject(obj)
end
end
end
printToAll("Tidying playermats and agenda mat...", "White")
printToAll("Tidying playermats and mythos area...", "White")
startLuaCoroutine(self, "tidyPlayerMatCoroutine")
return 1
end
-- clean up for the four playermats and the agenda/act playmat
-- clean up for the four playermats and the mythos area
function tidyPlayerMatCoroutine()
for i = 1, 5 do
-- only continue for playermat (1-4) if option enabled
@ -353,32 +271,38 @@ function tidyPlayerMatCoroutine()
-- delay for animation purpose
for k = 1, 30 do coroutine.yield(0) end
-- get respective trashcan
local trashcan = getObjectFromGUID(TRASHCAN_GUIDS[i])
if trashcan == nil then
-- get respective trash
local trash = guidReferenceApi.getObjectByOwnerAndType(COLORS[i], "Trash")
if trash == nil then
printToAll("Trashcan for " .. COLORS[i] .. " playmat could not be found!", "Red")
return 1
end
for _, entry in ipairs(findObjects(i)) do
local obj = entry.hit_object
local desc_low = string.lower(obj.getDescription())
local objList
if i < 5 then
objList = playmatApi.searchAroundPlaymat(COLORS[i])
else
objList = searchMythosArea()
end
for _, obj in ipairs(objList) do
-- ignore these elements
if not tableContains(IGNORE_GUIDS, obj.getGUID()) and obj.hasTag(IGNORE_TAG) == false and
desc_low ~= "chaos bag" and desc_low ~= "action token" then
if obj.hasTag(IGNORE_TAG) == false
and obj.hasTag("ActionToken") == false
and obj.hasTag("chaosBag") == false
and checkMemo(obj) == false then
coroutine.yield(0)
trashcan.putObject(obj)
trash.putObject(obj)
-- flip action tokens back to ready
elseif desc_low == "action token" and obj.is_face_down then
-- flip action tokens back to ready
elseif obj.hasTag("ActionToken") == false and obj.is_face_down then
obj.flip()
end
end
-- reset "activeInvestigatorId"
if i < 5 then
local playermat = getObjectFromGUID(PLAYERMAT_GUIDS[i])
local playermat = guidReferenceApi.getObjectByOwnerAndType(COLORS[i], "Playermat")
if playermat then
playermat.setVar("activeInvestigatorId", "00000")
end
@ -386,7 +310,7 @@ function tidyPlayerMatCoroutine()
end
end
local datahelper = getObjectFromGUID("708279")
local datahelper = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
if datahelper then
datahelper.setTable("SPAWNED_PLAYER_CARD_GUIDS", {})
end
@ -399,23 +323,31 @@ end
-- helper functions
---------------------------------------------------------
-- find objects depending on index (1 to 4 for playermats, 5 for agenda/act playmat, 6 for campaign log)
function findObjects(num)
return Physics.cast({
-- find objects in the mythos area
function searchMythosArea()
local searchResult = Physics.cast({
direction = { 0, 1, 0 },
max_distance = 1,
type = 3,
size = PHYSICS_SCALE[num],
origin = PHYSICS_POSITION[num],
orientation = { 0, PHYSICS_ROTATION[num], 0 },
size = { 55, 1, 13.5 },
origin = { -2, 2, 10 },
orientation = { 0, 270, 0 },
debug = false
})
local objList = {}
for _, v in ipairs(searchResult) do
table.insert(objList, v.hit_object)
end
return objList
end
-- search a table for a value, return true if found (else returns false)
function tableContains(table, value)
for _, v in ipairs(table) do
if v == value then
-- checks if the object is owned by a playermat or the mythos, returns boolean
function checkMemo(obj)
local memo = obj.getMemo()
if memo then
local decoded = JSON.decode(memo) or {}
if decoded.matColor then
return true
end
end

View File

@ -312,6 +312,10 @@ function layout(_, _, isRightClick)
local value = tonumber(objData.Nickname)
local precedence = tokenPrecedence[objData.Nickname]
-- remove GUID to avoid issues for high latency clients
objData["GUID"] = nil
-- store data with value / precendence
data[i] = {
token = objData,
value = value or precedence[1]

View File

@ -1,11 +1,12 @@
do
local TokenArrangerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
-- local function to call the token arranger, if it is on the table
---@param functionName String Name of the function to cal
---@param argument Variant Parameter to pass
local function callIfExistent(functionName, argument)
local tokenArranger = getObjectsWithTag("TokenArranger")[1]
local tokenArranger = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenArranger")
if tokenArranger ~= nil then
tokenArranger.call(functionName, argument)
end

View File

@ -198,7 +198,7 @@ do
local altArt = { front = "normal", back = "normal" }
-- translating front ID
if altFrontId > 90000 and altFrontId < 90047 then
if altFrontId > 90000 and altFrontId < 90050 then
altArt.front = "parallel"
elseif altFrontId > 01500 and altFrontId < 01506 then
altArt.front = "revised"
@ -207,7 +207,7 @@ do
end
-- translating back ID
if altBackId > 90000 and altBackId < 90047 then
if altBackId > 90000 and altBackId < 90050 then
altArt.back = "parallel"
elseif altBackId > 01500 and altBackId < 01506 then
altArt.back = "revised"
@ -295,7 +295,12 @@ do
local card = allCardsBagApi.getCardById(cardId)
if (card ~= nil and card.metadata.bonded ~= nil) then
for _, bond in ipairs(card.metadata.bonded) do
bondedCards[bond.id] = bond.count
-- add a bonded card for each copy of the parent card (except for Pendant of the Queen)
if bond.id == "06022" then
bondedCards[bond.id] = bond.count
else
bondedCards[bond.id] = bond.count * cardCount
end
-- We need to know which cards are bonded to determine their position, remember them
bondedList[bond.id] = true
-- Also adding taboo versions of bonded cards to the list

View File

@ -1,7 +1,11 @@
do
local DeckImporterApi = {}
local DECK_IMPORTER_GUID = "a28140"
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getDeckImporter()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "DeckImporter")
end
-- Returns a table with the full state of the UI, including options and deck IDs.
-- This can be used to persist via onSave(), or provide values for a load operation
-- Table values:
@ -14,7 +18,7 @@ do
-- investigators: True if investigator cards should be spawned
DeckImporterApi.getUiState = function()
local passthroughTable = {}
for k,v in pairs(getObjectFromGUID(DECK_IMPORTER_GUID).call("getUiState")) do
for k,v in pairs(getDeckImporter().call("getUiState")) do
passthroughTable[k] = v
end
return passthroughTable
@ -31,7 +35,7 @@ do
-- loadNewest: True if the most upgraded version of the deck should be loaded
-- investigators: True if investigator cards should be spawned
DeckImporterApi.setUiState = function(uiStateTable)
return getObjectFromGUID(DECK_IMPORTER_GUID).call("setUiState", uiStateTable)
return getDeckImporter().call("setUiState", uiStateTable)
end
return DeckImporterApi

View File

@ -88,6 +88,7 @@ function loadCards(slots, investigatorId, bondedList, customizations, playerColo
handleAncestralKnowledge(cardsToSpawn)
handleUnderworldMarket(cardsToSpawn, playerColor)
handleHunchDeck(investigatorId, cardsToSpawn, playerColor)
handleSpiritDeck(investigatorId, cardsToSpawn, playerColor)
handleCustomizableUpgrades(cardsToSpawn, customizations)
handlePeteSignatureAssets(investigatorId, cardsToSpawn)
@ -322,6 +323,48 @@ function handleHunchDeck(investigatorId, cardList, playerColor)
end
end
-- If the investigator is Parallel Jim Culver, extract all Ally assets to SetAside5 to build the Spirit
-- Deck.
---@param investigatorId String ID for the deck's investigator card. Passed separately because the
--- investigator may not be included in the cardList
---@param cardList Table Deck list being created
---@param playerColor String Color this deck is being loaded for
function handleSpiritDeck(investigatorId, cardList, playerColor)
if investigatorId == "02004-p" or investigatorId == "02004-pb" then -- Parallel Jim Culver
local spiritList = {}
for i, card in ipairs(cardList) do
if card.metadata.id == "90053" or (
card.metadata.type == "Asset"
and card.metadata.traits ~= nil
and string.match(card.metadata.traits, "Ally")
and card.metadata.level ~= nil
and card.metadata.level < 3) then
table.insert(spiritList, i)
end
end
-- Process allies to move them to the spirit deck. This is done in reverse
-- order because the sorting needs to be reversed (deck sorts for face down)
-- Performance here may be an issue, as table.remove() is an O(n) operation
-- which makes the full shift O(n^2). But keep it simple unless it becomes
-- a problem
for i = #spiritList, 1, -1 do
local moving = cardList[spiritList[i]]
moving.zone = "SetAside5"
table.remove(cardList, spiritList[i])
table.insert(cardList, moving)
end
if #spiritList < 10 then
printToAll("Jim's spirit deck must have 9 Ally assets but the deck only has " .. (#spiritList - 1) ..
" Ally assets.", playerColor)
elseif #spiritList > 11 then
printToAll("Moved all " .. (#spiritList - 1) ..
" Ally assets to the spirit deck, reduce it to 10 (including Vengeful Shade).", playerColor)
else
printToAll("Built Jim's spirit deck", playerColor)
end
end
end
-- For any customization upgrade cards in the card list, process the metadata from the deck to
-- set the save state to show the correct checkboxes/text field values
---@param cardList Table Deck list being created

View File

@ -58,36 +58,9 @@ end
-- loadNewest: True if the most upgraded version of the deck should be loaded
-- investigators: True if investigator cards should be spawned
function setUiState(uiStateTable)
-- Callback functions aren't triggered when editing buttons/inputs so values must be set manually
if uiStateTable["greenDeck"] then
greenDeckId = uiStateTable["greenDeck"]
self.editInput({index=0, value=greenDeckId})
end
if uiStateTable["redDeck"] then
redDeckId = uiStateTable["redDeck"]
self.editInput({index=1, value=redDeckId})
end
if uiStateTable["whiteDeck"] then
whiteDeckId = uiStateTable["whiteDeck"]
self.editInput({index=2, value=whiteDeckId})
end
if uiStateTable["orangeDeck"]then
orangeDeckId = uiStateTable["orangeDeck"]
self.editInput({index=3, value=orangeDeckId})
end
if uiStateTable["private"] then
privateDeck = uiStateTable["private"]
self.editButton { index = 0, label = PRIVATE_TOGGLE_LABELS[privateDeck] }
end
if uiStateTable["loadNewest"] then
loadNewestDeck = uiStateTable["loadNewest"]
self.editButton { index = 1, label = UPGRADED_TOGGLE_LABELS[loadNewestDeck] }
end
if uiStateTable["investigators"] then
loadInvestigators = uiStateTable["investigators"]
self.editButton { index = 2, label = LOAD_INVESTIGATOR_TOGGLE_LABELS[loadInvestigators] }
end
self.clearButtons()
self.clearInputs()
initializeUi(uiStateTable)
end
-- Sets up the UI for the deck loader, populating fields from the given save state table decoded from onLoad()

View File

@ -1,10 +1,14 @@
do
local BlessCurseManagerApi = {}
local MANAGER_GUID = "5933fb"
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
end
-- removes all taken tokens and resets the counts
BlessCurseManagerApi.removeTakenTokensAndReset = function()
local BlessCurseManager = getObjectFromGUID(MANAGER_GUID)
local BlessCurseManager = getManager()
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Bless") end, 0.05)
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Curse") end, 0.10)
Wait.time(function() BlessCurseManager.call("doReset", "White") end, 0.15)
@ -12,30 +16,30 @@ do
-- updates the internal count (called by cards that seal bless/curse tokens)
BlessCurseManagerApi.sealedToken = function(type, guid)
getObjectFromGUID(MANAGER_GUID).call("sealedToken", { type = type, guid = guid })
getManager().call("sealedToken", { type = type, guid = guid })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
BlessCurseManagerApi.releasedToken = function(type, guid)
getObjectFromGUID(MANAGER_GUID).call("releasedToken", { type = type, guid = guid })
getManager().call("releasedToken", { type = type, guid = guid })
end
-- broadcasts the current status for bless/curse tokens
---@param playerColor String Color of the player to show the broadcast to
BlessCurseManagerApi.broadcastStatus = function(playerColor)
getObjectFromGUID(MANAGER_GUID).call("broadcastStatus", playerColor)
getManager().call("broadcastStatus", playerColor)
end
-- removes all bless / curse tokens from the chaos bag and play
---@param playerColor String Color of the player to show the broadcast to
BlessCurseManagerApi.removeAll = function(playerColor)
getObjectFromGUID(MANAGER_GUID).call("doRemove", playerColor)
getManager().call("doRemove", playerColor)
end
-- adds Wendy's menu to the hovered card (allows sealing of tokens)
---@param color String Color of the player to show the broadcast to
BlessCurseManagerApi.addWendysMenu = function(playerColor, hoveredObject)
getObjectFromGUID(MANAGER_GUID).call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
end
return BlessCurseManagerApi

View File

@ -1,3 +1,5 @@
local guidReferenceApi = require("core/GUIDReferenceApi")
local optionsVisible = false
local options = {
Agenda = true,
@ -64,10 +66,9 @@ function startReset()
if options.Agenda then
updateVal(0)
end
-- call the "Doom-in-Play"-counter
local DoomInPlayCounter = getObjectFromGUID("652ff3")
if DoomInPlayCounter then
DoomInPlayCounter.call("removeDoom", options)
local doomInPlayCounter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DoomInPlayCounter")
if doomInPlayCounter then
doomInPlayCounter.call("removeDoom", options)
end
end

View File

@ -1,25 +1,24 @@
-- common parameters
local castParameters = {}
castParameters.direction = { 0, 1, 0 }
castParameters.type = 3
castParameters.max_distance = 0
local guidReferenceApi = require("core/GUIDReferenceApi")
local playmatApi = require("playermat/PlaymatApi")
local zone
local ZONE, TRASH, loopID
local doomURL = "https://i.imgur.com/EoL7yaZ.png"
local IGNORE_TAG = "DoomCounter_ignore"
-- playermats 1 to 4
local originAndSize = {
{ origin = { -55, 1.6, 16.5 }, size = { 12, 1, 25 } },
{ origin = { -55, 1.6, -16.5 }, size = { 12, 1, 25 } },
{ origin = { -25, 1.6, 27 }, size = { 25, 1, 12 } },
{ origin = { -25, 1.6, -27 }, size = { 25, 1, 12 } }
local TOTAL_PLAY_AREA = {
upperLeft = {
x = -10,
z = -35
},
lowerRight = {
x = -60,
z = 35
}
}
-- create button, context menu and start loop
function onLoad()
self.createButton({
label = tostring(0),
label = "0",
click_function = "none",
function_owner = self,
position = { 0, 0.06, 0 },
@ -31,76 +30,65 @@ function onLoad()
color = { 0, 0, 0, 0 }
})
zone = getObjectFromGUID("a2f932")
TRASH = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
ZONE = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayAreaZone")
loopID = Wait.time(countDoom, 2, -1)
end
-- main function
function countDoom()
local doom = 0
for i = 1, 5 do doom = doom + search(i) end
self.editButton({ index = 0, label = tostring(doom) })
local count = 0
-- get doom in play
for _, obj in ipairs(getObjects()) do
count = count + getDoomAmount(obj)
end
self.editButton({ index = 0, label = tostring(count) })
end
-- searches playermats (num = 1-4) or the scripting zone (num = 5)
function search(num)
local val = 0
if num == 5 then
for _, obj in ipairs(zone.getObjects()) do
val = val + isDoom(obj)
end
-- gets quantity (for stacks) of doom
function getDoomAmount(obj)
if (obj.is_face_down and obj.getCustomObject().image_bottom == doomURL)
and not obj.hasTag(IGNORE_TAG)
and inArea(obj.getPosition(), TOTAL_PLAY_AREA) then
return math.abs(obj.getQuantity())
else
castParameters.origin = originAndSize[num].origin
castParameters.size = originAndSize[num].size
for _, obj in ipairs(Physics.cast(castParameters)) do
val = val + isDoom(obj.hit_object)
end
return 0
end
return val
end
-- checks an object for the doom image and gets quantity (for stacks)
function isDoom(obj)
if (obj.is_face_down and obj.getCustomObject().image_bottom == doomURL) or
(obj.name == "Custom_Token" and obj.getCustomObject().image == doomURL) then
if not obj.hasTag(IGNORE_TAG) then
return math.abs(obj.getQuantity())
end
end
return 0
end
-- removes doom from playermats / playarea
function removeDoom(options)
local trashCan = getObjectFromGUID("70b9f6")
local count = 0
if options.Playermats then
for i = 1, 4 do
castParameters.origin = originAndSize[i].origin
castParameters.size = originAndSize[i].size
for _, obj in ipairs(Physics.cast(castParameters)) do
local obj = obj.hit_object
local amount = isDoom(obj)
if amount > 0 then
trashCan.putObject(obj)
count = count + amount
end
end
end
if options.Playermats then
count = removeDoomFromList(playmatApi.searchAroundPlaymat("All"))
broadcastToAll(count .. " doom removed from Playermats.", "White")
end
local count = 0
if options.Playarea then
for _, obj in ipairs(zone.getObjects()) do
local amount = isDoom(obj)
if amount > 0 then
trashCan.putObject(obj)
count = count + amount
end
end
broadcastToAll(count .. " doom removed from Playarea.", "White")
count = removeDoomFromList(ZONE.getObjects())
broadcastToAll(count .. " doom removed from Playerarea.", "White")
end
end
-- removes doom from provided object list and returns the removed amount
function removeDoomFromList(objList)
local count = 0
for _, obj in ipairs(objList) do
local amount = getDoomAmount(obj)
if amount > 0 then
TRASH.putObject(obj)
count = count + amount
end
end
return count
end
function inArea(point, bounds)
return (point.x < bounds.upperLeft.x
and point.x > bounds.lowerRight.x
and point.z > bounds.upperLeft.z
and point.z < bounds.lowerRight.z)
end

View File

@ -0,0 +1,28 @@
do
local GUIDReferenceApi = {}
local function getGuidHandler()
return getObjectFromGUID("123456")
end
-- returns all matching objects as a table with references
---@param owner String Parent object for this search
---@param type String Type of object to search for
GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)
return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type })
end
-- returns all matching objects as a table with references
---@param type String Type of object to search for
GUIDReferenceApi.getObjectsByType = function(type)
return getGuidHandler().call("getObjectsByType", type)
end
-- returns all matching objects as a table with references
---@param owner String Parent object for this search
GUIDReferenceApi.getObjectsByOwner = function(owner)
return getGuidHandler().call("getObjectsByOwner", owner)
end
return GUIDReferenceApi
end

View File

@ -0,0 +1,101 @@
local GuidReferences = {
White = {
ClueCounter = "d86b7c",
ClickableClueCounter = "db85d6",
DamageCounter = "eb08d6",
HandZone = "a70eee",
HorrorCounter = "468e88",
InvestigatorSkillTracker = "e598c2",
Playermat = "8b081b",
ResourceCounter = "4406f0",
Trash = "147e80"
},
Orange = {
ClueCounter = "1769ed",
ClickableClueCounter = "3f22e5",
DamageCounter = "e64eec",
HandZone = "5fe087",
HorrorCounter = "0257d9",
InvestigatorSkillTracker = "b4a5f7",
Playermat = "bd0ff4",
ResourceCounter = "816d84",
Trash = "f7b6c8"
},
Green = {
ClueCounter = "032300",
ClickableClueCounter = "891403",
DamageCounter = "1f5a0a",
HandZone = "0285cc",
HorrorCounter = "7b5729",
InvestigatorSkillTracker = "af7ed7",
Playermat = "383d8b",
ResourceCounter = "cd15ac",
Trash = "5f896a"
},
Red = {
ClueCounter = "37be78",
ClickableClueCounter = "4111de",
DamageCounter = "591a45",
HandZone = "be2f17",
HorrorCounter = "beb964",
InvestigatorSkillTracker = "e74881",
Playermat = "0840d5",
ResourceCounter = "a4b60d",
Trash = "4b8594"
},
Mythos = {
AllCardsBag = "15bb07",
BlessCurseManager = "5933fb",
CampaignThePathToCarcosa = "aca04c",
DataHelper = "708279",
DeckImporter = "a28140",
DoomCounter = "85c4c6",
DoomInPlayCounter = "652ff3",
InvestigatorCounter = "f182ee",
MasterClueCounter = "4a3aa4",
MythosArea = "9f334f",
NavigationOverlayHandler = "797ede",
OptionPanelSource = "830bd0",
PlaceholderBoxDummy = "a93466",
PlayArea = "721ba2",
PlayAreaZone = "a2f932",
PlayerCardPanel = "2d30ee",
ResourceTokenBag = "9fadf9",
RulesReference = "d99993",
SoundCube = "3c988f",
TokenArranger = "022907",
TokenSource = "124381",
TokenSpawnTracker = "e3ffc9",
TourStarter = "0e5aa8",
Trash = "70b9f6",
VictoryDisplay = "6ccd6d"
}
}
function getObjectByOwnerAndType(params)
local owner = params.owner or "Mythos"
local type = params.type
return getObjectFromGUID(GuidReferences[owner][type])
end
function getObjectsByType(type)
local objList = {}
for owner, objects in pairs(GuidReferences) do
local obj = getObjectFromGUID(objects[type])
if obj then
objList[owner] = obj
end
end
return objList
end
function getObjectsByOwner(owner)
local objList = {}
for type, guid in pairs(GuidReferences[owner]) do
local obj = getObjectFromGUID(guid)
if obj then
objList[type] = obj
end
end
return objList
end

View File

@ -1,4 +1,5 @@
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local optionPanelApi = require("core/OptionPanelApi")
local playmatApi = require("playermat/PlaymatApi")
local victoryDisplayApi = require("core/VictoryDisplayApi")
@ -41,7 +42,8 @@ end
-- adds 1 doom to the agenda
function addDoomToAgenda()
getObjectFromGUID("85c4c6").call("addVal", 1)
local doomCounter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DoomCounter")
doomCounter.call("addVal", 1)
end
-- moves the hovered card to the victory display
@ -105,7 +107,7 @@ function takeClueFromLocation(playerColor, hoveredObject)
local pos = nil
if clickableClues then
pos = {x = 0.49, y = 2.66, z = 0.00}
playmatApi.updateClueClicker(playerColor, playmatApi.getClueCount(clickableClues, playerColor) + 1)
playmatApi.updateCounter(matColor, "ClickableClueCounter", _, 1)
else
pos = playmatApi.transformLocalPosition({x = -1.12, y = 0.05, z = 0.7}, matColor)
end

View File

@ -1,4 +1,5 @@
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local mythosAreaApi = require("core/MythosAreaApi")
local navigationOverlayApi = require("core/NavigationOverlayApi")
local playAreaApi = require("core/PlayAreaApi")
@ -12,11 +13,8 @@ local tokenManager = require("core/token/TokenManager")
-- general setup
---------------------------------------------------------
ENCOUNTER_DECK_POS = {-3.93, 1, 5.76}
ENCOUNTER_DECK_DISCARD_POSITION = {-3.85, 1, 10.38}
-- GUID of data helper
DATA_HELPER_GUID = "708279"
ENCOUNTER_DECK_POS = { -3.93, 1, 5.76 }
ENCOUNTER_DECK_DISCARD_POSITION = { -3.85, 1, 10.38 }
-- GUIDs that will not be interactable (e.g. parts of the table)
local NOT_INTERACTABLE = {
@ -37,14 +35,28 @@ chaosTokens = {}
local chaosTokensLastMat = nil
local bagSearchers = {}
local MAT_COLORS = {"White", "Orange", "Green", "Red"}
local MAT_COLORS = { "White", "Orange", "Green", "Red" }
local hideTitleSplashWaitFunctionId = nil
-- online functionality related variables
local MOD_VERSION = "3.3.0"
local SOURCE_REPO = 'https://raw.githubusercontent.com/chr1z93/loadable-objects/main'
local library, requestObj, modMeta, notificationVisible
local library, requestObj, modMeta
local acknowledgedUpgradeVersions = {}
local contentToShow = "campaigns"
local currentListItem = 1
local xmlVisibility = {
downloadWindow = false,
optionPanel = false,
updateNotification = false
}
local tabIdTable = {
tab1 = "campaigns",
tab2 = "scenarios",
tab3 = "fanmadeCampaigns",
tab4 = "fanmadeScenarios",
tab5 = "fanmadePlayerCards"
}
-- optionPanel data
optionPanel = {}
@ -101,14 +113,6 @@ ID_URL_MAP = {
-- data for chaos token stat tracker
---------------------------------------------------------
local MAT_GUID_TO_COLOR = {
["Overall"] = "Overall",
["8b081b"] = "White",
["bd0ff4"] = "Orange",
["383d8b"] = "Green",
["0840d5"] = "Red"
}
local tokenDrawingStats = {
["Overall"] = {},
["8b081b"] = {},
@ -122,7 +126,12 @@ local tokenDrawingStats = {
---------------------------------------------------------
-- saving state of optionPanel to restore later
function onSave() return JSON.encode({ optionPanel = optionPanel, acknowledgedUpgradeVersions = acknowledgedUpgradeVersions }) end
function onSave()
return JSON.encode({
optionPanel = optionPanel,
acknowledgedUpgradeVersions = acknowledgedUpgradeVersions
})
end
function onLoad(savedData)
if savedData then
@ -142,6 +151,11 @@ function onLoad(savedData)
resetChaosTokenStatTracker()
getModVersion()
math.randomseed(os.time())
-- initialization of loadable objects library (delay to let Navigation Overlay build)
Wait.time(function()
WebRequest.get(SOURCE_REPO .. '/library.json', libraryDownloadCallback)
end, 1)
end
-- Event hook for any object search. When chaos tokens are manipulated while the chaos bag
@ -289,8 +303,8 @@ function handleStatTrackerClick(_, _, isRightClick)
if isRightClick then
resetChaosTokenStatTracker()
else
local squidKing = "Nobody"
local maxSquid = 0
local squidKing = "Nobody"
local maxSquid = 0
local foundAnyStats = false
for key, personalStats in pairs(tokenDrawingStats) do
@ -300,7 +314,9 @@ function handleStatTrackerClick(_, _, isRightClick)
playerColor = "White"
playerName = "Overall"
else
playerColor = playmatApi.getPlayerColor(MAT_GUID_TO_COLOR[key])
-- get mat color
local matColor = playmatApi.getMatColorByPosition(getObjectFromGUID(key).getPosition())
playerColor = playmatApi.getPlayerColor(matColor)
playerName = Player[playerColor].steam_name or playerColor
local playerSquidCount = personalStats["Auto-fail"]
@ -320,8 +336,8 @@ function handleStatTrackerClick(_, _, isRightClick)
if totalCount > 0 then
foundAnyStats = true
printToAll("------------------------------")
printToAll(playerName .. " Stats", playerColor)
printToAll(playerName .. " Stats", playerColor)
for tokenName, value in pairs(personalStats) do
if value ~= 0 then
printToAll(tokenName .. ': ' .. tostring(value))
@ -334,7 +350,7 @@ function handleStatTrackerClick(_, _, isRightClick)
-- detect if any player drew tokens
if foundAnyStats then
printToAll("------------------------------")
printToAll(squidKing .. " is an auto-fail magnet.", {255, 0, 0})
printToAll(squidKing .. " is an auto-fail magnet.", { 255, 0, 0 })
else
printToAll("No tokens have been drawn yet.", "Yellow")
end
@ -363,11 +379,11 @@ function createSetupButtons(args)
if data ~= nil then
local buttonParameters = {}
buttonParameters.function_owner = args.object
buttonParameters.position = {0, 0.1, -0.15}
buttonParameters.scale = {0.47, 1, 0.47}
buttonParameters.position = { 0, 0.1, -0.15 }
buttonParameters.scale = { 0.47, 1, 0.47 }
buttonParameters.height = 200
buttonParameters.width = 1150
buttonParameters.color = {0.87, 0.8, 0.7}
buttonParameters.color = { 0.87, 0.8, 0.7 }
if data.easy ~= nil then
buttonParameters.label = "Easy"
@ -450,7 +466,8 @@ function fillContainer(args)
end
function getDataValue(storage, key)
local data = getObjectFromGUID(DATA_HELPER_GUID).getTable(storage)
local DATA_HELPER = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
local data = DATA_HELPER.getTable(storage)
if data ~= nil then
local value = data[key]
if value ~= nil then
@ -495,7 +512,6 @@ function getChaosBagState()
end
return tokens
end
-- respawns the chaos bag with a new state of tokens
@ -524,7 +540,7 @@ function setChaosBagState(tokenList)
-- overwrite chaos bag content and respawn it
chaosbagData.ContainedObjects = containedObjects
chaosbag.destruct()
spawnObjectData({data = chaosbagData})
spawnObjectData({ data = chaosbagData })
-- remove tokens that are still in play
for _, token in pairs(chaosTokens) do
@ -553,7 +569,7 @@ function spawnChaosToken(id)
type = 'Custom_Tile',
position = { 0.49, 3, 0 },
scale = { 0.81, 1.0, 0.81 },
rotation = {0, 270, 0},
rotation = { 0, 270, 0 },
callback_function = function(obj)
obj.setName(ID_URL_MAP[id].name)
chaosbag.putObject(obj)
@ -603,7 +619,7 @@ function emptyChaosBag()
local chaosbag = findChaosBag()
for _, object in ipairs(chaosbag.getObjects()) do
chaosbag.takeObject({callback_function = function(item) item.destruct() end})
chaosbag.takeObject({ callback_function = function(item) item.destruct() end })
end
end
@ -619,176 +635,410 @@ end
-- Content Importing and XML functions
---------------------------------------------------------
function onClick_refreshList()
local request = WebRequest.get(SOURCE_REPO .. '/library.json', completed_list_update)
requestObj = request
startLuaCoroutine(Global, 'downloadCoroutine')
-- forwards the requested content type to the update function and sets highlight to clicked tab
---@param tabId String Id of the clicked tab
function onClick_tab(_, _, tabId)
for listId, listContent in pairs(tabIdTable) do
if listId == tabId then
UI.setClass(listId, 'downloadTab activeTab')
contentToShow = listContent
else
UI.setClass(listId, 'downloadTab')
end
end
currentListItem = 1
updateDownloadItemList()
end
function onClick_select(player, params)
params = JSON.decode(urldecode(params))
-- click function for the items in the download window
-- updates backgroundcolor for row panel and fontcolor for list item
function onClick_select(_, _, identificationKey)
UI.setAttribute("panel" .. currentListItem, "color", "clear")
UI.setAttribute(contentToShow .. "_" .. currentListItem, "color", "white")
-- parses the identification key (contentToShow_currentListItem)
if identificationKey then
contentToShow = nil
currentListItem = nil
for str in string.gmatch(identificationKey, "([^_]+)") do
if not contentToShow then
-- grab the first part to know the content type
contentToShow = str
else
-- get the index
currentListItem = tonumber(str)
break
end
end
end
UI.setAttribute("panel" .. currentListItem, "color", "grey")
UI.setAttribute(contentToShow .. "_" .. currentListItem, "color", "black")
updatePreviewWindow()
end
-- click function for the download button in the preview window
function onClick_download()
placeholder_download(library[contentToShow][currentListItem])
end
-- the download button on the placeholder objects calls this to directly initiate a download
---@param param Table contains url and guid of replacement object
function placeholder_download(params)
local url = SOURCE_REPO .. '/' .. params.url
local request = WebRequest.get(url, function (request) complete_obj_download(request, params) end )
requestObj = request
requestObj = WebRequest.get(url, function (request) contentDownloadCallback(request, params) end)
startLuaCoroutine(Global, 'downloadCoroutine')
end
function onClick_load()
UI.show('progress_display')
UI.hide('load_button')
function downloadCoroutine()
-- show progress bar
UI.setAttribute('download_progress', 'active', true)
-- update progress bar
while requestObj do
UI.setAttribute('download_progress', 'percentage', requestObj.download_progress * 100)
coroutine.yield(0)
end
UI.setAttribute('download_progress', 'percentage', 100)
-- wait 30 frames
for i = 1, 30 do
coroutine.yield(0)
end
-- hide progress bar
UI.setAttribute('download_progress', 'active', false)
-- hide download window
if xmlVisibility.downloadWindow then
xmlVisibility.downloadWindow = false
UI.hide('downloadWindow')
end
return 1
end
-- spawns a bag that contains every object from the library
function onClick_downloadAll()
broadcastToAll("Download initiated - this will take a few minutes!")
-- hide download window
if xmlVisibility.downloadWindow then
xmlVisibility.downloadWindow = false
UI.hide('downloadWindow')
end
startLuaCoroutine(Global, "coroutineDownloadAll")
end
function coroutineDownloadAll()
local JSON = [[
{
"Name": "Bag",
"Transform": {
"posX": -39.5,
"posY": 2,
"posZ": -87,
"rotX": 0,
"rotY": 270,
"rotZ": 0,
"scaleX": 1.0,
"scaleY": 1.0,
"scaleZ": 1.0
},
"Nickname": "All Downloadable Content",
"Bag": {
"Order": 0
},
"ContainedObjects": [
]]
local contained = ""
local downloadedItems = 0
local skippedItems = 0
-- loop through the library to add content
for contentType, objectList in pairs(library) do
broadcastToAll("Downloading " .. contentType .. "...")
for _, params in ipairs(objectList) do
local request = WebRequest.get(SOURCE_REPO .. '/' .. params.url)
local start = os.time()
while true do
if request.is_done then
contained = contained .. request.text .. ","
downloadedItems = downloadedItems + 1
break
-- time-out if item can't be loaded in 5s
elseif request.is_error or (os.time() - start) > 5 then
skippedItems = skippedItems + 1
break
end
coroutine.yield(0)
end
end
end
JSON = JSON .. contained .. "]}"
spawnObjectJSON({json = JSON})
broadcastToAll(downloadedItems .. " objects downloaded.", "Green")
broadcastToAll(skippedItems .. " objects had a time-out / error.", "Orange")
return 1
end
-- spawns a placeholder box for the selected object
function onClick_spawnPlaceholder()
-- get object references
local item = library[contentToShow][currentListItem]
local dummy = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlaceholderBoxDummy")
-- error handling
if not item.boxsize or item.boxsize == "" or not item.boxart or item.boxart == "" then
print("Error loading object.")
return
end
-- get data for placeholder
local spawnPos = {-39.5, 2, -87}
local meshTable = {
big = "https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/advboxes/core_h_MSH.obj",
small = "https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/advboxes/tuckbox_h_MSH.obj",
wide = "http://pastebin.com/raw.php?i=uWAmuNZ2"
}
local scaleTable = {
big = {1.00, 0.14, 1.00},
small = {2.21, 0.46, 2.42},
wide = {2.00, 0.11, 1.69}
}
local placeholder = spawnObject({
type = "Custom_Model",
position = spawnPos,
rotation = {0, 270, 0},
scale = scaleTable[item.boxsize],
})
placeholder.setCustomObject({
mesh = meshTable[item.boxsize],
diffuse = item.boxart,
material = 3
})
placeholder.setColorTint({1, 1, 1, 71/255})
placeholder.setName(item.name)
placeholder.setDescription("by " .. (item.author or "Unknown"))
placeholder.setGMNotes(item.url)
placeholder.setLuaScript(dummy.getLuaScript())
Player.getPlayers()[1].pingTable(spawnPos)
-- hide download window
if xmlVisibility.downloadWindow then
xmlVisibility.downloadWindow = false
UI.hide('downloadWindow')
end
end
-- toggles the visibility of the respective UI
---@param player LuaPlayer Player that triggered this
---@param title String Name of the UI to toggle
function onClick_toggleUi(player, title)
if title == "Navigation Overlay" then
navigationOverlayApi.cycleVisibility(player.color)
return
end
UI.hide('optionPanel')
UI.hide('load_ui')
-- when same button is clicked or close window button is pressed, don't open UI
if UI.getValue('title') ~= title and title ~= 'Hidden' then
UI.setValue('title', title)
if title == "Options" then
UI.show('optionPanel')
else
update_window_content(title)
UI.show('load_ui')
end
if xmlVisibility[title] then
-- small delay to allow button click sounds to play
Wait.time(function() UI.hide(title) end, 0.1)
else
UI.setValue('title', "Hidden")
UI.show(title)
end
xmlVisibility[title] = not xmlVisibility[title]
end
-- updates the preview window
function updatePreviewWindow()
local item = library[contentToShow][currentListItem]
local tempImage = "http://cloud-3.steamusercontent.com/ugc/2115061845788345842/2CD6ABC551555CCF58F9D0DDB7620197BA398B06/"
-- set default image if not defined
if item.boxsize == nil or item.boxsize == "" or item.boxart == nil or item.boxart == "" then
item.boxsize = "big"
item.boxart = "http://cloud-3.steamusercontent.com/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/"
end
UI.setValue("previewTitle", item.name)
UI.setValue("previewAuthor", "by " .. (item.author or "- Author not found -"))
UI.setValue("previewDescription", item.description or "- Description not found -")
-- update mask according to size (hardcoded values to align image in mask)
local maskData = {}
if item.boxsize == "big" then
maskData = {
image = "box-cover-mask-big",
width = "870",
height = "435",
offsetXY = "154 60"
}
elseif item.boxsize == "small" then
maskData = {
image = "box-cover-mask-small",
width = "792",
height = "594",
offsetXY = "135 13"
}
elseif item.boxsize == "wide" then
maskData = {
image = "box-cover-mask-wide",
width = "756",
height = "630",
offsetXY = "-190 -70"
}
end
-- loading empty image as placeholder until real image is loaded
UI.setAttribute("previewArtImage", "image", tempImage)
-- insert the image itself
UI.setAttribute("previewArtImage", "image", item.boxart)
UI.setAttributes("previewArtMask", maskData)
end
-- formats the json response from the webrequest into a key-value lua table
-- strips the prefix from the community content items
function formatLibrary(json_response)
library = {}
library["campaigns"] = json_response.campaigns
library["scenarios"] = json_response.scenarios
library["extras"] = json_response.extras
library["fanmadeCampaigns"] = {}
library["fanmadeScenarios"] = {}
library["fanmadePlayerCards"] = {}
for _, item in ipairs(json_response.community) do
local identifier = nil
for str in string.gmatch(item.name, "([^:]+)") do
if not identifier then
-- grab the first part to know the content type
identifier = str
else
-- update the name without the content type
item.name = str
break
end
end
if identifier == "Fan Investigators" then
table.insert(library["fanmadePlayerCards"], item)
elseif identifier == "Fan Campaign" then
table.insert(library["fanmadeCampaigns"], item)
elseif identifier == "Fan Scenario" then
table.insert(library["fanmadeScenarios"], item)
end
end
end
function downloadCoroutine()
while requestObj do
UI.setAttribute('download_progress', 'percentage', requestObj.download_progress * 100)
coroutine.yield(0)
end
return 1
end
-- updates the window content to the requested content
function updateDownloadItemList()
if not library then return end
function update_list(objects)
local ui = UI.getXmlTable()
local update_height = find_tag_with_id(ui, 'ui_update_height')
local update_children = find_tag_with_id(update_height.children, 'ui_update_point')
-- addition of list items according to library file
local globalXml = UI.getXmlTable()
local contentList = getXmlTableElementById(globalXml, 'contentList')
update_children.children = {}
for _, v in ipairs(objects) do
local s = JSON.encode(v);
table.insert(update_children.children,
{ tag = 'Text',
value = v.name,
attributes = { onClick = 'onClick_select(' .. urlencode(JSON.encode(v)) .. ')', alignment = 'MiddleLeft' }
contentList.children = {}
for i, v in ipairs(library[contentToShow]) do
table.insert(contentList.children,
{
tag = "Panel",
attributes = { id = "panel" .. i },
children = {
tag = 'Text',
value = v.name,
attributes = {
id = contentToShow .. "_" .. i,
onClick = 'onClick_select',
alignment = 'MiddleLeft'
}
}
})
end
update_height.attributes.height = #(update_children.children) * 24
UI.setXmlTable(ui)
contentList.attributes.height = #contentList.children * 27
UI.setXmlTable(globalXml)
-- select the first item
Wait.time(onClick_select, 0.2)
end
function update_window_content(new_title)
if not library then return end
-- called after the webrequest of downloading an item
-- deletes the placeholder and spawns the downloaded item
function contentDownloadCallback(request, params)
requestObj = nil
if new_title == 'Campaigns' then
update_list(library.campaigns)
elseif new_title == 'Standalone Scenarios' then
update_list(library.scenarios)
elseif new_title == 'Investigators' then
update_list(library.investigators)
elseif new_title == 'Community Content' then
update_list(library.community)
elseif new_title == 'Extras' then
update_list(library.extras)
else
update_list({})
end
end
function complete_obj_download(request, params)
assert(request.is_done)
-- error handling
if request.is_error or request.response_code ~= 200 then
print('error: ' .. request.error)
else
if pcall(function()
local replaced_object
pcall(function()
if params.replace then
replaced_object = getObjectFromGUID(params.replace)
end
end)
local json = request.text
if replaced_object then
local pos = replaced_object.getPosition()
local rot = replaced_object.getRotation()
destroyObject(replaced_object)
Wait.frames(function()
spawnObjectJSON({json = json, position = pos, rotation = rot})
end, 1)
else
spawnObjectJSON({json = json})
end
end) then
print('Object loaded.')
else
print('Error loading object.')
print('Error: ' .. request.error)
return
end
-- initiate content spawning
local spawnTable = { json = request.text }
if params.replace then
local replacedObject = getObjectFromGUID(params.replace)
if replacedObject then
spawnTable.position = replacedObject.getPosition()
spawnTable.rotation = replacedObject.getRotation()
spawnTable.scale = replacedObject.getScale()
destroyObject(replacedObject)
end
end
requestObj = nil
UI.setAttribute('download_progress', 'percentage', 100)
end
-- the download button on the placeholder objects calls this to directly initiate a download
-- params is a table with url and guid of replacement object, which happens to match what onClick_select wants
function placeholder_download(params)
onClick_select(nil, JSON.encode(params))
end
function completed_list_update(request)
assert(request.is_done)
if request.is_error or request.response_code ~= 200 then
print('error: ' .. request.error)
else
local json_response = nil
if pcall(function () json_response = JSON.decode(request.text) end) then
library = json_response
update_window_content(UI.getValue('title'))
else
print('error parsing downloaded library')
-- if spawned from menu, ping the position
if params.name then
spawnTable["callback_function"] = function(obj)
Player.getPlayers()[1].pingTable(obj.getPosition())
end
end
requestObj = nil
UI.setAttribute('download_progress', 'percentage', 100)
if pcall(function() spawnObjectJSON(spawnTable) end) then
print('Object loaded.')
else
print('Error loading object.')
end
end
function find_tag_with_id(ui, id)
-- downloading of the library file
function libraryDownloadCallback(request)
if request.is_error or request.response_code ~= 200 then
print('error: ' .. request.error)
return
end
local json_response = nil
if pcall(function () json_response = JSON.decode(request.text) end) then
formatLibrary(json_response)
updateDownloadItemList()
else
print('error parsing downloaded library')
end
end
-- loops through an XML table and returns the specified object
---@param ui Table XmlTable (get this via getXmlTable)
---@param id String Id of the object to return
function getXmlTableElementById(ui, id)
for _, obj in ipairs(ui) do
if obj.attributes and obj.attributes.id and obj.attributes.id == id then return obj end
if obj.children then
local result = find_tag_with_id(obj.children, id)
local result = getXmlTableElementById(obj.children, id)
if result then return result end
end
end
return nil
end
function urlencode(str)
local str = string.gsub(str, "([^A-Za-z0-9-_.~])",
function (c) return string.format("%%%02X", string.byte(c)) end)
return str
end
function urldecode(str)
local str = string.gsub(str, "%%(%x%x)",
function (h) return string.char(tonumber(h, 16)) end)
return str
end
---------------------------------------------------------
-- Option Panel related functionality
---------------------------------------------------------
@ -875,7 +1125,8 @@ function applyOptionPanelChange(id, state)
optionPanel[id] = state
-- update master clue counter
getObjectFromGUID("4a3aa4").setVar("useClickableCounters", state)
local counter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "MasterClueCounter")
counter.setVar("useClickableCounters", state)
-- option: Play area snap tags
elseif id == "playAreaSnapTags" then
@ -944,8 +1195,9 @@ end
-- copies the specified tool (by name) from the option panel source bag
---@param name String Name of the object that should be copied
---@param position Table Desired position of the object
---@param rotation Table Desired rotation of the object (defaults to object's rotation)
function spawnHelperObject(name, position, rotation)
local sourceBag = getObjectFromGUID("830bd0")
local sourceBag = guidReferenceApi.getObjectByOwnerAndType("Mythos","OptionPanelSource")
-- error handling for missing sourceBag
if not sourceBag then
@ -953,7 +1205,7 @@ function spawnHelperObject(name, position, rotation)
return
end
local spawnTable = {position = position}
local spawnTable = { position = position }
-- only overrride rotation if there is one provided (object's rotation used instead)
if rotation then
@ -1046,7 +1298,6 @@ 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)
@ -1088,7 +1339,7 @@ function compareVersion(request)
-- stop here if on latest version
if MOD_VERSION == modMeta["latestVersion"] then return end
-- stop here if "don't show again" was clicked for this version before
if acknowledgedUpgradeVersions[modMeta["latestVersion"]] then return end
@ -1110,25 +1361,14 @@ function updateNotificationLoading()
highlightText = highlightText .. "\n• " .. entry
end
end
-- update the XML UI
UI.setValue("notificationHeader", "New version available: ".. modMeta["latestVersion"])
UI.setValue("notificationHeader", "New version available: " .. modMeta["latestVersion"])
UI.setValue("releaseHighlightText", highlightText)
UI.setAttribute("highlightRow", "preferredHeight", 20*#highlights)
UI.setAttribute("updateNotification", "height", 20*#highlights + 125)
end
-- triggered by clicking on the Finn Icon
function onClick_FinnIcon()
if notificationVisible then
UI.hide("updateNotification")
notificationVisible = false
else
UI.show("updateNotification")
notificationVisible = true
end
end
-- close / don't show again buttons on the update notification
function onClick_notification(_, parameter)
if parameter == "dontShowAgain" then
@ -1137,4 +1377,5 @@ function onClick_notification(_, parameter)
end
UI.hide("FinnIcon")
UI.hide("updateNotification")
xmlVisibility["updateNotification"] = false
end

View File

@ -20,11 +20,10 @@ function onLoad(savedData)
width = 900,
scale = { 1.5, 1.5, 1.5 },
font_size = 650,
font_color = { 0, 0, 0, 100 },
font_color = { 1, 1, 1, 100 },
color = { 0, 0, 0, 0 }
})
loopID = Wait.time(sumClues, 2, -1)
Wait.time(sumClues, 2, -1)
end
-- removes all player clues by calling the respective function from the counting bowls / clickers

View File

@ -1,3 +1,4 @@
local guidReferenceApi = require("core/GUIDReferenceApi")
local playAreaApi = require("core/PlayAreaApi")
local tokenArrangerApi = require("accessories/TokenArrangerApi")
local tokenChecker = require("core/token/TokenChecker")
@ -20,11 +21,8 @@ local isReshuffling = false
-- scenario metadata
local currentScenario, useFrontData, tokenData
-- GUID of data helper
local DATA_HELPER_GUID = "708279"
local TRASHCAN
local TRASHCAN_GUID = "70b9f6"
-- object references
local TRASH, DATA_HELPER
-- we use this to turn off collision handling until onLoad() is complete
local collisionEnabled = false
@ -36,7 +34,8 @@ function onLoad(saveState)
useFrontData = loadedState.useFrontData or true
tokenData = loadedState.tokenData or {}
end
TRASHCAN = getObjectFromGUID(TRASHCAN_GUID)
TRASH = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
DATA_HELPER = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
collisionEnabled = true
end
@ -153,7 +152,7 @@ function actualEncounterCardDraw(card, params)
local faceUpRotation = 0
if not params.alwaysFaceUp then
local metadata = JSON.decode(card.getGMNotes()) or {}
if metadata.hidden or getObjectFromGUID(DATA_HELPER_GUID).call('checkHiddenCard', card.getName()) then
if metadata.hidden or DATA_HELPER.call('checkHiddenCard', card.getName()) then
faceUpRotation = 180
end
end
@ -209,7 +208,7 @@ function removeTokensFromObject(object)
obj.memo ~= nil and
obj.getLock() == false and
not tokenChecker.isChaosToken(obj) then
TRASHCAN.putObject(obj)
TRASH.putObject(obj)
end
end
end

View File

@ -1,15 +1,19 @@
do
local MythosAreaApi = {}
local MYTHOS_AREA_GUID = "9f334f"
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getMythosArea()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "MythosArea")
end
-- returns the chaos token metadata (if provided through scenario reference card)
MythosAreaApi.returnTokenData = function()
return getObjectFromGUID(MYTHOS_AREA_GUID).call("returnTokenData")
return getMythosArea().call("returnTokenData")
end
-- draw an encounter card to the requested position/rotation
MythosAreaApi.drawEncounterCard = function(pos, rotY, alwaysFaceUp)
getObjectFromGUID(MYTHOS_AREA_GUID).call("drawEncounterCard", {
getMythosArea().call("drawEncounterCard", {
pos = pos,
rotY = rotY,
alwaysFaceUp = alwaysFaceUp

View File

@ -1,21 +1,25 @@
do
local NavigationOverlayApi = {}
local HANDLER_GUID = "797ede"
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getNOHandler()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "NavigationOverlayHandler")
end
-- Copies the visibility for the Navigation overlay
---@param startColor String Color of the player to copy from
---@param targetColor String Color of the targeted player
NavigationOverlayApi.copyVisibility = function(startColor, targetColor)
getObjectFromGUID(HANDLER_GUID).call("copyVisibility", {
getNOHandler().call("copyVisibility", {
startColor = startColor,
targetColor = targetColor
})
end
end
-- Changes the Navigation Overlay view ("Full View" --> "Play Areas" --> "Closed" etc.)
---@param playerColor String Color of the player to update the visibility for
NavigationOverlayApi.cycleVisibility = function(playerColor)
getObjectFromGUID(HANDLER_GUID).call("cycleVisibility", playerColor)
getNOHandler().call("cycleVisibility", playerColor)
end
return NavigationOverlayApi

View File

@ -328,7 +328,7 @@ function loadCamera(player, index)
end
-- search on the playmat for objects
local bounds = getDynamicViewBounds(playmatApi.searchPlaymat(matColor))
local bounds = getDynamicViewBounds(playmatApi.searchAroundPlaymat(matColor))
lookHere = {
position = { bounds.middleX, 0, bounds.middleZ },

View File

@ -3,60 +3,60 @@
---------------------------------------------------------
-- Location connection directional options
local BIDIRECTIONAL = 0
local ONE_WAY = 1
local INCOMING_ONE_WAY = 2
local BIDIRECTIONAL = 0
local ONE_WAY = 1
local INCOMING_ONE_WAY = 2
-- Connector draw parameters
local CONNECTION_THICKNESS = 0.015
local CONNECTION_THICKNESS = 0.015
local DRAGGING_CONNECTION_THICKNESS = 0.15
local DRAGGING_CONNECTION_COLOR = { 0.8, 0.8, 0.8, 1 }
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
local DRAGGING_CONNECTION_COLOR = { 0.8, 0.8, 0.8, 1 }
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
local CONNECTION_LINE_Y = 1.529
-- we use this to turn off collision handling until onLoad() is complete
local collisionEnabled = false
local collisionEnabled = false
-- used for recreating the link to a custom data helper after image change
customDataHelper = nil
customDataHelper = nil
local DEFAULT_URL = "http://cloud-3.steamusercontent.com/ugc/998015670465071049/FFAE162920D67CF38045EFBD3B85AD0F916147B2/"
local DEFAULT_URL =
"http://cloud-3.steamusercontent.com/ugc/998015670465071049/FFAE162920D67CF38045EFBD3B85AD0F916147B2/"
local SHIFT_OFFSETS = {
local SHIFT_OFFSETS = {
left = { x = 0.00, y = 0, z = 7.67 },
right = { x = 0.00, y = 0, z = -7.67 },
up = { x = 6.54, y = 0, z = 0.00 },
down = { x = -6.54, y = 0, z = 0.00 }
}
local SHIFT_EXCLUSION = {
local SHIFT_EXCLUSION = {
["b7b45b"] = true,
["f182ee"] = true,
["721ba2"] = true
}
local LOC_LINK_EXCLUDE_SCENARIOS = {
local LOC_LINK_EXCLUDE_SCENARIOS = {
["The Witching Hour"] = true,
["The Heart of Madness"] = true
}
local tokenManager = require("core/token/TokenManager")
local INVESTIGATOR_COUNTER_GUID = "f182ee"
local PLAY_AREA_ZONE_GUID = "a2f932"
local guidReferenceApi = require("core/GUIDReferenceApi")
local tokenManager = require("core/token/TokenManager")
local clueData = {}
local spawnedLocationGUIDs = {}
local locations = {}
local locationConnections = {}
local draggingGuids = {}
local clueData = {}
local spawnedLocationGUIDs = {}
local locations = {}
local locationConnections = {}
local draggingGuids = {}
local locationData
local currentScenario
local missingData = {}
local countedVP = {}
local missingData = {}
local countedVP = {}
---------------------------------------------------------
-- general code
@ -71,8 +71,8 @@ end
function onLoad(saveState)
-- records locations we have spawned clues for
local save = JSON.decode(saveState) or { }
locations = save.trackedLocations or { }
local save = JSON.decode(saveState) or {}
locations = save.trackedLocations or {}
currentScenario = save.currentScenario
self.interactable = false
@ -93,13 +93,13 @@ function updateSurface(newURL)
local customInfo = self.getCustomObject()
if newURL ~= "" and newURL ~= nil and newURL ~= DEFAULT_URL then
customInfo.image = newURL
broadcastToAll("New Playmat Image Applied", { 0.2, 0.9, 0.2 })
customInfo.image = newURL
broadcastToAll("New Playmat Image Applied", { 0.2, 0.9, 0.2 })
else
customInfo.image = DEFAULT_URL
broadcastToAll("Default Playmat Image Applied", { 0.2, 0.9, 0.2 })
customInfo.image = DEFAULT_URL
broadcastToAll("Default Playmat Image Applied", { 0.2, 0.9, 0.2 })
end
self.setCustomObject(customInfo)
local guid = nil
@ -108,7 +108,7 @@ function updateSurface(newURL)
self.reload()
if guid ~= nil then
Wait.time(function() updateLocations({ guid }) end, 1)
Wait.time(function() updateLocations({ guid }) end, 1)
end
end
@ -129,12 +129,14 @@ function onCollisionEnter(collisionInfo)
if shouldSpawnTokens(card) then
tokenManager.spawnForCard(card)
end
-- If this card was being dragged, clear the dragging connections. A multi-drag/drop may send
-- the dropped card immediately into a deck, so this has to be done here
if draggingGuids[card.getGUID()] ~= nil then
card.setVectorLines(nil)
draggingGuids[card.getGUID()] = nil
end
maybeTrackLocation(card)
end
@ -167,20 +169,20 @@ function onObjectPickUp(player, object)
-- 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()) or { }
local metadata = JSON.decode(object.getGMNotes()) or {}
if metadata.type == "Location" then
-- onCollisionExit sometimes comes 1 frame after onObjectPickUp (rather than before it or in
-- the same frame). This causes a mismatch in the data between dragging the on-table, and
-- that one frame draws connectors on the card which then show up as shadows for snap points.
-- Waiting ensures we always do thing in the expected Exit->PickUp order
Wait.frames(function()
if object.is_face_down then
draggingGuids[pickedUpGuid] = metadata.locationBack
else
draggingGuids[pickedUpGuid] = metadata.locationFront
end
rebuildConnectionList()
end, 2)
if object.is_face_down then
draggingGuids[pickedUpGuid] = metadata.locationBack
else
draggingGuids[pickedUpGuid] = metadata.locationFront
end
rebuildConnectionList()
end, 2)
end
end
end
@ -273,11 +275,11 @@ end
-- drawBaseConnections()
function rebuildConnectionList()
if not showLocationLinks() then
locationConnections = { }
locationConnections = {}
return
end
local iconCardList = { }
local iconCardList = {}
-- Build a list of cards with each icon as their location ID
for cardId, metadata in pairs(draggingGuids) do
@ -288,7 +290,7 @@ function rebuildConnectionList()
end
-- Pair up all the icons
locationConnections = { }
locationConnections = {}
for cardId, metadata in pairs(draggingGuids) do
buildConnection(cardId, iconCardList, metadata)
end
@ -307,7 +309,7 @@ function buildLocListByIcon(cardId, iconCardList, locData)
if locData ~= nil and locData.icons ~= nil then
for icon in string.gmatch(locData.icons, "%a+") do
if iconCardList[icon] == nil then
iconCardList[icon] = { }
iconCardList[icon] = {}
end
table.insert(iconCardList[icon], cardId)
end
@ -321,19 +323,19 @@ end
---@param locData Table A table containing the metadata for the card (for the correct side)
function buildConnection(cardId, iconCardList, locData)
if locData ~= nil and locData.connections ~= nil then
locationConnections[cardId] = { }
locationConnections[cardId] = {}
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][cardId] == ONE_WAY
or locationConnections[connectedGuid][cardId] == BIDIRECTIONAL) then
or locationConnections[connectedGuid][cardId] == BIDIRECTIONAL) then
locationConnections[connectedGuid][cardId] = BIDIRECTIONAL
locationConnections[cardId][connectedGuid] = nil
else
if locationConnections[connectedGuid] == nil then
locationConnections[connectedGuid] = { }
locationConnections[connectedGuid] = {}
end
locationConnections[cardId][connectedGuid] = ONE_WAY
locationConnections[connectedGuid][cardId] = INCOMING_ONE_WAY
@ -348,10 +350,10 @@ end
-- Constructed vectors will be set to the playmat
function drawBaseConnections()
if not showLocationLinks() then
locationConnections = { }
locationConnections = {}
return
end
local cardConnectionLines = { }
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
@ -380,8 +382,8 @@ function drawDraggingConnections()
if not showLocationLinks() then
return
end
local cardConnectionLines = { }
local ownedVectors = { }
local cardConnectionLines = {}
local ownedVectors = {}
for originGuid, _ in pairs(draggingGuids) do
targetGuids = locationConnections[originGuid]
@ -389,7 +391,7 @@ function drawDraggingConnections()
-- object checks are conservative just to make sure.
local origin = getObjectFromGUID(originGuid)
if draggingGuids[originGuid] and origin ~= nil and targetGuids ~= nil then
ownedVectors[originGuid] = { }
ownedVectors[originGuid] = {}
for targetGuid, direction in pairs(targetGuids) do
local target = getObjectFromGUID(targetGuid)
if target != nil then
@ -427,9 +429,9 @@ function addBidirectionalVector(card1, card2, vectorOwner, lines)
local pos2 = vectorOwner.positionToLocal(cardPos2)
table.insert(lines, {
points = { pos1, pos2 },
color = vectorOwner == self and CONNECTION_COLOR or DRAGGING_CONNECTION_COLOR,
thickness = vectorOwner == self and CONNECTION_THICKNESS or DRAGGING_CONNECTION_THICKNESS,
points = { pos1, pos2 },
color = vectorOwner == self and CONNECTION_COLOR or DRAGGING_CONNECTION_COLOR,
thickness = vectorOwner == self and CONNECTION_THICKNESS or DRAGGING_CONNECTION_THICKNESS,
})
end
@ -451,11 +453,13 @@ function addOneWayVector(origin, target, vectorOwner, lines)
-- 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)))
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 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)
@ -474,14 +478,16 @@ end
--- positioning and scaling, as well as highlighting connections during a drag operation
---@param lines Table List of vector line elements. Mutable, will be updated to add this arrow
function addArrowLines(arrowheadPos, originPos, vectorOwner, 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 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 = vectorOwner.positionToLocal(arrowheadPos)
local arm1 = vectorOwner.positionToLocal(arrowArm1)
local arm2 = vectorOwner.positionToLocal(arrowArm2)
table.insert(lines, {
points = { arm1, head, arm2},
points = { arm1, head, arm2 },
color = vectorOwner == self and CONNECTION_COLOR or DRAGGING_CONNECTION_COLOR,
thickness = vectorOwner == self and CONNECTION_THICKNESS or DRAGGING_CONNECTION_THICKNESS,
})
@ -508,7 +514,7 @@ function shiftContentsRight(playerColor)
end
function shiftContents(playerColor, direction)
local zone = getObjectFromGUID(PLAY_AREA_ZONE_GUID)
local zone = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayAreaZone")
if not zone then
broadcastToColor("Scripting zone couldn't be found.", playerColor, "Red")
return
@ -530,8 +536,8 @@ 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}
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
@ -582,7 +588,7 @@ function countVP()
local cardVP = tonumber(metadata.victory) or 0
if cardVP ~= 0 and not cardHasClues(cardId) then
totalVP = totalVP + cardVP
if cardVP >0 then
if cardVP > 0 then
table.insert(countedVP, getObjectFromGUID(cardId))
end
end
@ -651,8 +657,8 @@ end
-- rebuilds local snap points (could be useful in the future again)
function buildSnaps()
local upperleft = { x = 1.53, z = -1.09}
local lowerright = {x = -1.53, z = 1.55}
local upperleft = { x = 1.53, z = -1.09 }
local lowerright = { x = -1.53, z = 1.55 }
local snaps = {}
-- creates 81 snap points, for uneven rows + columns it makes a rotation snap point
@ -666,7 +672,7 @@ function buildSnaps()
-- enable rotation snaps for uneven rows / columns
if (i % 2 ~= 0) and (j % 2 ~= 0) then
snap.rotation = {0, 0, 0}
snap.rotation = { 0, 0, 0 }
snap.rotation_snap = true
end
@ -678,6 +684,6 @@ end
-- utility function
function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
local mult = 10 ^ (numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end

View File

@ -1,105 +1,109 @@
do
local PlayAreaApi = { }
local PLAY_AREA_GUID = "721ba2"
local INVESTIGATOR_COUNTER_GUID = "f182ee"
local PlayAreaApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getPlayArea()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayArea")
end
local function getInvestigatorCounter()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "InvestigatorCounter")
end
-- Returns the current value of the investigator counter from the playmat
---@return Integer. Number of investigators currently set on the counter
PlayAreaApi.getInvestigatorCount = function()
return getObjectFromGUID(INVESTIGATOR_COUNTER_GUID).getVar("val")
return getInvestigatorCounter().getVar("val")
end
-- Updates the current value of the investigator counter from the playmat
---@param count Number of investigators to set on the counter
PlayAreaApi.setInvestigatorCount = function(count)
return getObjectFromGUID(INVESTIGATOR_COUNTER_GUID).call("updateVal", count)
getInvestigatorCounter().call("updateVal", count)
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'
---@param playerColor Color of the player requesting the shift. Used solely to send an error
--- message in the unlikely case that the scripting zone has been deleted
-- fixed objects will be ignored, as will anything the player has tagged with 'displacement_excluded'
---@param playerColor Color Color of the player requesting the shift for messages
PlayAreaApi.shiftContentsUp = function(playerColor)
return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsUp", playerColor)
return getPlayArea().call("shiftContentsUp", playerColor)
end
PlayAreaApi.shiftContentsDown = function(playerColor)
return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsDown", playerColor)
return getPlayArea().call("shiftContentsDown", playerColor)
end
PlayAreaApi.shiftContentsLeft = function(playerColor)
return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsLeft", playerColor)
return getPlayArea().call("shiftContentsLeft", playerColor)
end
PlayAreaApi.shiftContentsRight = function(playerColor)
return getObjectFromGUID(PLAY_AREA_GUID).call("shiftContentsRight", playerColor)
return getPlayArea().call("shiftContentsRight", playerColor)
end
-- Reset the play area's tracking of which cards have had tokens spawned.
PlayAreaApi.resetSpawnedCards = function()
return getObjectFromGUID(PLAY_AREA_GUID).call("resetSpawnedCards")
return getPlayArea().call("resetSpawnedCards")
end
-- Event to be called when the current scenario has changed.
---@param scenarioName Name of the new scenario
PlayAreaApi.onScenarioChanged = function(scenarioName)
getObjectFromGUID(PLAY_AREA_GUID).call("onScenarioChanged", scenarioName)
getPlayArea().call("onScenarioChanged", scenarioName)
end
-- Sets this playmat's snap points to limit snapping to locations or not.
-- If matchTypes is false, snap points will be reset to snap all cards.
---@param matchTypes Boolean Whether snap points should only snap for the matching card types.
PlayAreaApi.setLimitSnapsByType = function(matchCardTypes)
getObjectFromGUID(PLAY_AREA_GUID).call("setLimitSnapsByType", matchCardTypes)
getPlayArea().call("setLimitSnapsByType", matchCardTypes)
end
-- Receiver for the Global tryObjectEnterContainer event. Used to clear vector lines from dragged
-- cards before they're destroyed by entering the container
PlayAreaApi.tryObjectEnterContainer = function(container, object)
getObjectFromGUID(PLAY_AREA_GUID).call("tryObjectEnterContainer",
{ container = container, object = object })
getPlayArea().call("tryObjectEnterContainer", { container = container, object = object })
end
-- counts the VP on locations in the play area
PlayAreaApi.countVP = function()
return getObjectFromGUID(PLAY_AREA_GUID).call("countVP")
return getPlayArea().call("countVP")
end
-- highlights all locations in the play area without metadata
---@param state Boolean True if highlighting should be enabled
PlayAreaApi.highlightMissingData = function(state)
return getObjectFromGUID(PLAY_AREA_GUID).call("highlightMissingData", state)
return getPlayArea().call("highlightMissingData", state)
end
-- highlights all locations in the play area with VP
---@param state Boolean True if highlighting should be enabled
PlayAreaApi.highlightCountedVP = function(state)
return getObjectFromGUID(PLAY_AREA_GUID).call("highlightCountedVP", state)
return getPlayArea().call("highlightCountedVP", state)
end
-- Checks if an object is in the play area (returns true or false)
PlayAreaApi.isInPlayArea = function(object)
return getObjectFromGUID(PLAY_AREA_GUID).call("isInPlayArea", object)
return getPlayArea().call("isInPlayArea", object)
end
PlayAreaApi.getSurface = function()
return getObjectFromGUID(PLAY_AREA_GUID).getCustomObject().image
return getPlayArea().getCustomObject().image
end
PlayAreaApi.updateSurface = function(url)
return getObjectFromGUID(PLAY_AREA_GUID).call("updateSurface", url)
return getPlayArea().call("updateSurface", url)
end
-- Called by Custom Data Helpers to push their location data into the Data Helper. This adds the
-- data to the local token manager instance.
---@param args Table Single-value array holding the GUID of the Custom Data Helper making the call
PlayAreaApi.updateLocations = function(args)
getObjectFromGUID(PLAY_AREA_GUID).call("updateLocations", args)
getPlayArea().call("updateLocations", args)
end
PlayAreaApi.getCustomDataHelper = function()
return getObjectFromGUID(PLAY_AREA_GUID).getVar("customDataHelper")
return getPlayArea().getVar("customDataHelper")
end
return PlayAreaApi

View File

@ -1,5 +1,6 @@
do
local SoundCubeApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
-- this table links the name of a trigger effect to its index
local soundIndices = {
@ -9,7 +10,8 @@ do
}
local function playTriggerEffect(index)
getObjectsWithTag("SoundCube")[1].AssetBundle.playTriggerEffect(index)
local SoundCube = guidReferenceApi.getObjectByOwnerAndType("Mythos", "SoundCube")
SoundCube.AssetBundle.playTriggerEffect(index)
end
-- plays the by name requested sound

View File

@ -1,4 +1,5 @@
local chaosBagApi = require("chaosbag/ChaosBagApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playAreaApi = require("core/PlayAreaApi")
local tokenChecker = require("core/token/TokenChecker")
@ -10,13 +11,8 @@ local countedVP = {}
local highlightMissing = false
local highlightCounted = false
local TRASHCAN
local TRASHCAN_GUID = "70b9f6"
-- button creation when loading the game
function onLoad()
TRASHCAN = getObjectFromGUID(TRASHCAN_GUID)
-- index 0: VP - "Display"
local buttonParameters = {}
buttonParameters.label = "0"
@ -236,8 +232,7 @@ end
function highlightCountedVP()
self.editButton({
index = 4,
tooltip = (highlightCounted and "Enable" or "Disable") ..
" highlighting of cards with VP."
tooltip = (highlightCounted and "Enable" or "Disable") .. " highlighting of cards with VP."
})
for _, obj in pairs(countedVP) do
if obj ~= nil then
@ -254,6 +249,8 @@ end
-- places the provided card in the first empty spot
function placeCard(card)
local trash = guidReferenceApi.getObjectByOwnerAndType("Mythos", "Trash")
-- check snap point states
local snaps = self.getSnapPoints()
table.sort(snaps, function(a, b) return a.position.x > b.position.x end)
@ -283,7 +280,7 @@ function placeCard(card)
local chaosBag = chaosBagApi.findChaosBag()
chaosBag.putObject(obj)
elseif obj.memo ~= nil and obj.getLock() == false then
TRASHCAN.putObject(obj)
trash.putObject(obj)
end
end
@ -327,13 +324,3 @@ function checkSnapPointState(pos)
origin = pos
})
end
-- search a table for a value, return true if found (else returns false)
function tableContains(table, value)
for _, v in ipairs(table) do
if v == value then
return true
end
end
return false
end

View File

@ -1,18 +1,22 @@
do
local VictoryDisplayApi = {}
local VD_GUID = "6ccd6d"
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getVictoryDisplay()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "VictoryDisplay")
end
-- triggers an update of the Victory count
---@param delay Number Delay in seconds after which the update call is executed
VictoryDisplayApi.update = function(delay)
getObjectFromGUID(VD_GUID).call("startUpdate", delay)
getVictoryDisplay().call("startUpdate", delay)
end
-- moves a card to the victory display (in the first empty spot)
---@param object Object Object that should be checked and potentially moved
VictoryDisplayApi.placeCard = function(object)
if object ~= nil and object.tag == "Card" then
getObjectFromGUID(VD_GUID).call("placeCard", object)
getVictoryDisplay().call("placeCard", object)
end
end

View File

@ -1,4 +1,5 @@
do
local guidReferenceApi = require("core/GUIDReferenceApi")
local optionPanelApi = require("core/OptionPanelApi")
local playAreaApi = require("core/PlayAreaApi")
local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi")
@ -119,15 +120,10 @@ do
["supply"] = 7
}
-- Source for tokens
local TOKEN_SOURCE_GUID = "124381"
-- Table of data extracted from the token source bag, keyed by the Memo on each token which
-- should match the token type keys ("resource", "clue", etc)
local tokenTemplates
local DATA_HELPER_GUID = "708279"
local playerCardData
local locationData
@ -225,9 +221,11 @@ do
-- Copy the offsets to make sure we don't change the static values
local baseOffsets = offsets
offsets = { }
-- get a vector for the shifting (downwards local to the card)
local shiftDownVector = Vector(0, 0, shiftDown):rotateOver("y", card.getRotation().y)
for i, baseOffset in ipairs(baseOffsets) do
offsets[i] = baseOffset
offsets[i][3] = offsets[i][3] + shiftDown
offsets[i] = baseOffset + shiftDownVector
end
end
@ -340,8 +338,8 @@ do
if tokenTemplates ~= nil then
return
end
tokenTemplates = { }
local tokenSource = getObjectFromGUID(TOKEN_SOURCE_GUID)
tokenTemplates = {}
local tokenSource = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSource")
for _, tokenTemplate in ipairs(tokenSource.getData().ContainedObjects) do
local tokenName = tokenTemplate.Memo
tokenTemplates[tokenName] = tokenTemplate
@ -353,7 +351,7 @@ do
if playerCardData ~= nil then
return
end
local dataHelper = getObjectFromGUID(DATA_HELPER_GUID)
local dataHelper = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper")
playerCardData = dataHelper.getTable('PLAYER_CARD_DATA')
locationData = dataHelper.getTable('LOCATIONS_DATA')
end
@ -368,18 +366,16 @@ do
if uses == nil then return end
-- go through tokens to spawn
local type, token, tokenCount
local tokenCount
for i, useInfo in ipairs(uses) do
type = useInfo.type
token = useInfo.token
tokenCount = (useInfo.count or 0)
+ (useInfo.countPerInvestigator or 0) * playAreaApi.getInvestigatorCount()
if extraUses ~= nil and extraUses[type] ~= nil then
tokenCount = tokenCount + extraUses[type]
tokenCount = (useInfo.count or 0) + (useInfo.countPerInvestigator or 0) * playAreaApi.getInvestigatorCount()
if extraUses ~= nil and extraUses[useInfo.type] ~= nil then
tokenCount = tokenCount + extraUses[useInfo.type]
end
-- Shift each spawned group after the first down so they don't pile on each other
TokenManager.spawnTokenGroup(card, token, tokenCount, (i - 1) * 0.8, type)
TokenManager.spawnTokenGroup(card, useInfo.token, tokenCount, (i - 1) * 0.8, useInfo.type)
end
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
end
@ -403,9 +399,8 @@ do
---@param playerData Table Player card data structure retrieved from the DataHelper. Should be
-- the right data for this card.
internal.spawnPlayerCardTokensFromDataHelper = function(card, playerData)
token = playerData.tokenType
tokenCount = playerData.tokenCount
--log("Spawning data helper tokens for "..card.getName()..'['..card.getDescription()..']: '..tokenCount.."x "..token)
local token = playerData.tokenType
local tokenCount = playerData.tokenCount
TokenManager.spawnTokenGroup(card, token, tokenCount)
tokenSpawnTrackerApi.markTokensSpawned(card.getGUID())
end
@ -438,7 +433,6 @@ do
return 0
end
--log(card.getName() .. ' : ' .. locationData.type .. ' : ' .. locationData.value .. ' : ' .. locationData.clueSide)
if ((card.is_face_down and locationData.clueSide == 'back')
or (not card.is_face_down and locationData.clueSide == 'front')) then
if locationData.type == 'fixed' then

View File

@ -1,26 +1,15 @@
local spawnedCardGuids = { }
local spawnedCardGuids = {}
local HAND_ZONES = { }
HAND_ZONES["a70eee"] = true -- White
HAND_ZONES["0285cc"] = true -- Green
HAND_ZONES["5fe087"] = true -- Orange
HAND_ZONES["be2f17"] = true -- Red
function onSave() return JSON.encode({ cards = spawnedCardGuids }) end
function onLoad(saveState)
if saveState ~= nil then
local saveTable = JSON.decode(saveState) or { }
spawnedCardGuids = saveTable.cards or { }
local saveTable = JSON.decode(saveState) or {}
spawnedCardGuids = saveTable.cards or {}
end
createResetMenuItems()
end
function onSave()
return JSON.encode({
cards = spawnedCardGuids
})
end
function createResetMenuItems()
self.addContextMenuItem("Reset All", resetAll)
self.addContextMenuItem("Reset Locations", resetAllLocations)
@ -39,14 +28,20 @@ function resetTokensSpawned(cardGuid)
spawnedCardGuids[cardGuid] = nil
end
function resetAllAssetAndEvents()
local resetList = { }
function resetAll() spawnedCardGuids = {} end
function resetAllLocations() resetSpecificTypes("Location") end
function resetAllAssetAndEvents() resetSpecificTypes("Asset", "Event") end
function resetSpecificTypes(type1, type2)
local resetList = {}
for cardGuid, _ in pairs(spawnedCardGuids) do
local card = getObjectFromGUID(cardGuid)
if card ~= nil then
local cardMetadata = JSON.decode(card.getGMNotes()) or { }
local cardMetadata = JSON.decode(card.getGMNotes()) or {}
-- Check this by type rather than the PlayerCard tag so we don't reset weaknesses
if cardMetadata.type == "Asset" or cardMetadata.type == "Event" then
if cardMetadata.type == type1 or cardMetadata.type == type2 then
resetList[cardGuid] = true
end
end
@ -56,30 +51,9 @@ function resetAllAssetAndEvents()
end
end
function resetAllLocations()
local resetList = { }
for cardGuid, _ in pairs(spawnedCardGuids) do
local card = getObjectFromGUID(cardGuid)
if card ~= nil then
local cardMetadata = JSON.decode(card.getGMNotes()) or { }
-- Check this by type rather than the PlayerCard tag so we don't reset weaknesses
if cardMetadata.type == "Location" then
resetList[cardGuid] = true
end
end
end
for cardGuid, _ in pairs(resetList) do
spawnedCardGuids[cardGuid] = nil
end
end
function resetAll()
spawnedCardGuids = { }
end
-- Listener to reset card token spawns when they enter a hand.
function onObjectEnterZone(zone, enterObject)
if HAND_ZONES[zone.getGUID()] then
if zone.type == "Hand" and enterObject.type == "Card" then
resetTokensSpawned(enterObject.getGUID())
end
end

View File

@ -1,29 +1,33 @@
do
local TokenSpawnTracker = { }
local SPAWN_TRACKER_GUID = "e3ffc9"
local TokenSpawnTracker = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getSpawnTracker()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSpawnTracker")
end
TokenSpawnTracker.hasSpawnedTokens = function(cardGuid)
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("hasSpawnedTokens", cardGuid)
return getSpawnTracker().call("hasSpawnedTokens", cardGuid)
end
TokenSpawnTracker.markTokensSpawned = function(cardGuid)
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("markTokensSpawned", cardGuid)
return getSpawnTracker().call("markTokensSpawned", cardGuid)
end
TokenSpawnTracker.resetTokensSpawned = function(cardGuid)
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetTokensSpawned", cardGuid)
return getSpawnTracker().call("resetTokensSpawned", cardGuid)
end
TokenSpawnTracker.resetAllAssetAndEvents = function()
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllAssetAndEvents")
return getSpawnTracker().call("resetAllAssetAndEvents")
end
TokenSpawnTracker.resetAllLocations = function()
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAllLocations")
return getSpawnTracker().call("resetAllLocations")
end
TokenSpawnTracker.resetAll = function()
return getObjectFromGUID(SPAWN_TRACKER_GUID).call("resetAll")
return getSpawnTracker().call("resetAll")
end
return TokenSpawnTracker

View File

@ -1,8 +1,9 @@
do
require("core/tour/TourScript")
require("core/tour/TourCard")
local TourManager = { }
local internal = { }
local TourManager = {}
local internal = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
-- Base IDs for various tour card UI elements. Actual IDs will have _[playerColor] appended
local CARD_ID = "tourCard"
@ -123,8 +124,9 @@ do
delay = delay + 0.5
end
local lookPos
if TOUR_SCRIPT[cardIndex].showObj ~= nil then
local lookAtObj = getObjectFromGUID(TOUR_SCRIPT[cardIndex].showObj)
local objReferenceData = TOUR_SCRIPT[cardIndex].objReferenceData
if objReferenceData ~= nil then
local lookAtObj = guidReferenceApi.getObjectByOwnerAndType(objReferenceData.owner, objReferenceData.type)
lookPos = lookAtObj.getPosition()
lookPos.y = TOUR_SCRIPT[cardIndex].distanceFromObj or 0
-- Since camera isn't directly above the hook, changing the Y affects the visual position of

View File

@ -15,7 +15,7 @@ TOUR_SCRIPT = {
{
narrator = "Daisy",
text = "If you're new to the game, the library here has everything you'll need. A little research can go a long way, and looking into old newspapers for the weird and unusual can yield some surprisingly helpful information.\n\nI put a few right there that might prove enlightening.",
showObj = "d99993",
objReferenceData = { owner = "Mythos", type = "RulesReference" },
distanceFromObj = 20,
position = "west",
speakerSide = "right"
@ -23,7 +23,7 @@ TOUR_SCRIPT = {
{
narrator = "Mandy",
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",
objReferenceData = { owner = "Mythos", type = "DeckImporter" },
distanceFromObj = -5,
position = "northwest",
skipCentering = true,
@ -31,7 +31,7 @@ TOUR_SCRIPT = {
{
narrator = "Daniela",
text = "I prefer the hands-on approach to building things, if you do too you can build a deck yourself.\n\nAll the cards you could ever need are here, laid out like a disassembled engine. Place the cards on the table, copy them for your deck, and you'll be ready for anything.",
showObj = "2d30ee",
objReferenceData = { owner = "Mythos", type = "PlayerCardPanel" },
distanceFromObj = -7,
position = "south",
speakerSide = "right"
@ -39,7 +39,7 @@ TOUR_SCRIPT = {
{
narrator = "Finn",
text = "Ready to face the unknown? We've smuggled shocking revelations and devious enemies from all over the world. Download the campaign you want to play, then Place it on the table to see the scenarios.\n\nJust remember - if it turns out to be too much for you, I was never here.",
showObj = "aca04c",
objReferenceData = { owner = "Mythos", type = "CampaignThePathToCarcosa" },
distanceFromObj = 20,
position = "northwest",
},
@ -77,7 +77,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. I leave those to my bookkeeper though. Those tokens can work like counters, use the number keys to change the value.",
showObj = "9fadf9",
objReferenceData = { owner = "Mythos", type = "ResourceTokenBag" },
position = "north",
skipCentering = true,
speakerSide = "right"

View File

@ -1,29 +1,33 @@
do
local AllCardsBagApi = {}
local ALL_CARDS_BAG_GUID = "15bb07"
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getAllCardsBag()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "AllCardsBag")
end
-- Returns a specific card from the bag, based on ArkhamDB ID
-- @param table:
-- id: String ID of the card to retrieve
-- @return: If the indexes are still being constructed, an empty table is
-- returned. Otherwise, a single table with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
---@param id table String ID of the card to retrieve
---@return table table
-- If the indexes are still being constructed, an empty table is
-- returned. Otherwise, a single table with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
AllCardsBagApi.getCardById = function(id)
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("getCardById", {id = id})
return getAllCardsBag().call("getCardById", {id = id})
end
-- Gets a random basic weakness from the bag. Once a given ID has been returned
-- it will be removed from the list and cannot be selected again until a reload
-- occurs or the indexes are rebuilt, which will refresh the list to include all
-- weaknesses.
-- @return: String ID of the selected weakness.
---@return id String ID of the selected weakness.
AllCardsBagApi.getRandomWeaknessId = function()
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("getRandomWeaknessId")
return getAllCardsBag().call("getRandomWeaknessId")
end
AllCardsBagApi.isIndexReady = function()
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("isIndexReady")
return getAllCardsBag().call("isIndexReady")
end
-- Called by Hotfix bags when they load. If we are still loading indexes, then
@ -32,40 +36,38 @@ do
-- called once indexing is complete it means the hotfix bag has been added
-- later, and we should rebuild the index to integrate the hotfix bag.
AllCardsBagApi.rebuildIndexForHotfix = function()
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("rebuildIndexForHotfix")
return getAllCardsBag().call("rebuildIndexForHotfix")
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.
-- @param
-- name String or string fragment to search for names
-- exact Whether the name match should be exact
---@param name String or string fragment to search for names
---@param exact Boolean Whether the name match should be exact
AllCardsBagApi.getCardsByName = function(name, exact)
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("getCardsByName", {name = name, exact = exact})
return getAllCardsBag().call("getCardsByName", {name = name, exact = exact})
end
AllCardsBagApi.isBagPresent = function()
return getObjectFromGUID(ALL_CARDS_BAG_GUID) and true
return getAllCardsBag() and true
end
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
-- @param
-- class: String class to retrieve ("Guardian", "Seeker", etc)
-- upgraded: true for upgraded cards (Level 1-5), false for Level 0
-- @return: If the indexes are still being constructed, returns an empty table.
---@param class String class to retrieve ("Guardian", "Seeker", etc)
---@param upgraded Boolean true for upgraded cards (Level 1-5), false for Level 0
---@return: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a list of tables, each with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded)
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("getCardsByClassAndLevel", {class = class, upgraded = upgraded})
return getAllCardsBag().call("getCardsByClassAndLevel", {class = class, upgraded = upgraded})
end
AllCardsBagApi.getCardsByCycle = function(cycle)
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("getCardsByCycle", cycle)
return getAllCardsBag().call("getCardsByCycle", cycle)
end
AllCardsBagApi.getUniqueWeaknesses = function()
return getObjectFromGUID(ALL_CARDS_BAG_GUID).call("getUniqueWeaknesses")
return getAllCardsBag().call("getUniqueWeaknesses")
end
return AllCardsBagApi

View File

@ -245,9 +245,9 @@ INVESTIGATORS["Jenny Barnes"] = {
starterDeck = "2624961"
}
INVESTIGATORS["Jim Culver"] = {
cards = { "02004" },
cards = { "02004", "02004-p", "02004-pf", "02004-pb" },
minicards = { "02004-m" },
signatures = { "02012", "02013" },
signatures = { "02012", "02013", "90050", "90051", "90052", "90053" },
starterDeck = "2624965"
}
INVESTIGATORS["\"Ashcan\" Pete"] = {

View File

@ -16,8 +16,8 @@ function searchSelf()
for _, obj in ipairs(searchArea(self.getPosition(), { 2.5, 0.5, 3.5 })) do
local obj = obj.hit_object
if obj.getCustomObject().image ==
"http://cloud-3.steamusercontent.com/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/" then
local image = obj.getCustomObject().image
if image == "http://cloud-3.steamusercontent.com/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/" then
foundTokens = foundTokens + math.abs(obj.getQuantity())
obj.destruct()
elseif obj.getMemo() == "resourceCounter" then
@ -47,7 +47,7 @@ end
function takeAll(playerColor)
searchSelf()
local matColor = playmatApi.getMatColorByPosition(self.getPosition())
playmatApi.gainResources(foundTokens, matColor)
playmatApi.updateCounter(matColor, "ResourceCounter", foundTokens)
if clickableResourceCounter then
clickableResourceCounter.call("updateVal", 0)

View File

@ -1,4 +1,4 @@
local playmatAPI = require("playermat/PlaymatApi")
local playmatApi = require("playermat/PlaymatApi")
function onLoad()
self.addContextMenuItem("Discard 10 cards", shortSupply)
@ -6,11 +6,11 @@ end
-- called by context menu entry
function shortSupply(color)
local matColor = playmatAPI.getMatColorByPosition(self.getPosition())
local matColor = playmatApi.getMatColorByPosition(self.getPosition())
-- get draw deck and discard position
local drawDeck = playmatAPI.getDrawDeck(matColor)
local discardPos = playmatAPI.getDiscardPosition(matColor)
local drawDeck = playmatApi.getDrawDeck(matColor)
local discardPos = playmatApi.getDiscardPosition(matColor)
-- error handling
if discardPos == nil then
@ -21,7 +21,7 @@ function shortSupply(color)
if drawDeck == nil then
broadcastToColor("Deck not found!", color, "Yellow")
return
elseif drawDeck.tag ~= "Deck" then
elseif drawDeck.type ~= "Deck" then
broadcastToColor("Deck only contains a single card!", color, "Yellow")
return
end

Some files were not shown because too many files have changed in this diff Show More