diff --git a/objects/AdditionalPlayerCards.2cba6b.json b/objects/AdditionalPlayerCards.2cba6b.json index 20507862..5f44d8a7 100644 --- a/objects/AdditionalPlayerCards.2cba6b.json +++ b/objects/AdditionalPlayerCards.2cba6b.json @@ -51,7 +51,7 @@ "IgnoreFoW": false, "LayoutGroupSortIndex": 0, "Locked": true, - "LuaScript": "require(\"arkhamdb/HotfixBag\")", + "LuaScript": "require(\"playercards/AdditionalPlayerCards\")", "LuaScriptState": "", "MaterialIndex": -1, "MeasureMovement": false, diff --git a/objects/AdditionalPlayerCards.2cba6b/AliceLuxley2.94f23b.gmnotes b/objects/AdditionalPlayerCards.2cba6b/AliceLuxley2.94f23b.gmnotes index 82bdd12b..fc0395e5 100644 --- a/objects/AdditionalPlayerCards.2cba6b/AliceLuxley2.94f23b.gmnotes +++ b/objects/AdditionalPlayerCards.2cba6b/AliceLuxley2.94f23b.gmnotes @@ -1,6 +1,7 @@ { - "id": "b5151", + "id": "A2022", "type": "Asset", + "slot": "Ally", "class": "Guardian", "cost": 3, "level": 3, diff --git a/objects/AdditionalPlayerCards.2cba6b/DragonPole3.a20aef.gmnotes b/objects/AdditionalPlayerCards.2cba6b/DragonPole3.a20aef.gmnotes index 9193d02c..5953c545 100644 --- a/objects/AdditionalPlayerCards.2cba6b/DragonPole3.a20aef.gmnotes +++ b/objects/AdditionalPlayerCards.2cba6b/DragonPole3.a20aef.gmnotes @@ -1,6 +1,7 @@ { - "id": "b8060", + "id": "B2022", "type": "Asset", + "slot": "Hand x2", "class": "Guardian|Mystic", "cost": 3, "level": 3, diff --git a/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.gmnotes b/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.gmnotes index 16d590a1..0424efef 100644 --- a/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.gmnotes +++ b/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.gmnotes @@ -12,39 +12,12 @@ "agilityIcons": 3, "cycle": "Core", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "01012": 1, - "90018": 1 - }, - { - "01013": 1, - "90019": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 2 - } + "01012": 1, + "01013": 1, + "90018": 1, + "90019": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.json b/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.json index cbb4e1b1..a49fc99f 100644 --- a/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.json +++ b/objects/AllPlayerCards.15bb07/AgnesBaker.25e2db.json @@ -67,7 +67,7 @@ }, "Description": "The Waitress", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"01004\",\r\n \"alternate_ids\": [\r\n \"01504\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Mystic\",\r\n \"traits\": \"Sorcerer.\",\r\n \"willpowerIcons\": 5,\r\n \"intellectIcons\": 2,\r\n \"combatIcons\": 2,\r\n \"agilityIcons\": 3,\r\n \"cycle\": \"Core\",\r\n \"extraToken\": \"Reaction\", \r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"01012\": 1,\r\n \"90018\": 1\r\n },\r\n {\r\n \"01013\": 1,\r\n \"90019\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"mystic\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"survivor\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"01004\",\n \"alternate_ids\": [\n \"01504\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n \"traits\": \"Sorcerer.\",\n \"willpowerIcons\": 5,\n \"intellectIcons\": 2,\n \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"Core\",\n \"extraToken\": \"Reaction\",\n \"signatures\": [\n {\n \"01012\": 1,\n \"01013\": 1,\n \"90018\": 1,\n \"90019\": 1\n }\n ]\n}", "GUID": "6797bb", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/AgnesBakerParallel.01b6ef.gmnotes b/objects/AllPlayerCards.15bb07/AgnesBakerParallel.01b6ef.gmnotes index 01b7e21e..dd32b735 100644 --- a/objects/AllPlayerCards.15bb07/AgnesBakerParallel.01b6ef.gmnotes +++ b/objects/AllPlayerCards.15bb07/AgnesBakerParallel.01b6ef.gmnotes @@ -9,40 +9,12 @@ "agilityIcons": 3, "cycle": "Bad Blood", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "01012": 1, - "90018": 1 - }, - { - "01013": 1, - "90019": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "spell", - "occult" - ], - "level": { - "min": 0, - "max": 3 - } + "01012": 1, + "01013": 1, + "90018": 1, + "90019": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AgnesBakerParallelBack.909f30.gmnotes b/objects/AllPlayerCards.15bb07/AgnesBakerParallelBack.909f30.gmnotes index c300317b..1e870b28 100644 --- a/objects/AllPlayerCards.15bb07/AgnesBakerParallelBack.909f30.gmnotes +++ b/objects/AllPlayerCards.15bb07/AgnesBakerParallelBack.909f30.gmnotes @@ -9,40 +9,12 @@ "agilityIcons": 3, "cycle": "Bad Blood", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "01012": 1, - "90018": 1 - }, - { - "01013": 1, - "90019": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "spell", - "occult" - ], - "level": { - "min": 0, - "max": 3 - } + "01012": 1, + "01013": 1, + "90018": 1, + "90019": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AgnesBakerParallelFront.02db0a.gmnotes b/objects/AllPlayerCards.15bb07/AgnesBakerParallelFront.02db0a.gmnotes index 0faaeda5..a47209d5 100644 --- a/objects/AllPlayerCards.15bb07/AgnesBakerParallelFront.02db0a.gmnotes +++ b/objects/AllPlayerCards.15bb07/AgnesBakerParallelFront.02db0a.gmnotes @@ -9,39 +9,12 @@ "agilityIcons": 3, "cycle": "Bad Blood", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "01012": 1, - "90018": 1 - }, - { - "01013": 1, - "90019": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 2 - } + "01012": 1, + "01013": 1, + "90018": 1, + "90019": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AkachiOnyele.452ed8.gmnotes b/objects/AllPlayerCards.15bb07/AkachiOnyele.452ed8.gmnotes index 3861ca12..287c55ea 100644 --- a/objects/AllPlayerCards.15bb07/AkachiOnyele.452ed8.gmnotes +++ b/objects/AllPlayerCards.15bb07/AkachiOnyele.452ed8.gmnotes @@ -9,46 +9,10 @@ "agilityIcons": 3, "cycle": "The Path to Carcosa", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03014": 1 - }, - { - "03015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "uses": [ - "charge" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "trait": [ - "occult" - ], - "level": { - "min": 0, - "max": 0 - } + "03014": 1, + "03015": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AlchemicalDistillationUpgradeSheet.156166.json b/objects/AllPlayerCards.15bb07/AlchemicalDistillationUpgradeSheet.156166.json index ff0a7cad..91d87e42 100644 --- a/objects/AllPlayerCards.15bb07/AlchemicalDistillationUpgradeSheet.156166.json +++ b/objects/AllPlayerCards.15bb07/AlchemicalDistillationUpgradeSheet.156166.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09040-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09040-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "156166", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/AlessandraZorzi.54eaa5.gmnotes b/objects/AllPlayerCards.15bb07/AlessandraZorzi.54eaa5.gmnotes index c203fa0c..fe98bdf7 100644 --- a/objects/AllPlayerCards.15bb07/AlessandraZorzi.54eaa5.gmnotes +++ b/objects/AllPlayerCards.15bb07/AlessandraZorzi.54eaa5.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 4, "cycle": "The Feast of Hemlock Vale", "extraToken": "Parley", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "10010": 3 - }, - { - "10011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "special": [ - "parley" - ], - "level": { - "min": 0, - "max": 5 - } + "10010": 3, + "10011": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AmandaSharpe.05b950.gmnotes b/objects/AllPlayerCards.15bb07/AmandaSharpe.05b950.gmnotes index 4538f595..ca760bb2 100644 --- a/objects/AllPlayerCards.15bb07/AmandaSharpe.05b950.gmnotes +++ b/objects/AllPlayerCards.15bb07/AmandaSharpe.05b950.gmnotes @@ -9,41 +9,10 @@ "agilityIcons": 2, "cycle": "The Innsmouth Conspiracy", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "07008": 1 - }, - { - "07009": 1 - } - ] - - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "practiced" - ], - "type": [ - "skill" - ], - "level": { - "min": 0, - "max": 3 - } + "07008": 1, + "07009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AminaZidane.4c2a3d.gmnotes b/objects/AllPlayerCards.15bb07/AminaZidane.4c2a3d.gmnotes index 453a1aa3..8b33383d 100644 --- a/objects/AllPlayerCards.15bb07/AminaZidane.4c2a3d.gmnotes +++ b/objects/AllPlayerCards.15bb07/AminaZidane.4c2a3d.gmnotes @@ -9,38 +9,11 @@ "agilityIcons": 3, "cycle": "The Scarlet Keys", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "09012": 1, - "09013": 1 - }, - { - "09014": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "charm" - ], - "level": { - "min": 0, - "max": 4 - } + "09012": 1, + "09013": 1, + "09014": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AshcanPete.5294c3.gmnotes b/objects/AllPlayerCards.15bb07/AshcanPete.5294c3.gmnotes index f07a305c..8bf96a98 100644 --- a/objects/AllPlayerCards.15bb07/AshcanPete.5294c3.gmnotes +++ b/objects/AllPlayerCards.15bb07/AshcanPete.5294c3.gmnotes @@ -9,38 +9,12 @@ "agilityIcons": 3, "cycle": "The Dunwich Legacy", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90047": 1, - "02014": 1 - }, - { - "90048": 1, - "02015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Survivor or Neutral" + "02014": 1, + "02015": 1, + "90047": 1, + "90048": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AshcanPeteParallel.5294c3.gmnotes b/objects/AllPlayerCards.15bb07/AshcanPeteParallel.5294c3.gmnotes index a19bfe78..259d8ea6 100644 --- a/objects/AllPlayerCards.15bb07/AshcanPeteParallel.5294c3.gmnotes +++ b/objects/AllPlayerCards.15bb07/AshcanPeteParallel.5294c3.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 3, "cycle": "On the Road Again", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90047": 1, - "02014": 1 - }, - { - "90048": 1, - "02015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "improvised", - "tactic" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "faction": [ - "guardian" - ], - "error": "You cannot have more than 5 level 0 Guardian cards" + "02014": 1, + "02015": 1, + "90047": 1, + "90048": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AshcanPeteParallelBack.5294c3.gmnotes b/objects/AllPlayerCards.15bb07/AshcanPeteParallelBack.5294c3.gmnotes index 7740aa8c..2c6f9087 100644 --- a/objects/AllPlayerCards.15bb07/AshcanPeteParallelBack.5294c3.gmnotes +++ b/objects/AllPlayerCards.15bb07/AshcanPeteParallelBack.5294c3.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 3, "cycle": "On the Road Again", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90047": 1, - "02014": 1 - }, - { - "90048": 1, - "02015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "improvised", - "tactic" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "faction": [ - "guardian" - ], - "error": "You cannot have more than 5 level 0 Guardian cards" + "02014": 1, + "02015": 1, + "90047": 1, + "90048": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/AshcanPeteParallelFront.5294c3.gmnotes b/objects/AllPlayerCards.15bb07/AshcanPeteParallelFront.5294c3.gmnotes index 80bf37e5..3e5ffff7 100644 --- a/objects/AllPlayerCards.15bb07/AshcanPeteParallelFront.5294c3.gmnotes +++ b/objects/AllPlayerCards.15bb07/AshcanPeteParallelFront.5294c3.gmnotes @@ -9,38 +9,12 @@ "agilityIcons": 3, "cycle": "On the Road Again", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90047": 1, - "02014": 1 - }, - { - "90048": 1, - "02015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Survivor or Neutral" + "02014": 1, + "02015": 1, + "90047": 1, + "90048": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/BobJenkins.419b0c.gmnotes b/objects/AllPlayerCards.15bb07/BobJenkins.419b0c.gmnotes index 07fb06e2..cd529acc 100644 --- a/objects/AllPlayerCards.15bb07/BobJenkins.419b0c.gmnotes +++ b/objects/AllPlayerCards.15bb07/BobJenkins.419b0c.gmnotes @@ -9,56 +9,10 @@ "agilityIcons": 3, "cycle": "Edge of the Earth", "extraToken": "PlayItem", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08017": 1 - }, - { - "08018": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Rogue cards" + "08017": 1, + "08018": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/CalvinWright.b02a1e.gmnotes b/objects/AllPlayerCards.15bb07/CalvinWright.b02a1e.gmnotes index e6092bff..d5a71efd 100644 --- a/objects/AllPlayerCards.15bb07/CalvinWright.b02a1e.gmnotes +++ b/objects/AllPlayerCards.15bb07/CalvinWright.b02a1e.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 0, "cycle": "The Forgotten Age", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "04015": 1 - }, - { - "04016": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "spirit" - ], - "level": { - "min": 0, - "max": 3 - } + "04015": 1, + "04016": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.gmnotes b/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.gmnotes index 62cab3fd..57b6683b 100644 --- a/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.gmnotes +++ b/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.gmnotes @@ -12,72 +12,12 @@ "agilityIcons": 2, "cycle": "The Circle Undone", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { + "signatures": [ + { "05007": 1, - "98011": 1 - }, - { "05008": 1, + "98011": 1, "98012": 1 - } - ] - }, - "deck_options": [ - { - "not": true, - "trait": [ - "weapon" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "special": [ - "heals_horror" - ], - "tag": [ - "hh" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker", - "mystic" - ], - "level": { - "min": 0, - "max": 1 - }, - "limit": 15, - "error": "You cannot have more than 15 level 0-1 Seeker and/or Mystic cards" } ] } diff --git a/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.json b/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.json index d3ad0692..95f6f6b8 100644 --- a/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.json +++ b/objects/AllPlayerCards.15bb07/CarolynFern.b03b12.json @@ -67,7 +67,7 @@ }, "Description": "The Psychologist", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"05001\",\r\n \"alternate_ids\": [\r\n \"98010\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Guardian\",\r\n \"traits\": \"Medic.\",\r\n \"willpowerIcons\": 3,\r\n \"intellectIcons\": 4,\r\n \"combatIcons\": 2,\r\n \"agilityIcons\": 2,\r\n \"cycle\": \"The Circle Undone\",\r\n \"extraToken\": \"None\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"05007\": 1,\r\n \"98011\": 1\r\n },\r\n {\r\n \"05008\": 1,\r\n \"98012\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"not\": true,\r\n \"trait\": [\r\n \"weapon\"\r\n ],\r\n \"level\": {\r\n \"min\": 1,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"guardian\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 3\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"special\": [\r\n \"heals_horror\"\r\n ],\r\n \"tag\": [\r\n \"hh\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"seeker\",\r\n \"mystic\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 1\r\n },\r\n \"limit\": 15,\r\n \"error\": \"You cannot have more than 15 level 0-1 Seeker and/or Mystic cards\"\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"05001\",\n \"alternate_ids\": [\n \"98010\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Guardian\",\n \"traits\": \"Medic.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 4,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"The Circle Undone\",\n \"extraToken\": \"None\",\n \"signatures\": [\n {\n \"05007\": 1,\n \"05008\": 1,\n \"98011\": 1,\n \"98012\": 1\n }\n ]\n}", "GUID": "9900a3", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/CarsonSinclair.dc96d1.gmnotes b/objects/AllPlayerCards.15bb07/CarsonSinclair.dc96d1.gmnotes index 8183a952..dfe89002 100644 --- a/objects/AllPlayerCards.15bb07/CarsonSinclair.dc96d1.gmnotes +++ b/objects/AllPlayerCards.15bb07/CarsonSinclair.dc96d1.gmnotes @@ -9,74 +9,10 @@ "agilityIcons": 2, "cycle": "The Scarlet Keys", "extraToken": "Activate", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "09002": 2 - }, - { - "09003": 1 - } - ], - "choices": 1 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Seeker", - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Mystic", - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Survivor", - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 + "09002": 2, + "09003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/CharlieKane.95fb5e.gmnotes b/objects/AllPlayerCards.15bb07/CharlieKane.95fb5e.gmnotes index cbffa110..33a0d262 100644 --- a/objects/AllPlayerCards.15bb07/CharlieKane.95fb5e.gmnotes +++ b/objects/AllPlayerCards.15bb07/CharlieKane.95fb5e.gmnotes @@ -9,87 +9,10 @@ "agilityIcons": 1, "cycle": "The Scarlet Keys", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "09019": 1 - }, - { - "09020": 1 - } - ], - "choices": 2 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "ally" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Guardian", - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 2 - } - }, - { - "choiceName": "Seeker", - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } - }, - { - "choiceName": "Rogue", - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 2 - } - }, - { - "choiceName": "Mystic", - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 2 - } - }, - { - "choiceName": "Survivor", - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 2 - } + "09019": 1, + "09020": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/CustomModificationsUpgradeSheet.4104bf.json b/objects/AllPlayerCards.15bb07/CustomModificationsUpgradeSheet.4104bf.json index 4c216efc..8f035b9d 100644 --- a/objects/AllPlayerCards.15bb07/CustomModificationsUpgradeSheet.4104bf.json +++ b/objects/AllPlayerCards.15bb07/CustomModificationsUpgradeSheet.4104bf.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09023-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09023-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "4104bf", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.gmnotes b/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.gmnotes index 591b5b01..4e6868e8 100644 --- a/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.gmnotes +++ b/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.gmnotes @@ -12,39 +12,12 @@ "agilityIcons": 2, "cycle": "Core", "extraToken": "Tome", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90002": 1, - "01008": 1 - }, - { - "90003": 1, - "01009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 2 - } + "01008": 1, + "01009": 1, + "90002": 1, + "90003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.json b/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.json index 3299656c..bcbc65fa 100644 --- a/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.json +++ b/objects/AllPlayerCards.15bb07/DaisyWalker.6938eb.json @@ -67,7 +67,7 @@ }, "Description": "The Librarian", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"01002\",\r\n \"alternate_ids\": [\r\n \"01502\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Seeker\",\r\n \"traits\": \"Miskatonic.\",\r\n \"willpowerIcons\": 3,\r\n \"intellectIcons\": 5,\r\n \"combatIcons\": 2,\r\n \"agilityIcons\": 2,\r\n \"cycle\": \"Core\",\r\n \"extraToken\": \"Tome\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"90002\": 1,\r\n \"01008\": 1\r\n },\r\n {\r\n \"90003\": 1,\r\n \"01009\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"seeker\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"mystic\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"01002\",\n \"alternate_ids\": [\n \"01502\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n \"traits\": \"Miskatonic.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 5,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Core\",\n \"extraToken\": \"Tome\",\n \"signatures\": [\n {\n \"01008\": 1,\n \"01009\": 1,\n \"90002\": 1,\n \"90003\": 1\n }\n ]\n}", "GUID": "ac7047", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/DaisyWalkerParallel.282857.gmnotes b/objects/AllPlayerCards.15bb07/DaisyWalkerParallel.282857.gmnotes index 5ada1d01..b7173228 100644 --- a/objects/AllPlayerCards.15bb07/DaisyWalkerParallel.282857.gmnotes +++ b/objects/AllPlayerCards.15bb07/DaisyWalkerParallel.282857.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 2, "cycle": "Read or Die", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90002": 1, - "01008": 1 - }, - { - "90003": 1, - "01009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "trait": [ - "tome" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "guardian", - "mystic" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Guardian and/or Mystic cards" + "01008": 1, + "01009": 1, + "90002": 1, + "90003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DaisyWalkerParallelBack.2f2e0d.gmnotes b/objects/AllPlayerCards.15bb07/DaisyWalkerParallelBack.2f2e0d.gmnotes index 2e0da8d0..f43d825b 100644 --- a/objects/AllPlayerCards.15bb07/DaisyWalkerParallelBack.2f2e0d.gmnotes +++ b/objects/AllPlayerCards.15bb07/DaisyWalkerParallelBack.2f2e0d.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 2, "cycle": "Read or Die", "extraToken": "Tome", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90002": 1, - "01008": 1 - }, - { - "90003": 1, - "01009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "trait": [ - "tome" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "guardian", - "mystic" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Guardian and/or Mystic cards" + "01008": 1, + "01009": 1, + "90002": 1, + "90003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DaisyWalkerParallelFront.e8cafc.gmnotes b/objects/AllPlayerCards.15bb07/DaisyWalkerParallelFront.e8cafc.gmnotes index a90fb89a..91de1c0f 100644 --- a/objects/AllPlayerCards.15bb07/DaisyWalkerParallelFront.e8cafc.gmnotes +++ b/objects/AllPlayerCards.15bb07/DaisyWalkerParallelFront.e8cafc.gmnotes @@ -9,39 +9,12 @@ "agilityIcons": 2, "cycle": "Read or Die", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90002": 1, - "01008": 1 - }, - { - "90003": 1, - "01009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 2 - } + "01008": 1, + "01009": 1, + "90002": 1, + "90003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DamningTestimonyUpgradeSheet.dc4a62.json b/objects/AllPlayerCards.15bb07/DamningTestimonyUpgradeSheet.dc4a62.json index 8d488b33..25a2f24f 100644 --- a/objects/AllPlayerCards.15bb07/DamningTestimonyUpgradeSheet.dc4a62.json +++ b/objects/AllPlayerCards.15bb07/DamningTestimonyUpgradeSheet.dc4a62.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09059-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09059-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "dc4a62", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/DanielaReyes.444830.gmnotes b/objects/AllPlayerCards.15bb07/DanielaReyes.444830.gmnotes index c65984bc..ba48289f 100644 --- a/objects/AllPlayerCards.15bb07/DanielaReyes.444830.gmnotes +++ b/objects/AllPlayerCards.15bb07/DanielaReyes.444830.gmnotes @@ -9,56 +9,10 @@ "agilityIcons": 2, "cycle": "Edge of the Earth", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08002": 1 - }, - { - "08003": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Survivor cards" + "08002": 1, + "08003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DarrellSimmons.5d3d67.gmnotes b/objects/AllPlayerCards.15bb07/DarrellSimmons.5d3d67.gmnotes index 8f6852b0..f3dbc05b 100644 --- a/objects/AllPlayerCards.15bb07/DarrellSimmons.5d3d67.gmnotes +++ b/objects/AllPlayerCards.15bb07/DarrellSimmons.5d3d67.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 3, "cycle": "The Scarlet Keys", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "09016": 1 - }, - { - "09017": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } + "09016": 1, + "09017": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.gmnotes b/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.gmnotes index 4e628000..2280f293 100644 --- a/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.gmnotes +++ b/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.gmnotes @@ -12,39 +12,12 @@ "agilityIcons": 2, "cycle": "The Innsmouth Conspiracy", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "98017": 1, - "07012": 1 - }, - { - "98018": 1, - "07013": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 2 - } + "07012": 1, + "07013": 1, + "98017": 1, + "98018": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.json b/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.json index cd65c201..80f03018 100644 --- a/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.json +++ b/objects/AllPlayerCards.15bb07/DexterDrake.e015f8.json @@ -67,7 +67,7 @@ }, "Description": "The Magician", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"07004\",\r\n \"alternate_ids\": [\r\n \"98016\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Mystic\",\r\n \"traits\": \"Sorcerer. Veteran.\",\r\n \"willpowerIcons\": 5,\r\n \"intellectIcons\": 2,\r\n \"combatIcons\": 3,\r\n \"agilityIcons\": 2,\r\n \"cycle\": \"The Innsmouth Conspiracy\",\r\n \"extraToken\": \"Lightning\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"98017\": 1,\r\n \"07012\": 1\r\n },\r\n {\r\n \"98018\": 1,\r\n \"07013\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"mystic\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"rogue\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"07004\",\n \"alternate_ids\": [\n \"98016\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n \"traits\": \"Sorcerer. Veteran.\",\n \"willpowerIcons\": 5,\n \"intellectIcons\": 2,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"The Innsmouth Conspiracy\",\n \"extraToken\": \"FreeTrigger\",\n \"signatures\": [\n {\n \"07012\": 1,\n \"07013\": 1,\n \"98017\": 1,\n \"98018\": 1\n }\n ]\n}", "GUID": "3925ce", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/DianaStanley.32b091.gmnotes b/objects/AllPlayerCards.15bb07/DianaStanley.32b091.gmnotes index 675d5cfe..a23cea65 100644 --- a/objects/AllPlayerCards.15bb07/DianaStanley.32b091.gmnotes +++ b/objects/AllPlayerCards.15bb07/DianaStanley.32b091.gmnotes @@ -9,40 +9,11 @@ "agilityIcons": 3, "cycle": "The Circle Undone", "extraToken": "Reaction", - "deck_requirements": { - "size": 35, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "05013": 1 - }, - { - "05014": 1 - }, - { - "05015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 2 - } + "05013": 1, + "05014": 1, + "05015": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/EmpiricalHypothesisUpgradeSheet.0c46a7.json b/objects/AllPlayerCards.15bb07/EmpiricalHypothesisUpgradeSheet.0c46a7.json index b6a56bc7..f5c427c6 100644 --- a/objects/AllPlayerCards.15bb07/EmpiricalHypothesisUpgradeSheet.0c46a7.json +++ b/objects/AllPlayerCards.15bb07/EmpiricalHypothesisUpgradeSheet.0c46a7.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09041-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09041-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "0c46a7", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/FatherMateo.eb96e6.gmnotes b/objects/AllPlayerCards.15bb07/FatherMateo.eb96e6.gmnotes index 5c8ff645..fb836f73 100644 --- a/objects/AllPlayerCards.15bb07/FatherMateo.eb96e6.gmnotes +++ b/objects/AllPlayerCards.15bb07/FatherMateo.eb96e6.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 3, "cycle": "The Forgotten Age", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "04013": 1 - }, - { - "04014": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "blessed" - ], - "level": { - "min": 0, - "max": 3 - } + "04013": 1, + "04014": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/FinnEdwards.dd40c0.gmnotes b/objects/AllPlayerCards.15bb07/FinnEdwards.dd40c0.gmnotes index 00026708..d486ac92 100644 --- a/objects/AllPlayerCards.15bb07/FinnEdwards.dd40c0.gmnotes +++ b/objects/AllPlayerCards.15bb07/FinnEdwards.dd40c0.gmnotes @@ -9,60 +9,11 @@ "agilityIcons": 4, "cycle": "The Forgotten Age", "extraToken": "Evade", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "04010": 1 - }, - { - "04011": 1 - }, - { - "04012": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "trait": [ - "illicit" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker", - "survivor" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Seeker and/or Survivor cards" + "04010": 1, + "04011": 1, + "04012": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/FriendsinLowPlacesUpgradeSheet.9fb3b9.json b/objects/AllPlayerCards.15bb07/FriendsinLowPlacesUpgradeSheet.9fb3b9.json index fb5b0513..12a049f6 100644 --- a/objects/AllPlayerCards.15bb07/FriendsinLowPlacesUpgradeSheet.9fb3b9.json +++ b/objects/AllPlayerCards.15bb07/FriendsinLowPlacesUpgradeSheet.9fb3b9.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09060-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09060-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "9fb3b9", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/GloriaGoldberg.571596.gmnotes b/objects/AllPlayerCards.15bb07/GloriaGoldberg.571596.gmnotes index e2f48168..b3333b3a 100644 --- a/objects/AllPlayerCards.15bb07/GloriaGoldberg.571596.gmnotes +++ b/objects/AllPlayerCards.15bb07/GloriaGoldberg.571596.gmnotes @@ -8,73 +8,10 @@ "combatIcons": 2, "agilityIcons": 1, "cycle": "Promo", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "98020": 3 - }, - { - "98021": 3 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Guardian", - "faction_select": [ - "guardian" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Seeker", - "faction_select": [ - "seeker" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Rogue", - "faction_select": [ - "rogue" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 + "98020": 3, + "98021": 3 } ] } diff --git a/objects/AllPlayerCards.15bb07/GrizzledUpgradeSheet.ef8f08.json b/objects/AllPlayerCards.15bb07/GrizzledUpgradeSheet.ef8f08.json index 91489a69..160ddba6 100644 --- a/objects/AllPlayerCards.15bb07/GrizzledUpgradeSheet.ef8f08.json +++ b/objects/AllPlayerCards.15bb07/GrizzledUpgradeSheet.ef8f08.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09101-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09101-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "ef8f08", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/HankSamson.3764cd.gmnotes b/objects/AllPlayerCards.15bb07/HankSamson.3764cd.gmnotes index d2ed54e6..218b796c 100644 --- a/objects/AllPlayerCards.15bb07/HankSamson.3764cd.gmnotes +++ b/objects/AllPlayerCards.15bb07/HankSamson.3764cd.gmnotes @@ -21,39 +21,10 @@ "agilityIcons": 3, "cycle": "The Feast of Hemlock Vale", "extraToken": "None", - "deck_requirements": { - "size": 35, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "10017": 1 - }, - { - "10018": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "insight", - "spirit" - ], - "level": { - "min": 0, - "max": 2 - }, - "limit": 10 + "10017": 1, + "10018": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/HarveyWalters.1fa944.gmnotes b/objects/AllPlayerCards.15bb07/HarveyWalters.1fa944.gmnotes index 7e2c9a0e..cb208d6a 100644 --- a/objects/AllPlayerCards.15bb07/HarveyWalters.1fa944.gmnotes +++ b/objects/AllPlayerCards.15bb07/HarveyWalters.1fa944.gmnotes @@ -9,28 +9,10 @@ "agilityIcons": 2, "cycle": "Investigator Packs", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "60202": 1 - }, - { - "60203": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "60202": 1, + "60203": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/HonedInstinctUpgradeSheet.ba0e34.json b/objects/AllPlayerCards.15bb07/HonedInstinctUpgradeSheet.ba0e34.json index 40003124..1caa09e7 100644 --- a/objects/AllPlayerCards.15bb07/HonedInstinctUpgradeSheet.ba0e34.json +++ b/objects/AllPlayerCards.15bb07/HonedInstinctUpgradeSheet.ba0e34.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09061-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09061-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "ba0e34", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/HuntersArmorUpgradeSheet.d2d01b.json b/objects/AllPlayerCards.15bb07/HuntersArmorUpgradeSheet.d2d01b.json index ce7fc6ab..e909fce9 100644 --- a/objects/AllPlayerCards.15bb07/HuntersArmorUpgradeSheet.d2d01b.json +++ b/objects/AllPlayerCards.15bb07/HuntersArmorUpgradeSheet.d2d01b.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09021-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09021-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "d2d01b", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/HyperphysicalShotcasterUpgradeSheet.a4eec2.json b/objects/AllPlayerCards.15bb07/HyperphysicalShotcasterUpgradeSheet.a4eec2.json index d9aeb475..7ed8bc90 100644 --- a/objects/AllPlayerCards.15bb07/HyperphysicalShotcasterUpgradeSheet.a4eec2.json +++ b/objects/AllPlayerCards.15bb07/HyperphysicalShotcasterUpgradeSheet.a4eec2.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09119-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09119-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "a4eec2", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/JacquelineFine.a2cd75.gmnotes b/objects/AllPlayerCards.15bb07/JacquelineFine.a2cd75.gmnotes index dca14011..344fd172 100644 --- a/objects/AllPlayerCards.15bb07/JacquelineFine.a2cd75.gmnotes +++ b/objects/AllPlayerCards.15bb07/JacquelineFine.a2cd75.gmnotes @@ -9,28 +9,10 @@ "agilityIcons": 2, "cycle": "Investigator Packs", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "60402": 1 - }, - { - "60403": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "60402": 1, + "60403": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.gmnotes b/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.gmnotes index 134e6f67..99d826b5 100644 --- a/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.gmnotes +++ b/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.gmnotes @@ -12,38 +12,14 @@ "agilityIcons": 3, "cycle": "The Dunwich Legacy", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "98002": 1, - "02010": 1 - }, - { - "98003": 1, - "02011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Rogue or Neutral" + "02010": 1, + "02011": 1, + "90085": 1, + "90086": 1, + "98002": 1, + "98003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.json b/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.json index 4a058cf6..c03cd595 100644 --- a/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.json +++ b/objects/AllPlayerCards.15bb07/JennyBarnes.9058d3.json @@ -67,7 +67,7 @@ }, "Description": "The Dilettante", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"02003\",\n \"alternate_ids\": [\n \"98001\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Rogue\",\n \"traits\": \"Drifter.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 3,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Dunwich Legacy\",\r\n \"extraToken\": \"None\"\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"98002\": 1,\n \"02010\": 1\n },\n {\n \"98003\": 1,\n \"02011\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"rogue\",\n \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n \"level\": {\n \"min\": 0,\n \"max\": 0\n },\n \"limit\": 5,\n \"error\": \"You cannot have more than 5 cards that are not Rogue or Neutral\"\n }\n ]\n}\n", + "GMNotes": "{\n \"id\": \"02003\",\n \"alternate_ids\": [\n \"98001\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Rogue\",\n \"traits\": \"Drifter.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 3,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Dunwich Legacy\",\n \"extraToken\": \"None\",\n \"signatures\": [\n {\n \"02010\": 1,\n \"02011\": 1,\n \"90085\": 1,\n \"90086\": 1,\n \"98002\": 1,\n \"98003\": 1\n }\n ]\n}", "GUID": "b954f6", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/JennyBarnesParallel.9058d4.gmnotes b/objects/AllPlayerCards.15bb07/JennyBarnesParallel.9058d4.gmnotes index 466a485a..a9461ec5 100644 --- a/objects/AllPlayerCards.15bb07/JennyBarnesParallel.9058d4.gmnotes +++ b/objects/AllPlayerCards.15bb07/JennyBarnesParallel.9058d4.gmnotes @@ -8,5 +8,15 @@ "combatIcons": 3, "agilityIcons": 3, "cycle": "Pistols and Pearls", - "extraToken": "Reaction" + "extraToken": "Reaction", + "signatures": [ + { + "02010": 1, + "02011": 1, + "90085": 1, + "90086": 1, + "98002": 1, + "98003": 1 + } + ] } diff --git a/objects/AllPlayerCards.15bb07/JennyBarnesParallelBack.9058d5.gmnotes b/objects/AllPlayerCards.15bb07/JennyBarnesParallelBack.9058d5.gmnotes index fe53d018..89fdda05 100644 --- a/objects/AllPlayerCards.15bb07/JennyBarnesParallelBack.9058d5.gmnotes +++ b/objects/AllPlayerCards.15bb07/JennyBarnesParallelBack.9058d5.gmnotes @@ -8,5 +8,15 @@ "combatIcons": 3, "agilityIcons": 3, "cycle": "Pistols and Pearls", - "extraToken": "None" + "extraToken": "None", + "signatures": [ + { + "02010": 1, + "02011": 1, + "90085": 1, + "90086": 1, + "98002": 1, + "98003": 1 + } + ] } diff --git a/objects/AllPlayerCards.15bb07/JennyBarnesParallelFront.9058d6.gmnotes b/objects/AllPlayerCards.15bb07/JennyBarnesParallelFront.9058d6.gmnotes index 8d1d79f5..76705b71 100644 --- a/objects/AllPlayerCards.15bb07/JennyBarnesParallelFront.9058d6.gmnotes +++ b/objects/AllPlayerCards.15bb07/JennyBarnesParallelFront.9058d6.gmnotes @@ -8,5 +8,15 @@ "combatIcons": 3, "agilityIcons": 3, "cycle": "Pistols and Pearls", - "extraToken": "Reaction" + "extraToken": "Reaction", + "signatures": [ + { + "02010": 1, + "02011": 1, + "90085": 1, + "90086": 1, + "98002": 1, + "98003": 1 + } + ] } diff --git a/objects/AllPlayerCards.15bb07/JimCulver.ca079b.gmnotes b/objects/AllPlayerCards.15bb07/JimCulver.ca079b.gmnotes index f553e530..468cd61f 100644 --- a/objects/AllPlayerCards.15bb07/JimCulver.ca079b.gmnotes +++ b/objects/AllPlayerCards.15bb07/JimCulver.ca079b.gmnotes @@ -9,38 +9,12 @@ "agilityIcons": 2, "cycle": "The Dunwich Legacy", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90050": 1, - "02012": 1 - }, - { - "90051": 1, - "02013": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Mystic or Neutral" + "02012": 1, + "02013": 1, + "90050": 1, + "90051": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/JimCulverParallel.72bf31.gmnotes b/objects/AllPlayerCards.15bb07/JimCulverParallel.72bf31.gmnotes index 07b357c5..215a005a 100644 --- a/objects/AllPlayerCards.15bb07/JimCulverParallel.72bf31.gmnotes +++ b/objects/AllPlayerCards.15bb07/JimCulverParallel.72bf31.gmnotes @@ -9,76 +9,12 @@ "agilityIcons": 2, "cycle": "Laid to Rest", "extraToken": "Reaction", - "deck_requirements": { - "size": 39, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90050": 1, - "02012": 1 - }, - { - "90051": 1, - "02013": 1 - }, - { - "90052": 1 - }, - { - "90053": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Survivor cards" - }, - { - "trait": [ - "spell", - "cursed" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "trait": [ - "ally" - ], - "level": { - "min": 0, - "max": 2 - }, - "limit": 9, - "error": "You must have exactly 9 Ally cards" + "02012": 1, + "02013": 1, + "90050": 1, + "90051": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/JimCulverParallelBack.aba863.gmnotes b/objects/AllPlayerCards.15bb07/JimCulverParallelBack.aba863.gmnotes index 63574894..cec1edd6 100644 --- a/objects/AllPlayerCards.15bb07/JimCulverParallelBack.aba863.gmnotes +++ b/objects/AllPlayerCards.15bb07/JimCulverParallelBack.aba863.gmnotes @@ -9,76 +9,12 @@ "agilityIcons": 2, "cycle": "Laid to Rest", "extraToken": "None", - "deck_requirements": { - "size": 39, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90050": 1, - "02012": 1 - }, - { - "90051": 1, - "02013": 1 - }, - { - "90052": 1 - }, - { - "90053": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Survivor cards" - }, - { - "trait": [ - "spell", - "cursed" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "trait": [ - "ally" - ], - "level": { - "min": 0, - "max": 2 - }, - "limit": 9, - "error": "You must have exactly 9 Ally cards" + "02012": 1, + "02013": 1, + "90050": 1, + "90051": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/JimCulverParallelFront.c5fc80.gmnotes b/objects/AllPlayerCards.15bb07/JimCulverParallelFront.c5fc80.gmnotes index 3daecccc..b4b516ec 100644 --- a/objects/AllPlayerCards.15bb07/JimCulverParallelFront.c5fc80.gmnotes +++ b/objects/AllPlayerCards.15bb07/JimCulverParallelFront.c5fc80.gmnotes @@ -9,38 +9,12 @@ "agilityIcons": 2, "cycle": "Laid to Rest", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90050": 1, - "02012": 1 - }, - { - "90051": 1, - "02013": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Mystic or Neutral" + "02012": 1, + "02013": 1, + "90050": 1, + "90051": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/JoeDiamond.6dc626.gmnotes b/objects/AllPlayerCards.15bb07/JoeDiamond.6dc626.gmnotes index 8fc18af2..7524383c 100644 --- a/objects/AllPlayerCards.15bb07/JoeDiamond.6dc626.gmnotes +++ b/objects/AllPlayerCards.15bb07/JoeDiamond.6dc626.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 2, "cycle": "The Circle Undone", "extraToken": "None", - "deck_requirements": { - "size": 40, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "05009": 1 - }, - { - "05010": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 2 - } + "05009": 1, + "05010": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/KateWinthrop.ce2322.gmnotes b/objects/AllPlayerCards.15bb07/KateWinthrop.ce2322.gmnotes index 4008874c..1b15d818 100644 --- a/objects/AllPlayerCards.15bb07/KateWinthrop.ce2322.gmnotes +++ b/objects/AllPlayerCards.15bb07/KateWinthrop.ce2322.gmnotes @@ -9,46 +9,10 @@ "agilityIcons": 4, "cycle": "The Feast of Hemlock Vale", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "10005": 1 - }, - { - "10008": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "science" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "trait": [ - "insight" - ], - "level": { - "min": 0, - "max": 1 - } + "10005": 1, + "10008": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.gmnotes b/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.gmnotes index b30c4b94..7bc3924b 100644 --- a/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.gmnotes +++ b/objects/AllPlayerCards.15bb07/KhakuNarukami.54eaa7.gmnotes @@ -9,55 +9,10 @@ "agilityIcons": 1, "cycle": "The Feast of Hemlock Vale", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "10013": 1 - }, - { - "10014": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "trait": [ - "blessed", - "cursed" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "occult" - ], - "level": { - "min": 0, - "max": 0 - } + "10013": 1, + "10014": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/KymaniJones.9a9830.gmnotes b/objects/AllPlayerCards.15bb07/KymaniJones.9a9830.gmnotes index 5cab582c..44e068ff 100644 --- a/objects/AllPlayerCards.15bb07/KymaniJones.9a9830.gmnotes +++ b/objects/AllPlayerCards.15bb07/KymaniJones.9a9830.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 5, "cycle": "The Scarlet Keys", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "09009": 1 - }, - { - "09010": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral", - "rogue" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "tool" - ], - "level": { - "min": 0, - "max": 4 - } + "09009": 1, + "09010": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/LeoAnderson.126932.gmnotes b/objects/AllPlayerCards.15bb07/LeoAnderson.126932.gmnotes index 0f78629f..6d576ae6 100644 --- a/objects/AllPlayerCards.15bb07/LeoAnderson.126932.gmnotes +++ b/objects/AllPlayerCards.15bb07/LeoAnderson.126932.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 1, "cycle": "The Forgotten Age", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "04006": 1 - }, - { - "04007": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 2 - } + "04006": 1, + "04007": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/LilyChen.cc21e0.gmnotes b/objects/AllPlayerCards.15bb07/LilyChen.cc21e0.gmnotes index 52e20b3c..44edefbd 100644 --- a/objects/AllPlayerCards.15bb07/LilyChen.cc21e0.gmnotes +++ b/objects/AllPlayerCards.15bb07/LilyChen.cc21e0.gmnotes @@ -9,65 +9,13 @@ "agilityIcons": 3, "cycle": "Edge of the Earth", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08011a": 1, - "08012a": 1, - "08013a": 1, - "08014a": 1 - }, - { - "08015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "not": true, - "trait": [ - "firearm" - ] - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Guardian cards" + "08011a": 1, + "08012a": 1, + "08013a": 1, + "08014a": 1, + "08015": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/LivingInkUpgradeSheet.19a05b.json b/objects/AllPlayerCards.15bb07/LivingInkUpgradeSheet.19a05b.json index a612ae05..ca20ddaf 100644 --- a/objects/AllPlayerCards.15bb07/LivingInkUpgradeSheet.19a05b.json +++ b/objects/AllPlayerCards.15bb07/LivingInkUpgradeSheet.19a05b.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09079-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09079-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "19a05b", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/LolaHayes.d37332.gmnotes b/objects/AllPlayerCards.15bb07/LolaHayes.d37332.gmnotes index e8eefc9c..502888ae 100644 --- a/objects/AllPlayerCards.15bb07/LolaHayes.d37332.gmnotes +++ b/objects/AllPlayerCards.15bb07/LolaHayes.d37332.gmnotes @@ -9,41 +9,10 @@ "agilityIcons": 3, "cycle": "The Path to Carcosa", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 35, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03018": 2 - }, - { - "03019": 2 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "guardian", - "seeker", - "rogue", - "mystic" - ], - "level": { - "min": 0, - "max": 3 - }, - "error": "You must have at least 7 cards from 3 different factions" - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "03018": 2, + "03019": 2 } ] } diff --git a/objects/AllPlayerCards.15bb07/LolaHayesTaboo.52956d.gmnotes b/objects/AllPlayerCards.15bb07/LolaHayesTaboo.52956d.gmnotes index 7578f36d..0dcd93d5 100644 --- a/objects/AllPlayerCards.15bb07/LolaHayesTaboo.52956d.gmnotes +++ b/objects/AllPlayerCards.15bb07/LolaHayesTaboo.52956d.gmnotes @@ -9,41 +9,10 @@ "agilityIcons": 3, "cycle": "The Path to Carcosa", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 35, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03018": 2 - }, - { - "03019": 2 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "guardian", - "seeker", - "rogue", - "mystic" - ], - "level": { - "min": 0, - "max": 3 - }, - "error": "You must have at least 7 cards from 3 different factions" - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "03018": 2, + "03019": 2 } ] } diff --git a/objects/AllPlayerCards.15bb07/LukeRobinson.c59b75.gmnotes b/objects/AllPlayerCards.15bb07/LukeRobinson.c59b75.gmnotes index 78a13d2f..788461a3 100644 --- a/objects/AllPlayerCards.15bb07/LukeRobinson.c59b75.gmnotes +++ b/objects/AllPlayerCards.15bb07/LukeRobinson.c59b75.gmnotes @@ -8,37 +8,10 @@ "combatIcons": 2, "agilityIcons": 3, "cycle": "The Dream-Eaters", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "06013": 1 - }, - { - "06014": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "mystic", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } + "06013": 1, + "06014": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MakeshiftTrapUpgradeSheet.64dfce.json b/objects/AllPlayerCards.15bb07/MakeshiftTrapUpgradeSheet.64dfce.json index 777279ce..43a19a15 100644 --- a/objects/AllPlayerCards.15bb07/MakeshiftTrapUpgradeSheet.64dfce.json +++ b/objects/AllPlayerCards.15bb07/MakeshiftTrapUpgradeSheet.64dfce.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09100-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09100-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "64dfce", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/MandyThompson.57d586.gmnotes b/objects/AllPlayerCards.15bb07/MandyThompson.57d586.gmnotes index a8b5a1fe..6edafd6e 100644 --- a/objects/AllPlayerCards.15bb07/MandyThompson.57d586.gmnotes +++ b/objects/AllPlayerCards.15bb07/MandyThompson.57d586.gmnotes @@ -9,74 +9,10 @@ "agilityIcons": 3, "cycle": "The Dream-Eaters", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "06008": 1 - }, - { - "06009": 1 - } - ], - "choices": 1 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Mystic", - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Rogue", - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Survivor", - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 + "06008": 3, + "06009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MandyThompsonTaboo.4f3637.gmnotes b/objects/AllPlayerCards.15bb07/MandyThompsonTaboo.4f3637.gmnotes index e94256cb..b2656a84 100644 --- a/objects/AllPlayerCards.15bb07/MandyThompsonTaboo.4f3637.gmnotes +++ b/objects/AllPlayerCards.15bb07/MandyThompsonTaboo.4f3637.gmnotes @@ -9,74 +9,10 @@ "agilityIcons": 3, "cycle": "The Dream-Eaters", "extraToken": "Reaction", - "deck_requirements": { - "size": 50, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "06008": 3 - }, - { - "06009": 1 - } - ], - "choices": 1 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Mystic", - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Rogue", - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Survivor", - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 + "06008": 3, + "06009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MarieLambeau.11122f.gmnotes b/objects/AllPlayerCards.15bb07/MarieLambeau.11122f.gmnotes index 007bc06d..9d70a129 100644 --- a/objects/AllPlayerCards.15bb07/MarieLambeau.11122f.gmnotes +++ b/objects/AllPlayerCards.15bb07/MarieLambeau.11122f.gmnotes @@ -12,66 +12,10 @@ "agilityIcons": 3, "cycle": "The Circle Undone", "extraToken": "Spell", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "05018": 1 - }, - { - "05019": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "trait": [ - "spell" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "occult" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "seeker", - "survivor" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Seeker and/or Survivor cards" + "05018": 1, + "05019": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MarkHarrigan.01ac1b.gmnotes b/objects/AllPlayerCards.15bb07/MarkHarrigan.01ac1b.gmnotes index 95c26913..32293ab0 100644 --- a/objects/AllPlayerCards.15bb07/MarkHarrigan.01ac1b.gmnotes +++ b/objects/AllPlayerCards.15bb07/MarkHarrigan.01ac1b.gmnotes @@ -9,40 +9,11 @@ "agilityIcons": 3, "cycle": "The Path to Carcosa", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03007": 1 - }, - { - "03008": 1 - }, - { - "03009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "tactic" - ], - "level": { - "min": 0, - "max": 0 - } + "03007": 1, + "03008": 1, + "03009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MinhThiPhan.6c4c58.gmnotes b/objects/AllPlayerCards.15bb07/MinhThiPhan.6c4c58.gmnotes index a67c0449..cf671ace 100644 --- a/objects/AllPlayerCards.15bb07/MinhThiPhan.6c4c58.gmnotes +++ b/objects/AllPlayerCards.15bb07/MinhThiPhan.6c4c58.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 2, "cycle": "The Path to Carcosa", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03010": 1 - }, - { - "03011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 2 - } + "03010": 1, + "03011": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MontereyJack.46b145.gmnotes b/objects/AllPlayerCards.15bb07/MontereyJack.46b145.gmnotes index 4ac0b899..26d76968 100644 --- a/objects/AllPlayerCards.15bb07/MontereyJack.46b145.gmnotes +++ b/objects/AllPlayerCards.15bb07/MontereyJack.46b145.gmnotes @@ -9,58 +9,12 @@ "agilityIcons": 5, "cycle": "Edge of the Earth", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08008": 1, - "90063": 1 - }, - { - "08009": 1, - "90064": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Seeker cards" + "08008": 1, + "08009": 1, + "90063": 1, + "90064": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MontereyJackParallel.46b146.gmnotes b/objects/AllPlayerCards.15bb07/MontereyJackParallel.46b146.gmnotes index 1f98d2bb..7f44ef2b 100644 --- a/objects/AllPlayerCards.15bb07/MontereyJackParallel.46b146.gmnotes +++ b/objects/AllPlayerCards.15bb07/MontereyJackParallel.46b146.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 5, "cycle": "Relics of the Past", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08008": 1, - "90063": 1 - }, - { - "08009": 1, - "90064": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "relic", - "charm" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Seeker cards" + "08008": 1, + "08009": 1, + "90063": 1, + "90064": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MontereyJackParallelBack.46b148.gmnotes b/objects/AllPlayerCards.15bb07/MontereyJackParallelBack.46b148.gmnotes index 53cf33f3..71a1456f 100644 --- a/objects/AllPlayerCards.15bb07/MontereyJackParallelBack.46b148.gmnotes +++ b/objects/AllPlayerCards.15bb07/MontereyJackParallelBack.46b148.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 5, "cycle": "Relics of the Past", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08008": 1, - "90063": 1 - }, - { - "08009": 1, - "90064": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "relic", - "charm" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Seeker cards" + "08008": 1, + "08009": 1, + "90063": 1, + "90064": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/MontereyJackParallelFront.46b147.gmnotes b/objects/AllPlayerCards.15bb07/MontereyJackParallelFront.46b147.gmnotes index 6fd6bec5..1faaf156 100644 --- a/objects/AllPlayerCards.15bb07/MontereyJackParallelFront.46b147.gmnotes +++ b/objects/AllPlayerCards.15bb07/MontereyJackParallelFront.46b147.gmnotes @@ -9,58 +9,12 @@ "agilityIcons": 5, "cycle": "Relics of the Past", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "08008": 1, - "90063": 1 - }, - { - "08009": 1, - "90064": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Seeker cards" + "08008": 1, + "08009": 1, + "90063": 1, + "90064": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/NathanielCho.65588a.gmnotes b/objects/AllPlayerCards.15bb07/NathanielCho.65588a.gmnotes index 8a37918b..596e09e9 100644 --- a/objects/AllPlayerCards.15bb07/NathanielCho.65588a.gmnotes +++ b/objects/AllPlayerCards.15bb07/NathanielCho.65588a.gmnotes @@ -9,28 +9,10 @@ "agilityIcons": 2, "cycle": "Investigator Packs", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "60102": 1 - }, - { - "60103": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "60102": 1, + "60103": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.gmnotes b/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.gmnotes index 7dd6c471..03985d01 100644 --- a/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.gmnotes +++ b/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.gmnotes @@ -11,58 +11,12 @@ "combatIcons": 2, "agilityIcons": 1, "cycle": "Edge of the Earth", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "98008": 1, - "08005": 1 - }, - { - "98009": 1, - "08006": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 0 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 1, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 level 0 Mystic cards" + "08005": 1, + "08006": 1, + "98008": 1, + "98009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.json b/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.json index 2c8321c0..87dac401 100644 --- a/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.json +++ b/objects/AllPlayerCards.15bb07/NormanWithers.e0a155.json @@ -67,7 +67,7 @@ }, "Description": "The Astronomer", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"08004\",\r\n \"alternate_ids\": [\r\n \"98007\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Seeker\",\r\n \"traits\": \"Miskatonic.\",\r\n \"willpowerIcons\": 4,\r\n \"intellectIcons\": 5,\r\n \"combatIcons\": 2,\r\n \"agilityIcons\": 1,\r\n \"cycle\": \"Edge of the Earth\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"98008\": 1,\r\n \"08005\": 1\r\n },\r\n {\r\n \"98009\": 1,\r\n \"08006\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"seeker\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 0\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"mystic\"\r\n ],\r\n \"level\": {\r\n \"min\": 1,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"mystic\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 0\r\n },\r\n \"limit\": 5,\r\n \"error\": \"You cannot have more than 5 level 0 Mystic cards\"\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"08004\",\n \"alternate_ids\": [\n \"98007\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n \"traits\": \"Miskatonic.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\": 5,\n \"combatIcons\": 2,\n \"agilityIcons\": 1,\n \"cycle\": \"Edge of the Earth\",\n \"signatures\": [\n {\n \"08005\": 1,\n \"08006\": 1,\n \"98008\": 1,\n \"98009\": 1\n }\n ]\n}", "GUID": "49634c", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/PatriceHathaway.a7b79f.gmnotes b/objects/AllPlayerCards.15bb07/PatriceHathaway.a7b79f.gmnotes index 4894e52b..df7e26a2 100644 --- a/objects/AllPlayerCards.15bb07/PatriceHathaway.a7b79f.gmnotes +++ b/objects/AllPlayerCards.15bb07/PatriceHathaway.a7b79f.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 2, "cycle": "The Dream-Eaters", "extraToken": "None", - "deck_requirements": { - "size": 42, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "06016": 1 - }, - { - "06017": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 2 - } + "06016": 1, + "06017": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/PocketMultiToolUpgradeSheet.d706e7.json b/objects/AllPlayerCards.15bb07/PocketMultiToolUpgradeSheet.d706e7.json index a6a3ea19..ac62316b 100644 --- a/objects/AllPlayerCards.15bb07/PocketMultiToolUpgradeSheet.d706e7.json +++ b/objects/AllPlayerCards.15bb07/PocketMultiToolUpgradeSheet.d706e7.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09099-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09099-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "d706e7", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheet.0d9481.json b/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheet.0d9481.json index 595de1cc..4cf6185b 100644 --- a/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheet.0d9481.json +++ b/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheet.0d9481.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09081-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09081-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "0d9481", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheetTaboo.ebce85.json b/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheetTaboo.ebce85.json index 9d73762b..13edf8da 100644 --- a/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheetTaboo.ebce85.json +++ b/objects/AllPlayerCards.15bb07/PowerWordUpgradeSheetTaboo.ebce85.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09081-t-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09081-t-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "ebce85", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/PrestonFairmont.5e6298.gmnotes b/objects/AllPlayerCards.15bb07/PrestonFairmont.5e6298.gmnotes index 51057192..08f6970b 100644 --- a/objects/AllPlayerCards.15bb07/PrestonFairmont.5e6298.gmnotes +++ b/objects/AllPlayerCards.15bb07/PrestonFairmont.5e6298.gmnotes @@ -9,43 +9,10 @@ "agilityIcons": 1, "cycle": "The Circle Undone", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "05011": 1 - }, - { - "05012": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "not": true, - "trait": [ - "illicit" - ] - }, - { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 2 - } + "05011": 1, + "05012": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RexMurphy.4271cb.gmnotes b/objects/AllPlayerCards.15bb07/RexMurphy.4271cb.gmnotes index ae894dd0..f919c349 100644 --- a/objects/AllPlayerCards.15bb07/RexMurphy.4271cb.gmnotes +++ b/objects/AllPlayerCards.15bb07/RexMurphy.4271cb.gmnotes @@ -9,42 +9,12 @@ "agilityIcons": 3, "cycle": "The Dunwich Legacy", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "02008": 1 - }, - { - "02009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "not": true, - "trait": [ - "fortune" - ] - }, - { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Seeker or Neutral" + "02008": 1, + "02009": 1, + "90079": 1, + "90080": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RexMurphyParallel.0a5492.gmnotes b/objects/AllPlayerCards.15bb07/RexMurphyParallel.0a5492.gmnotes index 368f9fe3..4c71b433 100644 --- a/objects/AllPlayerCards.15bb07/RexMurphyParallel.0a5492.gmnotes +++ b/objects/AllPlayerCards.15bb07/RexMurphyParallel.0a5492.gmnotes @@ -8,5 +8,13 @@ "combatIcons": 2, "agilityIcons": 3, "cycle": "Hunting for Answers", - "extraToken": "None" + "extraToken": "None", + "signatures": [ + { + "02008": 1, + "02009": 1, + "90079": 1, + "90080": 1 + } + ] } diff --git a/objects/AllPlayerCards.15bb07/RexMurphyParallelBack.0a5493.gmnotes b/objects/AllPlayerCards.15bb07/RexMurphyParallelBack.0a5493.gmnotes index 83d794f2..dc15bc9e 100644 --- a/objects/AllPlayerCards.15bb07/RexMurphyParallelBack.0a5493.gmnotes +++ b/objects/AllPlayerCards.15bb07/RexMurphyParallelBack.0a5493.gmnotes @@ -8,5 +8,13 @@ "combatIcons": 2, "agilityIcons": 3, "cycle": "Hunting for Answers", - "extraToken": "Reaction" + "extraToken": "Reaction", + "signatures": [ + { + "02008": 1, + "02009": 1, + "90079": 1, + "90080": 1 + } + ] } diff --git a/objects/AllPlayerCards.15bb07/RexMurphyParallelFront.0a5494.gmnotes b/objects/AllPlayerCards.15bb07/RexMurphyParallelFront.0a5494.gmnotes index 5169779a..64d82857 100644 --- a/objects/AllPlayerCards.15bb07/RexMurphyParallelFront.0a5494.gmnotes +++ b/objects/AllPlayerCards.15bb07/RexMurphyParallelFront.0a5494.gmnotes @@ -8,5 +8,13 @@ "combatIcons": 2, "agilityIcons": 3, "cycle": "Hunting for Answers", - "extraToken": "None" + "extraToken": "None", + "signatures": [ + { + "02008": 1, + "02009": 1, + "90079": 1, + "90080": 1 + } + ] } diff --git a/objects/AllPlayerCards.15bb07/RexMurphyTaboo.0a5491.gmnotes b/objects/AllPlayerCards.15bb07/RexMurphyTaboo.0a5491.gmnotes index 01f870d5..b102ee45 100644 --- a/objects/AllPlayerCards.15bb07/RexMurphyTaboo.0a5491.gmnotes +++ b/objects/AllPlayerCards.15bb07/RexMurphyTaboo.0a5491.gmnotes @@ -9,42 +9,12 @@ "agilityIcons": 3, "cycle": "The Dunwich Legacy", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "02008": 1 - }, - { - "02009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "not": true, - "trait": [ - "fortune" - ] - }, - { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Seeker or Neutral" + "02008": 1, + "02009": 1, + "90079": 1, + "90080": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RitaYoung.bb8296.gmnotes b/objects/AllPlayerCards.15bb07/RitaYoung.bb8296.gmnotes index ec9d8f09..d37aa2db 100644 --- a/objects/AllPlayerCards.15bb07/RitaYoung.bb8296.gmnotes +++ b/objects/AllPlayerCards.15bb07/RitaYoung.bb8296.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 5, "cycle": "The Circle Undone", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "05016": 1 - }, - { - "05017": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "trait": [ - "trick" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "05016": 1, + "05017": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.gmnotes b/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.gmnotes index d318fdaa..7a3be768 100644 --- a/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.gmnotes +++ b/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.gmnotes @@ -13,41 +13,19 @@ "agilityIcons": 2, "cycle": "Core", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90030": 1, - "98005": 1, - "01006": 1 - }, - { - "90031": 1, - "98006": 1, - "01007": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } + "01006": 1, + "01007": 1, + "90031": 1, + "90025": 1, + "90026": 1, + "90027": 1, + "90028": 1, + "90029": 1, + "90030": 1, + "98005": 1, + "98006": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.json b/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.json index 0d450428..e3b4455d 100644 --- a/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.json +++ b/objects/AllPlayerCards.15bb07/RolandBanks.9e9e98.json @@ -67,7 +67,7 @@ }, "Description": "The Fed", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"01001\",\r\n \"alternate_ids\": [\r\n \"98004\",\r\n \"01501\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Guardian\",\r\n \"traits\": \"Agency. Detective.\",\r\n \"willpowerIcons\": 3,\r\n \"intellectIcons\": 3,\r\n \"combatIcons\": 4,\r\n \"agilityIcons\": 2,\r\n \"cycle\": \"Core\",\r\n \"extraToken\": \"Reaction\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"90030\": 1,\r\n \"98005\": 1,\r\n \"01006\": 1\r\n },\r\n {\r\n \"90031\": 1,\r\n \"98006\": 1,\r\n \"01007\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"guardian\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"seeker\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"01001\",\n \"alternate_ids\": [\n \"98004\",\n \"01501\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Guardian\",\n \"traits\": \"Agency. Detective.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 3,\n \"combatIcons\": 4,\n \"agilityIcons\": 2,\n \"cycle\": \"Core\",\n \"extraToken\": \"Reaction\",\n \"signatures\": [\n {\n \"01006\": 1,\n \"01007\": 1,\n \"90031\": 1,\n \"90025\": 1,\n \"90026\": 1,\n \"90027\": 1,\n \"90028\": 1,\n \"90029\": 1,\n \"90030\": 1,\n \"98005\": 1,\n \"98006\": 1\n }\n ]\n}", "GUID": "a684e0", "Grid": true, "GridProjection": false, @@ -129,7 +129,7 @@ }, "Description": "The Fed", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"01001\",\r\n \"alternate_ids\": [\r\n \"98004\",\r\n \"01501\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Guardian\",\r\n \"traits\": \"Agency. Detective.\",\r\n \"willpowerIcons\": 3,\r\n \"intellectIcons\": 3,\r\n \"combatIcons\": 4,\r\n \"agilityIcons\": 2,\r\n \"cycle\": \"Core\",\r\n \"extraToken\": \"Reaction\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"90030\": 1,\r\n \"98005\": 1,\r\n \"01006\": 1\r\n },\r\n {\r\n \"90031\": 1,\r\n \"98006\": 1,\r\n \"01007\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"guardian\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"seeker\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"01001\",\n \"alternate_ids\": [\n \"98004\",\n \"01501\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Guardian\",\n \"traits\": \"Agency. Detective.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 3,\n \"combatIcons\": 4,\n \"agilityIcons\": 2,\n \"cycle\": \"Core\",\n \"extraToken\": \"Reaction\",\n \"signatures\": [\n {\n \"01006\": 1,\n \"01007\": 1,\n \"90031\": 1,\n \"90025\": 1,\n \"90026\": 1,\n \"90027\": 1,\n \"90028\": 1,\n \"90029\": 1,\n \"90030\": 1,\n \"98005\": 1,\n \"98006\": 1\n }\n ]\n}", "GUID": "e46857", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/RolandBanksParallel.502768.gmnotes b/objects/AllPlayerCards.15bb07/RolandBanksParallel.502768.gmnotes index f9d0f2f8..c4bbdec1 100644 --- a/objects/AllPlayerCards.15bb07/RolandBanksParallel.502768.gmnotes +++ b/objects/AllPlayerCards.15bb07/RolandBanksParallel.502768.gmnotes @@ -9,57 +9,19 @@ "agilityIcons": 2, "cycle": "By the Book", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90030": 1, - "98005": 1, - "01006": 1 - }, - { - "90031": 1, - "98006": 1, - "01007": 1 - }, - { - "90025": 1, - "90026": 1, - "90027": 1, - "90028": 1, - "90029": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "insight", - "tactic" - ], - "level": { - "min": 0, - "max": 3 - } + "01006": 1, + "01007": 1, + "90031": 1, + "90025": 1, + "90026": 1, + "90027": 1, + "90028": 1, + "90029": 1, + "90030": 1, + "98005": 1, + "98006": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RolandBanksParallelBack.560cef.gmnotes b/objects/AllPlayerCards.15bb07/RolandBanksParallelBack.560cef.gmnotes index 6db8a32f..ca820e52 100644 --- a/objects/AllPlayerCards.15bb07/RolandBanksParallelBack.560cef.gmnotes +++ b/objects/AllPlayerCards.15bb07/RolandBanksParallelBack.560cef.gmnotes @@ -9,50 +9,19 @@ "agilityIcons": 2, "cycle": "By the Book", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90030": 1, - "98005": 1, - "01006": 1 - }, - { - "90031": 1, - "98006": 1, - "01007": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "insight", - "tactic" - ], - "level": { - "min": 0, - "max": 3 - } + "01006": 1, + "01007": 1, + "90031": 1, + "90025": 1, + "90026": 1, + "90027": 1, + "90028": 1, + "90029": 1, + "90030": 1, + "98005": 1, + "98006": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RolandBanksParallelFront.f7361e.gmnotes b/objects/AllPlayerCards.15bb07/RolandBanksParallelFront.f7361e.gmnotes index 74342654..59639ceb 100644 --- a/objects/AllPlayerCards.15bb07/RolandBanksParallelFront.f7361e.gmnotes +++ b/objects/AllPlayerCards.15bb07/RolandBanksParallelFront.f7361e.gmnotes @@ -9,48 +9,19 @@ "agilityIcons": 2, "cycle": "By the Book", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90030": 1, - "98005": 1, - "01006": 1 - }, - { - "90031": 1, - "98006": 1, - "01007": 1 - }, - { - "90025": 1, - "90026": 1, - "90027": 1, - "90028": 1, - "90029": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } + "01006": 1, + "01007": 1, + "90031": 1, + "90025": 1, + "90026": 1, + "90027": 1, + "90028": 1, + "90029": 1, + "90030": 1, + "98005": 1, + "98006": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheet.be427d.json b/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheet.be427d.json index d134d859..8fa4f90b 100644 --- a/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheet.be427d.json +++ b/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheet.be427d.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09022-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09022-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "be427d", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheetTaboo.4d729b.json b/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheetTaboo.4d729b.json index 5744c706..8731573e 100644 --- a/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheetTaboo.4d729b.json +++ b/objects/AllPlayerCards.15bb07/RunicAxeUpgradeSheetTaboo.4d729b.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09022-t-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09022-t-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "4d729b", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/SefinaRousseau.342311.gmnotes b/objects/AllPlayerCards.15bb07/SefinaRousseau.342311.gmnotes index 06846488..daf8445a 100644 --- a/objects/AllPlayerCards.15bb07/SefinaRousseau.342311.gmnotes +++ b/objects/AllPlayerCards.15bb07/SefinaRousseau.342311.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 4, "cycle": "The Path to Carcosa", "extraToken": "None", - "deck_requirements": { - "size": 33, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03012": 3 - }, - { - "03013": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 2 - } + "03012": 3, + "03013": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.gmnotes b/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.gmnotes index 0734547c..ee029b03 100644 --- a/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.gmnotes +++ b/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.gmnotes @@ -12,40 +12,13 @@ "agilityIcons": 4, "cycle": "The Innsmouth Conspiracy", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "98014": 1, - "07014": 1, - "07015": 1 - }, - { - "98015": 1, - "07016": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "innate" - ], - "level": { - "min": 0, - "max": 2 - } + "07014": 1, + "07015": 1, + "07016": 1, + "98014": 1, + "98015": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.json b/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.json index 87a6250b..883311e5 100644 --- a/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.json +++ b/objects/AllPlayerCards.15bb07/SilasMarsh.3f92cf.json @@ -67,7 +67,7 @@ }, "Description": "The Sailor", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"07005\",\r\n \"alternate_ids\": [\r\n \"98013\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Survivor\",\r\n \"traits\": \"Drifter.\",\r\n \"willpowerIcons\": 2,\r\n \"intellectIcons\": 2,\r\n \"combatIcons\": 4,\r\n \"agilityIcons\": 4,\r\n \"cycle\": \"The Innsmouth Conspiracy\",\r\n \"extraToken\": \"Reaction\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"98014\": 1,\r\n \"07014\": 1,\r\n \"07015\": 1\r\n },\r\n {\r\n \"98015\": 1,\r\n \"07016\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"survivor\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"trait\": [\r\n \"innate\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"07005\",\n \"alternate_ids\": [\n \"98013\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n \"traits\": \"Drifter.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\": 2,\n \"combatIcons\": 4,\n \"agilityIcons\": 4,\n \"cycle\": \"The Innsmouth Conspiracy\",\n \"extraToken\": \"Reaction\",\n \"signatures\": [\n {\n \"07014\": 1,\n \"07015\": 1,\n \"07016\": 1,\n \"98014\": 1,\n \"98015\": 1\n }\n ]\n}\n", "GUID": "cd3308", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/SisterMary.617aeb.gmnotes b/objects/AllPlayerCards.15bb07/SisterMary.617aeb.gmnotes index b993c4bb..391c6c89 100644 --- a/objects/AllPlayerCards.15bb07/SisterMary.617aeb.gmnotes +++ b/objects/AllPlayerCards.15bb07/SisterMary.617aeb.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 3, "cycle": "The Innsmouth Conspiracy", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "07006": 1 - }, - { - "07007": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 2 - } + "07006": 1, + "07007": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.gmnotes b/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.gmnotes index 09f97ec6..0f66e1b5 100644 --- a/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.gmnotes +++ b/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.gmnotes @@ -12,39 +12,12 @@ "agilityIcons": 4, "cycle": "Core", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90009": 1, - "01010": 1 - }, - { - "90010": 1, - "01011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 2 - } + "01010": 1, + "01011": 1, + "90010": 1, + "90009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.json b/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.json index 946736fd..d3c963e9 100644 --- a/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.json +++ b/objects/AllPlayerCards.15bb07/SkidsOToole.9015b4.json @@ -67,7 +67,7 @@ }, "Description": "The Ex-Con", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"01003\",\r\n \"alternate_ids\": [\r\n \"01503\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Rogue\",\r\n \"traits\": \"Criminal.\",\r\n \"willpowerIcons\": 2,\r\n \"intellectIcons\": 3,\r\n \"combatIcons\": 3,\r\n \"agilityIcons\": 4,\r\n \"cycle\": \"Core\",\r\n \"extraToken\": \"Lightning\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"90009\": 1,\r\n \"01010\": 1\r\n },\r\n {\r\n \"90010\": 1,\r\n \"01011\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"rogue\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"guardian\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"01003\",\n \"alternate_ids\": [\n \"01503\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Rogue\",\n \"traits\": \"Criminal.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\": 3,\n \"combatIcons\": 3,\n \"agilityIcons\": 4,\n \"cycle\": \"Core\",\n \"extraToken\": \"FreeTrigger\",\n \"signatures\": [\n {\n \"01010\": 1,\n \"01011\": 1,\n \"90010\": 1,\n \"90009\": 1\n }\n ]\n}", "GUID": "a41f81", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/SkidsOTooleParallel.22ebb2.gmnotes b/objects/AllPlayerCards.15bb07/SkidsOTooleParallel.22ebb2.gmnotes index ba5c5fb4..5b87b6f7 100644 --- a/objects/AllPlayerCards.15bb07/SkidsOTooleParallel.22ebb2.gmnotes +++ b/objects/AllPlayerCards.15bb07/SkidsOTooleParallel.22ebb2.gmnotes @@ -9,40 +9,12 @@ "agilityIcons": 4, "cycle": "All or Nothing", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 25, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90009": 1, - "01010": 1 - }, - { - "90010": 1, - "01011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "gambit", - "fortune" - ], - "level": { - "min": 0, - "max": 3 - } + "01010": 1, + "01011": 1, + "90010": 1, + "90009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/SkidsOTooleParallelBack.a03077.gmnotes b/objects/AllPlayerCards.15bb07/SkidsOTooleParallelBack.a03077.gmnotes index fab1da7a..5182be12 100644 --- a/objects/AllPlayerCards.15bb07/SkidsOTooleParallelBack.a03077.gmnotes +++ b/objects/AllPlayerCards.15bb07/SkidsOTooleParallelBack.a03077.gmnotes @@ -9,40 +9,12 @@ "agilityIcons": 4, "cycle": "All or Nothing", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 25, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90009": 1, - "01010": 1 - }, - { - "90010": 1, - "01011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "gambit", - "fortune" - ], - "level": { - "min": 0, - "max": 3 - } + "01010": 1, + "01011": 1, + "90010": 1, + "90009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/SkidsOTooleParallelFront.8116a6.gmnotes b/objects/AllPlayerCards.15bb07/SkidsOTooleParallelFront.8116a6.gmnotes index cc7bfe0b..2ba58d2d 100644 --- a/objects/AllPlayerCards.15bb07/SkidsOTooleParallelFront.8116a6.gmnotes +++ b/objects/AllPlayerCards.15bb07/SkidsOTooleParallelFront.8116a6.gmnotes @@ -9,39 +9,12 @@ "agilityIcons": 4, "cycle": "All or Nothing", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90009": 1, - "01010": 1 - }, - { - "90010": 1, - "01011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 2 - } + "01010": 1, + "01011": 1, + "90010": 1, + "90009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/StellaClark.00e18e.gmnotes b/objects/AllPlayerCards.15bb07/StellaClark.00e18e.gmnotes index 10c7070d..54a5e010 100644 --- a/objects/AllPlayerCards.15bb07/StellaClark.00e18e.gmnotes +++ b/objects/AllPlayerCards.15bb07/StellaClark.00e18e.gmnotes @@ -9,28 +9,10 @@ "agilityIcons": 4, "cycle": "Investigator Packs", "extraToken": "Survivor", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "60502": 3 - }, - { - "60503": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "60502": 3, + "60503": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/Subject5U-21.758b0a.gmnotes b/objects/AllPlayerCards.15bb07/Subject5U-21.758b0a.gmnotes index e78f3dfc..a6a299e1 100644 --- a/objects/AllPlayerCards.15bb07/Subject5U-21.758b0a.gmnotes +++ b/objects/AllPlayerCards.15bb07/Subject5U-21.758b0a.gmnotes @@ -9,53 +9,11 @@ "agilityIcons": 1, "cycle": "The Blob That Ate Everything ELSE", "extraToken": "FreeTrigger", - "deck_requirements": { - "size": 50, - "randomBasicWeaknessCount": 2, - "signatures": [ - { - "89002": 1 - }, - { - "89003": 3 - }, - { - "89004": 3 - } - ] - }, - "deck_options": [ + "signatures": [ { - "not": true, - "permanent": true, - "level": { - "min": 0, - "max": 5 - }, - "error": "No permanents except story and signature permanents" - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian", - "seeker", - "rogue", - "mystic", - "survivor" - ], - "level": { - "min": 0, - "max": 0 - }, - "error": "You must have at least 7 cards from each class" + "89002": 1, + "89003": 3, + "89004": 3 } ] } diff --git a/objects/AllPlayerCards.15bb07/SummonedServitorUpgradeSheet.5397a6.json b/objects/AllPlayerCards.15bb07/SummonedServitorUpgradeSheet.5397a6.json index a5176e38..0fccb6a2 100644 --- a/objects/AllPlayerCards.15bb07/SummonedServitorUpgradeSheet.5397a6.json +++ b/objects/AllPlayerCards.15bb07/SummonedServitorUpgradeSheet.5397a6.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09080-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09080-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "5397a6", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/TheRavenQuillUpgradeSheet.23b96a.json b/objects/AllPlayerCards.15bb07/TheRavenQuillUpgradeSheet.23b96a.json index 57986348..af3d5750 100644 --- a/objects/AllPlayerCards.15bb07/TheRavenQuillUpgradeSheet.23b96a.json +++ b/objects/AllPlayerCards.15bb07/TheRavenQuillUpgradeSheet.23b96a.json @@ -24,7 +24,7 @@ }, "Description": "", "DragSelectable": true, - "GMNotes": "{\n \"id\": \"09042-c\",\n \"type\": \"UpgradeSheet\"\n}", + "GMNotes": "{\n \"id\": \"09042-c\",\n \"type\": \"UpgradeSheet\",\n \"cycle\": \"The Scarlet Keys\"\n}", "GUID": "23b96a", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/TommyMuldoon.e637cd.gmnotes b/objects/AllPlayerCards.15bb07/TommyMuldoon.e637cd.gmnotes index a28edb0f..a1973ed3 100644 --- a/objects/AllPlayerCards.15bb07/TommyMuldoon.e637cd.gmnotes +++ b/objects/AllPlayerCards.15bb07/TommyMuldoon.e637cd.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 2, "cycle": "The Dream-Eaters", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "06006": 1 - }, - { - "06007": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 2 - } + "06006": 1, + "06007": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/TonyMorgan.53a412.gmnotes b/objects/AllPlayerCards.15bb07/TonyMorgan.53a412.gmnotes index 7c77dc8f..cf63b924 100644 --- a/objects/AllPlayerCards.15bb07/TonyMorgan.53a412.gmnotes +++ b/objects/AllPlayerCards.15bb07/TonyMorgan.53a412.gmnotes @@ -9,77 +9,11 @@ "agilityIcons": 2, "cycle": "The Dream-Eaters", "extraToken": "Fight/Engage", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "06010": 1 - }, - { - "06011": 2 - }, - { - "06012": 1 - } - ], - "choices": 1 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Guardian", - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Seeker", - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 - }, - { - "choiceName": "Survivor", - "faction": [ - "survivor" - ], - "level": { - "min": 0, - "max": 1 - }, - "type": [ - "event", - "skill" - ], - "limit": 10 + "06010": 1, + "06011": 2, + "06012": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/TrishScarborough.333fe7.gmnotes b/objects/AllPlayerCards.15bb07/TrishScarborough.333fe7.gmnotes index 92ad18ce..c2b53e4f 100644 --- a/objects/AllPlayerCards.15bb07/TrishScarborough.333fe7.gmnotes +++ b/objects/AllPlayerCards.15bb07/TrishScarborough.333fe7.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 4, "cycle": "The Innsmouth Conspiracy", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "07010": 1 - }, - { - "07011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } + "07010": 1, + "07011": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.gmnotes b/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.gmnotes index fbf47170..5996e747 100644 --- a/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.gmnotes +++ b/objects/AllPlayerCards.15bb07/TrishScarboroughTaboo.2ce76d.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 4, "cycle": "The Innsmouth Conspiracy", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "07010": 1 - }, - { - "07011": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 2 - } + "07010": 1, + "07011": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/UrsulaDowns.07c37d.gmnotes b/objects/AllPlayerCards.15bb07/UrsulaDowns.07c37d.gmnotes index 4dccdca7..1d17dbda 100644 --- a/objects/AllPlayerCards.15bb07/UrsulaDowns.07c37d.gmnotes +++ b/objects/AllPlayerCards.15bb07/UrsulaDowns.07c37d.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 4, "cycle": "The Forgotten Age", "extraToken": "Investigate", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "04008": 1 - }, - { - "04009": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "relic" - ], - "level": { - "min": 0, - "max": 4 - } + "04008": 1, + "04009": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/VincentLee.c431f3.gmnotes b/objects/AllPlayerCards.15bb07/VincentLee.c431f3.gmnotes index 406d4c07..e35fafec 100644 --- a/objects/AllPlayerCards.15bb07/VincentLee.c431f3.gmnotes +++ b/objects/AllPlayerCards.15bb07/VincentLee.c431f3.gmnotes @@ -9,63 +9,11 @@ "agilityIcons": 1, "cycle": "The Scarlet Keys", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "09005": 1 - }, - { - "09006": 1 - }, - { - "09007": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "seeker" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "special": [ - "heals_damage" - ], - "tag": [ - "hd" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian", - "survivor" - ], - "level": { - "min": 0, - "max": 1 - }, - "limit": 15, - "error": "You cannot have more than 15 level 0-1 Guardian and/or Survivor cards" + "09005": 1, + "09006": 4, + "09007": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.gmnotes b/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.gmnotes index fe786690..f6edeec2 100644 --- a/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.gmnotes +++ b/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.gmnotes @@ -12,39 +12,13 @@ "agilityIcons": 4, "cycle": "Core", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90039": 1, - "01014": 1 - }, - { - "90040": 1, - "01015": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 2 - } + "01014": 1, + "01015": 1, + "90038": 1, + "90039": 1, + "90040": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.json b/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.json index 0dba38a8..b7570f1c 100644 --- a/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.json +++ b/objects/AllPlayerCards.15bb07/WendyAdams.fc1d17.json @@ -67,7 +67,7 @@ }, "Description": "The Urchin", "DragSelectable": true, - "GMNotes": "{\r\n \"id\": \"01005\",\r\n \"alternate_ids\": [\r\n \"01505\"\r\n ],\r\n \"type\": \"Investigator\",\r\n \"class\": \"Survivor\",\r\n \"traits\": \"Drifter.\",\r\n \"willpowerIcons\": 4,\r\n \"intellectIcons\": 3,\r\n \"combatIcons\": 1,\r\n \"agilityIcons\": 4,\r\n \"cycle\": \"Core\",\r\n \"extraToken\": \"None\",\r\n \"deck_requirements\": {\r\n \"size\": 30,\r\n \"randomBasicWeaknessCount\": 1,\r\n \"signatures\": [\r\n {\r\n \"90039\": 1,\r\n \"01014\": 1\r\n },\r\n {\r\n \"90040\": 1,\r\n \"01015\": 1\r\n }\r\n ]\r\n },\r\n \"deck_options\": [\r\n {\r\n \"faction\": [\r\n \"survivor\",\r\n \"neutral\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 5\r\n }\r\n },\r\n {\r\n \"faction\": [\r\n \"rogue\"\r\n ],\r\n \"level\": {\r\n \"min\": 0,\r\n \"max\": 2\r\n }\r\n }\r\n ]\r\n}\r\n", + "GMNotes": "{\n \"id\": \"01005\",\n \"alternate_ids\": [\n \"01505\"\n ],\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n \"traits\": \"Drifter.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\": 3,\n \"combatIcons\": 1,\n \"agilityIcons\": 4,\n \"cycle\": \"Core\",\n \"extraToken\": \"None\",\n \"signatures\": [\n {\n \"01014\": 1,\n \"01015\": 1,\n \"90038\": 1,\n \"90039\": 1,\n \"90040\": 1\n }\n ]\n}", "GUID": "11bcb3", "Grid": true, "GridProjection": false, diff --git a/objects/AllPlayerCards.15bb07/WendyAdamsParallel.fd91ea.gmnotes b/objects/AllPlayerCards.15bb07/WendyAdamsParallel.fd91ea.gmnotes index 2971f13c..8da70b2d 100644 --- a/objects/AllPlayerCards.15bb07/WendyAdamsParallel.fd91ea.gmnotes +++ b/objects/AllPlayerCards.15bb07/WendyAdamsParallel.fd91ea.gmnotes @@ -9,66 +9,13 @@ "agilityIcons": 4, "cycle": "Red Tide Rising", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90039": 1, - "01014": 1 - }, - { - "90040": 1, - "01015": 1 - }, - { - "90038": 1 - } - ], - "choices": 1 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Blessed", - "trait": [ - "blessed" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Cursed", - "trait": [ - "cursed" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Both", - "trait": [ - "blessed", - "cursed" - ], - "level": { - "min": 0, - "max": 5 - }, - "size": 5 + "01014": 1, + "01015": 1, + "90038": 1, + "90039": 1, + "90040": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WendyAdamsParallelBack.4232d9.gmnotes b/objects/AllPlayerCards.15bb07/WendyAdamsParallelBack.4232d9.gmnotes index 8e4b58da..46a01213 100644 --- a/objects/AllPlayerCards.15bb07/WendyAdamsParallelBack.4232d9.gmnotes +++ b/objects/AllPlayerCards.15bb07/WendyAdamsParallelBack.4232d9.gmnotes @@ -9,63 +9,13 @@ "agilityIcons": 4, "cycle": "Red Tide Rising", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90039": 1, - "01014": 1 - }, - { - "90040": 1, - "01015": 1 - } - ], - "choices": 1 - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Blessed", - "trait": [ - "blessed" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Cursed", - "trait": [ - "cursed" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "choiceName": "Both", - "trait": [ - "blessed", - "cursed" - ], - "level": { - "min": 0, - "max": 5 - }, - "size": 5 + "01014": 1, + "01015": 1, + "90038": 1, + "90039": 1, + "90040": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WendyAdamsParallelFront.61503e.gmnotes b/objects/AllPlayerCards.15bb07/WendyAdamsParallelFront.61503e.gmnotes index acf38b31..7ad2dc1b 100644 --- a/objects/AllPlayerCards.15bb07/WendyAdamsParallelFront.61503e.gmnotes +++ b/objects/AllPlayerCards.15bb07/WendyAdamsParallelFront.61503e.gmnotes @@ -9,42 +9,13 @@ "agilityIcons": 4, "cycle": "Red Tide Rising", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "90039": 1, - "01014": 1 - }, - { - "90040": 1, - "01015": 1 - }, - { - "90038": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "rogue" - ], - "level": { - "min": 0, - "max": 2 - } + "01014": 1, + "01015": 1, + "90038": 1, + "90039": 1, + "90040": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WilliamYorick.7e4c56.gmnotes b/objects/AllPlayerCards.15bb07/WilliamYorick.7e4c56.gmnotes index 97a834dc..84e120a6 100644 --- a/objects/AllPlayerCards.15bb07/WilliamYorick.7e4c56.gmnotes +++ b/objects/AllPlayerCards.15bb07/WilliamYorick.7e4c56.gmnotes @@ -9,37 +9,10 @@ "agilityIcons": 3, "cycle": "The Path to Carcosa", "extraToken": "Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "03016": 1 - }, - { - "03017": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "survivor", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 2 - } + "03016": 1, + "03017": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WilsonRichards.55eab5.gmnotes b/objects/AllPlayerCards.15bb07/WilsonRichards.55eab5.gmnotes index 18130933..43be9292 100644 --- a/objects/AllPlayerCards.15bb07/WilsonRichards.55eab5.gmnotes +++ b/objects/AllPlayerCards.15bb07/WilsonRichards.55eab5.gmnotes @@ -8,56 +8,10 @@ "combatIcons": 3, "agilityIcons": 3, "cycle": "The Feast of Hemlock Vale", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "10002": 1 - }, - { - "10003": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "trait": [ - "tool" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "trait": [ - "improvised", - "upgrade" - ], - "level": { - "min": 0, - "max": 1 - }, - "limit": 5 + "10002": 1, + "10003": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/WinifredHabbamock.cd4028.gmnotes b/objects/AllPlayerCards.15bb07/WinifredHabbamock.cd4028.gmnotes index d696de72..2113d315 100644 --- a/objects/AllPlayerCards.15bb07/WinifredHabbamock.cd4028.gmnotes +++ b/objects/AllPlayerCards.15bb07/WinifredHabbamock.cd4028.gmnotes @@ -9,28 +9,10 @@ "agilityIcons": 5, "cycle": "Investigator Packs", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "60302": 1 - }, - { - "60303": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "rogue", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } + "60302": 1, + "60303": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/ZoeySamaras.98a0e1.gmnotes b/objects/AllPlayerCards.15bb07/ZoeySamaras.98a0e1.gmnotes index 4dce45ec..1c897083 100644 --- a/objects/AllPlayerCards.15bb07/ZoeySamaras.98a0e1.gmnotes +++ b/objects/AllPlayerCards.15bb07/ZoeySamaras.98a0e1.gmnotes @@ -9,38 +9,12 @@ "agilityIcons": 2, "cycle": "The Dunwich Legacy", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "02006": 1, - "90060": 1 - }, - { - "02007": 1, - "90061": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Guardian or Neutral" + "02006": 1, + "02007": 1, + "90060": 1, + "90061": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/ZoeySamarasParallel.98a0e2.gmnotes b/objects/AllPlayerCards.15bb07/ZoeySamarasParallel.98a0e2.gmnotes index 26876b64..80209047 100644 --- a/objects/AllPlayerCards.15bb07/ZoeySamarasParallel.98a0e2.gmnotes +++ b/objects/AllPlayerCards.15bb07/ZoeySamarasParallel.98a0e2.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 2, "cycle": "Path of the Righteous", "extraToken": "FreeTrigger|Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "02006": 1, - "90060": 1 - }, - { - "02007": 1, - "90061": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "trait": [ - "blessed", - "charm" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Mystic cards" + "02006": 1, + "02007": 1, + "90060": 1, + "90061": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/ZoeySamarasParallelBack.98a0e4.gmnotes b/objects/AllPlayerCards.15bb07/ZoeySamarasParallelBack.98a0e4.gmnotes index 378b5e74..30264b57 100644 --- a/objects/AllPlayerCards.15bb07/ZoeySamarasParallelBack.98a0e4.gmnotes +++ b/objects/AllPlayerCards.15bb07/ZoeySamarasParallelBack.98a0e4.gmnotes @@ -9,59 +9,12 @@ "agilityIcons": 2, "cycle": "Path of the Righteous", "extraToken": "None", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "02006": 1, - "90060": 1 - }, - { - "02007": 1, - "90061": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "faction": [ - "guardian" - ], - "level": { - "min": 0, - "max": 3 - } - }, - { - "trait": [ - "blessed", - "charm" - ], - "level": { - "min": 0, - "max": 4 - } - }, - { - "faction": [ - "mystic" - ], - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 Mystic cards" + "02006": 1, + "02007": 1, + "90060": 1, + "90061": 1 } ] } diff --git a/objects/AllPlayerCards.15bb07/ZoeySamarasParallelFront.98a0e3.gmnotes b/objects/AllPlayerCards.15bb07/ZoeySamarasParallelFront.98a0e3.gmnotes index 80eb0151..03ffccc2 100644 --- a/objects/AllPlayerCards.15bb07/ZoeySamarasParallelFront.98a0e3.gmnotes +++ b/objects/AllPlayerCards.15bb07/ZoeySamarasParallelFront.98a0e3.gmnotes @@ -9,38 +9,12 @@ "agilityIcons": 2, "cycle": "Path of the Righteous", "extraToken": "FreeTrigger|Reaction", - "deck_requirements": { - "size": 30, - "randomBasicWeaknessCount": 1, - "signatures": [ - { - "02006": 1, - "90060": 1 - }, - { - "02007": 1, - "90061": 1 - } - ] - }, - "deck_options": [ + "signatures": [ { - "faction": [ - "guardian", - "neutral" - ], - "level": { - "min": 0, - "max": 5 - } - }, - { - "level": { - "min": 0, - "max": 0 - }, - "limit": 5, - "error": "You cannot have more than 5 cards that are not Guardian or Neutral" + "02006": 1, + "02007": 1, + "90060": 1, + "90061": 1 } ] } diff --git a/objects/EncounterSets.304ffc.ttslua b/objects/EncounterSets.304ffc.ttslua index 69b8a2d9..5f0cbde8 100644 --- a/objects/EncounterSets.304ffc.ttslua +++ b/objects/EncounterSets.304ffc.ttslua @@ -1,11 +1,12 @@ +local GlobalApi = require("core/GlobalApi") + function onLoad() self.addContextMenuItem("Download", download) end function download(playerColor) - Global.call('placeholder_download', { - filename = self.getGMNotes(), - player = Player[playerColor], - replace = self.guid - }) + local filename = self.getGMNotes() + local player = Player[playerColor] + local replace = self.guid + GlobalApi.placeholderDownload(filename, player, replace) end diff --git a/src/accessories/CampaignImporterExporter.ttslua b/src/accessories/CampaignImporterExporter.ttslua index 3f3ab79f..c8c30eb5 100644 --- a/src/accessories/CampaignImporterExporter.ttslua +++ b/src/accessories/CampaignImporterExporter.ttslua @@ -1,8 +1,8 @@ local blessCurseApi = require("chaosbag/BlessCurseManagerApi") local chaosBagApi = require("chaosbag/ChaosBagApi") local deckImporterApi = require("arkhamdb/DeckImporterApi") +local GlobalApi = require("core/GlobalApi") local guidReferenceApi = require("core/GUIDReferenceApi") -local optionPanelApi = require("core/OptionPanelApi") local playAreaApi = require("core/PlayAreaApi") local playermatApi = require("playermat/PlayermatApi") @@ -30,11 +30,7 @@ local campaignTokenData = { MaterialIndex = 2, TypeIndex = 6, CustomShader = { - SpecularColor = { - r = 0.72, - g = 0.51, - b = 0.34 - }, + SpecularColor = { r = 0.72, g = 0.51, b = 0.34 }, SpecularIntensity = 0.4, SpecularSharpness = 7.0, FresnelStrength = 0.0 @@ -58,7 +54,8 @@ end function onObjectLeaveContainer(container) if container.hasTag("ImporterToken") then - broadcastToAll("Removing objects from the Save Coin bag will break functionality. Please return the removed objects.", "Yellow") + broadcastToAll( + "Removing objects from the Save Coin bag will break functionality. Please return the removed objects.", "Yellow") end end @@ -178,7 +175,7 @@ function restoreCampaignData(importData, coin) end end - Wait.time(function() optionPanelApi.loadSettings(importData["options"]) end, 0.5) + Wait.time(function() GlobalApi.loadOptionPanelSettings(importData["options"]) end, 0.5) -- destroy Tour Starter token local tourStarter = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TourStarter") @@ -231,15 +228,15 @@ function createCampaignToken(_, playerColor, _) chaosBagApi.releaseAllSealedTokens(playerColor) -- main data collection - campaignData.box = campaignBox.getGUID() - campaignData.bag = chaosBagApi.getChaosBagState() - campaignData.decks = deckImporterApi.getUiState() + campaignData.box = campaignBox.getGUID() + campaignData.bag = chaosBagApi.getChaosBagState() + campaignData.decks = deckImporterApi.getUiState() campaignData.clueCount = playAreaApi.getInvestigatorCount() - campaignData.playarea = playAreaApi.getSurface() - campaignData.options = optionPanelApi.getOptions() + campaignData.playarea = playAreaApi.getSurface() + campaignData.options = GlobalApi.getOptionPanelState() -- save campaign log if present - local campaignLog = findUniqueObjectWithTag("CampaignLog") + local campaignLog = findUniqueObjectWithTag("CampaignLog") if campaignLog then local logData = campaignLog.getData() logData.Locked = false diff --git a/src/accessories/CardBackEnhancer.ttslua b/src/accessories/CardBackEnhancer.ttslua index ae288f62..7ffb9a54 100644 --- a/src/accessories/CardBackEnhancer.ttslua +++ b/src/accessories/CardBackEnhancer.ttslua @@ -31,6 +31,7 @@ function onCollisionEnter(collisionInfo) for customDeckId, customDeckData in pairs(data["CustomDeck"]) do if deckChanges[customDeckId] then customDeckData["BackURL"] = deckChanges[customDeckId] + customDeckData["BackIsHidden"] = true end end end @@ -67,12 +68,13 @@ function processCard(cardData) local customDeckId, customDeckData = next(cardData["CustomDeck"]) - -- if this card already has the correct back - if customDeckData["BackURL"] == newBack then return false end + -- if this card already has the correct back settings + if customDeckData["BackURL"] == newBack and customDeckData["BackIsHidden"] then return false end -- skip cards with decksheets as back if (customDeckData["NumHeight"] == 1 and customDeckData["NumWidth"] == 1) or customDeckData["UniqueBack"] == false then + customDeckData["BackIsHidden"] = true customDeckData["BackURL"] = newBack deckChanges[customDeckId] = newBack end diff --git a/src/chaosbag/ChaosBagApi.ttslua b/src/chaosbag/ChaosBagApi.ttslua index 4aa52eea..f2842761 100644 --- a/src/chaosbag/ChaosBagApi.ttslua +++ b/src/chaosbag/ChaosBagApi.ttslua @@ -67,6 +67,23 @@ do return Global.call("canTouchChaosTokens") end + ChaosBagApi.activeRedrawEffect = function(validTokens, invalidTokens, returnToPool, drawSpecificToken) + Global.call("activeRedrawEffect", { + validTokens = validTokens, + invalidTokens = invalidTokens, + returnToPool = returnToPool, + drawSpecificToken = drawSpecificToken + }) + end + + ChaosBagApi.getReadableTokenName = function(tokenName) + return Global.call("getReadableTokenName", tokenName) + end + + ChaosBagApi.getChaosTokenName = function(chosenToken) + return Global.call("getChaosTokenName", chosenToken) + end + -- draws a chaos token to a playermat ---@param mat tts__Object Playermat that triggered this ---@param drawAdditional boolean Controls whether additional tokens should be drawn diff --git a/src/core/DownloadBox.ttslua b/src/core/DownloadBox.ttslua index 36e84566..2e072dcd 100644 --- a/src/core/DownloadBox.ttslua +++ b/src/core/DownloadBox.ttslua @@ -1,3 +1,5 @@ +local GlobalApi = require("core/GlobalApi") + function onLoad() -- make sure the model is loaded so that we can use the bounds Wait.condition(buttonCreation, function() return not self.loading_custom end) @@ -27,9 +29,8 @@ function buttonCreation() end function buttonClick_download(_, playerColor) - Global.call('placeholder_download', { - filename = self.getGMNotes(), - player = playerColor and Player[playerColor] or nil, - replace = self.guid - }) + local filename = self.getGMNotes() + local player = playerColor and Player[playerColor] or nil + local replace = self.guid + GlobalApi.placeholderDownload(filename, player, replace) end diff --git a/src/core/GameKeyHandler.ttslua b/src/core/GameKeyHandler.ttslua index 4fcfc6c5..b83e64ad 100644 --- a/src/core/GameKeyHandler.ttslua +++ b/src/core/GameKeyHandler.ttslua @@ -1,8 +1,8 @@ local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi") +local GlobalApi = require("core/GlobalApi") local guidReferenceApi = require("core/GUIDReferenceApi") local mythosAreaApi = require("core/MythosAreaApi") local navigationOverlayApi = require("core/NavigationOverlayApi") -local optionPanelApi = require("core/OptionPanelApi") local playAreaApi = require("core/PlayAreaApi") local playermatApi = require("playermat/PlayermatApi") local searchLib = require("util/SearchLib") @@ -106,7 +106,7 @@ function takeCardIntoThreatArea(playerColor, hoveredObject) -- contruct feedback message local cardName = hoveredObject.getName() if cardName == "" then cardName = "a card" end - local playerName = Global.call("getColoredName", playerColor) + local playerName = GlobalApi.getColoredName(playerColor) broadcastToAll("Moved " .. cardName .. " to " .. playerName .. "'s threat area.", "White") -- get new rotation (rounded) @@ -379,7 +379,7 @@ function removeOneUse(playerColor, hoveredObject) end -- construct feedback message - local playerName = Global.call("getColoredName", playerColor) + local playerName = GlobalApi.getColoredName(playerColor) local cardInfo = "" if cardName and cardName ~= "" then cardInfo = " from " .. cardName @@ -502,7 +502,7 @@ function takeClueFromLocation(playerColor, hoveredObject) return end - local clickableClues = optionPanelApi.getOptions()["useClueClickers"] + local clickableClues = GlobalApi.getOptionPanelState()["useClueClickers"] -- handling for calling this for a specific mat via hotkey local matColor, pos @@ -530,7 +530,7 @@ function takeClueFromLocation(playerColor, hoveredObject) end -- construct feedback message - local playerName = Global.call("getColoredName", playerColor) + local playerName = GlobalApi.getColoredName(playerColor) local cardInfo = "" if cardName and cardName ~= "" then cardInfo = " from " .. cardName diff --git a/src/core/Global.ttslua b/src/core/Global.ttslua index d0877616..e035e655 100644 --- a/src/core/Global.ttslua +++ b/src/core/Global.ttslua @@ -1,24 +1,24 @@ -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") -local playermatApi = require("playermat/PlayermatApi") -local searchLib = require("util/SearchLib") -local soundCubeApi = require("core/SoundCubeApi") -local tokenArrangerApi = require("accessories/TokenArrangerApi") -local tokenChecker = require("core/token/TokenChecker") -local tokenManager = require("core/token/TokenManager") +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") +local playermatApi = require("playermat/PlayermatApi") +local searchLib = require("util/SearchLib") +local soundCubeApi = require("core/SoundCubeApi") +local tokenArrangerApi = require("accessories/TokenArrangerApi") +local tokenChecker = require("core/token/TokenChecker") +local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") --------------------------------------------------------- -- general setup --------------------------------------------------------- -ENCOUNTER_DECK_POS = { -3.93, 1, 5.76 } -ENCOUNTER_DECK_DISCARD_POSITION = { -3.85, 1, 10.38 } +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 = { +local NOT_INTERACTABLE = { "6161b4", -- Decoration-Map "9f334f", -- MythosArea "463022", -- Panel behind tentacle stand @@ -30,13 +30,13 @@ local NOT_INTERACTABLE = { "975c39", -- vertical border right } -local chaosTokens = {} -local chaosTokensLastMatGUID = nil +local chaosTokens = {} +local chaosTokensLastMatGUID = nil -- chaos token stat tracking -local tokenDrawingStats = { ["Overall"] = {} } +local tokenDrawingStats = { ["Overall"] = {} } -local bagSearchers = {} +local bagSearchers = {} local hideTitleSplashWaitFunctionId = nil -- online functionality related variables @@ -55,8 +55,8 @@ local tabIdTable = { } -- optionPanel data (intentionally not local!) -optionPanel = {} -local LANGUAGES = { +optionPanel = {} +local LANGUAGES = { { code = "zh_CN", name = "简体中文" }, { code = "zh_TW", name = "繁體中文" }, { code = "de", name = "Deutsch" }, @@ -65,20 +65,20 @@ local LANGUAGES = { { code = "fr", name = "Français" }, { code = "it", name = "Italiano" } } -local RESOURCE_OPTIONS = { +local RESOURCE_OPTIONS = { "enabled", "custom", "disabled" } -- tracks the visibility of each hand -local handVisibility = {} +local handVisibility = {} --------------------------------------------------------- -- data for tokens --------------------------------------------------------- -TOKEN_DATA = { +TOKEN_DATA = { damage = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357115146/903D11AAE7BD5C254C8DC136E9202EE516289DEA/", scale = { 0.17, 0.17, 0.17 } }, horror = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357163535/6D9E0756503664D65BDB384656AC6D4BD713F5FC/", scale = { 0.17, 0.17, 0.17 } }, resource = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357192910/11DDDC7EF621320962FDCF3AE3211D5EDC3D1573/", scale = { 0.17, 0.17, 0.17 } }, @@ -86,7 +86,7 @@ TOKEN_DATA = { clue = { image = "https://steamusercontent-a.akamaihd.net/ugc/1758068501357164917/1D06F1DC4D6888B6F57124BD2AFE20D0B0DA15A8/", scale = { 0.15, 0.15, 0.15 } } } -ID_URL_MAP = { +ID_URL_MAP = { ['blue'] = { name = "Elder Sign", url = 'https://i.imgur.com/nEmqjmj.png' }, ['p1'] = { name = "+1", url = 'https://i.imgur.com/uIx8jbY.png' }, ['0'] = { name = "0", url = 'https://i.imgur.com/btEtVfd.png' }, @@ -108,6 +108,26 @@ ID_URL_MAP = { ['frost'] = { name = "Frost", url = 'https://steamusercontent-a.akamaihd.net/ugc/1858293462583104677/195F93C063A8881B805CE2FD4767A9718B27B6AE/' } } +TokenManager = {} +local tokenOffsets = {} + +-- 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 playerCardData, locationData + +-- stateIDs for the multi-stated resource tokens +local stateTable = { + ["resource"] = 1, + ["ammo"] = 2, + ["bounty"] = 3, + ["charge"] = 4, + ["evidence"] = 5, + ["secret"] = 6, + ["supply"] = 7, + ["offering"] = 8 +} + --------------------------------------------------------- -- general code --------------------------------------------------------- @@ -154,6 +174,7 @@ function onLoad(savedData) getModVersion() updateHandVisibility() math.randomseed(os.time()) + TokenManager.initialiize() -- initialization of loadable objects library (delay to let Navigation Overlay build) Wait.time(function() @@ -577,11 +598,15 @@ end -- token spawning --------------------------------------------------------- --- DEPRECATED. Use TokenManager instead. +-- DEPRECATED. Use TokenManager instead --> TODO: Remove this with the new downloads repo (v.4.0.0) -- Spawns a single token. ---@param params table Array with arguments to the method. 1 = position, 2 = type, 3 = rotation function spawnToken(params) - return tokenManager.spawnToken(params[1], params[2], params[3]) + return TokenManager.spawnToken({ + position = params[1], + tokenType = params[2], + rotation = params[3] + }) end --------------------------------------------------------- @@ -1164,17 +1189,15 @@ function onClick_toggleUi(player, windowId) return end - -- hide the playAreaGallery if visible - if windowId == "downloadWindow" then - changeWindowVisibilityForColor(player.color, "playAreaGallery", false) - -- hide the downloadWindow if visible - elseif windowId == "playAreaGallery" then - changeWindowVisibilityForColor(player.color, "downloadWindow", false) + -- hide the playAreaGallery / downloadWindow if visible + if windowId == "downloadWindow" or windowId == "playAreaGallery" then + changeWindowVisibilityForColor(player.color, windowId, false) end changeWindowVisibilityForColor(player.color, windowId) end +-- wrapper for the real function to unpack arguments function changeWindowVisibilityForColorWrapper(params) changeWindowVisibilityForColor(params.color, params.windowId, params.overrideState, params.owner) end @@ -1237,20 +1260,17 @@ function changeWindowVisibilityForColor(color, windowId, overrideState, owner) return visible end --- forwards the call to the onClick function -function togglePlayAreaGallery(playerColor) - changeWindowVisibilityForColor(playerColor, "playareaGallery") -end - -- updates the preview window function updatePreviewWindow() local item = library[contentToShow][currentListItem] - local tempImage = "https://steamusercontent-a.akamaihd.net/ugc/2115061845788345842/2CD6ABC551555CCF58F9D0DDB7620197BA398B06/" + local tempImage = + "https://steamusercontent-a.akamaihd.net/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 = "https://steamusercontent-a.akamaihd.net/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/" + item.boxart = + "https://steamusercontent-a.akamaihd.net/ugc/762723517667628371/18438B0A0045038A7099648AA3346DFCAA267C66/" end UI.setValue("previewTitle", item.name) @@ -1945,10 +1965,531 @@ function onClick_notification(_, parameter) UI.hide("updateNotification") end +--------------------------------------------------------- +-- Token Manager +--------------------------------------------------------- + +function TokenManager.initialiize() + TokenManager.generateOffsets(12) +end + +-- Generates the offsets for tokens on a card (clues on locations are different and have their own function) +---@param maxTokens number Maximum amount of tokens on a card +function TokenManager.generateOffsets(maxTokens) + tokenOffsets = {} + for numTokens = 1, maxTokens do + if numTokens == 1 then + tokenOffsets[1] = { Vector(0, 3, -0.2) } + else + local offsets = {} + local rows = math.min(4, math.ceil(numTokens / 3)) + local tokensPlaced = 0 + for row = 1, rows do + local y = 3 + local z = -0.9 + (row - 1) * 0.7 + local tokensInRow = math.min(3, numTokens - tokensPlaced) + for col = 1, tokensInRow do + local x = 0 + if tokensInRow == 2 then + x = col == 1 and -0.4 or 0.4 + elseif tokensInRow == 3 then + x = (col - 2) * 0.7 + end + table.insert(offsets, Vector(x, y, z)) + tokensPlaced = tokensPlaced + 1 + end + end + tokenOffsets[numTokens] = offsets + end + end +end + +-- Spawns tokens for the card. This function is built to just throw a card at it and let it do +-- the work once a card has hit an area where it might spawn tokens. It will check to see if +-- the card has already spawned, find appropriate data from either the uses metadata or the Data +-- Helper, and spawn the tokens. +function TokenManager.spawnForCard(params) + if tokenSpawnTrackerApi.hasSpawnedTokens(params.card.getGUID()) then return end + local metadata = JSON.decode(params.card.getGMNotes()) + if metadata ~= nil then + TokenManager.spawnTokensFromUses(params.card, params.extraUses) + else + TokenManager.spawnTokensFromDataHelper(params.card) + end +end + +-- Spawns a set of tokens on the given card. +function TokenManager.spawnTokenGroup(params) + local card = params.card + local tokenType = params.tokenType + local tokenCount = params.tokenCount + local shiftDown = params.shiftDown + local subType = params.subType + + if tokenType == "damage" or tokenType == "horror" then + TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown) + elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "enabled" then + TokenManager.spawnResourceCounterToken(card, tokenCount) + elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "custom" and tokenCount == 0 then + TokenManager.spawnResourceCounterToken(card, tokenCount) + else + TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType) + end +end + +-- Spawns a single counter token and sets the value to tokenValue. Used for damage and horror tokens. +---@param card tts__Object Card to spawn tokens on +---@param tokenType string Type of token to spawn (template needs to be in source bag) +---@param tokenValue number Value to set the damage/horror to +function TokenManager.spawnCounterToken(card, tokenType, tokenValue, shiftDown) + if tokenValue < 1 or tokenValue > 50 then return end + + TokenManager.spawnToken({ + position = card.positionToWorld(tokenOffsets[1][1] + Vector(0, 0, shiftDown)), + tokenType = tokenType, + rotation = card.getRotation(), + callbackName = "updateStateToken", + callbackParams = tokenValue + }) +end + +function TokenManager.spawnResourceCounterToken(card, tokenCount) + TokenManager.spawnToken({ + position = card.positionToWorld(card.positionToLocal(card.getPosition()) + Vector(0, 0.2, -0.5)), + tokenType = "resourceCounter", + rotation = card.getRotation(), + callbackName = "updateTokenValue", + callbackParams = tokenCount + }) +end + +-- Spawns a number of tokens. +---@param tokenType string Type of token to spawn (template needs to be in source bag) +---@param tokenCount number How many tokens to spawn +---@param shiftDown? number An offset for the z-value of this group of tokens +---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource or action tokens +function TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType) + if tokenCount < 1 then return end + + local offsets = {} + if tokenType == "clue" then + offsets = TokenManager.buildClueOffsets(card, tokenCount) + else + if tokenCount > 12 then + printToAll("Attempting to spawn " .. tokenCount .. " tokens. Spawning clickable counter instead.") + TokenManager.spawnResourceCounterToken(card, tokenCount) + return + end + for i = 1, tokenCount do + offsets[i] = card.positionToWorld(tokenOffsets[tokenCount][i]) + end + end + + if shiftDown ~= nil then + -- 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 + shiftDownVector + end + end + + if offsets == nil then + error("couldn't find offsets for " .. tokenCount .. ' tokens') + return + end + + -- this is used to load the correct state for additional resource tokens (e.g. "Ammo") + local callbackName = nil + local callbackParams = nil + local stateID = stateTable[string.lower(subType or "")] + if tokenType == "resource" then + callbackName = "updateStateToken" + callbackParams = stateID + elseif tokenType == "universalActionAbility" then + local matColor = playermatApi.getMatColorByPosition(card.getPosition()) + local activeInvestigatorData = playermatApi.getActiveInvestigatorData(matColor) + callbackName = "updateUniversalActionAbilityToken" + callbackParams = { class = activeInvestigatorData.class, symbol = subType or activeInvestigatorData.class } + end + + for i = 1, tokenCount do + TokenManager.spawnToken({ + position = offsets[i], + tokenType = tokenType, + rotation = card.getRotation(), + callbackName = callbackName, + callbackParams = callbackParams + }) + end +end + +-- Spawns a single token at the given global position by copying it from the template bag. +function TokenManager.spawnToken(params) + local position = params.position + local rotation = params.rotation + local tokenType = params.tokenType + local callbackName = params.callbackName + local callbackParams = params.callbackParams + + -- initialize data table + local spawnData = { position = position } + + -- maybe set callback function + if callbackName then + if type(_G[callbackName]) ~= "function" then + error("Callback function " .. callbackName .. " does not exist") + return + else + spawnData.callback_function = function(obj) _G[callbackName](obj, callbackParams) end + end + end + + -- get data for token type + local loadTokenType = tokenType + if tokenType == "clue" or tokenType == "doom" then + loadTokenType = "clueDoom" + end + + TokenManager.initTokenTemplates() + local tokenTemplate = tokenTemplates[loadTokenType] + if tokenTemplate == nil then + error("Unknown token type '" .. loadTokenType .. "'") + return + end + + tokenTemplate.Nickname = "" + spawnData.data = tokenTemplate + + -- get rotation for the token + -- Take ONLY the Y-value for rotation, so we don't flip the token coming out of the bag + local rot = Vector(tokenTemplate.Transform.rotX, 270, tokenTemplate.Transform.rotZ) + if rotation ~= nil then + rot.y = rotation.y + end + if tokenType == "doom" then + rot.z = 180 + end + + spawnData.rotation = rot + + return spawnObjectData(spawnData) +end + +-- Checks a card for metadata to maybe replenish it +function TokenManager.maybeReplenishCard(params) + for _, useInfo in ipairs(params.uses) do + if useInfo.count and useInfo.replenish then + TokenManager.replenishTokens(params.card, useInfo) + end + end +end + +-- Pushes new player card data into the local copy of the Data Helper player data. +---@param dataTable table Key/Value pairs following the DataHelper style +function TokenManager.addPlayerCardData(dataTable) + TokenManager.initDataHelperData() + for k, v in pairs(dataTable) do + playerCardData[k] = v + end +end + +-- Pushes new location data into the local copy of the Data Helper location data. +---@param dataTable table Key/Value pairs following the DataHelper style +function TokenManager.addLocationData(dataTable) + TokenManager.initDataHelperData() + for k, v in pairs(dataTable) do + locationData[k] = v + end +end + +-- Checks to see if the given card has location data in the DataHelper +---@param card tts__Object Card to check for data +---@return boolean: True if this card has data in the helper, false otherwise +function TokenManager.hasLocationData(card) + TokenManager.initDataHelperData() + return TokenManager.getLocationData(card) ~= nil +end + +function TokenManager.initTokenTemplates() + if tokenTemplates ~= nil then return end + tokenTemplates = {} + local tokenSource = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSource") + for _, tokenTemplate in ipairs(tokenSource.getData().ContainedObjects) do + local tokenName = tokenTemplate.Memo + tokenTemplates[tokenName] = tokenTemplate + end +end + +-- Copies the data from the DataHelper. Will only happen once. +function TokenManager.initDataHelperData() + if playerCardData ~= nil then return end + local dataHelper = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper") + playerCardData = dataHelper.getTable('PLAYER_CARD_DATA') + locationData = dataHelper.getTable('LOCATIONS_DATA') +end + +-- Spawn tokens for a card based on the uses metadata. This will consider the face up/down state +-- of the card for both locations and standard cards. +---@param card tts__Object Card to maybe spawn tokens for +---@param extraUses table A table of = which will modify the number of tokens +--- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1 +function TokenManager.spawnTokensFromUses(card, extraUses) + local uses = TokenManager.getUses(card) + if uses == nil then return end + + -- go through tokens to spawn + local tokenCount + for i, useInfo in ipairs(uses) do + 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 = card, + tokenType = useInfo.token, + tokenCount = tokenCount, + shiftDown = (i - 1) * 0.8, + subType = useInfo.type + }) + end + + tokenSpawnTrackerApi.markTokensSpawned(card.getGUID()) +end + +-- Spawn tokens for a card based on the data helper data. This will consider the face up/down state +-- of the card for both locations and standard cards. +---@param card tts__Object Card to maybe spawn tokens for +function TokenManager.spawnTokensFromDataHelper(card) + TokenManager.initDataHelperData() + local playerData = TokenManager.getPlayerCardData(card) + if playerData ~= nil then + TokenManager.spawnPlayerCardTokensFromDataHelper(card, playerData) + end + local specificLocationData = TokenManager.getLocationData(card) + if specificLocationData ~= nil then + TokenManager.spawnLocationTokensFromDataHelper(card, specificLocationData) + end +end + +-- Spawn tokens for a player card using data retrieved from the Data Helper. +---@param card tts__Object Card to maybe spawn tokens for +---@param playerData table Player card data structure retrieved from the DataHelper. Should be +-- the right data for this card. +function TokenManager.spawnPlayerCardTokensFromDataHelper(card, playerData) + TokenManager.spawnTokenGroup({ + card = card, + tokenType = playerData.tokenType, + tokenCount = playerData.tokenCount + }) + tokenSpawnTrackerApi.markTokensSpawned(card.getGUID()) +end + +-- Spawn tokens for a location using data retrieved from the Data Helper. +---@param card tts__Object Card to maybe spawn tokens for +---@param locationData table Location data structure retrieved from the DataHelper. Should be +-- the right data for this card. +function TokenManager.spawnLocationTokensFromDataHelper(card, locationData) + local clueCount = TokenManager.getClueCountFromData(card, locationData) + if clueCount > 0 then + TokenManager.spawnTokenGroup({ + card = card, + tokenType = "clue", + tokenCount = clueCount + }) + tokenSpawnTrackerApi.markTokensSpawned(card.getGUID()) + end +end + +function TokenManager.getPlayerCardData(card) + return playerCardData[card.getName() .. ':' .. card.getDescription()] + or playerCardData[card.getName()] +end + +function TokenManager.getLocationData(card) + return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()] +end + +function TokenManager.getClueCountFromData(card, locationData) + -- Return the number of clues to spawn on this location + if locationData == nil then + error('attempted to get clue for unexpected object: ' .. card.getName()) + return 0 + end + + 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 + return locationData.value + elseif locationData.type == 'perPlayer' then + return locationData.value * playAreaApi.getInvestigatorCount() + end + error('unexpected location type: ' .. locationData.type) + end + return 0 +end + +-- Gets the right uses structure for this card, based on metadata and face up/down state +---@param card tts__Object Card to pull the uses from +TokenManager.getUses = function(card) + local metadata = JSON.decode(card.getGMNotes()) or {} + if metadata.type == "Location" then + if card.is_face_down and metadata.locationBack ~= nil then + return metadata.locationBack.uses + elseif not card.is_face_down and metadata.locationFront ~= nil then + return metadata.locationFront.uses + end + elseif not card.is_face_down then + return metadata.uses + end + return nil +end + +-- Dynamically create positions for clues on a card +---@param card tts__Object Card the clues will be placed on +---@param count number How many clues? +---@return table: Array of global positions to spawn the clues at +TokenManager.buildClueOffsets = function(card, count) + -- make sure clues always spawn from left to right + local modifier = card.is_face_down and 1 or -1 + + local cluePositions = {} + for i = 1, count do + -- get the set number (1 for clue 1-16, 2 for 17-32 etc.) + local set = math.floor((i - 1) / 16) + 1 + + -- get the local index (always number from 1-16) + local localIndex = (i - 1) % 16 + + -- get row and column for this clue + local row = math.floor(localIndex / 4) + 1 + local column = localIndex % 4 + + -- calculate local position + local localPos = Vector((-0.825 + 0.55 * column) * modifier, 0, -1.5 + 0.55 * row) + + -- get the global clue position (higher y-position for each set) + local cluePos = card.positionToWorld(localPos) + Vector(0, 0.03 + 0.103 * (set - 1), 0) + + -- add position to table + table.insert(cluePositions, cluePos) + end + return cluePositions +end + +---@param card tts__Object Card object to be replenished +---@param useInfo table The already decoded subtable of metadata.uses (to avoid decoding again) +function TokenManager.replenishTokens(card, useInfo) + -- get current amount of matching resource tokens on the card + local clickableResourceCounter = nil + local foundTokens = 0 + + local maybeDeleteThese = {} + if useInfo.token == "clue" then + for _, obj in ipairs(searchLib.onObject(card, "isClue")) do + foundTokens = foundTokens + math.abs(obj.getQuantity()) + table.insert(maybeDeleteThese, obj) + end + elseif useInfo.token == "doom" then + for _, obj in ipairs(searchLib.onObject(card, "isDoom")) do + foundTokens = foundTokens + math.abs(obj.getQuantity()) + table.insert(maybeDeleteThese, obj) + end + else + -- search for the token instead if there's no special resource state for it + local searchType = string.lower(useInfo.type) + if stateTable[searchType] == nil then + searchType = useInfo.token + end + + for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do + local memo = obj.getMemo() + if searchType == memo then + foundTokens = foundTokens + math.abs(obj.getQuantity()) + table.insert(maybeDeleteThese, obj) + elseif memo == "resourceCounter" then + foundTokens = obj.getVar("val") + clickableResourceCounter = obj + break + end + end + end + + -- this is the theoretical new amount of uses (to be checked below) + local newCount = foundTokens + useInfo.replenish + + -- if there are already more uses than the replenish amount, keep them + if foundTokens > useInfo.count then + newCount = foundTokens + -- only replenish up until the replenish amount + elseif newCount > useInfo.count then + newCount = useInfo.count + end + + -- update the clickable counter or spawn a group of tokens + if clickableResourceCounter then + clickableResourceCounter.call("updateVal", newCount) + else + -- delete existing tokens + for _, obj in ipairs(maybeDeleteThese) do + obj.destruct() + end + + -- spawn new token group + TokenManager.spawnTokenGroup({ + card = card, + tokenType = useInfo.token, + tokenCount = newCount, + subType = useInfo.type + }) + end +end + +--------------------------------------------------------- +-- Callback functions for token spawning +--------------------------------------------------------- + +function updateUniversalActionAbilityToken(obj, params) + obj.call("updateClassAndSymbol", params) + if params.addTag then + obj.addTag(params.addTag) + end +end + +function updateStateToken(obj, stateID) + if stateID ~= nil and stateID ~= 1 then + obj.setState(stateID) + end +end + +function updateTokenValue(obj, newVal) + obj.call("updateVal", newVal) +end + --------------------------------------------------------- -- Utility functions --------------------------------------------------------- +-- allows calling a function inside of a table (like the TokenManager) +function callTable(params) + local keys = params[1] or {} + local arg = params[2] or nil + local var = _G + for _, key in ipairs(keys) do + var = var[key] + if type(var) ~= "table" then break end + end + if type(var) ~= "function" then + log("resulting var was not a function " .. table.concat(keys, "->")) + return + end + return var(arg) +end + -- removes a value from a table ---@return boolean: True if something was removed function removeValueFromTable(t, val) @@ -1971,6 +2512,7 @@ function isTableEmpty(tbl) end -- returns the colored steam name or color +---@param playerColor string Color of the player function getColoredName(playerColor) local displayName = playerColor if Player[playerColor].steam_name then diff --git a/src/core/GlobalApi.ttslua b/src/core/GlobalApi.ttslua new file mode 100644 index 00000000..22dbee5d --- /dev/null +++ b/src/core/GlobalApi.ttslua @@ -0,0 +1,69 @@ +do + local GlobalApi = {} + + -- downloads an object from the library and optionally replaces an existing object + ---@param url string Path to JSON file (without .json extension) in the library + ---@param player tts__Player Player that initiated this + ---@param replace string GUID of the object to replace + function GlobalApi.placeholderDownload(url, player, replace) + Global.call("placeholder_download", { + url = url, + player = player, + replace = replace + }) + end + + -- splashes the scenario title (used when placing a scenario) and plays a sound + ---@param scenarioName string Name of the scenario + function GlobalApi.titleSplash(scenarioName) + Global.call("titleSplash", scenarioName) + end + + -- toggles the visibility of the specific window for the specified color + ---@param playerColor string Player color to toggle the visibility for + ---@param windowId string ID of the XML element + ---@param overrideState? boolean Forcefully sets the new visibility + ---@param owner? tts__Object Object that owns the XML (or nil if Global) + ---@return boolean visible Returns the new state of the visibility + function GlobalApi.changeWindowVisibility(playerColor, windowId, overrideState, owner) + Global.call("changeWindowVisibilityForColorWrapper", { + color = playerColor, + windowId = windowId, + overrideState = overrideState, + owner = owner + }) + end + + -- this helper function updates the global XML while preserving the visibility of windows + ---@param newXml table Complete new XmlTable for the Global UI + function GlobalApi.updateGlobalXml(newXml) + Global.call("updateGlobalXml", newXml) + end + + -- returns the colored steam name or color + ---@param playerColor string Color of the player + function GlobalApi.getColoredName(playerColor) + return Global.call("getColoredName", playerColor) + end + + -- toggles the hand visibility of a hand for a specific player + ---@param playerColor string Color of the player that needs the visibility toggled + ---@param handColor string Color of the hand to toggle the visibility for + function GlobalApi.handVisibilityToggle(playerColor, handColor) + Global.call("handVisibilityToggle", { playerColor = playerColor, handColor = handColor}) + end + + -- loads saved options + ---@param options table Set a new state for the option table + function GlobalApi.loadOptionPanelSettings(options) + Global.call("loadSettings", options) + end + + -- gets the current state of the option panel + ---@return table: option panel state + function GlobalApi.getOptionPanelState() + return Global.getTable("optionPanel") + end + + return GlobalApi +end diff --git a/src/core/MythosArea.ttslua b/src/core/MythosArea.ttslua index 25f451d8..4cb762b7 100644 --- a/src/core/MythosArea.ttslua +++ b/src/core/MythosArea.ttslua @@ -1,26 +1,27 @@ -local deckLib = require("util/DeckLib") -local guidReferenceApi = require("core/GUIDReferenceApi") -local playAreaApi = require("core/PlayAreaApi") -local playermatApi = require("playermat/PlayermatApi") -local searchLib = require("util/SearchLib") -local tokenArrangerApi = require("accessories/TokenArrangerApi") -local tokenChecker = require("core/token/TokenChecker") -local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") +local deckLib = require("util/DeckLib") +local GlobalApi = require("core/GlobalApi") +local guidReferenceApi = require("core/GUIDReferenceApi") +local playAreaApi = require("core/PlayAreaApi") +local playermatApi = require("playermat/PlayermatApi") +local searchLib = require("util/SearchLib") +local tokenArrangerApi = require("accessories/TokenArrangerApi") +local tokenChecker = require("core/token/TokenChecker") +local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") -local ENCOUNTER_DECK_AREA = { +local ENCOUNTER_DECK_AREA = { upperLeft = { x = 1.05, z = 0.15 }, lowerRight = { x = 0.70, z = 0.59 } } -local ENCOUNTER_DISCARD_AREA = { +local ENCOUNTER_DISCARD_AREA = { upperLeft = { x = 1.77, z = 0.15 }, lowerRight = { x = 1.42, z = 0.59 } } -- global position of encounter deck and discard pile -local ENCOUNTER_DECK_POS = { x = -3.93, y = 1, z = 5.76 } +local ENCOUNTER_DECK_POS = { x = -3.93, y = 1, z = 5.76 } local ENCOUNTER_DISCARD_POSITION = { x = -3.85, y = 1, z = 10.38 } -local isReshuffling = false -local collisionEnabled = false +local isReshuffling = false +local collisionEnabled = false local currentScenario, useFrontData, tokenData function onSave() @@ -123,7 +124,7 @@ end -- fires if the scenario title changes function fireScenarioChangedEvent() -- maybe show the title splash screen - Wait.frames(function() Global.call('titleSplash', currentScenario) end, 20) + Wait.frames(function() GlobalApi.titleSplash(currentScenario) end, 20) -- set the scenario for the playarea (connections might be disabled) playAreaApi.onScenarioChanged(currentScenario) diff --git a/src/core/OptionPanelApi.ttslua b/src/core/OptionPanelApi.ttslua deleted file mode 100644 index acca1fb4..00000000 --- a/src/core/OptionPanelApi.ttslua +++ /dev/null @@ -1,16 +0,0 @@ -do - local OptionPanelApi = {} - - -- loads saved options - ---@param options table Set a new state for the option table - OptionPanelApi.loadSettings = function(options) - return Global.call("loadSettings", options) - end - - ---@return any: Table of option panel state - OptionPanelApi.getOptions = function() - return Global.getTable("optionPanel") - end - - return OptionPanelApi -end \ No newline at end of file diff --git a/src/core/PlayArea.ttslua b/src/core/PlayArea.ttslua index 18da1f3e..dd0836b1 100644 --- a/src/core/PlayArea.ttslua +++ b/src/core/PlayArea.ttslua @@ -1,6 +1,6 @@ local guidReferenceApi = require("core/GUIDReferenceApi") local searchLib = require("util/SearchLib") -local tokenManager = require("core/token/TokenManager") +local tokenManagerApi = require("core/token/TokenManagerApi") -- Location connection directional options local BIDIRECTIONAL = 0 @@ -80,7 +80,7 @@ end function updateLocations(args) customDataHelper = getObjectFromGUID(args[1]) if customDataHelper ~= nil then - tokenManager.addLocationData(customDataHelper.getTable("LOCATIONS_DATA")) + tokenManagerApi.addLocationData(customDataHelper.getTable("LOCATIONS_DATA")) end end @@ -102,7 +102,7 @@ function onCollisionEnter(collisionInfo) -- check if we should spawn clues here and do so according to playercount if shouldSpawnTokens(object) then - tokenManager.spawnForCard(object) + tokenManagerApi.spawnForCard(object) end -- If this card was being dragged, clear the dragging connections. A multi-drag/drop may send @@ -192,7 +192,7 @@ end function shouldSpawnTokens(card) local metadata = JSON.decode(card.getGMNotes()) if metadata == nil then - return tokenManager.hasLocationData(card) + return tokenManagerApi.hasLocationData(card) end return metadata.type == "Location" or metadata.type == "Enemy" diff --git a/src/core/PlayAreaSelector.ttslua b/src/core/PlayAreaSelector.ttslua index 0824dde9..a3c3f96a 100644 --- a/src/core/PlayAreaSelector.ttslua +++ b/src/core/PlayAreaSelector.ttslua @@ -1,5 +1,5 @@ require("core/PlayAreaImageData") -- this fills the variable "PLAYAREA_IMAGE_DATA" -local optionPanelApi = require("core/OptionPanelApi") +local GlobalApi = require("core/GlobalApi") local playAreaApi = require("core/PlayAreaApi") local typeIndex, selectionIndex, plainNameCache @@ -33,7 +33,7 @@ end -- click function for main button function onClick_toggleGallery(_, playerColor) - Global.call("togglePlayAreaGallery", playerColor) + GlobalApi.changeWindowVisibility(playerColor, "playareaGallery") end function getDataSubTableByIndex(dataTable, index) @@ -96,7 +96,7 @@ function updatePlayAreaGallery() end playareaList.attributes.height = round(#playareaList.children / 2, 0) * 380 - Global.call("updateGlobalXml", globalXml) + GlobalApi.updateGlobalXml(globalXml) Wait.time(highlightTabAndItem, 0.1) end @@ -117,7 +117,7 @@ function onClick_image(player, _, id) local dataForSelection = getDataSubTableByIndex(dataForType, selectionIndex) local newURL = dataForSelection[imageIndex].URL playAreaApi.updateSurface(newURL) - Global.call("togglePlayAreaGallery", player.color) + GlobalApi.changeWindowVisibility(player.color, "playareaGallery") end function highlightTabAndItem() @@ -155,7 +155,7 @@ end function maybeUpdatePlayAreaImage(scenarioName) -- check if option is enabled - local optionPanelState = optionPanelApi.getOptions() + local optionPanelState = GlobalApi.getOptionPanelState() if not optionPanelState["changePlayAreaImage"] then return end -- initialize cache if nil @@ -165,7 +165,7 @@ function maybeUpdatePlayAreaImage(scenarioName) for j, dataForCycle in pairs(dataForType) do for k, data in ipairs(dataForCycle) do local plainName = getPlainName(data.Name) - + -- override plainName for all images in the "Other Images" category (except the default image) if i == "Other Images" and data.Name ~= "Default Image" then plainName = "Generic" diff --git a/src/core/UniversalActionAbilityToken.ttslua b/src/core/UniversalActionAbilityToken.ttslua index de55b40d..138575db 100644 --- a/src/core/UniversalActionAbilityToken.ttslua +++ b/src/core/UniversalActionAbilityToken.ttslua @@ -45,10 +45,10 @@ local listOfSymbols = { local colorsForClasses = { Guardian = Color.new(19 / 255, 84 / 255, 165 / 255), - Mystic = Color.new(82 / 255, 18 / 255, 97 / 255), - Neutral = Color.new(108 / 255, 110 / 255, 112 / 255), - Rogue = Color.new(17 / 255, 72 / 255, 54 / 255), - Seeker = Color.new(215 / 255, 115 / 255, 35 / 255), + Mystic = Color.new(82 / 255, 18 / 255, 97 / 255), + Neutral = Color.new(108 / 255, 110 / 255, 112 / 255), + Rogue = Color.new(17 / 255, 72 / 255, 54 / 255), + Seeker = Color.new(215 / 255, 115 / 255, 35 / 255), Survivor = Color.new(190 / 255, 30 / 255, 45 / 255) } diff --git a/src/core/token/TokenManager.ttslua b/src/core/token/TokenManager.ttslua deleted file mode 100644 index 6fe68f09..00000000 --- a/src/core/token/TokenManager.ttslua +++ /dev/null @@ -1,561 +0,0 @@ -do - local guidReferenceApi = require("core/GUIDReferenceApi") - local optionPanelApi = require("core/OptionPanelApi") - local playAreaApi = require("core/PlayAreaApi") - local playermatApi = require("playermat/PlayermatApi") - local searchLib = require("util/SearchLib") - local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") - - local PLAYER_CARD_TOKEN_OFFSETS = { - [1] = { - Vector(0, 3, -0.2) - }, - [2] = { - Vector(0.4, 3, -0.2), - Vector(-0.4, 3, -0.2) - }, - [3] = { - Vector(0, 3, -0.9), - Vector(0.4, 3, -0.2), - Vector(-0.4, 3, -0.2) - }, - [4] = { - Vector(0.4, 3, -0.9), - Vector(-0.4, 3, -0.9), - Vector(0.4, 3, -0.2), - Vector(-0.4, 3, -0.2) - }, - [5] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.4, 3, -0.2), - Vector(-0.4, 3, -0.2) - }, - [6] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2) - }, - [7] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2), - Vector(0, 3, 0.5) - }, - [8] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2), - Vector(-0.35, 3, 0.5), - Vector(0.35, 3, 0.5) - }, - [9] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2), - Vector(0.7, 3, 0.5), - Vector(0, 3, 0.5), - Vector(-0.7, 3, 0.5) - }, - [10] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2), - Vector(0.7, 3, 0.5), - Vector(0, 3, 0.5), - Vector(-0.7, 3, 0.5), - Vector(0, 3, 1.2) - }, - [11] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2), - Vector(0.7, 3, 0.5), - Vector(0, 3, 0.5), - Vector(-0.7, 3, 0.5), - Vector(-0.35, 3, 1.2), - Vector(0.35, 3, 1.2) - }, - [12] = { - Vector(0.7, 3, -0.9), - Vector(0, 3, -0.9), - Vector(-0.7, 3, -0.9), - Vector(0.7, 3, -0.2), - Vector(0, 3, -0.2), - Vector(-0.7, 3, -0.2), - Vector(0.7, 3, 0.5), - Vector(0, 3, 0.5), - Vector(-0.7, 3, 0.5), - Vector(0.7, 3, 1.2), - Vector(0, 3, 1.2), - Vector(-0.7, 3, 1.2) - } - } - - -- stateIDs for the multi-stated resource tokens - local stateTable = { - ["resource"] = 1, - ["ammo"] = 2, - ["bounty"] = 3, - ["charge"] = 4, - ["evidence"] = 5, - ["secret"] = 6, - ["supply"] = 7, - ["offering"] = 8 - } - - -- 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 playerCardData - local locationData - - local TokenManager = {} - local internal = {} - - -- Spawns tokens for the card. This function is built to just throw a card at it and let it do - -- the work once a card has hit an area where it might spawn tokens. It will check to see if - -- the card has already spawned, find appropriate data from either the uses metadata or the Data - -- Helper, and spawn the tokens. - ---@param card tts__Object Card to maybe spawn tokens for - ---@param extraUses table A table of = which will modify the number of tokens - --- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1 - TokenManager.spawnForCard = function(card, extraUses) - if tokenSpawnTrackerApi.hasSpawnedTokens(card.getGUID()) then - return - end - local metadata = JSON.decode(card.getGMNotes()) - if metadata ~= nil then - internal.spawnTokensFromUses(card, extraUses) - else - internal.spawnTokensFromDataHelper(card) - end - end - - -- Spawns a set of tokens on the given card. - ---@param card tts__Object Card to spawn tokens on - ---@param tokenType string Type of token to spawn (template needs to be in source bag) - ---@param tokenCount number How many tokens to spawn. For damage or horror this value will be set to the - -- spawned state object rather than spawning multiple tokens - ---@param shiftDown? number An offset for the z-value of this group of tokens - ---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource tokens - TokenManager.spawnTokenGroup = function(card, tokenType, tokenCount, shiftDown, subType) - local optionPanel = optionPanelApi.getOptions() - - if tokenType == "damage" or tokenType == "horror" then - TokenManager.spawnCounterToken(card, tokenType, tokenCount, shiftDown) - elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "enabled" then - TokenManager.spawnResourceCounterToken(card, tokenCount) - elseif tokenType == "resource" and optionPanel["useResourceCounters"] == "custom" and tokenCount == 0 then - TokenManager.spawnResourceCounterToken(card, tokenCount) - else - TokenManager.spawnMultipleTokens(card, tokenType, tokenCount, shiftDown, subType) - end - end - - -- Spawns a single counter token and sets the value to tokenValue. Used for damage and horror tokens. - ---@param card tts__Object Card to spawn tokens on - ---@param tokenType string Type of token to spawn (template needs to be in source bag) - ---@param tokenValue number Value to set the damage/horror to - TokenManager.spawnCounterToken = function(card, tokenType, tokenValue, shiftDown) - if tokenValue < 1 or tokenValue > 50 then return end - - local pos = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[1][1] + Vector(0, 0, shiftDown)) - local rot = card.getRotation() - TokenManager.spawnToken(pos, tokenType, rot, function(spawned) - -- token starts in state 1, so don't attempt to change it to avoid error - if tokenValue ~= 1 then - spawned.setState(tokenValue) - end - end) - end - - TokenManager.spawnResourceCounterToken = function(card, tokenCount) - local pos = card.positionToWorld(card.positionToLocal(card.getPosition()) + Vector(0, 0.2, -0.5)) - local rot = card.getRotation() - TokenManager.spawnToken(pos, "resourceCounter", rot, function(spawned) - spawned.call("updateVal", tokenCount) - end) - end - - -- Spawns a number of tokens. - ---@param tokenType string Type of token to spawn (template needs to be in source bag) - ---@param tokenCount number How many tokens to spawn - ---@param shiftDown? number An offset for the z-value of this group of tokens - ---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource or action tokens - TokenManager.spawnMultipleTokens = function(card, tokenType, tokenCount, shiftDown, subType) - -- not checking the max at this point since clue offsets are calculated dynamically - if tokenCount < 1 then return end - - local offsets = {} - if tokenType == "clue" then - offsets = internal.buildClueOffsets(card, tokenCount) - else - -- only up to 12 offset tables defined - if tokenCount > 12 then - printToAll("Attempting to spawn " .. tokenCount .. " tokens. Spawning clickable counter instead.") - TokenManager.spawnResourceCounterToken(card, tokenCount) - return - end - for i = 1, tokenCount do - offsets[i] = card.positionToWorld(PLAYER_CARD_TOKEN_OFFSETS[tokenCount][i]) - end - end - - if shiftDown ~= nil then - -- 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 + shiftDownVector - end - end - - if offsets == nil then - error("couldn't find offsets for " .. tokenCount .. ' tokens') - return - end - - -- this is used to load the correct state for additional resource tokens (e.g. "Ammo") - local callback = nil - local stateID = stateTable[string.lower(subType or "")] - if tokenType == "resource" and stateID ~= nil and stateID ~= 1 then - callback = function(spawned) spawned.setState(stateID) end - elseif tokenType == "universalActionAbility" then - callback = function(spawned) - local matColor = playermatApi.getMatColorByPosition(card.getPosition()) - local activeInvestigatorData = playermatApi.getActiveInvestigatorData(matColor) - - spawned.call("updateClassAndSymbol", { - class = activeInvestigatorData.class, - symbol = subType or activeInvestigatorData.class - }) - end - end - - for i = 1, tokenCount do - TokenManager.spawnToken(offsets[i], tokenType, card.getRotation(), callback) - end - end - - -- Spawns a single token at the given global position by copying it from the template bag. - ---@param position tts__Vector Global position to spawn the token - ---@param tokenType string Type of token to spawn (template needs to be in source bag) - ---@param rotation tts__Vector Rotation to be used for the new token. Only the y-value will be used, - -- x and z will use the default rotation from the source bag - ---@param callback? function A callback function triggered after the new token is spawned - TokenManager.spawnToken = function(position, tokenType, rotation, callback) - internal.initTokenTemplates() - local loadTokenType = tokenType - if tokenType == "clue" or tokenType == "doom" then - loadTokenType = "clueDoom" - end - if tokenTemplates[loadTokenType] == nil then - error("Unknown token type '" .. tokenType .. "'") - return - end - local tokenTemplate = tokenTemplates[loadTokenType] - - -- Take ONLY the Y-value for rotation, so we don't flip the token coming out of the bag - local rot = Vector(tokenTemplate.Transform.rotX, 270, tokenTemplate.Transform.rotZ) - if rotation ~= nil then - rot.y = rotation.y - end - if tokenType == "doom" then - rot.z = 180 - end - - tokenTemplate.Nickname = "" - return spawnObjectData({ - data = tokenTemplate, - position = position, - rotation = rot, - callback_function = callback - }) - end - - -- Checks a card for metadata to maybe replenish it - ---@param card tts__Object Card object to be replenished - ---@param uses table The already decoded metadata.uses (to avoid decoding again) - TokenManager.maybeReplenishCard = function(card, uses) - for _, useInfo in ipairs(uses) do - if useInfo.count and useInfo.replenish then - internal.replenishTokens(card, useInfo) - end - end - end - - -- Pushes new player card data into the local copy of the Data Helper player data. - ---@param dataTable table Key/Value pairs following the DataHelper style - TokenManager.addPlayerCardData = function(dataTable) - internal.initDataHelperData() - for k, v in pairs(dataTable) do - playerCardData[k] = v - end - end - - -- Pushes new location data into the local copy of the Data Helper location data. - ---@param dataTable table Key/Value pairs following the DataHelper style - TokenManager.addLocationData = function(dataTable) - internal.initDataHelperData() - for k, v in pairs(dataTable) do - locationData[k] = v - end - end - - -- Checks to see if the given card has location data in the DataHelper - ---@param card tts__Object Card to check for data - ---@return boolean: True if this card has data in the helper, false otherwise - TokenManager.hasLocationData = function(card) - internal.initDataHelperData() - return internal.getLocationData(card) ~= nil - end - - internal.initTokenTemplates = function() - if tokenTemplates ~= nil then - return - end - tokenTemplates = {} - local tokenSource = guidReferenceApi.getObjectByOwnerAndType("Mythos", "TokenSource") - for _, tokenTemplate in ipairs(tokenSource.getData().ContainedObjects) do - local tokenName = tokenTemplate.Memo - tokenTemplates[tokenName] = tokenTemplate - end - end - - -- Copies the data from the DataHelper. Will only happen once. - internal.initDataHelperData = function() - if playerCardData ~= nil then - return - end - local dataHelper = guidReferenceApi.getObjectByOwnerAndType("Mythos", "DataHelper") - playerCardData = dataHelper.getTable('PLAYER_CARD_DATA') - locationData = dataHelper.getTable('LOCATIONS_DATA') - end - - -- Spawn tokens for a card based on the uses metadata. This will consider the face up/down state - -- of the card for both locations and standard cards. - ---@param card tts__Object Card to maybe spawn tokens for - ---@param extraUses table A table of = which will modify the number of tokens - --- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1 - internal.spawnTokensFromUses = function(card, extraUses) - local uses = internal.getUses(card) - if uses == nil then return end - - -- go through tokens to spawn - local tokenCount - for i, useInfo in ipairs(uses) do - 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, useInfo.token, tokenCount, (i - 1) * 0.8, useInfo.type) - end - - tokenSpawnTrackerApi.markTokensSpawned(card.getGUID()) - end - - -- Spawn tokens for a card based on the data helper data. This will consider the face up/down state - -- of the card for both locations and standard cards. - ---@param card tts__Object Card to maybe spawn tokens for - internal.spawnTokensFromDataHelper = function(card) - internal.initDataHelperData() - local playerData = internal.getPlayerCardData(card) - if playerData ~= nil then - internal.spawnPlayerCardTokensFromDataHelper(card, playerData) - end - local locationData = internal.getLocationData(card) - if locationData ~= nil then - internal.spawnLocationTokensFromDataHelper(card, locationData) - end - end - - -- Spawn tokens for a player card using data retrieved from the Data Helper. - ---@param card tts__Object Card to maybe spawn tokens for - ---@param playerData table Player card data structure retrieved from the DataHelper. Should be - -- the right data for this card. - internal.spawnPlayerCardTokensFromDataHelper = function(card, playerData) - local token = playerData.tokenType - local tokenCount = playerData.tokenCount - TokenManager.spawnTokenGroup(card, token, tokenCount) - tokenSpawnTrackerApi.markTokensSpawned(card.getGUID()) - end - - -- Spawn tokens for a location using data retrieved from the Data Helper. - ---@param card tts__Object Card to maybe spawn tokens for - ---@param locationData table Location data structure retrieved from the DataHelper. Should be - -- the right data for this card. - internal.spawnLocationTokensFromDataHelper = function(card, locationData) - local clueCount = internal.getClueCountFromData(card, locationData) - if clueCount > 0 then - TokenManager.spawnTokenGroup(card, "clue", clueCount) - tokenSpawnTrackerApi.markTokensSpawned(card.getGUID()) - end - end - - internal.getPlayerCardData = function(card) - return playerCardData[card.getName() .. ':' .. card.getDescription()] - or playerCardData[card.getName()] - end - - internal.getLocationData = function(card) - return locationData[card.getName() .. '_' .. card.getGUID()] or locationData[card.getName()] - end - - internal.getClueCountFromData = function(card, locationData) - -- Return the number of clues to spawn on this location - if locationData == nil then - error('attempted to get clue for unexpected object: ' .. card.getName()) - return 0 - end - - 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 - return locationData.value - elseif locationData.type == 'perPlayer' then - return locationData.value * playAreaApi.getInvestigatorCount() - end - error('unexpected location type: ' .. locationData.type) - end - return 0 - end - - -- Gets the right uses structure for this card, based on metadata and face up/down state - ---@param card tts__Object Card to pull the uses from - internal.getUses = function(card) - local metadata = JSON.decode(card.getGMNotes()) or {} - if metadata.type == "Location" then - if card.is_face_down and metadata.locationBack ~= nil then - return metadata.locationBack.uses - elseif not card.is_face_down and metadata.locationFront ~= nil then - return metadata.locationFront.uses - end - elseif not card.is_face_down then - return metadata.uses - end - - return nil - end - - -- Dynamically create positions for clues on a card - ---@param card tts__Object Card the clues will be placed on - ---@param count number How many clues? - ---@return table: Array of global positions to spawn the clues at - internal.buildClueOffsets = function(card, count) - -- make sure clues always spawn from left to right - local modifier = card.is_face_down and 1 or -1 - - local cluePositions = {} - for i = 1, count do - -- get the set number (1 for clue 1-16, 2 for 17-32 etc.) - local set = math.floor((i - 1) / 16) + 1 - - -- get the local index (always number from 1-16) - local localIndex = (i - 1) % 16 - - -- get row and column for this clue - local row = math.floor(localIndex / 4) + 1 - local column = localIndex % 4 - - -- calculate local position - local localPos = Vector((-0.825 + 0.55 * column) * modifier, 0, -1.5 + 0.55 * row) - - -- get the global clue position (higher y-position for each set) - local cluePos = card.positionToWorld(localPos) + Vector(0, 0.03 + 0.103 * (set - 1), 0) - - -- add position to table - table.insert(cluePositions, cluePos) - end - return cluePositions - end - - ---@param card tts__Object Card object to be replenished - ---@param useInfo table The already decoded subtable of metadata.uses (to avoid decoding again) - internal.replenishTokens = function(card, useInfo) - -- get current amount of matching resource tokens on the card - local clickableResourceCounter = nil - local foundTokens = 0 - - local maybeDeleteThese = {} - if useInfo.token == "clue" then - for _, obj in ipairs(searchLib.onObject(card, "isClue")) do - foundTokens = foundTokens + math.abs(obj.getQuantity()) - table.insert(maybeDeleteThese, obj) - end - elseif useInfo.token == "doom" then - for _, obj in ipairs(searchLib.onObject(card, "isDoom")) do - foundTokens = foundTokens + math.abs(obj.getQuantity()) - table.insert(maybeDeleteThese, obj) - end - else - -- search for the token instead if there's no special resource state for it - local searchType = string.lower(useInfo.type) - if stateTable[searchType] == nil then - searchType = useInfo.token - end - - for _, obj in ipairs(searchLib.onObject(card, "isTileOrToken")) do - local memo = obj.getMemo() - if searchType == memo then - foundTokens = foundTokens + math.abs(obj.getQuantity()) - table.insert(maybeDeleteThese, obj) - elseif memo == "resourceCounter" then - foundTokens = obj.getVar("val") - clickableResourceCounter = obj - break - end - end - end - - -- this is the theoretical new amount of uses (to be checked below) - local newCount = foundTokens + useInfo.replenish - - -- if there are already more uses than the replenish amount, keep them - if foundTokens > useInfo.count then - newCount = foundTokens - -- only replenish up until the replenish amount - elseif newCount > useInfo.count then - newCount = useInfo.count - end - - -- update the clickable counter or spawn a group of tokens - if clickableResourceCounter then - clickableResourceCounter.call("updateVal", newCount) - else - -- delete existing tokens - for _, obj in ipairs(maybeDeleteThese) do - obj.destruct() - end - - -- spawn new token group - TokenManager.spawnTokenGroup(card, useInfo.token, newCount, _, useInfo.type) - end - end - - return TokenManager -end diff --git a/src/core/token/TokenManagerApi.ttslua b/src/core/token/TokenManagerApi.ttslua new file mode 100644 index 00000000..abd1f424 --- /dev/null +++ b/src/core/token/TokenManagerApi.ttslua @@ -0,0 +1,100 @@ +do + local TokenManagerApi = {} + + -- Pushes new location data into the local copy of the Data Helper location data. + ---@param dataTable table Key/Value pairs following the DataHelper style + function TokenManagerApi.addLocationData(dataTable) + Global.call("callTable", { + { "TokenManager", "addLocationData" }, + dataTable + }) + end + + -- Checks to see if the given card has location data in the DataHelper + ---@param card tts__Object Card to check for data + ---@return boolean: True if this card has data in the helper, false otherwise + function TokenManagerApi.hasLocationData(card) + Global.call("callTable", { + { "TokenManager", "hasLocationData" }, + card + }) + end + + -- Pushes new player card data into the local copy of the Data Helper player data. + ---@param dataTable table Key/Value pairs following the DataHelper style + function TokenManagerApi.addPlayerCardData(dataTable) + Global.call("callTable", { + { "TokenManager", "addPlayerCardData" }, + dataTable + }) + end + + -- Spawns tokens for the card. This function is built to just throw a card at it and let it do + -- the work once a card has hit an area where it might spawn tokens. It will check to see if + -- the card has already spawned, find appropriate data from either the uses metadata or the Data + -- Helper, and spawn the tokens. + ---@param card tts__Object Card to maybe spawn tokens for + ---@param extraUses table A table of = which will modify the number of tokens + --- spawned for that type. e.g. Akachi's playermat should pass "Charge"=1 + function TokenManagerApi.spawnForCard(card, extraUses) + Global.call("callTable", { + { "TokenManager", "spawnForCard" }, + { card = card, extraUses = extraUses } + }) + end + + -- Spawns a single token at the given global position by copying it from the template bag. + ---@param position tts__Vector Global position to spawn the token + ---@param tokenType string Type of token to spawn (template needs to be in source bag) + ---@param rotation tts__Vector Rotation to be used for the new token. Only the y-value will be used, + -- x and z will use the default rotation from the source bag + ---@param callbackName? string Name of the callback function (in Global) + ---@param callbackParams? any Parameters for the callback function + function TokenManagerApi.spawnToken(position, tokenType, rotation, callbackName, callbackParams) + Global.call("callTable", { + { "TokenManager", "spawnToken" }, + { + position = position, + rotation = rotation, + tokenType = tokenType, + callbackName = callbackName, + callbackParams = callbackParams + } + }) + end + + -- Spawns a set of tokens on the given card. + ---@param card tts__Object Card to spawn tokens on + ---@param tokenType string Type of token to spawn (template needs to be in source bag) + ---@param tokenCount number How many tokens to spawn. For damage or horror this value will be set to the + -- spawned state object rather than spawning multiple tokens + ---@param shiftDown? number An offset for the z-value of this group of tokens + ---@param subType? string Subtype of token to spawn. This will only differ from the tokenName for resource tokens + function TokenManagerApi.spawnTokenGroup(card, tokenType, tokenCount, shiftDown, subType) + Global.call("callTable", { + { "TokenManager", "spawnTokenGroup" }, + { + card = card, + tokenType = tokenType, + tokenCount = tokenCount, + shiftDown = shiftDown, + subType = subType + } + }) + end + + -- Checks a card for metadata to maybe replenish it + ---@param card tts__Object Card object to be replenished + ---@param uses table The already decoded metadata.uses (to avoid decoding again) + function TokenManagerApi.maybeReplenishCard(card, uses) + Global.call("callTable", { + { "TokenManager", "maybeReplenishCard" }, + { + card = card, + uses = uses + } + }) + end + + return TokenManagerApi +end diff --git a/src/playercards/AdditionalPlayerCards.ttslua b/src/playercards/AdditionalPlayerCards.ttslua new file mode 100644 index 00000000..a284450a --- /dev/null +++ b/src/playercards/AdditionalPlayerCards.ttslua @@ -0,0 +1,30 @@ +local allCardsBagApi = require("playercards/AllCardsBagApi") +local waitId + +function onLoad() + allCardsBagApi.rebuildIndexForHotfix() + self.addContextMenuItem("Update card index", requestUpdate) +end + +function onObjectEnterContainer(container) + if container ~= self then return end + delayedIndexUpdate() +end + +function onObjectLeaveContainer(container) + if container ~= self then return end + delayedIndexUpdate() +end + +-- updates the index if there weren't changes for a specified amount of time +function delayedIndexUpdate() + if waitId then + Wait.stop(waitId) + end + waitId = Wait.time(requestUpdate, 2) +end + +function requestUpdate() + allCardsBagApi.rebuildIndexForHotfix() + printToAll("'AllCardsBag' triggered a rebuild of the card index.") +end diff --git a/src/playercards/AllCardsBag.ttslua b/src/playercards/AllCardsBag.ttslua index 9e6d6f50..5e30184f 100644 --- a/src/playercards/AllCardsBag.ttslua +++ b/src/playercards/AllCardsBag.ttslua @@ -1,13 +1,47 @@ -local guidReferenceApi = require("core/GUIDReferenceApi") +local guidReferenceApi = require("core/GUIDReferenceApi") -local cardIdIndex = {} -local classAndLevelIndex = {} -local basicWeaknessList = {} -local uniqueWeaknessList = {} -local cycleIndex = {} +-- card index and sub-indices +local cardIdIndex = {} +local classAndLevelIndex = {} +local customInvestigatorData = {} +local customSignatureDict = {} +local basicWeaknessList = {} +local uniqueWeaknessList = {} +local cycleIndex = {} -local indexingDone = false -local otherCardsDetected = false +local indexingDone = false +local otherCardsDetected = false + +-- helper data for class sorting +local classValueList = { + Guardian = 1, + Seeker = 2, + Rogue = 3, + Mystic = 4, + Survivor = 5, + Neutral = 99998, + Mythos = 99999 +} +local classesInOrder = { + "Guardian", + "Seeker", + "Rogue", + "Mystic", + "Survivor", + "Neutral" +} + +-- conversion tables to simplify type sorting +local typeConversion = { + Investigator = 1, + Minicard = 2, + Asset = 3, + Event = 4, + Skill = 5, + Enemy = 6, + Treachery = 7, + Location = 8 +} function onLoad() self.addContextMenuItem("Rebuild Index", startIndexBuild) @@ -28,24 +62,14 @@ end -- Resets all current bag indexes function clearIndexes() - indexingDone = false - cardIdIndex = {} - classAndLevelIndex = {} - classAndLevelIndex["Guardian-upgrade"] = {} - classAndLevelIndex["Seeker-upgrade"] = {} - classAndLevelIndex["Mystic-upgrade"] = {} - classAndLevelIndex["Survivor-upgrade"] = {} - classAndLevelIndex["Rogue-upgrade"] = {} - classAndLevelIndex["Neutral-upgrade"] = {} - classAndLevelIndex["Guardian-level0"] = {} - classAndLevelIndex["Seeker-level0"] = {} - classAndLevelIndex["Mystic-level0"] = {} - classAndLevelIndex["Survivor-level0"] = {} - classAndLevelIndex["Rogue-level0"] = {} - classAndLevelIndex["Neutral-level0"] = {} - cycleIndex = {} - basicWeaknessList = {} - uniqueWeaknessList = {} + indexingDone = false + cardIdIndex = {} + classAndLevelIndex = {} + cycleIndex = {} + customInvestigatorData = {} + customSignatureDict = {} + basicWeaknessList = {} + uniqueWeaknessList = {} end -- Clears the bag indexes and starts the coroutine to rebuild the indexes @@ -64,59 +88,68 @@ end -- and creating the keyed lookup tables for the cards. This is a coroutine which will -- spread the workload by processing 20 cards before yielding. function buildIndex() - local cardCount = 0 + cardCount = 0 indexingDone = false otherCardsDetected = false -- process the allcardsbag itself - for _, cardData in ipairs(self.getData().ContainedObjects) do - addCardToIndex(cardData) - cardCount = cardCount + 1 - if cardCount > 19 then - cardCount = 0 - coroutine.yield(0) - end + local selfData = self.getData() + if selfData.ContainedObjects then + processContainedObjects(selfData.ContainedObjects) end -- process hotfix bags (and the additional playercards bag) for _, hotfixBag in ipairs(getObjectsWithTag("AllCardsHotfix")) do local hotfixData = hotfixBag.getData() - - -- if the bag is empty, continue with the next bag - if not hotfixData.ContainedObjects then - goto nextBag + if hotfixData.ContainedObjects then + processContainedObjects(hotfixData.ContainedObjects, hotfixData.CustomDeck) end - - for _, cardData in ipairs(hotfixData.ContainedObjects) do - if cardData.ContainedObjects then - -- process containers - for _, deepCardData in ipairs(cardData.ContainedObjects) do - addCardToIndex(deepCardData) - cardCount = cardCount + 1 - if cardCount > 19 then - cardCount = 0 - coroutine.yield(0) - end - end - else - -- process single cards - addCardToIndex(cardData) - cardCount = cardCount + 1 - if cardCount > 19 then - cardCount = 0 - coroutine.yield(0) - end - end - end - ::nextBag:: end buildSupplementalIndexes() + collectSortingData() + sortIndexes() updatePlayerCardPanel() indexingDone = true return 1 end +-- Processes the contained objects for cards to add to the index +function processContainedObjects(containedObjects, customDeck) + for _, objData in ipairs(containedObjects) do + if objData.ContainedObjects then + -- recursively process nested containers + processContainedObjects(objData.ContainedObjects, objData.CustomDeck) + elseif objData.Name == "Card" or objData.Name == "CardCustom" then + if customDeck then + -- we might need to fix the "CustomDeck" entry for cards inside decks since TTS doesn't update it while they are in bags + local wantedCustomDeckIdStr = tostring(objData.CardID):sub(1, -3) + local presentCustomDeckIdNum = next(objData.CustomDeck) + + -- type conversion (TTS seems to store these as strings, but reads them as numbers) + local wantedCustomDeckIdNum = tonumber(wantedCustomDeckIdStr) + if wantedCustomDeckIdNum ~= presentCustomDeckIdNum then + if customDeck[wantedCustomDeckIdNum] then + objData.CustomDeck = {} + objData.CustomDeck[wantedCustomDeckIdStr] = customDeck[wantedCustomDeckIdNum] + log("Corrected CustomDeckData for " .. objData.Nickname) + else + log("Could not correct CustomDeckData for " .. objData.Nickname) + return + end + end + end + + addCardToIndex(objData) + cardCount = cardCount + 1 + if cardCount > 19 then + cardCount = 0 + coroutine.yield(0) + end + end + end +end + -- Adds a card to any indexes it should be a part of, based on its metadata ---@param cardData table TTS object data for the card function addCardToIndex(cardData) @@ -130,19 +163,20 @@ function addCardToIndex(cardData) end -- if metadata was not valid JSON or empty, don't add the card - if not cardMetadata == nil then + if not cardMetadata then log("Error parsing " .. cardData.Nickname) return end -- use the ZoopGuid as fallback if no id present cardMetadata.id = cardMetadata.id or cardMetadata.TtsZoopGuid - cardIdIndex[cardMetadata.id] = { data = cardData, metadata = cardMetadata } + local indexData = { data = cardData, metadata = cardMetadata } + cardIdIndex[cardMetadata.id] = indexData -- also add data for alternate ids - if cardMetadata.alternate_ids ~= nil then + if cardMetadata.alternate_ids then for _, alternateId in ipairs(cardMetadata.alternate_ids) do - cardIdIndex[alternateId] = { data = cardData, metadata = cardMetadata } + cardIdIndex[alternateId] = indexData end end end @@ -154,16 +188,16 @@ function buildSupplementalIndexes() if cardId == card.metadata.id then -- Add card to the basic weakness list, if appropriate. Some weaknesses have multiple copies, and are added multiple times if card.metadata.weakness then - table.insert(uniqueWeaknessList, card.metadata.id) + table.insert(uniqueWeaknessList, cardId) if card.metadata.basicWeaknessCount ~= nil then for i = 1, card.metadata.basicWeaknessCount do - table.insert(basicWeaknessList, card.metadata.id) + table.insert(basicWeaknessList, cardId) end end end - -- Excludes signature cards (which have no class or level) - if card.metadata.class ~= nil and card.metadata.level ~= nil then + -- Excludes signature cards and story assets for official cycles (which have no class or level) + if card.metadata.class and card.metadata.level then local upgradeKey = "-level0" if card.metadata.level > 0 then upgradeKey = "-upgrade" @@ -171,37 +205,101 @@ function buildSupplementalIndexes() -- parse classes (separated by "|") and add the card to the appropriate class and level indices for str in card.metadata.class:gmatch("([^|]+)") do - table.insert(classAndLevelIndex[str .. upgradeKey], card.metadata.id) + writeToNestedTable(classAndLevelIndex, str .. upgradeKey, cardId) end - - -- add to cycle index - local cycleName = card.metadata.cycle - if cycleName ~= nil then - cycleName = string.lower(cycleName) - - -- remove "return to " from cycle names - cycleName = cycleName:gsub("return to ", "") - - -- override cycle name for night of the zealot - cycleName = cycleName:gsub("the night of the zealot", "core") - else - -- track cards without defined cycle (should only be fan-made cards) - cycleName = "other" - otherCardsDetected = true - end - - -- maybe initialize table - if cycleIndex[cycleName] == nil then - cycleIndex[cycleName] = {} - end - table.insert(cycleIndex[cycleName], card.metadata.id) end + + -- add to cycle index + local cycleName = card.metadata.cycle + + -- if this is a minicard without cycle, check the parent card for cycle data + if not cycleName and card.metadata.type == "Minicard" then + local parentId = getParentId(cardId) + local parent = cardIdIndex[parentId] + if parent and parent.metadata.cycle then + cycleName = parent.metadata.cycle + end + end + + if cycleName then + cycleName = string.lower(cycleName) + + -- remove "return to " from cycle names + cycleName = cycleName:gsub("return to ", "") + + -- override cycle name for night of the zealot + cycleName = cycleName:gsub("the night of the zealot", "core") + else + -- track cards without defined cycle (should only be fan-made cards) + cycleName = "other" + otherCardsDetected = true + + -- maybe add to special investigator / minicard index + if card.metadata.type == "Investigator" then + writeToNestedTable(customInvestigatorData, "InvestigatorGroup", cardId) + writeToNestedTable(customInvestigatorData, "InvestigatorSubdata", cardId, "cards", cardId) + + -- read the signatures + if card.metadata.signatures and type(card.metadata.signatures) == "table" then + for sigId, sigCount in pairs(card.metadata.signatures[1] or {}) do + customSignatureDict[sigId] = true + for i = 1, sigCount do + writeToNestedTable(customInvestigatorData, "InvestigatorSubdata", cardId, "signatures", sigId) + end + end + end + elseif card.metadata.type == "Minicard" then + local parentId = getParentId(cardId) + writeToNestedTable(customInvestigatorData, "InvestigatorSubdata", parentId, "minicards", cardId) + end + end + + -- maybe initialize table + writeToNestedTable(cycleIndex, cycleName, cardId) + end + end +end + +-- collect the sorting data and cache it +function collectSortingData() + for _, card in pairs(cardIdIndex) do + card.sortingData = { + class = getClassValue(card.metadata.class), + cardType = typeConversion[card.metadata.type] or 99, + level = card.metadata.level or 6, + name = card.data.Nickname + } + end +end + +-- generalized comparison function for table.sort() with customizable criteria +function generalizedCardComparator(id1, id2, criteria) + local card1 = cardIdIndex[id1] + local card2 = cardIdIndex[id2] + + for _, key in ipairs(criteria) do + if card1.sortingData[key] ~= card2.sortingData[key] then + return card1.sortingData[key] < card2.sortingData[key] end end + return id1 < id2 +end + +-- sorts by level and then name +function generalSortFunction(id1, id2) + return generalizedCardComparator(id1, id2, { "level", "name" }) +end + +-- sort by class, cardType, level and then name +function metadataSortFunction(id1, id2) + return generalizedCardComparator(id1, id2, { "class", "cardType", "level", "name" }) +end + +function sortIndexes() -- sort class and level indices for _, indexTable in pairs(classAndLevelIndex) do - table.sort(indexTable, cardComparator) + table.sort(indexTable, generalSortFunction) end -- sort cycle indices @@ -210,22 +308,33 @@ function buildSupplementalIndexes() end -- sort weakness indices - table.sort(basicWeaknessList, cardComparator) - table.sort(uniqueWeaknessList, cardComparator) + table.sort(basicWeaknessList) + table.sort(uniqueWeaknessList) + + -- sort custom investigator / minicard data + for _, indexTable in pairs(customInvestigatorData) do + table.sort(indexTable, metadataSortFunction) + end end --- Comparison function used to sort the class card bag indexes. Sorts by card level, then name, then subname. -function cardComparator(id1, id2) - local card1 = cardIdIndex[id1] - local card2 = cardIdIndex[id2] +-- get the parent id for minicards by removing the last two characters +function getParentId(miniId) + return string.sub(miniId, 1, -3) +end - if card1.metadata.level ~= card2.metadata.level then - return card1.metadata.level < card2.metadata.level - elseif card1.data.Nickname ~= card2.data.Nickname then - return card1.data.Nickname < card2.data.Nickname - else - return card1.data.Description < card2.data.Description +-- helper function to ensure nested tables are initialized - last argument will be used as table element +function writeToNestedTable(rootTable, ...) + local args = { ... } + local currentTable = rootTable + + for i = 1, #args - 1 do + local key = args[i] + if not currentTable[key] then + currentTable[key] = {} + end + currentTable = currentTable[key] end + table.insert(currentTable, args[#args]) end -- inform the player card panel about the presence of other cards (no cycle -> fan-made) @@ -273,8 +382,9 @@ end -- Returns a list of cards from the bag matching a cycle ---@param params table --- cycle: String cycle to retrieve ("The Scarlet Keys" etc.) --- sortByMetadata: true to sort the table by metadata instead of ID +-- cycle: string Name of the cycle to retrieve ("The Scarlet Keys" etc.) +-- sortByMetadata: boolean If true, sorts the table by metadata instead of ID +-- includeNoLevelCards: boolean If true, includes cards without level ---@return table: If the indexes are still being constructed, returns an empty table. -- Otherwise, a list of tables, each with the following fields -- data: TTS object data, suitable for spawning the card @@ -282,65 +392,57 @@ end function getCardsByCycle(params) if not isIndexReady() then return {} end - if not params.sortByMetadata then - return cycleIndex[string.lower(params.cycle)] + local cycleData = cycleIndex[string.lower(params.cycle)] or {} + + -- create a copy of the data to not change the source + local cardList = {} + for _, id in ipairs(cycleData) do + local md = cardIdIndex[id].metadata + -- only include cards without level if requested (don't include investigators / minicards / signatures though) + if (md.level or params.includeNoLevelCards) and md.type ~= "Investigator" and md.type ~= "Minicard" and customSignatureDict[id] ~= true then + table.insert(cardList, id) + end end -- sort list by metadata (useful for custom cards without proper IDs) - local cardList = {} - for _, id in ipairs(cycleIndex[string.lower(params.cycle)]) do - table.insert(cardList, id) + if params.sortByMetadata and #cardList > 0 then + table.sort(cardList, metadataSortFunction) end - - table.sort(cardList, metadataSortFunction) return cardList end --- sorts cards by metadata: class, type, level, name and then description -function metadataSortFunction(id1, id2) - local card1 = cardIdIndex[id1] - local card2 = cardIdIndex[id2] +-- helper function to calculate the class value for sorting from the "|" separated string +function getClassValue(s) + -- make sure cards without class (e.g. weaknesses) get sorted last + if not s then return 99999 end - -- extract class per card - local classValue1 = getClassValueFromString(card1.metadata.class) - local classValue2 = getClassValueFromString(card2.metadata.class) - - -- conversion tables to simplify type sorting - local typeConversion = { - Asset = 1, - Event = 2, - Skill = 3 - } - - if classValue1 ~= classValue2 then - return classValue1 < classValue2 - elseif typeConversion[card1.metadata.type] ~= typeConversion[card2.metadata.type] then - return typeConversion[card1.metadata.type] < typeConversion[card2.metadata.type] - elseif card1.metadata.level ~= card2.metadata.level then - return card1.metadata.level < card2.metadata.level - elseif card1.data.Nickname ~= card2.data.Nickname then - return card1.data.Nickname < card2.data.Nickname - else - return card1.data.Description < card2.data.Description + -- split the "|" separated string and remove whitespace + local splitClasses = {} + for str in s:gmatch("([^|]+)") do + str = str:gsub("%s", "") + splitClasses[str] = true end + + -- sort multiclass cards (outputs 12 for "Guardian|Seeker") + local classValue = 0 + local multiplier = 1 + for _, class in pairs(classesInOrder) do + if splitClasses[class] then + classValue = classValue + classValueList[class] * multiplier + multiplier = multiplier * 10 + end + end + + return classValue end --- helper function to calculate the class value for sorting from the "|" separated string -function getClassValueFromString(s) - local classValueList = { - Guardian = 1, - Seeker = 2, - Rogue = 3, - Mystic = 4, - Survivor = 5, - Neutral = 6 +function getCustomInvestigatorData() + if not isIndexReady() then return {} end + + return { + InvestigatorGroup = customInvestigatorData.InvestigatorGroup or {}, + InvestigatorSubdata = customInvestigatorData.InvestigatorSubdata or {} } - local classValue = 0 - for str in s:gmatch("([^|]+)") do - -- this sorts multiclass cards - classValue = classValue * 10 + classValueList[str] - end - return classValue end -- Searches the bag for cards which match the given name and returns a list. Note that this is diff --git a/src/playercards/AllCardsBagApi.ttslua b/src/playercards/AllCardsBagApi.ttslua index 2a69145b..42934024 100644 --- a/src/playercards/AllCardsBagApi.ttslua +++ b/src/playercards/AllCardsBagApi.ttslua @@ -7,10 +7,15 @@ do end -- internal function to create a copy of the table to avoid operating on variables owned by different objects - local function returnCopyOfList(data) + local function deepCopy(data) + if type(data) ~= "table" then return data end local copiedList = {} - for _, id in ipairs(data) do - table.insert(copiedList, id) + for key, value in pairs(data) do + if type(value) == "table" then + copiedList[key] = deepCopy(value) + else + copiedList[key] = value + end end return copiedList end @@ -35,7 +40,7 @@ do --- traits? string Trait(s) to use as filter ---@return table: Table with IDs of the selected weaknesses AllCardsBagApi.getRandomWeaknessIds = function(count, restrictions) - return returnCopyOfList(getAllCardsBag().call("getRandomWeaknessIds", {count = count, restrictions = restrictions})) + return deepCopy(getAllCardsBag().call("getRandomWeaknessIds", {count = count, restrictions = restrictions})) end AllCardsBagApi.isIndexReady = function() @@ -56,7 +61,7 @@ do ---@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 returnCopyOfList(getAllCardsBag().call("getCardsByName", { name = name, exact = exact })) + return deepCopy(getAllCardsBag().call("getCardsByName", { name = name, exact = exact })) end AllCardsBagApi.isBagPresent = function() @@ -71,18 +76,28 @@ do -- data: TTS object data, suitable for spawning the card -- metadata: Table of parsed metadata AllCardsBagApi.getCardsByClassAndLevel = function(class, upgraded) - return returnCopyOfList(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded })) + return deepCopy(getAllCardsBag().call("getCardsByClassAndLevel", { class = class, upgraded = upgraded })) end -- Returns a list of cards from the bag matching a cycle ---@param cycle string Cycle to retrieve ("The Scarlet Keys" etc.) ---@param sortByMetadata boolean If true, sorts the table by metadata instead of ID + ---@param includeNoLevelCards boolean If true, includes cards without level ---@return table: If the indexes are still being constructed, returns an empty table. -- Otherwise, a list of tables, each with the following fields -- data: TTS object data, suitable for spawning the card -- metadata: Table of parsed metadata - AllCardsBagApi.getCardsByCycle = function(cycle, sortByMetadata) - return returnCopyOfList(getAllCardsBag().call("getCardsByCycle", { cycle = cycle, sortByMetadata = sortByMetadata })) + AllCardsBagApi.getCardsByCycle = function(cycle, sortByMetadata, includeNoLevelCards) + return deepCopy(getAllCardsBag().call("getCardsByCycle", { + cycle = cycle, + sortByMetadata = sortByMetadata, + includeNoLevelCards = includeNoLevelCards + })) + end + + -- Returns the data for custom investigators for the PlayerCardPanel + AllCardsBagApi.getCustomInvestigatorData = function() + return deepCopy(getAllCardsBag().call("getCustomInvestigatorData")) end -- Constructs a list of available basic weaknesses by starting with the full pool of basic @@ -93,11 +108,11 @@ do --- traits? string Trait(s) to use as filter ---@return table: Array of weakness IDs which are valid to choose from AllCardsBagApi.buildAvailableWeaknesses = function(restrictions) - return returnCopyOfList(getAllCardsBag().call("buildAvailableWeaknesses", restrictions)) + return deepCopy(getAllCardsBag().call("buildAvailableWeaknesses", restrictions)) end AllCardsBagApi.getUniqueWeaknesses = function() - return returnCopyOfList(getAllCardsBag().call("getUniqueWeaknesses")) + return deepCopy(getAllCardsBag().call("getUniqueWeaknesses")) end return AllCardsBagApi diff --git a/src/playercards/CardsThatRedrawTokens.ttslua b/src/playercards/CardsThatRedrawTokens.ttslua index 5f7c8813..7ddf0078 100644 --- a/src/playercards/CardsThatRedrawTokens.ttslua +++ b/src/playercards/CardsThatRedrawTokens.ttslua @@ -51,6 +51,8 @@ As a nice reminder the XML button takes on the Frost color and icon with the tex > require... ----------------------------------------------------------]] +local chaosBagApi = require("chaosBag/ChaosBagApi") + -- intentionally global hasXML = true isHelperEnabled = false @@ -97,9 +99,5 @@ function createHelperXML() end function triggerXMLTokenLabelCreation() - Global.call("activeRedrawEffect", { - VALID_TOKENS = VALID_TOKENS, - INVALID_TOKENS = INVALID_TOKENS, - RETURN_TO_POOL = RETURN_TO_POOL - }) + chaosBagApi.activeRedrawEffect(VALID_TOKENS, INVALID_TOKENS, RETURN_TO_POOL) end diff --git a/src/playercards/CardsWithHelper.ttslua b/src/playercards/CardsWithHelper.ttslua index dc67ee1b..5b1ea3c0 100644 --- a/src/playercards/CardsWithHelper.ttslua +++ b/src/playercards/CardsWithHelper.ttslua @@ -13,12 +13,12 @@ isHelperEnabled = false (default state of the helper, should be 'false') 2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()' ----------------------------------------------------------]] -local optionPanelApi = require("core/OptionPanelApi") +local GlobalApi = require("core/GlobalApi") -- if the respective option is enabled in onLoad(), enable the helper function syncDisplayWithOptionPanel() self.addTag("CardWithHelper") - local options = optionPanelApi.getOptions() + local options = GlobalApi.getOptionPanelState() if options.enableCardHelpers then setHelperState(true) else diff --git a/src/playercards/PlayerCardPanel.ttslua b/src/playercards/PlayerCardPanel.ttslua index c3ee6bc4..ca536cbf 100644 --- a/src/playercards/PlayerCardPanel.ttslua +++ b/src/playercards/PlayerCardPanel.ttslua @@ -44,11 +44,11 @@ local CARD_WIDTH = 2.3 -- IMPORTANT! Because of the mix of global card sizes and relative-to-scale positions, the X and Y -- coordinates on these provide global disances while the Z is local. local START_POSITIONS = { - classCards = Vector(CARD_WIDTH * 9.5, 2, 1.4), - investigator = Vector(6 * 2.5, 2, 1.3), - cycle = Vector(CARD_WIDTH * 9.5, 2, 2.4), - other = Vector(CARD_WIDTH * 9.5, 2, 1.4), - randomWeakness = Vector(0, 2, 1.4), + classCards = Vector(CARD_WIDTH * 9.5, 2, 1.4), + investigator = Vector(6 * 2.5, 2, 1.3), + cycle = Vector(CARD_WIDTH * 9.5, 2, 2.4), + other = Vector(CARD_WIDTH * 9.5, 2, 1.4), + randomWeakness = Vector(0, 2, 1.4), -- Because the card spread is handled by the SpawnBag, we don't know (programatically) where this -- should be placed. If more customizable cards are added it will need to be moved. summonedServitor = Vector(CARD_WIDTH * -7.5, 2, 1.7) @@ -57,10 +57,12 @@ local START_POSITIONS = { -- Shifts to move rows of cards, and groups of rows, as different groupings are laid out local CARD_ROW_OFFSET = 3.7 local CARD_GROUP_OFFSET = 2 +local CARD_MAX_ROWS = 15 +local CARD_MAX_COLS = 20 -- Position offsets for investigator decks in investigator mode, defines the spacing for how the -- rows and columns are laid out -local INVESTIGATOR_POSITION_SHIFT_ROW = Vector(0, 0, 11) +local INVESTIGATOR_POSITION_SHIFT_ROW = Vector(0, 0, 10) local INVESTIGATOR_POSITION_SHIFT_COL = Vector(-6, 0, 0) local INVESTIGATOR_MAX_COLS = 6 @@ -173,7 +175,7 @@ function createInvestigatorButtons() invButtonParams.position = buttonPos self.createButton(invButtonParams) buttonPos.x = buttonPos.x + CLASS_BUTTONS_X_OFFSET - self.setVar(invButtonParams.click_function, function(_, _, _) spawnInvestigatorGroup(class) end) + self.setVar(invButtonParams.click_function, function() spawnInvestigatorGroup(class) end) end end @@ -336,6 +338,7 @@ function createInvestigatorModeButtons() }) end +-- this will be called by the AllCardsBag after indexing function createXML(showOtherCardsButton) -- basic XML for the help button local xmlTable = { @@ -377,7 +380,7 @@ function createXML(showOtherCardsButton) local otherCardsButtonXml = { tag = "Panel", attributes = { - position = "44.25 65.75 -11", + position = "44.25 65.5 -11", rotation = "0 0 180", height = "225", width = "225", @@ -457,16 +460,14 @@ function scalePositions() for key, pos in pairs(START_POSITIONS) do -- Because a scaled object means a different global size, using global distance for Z results in -- the cards being closer or farther depending on the scale. Leave the Z values and only scale X and Y - startPositions[key] = Vector(pos) - startPositions[key].x = startPositions[key].x * scale - startPositions[key].y = startPositions[key].y * scale + startPositions[key] = Vector(pos):scale(Vector(scale, scale, 1)) end - cardRowOffset = CARD_ROW_OFFSET * scale - cardGroupOffset = CARD_GROUP_OFFSET * scale + cardRowOffset = CARD_ROW_OFFSET * scale + cardGroupOffset = CARD_GROUP_OFFSET * scale investigatorPositionShiftRow = Vector(INVESTIGATOR_POSITION_SHIFT_ROW):scale(scale) investigatorPositionShiftCol = Vector(INVESTIGATOR_POSITION_SHIFT_COL):scale(scale) - investigatorCardOffset = Vector(INVESTIGATOR_CARD_OFFSET):scale(scale) - investigatorSignatureOffset = Vector(INVESTIGATOR_SIGNATURE_OFFSET):scale(scale) + investigatorCardOffset = Vector(INVESTIGATOR_CARD_OFFSET):scale(scale) + investigatorSignatureOffset = Vector(INVESTIGATOR_SIGNATURE_OFFSET):scale(scale) end -- Deletes all cards currently placed on the table @@ -491,41 +492,50 @@ end -- investigator cards and minicards as well as the signature cards. ---@param groupName string Name of the group to spawn, matching a key in InvestigatorPanelData function spawnInvestigators(groupName) - if INVESTIGATOR_GROUPS[groupName] == nil then - printToAll("No investigator data for " .. groupName .. " yet") + local mainData = INVESTIGATOR_GROUPS[groupName] + local subData = INVESTIGATORS + + -- handling for custom investigators + if groupName == "Other" then + local customData = allCardsBagApi.getCustomInvestigatorData() + mainData = customData.InvestigatorGroup + subData = customData.InvestigatorSubdata + end + + if mainData == nil or subData == nil then + printToAll("No investigator data for '" .. groupName .. "' yet") return end - local col = 1 + local col = 0 local row = 1 - local investigatorCount = #INVESTIGATOR_GROUPS[groupName] + local investigatorCount = #mainData local position = getInvestigatorRowStartPos(investigatorCount, row) - for _, investigatorName in ipairs(INVESTIGATOR_GROUPS[groupName]) do - for _, spawnSpec in ipairs(buildInvestigatorSpawnSpec(investigatorName, INVESTIGATORS[investigatorName], position)) do - spawnBag.spawn(spawnSpec) - end - position:add(investigatorPositionShiftCol) + for _, investigatorName in ipairs(mainData) do col = col + 1 if col > INVESTIGATOR_MAX_COLS then - col = 1 + col = 0 row = row + 1 position = getInvestigatorRowStartPos(investigatorCount, row) end + + for _, spawnSpec in ipairs(buildInvestigatorSpawnSpec(investigatorName, subData[investigatorName], position)) do + spawnBag.spawn(spawnSpec) + end + position:add(investigatorPositionShiftCol) end + return row end function getInvestigatorRowStartPos(investigatorCount, row) - local rowStart = Vector(startPositions.investigator) - rowStart:add(Vector( - investigatorPositionShiftRow.x * (row - 1), - investigatorPositionShiftRow.y * (row - 1), - investigatorPositionShiftRow.z * (row - 1))) + local rowOffset = Vector(investigatorPositionShiftRow):scale(row - 1) local investigatorsInRow = math.min(investigatorCount - INVESTIGATOR_MAX_COLS * (row - 1), INVESTIGATOR_MAX_COLS) - rowStart:add(Vector( - investigatorPositionShiftCol.x * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2, - investigatorPositionShiftCol.y * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2, - investigatorPositionShiftCol.z * (INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2)) + local colOffset = Vector(investigatorPositionShiftCol):scale((INVESTIGATOR_MAX_COLS - investigatorsInRow) / 2) + + local rowStart = Vector(startPositions.investigator) + rowStart:add(rowOffset) + rowStart:add(colOffset) return rowStart end @@ -542,7 +552,6 @@ function buildInvestigatorSpawnSpec(investigatorName, investigatorData, position globalPos = self.positionToWorld(sigPos), rotation = FACE_UP_ROTATION }) - return spawns end @@ -657,9 +666,9 @@ function placeClassCards(cardClass, isUpgraded) globalPos = self.positionToWorld(groupPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) - groupPos.z = groupPos.z + math.ceil(#skillList / 20) * cardRowOffset + cardGroupOffset + groupPos.z = groupPos.z + math.ceil(#skillList / CARD_MAX_COLS) * cardRowOffset + cardGroupOffset end if #eventList > 0 then spawnBag.spawn({ @@ -668,9 +677,9 @@ function placeClassCards(cardClass, isUpgraded) globalPos = self.positionToWorld(groupPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) - groupPos.z = groupPos.z + math.ceil(#eventList / 20) * cardRowOffset + cardGroupOffset + groupPos.z = groupPos.z + math.ceil(#eventList / CARD_MAX_COLS) * cardRowOffset + cardGroupOffset end if #assetList > 0 then spawnBag.spawn({ @@ -679,7 +688,7 @@ function placeClassCards(cardClass, isUpgraded) globalPos = self.positionToWorld(groupPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) end end @@ -689,22 +698,36 @@ end function spawnCycle(cycle) if not allCardsBagApi.isIndexReady() then return end - prepareToPlaceCards() - spawnInvestigators(cycle) - - -- sort custom cards + -- sort custom cards and include cards without level local sortByMetadata = false + local includeNoLevelCards = false if cycle == "Other" then sortByMetadata = true + includeNoLevelCards = true + end + local cardList = allCardsBagApi.getCardsByCycle(cycle, sortByMetadata, includeNoLevelCards) + + prepareToPlaceCards() + local rowCount = spawnInvestigators(cycle) + + -- need to shift the start position for cycle down if there are multiple rows of investigators + local rowOffset = Vector((rowCount - 1) * investigatorPositionShiftRow) + local cycleStartPos = Vector(startPositions.cycle):add(rowOffset) + + -- potentially don't spawn all cards if there are too many + local maxCardCount = (CARD_MAX_ROWS - rowCount * 3) * CARD_MAX_COLS + if #cardList > maxCardCount then + printToAll("Only spawning the first " .. maxCardCount .. " cards from " .. #cardList .. " total cards.") + cardList = table.pack(table.unpack(cardList, 1, maxCardCount)) end spawnBag.spawn({ name = "cycle" .. cycle, - cards = allCardsBagApi.getCardsByCycle(cycle, sortByMetadata), - globalPos = self.positionToWorld(startPositions.cycle), + cards = cardList, + globalPos = self.positionToWorld(cycleStartPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) end @@ -716,7 +739,7 @@ function spawnBonded() globalPos = self.positionToWorld(startPositions.classCards), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) end @@ -728,7 +751,7 @@ function spawnUpgradeSheets() globalPos = self.positionToWorld(startPositions.classCards), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) spawnBag.spawn({ name = "servitor", @@ -761,25 +784,25 @@ function spawnWeaknesses() globalPos = self.positionToWorld(groupPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) - groupPos.z = groupPos.z + math.ceil(#basicWeaknessList / 20) * cardRowOffset + cardGroupOffset + groupPos.z = groupPos.z + math.ceil(#basicWeaknessList / CARD_MAX_COLS) * cardRowOffset + cardGroupOffset spawnBag.spawn({ name = "evolvedWeaknesses", cards = EVOLVED_WEAKNESSES, globalPos = self.positionToWorld(groupPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) - groupPos.z = groupPos.z + math.ceil(#EVOLVED_WEAKNESSES / 20) * cardRowOffset + cardGroupOffset + groupPos.z = groupPos.z + math.ceil(#EVOLVED_WEAKNESSES / CARD_MAX_COLS) * cardRowOffset + cardGroupOffset spawnBag.spawn({ name = "otherWeaknesses", cards = otherWeaknessList, globalPos = self.positionToWorld(groupPos), rotation = FACE_UP_ROTATION, spread = true, - spreadCols = 20 + spreadCols = CARD_MAX_COLS }) end @@ -792,7 +815,8 @@ function spawnRandomWeakness(_, playerColor, isRightClick) spawnSingleWeakness(weaknessIds[1]) end else - Player[playerColor].showInputDialog("Specify a trait for the weakness (split multiple eligible traits with '|'):", lastWeaknessTrait, + Player[playerColor].showInputDialog("Specify a trait for the weakness (split multiple eligible traits with '|'):", + lastWeaknessTrait, function(text) lastWeaknessTrait = text local weaknessIds = allCardsBagApi.getRandomWeaknessIds(1, { traits = text }) diff --git a/src/playercards/SpawnBag.ttslua b/src/playercards/SpawnBag.ttslua index 31db3b9d..7cb09dec 100644 --- a/src/playercards/SpawnBag.ttslua +++ b/src/playercards/SpawnBag.ttslua @@ -81,7 +81,7 @@ do -- Places the given spawnSpec on the table. See comment at the start of the file for spawnSpec table data and examples SpawnBag.spawn = function(spawnSpec) -- Limit to one placement at a time - if placedSpecs[spawnSpec.name] or spawnSpec == nil then return end + if placedSpecs[spawnSpec.name] or spawnSpec == nil or spawnSpec.cards == nil then return end local cardsToSpawn = {} for _, cardId in ipairs(spawnSpec.cards) do diff --git a/src/playercards/cards/FamilyInheritance.ttslua b/src/playercards/cards/FamilyInheritance.ttslua index 9e7b655e..6b1d0534 100644 --- a/src/playercards/cards/FamilyInheritance.ttslua +++ b/src/playercards/cards/FamilyInheritance.ttslua @@ -1,7 +1,7 @@ require("playercards/CardsWithHelper") local playermatApi = require("playermat/PlayermatApi") local searchLib = require("util/SearchLib") -local tokenManager = require("core/token/TokenManager") +local tokenManagerApi = require("core/token/TokenManagerApi") -- intentionally global hasXML = true @@ -45,7 +45,7 @@ function add4() if clickableResourceCounter then clickableResourceCounter.call("updateVal", newCount) else - tokenManager.spawnTokenGroup(self, "resource", newCount) + tokenManagerApi.spawnTokenGroup(self, "resource", newCount) end end diff --git a/src/playercards/cards/KohakuNarukami.ttslua b/src/playercards/cards/KohakuNarukami.ttslua index d033135f..561dce0b 100644 --- a/src/playercards/cards/KohakuNarukami.ttslua +++ b/src/playercards/cards/KohakuNarukami.ttslua @@ -3,7 +3,7 @@ local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi") local guidReferenceApi = require("core/GUIDReferenceApi") local playermatApi = require("playermat/PlayermatApi") local searchLib = require("util/SearchLib") -local tokenManager = require("core/token/TokenManager") +local tokenManagerApi = require("core/token/TokenManagerApi") -- intentionally global hasXML = true @@ -84,11 +84,9 @@ function removeAndExtraAction() end end - callback = function(spawned) - spawned.call("updateClassAndSymbol", { class = "Mystic", symbol = "Mystic" }) - spawned.addTag("Temporary") - end - tokenManager.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callback) + local callbackName = "updateUniversalActionAbilityToken" + local callbackParams = { class = "Mystic", symbol = "Mystic", addTag = "Temporary"} + tokenManagerApi.spawnToken(emptyPos + Vector(0, 0.7, 0), "universalActionAbility", rotation, callbackName, callbackParams) end function elderSignAbility() diff --git a/src/playercards/cards/NkosiMabati3.ttslua b/src/playercards/cards/NkosiMabati3.ttslua index e5cf8643..333c5da6 100644 --- a/src/playercards/cards/NkosiMabati3.ttslua +++ b/src/playercards/cards/NkosiMabati3.ttslua @@ -1,22 +1,21 @@ require("playercards/CardsWithHelper") +local chaosBagApi = require("chaosbag/ChaosBagApi") -- intentionally global -hasXML = false -isHelperEnabled = false - -local chaosBagApi = require("chaosbag/ChaosBagApi") +hasXML = false +isHelperEnabled = false -- XML background color for each token local tokenColor = { - ["Skull"] = "#4A0400E6", - ["Cultist"] = "#173B0BE6", - ["Tablet"] = "#1D2238E6", + ["Skull"] = "#4A0400E6", + ["Cultist"] = "#173B0BE6", + ["Tablet"] = "#1D2238E6", ["Elder Thing"] = "#4D2331E6", - ["Auto-fail"] = "#9B0004E6", - ["Bless"] = "#9D702CE6", - ["Curse"] = "#633A84E6", - ["Frost"] = "#404450E6", - [""] = "#77674DE6" + ["Auto-fail"] = "#9B0004E6", + ["Bless"] = "#9D702CE6", + ["Curse"] = "#633A84E6", + ["Frost"] = "#404450E6", + [""] = "#77674DE6" } function updateSave() @@ -42,7 +41,7 @@ end function makeXMLButton() -- get name of the icon for the sigil ("token" + lowercase name without space characters) - local sigilName = Global.call("getReadableTokenName", sigil) + local sigilName = chaosBagApi.getReadableTokenName(sigil) local iconName = "token-" .. string.lower(sigilName) iconName = iconName:gsub("%s", "-") @@ -109,13 +108,13 @@ function chooseSigil(player) -- get list of readable names local readableNames = {} for token, _ in pairs(tokenColor) do - table.insert(readableNames, Global.call("getReadableTokenName", token)) + table.insert(readableNames, chaosBagApi.getReadableTokenName(token)) end -- prompt player to choose sigil player.showOptionsDialog("Choose Sigil", readableNames, 1, function(chosenToken) - sigil = Global.call("getChaosTokenName", chosenToken) + sigil = chaosBagApi.getChaosTokenName(chosenToken) makeXMLButton() end ) @@ -139,16 +138,14 @@ function resolveSigil() end if not match then - broadcastToAll(Global.call("getReadableTokenName", sigil) .. " not found in chaos bag", "Red") + broadcastToAll(chaosBagApi.getReadableTokenName(sigil) .. " not found in chaos bag", "Red") return end - Global.call("activeRedrawEffect", { - DRAW_SPECIFIC_TOKEN = sigil, - VALID_TOKENS = { - ["Tablet"] = true, - ["Elder Thing"] = true, - ["Cultist"] = true - } - }) + local VALID_TOKENS = { + ["Tablet"] = true, + ["Elder Thing"] = true, + ["Cultist"] = true + } + chaosBagApi.activeRedrawEffect(VALID_TOKENS, _, _, sigil) end diff --git a/src/playermat/Playermat.ttslua b/src/playermat/Playermat.ttslua index 650b2a38..c78eed6a 100644 --- a/src/playermat/Playermat.ttslua +++ b/src/playermat/Playermat.ttslua @@ -1,11 +1,12 @@ local chaosBagApi = require("chaosbag/ChaosBagApi") local deckLib = require("util/DeckLib") +local GlobalApi = require("core/GlobalApi") local guidReferenceApi = require("core/GUIDReferenceApi") local mythosAreaApi = require("core/MythosAreaApi") local navigationOverlayApi = require("core/NavigationOverlayApi") local searchLib = require("util/SearchLib") local tokenChecker = require("core/token/TokenChecker") -local tokenManager = require("core/token/TokenManager") +local tokenManagerApi = require("core/token/TokenManagerApi") local tokenSpawnTrackerApi = require("core/token/TokenSpawnTrackerApi") -- option panel data @@ -73,6 +74,16 @@ local DECK_DISCARD_AREA = { local DRAW_DECK_POSITION = { x = -1.82, y = 0.1, z = 0 } local DISCARD_PILE_POSITION = { x = -1.82, y = 0.1, z = 0.61 } local DRAWN_ENCOUNTER_POSITION = { x = 1.365, y = 0.5, z = -0.625 } +local tokenSpawnPos = { + action = { + Vector(-0.86, 0, -0.28), -- left of the regular three actions + Vector(-1.54, 0, -0.28), -- right of the regular three actions + }, + ability = { + Vector(-1, 0, 0.118), -- bottom left corner of the investigator card + Vector(-1, 0, -0.118), -- top left corner of the investigator card + } +} -- global position of encounter discard pile local ENCOUNTER_DISCARD_POSITION = { x = -3.85, y = 1.5, z = 10.38 } @@ -149,14 +160,14 @@ end function onLoad(savedData) if savedData and savedData ~= "" then - local loadedData = JSON.decode(savedData) - activeInvestigatorData = loadedData.activeInvestigatorData - isClassTextureEnabled = loadedData.isClassTextureEnabled - isDrawButtonVisible = loadedData.isDrawButtonVisible - optionPanelData = loadedData.optionPanelData - optionPanelVisibility = loadedData.optionPanelVisibility - playerColor = loadedData.playerColor - slotData = loadedData.slotData + local loadedData = JSON.decode(savedData) + activeInvestigatorData = loadedData.activeInvestigatorData + isClassTextureEnabled = loadedData.isClassTextureEnabled + isDrawButtonVisible = loadedData.isDrawButtonVisible + optionPanelData = loadedData.optionPanelData + optionPanelVisibility = loadedData.optionPanelVisibility + playerColor = loadedData.playerColor + slotData = loadedData.slotData -- make sure that edit mode starts disabled optionPanelData.slotEditing = false @@ -365,7 +376,7 @@ function doUpkeep(_, clickedByColor, isRightClick) -- maybe replenish uses on certain cards (don't continue for cards on the deck (Norman) or in the discard pile) if cardMetadata.uses ~= nil and self.positionToLocal(obj.getPosition()).x > -1 and not obj.is_face_down then - tokenManager.maybeReplenishCard(obj, cardMetadata.uses, self) + tokenManagerApi.maybeReplenishCard(obj, cardMetadata.uses, self) end elseif obj.type == "Deck" and forcedLearning == false then -- check decks for forced learning @@ -623,7 +634,7 @@ function doDiscardOne() local cardId = choices[num] .. "/" .. #hand deckLib.placeOrMergeIntoDeck(card, returnGlobalDiscardPosition(), self.getRotation()) - local playerName = Global.call("getColoredName", playerColor) + local playerName = GlobalApi.getColoredName(playerColor) broadcastToAll(playerName .. " randomly discarded " .. cardName " (" .. cardId .. ").", "White") end end @@ -922,11 +933,7 @@ function createXML() end function onClick_hideOrShowOptions(player) - Global.call("changeWindowVisibilityForColorWrapper", { - color = player.color, - windowId = "optionPanelMain", - owner = self - }) + GlobalApi.changeWindowVisibility(player.color, "optionPanelMain", _, self) end -- show the triggering player a dialog to select a playermat texture @@ -958,16 +965,8 @@ function onClick_handColorSelect(player) handZone.setValue(newColor) -- update visibility for old and new color - Global.call("changeWindowVisibilityForColorWrapper", { - color = playerColor, - windowId = "optionPanelMain", - owner = self - }) - Global.call("changeWindowVisibilityForColorWrapper", { - color = newColor, - windowId = "optionPanelMain", - owner = self - }) + GlobalApi.changeWindowVisibility(playerColor, "optionPanelMain", _, self) + GlobalApi.changeWindowVisibility(newColor, "optionPanelMain", _, self) navigationOverlayApi.copyVisibility(playerColor, newColor) -- if there was a seated player, reseat to the new color @@ -986,11 +985,13 @@ end -- instruct Global to update this mat's hand visibility function onClick_visibilitySelect(player) if player.color == playerColor then - printToColor("This is meant to be clicked by other players to be able to see your hand (primarily for multi-handed gameplay). It won't do anything for you.", playerColor) + printToColor( + "This is meant to be clicked by other players to be able to see your hand (primarily for multi-handed gameplay). It won't do anything for you.", + playerColor) return end - Global.call("handVisibilityToggle", { playerColor = player.color, handColor = playerColor }) + GlobalApi.handVisibilityToggle(player.color, playerColor) end -- changes the UI state and the internal variable for the togglebuttons @@ -1132,7 +1133,7 @@ function spawnTokensFor(object) extraUses["Charge"] = 1 end - tokenManager.spawnForCard(object, extraUses) + tokenManagerApi.spawnForCard(object, extraUses) end function onCollisionEnter(collisionInfo) @@ -1158,9 +1159,7 @@ end -- checks if tokens should be spawned for the provided card function shouldSpawnTokens(card) - if card.is_face_down then - return false - end + if card.is_face_down then return false end local localCardPos = self.positionToLocal(card.getPosition()) local metadata = JSON.decode(card.getGMNotes()) @@ -1270,30 +1269,17 @@ function maybeUpdateActiveInvestigator(card) end -- spawn three regular action tokens (investigator specific one in the bottom spot) + local rotation = self.getRotation() + local callbackName = "updateUniversalActionAbilityToken" + local callbackParams = { class = activeInvestigatorData.class, symbol = activeInvestigatorData.class } + for i = 1, 3 do local pos = self.positionToWorld(Vector(-1.54 + i * 0.17, 0, -0.28)):add(Vector(0, 0.2, 0)) - - tokenManager.spawnToken(pos, "universalActionAbility", self.getRotation(), - function(spawned) - spawned.call("updateClassAndSymbol", - { class = activeInvestigatorData.class, symbol = activeInvestigatorData.class }) - end) + tokenManagerApi.spawnToken(pos, "universalActionAbility", rotation, callbackName, callbackParams) end -- spawn additional token (maybe specific for investigator) if extraToken and extraToken ~= "None" then - -- local positions - local tokenSpawnPos = { - action = { - Vector(-0.86, 0, -0.28), -- left of the regular three actions - Vector(-1.54, 0, -0.28), -- right of the regular three actions - }, - ability = { - Vector(-1, 0, 0.118), -- bottom left corner of the investigator card - Vector(-1, 0, -0.118), -- top left corner of the investigator card - } - } - -- spawn tokens (split string by "|") local count = { action = 0, ability = 0 } for str in string.gmatch(extraToken, "([^|]+)") do @@ -1308,11 +1294,8 @@ function maybeUpdateActiveInvestigator(card) else local localSpawnPos = tokenSpawnPos[type][count[type]] local globalSpawnPos = self.positionToWorld(localSpawnPos):add(Vector(0, 0.2, 0)) - - tokenManager.spawnToken(globalSpawnPos, "universalActionAbility", self.getRotation(), - function(spawned) - spawned.call("updateClassAndSymbol", { class = activeInvestigatorData.class, symbol = str }) - end) + callbackParams.symbol = str + tokenManagerApi.spawnToken(globalSpawnPos, "universalActionAbility", rotation, callbackName, callbackParams) end end end @@ -1495,7 +1478,7 @@ function clickableClues(showCounter) local pos = self.positionToWorld({ x = -1.12, y = 0.05, z = 0.7 }) for i = 1, clueCount do pos.y = pos.y + 0.045 * i - tokenManager.spawnToken(pos, "clue", self.getRotation()) + tokenManagerApi.spawnToken(pos, "clue", self.getRotation()) end end end @@ -1575,10 +1558,9 @@ end function updatePlayerCards(args) local customDataHelper = getObjectFromGUID(args[1]) local playerCardData = customDataHelper.getTable("PLAYER_CARD_DATA") - tokenManager.addPlayerCardData(playerCardData) + tokenManagerApi.addPlayerCardData(playerCardData) end - function getActiveInvestigatorData() return activeInvestigatorData end function setActiveInvestigatorData(newData) activeInvestigatorData = newData end diff --git a/src/util/TokenSpawnTool.ttslua b/src/util/TokenSpawnTool.ttslua index 07a2942d..6dd65478 100644 --- a/src/util/TokenSpawnTool.ttslua +++ b/src/util/TokenSpawnTool.ttslua @@ -1,16 +1,16 @@ -local playermatApi = require("playermat/PlayermatApi") -local searchLib = require("util/SearchLib") -local tokenManager = require("core/token/TokenManager") -local TOKEN_INDEX = {} +local playermatApi = require("playermat/PlayermatApi") +local searchLib = require("util/SearchLib") +local tokenManagerApi = require("core/token/TokenManagerApi") -TOKEN_INDEX[1] = "universalActionAbility" -TOKEN_INDEX[3] = "resourceCounter" -TOKEN_INDEX[4] = "damage" -TOKEN_INDEX[5] = "path" -TOKEN_INDEX[6] = "horror" -TOKEN_INDEX[7] = "doom" -TOKEN_INDEX[8] = "clue" -TOKEN_INDEX[9] = "resource" +local TOKEN_INDEX = {} +TOKEN_INDEX[1] = "universalActionAbility" +TOKEN_INDEX[3] = "resourceCounter" +TOKEN_INDEX[4] = "damage" +TOKEN_INDEX[5] = "path" +TOKEN_INDEX[6] = "horror" +TOKEN_INDEX[7] = "doom" +TOKEN_INDEX[8] = "clue" +TOKEN_INDEX[9] = "resource" ---@param index number Index of the pressed key ---@param playerColor string Color of the triggering player @@ -18,9 +18,10 @@ function onScriptingButtonDown(index, playerColor) local tokenType = TOKEN_INDEX[index] if not tokenType then return end - local rotation = { x = 0, y = Player[playerColor].getPointerRotation(), z = 0 } local position = Player[playerColor].getPointerPosition() + Vector(0, 0.2, 0) - callback = nil + local rotation = Vector(0, Player[playerColor].getPointerRotation(), 0) + callbackName = nil + callbackParams = nil -- check for subtype of resource based on card below if tokenType == "resource" then @@ -66,20 +67,14 @@ function onScriptingButtonDown(index, playerColor) -- check for nearest investigator card and change action token state to its class elseif tokenType == "universalActionAbility" then - callback = function(spawned) - local matColor = playermatApi.getMatColorByPosition(position) - local matRotation = playermatApi.returnRotation(matColor) - local activeInvestigatorData = playermatApi.getActiveInvestigatorData(matColor) - - spawned.setRotation(matRotation) - spawned.call("updateClassAndSymbol", { - class = activeInvestigatorData.class, - symbol = activeInvestigatorData.class - }) - end + local matColor = playermatApi.getMatColorByPosition(position) + local activeInvestigatorData = playermatApi.getActiveInvestigatorData(matColor) + rotation = playermatApi.returnRotation(matColor) + callbackName = "updateUniversalActionAbilityToken" + callbackParams = { class = activeInvestigatorData.class, symbol = activeInvestigatorData.class } end - tokenManager.spawnToken(position, tokenType, rotation, callback) + tokenManagerApi.spawnToken(position, tokenType, rotation, callbackName, callbackParams) end -- gets the target card for this operation @@ -132,7 +127,7 @@ function addUseToCard(card, useType) -- if matching uses were found, perform the "fake" replenish if match then - tokenManager.maybeReplenishCard(card, metadata.uses) + tokenManagerApi.maybeReplenishCard(card, metadata.uses) return true else return false