This commit is contained in:
Adam Goldsmith 2024-07-27 21:47:52 -04:00
parent d41b69bf4a
commit e5aea0cf67
703 changed files with 59908 additions and 93546 deletions

File diff suppressed because it is too large Load Diff

View File

@ -628,7 +628,7 @@
<Panel class="doubleColumn-wrapper"
padding="0 17 3 3"/>
<Button class="optionToggle"
image="option-off"
image="option_off"
rectAlignment="MiddleRight"
offsetXY="-30 0"
colors="#FFFFFF|#dfdfdf"
@ -727,6 +727,21 @@
</Cell>
</Row>
<!-- Option: Enable all card helpers -->
<Row class="option-text"
tooltip="Enable all card helpers (usually enabled via context menu entries).&#xA;Examples: False Covenant and Book of Living Myths">
<Cell class="option-text">
<Panel class="singleColumn-wrapper">
<Text class="option-header">Enable all card helpers</Text>
</Panel>
</Cell>
<Cell class="option-button">
<Button class="optionToggle"
id="enableCardHelpers"
onClick="onClick_toggleOption"/>
</Cell>
</Row>
<!-- Group: play area settings -->
<Row class="group-header">
<Cell class="group-header">
@ -839,6 +854,21 @@
</Cell>
</Row>
<!-- Option: use class-specific texture -->
<Row class="option-text"
tooltip="Controls whether a class-specific playermat texture should be automatically loaded.">
<Cell class="option-text">
<Panel class="singleColumn-wrapper">
<Text class="option-header">Use class-specific texture</Text>
</Panel>
</Cell>
<Cell class="option-button">
<Button class="optionToggle"
id="useClassTexture"
onClick="onClick_toggleOption"/>
</Cell>
</Row>
<!-- Option: use clickable clue-counters -->
<Row class="option-text"
tooltip="Instead of automatically counting clues in the respective area on your playermat,&#xA;this displays a clickable counter for clues.">
@ -874,7 +904,7 @@
</Cell>
</Row>
<!-- Option: remove a player mat -->
<!-- Option: remove a playermat -->
<Row class="option-text"
tooltip="Remove an unused playermat for more table space.&#xA;Displayed are the default colors.">
<Cell class="option-singleColumn">

View File

@ -71,14 +71,14 @@ ComponentTags:
normalized: investigator
- displayed: chaosBag
normalized: chaosbag
- displayed: ActionToken
normalized: actiontoken
- displayed: LargeBox
normalized: largebox
- displayed: CampaignBox
normalized: campaignbox
- displayed: CameraZoom_ignore
normalized: camerazoom_ignore
- displayed: UniversalToken
normalized: universaltoken
CustomUIAssets:
- Name: close
Type: 0
@ -98,10 +98,10 @@ CustomUIAssets:
- Name: option-gear
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2026086584372569912/5CB461AEAE2E59D3064D90A776EB86C46081EC78/
- Name: option-on
- Name: option_on
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2462982115668997008/2178787B67B3C96F3419EDBAB8420E39893756BC/
- Name: option-off
- Name: option_off
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2462982115668996901/D6438ECBB11DECC6DB9987589FF526FBAD4D2368/
- Name: font_arkhamicons
@ -122,12 +122,6 @@ CustomUIAssets:
- Name: header_olive
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2280574378889753733/F67B7B37FF7AA253B6D697E577DF54A3E76030C2/
- Name: option_on
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2024962321889555728/22ABD35CBB49A001F3A5318E4AFCFB22D24FEA39/
- Name: option_off
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2024962321889555661/6643E5CC9160FF4624672C255D0DF7B313DA00A5/
- Name: SpeechBubble
Type: 0
URL: https://i.imgur.com/6MReiEO.png
@ -227,10 +221,13 @@ CustomUIAssets:
- Name: token-auto-fail
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2510267932886739653/CB7AA2D73777EF5938A6E6CD664B2ABA52B6E20A/
- Name: token-eldersign
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2540675016035917168/C0D6F531A85FD94C2F54825DFC50781B5B499A1D/
- Name: token-custom-token
Type: 0
URL: http://cloud-3.steamusercontent.com/ugc/2380784374792650571/E4C2B2B69282A4EE15FE290FF6B08BEFC8FCA65C/
Date: Sun May 12 13:10:46 CEST 2024
Date: Mon Jul 8 20:19:48 CEST 2024
DecalPallet:
- ImageURL: http://cloud-3.steamusercontent.com/ugc/1474319121424323663/BC5570ECF747F1B30224461B576E8B0FE7FA5F33/
Name: Achivement Checkmark
@ -239,7 +236,7 @@ DecalPallet:
Name: Victory Display
Size: 15
Decals: []
EpochTime: 1715512246
EpochTime: 1720462788
GameComplexity: ''
GameMode: Arkham Horror LCG - Super Complete Edition
GameType: ''
@ -290,7 +287,7 @@ Lighting:
LutIndex: 0
ReflectionIntensity: 1
LuaScript: !include 'unpacked.ttslua'
LuaScriptState: '{"acknowledgedUpgradeVersions":[],"chaosTokensGUID":[],"optionPanel":{"cardLanguage":"en","changePlayAreaImage":false,"playAreaConnectionColor":{"a":1,"b":0.4,"g":0.4,"r":0.4},"playAreaConnections":true,"playAreaSnapTags":true,"showAttachmentHelper":false,"showCleanUpHelper":false,"showCYOA":false,"showDisplacementTool":false,"showDrawButton":false,"showHandHelper":false,"showSearchAssistant":false,"showTitleSplash":true,"useClueClickers":false,"useResourceCounters":"disabled","useSnapTags":true}}'
LuaScriptState: '{"acknowledgedUpgradeVersions":[],"chaosTokensGUID":[],"optionPanel":{"cardLanguage":"en","changePlayAreaImage":false,"enableCardHelpers":true,"playAreaConnectionColor":{"a":1,"b":0.4,"g":0.4,"r":0.4},"playAreaConnections":true,"playAreaSnapTags":true,"showAttachmentHelper":false,"showCleanUpHelper":false,"showCYOA":false,"showDisplacementTool":false,"showDrawButton":false,"showHandHelper":false,"showSearchAssistant":false,"showTitleSplash":true,"useClassTexture":true,"useClueClickers":false,"useResourceCounters":"disabled","useSnapTags":true}}'
MusicPlayer:
AudioLibrary:
- Item1: http://cloud-3.steamusercontent.com/ugc/784110538847453001/4481D1CC5684FCF04AB143954DEFE09E94BF5CEB/
@ -348,11 +345,14 @@ MusicPlayer:
Note: ''
ObjectStates:
- !include 'unpacked/go_game_piece_white GUID Reference Handler 123456.yaml'
- !include 'unpacked/go_game_piece_white Game Key Handler fce69c.yaml'
- !include 'unpacked/Checker_white Token Spawn Tracker e3ffc9.yaml'
- !include 'unpacked/HandTrigger 5fe087.yaml'
- !include 'unpacked/HandTrigger be2f17.yaml'
- !include 'unpacked/HandTrigger 0285cc.yaml'
- !include 'unpacked/HandTrigger a70eee.yaml'
- !include 'unpacked/ScriptingTrigger a2f932.yaml'
- !include 'unpacked/FogOfWarTrigger 3aab97.yaml'
- !include 'unpacked/Custom_Assetbundle TableLegBottomRight afc863.yaml'
- !include 'unpacked/Custom_Assetbundle TableLegBottomLeft c8edca.yaml'
- !include 'unpacked/Custom_Assetbundle TableLegTopLeft 393bf7.yaml'
@ -408,7 +408,9 @@ ObjectStates:
- !include 'unpacked/Custom_Model_Bag Trash 5f896a.yaml'
- !include 'unpacked/Custom_Model_Bag Trash 147e80.yaml'
- !include 'unpacked/Custom_Model_Bag Trash f7b6c8.yaml'
- !include 'unpacked/Custom_Tile Patch Notes f47225.yaml'
- !include 'unpacked/Custom_PDF Rules Reference d99993.yaml'
- !include 'unpacked/Custom_PDF Latest FAQ faqfaq.yaml'
- !include 'unpacked/Custom_Model_Infinite_Bag Doom tokens 16724b.yaml'
- !include 'unpacked/Custom_Model_Infinite_Bag Clue tokens fae2f6.yaml'
- !include 'unpacked/Custom_Model_Infinite_Bag Clue tokens 3b2550.yaml'
@ -422,8 +424,6 @@ ObjectStates:
- !include 'unpacked/Custom_Model_Infinite_Bag Damage tokens 480bda.yaml'
- !include 'unpacked/Custom_Model_Infinite_Bag Resource tokens 9fadf9.yaml'
- !include 'unpacked/Custom_Model_Infinite_Bag Connection markers 170f10.yaml'
- !include 'unpacked/FogOfWarTrigger 3aab97.yaml'
- !include 'unpacked/Custom_Model_Bag Leaked Items 42cd6e.yaml'
- !include 'unpacked/Custom_Model_Bag Chaos Token Reserve 106418.yaml'
- !include 'unpacked/Custom_Model Clue Counter 37be78.yaml'
- !include 'unpacked/Custom_Model Clue Counter 1769ed.yaml'
@ -432,8 +432,7 @@ ObjectStates:
- !include 'unpacked/Custom_Token Master Clue Counter 4a3aa4.yaml'
- !include 'unpacked/Custom_Model_Bag Legacy Assets 7165a9.yaml'
- !include 'unpacked/Custom_Token Play Area 721ba2.yaml'
- !include 'unpacked/Bag Additional Player Cards 2cba6b.yaml'
- !include 'unpacked/Custom_Assetbundle_Bag Barkham Horror 308439.yaml'
- !include 'unpacked/Custom_Model_Bag Additional Player Cards 2cba6b.yaml'
- !include 'unpacked/Custom_Token Chaos Bag Stat Tracker 766620.yaml'
- !include 'unpacked/Checker_white Token Spawn Tool 36b4ee.yaml'
- !include 'unpacked/Custom_Model_Bag Standalone Scenarios 77a5f9.yaml'
@ -454,8 +453,6 @@ ObjectStates:
- !include 'unpacked/Custom_Model_Bag Chaos Bag fea079.yaml'
- !include 'unpacked/Custom_Tile Data Helper 708279.yaml'
- !include 'unpacked/Custom_Token BlessCurse Manager 5933fb.yaml'
- !include 'unpacked/Notecard d8d357.yaml'
- !include 'unpacked/ScriptingTrigger a2f932.yaml'
- !include 'unpacked/Custom_Model Edge of the Earth 895eaa.yaml'
- !include 'unpacked/Custom_Model The Dream-Eaters a16a1a.yaml'
- !include 'unpacked/Custom_Model The Feast of Hemlock Vale c740af.yaml'
@ -465,27 +462,11 @@ ObjectStates:
- !include 'unpacked/Custom_Tile Playermat 2 Orange bd0ff4.yaml'
- !include 'unpacked/Custom_Tile Playermat 3 Green 383d8b.yaml'
- !include 'unpacked/Custom_Tile Playermat 4 Red 0840d5.yaml'
- !include 'unpacked/Custom_Tile Neutral 2691e1.yaml'
- !include 'unpacked/Custom_Tile Neutral 748245.yaml'
- !include 'unpacked/Custom_Tile Neutral 271b17.yaml'
- !include 'unpacked/Custom_Tile Neutral 5bafdf.yaml'
- !include 'unpacked/Custom_Tile Neutral 012577.yaml'
- !include 'unpacked/Custom_Tile Neutral 04765b.yaml'
- !include 'unpacked/Custom_Tile Neutral b71036.yaml'
- !include 'unpacked/Custom_Tile Neutral 1cb302.yaml'
- !include 'unpacked/Custom_Tile Neutral bbc5d4.yaml'
- !include 'unpacked/Custom_Tile Neutral 429bb3.yaml'
- !include 'unpacked/Custom_Tile Neutral 183dbe.yaml'
- !include 'unpacked/Custom_Tile Neutral b80db6.yaml'
- !include 'unpacked/Custom_Tile Neutral af1927.yaml'
- !include 'unpacked/Custom_Tile Neutral 0329cc.yaml'
- !include 'unpacked/Custom_Tile Neutral 5bec40.yaml'
- !include 'unpacked/Custom_Tile Neutral 5825ca.yaml'
- !include 'unpacked/Custom_Token Lead Investigator acaa93.yaml'
- !include 'unpacked/Custom_Tile ArkhamDB Deck Importer a28140.yaml'
- !include 'unpacked/Checker_white Configuration 03804b.yaml'
- !include 'unpacked/Custom_Token Drawing Tool 280086.yaml'
- !include 'unpacked/Custom_Token Playmat Image Swapper b7b45b.yaml'
- !include 'unpacked/Custom_Token PlayArea Image Swapper b7b45b.yaml'
- !include 'unpacked/Bag All Player Cards 15bb07.yaml'
- !include 'unpacked/Custom_Token Investigator Skill Tracker af7ed7.yaml'
- !include 'unpacked/Custom_Token Investigator Skill Tracker e598c2.yaml'
@ -511,7 +492,6 @@ ObjectStates:
- !include 'unpacked/ScriptingTrigger TokenDiscardZone 457de5.yaml'
- !include 'unpacked/ScriptingTrigger TokenDiscardZone 457de6.yaml'
- !include 'unpacked/Custom_Tile Decoration - Map 6161b4.yaml'
- !include 'unpacked/Custom_Model_Bag Rulebooks, Guides and Tablets fcfa7f.yaml'
- !include 'unpacked/BlockRectangle Table Divider 612072.yaml'
- !include 'unpacked/BlockRectangle Table Divider 975c39.yaml'
- !include 'unpacked/BlockRectangle Table Divider 75937e.yaml'
@ -524,7 +504,6 @@ ObjectStates:
- !include 'unpacked/Custom_Tile Fan-Made Expansion Overview de7cae.yaml'
- !include 'unpacked/Bag OptionPanel Source 830bd0.yaml'
- !include 'unpacked/Custom_Assetbundle SoundCube 3c988f.yaml'
- !include 'unpacked/go_game_piece_white Game Key Handler fce69c.yaml'
- !include 'unpacked/Custom_Tile Token Spawning Reference f8b3a7.yaml'
- !include 'unpacked/3DText d628cc.yaml'
- !include 'unpacked/go_game_piece_black Navigation Overlay Handler 797ede.yaml'
@ -532,7 +511,7 @@ ObjectStates:
- !include 'unpacked/Custom_Token Token Arranger 022907.yaml'
- !include 'unpacked/Custom_Tile Chaos Bag Manager 023240.yaml'
- !include 'unpacked/BlockRectangle Placeholder Box Dummy a93466.yaml'
- !include 'unpacked/Custom_Model The Matter of Britain 194cc5.yaml'
- !include 'unpacked/Custom_Model Lovecrafter 3077 b08d20.yaml'
- !include 'unpacked/Custom_Tile Tokencache_+1 a15273.yaml'
- !include 'unpacked/Custom_Tile Tokencache_0 0a8592.yaml'
- !include 'unpacked/Custom_Tile Tokencache_-1 b644d2.yaml'
@ -553,7 +532,18 @@ ObjectStates:
- !include 'unpacked/Custom_Tile Tokencache_Curse 16a9a7.yaml'
- !include 'unpacked/Custom_Tile Tokencache_Frost b2b7be.yaml'
- !include 'unpacked/BlockSquare Physics Detector b300d8.yaml'
- !include 'unpacked/Notecard Arkham SCE 3.8.0 - 5122024 - Page 1 bd6b3e.yaml'
- !include 'unpacked/Custom_Tile Neutral 834ad5.yaml'
- !include 'unpacked/Custom_Tile Neutral a84ae4.yaml'
- !include 'unpacked/Custom_Tile Neutral 762df8.yaml'
- !include 'unpacked/Custom_Tile Neutral 589c7d.yaml'
- !include 'unpacked/Custom_Tile Neutral 642585.yaml'
- !include 'unpacked/Custom_Tile Neutral 6441b3.yaml'
- !include 'unpacked/Custom_Tile Neutral f2d25a.yaml'
- !include 'unpacked/Custom_Tile Neutral a5476b.yaml'
- !include 'unpacked/Custom_Tile Neutral 06ee01.yaml'
- !include 'unpacked/Custom_Tile Neutral 88d9ff.yaml'
- !include 'unpacked/Custom_Tile Neutral 42ec66.yaml'
- !include 'unpacked/Custom_Tile Neutral f94579.yaml'
PlayArea: 1
PlayerCounts:
- 0
@ -561,7 +551,7 @@ PlayerCounts:
PlayingTime:
- 0
- 0
SaveName: Arkham SCE - 3.8.0
SaveName: Arkham SCE - 3.9.0
Sky: Sky_Museum
SkyURL: https://i.imgur.com/GkQqaOF.jpg
SnapPoints:
@ -749,182 +739,80 @@ SnapPoints:
x: -26
y: 1.48
z: -87
- Position:
x: 60
y: 1.48
z: 48
- Position:
x: -42
y: 1.48
z: 89
- Position:
x: -42
y: 1.48
z: 71
- Position:
x: -62
y: 1.48
z: 71
- Position:
x: -62
y: 1.48
z: 89
TabStates:
'10':
body: "Created by Whimsical\n\nAnything that passes over the remover that isn't
a card, deck or chaos token will be deleted.\r\nTo use the remover, right click
on it, choose the \"Enable\" option, and take your card with resources/horror/damage
and swipe it over the remover. You may wish to unlock and/or copy the remover
to your play area first."
'1':
body: 'Welcome to Arkham Horror LCG - Super Complete Edition!
Make sure to take the tour that can be started with the token in the middle
of the main playarea. Some basic notes:
DECKBUILDING
- All currently existing investigators and player cards are accessible via the
player card panel in the upper left corner of the table.
- On the leftside underneath the Investigators, you will find the ArkhamDB Deckimporter.
Insert your deck ID and it will build the deck automatically for you.
SCENARIOS & SETUP
- Arkham Horror LCG comes with a core campaign (Night of the Zealot) and several
expansions. Within each box you will find all the cards required for each scenario
setup, as well as a the official campaign guide PDF.
2. Each scenario is setup differently, and while some of the work has been prepared
beforehand (such as building encounter decks), you will have to refer to the
Campaign Guide for specific instructions on how to set up each scenario.
INVESTIGATOR PLAYMAT AND GAMEPLAY
- Playermats are scripted to automate most of the gameplay for you.'
color: Grey
id: 10
title: Token Remover
id: 1
title: Basic Intro
visibleColor:
b: 0.5
g: 0.5
r: 0.5
'11':
body: 'By Whimsical. Requires Numlock set to On.
Numpad 1: Cut top 3 cards of deck
Numpad 2: Cut top 6 cards of deck
Numpad 3: Cut top 9 cards of deck
Numpad 4: Spawn Damage
Numpad 5: Spawn Connection Marker
Numpad 6: Spawn Horror
Numpad 7: Spawn Doom
Numpad 8: Spawn Clue
Numpad 9: Spawn Resource
Numpad 0: Draw lines between selected objects. Hold to draw lines from mouseover
object to other selected objects.'
color: Grey
id: 11
title: Numpad Hotkeys
visibleColor:
b: 0.5
g: 0.5
r: 0.5
'7':
'2':
body: The server host can enable or disable cards in hands being hidden from other
players by going to the menu at the top of the TTS screen, clicking options,
and choosing Hands. The "Disable" setting reveals all player hands to all players,
while the "Default" setting means that each player can only see the cards in
their own hand.
color: Grey
id: 7
id: 2
title: How to Hide Hands
visibleColor:
b: 0.5
g: 0.5
r: 0.5
'8':
body: "Welcome to Arkham Horror LCG - Super Complete Edition!\r\n\r\nBelow you
will find all the features and instructions this mod is loaded with, that will
make your AH LCG experience easier.\r\n\r\nDECKBUILDING\r\n1. All current existing
investigators are on the right-hand side, and within each chest you will find
their investigator-specific assets and weaknesses. Also included is a basic
starter deck which only requires you to add a basic random weakness to get going.\r\n\r\n2.
On the left-hand side you will find both the weakness decks as well as lvl 0
cards sorted by class. To reveal the cards, click on each corresponding token
to deal the cards onto the table. Cards are sorted by order of Skill, event
and Asset cards top-down and increasing resource cost, left to right. \r\n\r\n3.
On the upper side you have the upgrade cards. Similarly, click each token to
deal the cards out onto the table. Cards are arranged both in increasing xp
cost and resource cost, left to right. Typically, these are the cards you will
be spending XP on between scenarios to purchase and improve your deck.\r\n4.
On the right-hand side underneath the Investigators, you will find the automated
ArkhamDB Deckbuilder (coded and maintained by Grabben). Click the load cards
button to activate the Deckbuilder, check ArkhamDB for your chosen deck\u2019s
URL and insert its number code following the instructions on the deckbuilder,
and it will build the deck automatically for you.\r\n\r\nSCENARIOS & SETUP\r\n1.
\tArkham Horror LCG comes with a core campaign (Night of the Zealot) and several
expansions (The Dunwich Legacy, The Path to Carcosa & The Forgotten Age). Within
each box you will find the volumes that contain all the cards required for each
scenario setup, as well as a tablet linking to the official campaign guide PDF.
Also included are chaos token cards and a Campaign Log.\r\n\r\n2. \tEach scenario
is setup differently, and while some of the work has been prepared beforehand
(such as building encounter decks), you will have to refer to the Campaign Guide
for specific instructions on how to set up each scenario.\r\n\r\n3. \tThe chaos
bag is always placed on the scenario setup mat in the upper right-hand corner
onto a snap point that tilts it at a 45-degree angle. Each scenario volume will
contain a difficulty card, where you will have the choice of four difficulties.
Press the button on the card according to the difficulty of your choosing and
the chaos bag will automatically be configured with the tokens specific to that
difficulty. In campaign play it is recommended to save your decks and chaos
bag at the end of your scenario to carry over onto the next, as often tokens
are added or removed from the chaos bag depending on actions or decisions made
during the game. These additional tokens can be drawn from the token reserve
book resting next to the newspaper in the middle of the main table \u2013 right
click it and search for the token you need.\n\r\n4. \tWhen placing location
cards, always place them face down on the main play area (the dark map of Arkham,
Massachusetts) with the number of clues per location unrevealed. The mod is
scripted so that when you flip said location cards (usually when entering the
location with an investigator), if it contains clues, the number of clues specific
to that location will automatically spawn. Note, that the mod only spawns tokens
in relation to the number of players currently set on the playmat player settings.
To set the number of players, left-click on the \"Investigators Playing\" number
to increase, or right-click to decrease.\r\n\n5. To make location mapping easier,
you can draw location connector tokens from the arrow-shaped container below
the main play area. Each token has three states (one way, two way and four way)
to use them accordingly to better visualize how your locations connect.\n\r\n6.
\tIf you require additional doom or clue tokens, these are located on the scenario
playmat in their corresponding containers. A handy Doom counter has been also
been added to track the doom on the agenda \u2013 left-click to add to add,
and right-click to deduct. Keep in mind that any doom spawned on enemies, locations
or assets needs to be mentally added to the doom in play on the agenda to account
for total doom.\r\n\r\nINVESTIGATOR PLAYMAT AND GAMEPLAY\r\n1. \tInvestigator
mats are scripted to automate most of the gameplay for you. wdw\n\r\n2. \tEach
mat has slots for inventory, where if you play an asset (for example you put
a gun that has 4 ammo into your right-hand slot), the mod will automatically
spawn the 4 resource tokens onto your equipped card.\n\r\n3.\tThe draw encounter
button on the left-hand side will draw the topmost card from the encounter deck
and put it in your threat area. Left-clicking will draw the card face-down,
and right-clicking will draw the card face-up. When you draw a weakness, or
engage an enemy, it is recommended you put it in your threat area, and once
you defeat the enemy or treachery, you can send it to the encounter discard
pile by clicking the discard button. If you defeat an enemy with a victory point,
make sure not to discard to the discard pile, but pick the card up and drop
it at the victory display.\n\r\n4. \tThe Click for Chaos button does just that,
draw a random chaos token from your chaos bag. Clicking a second time, sends
your chaos token back into the bag, which is then shuffled. If one player clicks
to draw a token and doesn\u2019t click a second time to send it back, the click
from another player on his personal mat will send the token back first, and
the next click will draw the token. Additionally, right-clicking the button
will continue drawing tokens and line them up next to each other, which is useful
for specific draw conditions the game may require from you. Left-clicking again
will send all drawn tokens back to the bag.\n\nADDITIONAL FEATURES:\n1. Over
20 Fan-made scenarios created by the thriving community of Arkham Horror LCG
have been included. Some of these are one-scenario missions, others are long
involved campaigns spanning multiple scenarios. These are all contained in \"The
Side Missions\". This boxset also includes the official FFG-created sidemissions
Curse of the Rougarou, Carnevale of Horrors, Labrynths of Lunacy and The Eternal
Slumber. Read the rulebook on including a side-mission into an ongoing campaign,
or play it as a one-off adventure! Setup instructions are included in each
volume.\n\n2. If you are not a fan of the dark themed Arkham map for the playmat,
you can change the image on it to any you like. At the top left hand side of
the playmat is an image icon, which when clicked will reveal a image swap panel.
Input the URL for the image you want to repalce the playmat with, and the panel
will apply the image for you. Keep in mind this will not change the existing
snap points on the current playmat.\n\r\nAs a final comment, please be sure
to let me know on the mod page in steam workshop if you find any bugs, issues
or have any suggestions for improvement!\r\n\r\n\r\n \r\n\r\n"
color: Grey
id: 8
title: Basic Intro
visibleColor:
b: 0.5
g: 0.5
r: 0.5
'9':
body: "Implemented by Tikatoy\nIdea conceived by Cadentia\n\nVersion 3.3\n\nTop
buttons manage bless tokens, bottom buttons manage curse tokens\nADD - creates
a new token and adds it to the chaos bag\nREMOVE - removes a token from the
chaos bag and destroys it\nTAKE - takes a token from the chaos bag and places
it below the manager (for sealing)\nRETURN - returns the last token taken from
the chaos bag to the chaos bag\n\nTo use Parallel Wendy, go to Options -> Game
Keys, then bind a key or mouse button to Wendy's Menu. Hover over any card (won't
work on decks) then press the bound key. Right-click seal/release options will
be added to the card.\n\n---Other Notes---\n\nOnly use ONE token manager at
a time\nTokens are limited to 10 of each type in play\nBless and curse tokens
should be in the chaos bag before trying to REMOVE or TAKE them\nEach action
logs a message which ends with (# in bag/# taken); hit enter to view log\n**WARNING**:
Tracking # of tokens in bag and in play will NOT persist between saves\n\r"
color: Grey
id: 9
title: Bless / Curse Manager
visibleColor:
b: 0.5
g: 0.5
r: 0.5
Table: Table_None
Tags: []
Turns:

View File

@ -1,55 +0,0 @@
AltLookAngle:
x: 0
y: 0
z: 0
Autoraise: true
Bag:
Order: 0
ColorDiffuse:
b: 0
g: 0.36652
r: 0.70588
Description: 'Put any cards in here to add them to the indices for the player card
panel and deck importer.
Select the ''update index'' entry in the context menu of this bag once you''ve added
all cards.
This can be used for custom cards too.'
DragSelectable: true
GMNotes: ''
GUID: 2cba6b
Grid: true
GridProjection: false
Hands: false
HideWhenFaceDown: false
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: true
LuaScript: !include 'Bag Additional Player Cards 2cba6b.ttslua'
LuaScriptState: ''
MaterialIndex: -1
MeasureMovement: false
MeshIndex: -1
Name: Bag
Nickname: Additional Player Cards
Number: 0
Snap: true
Sticky: true
Tags:
- AllCardsHotfix
Tooltip: true
Transform:
posX: 60
posY: 1.2
posZ: 48
rotX: 0
rotY: 0
rotZ: 0
scaleX: 1.5
scaleY: 1.5
scaleZ: 1.5
Value: 0
XmlUI: ''

View File

@ -45,13 +45,16 @@ __bundle_register("__root", function(require, _LOADED, __bundle_register, __bund
require("playercards/AllCardsBag")
end)
__bundle_register("playercards/AllCardsBag", function(require, _LOADED, __bundle_register, __bundle_modules)
local cardIdIndex = { }
local classAndLevelIndex = { }
local basicWeaknessList = { }
local uniqueWeaknessList = { }
local cycleIndex = { }
local guidReferenceApi = require("core/GUIDReferenceApi")
local cardIdIndex = {}
local classAndLevelIndex = {}
local basicWeaknessList = {}
local uniqueWeaknessList = {}
local cycleIndex = {}
local indexingDone = false
local otherCardsDetected = false
function onLoad()
self.addContextMenuItem("Rebuild Index", startIndexBuild)
@ -59,13 +62,13 @@ function onLoad()
Wait.frames(startIndexBuild, 30)
end
-- Called by Hotfix bags when they load. If we are still loading indexes, then
-- Called by Hotfix bags when they load. If we are still loading indexes, then
-- the all cards and hotfix bags are being loaded together, and we can ignore
-- this call as the hotfix will be included in the initial indexing. If it is
-- this call as the hotfix will be included in the initial indexing. If it is
-- called once indexing is complete it means the hotfix bag has been added
-- later, and we should rebuild the index to integrate the hotfix bag.
function rebuildIndexForHotfix()
if (indexingDone) then
if indexingDone then
startIndexBuild()
end
end
@ -73,23 +76,23 @@ 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 = { }
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 = {}
end
-- Clears the bag indexes and starts the coroutine to rebuild the indexes
@ -110,6 +113,7 @@ end
function buildIndex()
local cardCount = 0
indexingDone = false
otherCardsDetected = false
-- process the allcardsbag itself
for _, cardData in ipairs(self.getData().ContainedObjects) do
@ -124,11 +128,15 @@ function buildIndex()
-- process hotfix bags (and the additional playercards bag)
for _, hotfixBag in ipairs(getObjectsWithTag("AllCardsHotfix")) do
local hotfixData = hotfixBag.getData()
if not hotfixData.ContainedObjects then break end
-- if the bag is empty, continue with the next bag
if not hotfixData.ContainedObjects then
goto nextBag
end
for _, cardData in ipairs(hotfixData.ContainedObjects) do
-- process containers
if cardData.ContainedObjects then
-- process containers
for _, deepCardData in ipairs(cardData.ContainedObjects) do
addCardToIndex(deepCardData)
cardCount = cardCount + 1
@ -137,8 +145,8 @@ function buildIndex()
coroutine.yield(0)
end
end
-- process single cards
else
-- process single cards
addCardToIndex(cardData)
cardCount = cardCount + 1
if cardCount > 19 then
@ -147,9 +155,11 @@ function buildIndex()
end
end
end
::nextBag::
end
buildSupplementalIndexes()
updatePlayerCardPanel()
indexingDone = true
return 1
end
@ -158,11 +168,23 @@ end
---@param cardData table TTS object data for the card
function addCardToIndex(cardData)
-- using the more efficient 'json.parse()' to speed this process up
local cardMetadata = json.parse(cardData.GMNotes)
if not cardMetadata then return end
local status, cardMetadata = pcall(function() return json.parse(cardData.GMNotes) end)
-- if an error happens, fallback to the regular parser
if status ~= true or cardMetadata == nil then
log("Fast parser failed for " .. cardData.Nickname .. ", using old parser instead.")
cardMetadata = JSON.decode(cardData.GMNotes)
end
-- if metadata was not valid JSON or empty, don't add the card
if not cardMetadata == nil then
log("Error parsing " .. cardData.Nickname)
return
end
-- use the ZoopGuid as fallback if no id present
cardIdIndex[cardMetadata.id or cardMetadata.TtsZoopGuid] = { data = cardData, metadata = cardMetadata }
cardMetadata.id = cardMetadata.id or cardMetadata.TtsZoopGuid
cardIdIndex[cardMetadata.id] = { data = cardData, metadata = cardMetadata }
-- also add data for alternate ids
if cardMetadata.alternate_ids ~= nil then
@ -172,37 +194,35 @@ function addCardToIndex(cardData)
end
end
-- Creates the supplemental indexes for classes, weaknesses etc.
function buildSupplementalIndexes()
for cardId, card in pairs(cardIdIndex) do
local cardMetadata = card.metadata
-- If the ID key and the metadata ID don't match this is a duplicate card created by an alternate_id, and we should skip it
if cardId == cardMetadata.id then
if cardId == card.metadata.id then
-- Add card to the basic weakness list, if appropriate. Some weaknesses have multiple copies, and are added multiple times
if cardMetadata.weakness then
table.insert(uniqueWeaknessList, cardMetadata.id)
if cardMetadata.basicWeaknessCount ~= nil then
for i = 1, cardMetadata.basicWeaknessCount do
table.insert(basicWeaknessList, cardMetadata.id)
if card.metadata.weakness then
table.insert(uniqueWeaknessList, card.metadata.id)
if card.metadata.basicWeaknessCount ~= nil then
for i = 1, card.metadata.basicWeaknessCount do
table.insert(basicWeaknessList, card.metadata.id)
end
end
end
-- Excludes signature cards (which have no class or level)
if cardMetadata.class ~= nil and cardMetadata.level ~= nil then
local upgradeKey
if cardMetadata.level > 0 then
if card.metadata.class ~= nil and card.metadata.level ~= nil then
local upgradeKey = "-level0"
if card.metadata.level > 0 then
upgradeKey = "-upgrade"
else
upgradeKey = "-level0"
end
-- parse classes (separated by "|") and add the card to the appropriate class and level indices
for str in cardMetadata.class:gmatch("([^|]+)") do
table.insert(classAndLevelIndex[str .. upgradeKey], cardMetadata.id)
for str in card.metadata.class:gmatch("([^|]+)") do
table.insert(classAndLevelIndex[str .. upgradeKey], card.metadata.id)
end
-- add to cycle index
local cycleName = cardMetadata.cycle
local cycleName = card.metadata.cycle
if cycleName ~= nil then
cycleName = string.lower(cycleName)
@ -211,12 +231,17 @@ function buildSupplementalIndexes()
-- override cycle name for night of the zealot
cycleName = cycleName:gsub("the night of the zealot", "core")
if cycleIndex[cycleName] == nil then
cycleIndex[cycleName] = { }
end
table.insert(cycleIndex[cycleName], cardMetadata.id)
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
end
end
@ -250,114 +275,204 @@ function cardComparator(id1, id2)
end
end
-- inform the player card panel about the presence of other cards (no cycle -> fan-made)
function updatePlayerCardPanel()
local panel = guidReferenceApi.getObjectByOwnerAndType("Mythos", "PlayerCardPanel")
panel.call("createXML", otherCardsDetected)
end
---@return boolean: If true, the bag is currently not indexing and ready to be accessed
function isIndexReady()
if not indexingDone then
broadcastToAll("Still loading player cards, please try again in a few seconds", { 0.9, 0.2, 0.2 })
end
return indexingDone
end
-- Returns a specific card from the bag, based on ArkhamDB ID
-- Params table:
-- id: String ID of the card to retrieve
-- Return: If the indexes are still being constructed, an empty table is
-- returned. Otherwise, a single table with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
---@param params table ID of the card to retrieve
---@return table: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a single table with the following fields
-- data: TTS object data, suitable for spawning the card
-- metadata: Table of parsed metadata
function getCardById(params)
if (not indexingDone) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return { }
end
if not isIndexReady() then return {} end
return cardIdIndex[params.id]
end
-- Returns a list of cards from the bag matching a class and level (0 or upgraded)
-- Params table:
-- class: String class to retrieve ("Guardian", "Seeker", etc)
-- isUpgraded: true for upgraded cards (Level 1-5), false for Level 0
-- Return: If the indexes are still being constructed, returns an empty table.
-- Otherwise, a list of tables, each with the following fields
-- cardData: TTS object data, suitable for spawning the card
-- cardMetadata: Table of parsed metadata
---@param params table
-- class: String class to retrieve ("Guardian", "Seeker", etc)
-- isUpgraded: true for upgraded cards (Level 1-5), false for Level 0
---@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
function getCardsByClassAndLevel(params)
if (not indexingDone) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return { }
end
local upgradeKey
if (params.upgraded) then
if not isIndexReady() then return {} end
local upgradeKey = "-level0"
if params.upgraded then
upgradeKey = "-upgrade"
end
return classAndLevelIndex[params.class .. upgradeKey]
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
---@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
function getCardsByCycle(params)
if not isIndexReady() then return {} end
if not params.sortByMetadata then
return cycleIndex[string.lower(params.cycle)]
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)
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]
-- 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
upgradeKey = "-level0"
return card1.data.Description < card2.data.Description
end
return classAndLevelIndex[params.class..upgradeKey];
end
function getCardsByCycle(cycleName)
if (not indexingDone) then
broadcastToAll("Still loading player cards, please try again in a few seconds", {0.9, 0.2, 0.2})
return { }
-- 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
}
local classValue = 0
for str in s:gmatch("([^|]+)") do
-- this sorts multiclass cards
classValue = classValue * 10 + classValueList[str]
end
return cycleIndex[string.lower(cycleName)]
return classValue
end
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
-- Searches the bag for cards which match the given name and returns a list. Note that this is
-- an O(n) search without index support. It may be slow.
-- Parameter array must contain these fields to define the search:
-- name String or string fragment to search for names
-- exact Whether the name match should be exact
-- name: String or string fragment to search for names
-- exact: Whether the name match should be exact
function getCardsByName(params)
local name = params.name
local exact = params.exact
local results = { }
local results = {}
-- Track cards (by ID) that we've added to avoid duplicates that may come from alternate IDs
local addedCards = { }
local addedCards = {}
for _, cardData in pairs(cardIdIndex) do
if (not addedCards[cardData.metadata.id]) then
if (exact and (string.lower(cardData.data.Nickname) == string.lower(name)))
or (not exact and string.find(string.lower(cardData.data.Nickname), string.lower(name), 1, true)) then
table.insert(results, cardData)
addedCards[cardData.metadata.id] = true
table.insert(results, cardData)
addedCards[cardData.metadata.id] = true
end
end
end
return results
end
-- Gets a random basic weakness from the bag. Once a given ID has been returned
-- it will be removed from the list and cannot be selected again until a reload
-- occurs or the indexes are rebuilt, which will refresh the list to include all
-- weaknesses.
-- Return: String ID of the selected weakness.
-- Gets a random basic weakness from the bag. Once a given ID has been returned it will be
-- removed from the list and cannot be selected again until a reload occurs or the indexes
-- are rebuilt, which will refresh the list to include all weaknesses.
---@return string: ID of the selected weakness
function getRandomWeaknessId()
local availableWeaknesses = buildAvailableWeaknesses()
if (#availableWeaknesses > 0) then
local availableWeaknesses = buildAvailableWeaknesses()
if #availableWeaknesses > 0 then
return availableWeaknesses[math.random(#availableWeaknesses)]
end
end
-- Constructs a list of available basic weaknesses by starting with the full pool of basic
-- weaknesses then removing any which are currently in the play or deck construction areas
-- Return: Table array of weakness IDs which are valid to choose from
function buildAvailableWeaknesses()
local weaknessesInPlay = { }
---@param traits? string Trait(s) to use as filter
---@return table: Array of weakness IDs which are valid to choose from
function buildAvailableWeaknesses(traits)
local weaknessesInPlay = {}
local allObjects = getAllObjects()
for _, object in ipairs(allObjects) do
if (object.name == "Deck") then
if object.type == "Deck" then
for _, cardData in ipairs(object.getData().ContainedObjects) do
local cardMetadata = JSON.decode(cardData.GMNotes)
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
incrementWeaknessCount(weaknessesInPlay, JSON.decode(cardData.GMNotes))
end
elseif (object.name == "Card") then
local cardMetadata = JSON.decode(object.getGMNotes())
incrementWeaknessCount(weaknessesInPlay, cardMetadata)
elseif object.type == "Card" then
incrementWeaknessCount(weaknessesInPlay, JSON.decode(object.getGMNotes()))
end
end
local availableWeaknesses = { }
local availableWeaknesses = {}
for _, weaknessId in ipairs(basicWeaknessList) do
if (weaknessesInPlay[weaknessId] ~= nil and weaknessesInPlay[weaknessId] > 0) then
weaknessesInPlay[weaknessId] = weaknessesInPlay[weaknessId] - 1
else
table.insert(availableWeaknesses, weaknessId)
if traits then
-- split the string into separate traits (separated by "|")
local allowedTraits = {}
for str in traits:gmatch("([^|]+)") do
-- remove dots
str = str:gsub("[%.]", "")
-- remove leading and trailing whitespace
str = str:match("^%s*(.-)%s*$")
-- make sure string ends with a dot
str = string.lower(str .. ".")
table.insert(allowedTraits, str)
end
-- make sure the trait is present on the weakness
local card = cardIdIndex[weaknessId]
for _, allowedTrait in ipairs(allowedTraits) do
if string.contains(string.lower(card.metadata.traits), allowedTrait) then
table.insert(availableWeaknesses, weaknessId)
break
end
end
else
table.insert(availableWeaknesses, weaknessId)
end
end
end
return availableWeaknesses
@ -373,8 +488,8 @@ end
-- Helper function that adds one to the table entry for the number of weaknesses in play
function incrementWeaknessCount(table, cardMetadata)
if (isBasicWeakness(cardMetadata)) then
if (table[cardMetadata.id] == nil) then
if isBasicWeakness(cardMetadata) then
if table[cardMetadata.id] == nil then
table[cardMetadata.id] = 1
else
table[cardMetadata.id] = table[cardMetadata.id] + 1
@ -384,9 +499,61 @@ end
function isBasicWeakness(cardMetadata)
return cardMetadata ~= nil
and cardMetadata.weakness
and cardMetadata.basicWeaknessCount ~= nil
and cardMetadata.basicWeaknessCount > 0
and cardMetadata.weakness
and cardMetadata.basicWeaknessCount ~= nil
and cardMetadata.basicWeaknessCount > 0
end
end)
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local GUIDReferenceApi = {}
local function getGuidHandler()
return getObjectFromGUID("123456")
end
-- Returns the matching object
---@param owner string Parent object for this search
---@param type string Type of object to search for
---@return any: Object reference to the matching object
GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)
return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type })
end
-- Returns all matching objects as a table with references
---@param type string Type of object to search for
---@return table: List of object references to matching objects
GUIDReferenceApi.getObjectsByType = function(type)
return getGuidHandler().call("getObjectsByType", type)
end
-- Returns all matching objects as a table with references
---@param owner string Parent object for this search
---@return table: List of object references to matching objects
GUIDReferenceApi.getObjectsByOwner = function(owner)
return getGuidHandler().call("getObjectsByOwner", owner)
end
-- Sends new information to the reference handler to edit the main index
---@param owner string Parent of the object
---@param type string Type of the object
---@param guid string GUID of the object
GUIDReferenceApi.editIndex = function(owner, type, guid)
return getGuidHandler().call("editIndex", {
owner = owner,
type = type,
guid = guid
})
end
-- Returns the owner of an object or the object it's located on
---@param object tts__GameObject Object for this search
---@return string: Parent of the object or object it's located on
GUIDReferenceApi.getOwnerOfObject = function(object)
return getGuidHandler().call("getOwnerOfObject", object)
end
return GUIDReferenceApi
end
end)
return __bundle_require("__root")

View File

@ -20,7 +20,7 @@ CustomDeck:
Description: Advanced
DragSelectable: true
GMNotes: "{\n \"id\": \"90040\",\n \"type\": \"Treachery\",\n \"class\": \"Neutral\",\n
\ \"traits\": \"Madness.\",\n \"weakness\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"traits\": \"Madness.\",\n \"weakness\": true,\n \"cycle\": \"Red Tide Rising\"\n}"
GUID: 89fe92
Grid: true
GridProjection: false

View File

@ -20,8 +20,9 @@ CustomDeck:
Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"02266-t\",\n \"type\": \"Event\",\n \"class\": \"Rogue\",\n
\ \"cost\": 0,\n \"level\": 3,\n \"traits\": \"Trick.\",\n \"cycle\": \"The Dunwich
Legacy\"\n}"
\ \"cost\": 0,\n \"level\": 3,\n \"traits\": \"Trick.\",\n \"uses\": [\n {\n
\ \"count\": 3,\n \"type\": \"Universal\",\n \"token\": \"universalActionAbility\"\n
\ }\n ],\n \"cycle\": \"The Dunwich Legacy\"\n}"
GUID: e92f21
Grid: true
GridProjection: false

View File

@ -20,8 +20,9 @@ CustomDeck:
Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"02266\",\n \"type\": \"Event\",\n \"class\": \"Rogue\",\n
\ \"cost\": 0,\n \"level\": 3,\n \"traits\": \"Trick.\",\n \"cycle\": \"The Dunwich
Legacy\"\n}"
\ \"cost\": 0,\n \"level\": 3,\n \"traits\": \"Trick.\",\n \"uses\": [\n {\n
\ \"count\": 3,\n \"type\": \"Universal\",\n \"token\": \"universalActionAbility\"\n
\ }\n ],\n \"cycle\": \"The Dunwich Legacy\"\n}"
GUID: 074858
Grid: true
GridProjection: false

View File

@ -21,14 +21,15 @@ Description: The Waitress
DragSelectable: true
GMNotes: "{\n \"id\": \"01004-pb\",\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 \"deck_requirements\":
{\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"01012\": 1,\n \"90018\": 1\n },\n {\n \"01013\":
1,\n \"90019\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"mystic\",\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"spell\",\n
\ \"occult\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
3\n }\n }\n ]\n}"
2,\n \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"Bad Blood\",\n
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"01012\":
1,\n \"90018\": 1\n },\n {\n \"01013\": 1,\n \"90019\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"spell\",\n \"occult\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ }\n ]\n}"
GUID: 909f30
Grid: true
GridProjection: false

View File

@ -21,14 +21,14 @@ Description: The Waitress
DragSelectable: true
GMNotes: "{\n \"id\": \"01004-pf\",\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 \"deck_requirements\":
{\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"01012\": 1,\n \"90018\": 1\n },\n {\n \"01013\":
1,\n \"90019\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"mystic\",\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"survivor\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n
\ }\n ]\n}"
2,\n \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"Bad Blood\",\n
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"01012\":
1,\n \"90018\": 1\n },\n {\n \"01013\": 1,\n \"90019\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"survivor\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
GUID: 02db0a
Grid: true
GridProjection: false

View File

@ -21,14 +21,15 @@ Description: The Waitress
DragSelectable: true
GMNotes: "{\n \"id\": \"01004-p\",\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 \"deck_requirements\":
{\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"01012\": 1,\n \"90018\": 1\n },\n {\n \"01013\":
1,\n \"90019\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"mystic\",\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"spell\",\n
\ \"occult\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
3\n }\n }\n ]\n}"
\ \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"Bad Blood\",\n \"extraToken\":
\"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"01012\": 1,\n \"90018\": 1\n
\ },\n {\n \"01013\": 1,\n \"90019\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"spell\",\n \"occult\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ }\n ]\n}"
GUID: 01b6ef
Grid: true
GridProjection: false

View File

@ -22,10 +22,10 @@ DragSelectable: true
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 \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"01012\": 1,\n \"90018\": 1\n
\ },\n {\n \"01013\": 1,\n \"90019\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\"Core\",\n \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"01012\":
1,\n \"90018\": 1\n },\n {\n \"01013\": 1,\n \"90019\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"survivor\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
@ -70,15 +70,15 @@ States:
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 \"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"
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"
GUID: 6797bb
Grid: true
GridProjection: false

View File

@ -81,12 +81,12 @@ States:
- Minicard
Tooltip: true
Transform:
posX: 40.7253036
posY: 1.29860592
posZ: 66.7765656
rotX: 1.697304e-07
rotY: 270.0102
rotZ: 2.00479718e-07
posX: 40
posY: 1.3
posZ: 66
rotX: 0
rotY: 270
rotZ: 0
scaleX: 0.6
scaleY: 1
scaleZ: 0.6

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"03004\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Sorcerer.\",\n \"willpowerIcons\": 5,\n \"intellectIcons\": 2,\n
\ \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Path to Carcosa\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"03014\": 1\n },\n {\n \"03015\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":

View File

@ -22,12 +22,13 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"10009\",\n \"type\": \"Investigator\",\n \"class\": \"Rogue\",\n
\ \"traits\": \"Drifter. Socialite.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\":
4,\n \"combatIcons\": 2,\n \"agilityIcons\": 4,\n \"cycle\": \"The Feast of Hemlock
Vale\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"10010\": 3\n },\n {\n \"10011\":
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 \"special\": [\n \"parley\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n }\n ]\n}"
Vale\",\n \"extraToken\": \"Parley\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"10010\":
3\n },\n {\n \"10011\": 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
\ \"special\": [\n \"parley\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n }\n ]\n}"
GUID: 54eaa5
Grid: true
GridProjection: false

View File

@ -22,13 +22,14 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"07002\",\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n
\ \"traits\": \"Miskatonic. Scholar.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\":
2,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"The Innsmouth
Conspiracy\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"07008\": 1\n },\n {\n \"07009\":
1\n }\n ]\n \n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"seeker\",\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"trait\": [\n \"practiced\"\n
\ ],\n \"type\": [\n \"skill\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 3\n }\n }\n ]\n}"
Conspiracy\",\n \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"07008\":
1\n },\n {\n \"07009\": 1\n }\n ]\n \n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"seeker\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"trait\": [\n \"practiced\"\n ],\n \"type\": [\n \"skill\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ }\n ]\n}"
GUID: 05b950
Grid: true
GridProjection: false

View File

@ -22,13 +22,13 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"09011\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Chosen. Cursed.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\":
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Scarlet Keys\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"09012\": 1,\n \"09013\": 1\n
\ },\n {\n \"09014\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"mystic\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"trait\": [\n \"charm\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 4\n }\n }\n ]\n}"
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"09012\":
1,\n \"09013\": 1\n },\n {\n \"09014\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"trait\": [\n \"charm\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 4\n }\n }\n ]\n}"
GUID: 4c2a3d
Grid: true
GridProjection: false

View File

@ -0,0 +1,238 @@
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("playercards/CardsWithHelper", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
else
updateDisplay()
end
end
-- forces a new state
function setHelperState(newState)
isHelperEnabled = newState
updateSave()
updateDisplay()
end
-- toggles the current state
function toggleHelper()
isHelperEnabled = not isHelperEnabled
updateSave()
updateDisplay()
end
-- updates the visibility and calls events (after a small delay to allow XML being set)
function updateDisplay()
Wait.frames(actualDisplayUpdate, 5)
end
function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end
end)
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
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
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/Analysis")
end)
__bundle_register("playercards/cards/Analysis", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")
end)
__bundle_register("playercards/CardsThatRedrawTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that return and redraw tokens
This file is used to add an XML button to a card, turned on via context menu.
Valid options modify the appearance of the XML button, as well as the
behavior of the return and redraw function. Set options before requiring this file.
Parameters for the return and redraw functions. Typically set VALID_TOKENS or INVALID_TOKENS, not both.
If there are no restrictions on which tokens can be redrawn (e.g. Wendy Adams), keep both empty.
* VALID_TOKENS --@type table
- keyed table which lists all tokens that can be redrawn by the card
- example usage: "False Covenant"
> VALID_TOKENS = {
> ["Curse"] = true
> }
* INVALID_TOKENS --@type table
- keyed table which lists all tokens that cannot be redrawn by the card
- example usage: "Custom Ammunition"
> INVALID_TOKENS = {
> ["Auto-fail"] = true
> }
* DRAW_SPECIFIC_TOKEN --@type string (name of token or nil)
- if set, will attempt to draw that specific token
* RETURN_TO_POOL --@type string
- allows for the name of the card to be passed onto Global for any special handling
The following parameters modify the appearence of the XML button and are not listed as part of a table.
- buttonHeight (default is 450)
- buttonWidth (default is 1400)
- buttonPosition (default is "0 -55 -22")
- buttonFontSize (default is 250)
- buttonRotation (change if button is placed on an investigator cards)
- buttonLabel (default is "Redraw Token")
- buttonIcon (to add an icon to the right)
- buttonColor (default is "#77674DE6")
----------------------------------------------------------
EXAMPLE: Claypool's Furs
This card can only redraw the Frost token, and is replaced with a random token from the bag.
As a nice reminder the XML button takes on the Frost color and icon with the text "Cancel".
> buttonValue = "Cancel"
> buttonIcon = "token-frost"
> buttonColor = "#404450E6"
> buttonFontSize = 300
> VALID_TOKENS = {
> ["Frost"] = true
> }
>
> require...
----------------------------------------------------------]]
-- intentionally global
hasXML = true
isHelperEnabled = false
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
createHelperXML()
syncDisplayWithOptionPanel()
end
function createHelperXML()
local xmlTable = { {
tag = "Button",
attributes = {
active = "false",
id = "Helper",
height = buttonHeight or 450,
width = buttonWidth or 1400,
rotation = buttonRotation or "0 0 180",
scale = "0.1 0.1 1",
position = buttonPosition or "0 -55 -22",
padding = "50 50 50 50",
font = "font_teutonic-arkham",
fontSize = buttonFontSize or 250,
onClick = "triggerXMLTokenLabelCreation",
color = buttonColor or "#77674DE6",
textColor = "White"
},
value = buttonLabel or "Redraw Token"
} }
if buttonIcon then
xmlTable[1].attributes.iconWidth = "400"
xmlTable[1].attributes.iconAlignment = "Right"
xmlTable[1].attributes.icon = buttonIcon
end
self.UI.setXmlTable(xmlTable)
end
function triggerXMLTokenLabelCreation()
Global.call("activeRedrawEffect", {
VALID_TOKENS = VALID_TOKENS,
INVALID_TOKENS = INVALID_TOKENS,
RETURN_TO_POOL = RETURN_TO_POOL
})
end
end)
return __bundle_require("__root")

View File

@ -30,7 +30,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: ''
LuaScript: !include 'Card Analysis 80285f.ttslua'
LuaScriptState: ''
MeasureMovement: false
Name: Card

View File

@ -21,11 +21,11 @@ Description: The Drifter
DragSelectable: true
GMNotes: "{\n \"id\": \"02005-pb\",\n \"type\": \"Investigator\",\n \"class\":
\"Survivor\",\n \"traits\": \"Drifter.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
2,\n \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90047\": 1,\n \"02014\": 1\n
\ },\n {\n \"90048\": 1,\n \"02015\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\"\n
2,\n \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"On the Road Again\",\n
\ \"extraToken\": \"FreeTrigger\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90047\":
1,\n \"02014\": 1\n },\n {\n \"90048\": 1,\n \"02015\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ },\n {\n \"faction\": [\n \"neutral\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n \"trait\":

View File

@ -21,11 +21,11 @@ Description: The Drifter
DragSelectable: true
GMNotes: "{\n \"id\": \"02005-pf\",\n \"type\": \"Investigator\",\n \"class\":
\"Survivor\",\n \"traits\": \"Drifter.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
2,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90047\": 1,\n \"02014\": 1\n
\ },\n {\n \"90048\": 1,\n \"02015\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\",\n
2,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"On the Road Again\",\n
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90047\":
1,\n \"02014\": 1\n },\n {\n \"90048\": 1,\n \"02015\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\",\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

View File

@ -21,11 +21,11 @@ Description: The Drifter
DragSelectable: true
GMNotes: "{\n \"id\": \"02005-p\",\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n
\ \"traits\": \"Drifter.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\": 2,\n
\ \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90047\": 1,\n \"02014\": 1\n
\ },\n {\n \"90048\": 1,\n \"02015\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\"\n
\ \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"On the Road Again\",\n
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90047\":
1,\n \"02014\": 1\n },\n {\n \"90048\": 1,\n \"02015\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ },\n {\n \"faction\": [\n \"neutral\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n \"trait\":

View File

@ -22,10 +22,10 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"02005\",\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n
\ \"traits\": \"Drifter.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\": 2,\n
\ \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90047\": 1,\n \"02014\": 1\n
\ },\n {\n \"90048\": 1,\n \"02015\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\",\n
\ \"extraToken\": \"FreeTrigger\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90047\":
1,\n \"02014\": 1\n },\n {\n \"90048\": 1,\n \"02015\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\",\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

View File

@ -21,7 +21,9 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"09091\",\n \"type\": \"Asset\",\n \"slot\": \"Body\",\n
\ \"class\": \"Mystic\",\n \"cost\": 3,\n \"level\": 2,\n \"traits\": \"Ritual.\",\n
\ \"combatIcons\": 1,\n \"cycle\": \"The Scarlet Keys\"\n}"
\ \"combatIcons\": 1,\n \"uses\": [\n {\n \"count\": 1,\n \"type\":
\"PlayItem\",\n \"token\": \"universalActionAbility\"\n }\n ],\n \"cycle\":
\"The Scarlet Keys\"\n}"
GUID: b5d894
Grid: true
GridProjection: false

View File

@ -39,7 +39,7 @@ SidewaysCard: false
Snap: true
Sticky: true
Tags:
- ScenarioCard
- PlayerCard
Tooltip: true
Transform:
posX: 48.98

View File

@ -21,7 +21,8 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"10129\",\n \"type\": \"Event\",\n \"class\": \"Neutral\",\n
\ \"cost\": 0,\n \"level\": 0,\n \"traits\": \"Double.\",\n \"wildIcons\": 1,\n
\ \"cycle\": \"The Feast of Hemlock Vale\"\n}"
\ \"uses\": [\n {\n \"count\": 2,\n \"type\": \"Universal\",\n \"token\":
\"universalActionAbility\"\n }\n ],\n \"cycle\": \"The Feast of Hemlock Vale\"\n}"
GUID: 24d3b3
Grid: true
GridProjection: false

View File

@ -22,16 +22,17 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"08016\",\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n
\ \"traits\": \"Entrepreneur.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\":
4,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"Edge of the Earth\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"08017\": 1\n },\n {\n \"08018\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 0\n }\n
\ },\n {\n \"faction\": [\n \"neutral\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n \"faction\":
[\n \"rogue\"\n ],\n \"level\": {\n \"min\": 1,\n \"max\":
\ \"extraToken\": \"PlayItem\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"08017\":
1\n },\n {\n \"08018\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"survivor\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 0\n }\n },\n {\n \"faction\":
[\n \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"rogue\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 0\n },\n \"limit\": 5,\n \"error\":
\"You cannot have more than 5 level 0 Rogue cards\"\n }\n ]\n}"
{\n \"min\": 1,\n \"max\": 5\n }\n },\n {\n \"faction\":
[\n \"rogue\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
0\n },\n \"limit\": 5,\n \"error\": \"You cannot have more than 5
level 0 Rogue cards\"\n }\n ]\n}"
GUID: 419b0c
Grid: true
GridProjection: false

View File

@ -14,18 +14,18 @@
onClick="resolveToken"
textColor="white"
active="false"/>
<Panel position="0 -55 -22"
<TableLayout position="0 -55 -22"
rotation="0 0 180"
height="900"
width="1400"
scale="0.1 0.1 1"/>
<TableLayout active="false"
scale="0.1 0.1 1"
cellSpacing="80"
cellBackgroundColor="rgba(1,1,1,0)"/>
</Defaults>
<Panel>
<TableLayout id="actives">
<Panel id="Helper"
active="false">
<TableLayout>
<Row>
<Cell>
<Button id="Bless"
@ -43,10 +43,7 @@
</Cell>
</Row>
</TableLayout>
</Panel>
<Panel>
<TableLayout id="inactives">
<TableLayout>
<Row>
<Cell>
<Button id="inactiveBless"

View File

@ -22,12 +22,13 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"04005\",\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n
\ \"traits\": \"Cursed. Drifter.\",\n \"willpowerIcons\": 0,\n \"intellectIcons\":
0,\n \"combatIcons\": 0,\n \"agilityIcons\": 0,\n \"cycle\": \"The Forgotten
Age\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"04015\": 1\n },\n {\n \"04016\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"trait\": [\n \"spirit\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 3\n }\n }\n ]\n}"
Age\",\n \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"04015\":
1\n },\n {\n \"04016\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"survivor\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"trait\": [\n \"spirit\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 3\n }\n }\n ]\n}"
GUID: b02a1e
Grid: true
GridProjection: false

View File

@ -22,20 +22,20 @@ DragSelectable: true
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 \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"05007\": 1,\n \"98011\": 1\n },\n
\ {\n \"05008\": 1,\n \"98012\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"not\": true,\n \"trait\": [\n \"weapon\"\n ],\n
\ \"level\": {\n \"min\": 1,\n \"max\": 5\n }\n },\n {\n
\ \"faction\": [\n \"guardian\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 3\n }\n },\n {\n \"faction\": [\n \"neutral\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n
\ },\n {\n \"special\": [\n \"heals_horror\"\n ],\n \"tag\":
[\n \"hh\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"seeker\",\n \"mystic\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 1\n },\n
\ \"limit\": 15,\n \"error\": \"You cannot have more than 15 level 0-1
Seeker and/or Mystic cards\"\n }\n ]\n}"
\"The Circle Undone\",\n \"extraToken\": \"None\",\n \"deck_requirements\": {\n
\ \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n
\ {\n \"05007\": 1,\n \"98011\": 1\n },\n {\n \"05008\":
1,\n \"98012\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"not\":
true,\n \"trait\": [\n \"weapon\"\n ],\n \"level\": {\n \"min\":
1,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"guardian\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ },\n {\n \"faction\": [\n \"neutral\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n \"special\":
[\n \"heals_horror\"\n ],\n \"tag\": [\n \"hh\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"faction\": [\n \"seeker\",\n \"mystic\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 1\n },\n \"limit\": 15,\n \"error\":
\"You cannot have more than 15 level 0-1 Seeker and/or Mystic cards\"\n }\n ]\n}"
GUID: b03b12
Grid: true
GridProjection: false
@ -77,23 +77,24 @@ States:
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 \"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\":
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
\ \"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"
\ \"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"
GUID: 9900a3
Grid: true
GridProjection: false

View File

@ -22,20 +22,21 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"09001\",\n \"type\": \"Investigator\",\n \"class\": \"Guardian\",\n
\ \"traits\": \"Assistant.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\": 2,\n
\ \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"The Scarlet Keys\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"09002\": 2\n },\n {\n \"09003\":
1\n }\n ],\n \"choices\": 1\n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"guardian\",\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"choiceName\": \"Seeker\",\n
\ \"faction\": [\n \"seeker\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 1\n },\n \"type\": [\n \"event\",\n \"skill\"\n
\ ],\n \"limit\": 10\n },\n {\n \"choiceName\": \"Mystic\",\n
\ \"faction\": [\n \"mystic\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 1\n },\n \"type\": [\n \"event\",\n \"skill\"\n
\ ],\n \"limit\": 10\n },\n {\n \"choiceName\": \"Survivor\",\n
\ \"faction\": [\n \"survivor\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 1\n },\n \"type\": [\n \"event\",\n \"skill\"\n
\ ],\n \"limit\": 10\n }\n ]\n}"
\ \"extraToken\": \"Activate\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"09002\":
2\n },\n {\n \"09003\": 1\n }\n ],\n \"choices\": 1\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"guardian\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"choiceName\": \"Seeker\",\n \"faction\":
[\n \"seeker\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
1\n },\n \"type\": [\n \"event\",\n \"skill\"\n ],\n
\ \"limit\": 10\n },\n {\n \"choiceName\": \"Mystic\",\n \"faction\":
[\n \"mystic\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
1\n },\n \"type\": [\n \"event\",\n \"skill\"\n ],\n
\ \"limit\": 10\n },\n {\n \"choiceName\": \"Survivor\",\n \"faction\":
[\n \"survivor\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
1\n },\n \"type\": [\n \"event\",\n \"skill\"\n ],\n
\ \"limit\": 10\n }\n ]\n}"
GUID: dc96d1
Grid: true
GridProjection: false

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"09018\",\n \"type\": \"Investigator\",\n \"class\": \"Neutral\",\n
\ \"traits\": \"Civic. Socialite.\",\n \"willpowerIcons\": 1,\n \"intellectIcons\":
1,\n \"combatIcons\": 1,\n \"agilityIcons\": 1,\n \"cycle\": \"The Scarlet Keys\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"09019\": 1\n },\n {\n \"09020\":
1\n }\n ],\n \"choices\": 2\n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":

View File

@ -0,0 +1,247 @@
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("playercards/CardsThatRedrawTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that return and redraw tokens
This file is used to add an XML button to a card, turned on via context menu.
Valid options modify the appearance of the XML button, as well as the
behavior of the return and redraw function. Set options before requiring this file.
Parameters for the return and redraw functions. Typically set VALID_TOKENS or INVALID_TOKENS, not both.
If there are no restrictions on which tokens can be redrawn (e.g. Wendy Adams), keep both empty.
* VALID_TOKENS --@type table
- keyed table which lists all tokens that can be redrawn by the card
- example usage: "False Covenant"
> VALID_TOKENS = {
> ["Curse"] = true
> }
* INVALID_TOKENS --@type table
- keyed table which lists all tokens that cannot be redrawn by the card
- example usage: "Custom Ammunition"
> INVALID_TOKENS = {
> ["Auto-fail"] = true
> }
* DRAW_SPECIFIC_TOKEN --@type string (name of token or nil)
- if set, will attempt to draw that specific token
* RETURN_TO_POOL --@type string
- allows for the name of the card to be passed onto Global for any special handling
The following parameters modify the appearence of the XML button and are not listed as part of a table.
- buttonHeight (default is 450)
- buttonWidth (default is 1400)
- buttonPosition (default is "0 -55 -22")
- buttonFontSize (default is 250)
- buttonRotation (change if button is placed on an investigator cards)
- buttonLabel (default is "Redraw Token")
- buttonIcon (to add an icon to the right)
- buttonColor (default is "#77674DE6")
----------------------------------------------------------
EXAMPLE: Claypool's Furs
This card can only redraw the Frost token, and is replaced with a random token from the bag.
As a nice reminder the XML button takes on the Frost color and icon with the text "Cancel".
> buttonValue = "Cancel"
> buttonIcon = "token-frost"
> buttonColor = "#404450E6"
> buttonFontSize = 300
> VALID_TOKENS = {
> ["Frost"] = true
> }
>
> require...
----------------------------------------------------------]]
-- intentionally global
hasXML = true
isHelperEnabled = false
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
createHelperXML()
syncDisplayWithOptionPanel()
end
function createHelperXML()
local xmlTable = { {
tag = "Button",
attributes = {
active = "false",
id = "Helper",
height = buttonHeight or 450,
width = buttonWidth or 1400,
rotation = buttonRotation or "0 0 180",
scale = "0.1 0.1 1",
position = buttonPosition or "0 -55 -22",
padding = "50 50 50 50",
font = "font_teutonic-arkham",
fontSize = buttonFontSize or 250,
onClick = "triggerXMLTokenLabelCreation",
color = buttonColor or "#77674DE6",
textColor = "White"
},
value = buttonLabel or "Redraw Token"
} }
if buttonIcon then
xmlTable[1].attributes.iconWidth = "400"
xmlTable[1].attributes.iconAlignment = "Right"
xmlTable[1].attributes.icon = buttonIcon
end
self.UI.setXmlTable(xmlTable)
end
function triggerXMLTokenLabelCreation()
Global.call("activeRedrawEffect", {
VALID_TOKENS = VALID_TOKENS,
INVALID_TOKENS = INVALID_TOKENS,
RETURN_TO_POOL = RETURN_TO_POOL
})
end
end)
__bundle_register("playercards/CardsWithHelper", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
else
updateDisplay()
end
end
-- forces a new state
function setHelperState(newState)
isHelperEnabled = newState
updateSave()
updateDisplay()
end
-- toggles the current state
function toggleHelper()
isHelperEnabled = not isHelperEnabled
updateSave()
updateDisplay()
end
-- updates the visibility and calls events (after a small delay to allow XML being set)
function updateDisplay()
Wait.frames(actualDisplayUpdate, 5)
end
function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end
end)
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
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
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/ClaypoolsFurs")
end)
__bundle_register("playercards/cards/ClaypoolsFurs", function(require, _LOADED, __bundle_register, __bundle_modules)
buttonLabel = "Cancel"
buttonIcon = "token-frost"
buttonColor = "#404450E6"
buttonFontSize = 300
VALID_TOKENS = {
["Frost"] = true
}
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")
end)
return __bundle_require("__root")

View File

@ -30,7 +30,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: ''
LuaScript: !include 'Card Claypool''s Furs c1f999.ttslua'
LuaScriptState: ''
MeasureMovement: false
Name: Card

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"90031\",\n \"type\": \"Treachery\",\n \"class\": \"Neutral\",\n
\ \"traits\": \"Task.\",\n \"weakness\": true,\n \"uses\": [\n {\n \"count\":
4,\n \"type\": \"Clue\",\n \"token\": \"clue\"\n }\n ],\n \"cycle\":
\"Standalone\"\n}"
\"By the Book\"\n}"
GUID: f802e3
Grid: true
GridProjection: false

View File

@ -0,0 +1,242 @@
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/CustomModifications")
end)
__bundle_register("playercards/cards/CustomModifications", function(require, _LOADED, __bundle_register, __bundle_modules)
INVALID_TOKENS = {
["Auto-fail"] = true
}
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")
end)
__bundle_register("playercards/CardsThatRedrawTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that return and redraw tokens
This file is used to add an XML button to a card, turned on via context menu.
Valid options modify the appearance of the XML button, as well as the
behavior of the return and redraw function. Set options before requiring this file.
Parameters for the return and redraw functions. Typically set VALID_TOKENS or INVALID_TOKENS, not both.
If there are no restrictions on which tokens can be redrawn (e.g. Wendy Adams), keep both empty.
* VALID_TOKENS --@type table
- keyed table which lists all tokens that can be redrawn by the card
- example usage: "False Covenant"
> VALID_TOKENS = {
> ["Curse"] = true
> }
* INVALID_TOKENS --@type table
- keyed table which lists all tokens that cannot be redrawn by the card
- example usage: "Custom Ammunition"
> INVALID_TOKENS = {
> ["Auto-fail"] = true
> }
* DRAW_SPECIFIC_TOKEN --@type string (name of token or nil)
- if set, will attempt to draw that specific token
* RETURN_TO_POOL --@type string
- allows for the name of the card to be passed onto Global for any special handling
The following parameters modify the appearence of the XML button and are not listed as part of a table.
- buttonHeight (default is 450)
- buttonWidth (default is 1400)
- buttonPosition (default is "0 -55 -22")
- buttonFontSize (default is 250)
- buttonRotation (change if button is placed on an investigator cards)
- buttonLabel (default is "Redraw Token")
- buttonIcon (to add an icon to the right)
- buttonColor (default is "#77674DE6")
----------------------------------------------------------
EXAMPLE: Claypool's Furs
This card can only redraw the Frost token, and is replaced with a random token from the bag.
As a nice reminder the XML button takes on the Frost color and icon with the text "Cancel".
> buttonValue = "Cancel"
> buttonIcon = "token-frost"
> buttonColor = "#404450E6"
> buttonFontSize = 300
> VALID_TOKENS = {
> ["Frost"] = true
> }
>
> require...
----------------------------------------------------------]]
-- intentionally global
hasXML = true
isHelperEnabled = false
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
createHelperXML()
syncDisplayWithOptionPanel()
end
function createHelperXML()
local xmlTable = { {
tag = "Button",
attributes = {
active = "false",
id = "Helper",
height = buttonHeight or 450,
width = buttonWidth or 1400,
rotation = buttonRotation or "0 0 180",
scale = "0.1 0.1 1",
position = buttonPosition or "0 -55 -22",
padding = "50 50 50 50",
font = "font_teutonic-arkham",
fontSize = buttonFontSize or 250,
onClick = "triggerXMLTokenLabelCreation",
color = buttonColor or "#77674DE6",
textColor = "White"
},
value = buttonLabel or "Redraw Token"
} }
if buttonIcon then
xmlTable[1].attributes.iconWidth = "400"
xmlTable[1].attributes.iconAlignment = "Right"
xmlTable[1].attributes.icon = buttonIcon
end
self.UI.setXmlTable(xmlTable)
end
function triggerXMLTokenLabelCreation()
Global.call("activeRedrawEffect", {
VALID_TOKENS = VALID_TOKENS,
INVALID_TOKENS = INVALID_TOKENS,
RETURN_TO_POOL = RETURN_TO_POOL
})
end
end)
__bundle_register("playercards/CardsWithHelper", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
else
updateDisplay()
end
end
-- forces a new state
function setHelperState(newState)
isHelperEnabled = newState
updateSave()
updateDisplay()
end
-- toggles the current state
function toggleHelper()
isHelperEnabled = not isHelperEnabled
updateSave()
updateDisplay()
end
-- updates the visibility and calls events (after a small delay to allow XML being set)
function updateDisplay()
Wait.frames(actualDisplayUpdate, 5)
end
function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end
end)
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
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
end)
return __bundle_require("__root")

View File

@ -44,7 +44,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: ''
LuaScript: !include 'Card Custom Modifications d2252d.ttslua'
LuaScriptState: ''
MeasureMovement: false
Name: Card

View File

@ -21,18 +21,19 @@ Description: The Librarian
DragSelectable: true
GMNotes: "{\n \"id\": \"01002-pb\",\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 \"deck_requirements\":
{\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"90002\": 1,\n \"01008\": 1\n },\n {\n \"90003\":
1,\n \"01009\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"trait\":
[\n \"tome\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"neutral\"\n ],\n
5,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Read or Die\",\n
\ \"extraToken\": \"Tome\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90002\": 1,\n \"01008\": 1\n
\ },\n {\n \"90003\": 1,\n \"01009\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"trait\": [\n \"tome\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"faction\": [\n \"seeker\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 3\n }\n },\n {\n \"faction\": [\n \"guardian\",\n
\ \"mystic\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
0\n },\n \"limit\": 5,\n \"error\": \"You cannot have more than 5
Guardian and/or Mystic cards\"\n }\n ]\n}"
\ \"faction\": [\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"seeker\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ },\n {\n \"faction\": [\n \"guardian\",\n \"mystic\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 0\n },\n
\ \"limit\": 5,\n \"error\": \"You cannot have more than 5 Guardian and/or
Mystic cards\"\n }\n ]\n}"
GUID: 2f2e0d
Grid: true
GridProjection: false

View File

@ -21,14 +21,14 @@ Description: The Librarian
DragSelectable: true
GMNotes: "{\n \"id\": \"01002-pf\",\n \"type\": \"Investigator\",\n \"class\":
\"Seeker\",\n \"traits\": \"Miskatonic.\",\n \"willpowerIcons\": 1,\n \"intellectIcons\":
5,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Core\",\n \"deck_requirements\":
{\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"90002\": 1,\n \"01008\": 1\n },\n {\n \"90003\":
1,\n \"01009\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\":
[\n \"seeker\",\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"mystic\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n
\ }\n ]\n}"
5,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Read or Die\",\n
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90002\": 1,\n \"01008\": 1\n
\ },\n {\n \"90003\": 1,\n \"01009\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"mystic\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
GUID: e8cafc
Grid: true
GridProjection: false

View File

@ -21,18 +21,19 @@ Description: The Librarian
DragSelectable: true
GMNotes: "{\n \"id\": \"01002-p\",\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n
\ \"traits\": \"Miskatonic.\",\n \"willpowerIcons\": 1,\n \"intellectIcons\":
5,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Core\",\n \"deck_requirements\":
{\n \"size\": 30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"90002\": 1,\n \"01008\": 1\n },\n {\n \"90003\":
1,\n \"01009\": 1\n }\n ]\n },\n \"deck_options\": [\n {\n \"trait\":
[\n \"tome\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"neutral\"\n ],\n
5,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Read or Die\",\n
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90002\": 1,\n \"01008\": 1\n
\ },\n {\n \"90003\": 1,\n \"01009\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"trait\": [\n \"tome\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"faction\": [\n \"seeker\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 3\n }\n },\n {\n \"faction\": [\n \"guardian\",\n
\ \"mystic\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
0\n },\n \"limit\": 5,\n \"error\": \"You cannot have more than 5
Guardian and/or Mystic cards\"\n }\n ]\n}"
\ \"faction\": [\n \"neutral\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"faction\": [\n \"seeker\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 3\n }\n
\ },\n {\n \"faction\": [\n \"guardian\",\n \"mystic\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 0\n },\n
\ \"limit\": 5,\n \"error\": \"You cannot have more than 5 Guardian and/or
Mystic cards\"\n }\n ]\n}"
GUID: '282857'
Grid: true
GridProjection: false

View File

@ -22,10 +22,10 @@ DragSelectable: true
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 \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90002\": 1,\n \"01008\": 1\n
\ },\n {\n \"90003\": 1,\n \"01009\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\"Core\",\n \"extraToken\": \"Tome\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90002\":
1,\n \"01008\": 1\n },\n {\n \"90003\": 1,\n \"01009\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"mystic\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
@ -71,14 +71,15 @@ States:
\ ],\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
\ \"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"
\ \"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"
GUID: ac7047
Grid: true
GridProjection: false

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"08001\",\n \"type\": \"Investigator\",\n \"class\": \"Guardian\",\n
\ \"traits\": \"Entrepreneur.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
1,\n \"combatIcons\": 5,\n \"agilityIcons\": 2,\n \"cycle\": \"Edge of the Earth\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"08002\": 1\n },\n {\n \"08003\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"guardian\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 0\n }\n

View File

@ -20,7 +20,8 @@ CustomDeck:
Description: Advanced
DragSelectable: true
GMNotes: "{\n \"id\": \"90019\",\n \"type\": \"Event\",\n \"class\": \"Neutral\",\n
\ \"cost\": 4,\n \"traits\": \"Spell.\",\n \"weakness\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"cost\": 4,\n \"traits\": \"Spell.\",\n \"weakness\": true,\n \"cycle\": \"Bad
Blood\"\n}"
GUID: 580a4d
Grid: true
GridProjection: false

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"09015\",\n \"type\": \"Investigator\",\n \"class\": \"Survivor\",\n
\ \"traits\": \"Reporter.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\": 5,\n
\ \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"The Scarlet Keys\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"09016\": 1\n },\n {\n \"09017\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"survivor\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":

View File

@ -22,10 +22,11 @@ DragSelectable: true
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 \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"98017\":
1,\n \"07012\": 1\n },\n {\n \"98018\": 1,\n \"07013\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
2,\n \"cycle\": \"The Innsmouth Conspiracy\",\n \"extraToken\": \"FreeTrigger\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"98017\": 1,\n \"07012\": 1\n
\ },\n {\n \"98018\": 1,\n \"07013\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"rogue\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
@ -71,15 +72,15 @@ States:
\ ],\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 \"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"
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"
GUID: 3925ce
Grid: true
GridProjection: false

View File

@ -22,13 +22,13 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"05004\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Cultist. Silver Twilight.\",\n \"willpowerIcons\": 1,\n \"intellectIcons\":
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 3,\n \"cycle\": \"The Circle Undone\",\n
\ \"deck_requirements\": {\n \"size\": 35,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"05013\": 1\n },\n {\n \"05014\":
1\n },\n {\n \"05015\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"mystic\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"faction\": [\n \"guardian\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 2\n }\n }\n ]\n}"
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 35,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"05013\":
1\n },\n {\n \"05014\": 1\n },\n {\n \"05015\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"guardian\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
GUID: 32b091
Grid: true
GridProjection: false

View File

@ -20,7 +20,7 @@ CustomDeck:
Description: Leave No Doubt
DragSelectable: true
GMNotes: "{\n \"id\": \"90029\",\n \"type\": \"Asset\",\n \"class\": \"Neutral\",\n
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"By the Book\"\n}"
GUID: 07e7bd
Grid: true
GridProjection: false

View File

@ -20,7 +20,7 @@ CustomDeck:
Description: Seek the Truth
DragSelectable: true
GMNotes: "{\n \"id\": \"90028\",\n \"type\": \"Asset\",\n \"class\": \"Neutral\",\n
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"By the Book\"\n}"
GUID: 0994c9
Grid: true
GridProjection: false

View File

@ -20,7 +20,7 @@ CustomDeck:
Description: Due Diligence
DragSelectable: true
GMNotes: "{\n \"id\": \"90025\",\n \"type\": \"Asset\",\n \"class\": \"Neutral\",\n
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"By the Book\"\n}"
GUID: '133521'
Grid: true
GridProjection: false

View File

@ -20,7 +20,7 @@ CustomDeck:
Description: Consult Experts
DragSelectable: true
GMNotes: "{\n \"id\": \"90027\",\n \"type\": \"Asset\",\n \"class\": \"Neutral\",\n
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"By the Book\"\n}"
GUID: 2d9256
Grid: true
GridProjection: false

View File

@ -20,7 +20,7 @@ CustomDeck:
Description: Red Tape
DragSelectable: true
GMNotes: "{\n \"id\": \"90026\",\n \"type\": \"Asset\",\n \"class\": \"Neutral\",\n
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"Standalone\"\n}"
\ \"startsInPlay\": true,\n \"permanent\": true,\n \"cycle\": \"By the Book\"\n}"
GUID: '706176'
Grid: true
GridProjection: false

View File

@ -20,8 +20,9 @@ CustomDeck:
Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"09104\",\n \"type\": \"Event\",\n \"class\": \"Survivor\",\n
\ \"cost\": 0,\n \"level\": 0,\n \"traits\": \"Insight. Spirit.\",\n \"cycle\":
\"The Scarlet Keys\"\n}"
\ \"cost\": 0,\n \"level\": 0,\n \"traits\": \"Insight. Spirit.\",\n \"uses\":
[\n {\n \"count\": 1,\n \"type\": \"Universal\",\n \"token\":
\"universalActionAbility\"\n }\n ],\n \"cycle\": \"The Scarlet Keys\"\n}"
GUID: a3d041
Grid: true
GridProjection: false

View File

@ -20,8 +20,9 @@ CustomDeck:
Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"04148\",\n \"type\": \"Asset\",\n \"class\": \"Neutral\",\n
\ \"cost\": 2,\n \"traits\": \"Item. Tome.\",\n \"intellectIcons\": 2,\n \"cycle\":
\"The Forgotten Age\"\n}"
\ \"cost\": 2,\n \"traits\": \"Item. Tome.\",\n \"intellectIcons\": 2,\n \"uses\":
[\n {\n \"count\": 1,\n \"type\": \"Explore\",\n \"token\": \"universalActionAbility\"\n
\ }\n ],\n \"cycle\": \"The Forgotten Age\"\n}"
GUID: 9dc3d4
Grid: true
GridProjection: false

View File

@ -21,8 +21,9 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"10082\",\n \"type\": \"Asset\",\n \"slot\": \"Hand\",\n
\ \"class\": \"Rogue\",\n \"cost\": 2,\n \"level\": 4,\n \"traits\": \"Item.
Illicit.\",\n \"intellectIcons\": 1,\n \"agilityIcons\": 1,\n \"cycle\": \"The
Feast of Hemlock Vale\"\n}"
Illicit.\",\n \"intellectIcons\": 1,\n \"agilityIcons\": 1,\n \"uses\": [\n {\n
\ \"count\": 0,\n \"type\": \"Suspicion\",\n \"token\": \"resource\"\n
\ }\n ],\n \"cycle\": \"The Feast of Hemlock Vale\"\n}"
GUID: 7ebb67
Grid: true
GridProjection: false

View File

@ -21,7 +21,9 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"10066\",\n \"type\": \"Asset\",\n \"slot\": \"Hand\",\n
\ \"class\": \"Rogue\",\n \"cost\": 3,\n \"level\": 0,\n \"traits\": \"Item.
Illicit.\",\n \"intellectIcons\": 1,\n \"cycle\": \"The Feast of Hemlock Vale\"\n}"
Illicit.\",\n \"intellectIcons\": 1,\n \"uses\": [\n {\n \"count\": 0,\n
\ \"type\": \"Suspicion\",\n \"token\": \"resource\"\n }\n ],\n \"cycle\":
\"The Feast of Hemlock Vale\"\n}"
GUID: acd38d
Grid: true
GridProjection: false

View File

@ -0,0 +1,248 @@
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
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
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/FalseCovenant")
end)
__bundle_register("playercards/cards/FalseCovenant", function(require, _LOADED, __bundle_register, __bundle_modules)
buttonLabel = "Cancel"
buttonIcon = "token-curse"
buttonColor = "#633A84E6"
buttonFontSize = 300
RETURN_TO_POOL = true
VALID_TOKENS = {
["Curse"] = true
}
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")
end)
__bundle_register("playercards/CardsThatRedrawTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that return and redraw tokens
This file is used to add an XML button to a card, turned on via context menu.
Valid options modify the appearance of the XML button, as well as the
behavior of the return and redraw function. Set options before requiring this file.
Parameters for the return and redraw functions. Typically set VALID_TOKENS or INVALID_TOKENS, not both.
If there are no restrictions on which tokens can be redrawn (e.g. Wendy Adams), keep both empty.
* VALID_TOKENS --@type table
- keyed table which lists all tokens that can be redrawn by the card
- example usage: "False Covenant"
> VALID_TOKENS = {
> ["Curse"] = true
> }
* INVALID_TOKENS --@type table
- keyed table which lists all tokens that cannot be redrawn by the card
- example usage: "Custom Ammunition"
> INVALID_TOKENS = {
> ["Auto-fail"] = true
> }
* DRAW_SPECIFIC_TOKEN --@type string (name of token or nil)
- if set, will attempt to draw that specific token
* RETURN_TO_POOL --@type string
- allows for the name of the card to be passed onto Global for any special handling
The following parameters modify the appearence of the XML button and are not listed as part of a table.
- buttonHeight (default is 450)
- buttonWidth (default is 1400)
- buttonPosition (default is "0 -55 -22")
- buttonFontSize (default is 250)
- buttonRotation (change if button is placed on an investigator cards)
- buttonLabel (default is "Redraw Token")
- buttonIcon (to add an icon to the right)
- buttonColor (default is "#77674DE6")
----------------------------------------------------------
EXAMPLE: Claypool's Furs
This card can only redraw the Frost token, and is replaced with a random token from the bag.
As a nice reminder the XML button takes on the Frost color and icon with the text "Cancel".
> buttonValue = "Cancel"
> buttonIcon = "token-frost"
> buttonColor = "#404450E6"
> buttonFontSize = 300
> VALID_TOKENS = {
> ["Frost"] = true
> }
>
> require...
----------------------------------------------------------]]
-- intentionally global
hasXML = true
isHelperEnabled = false
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
createHelperXML()
syncDisplayWithOptionPanel()
end
function createHelperXML()
local xmlTable = { {
tag = "Button",
attributes = {
active = "false",
id = "Helper",
height = buttonHeight or 450,
width = buttonWidth or 1400,
rotation = buttonRotation or "0 0 180",
scale = "0.1 0.1 1",
position = buttonPosition or "0 -55 -22",
padding = "50 50 50 50",
font = "font_teutonic-arkham",
fontSize = buttonFontSize or 250,
onClick = "triggerXMLTokenLabelCreation",
color = buttonColor or "#77674DE6",
textColor = "White"
},
value = buttonLabel or "Redraw Token"
} }
if buttonIcon then
xmlTable[1].attributes.iconWidth = "400"
xmlTable[1].attributes.iconAlignment = "Right"
xmlTable[1].attributes.icon = buttonIcon
end
self.UI.setXmlTable(xmlTable)
end
function triggerXMLTokenLabelCreation()
Global.call("activeRedrawEffect", {
VALID_TOKENS = VALID_TOKENS,
INVALID_TOKENS = INVALID_TOKENS,
RETURN_TO_POOL = RETURN_TO_POOL
})
end
end)
__bundle_register("playercards/CardsWithHelper", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
else
updateDisplay()
end
end
-- forces a new state
function setHelperState(newState)
isHelperEnabled = newState
updateSave()
updateDisplay()
end
-- toggles the current state
function toggleHelper()
isHelperEnabled = not isHelperEnabled
updateSave()
updateDisplay()
end
-- updates the visibility and calls events (after a small delay to allow XML being set)
function updateDisplay()
Wait.frames(actualDisplayUpdate, 5)
end
function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end
end)
return __bundle_require("__root")

View File

@ -30,7 +30,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: ''
LuaScript: !include 'Card False Covenant (2) 3442f5.ttslua'
LuaScriptState: ''
MeasureMovement: false
Name: Card

View File

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

View File

@ -54,4 +54,4 @@ Transform:
scaleY: 1
scaleZ: 1
Value: 0
XmlUI: ''
XmlUI: !include 'Card Family Inheritance 394603.xml'

View File

@ -22,12 +22,13 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"04004\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Believer. Warden.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
3,\n \"combatIcons\": 2,\n \"agilityIcons\": 3,\n \"cycle\": \"The Forgotten
Age\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"04013\": 1\n },\n {\n \"04014\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"trait\": [\n \"blessed\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 3\n }\n }\n ]\n}"
Age\",\n \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"04013\":
1\n },\n {\n \"04014\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"mystic\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"trait\": [\n \"blessed\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 3\n }\n }\n ]\n}"
GUID: eb96e6
Grid: true
GridProjection: false

View File

@ -44,86 +44,112 @@ end)(nil)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/FavoroftheMoon1")
end)
__bundle_register("playercards/cards/FavoroftheMoon1", function(require, _LOADED, __bundle_register, __bundle_modules)
VALID_TOKENS = {
["Curse"] = true
}
SHOW_SINGLE_RELEASE = true
KEEP_OPEN = true
RESOLVE_TOKEN = true
require("playercards/CardsThatSealTokens")
end)
__bundle_register("chaosbag/BlessCurseManagerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
__bundle_register("chaosbag/ChaosBagApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local BlessCurseManagerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local ChaosBagApi = {}
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
-- respawns the chaos bag with a new state of tokens
---@param tokenList table List of chaos token ids
ChaosBagApi.setChaosBagState = function(tokenList)
Global.call("setChaosBagState", tokenList)
end
-- removes all taken tokens and resets the counts
BlessCurseManagerApi.removeTakenTokensAndReset = function()
local BlessCurseManager = getManager()
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Bless") end, 0.05)
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Curse") end, 0.10)
Wait.time(function() BlessCurseManager.call("doReset", "White") end, 0.15)
-- returns a Table List of chaos token ids in the current chaos bag
-- requires copying the data into a new table because TTS is weird about handling table return values in Global
ChaosBagApi.getChaosBagState = function()
local chaosBagContentsCatcher = Global.call("getChaosBagState")
local chaosBagContents = {}
for _, v in ipairs(chaosBagContentsCatcher) do
table.insert(chaosBagContents, v)
end
return chaosBagContents
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.sealedToken = function(type, guid)
getManager().call("sealedToken", { type = type, guid = guid })
-- checks scripting zone for chaos bag (also called by a lot of objects!)
ChaosBagApi.findChaosBag = function()
return Global.call("findChaosBag")
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.releasedToken = function(type, guid)
getManager().call("releasedToken", { type = type, guid = guid })
-- returns a table of object references to the tokens in play (does not include sealed tokens!)
ChaosBagApi.getTokensInPlay = function()
return Global.call("getChaosTokensinPlay")
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.returnedToken = function(type, guid)
getManager().call("returnedToken", { type = type, guid = guid })
end
-- broadcasts the current status for bless/curse tokens
-- returns all sealed tokens on cards to the chaos bag
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.broadcastStatus = function(playerColor)
getManager().call("broadcastStatus", playerColor)
ChaosBagApi.releaseAllSealedTokens = function(playerColor)
Global.call("releaseAllSealedTokens", playerColor)
end
-- removes all bless / curse tokens from the chaos bag and play
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.removeAll = function(playerColor)
getManager().call("doRemove", playerColor)
-- returns all drawn tokens to the chaos bag
ChaosBagApi.returnChaosTokens = function()
Global.call("returnChaosTokens")
end
-- adds bless / curse sealing to the hovered card
---@param playerColor string Color of the player to show the broadcast to
---@param hoveredObject tts__Object Hovered object
BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject)
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
-- removes the specified chaos token from the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.removeChaosToken = function(id)
Global.call("removeChaosToken", id)
end
return BlessCurseManagerApi
-- returns a chaos token to the bag and calls all relevant functions
---@param token tts__Object Chaos token to return
---@param fromBag boolean whether or not the token to return was in the middle of being drawn (true) or elsewhere (false)
ChaosBagApi.returnChaosTokenToBag = function(token, fromBag)
Global.call("returnChaosTokenToBag", { token = token, fromBag = fromBag })
end
-- spawns the specified chaos token and puts it into the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.spawnChaosToken = function(id)
Global.call("spawnChaosToken", id)
end
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens
-- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the
-- contents of the bag should check this method before doing so.
-- This method will broadcast a message to all players if the bag is being searched.
---@return any: True if the bag is manipulated, false if it should be blocked.
ChaosBagApi.canTouchChaosTokens = function()
return Global.call("canTouchChaosTokens")
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
---@param tokenType? string Name of token (e.g. "Bless") to be drawn from the bag
---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag
---@param takeParameters? table Position and rotation of the location where the new token should be drawn to, usually to replace a returned token
---@return tts__Object: Object reference to the token that was drawn
ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, takeParameters)
return Global.call("drawChaosToken", {
mat = mat,
drawAdditional = drawAdditional,
tokenType = tokenType,
guidToBeResolved = guidToBeResolved,
takeParameters = takeParameters
})
end
-- returns a Table List of chaos token ids in the current chaos bag
-- requires copying the data into a new table because TTS is weird about handling table return values in Global
ChaosBagApi.getIdUrlMap = function()
return Global.getTable("ID_URL_MAP")
end
return ChaosBagApi
end
end)
__bundle_register("playermat/PlaymatApi", function(require, _LOADED, __bundle_register, __bundle_modules)
__bundle_register("playermat/PlayermatApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local PlaymatApi = {}
local PlayermatApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local searchLib = require("util/SearchLib")
local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }
-- Convenience function to look up a mat's object by color, or get all mats.
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
---@return table: Single-element if only single playmat is requested
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@return table: Single-element if only single playermat is requested
local function getMatForColor(matColor)
if matColor == "All" then
return guidReferenceApi.getObjectsByType("Playermat")
@ -132,9 +158,9 @@ do
end
end
-- Returns the color of the closest playmat
-- Returns the color of the closest playermat
---@param startPos table Starting position to get the closest mat from
PlaymatApi.getMatColorByPosition = function(startPos)
PlayermatApi.getMatColorByPosition = function(startPos)
local result, smallestDistance
for matColor, mat in pairs(getMatForColor("All")) do
local distance = Vector.between(startPos, mat.getPosition()):magnitude()
@ -146,17 +172,17 @@ do
return result
end
-- Returns the color of the player's hand that is seated next to the playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getPlayerColor = function(matColor)
-- Returns the color of the player's hand that is seated next to the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getPlayerColor = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("playerColor")
end
end
-- Returns the color of the playmat that owns the playercolor's hand
---@param handColor string Color of the playmat
PlaymatApi.getMatColor = function(handColor)
-- Returns the color of the playermat that owns the playercolor's hand
---@param handColor string Color of the playermat
PlayermatApi.getMatColor = function(handColor)
for matColor, mat in pairs(getMatForColor("All")) do
local playerColor = mat.getVar("playerColor")
if playerColor == handColor then
@ -165,41 +191,69 @@ do
end
end
-- Returns if there is the card "Dream-Enhancing Serum" on the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.isDES = function(matColor)
-- Instructs a playermat to check for DES
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.checkForDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("isDES")
mat.call("checkForDES")
end
end
-- Performs a search of the deck area of the requested playmat and returns the result as table
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDeckAreaObjects = function(matColor)
-- Returns if there is the card "Dream-Enhancing Serum" on the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@return boolean: whether DES is present on the playermat
PlayermatApi.hasDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("hasDES")
end
end
-- gets the slot data for the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getSlotData = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getTable("slotData")
end
end
-- sets the slot data for the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param newSlotData table New slot data for the playermat
PlayermatApi.loadSlotData = function(matColor, newSlotData)
for _, mat in pairs(getMatForColor(matColor)) do
mat.setTable("slotData", newSlotData)
mat.call("redrawSlotSymbols")
return
end
end
-- Performs a search of the deck area of the requested playermat and returns the result as table
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDeckAreaObjects = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getDeckAreaObjects")
end
end
-- Flips the top card of the deck (useful after deck manipulation for Norman Withers)
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.flipTopCardFromDeck = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.flipTopCardFromDeck = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("flipTopCardFromDeck")
end
end
-- Returns the position of the discard pile of the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDiscardPosition = function(matColor)
-- Returns the position of the discard pile of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDiscardPosition = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDiscardPosition")
end
end
-- Returns the position of the draw pile of the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDrawPosition = function(matColor)
-- Returns the position of the draw pile of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDrawPosition = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDrawPosition")
end
@ -207,25 +261,25 @@ do
-- Transforms a local position into a global position
---@param localPos table Local position to be transformed
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.transformLocalPosition = function(localPos, matColor)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.transformLocalPosition = function(localPos, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.positionToWorld(localPos)
end
end
-- Returns the rotation of the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.returnRotation = function(matColor)
-- Returns the rotation of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnRotation = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getRotation()
end
end
-- Returns a table with spawn data (position and rotation) for a helper object
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param helperName string Name of the helper object
PlaymatApi.getHelperSpawnData = function(matColor, helperName)
PlayermatApi.getHelperSpawnData = function(matColor, helperName)
local resultTable = {}
local localPositionTable = {
["Hand Helper"] = {0.05, 0, -1.182},
@ -242,82 +296,99 @@ do
end
-- Triggers the Upkeep for the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
-- Triggers the Upkeep for the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param playerColor string Color of the calling player (for messages)
PlaymatApi.doUpkeepFromHotkey = function(matColor, playerColor)
PlayermatApi.doUpkeepFromHotkey = function(matColor, playerColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doUpkeepFromHotkey", playerColor)
end
end
-- Handles discarding for the requested playmat for the provided list of objects
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
-- Handles discarding for the requested playermat for the provided list of objects
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param objList table List of objects to discard
PlaymatApi.discardListOfObjects = function(matColor, objList)
PlayermatApi.discardListOfObjects = function(matColor, objList)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("discardListOfObjects", objList)
end
end
-- Returns the active investigator id
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.returnInvestigatorId = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnInvestigatorId = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorId")
end
end
-- Returns the class of the active investigator
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnInvestigatorClass = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorClass")
end
end
-- Returns the position for encounter card drawing
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param stack boolean If true, returns the leftmost position instead of the first empty from the right
PlaymatApi.getEncounterCardDrawPosition = function(matColor, stack)
PlayermatApi.getEncounterCardDrawPosition = function(matColor, stack)
for _, mat in pairs(getMatForColor(matColor)) do
return Vector(mat.call("getEncounterCardDrawPosition", stack))
end
end
-- Sets the requested playmat's snap points to limit snapping to matching card types or not. If
-- Sets the requested playermat's snap points to limit snapping to matching card types or not. If
-- matchTypes is true, the main card slot snap points will only snap assets, while the
-- investigator area point will only snap Investigators. If matchTypes is false, snap points will
-- be reset to snap all cards.
---@param matchCardTypes boolean Whether snap points should only snap for the matching card types
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.setLimitSnapsByType = function(matchCardTypes, matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.setLimitSnapsByType = function(matchCardTypes, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("setLimitSnapsByType", matchCardTypes)
end
end
-- Sets the requested playmat's draw 1 button to visible
-- Sets the requested playermat's draw 1 button to visible
---@param isDrawButtonVisible boolean Whether the draw 1 button should be visible or not
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.showDrawButton = function(isDrawButtonVisible, matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.showDrawButton = function(isDrawButtonVisible, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("showDrawButton", isDrawButtonVisible)
end
end
-- Shows or hides the clickable clue counter for the requested playmat
-- Shows or hides the clickable clue counter for the requested playermat
---@param showCounter boolean Whether the clickable counter should be present or not
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.clickableClues = function(showCounter, matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.clickableClues = function(showCounter, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("clickableClues", showCounter)
end
end
-- Removes all clues (to the trash for tokens and counters set to 0) for the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.removeClues = function(matColor)
-- Toggles the use of class textures for the requested playermat
---@param state boolean Whether the class texture should be used or not
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.useClassTexture = function(state, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("useClassTexture", state)
end
end
-- Removes all clues (to the trash for tokens and counters set to 0) for the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.removeClues = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("removeClues")
end
end
-- Reports the clue count for the requested playmat
-- Reports the clue count for the requested playermat
---@param useClickableCounters boolean Controls which type of counter is getting checked
PlaymatApi.getClueCount = function(useClickableCounters, matColor)
PlayermatApi.getClueCount = function(useClickableCounters, matColor)
local count = 0
for _, mat in pairs(getMatForColor(matColor)) do
count = count + mat.call("getClueCount", useClickableCounters)
@ -326,37 +397,36 @@ do
end
-- Updates the specified owned counter
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param type string Counter to target
---@param newValue number Value to set the counter to
---@param modifier number If newValue is not provided, the existing value will be adjusted by this modifier
PlaymatApi.updateCounter = function(matColor, type, newValue, modifier)
PlayermatApi.updateCounter = function(matColor, type, newValue, modifier)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("updateCounter", { type = type, newValue = newValue, modifier = modifier })
end
end
-- Triggers the draw function for the specified playmat
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
-- Triggers the draw function for the specified playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param number number Amount of cards to draw
PlaymatApi.drawCardsWithReshuffle = function(matColor, number)
PlayermatApi.drawCardsWithReshuffle = function(matColor, number)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("drawCardsWithReshuffle", number)
end
end
-- Returns the resource counter amount
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param type string Counter to target
PlaymatApi.getCounterValue = function(matColor, type)
PlayermatApi.getCounterValue = function(matColor, type)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getCounterValue", type)
end
end
-- Returns a list of mat colors that have an investigator placed
PlaymatApi.getUsedMatColors = function()
local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }
PlayermatApi.getUsedMatColors = function()
local usedColors = {}
for matColor, mat in pairs(getMatForColor("All")) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
@ -368,18 +438,39 @@ do
return usedColors
end
-- Returns investigator name
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getInvestigatorName = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
local searchResult = searchLib.atPosition(searchPos, "isCardOrDeck")
if #searchResult == 1 then
return searchResult[1].getName()
end
end
return ""
end
-- Resets the specified skill tracker to "1, 1, 1, 1"
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.resetSkillTracker = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.resetSkillTracker = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("resetSkillTracker")
end
end
-- Finds all objects on the playmat and associated set aside zone and returns a table
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
-- Redraws the XML for the slot symbols based on the slotData table
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.redrawSlotSymbols = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("redrawSlotSymbols")
end
end
-- Finds all objects on the playermat and associated set aside zone and returns a table
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param filter string Name of the filte function (see util/SearchLib)
PlaymatApi.searchAroundPlaymat = function(matColor, filter)
PlayermatApi.searchAroundPlayermat = function(matColor, filter)
local objList = {}
for _, mat in pairs(getMatForColor(matColor)) do
for _, obj in ipairs(mat.call("searchAroundSelf", filter)) do
@ -390,33 +481,33 @@ do
end
-- Discard a non-hidden card from the corresponding player's hand
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.doDiscardOne = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.doDiscardOne = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doDiscardOne")
end
end
-- Triggers the metadata sync for all playmats
PlaymatApi.syncAllCustomizableCards = function()
-- Triggers the metadata sync for all playermats
PlayermatApi.syncAllCustomizableCards = function()
for _, mat in pairs(getMatForColor("All")) do
mat.call("syncAllCustomizableCards")
end
end
return PlaymatApi
return PlayermatApi
end
end)
__bundle_register("util/SearchLib", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local SearchLib = {}
local filterFunctions = {
isActionToken = function(x) return x.getDescription() == "Action Token" end,
isCard = function(x) return x.type == "Card" end,
isDeck = function(x) return x.type == "Deck" end,
isCardOrDeck = function(x) return x.type == "Card" or x.type == "Deck" end,
isClue = function(x) return x.memo == "clueDoom" and x.is_face_down == false end,
isTileOrToken = function(x) return x.type == "Tile" end
isTileOrToken = function(x) return x.type == "Tile" end,
isUniversalToken = function(x) return x.getMemo() == "universalActionAbility" end,
}
-- performs the actual search and returns a filtered list of object references
@ -440,7 +531,7 @@ do
max_distance = maxDistance or 0
})
-- filtering the result
-- filter the result for matching objects
local objList = {}
for _, v in ipairs(searchResult) do
if not filter or filterFunc(v.hit_object) then
@ -457,32 +548,53 @@ do
-- searches the area on an object
SearchLib.onObject = function(obj, filter)
pos = obj.getPosition()
size = obj.getBounds().size:setAt("y", 1)
local pos = obj.getPosition()
local size = obj.getBounds().size:setAt("y", 1)
return returnSearchResult(pos, _, size, filter)
end
-- searches the specified position (a single point)
SearchLib.atPosition = function(pos, filter)
size = { 0.1, 2, 0.1 }
local size = { 0.1, 2, 0.1 }
return returnSearchResult(pos, _, size, filter)
end
-- searches below the specified position (downwards until y = 0)
SearchLib.belowPosition = function(pos, filter)
direction = { 0, -1, 0 }
maxDistance = pos.y
local size = { 0.1, 2, 0.1 }
local direction = { 0, -1, 0 }
local maxDistance = pos.y
return returnSearchResult(pos, _, size, filter, direction, maxDistance)
end
return SearchLib
end
end)
__bundle_register("playercards/cards/FavoroftheMoon1", function(require, _LOADED, __bundle_register, __bundle_modules)
VALID_TOKENS = {
["Curse"] = true
}
KEEP_OPEN = true
MAX_SEALED = 3
RESOLVE_TOKEN = true
require("playercards/CardsThatSealTokens")
end)
__bundle_register("playercards/CardsThatSealTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that seal tokens
This file is used to add sealing option to cards' context menu.
NOTE: all cards are allowed to release a single token to enable Hallow and A Watchful Peace,
and to release all sealed tokens to allow for cards that might leave play with sealed tokens on them.
Valid options (set before requiring this file):
MAX_SEALED --@type: number (maximum number of tokens allowable by the card to be sealed)
- required for all cards
- if MAX_SEALED is more than 1, then an XML label is created for the topmost token indicating the number of sealed tokens
- gives an error if user tries to seal additional tokens on the card
- example usage: "The Chthonian Stone"
> MAX_SEALED = 1
UPDATE_ON_HOVER --@type: boolean
- automatically updates the context menu options when the card is hovered
- the "Read Bag" function reads the content of the chaos bag to update the context menu
@ -493,19 +605,16 @@ KEEP_OPEN --@type: boolean
- makes the context menu stay open after selecting an option
- example usage: "Unrelenting"
SHOW_SINGLE_RELEASE --@type: boolean
SHOW_MULTI_RELEASE --@type: number (maximum amount of tokens to release at once)
- enables an entry in the context menu
- this entry allows releasing a single token
- example usage: "Holy Spear" (to keep the other tokens and just release one)
SHOW_MULTI_RELEASE --@type: number (amount of tokens to release at once)
- enables an entry in the context menu
- this entry allows releasing of multiple tokens at once
- example usage: "Nephthys" (to release 3 bless tokens at once)
- this entry allows releasing of multiple tokens at once, to the maximum number
- does not fail if there are fewer than the maximum sealed
- example usage: "Nephthys" (to release up to 3 bless tokens at once)
SHOW_MULTI_RETURN --@type: number (amount of tokens to return to pool at once)
- enables an entry in the context menu
- this entry allows returning tokens to the token pool
- fails if not enough tokens are sealed
- example usage: "Nephthys" (to return 3 bless tokens at once)
SHOW_MULTI_SEAL --@type: number (amount of tokens to seal at once)
@ -539,6 +648,7 @@ Thus it should be implemented like this:
> ["+1"] = true,
> ["Elder Sign"] = true
> }
> MAX_SEALED = 1
> require...
----------------------------------------------------------
Example 2: Holy Spear
@ -549,21 +659,35 @@ Thus it should be implemented like this:
> VALID_TOKENS = {
> ["Bless"] = true
> }
> SHOW_SINGLE_RELEASE = true
> SHOW_MULTI_SEAL = 2
> MAX_SEALED = 10
> require...
----------------------------------------------------------]]
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playmatApi = require("playermat/PlaymatApi")
local playermatApi = require("playermat/PlayermatApi")
local tokenArrangerApi = require("accessories/TokenArrangerApi")
local sealedTokens = {}
local ID_URL_MAP = {}
local tokensInBag = {}
-- XML background color for each token for label when stacked
local tokenColor = {
["Skull"] = "#4A0400E6",
["Cultist"] = "#173B0BE6",
["Tablet"] = "#1D2238E6",
["Elder Thing"] = "#4D2331E6",
["Auto-fail"] = "#9B0004E6",
["Bless"] = "#9D702CE6",
["Curse"] = "#633A84E6",
["Frost"] = "#404450E6",
["Elder Sign"] = "#50A8CEE6",
[""] = "#77674DE6"
}
function onSave() return JSON.encode(sealedTokens) end
function onLoad(savedData)
@ -575,13 +699,15 @@ end
-- builds the context menu
function generateContextMenu()
-- conditional single or multi release options
if SHOW_SINGLE_RELEASE then
self.addContextMenuItem("Release token", releaseOneToken)
elseif SHOW_MULTI_RELEASE then
self.addContextMenuItem("Release one token", releaseOneToken)
-- conditional release options
if MAX_SEALED > 1 then
self.addContextMenuItem("Release all tokens", releaseAllTokens)
end
if SHOW_MULTI_RELEASE then
self.addContextMenuItem("Release " .. SHOW_MULTI_RELEASE .. " token(s)", releaseMultipleTokens)
else
self.addContextMenuItem("Release token(s)", releaseAllTokens)
end
if RESOLVE_TOKEN then
@ -619,7 +745,7 @@ function generateContextMenu()
end
if allowed then
for i = 1, SHOW_MULTI_SEAL do
for i = SHOW_MULTI_SEAL, 1, -1 do
sealToken(map.name, playerColor)
end
else
@ -656,6 +782,10 @@ end
-- seals the named token on this card
function sealToken(name, playerColor)
if #sealedTokens >= MAX_SEALED then
printToColor("Cannot seal any more tokens on this card", playerColor, "Red")
return
end
if not chaosBagApi.canTouchChaosTokens() then return end
local chaosbag = chaosBagApi.findChaosBag()
for i, obj in ipairs(chaosbag.getObjects()) do
@ -672,6 +802,16 @@ function sealToken(name, playerColor)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.sealedToken(name, guid)
end
-- destroy XML on just covered token
if #sealedTokens > 1 then
local coveredToken = getObjectFromGUID(sealedTokens[#sealedTokens - 1])
if coveredToken ~= nil then
coveredToken.UI.setXml("")
else
table.remove(sealedTokens, #sealedTokens - 1)
end
end
updateStackSize()
end
})
return
@ -691,16 +831,22 @@ function releaseOneToken(playerColor)
end
end
-- release multiple tokens at once
-- release up to multiple tokens at once with no minimum
function releaseMultipleTokens(playerColor)
if SHOW_MULTI_RELEASE <= #sealedTokens then
for i = 1, SHOW_MULTI_RELEASE do
putTokenAway(table.remove(sealedTokens))
end
printToColor("Releasing " .. SHOW_MULTI_RELEASE .. " tokens", playerColor)
else
if #sealedTokens == 0 then
printToColor("Not enough tokens sealed.", playerColor)
return
end
local numRemoved = SHOW_MULTI_RELEASE
if #sealedTokens < SHOW_MULTI_RELEASE then
numRemoved = #sealedTokens
end
for i = 1, numRemoved do
putTokenAway(table.remove(sealedTokens))
end
printToColor("Releasing " .. numRemoved .. " tokens", playerColor)
end
-- releases all sealed tokens
@ -741,6 +887,7 @@ function putTokenAway(guid)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.releasedToken(name, guid)
end
updateStackSize()
end
-- returns the token to the pool (== removes it)
@ -753,6 +900,7 @@ function returnToken(guid)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.returnedToken(name, guid)
end
updateStackSize()
end
-- resolves sealed token as if it came from the chaos bag
@ -761,11 +909,47 @@ function resolveSealed()
broadcastToAll("No tokens sealed.", "Red")
return
end
local closestMatColor = playmatApi.getMatColorByPosition(self.getPosition())
local closestMatColor = playermatApi.getMatColorByPosition(self.getPosition())
local mat = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, "Playermat")
local guidToBeResolved = table.remove(sealedTokens)
local resolvedToken = getObjectFromGUID(guidToBeResolved)
resolvedToken.UI.setXml("")
updateStackSize()
chaosBagApi.drawChaosToken(mat, true, _, guidToBeResolved)
end
function updateStackSize()
if MAX_SEALED == 1 then return end
if #sealedTokens == 0 then return end
-- get topmost sealed token
local topToken = getObjectFromGUID(sealedTokens[#sealedTokens])
local name = topToken.getName()
topToken.UI.setXmlTable({
{
tag = "Panel",
attributes = {
height = 380,
width = 380,
rotation = "0 0 180",
scale = "0.2 0.2 1",
position = "0 0 -12",
color = tokenColor[name] or "#77674DE6"
},
children = {
tag = "Text",
attributes = {
fontSize = "380",
font = "font_teutonic-arkham",
color = "#ffffff",
outline = "#000000",
outlineSize = "8 -8",
text = "x" .. #sealedTokens
}
}
}
})
end
end)
__bundle_register("accessories/TokenArrangerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
@ -801,92 +985,81 @@ do
return TokenArrangerApi
end
end)
__bundle_register("chaosbag/ChaosBagApi", function(require, _LOADED, __bundle_register, __bundle_modules)
__bundle_register("chaosbag/BlessCurseManagerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local ChaosBagApi = {}
local BlessCurseManagerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
-- respawns the chaos bag with a new state of tokens
---@param tokenList table List of chaos token ids
ChaosBagApi.setChaosBagState = function(tokenList)
return Global.call("setChaosBagState", tokenList)
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
end
-- returns a Table List of chaos token ids in the current chaos bag
-- requires copying the data into a new table because TTS is weird about handling table return values in Global
ChaosBagApi.getChaosBagState = function()
local chaosBagContentsCatcher = Global.call("getChaosBagState")
local chaosBagContents = {}
for _, v in ipairs(chaosBagContentsCatcher) do
table.insert(chaosBagContents, v)
end
return chaosBagContents
-- removes all taken tokens and resets the counts
BlessCurseManagerApi.removeTakenTokensAndReset = function()
local BlessCurseManager = getManager()
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Bless") end, 0.05)
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Curse") end, 0.10)
Wait.time(function() BlessCurseManager.call("doReset", "White") end, 0.15)
end
-- checks scripting zone for chaos bag (also called by a lot of objects!)
ChaosBagApi.findChaosBag = function()
return Global.call("findChaosBag")
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.sealedToken = function(type, guid)
getManager().call("sealedToken", { type = type, guid = guid })
end
-- returns a table of object references to the tokens in play (does not include sealed tokens!)
ChaosBagApi.getTokensInPlay = function()
return Global.call("getChaosTokensinPlay")
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
---@param fromBag? boolean Whether or not token was just drawn from the chaos bag
BlessCurseManagerApi.releasedToken = function(type, guid, fromBag)
getManager().call("releasedToken", { type = type, guid = guid, fromBag = fromBag })
end
-- returns all sealed tokens on cards to the chaos bag
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.returnedToken = function(type, guid)
getManager().call("returnedToken", { type = type, guid = guid })
end
-- broadcasts the current status for bless/curse tokens
---@param playerColor string Color of the player to show the broadcast to
ChaosBagApi.releaseAllSealedTokens = function(playerColor)
return Global.call("releaseAllSealedTokens", playerColor)
BlessCurseManagerApi.broadcastStatus = function(playerColor)
getManager().call("broadcastStatus", playerColor)
end
-- returns all drawn tokens to the chaos bag
ChaosBagApi.returnChaosTokens = function()
return Global.call("returnChaosTokens")
-- removes all bless / curse tokens from the chaos bag and play
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.removeAll = function(playerColor)
getManager().call("doRemove", playerColor)
end
-- removes the specified chaos token from the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.removeChaosToken = function(id)
return Global.call("removeChaosToken", id)
-- adds bless / curse sealing to the hovered card
---@param playerColor string Color of the player to show the broadcast to
---@param hoveredObject tts__Object Hovered object
BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject)
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
end
-- adds bless / curse to the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.addToken = function(type)
getManager().call("addToken", type)
end
-- returns a chaos token to the bag and calls all relevant functions
---@param token tts__Object Chaos token to return
ChaosBagApi.returnChaosTokenToBag = function(token)
return Global.call("returnChaosTokenToBag", token)
-- removes bless / curse from the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.removeToken = function(type)
getManager().call("removeToken", type)
end
BlessCurseManagerApi.getBlessCurseInBag = function()
return getManager().call("getBlessCurseInBag", {})
end
-- spawns the specified chaos token and puts it into the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.spawnChaosToken = function(id)
return Global.call("spawnChaosToken", id)
end
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens
-- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the
-- contents of the bag should check this method before doing so.
-- This method will broadcast a message to all players if the bag is being searched.
---@return any canTouch True if the bag is manipulated, false if it should be blocked.
ChaosBagApi.canTouchChaosTokens = function()
return Global.call("canTouchChaosTokens")
end
-- called by playermats (by the "Draw chaos token" button)
---@param mat tts__Object Playermat that triggered this
---@param drawAdditional boolean Controls whether additional tokens should be drawn
---@param tokenType? string Name of token (e.g. "Bless") to be drawn from the bag
---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag
---@param returnedToken? tts__Object Token to be replaced with newly drawn token
ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, returnedToken)
return Global.call("drawChaosToken", {mat = mat, drawAdditional = drawAdditional, tokenType = tokenType, guidToBeResolved = guidToBeResolved, returnedToken = returnedToken})
end
-- returns a Table List of chaos token ids in the current chaos bag
-- requires copying the data into a new table because TTS is weird about handling table return values in Global
ChaosBagApi.getIdUrlMap = function()
return Global.getTable("ID_URL_MAP")
end
return ChaosBagApi
return BlessCurseManagerApi
end
end)
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)

View File

@ -20,8 +20,7 @@ CustomDeck:
Description: Advanced
DragSelectable: true
GMNotes: "{\n \"id\": \"90051\",\n \"type\": \"Treachery\",\n \"class\": \"Neutral\",\n
\ \"traits\": \"Endtimes.\",\n \"weakness\": true,\n \"cycle\": \"The Dunwich
Legacy\"\n}"
\ \"traits\": \"Endtimes.\",\n \"weakness\": true,\n \"cycle\": \"Laid to Rest\"\n}"
GUID: '561775'
Grid: true
GridProjection: false

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"04003\",\n \"type\": \"Investigator\",\n \"class\": \"Rogue\",\n
\ \"traits\": \"Criminal.\",\n \"willpowerIcons\": 1,\n \"intellectIcons\": 4,\n
\ \"combatIcons\": 3,\n \"agilityIcons\": 4,\n \"cycle\": \"The Forgotten Age\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"Evade\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"04010\": 1\n },\n {\n \"04011\":
1\n },\n {\n \"04012\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"trait\": [\n \"illicit\"\n ],\n \"level\": {\n

View File

@ -49,71 +49,11 @@ VALID_TOKENS = {
["Curse"] = true
}
SHOW_SINGLE_RELEASE = true
MAX_SEALED = 10
KEEP_OPEN = true
require("playercards/CardsThatSealTokens")
end)
__bundle_register("chaosbag/BlessCurseManagerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local BlessCurseManagerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
end
-- removes all taken tokens and resets the counts
BlessCurseManagerApi.removeTakenTokensAndReset = function()
local BlessCurseManager = getManager()
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Bless") end, 0.05)
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Curse") end, 0.10)
Wait.time(function() BlessCurseManager.call("doReset", "White") end, 0.15)
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.sealedToken = function(type, guid)
getManager().call("sealedToken", { type = type, guid = guid })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.releasedToken = function(type, guid)
getManager().call("releasedToken", { type = type, guid = guid })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.returnedToken = function(type, guid)
getManager().call("returnedToken", { type = type, guid = guid })
end
-- broadcasts the current status for bless/curse tokens
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.broadcastStatus = function(playerColor)
getManager().call("broadcastStatus", playerColor)
end
-- removes all bless / curse tokens from the chaos bag and play
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.removeAll = function(playerColor)
getManager().call("doRemove", playerColor)
end
-- adds bless / curse sealing to the hovered card
---@param playerColor string Color of the player to show the broadcast to
---@param hoveredObject tts__Object Hovered object
BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject)
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
end
return BlessCurseManagerApi
end
end)
__bundle_register("chaosbag/ChaosBagApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local ChaosBagApi = {}
@ -121,7 +61,7 @@ do
-- respawns the chaos bag with a new state of tokens
---@param tokenList table List of chaos token ids
ChaosBagApi.setChaosBagState = function(tokenList)
return Global.call("setChaosBagState", tokenList)
Global.call("setChaosBagState", tokenList)
end
-- returns a Table List of chaos token ids in the current chaos bag
@ -148,49 +88,57 @@ do
-- returns all sealed tokens on cards to the chaos bag
---@param playerColor string Color of the player to show the broadcast to
ChaosBagApi.releaseAllSealedTokens = function(playerColor)
return Global.call("releaseAllSealedTokens", playerColor)
Global.call("releaseAllSealedTokens", playerColor)
end
-- returns all drawn tokens to the chaos bag
ChaosBagApi.returnChaosTokens = function()
return Global.call("returnChaosTokens")
Global.call("returnChaosTokens")
end
-- removes the specified chaos token from the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.removeChaosToken = function(id)
return Global.call("removeChaosToken", id)
Global.call("removeChaosToken", id)
end
-- returns a chaos token to the bag and calls all relevant functions
---@param token tts__Object Chaos token to return
ChaosBagApi.returnChaosTokenToBag = function(token)
return Global.call("returnChaosTokenToBag", token)
---@param fromBag boolean whether or not the token to return was in the middle of being drawn (true) or elsewhere (false)
ChaosBagApi.returnChaosTokenToBag = function(token, fromBag)
Global.call("returnChaosTokenToBag", { token = token, fromBag = fromBag })
end
-- spawns the specified chaos token and puts it into the chaos bag
---@param id string ID of the chaos token
ChaosBagApi.spawnChaosToken = function(id)
return Global.call("spawnChaosToken", id)
Global.call("spawnChaosToken", id)
end
-- Checks to see if the chaos bag can be manipulated. If a player is searching the bag when tokens
-- are drawn or replaced a TTS bug can cause those tokens to vanish. Any functions which change the
-- contents of the bag should check this method before doing so.
-- This method will broadcast a message to all players if the bag is being searched.
---@return any canTouch True if the bag is manipulated, false if it should be blocked.
---@return any: True if the bag is manipulated, false if it should be blocked.
ChaosBagApi.canTouchChaosTokens = function()
return Global.call("canTouchChaosTokens")
end
-- called by playermats (by the "Draw chaos token" button)
-- 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
---@param tokenType? string Name of token (e.g. "Bless") to be drawn from the bag
---@param guidToBeResolved? string GUID of the sealed token to be resolved instead of drawing a token from the bag
---@param returnedToken? tts__Object Token to be replaced with newly drawn token
ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, returnedToken)
return Global.call("drawChaosToken", {mat = mat, drawAdditional = drawAdditional, tokenType = tokenType, guidToBeResolved = guidToBeResolved, returnedToken = returnedToken})
---@param takeParameters? table Position and rotation of the location where the new token should be drawn to, usually to replace a returned token
---@return tts__Object: Object reference to the token that was drawn
ChaosBagApi.drawChaosToken = function(mat, drawAdditional, tokenType, guidToBeResolved, takeParameters)
return Global.call("drawChaosToken", {
mat = mat,
drawAdditional = drawAdditional,
tokenType = tokenType,
guidToBeResolved = guidToBeResolved,
takeParameters = takeParameters
})
end
-- returns a Table List of chaos token ids in the current chaos bag
@ -202,15 +150,16 @@ do
return ChaosBagApi
end
end)
__bundle_register("playermat/PlaymatApi", function(require, _LOADED, __bundle_register, __bundle_modules)
__bundle_register("playermat/PlayermatApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local PlaymatApi = {}
local PlayermatApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local searchLib = require("util/SearchLib")
local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }
-- Convenience function to look up a mat's object by color, or get all mats.
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
---@return table: Single-element if only single playmat is requested
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@return table: Single-element if only single playermat is requested
local function getMatForColor(matColor)
if matColor == "All" then
return guidReferenceApi.getObjectsByType("Playermat")
@ -219,9 +168,9 @@ do
end
end
-- Returns the color of the closest playmat
-- Returns the color of the closest playermat
---@param startPos table Starting position to get the closest mat from
PlaymatApi.getMatColorByPosition = function(startPos)
PlayermatApi.getMatColorByPosition = function(startPos)
local result, smallestDistance
for matColor, mat in pairs(getMatForColor("All")) do
local distance = Vector.between(startPos, mat.getPosition()):magnitude()
@ -233,17 +182,17 @@ do
return result
end
-- Returns the color of the player's hand that is seated next to the playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getPlayerColor = function(matColor)
-- Returns the color of the player's hand that is seated next to the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getPlayerColor = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("playerColor")
end
end
-- Returns the color of the playmat that owns the playercolor's hand
---@param handColor string Color of the playmat
PlaymatApi.getMatColor = function(handColor)
-- Returns the color of the playermat that owns the playercolor's hand
---@param handColor string Color of the playermat
PlayermatApi.getMatColor = function(handColor)
for matColor, mat in pairs(getMatForColor("All")) do
local playerColor = mat.getVar("playerColor")
if playerColor == handColor then
@ -252,41 +201,69 @@ do
end
end
-- Returns if there is the card "Dream-Enhancing Serum" on the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.isDES = function(matColor)
-- Instructs a playermat to check for DES
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.checkForDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("isDES")
mat.call("checkForDES")
end
end
-- Performs a search of the deck area of the requested playmat and returns the result as table
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDeckAreaObjects = function(matColor)
-- Returns if there is the card "Dream-Enhancing Serum" on the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@return boolean: whether DES is present on the playermat
PlayermatApi.hasDES = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("hasDES")
end
end
-- gets the slot data for the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getSlotData = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getTable("slotData")
end
end
-- sets the slot data for the playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param newSlotData table New slot data for the playermat
PlayermatApi.loadSlotData = function(matColor, newSlotData)
for _, mat in pairs(getMatForColor(matColor)) do
mat.setTable("slotData", newSlotData)
mat.call("redrawSlotSymbols")
return
end
end
-- Performs a search of the deck area of the requested playermat and returns the result as table
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDeckAreaObjects = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getDeckAreaObjects")
end
end
-- Flips the top card of the deck (useful after deck manipulation for Norman Withers)
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.flipTopCardFromDeck = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.flipTopCardFromDeck = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("flipTopCardFromDeck")
end
end
-- Returns the position of the discard pile of the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDiscardPosition = function(matColor)
-- Returns the position of the discard pile of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDiscardPosition = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDiscardPosition")
end
end
-- Returns the position of the draw pile of the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.getDrawPosition = function(matColor)
-- Returns the position of the draw pile of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getDrawPosition = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("returnGlobalDrawPosition")
end
@ -294,25 +271,25 @@ do
-- Transforms a local position into a global position
---@param localPos table Local position to be transformed
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.transformLocalPosition = function(localPos, matColor)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.transformLocalPosition = function(localPos, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.positionToWorld(localPos)
end
end
-- Returns the rotation of the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.returnRotation = function(matColor)
-- Returns the rotation of the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnRotation = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getRotation()
end
end
-- Returns a table with spawn data (position and rotation) for a helper object
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param helperName string Name of the helper object
PlaymatApi.getHelperSpawnData = function(matColor, helperName)
PlayermatApi.getHelperSpawnData = function(matColor, helperName)
local resultTable = {}
local localPositionTable = {
["Hand Helper"] = {0.05, 0, -1.182},
@ -329,82 +306,99 @@ do
end
-- Triggers the Upkeep for the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
-- Triggers the Upkeep for the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param playerColor string Color of the calling player (for messages)
PlaymatApi.doUpkeepFromHotkey = function(matColor, playerColor)
PlayermatApi.doUpkeepFromHotkey = function(matColor, playerColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doUpkeepFromHotkey", playerColor)
end
end
-- Handles discarding for the requested playmat for the provided list of objects
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
-- Handles discarding for the requested playermat for the provided list of objects
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param objList table List of objects to discard
PlaymatApi.discardListOfObjects = function(matColor, objList)
PlayermatApi.discardListOfObjects = function(matColor, objList)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("discardListOfObjects", objList)
end
end
-- Returns the active investigator id
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlaymatApi.returnInvestigatorId = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnInvestigatorId = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorId")
end
end
-- Returns the class of the active investigator
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
PlayermatApi.returnInvestigatorClass = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.getVar("activeInvestigatorClass")
end
end
-- Returns the position for encounter card drawing
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param stack boolean If true, returns the leftmost position instead of the first empty from the right
PlaymatApi.getEncounterCardDrawPosition = function(matColor, stack)
PlayermatApi.getEncounterCardDrawPosition = function(matColor, stack)
for _, mat in pairs(getMatForColor(matColor)) do
return Vector(mat.call("getEncounterCardDrawPosition", stack))
end
end
-- Sets the requested playmat's snap points to limit snapping to matching card types or not. If
-- Sets the requested playermat's snap points to limit snapping to matching card types or not. If
-- matchTypes is true, the main card slot snap points will only snap assets, while the
-- investigator area point will only snap Investigators. If matchTypes is false, snap points will
-- be reset to snap all cards.
---@param matchCardTypes boolean Whether snap points should only snap for the matching card types
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.setLimitSnapsByType = function(matchCardTypes, matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.setLimitSnapsByType = function(matchCardTypes, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("setLimitSnapsByType", matchCardTypes)
end
end
-- Sets the requested playmat's draw 1 button to visible
-- Sets the requested playermat's draw 1 button to visible
---@param isDrawButtonVisible boolean Whether the draw 1 button should be visible or not
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.showDrawButton = function(isDrawButtonVisible, matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.showDrawButton = function(isDrawButtonVisible, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("showDrawButton", isDrawButtonVisible)
end
end
-- Shows or hides the clickable clue counter for the requested playmat
-- Shows or hides the clickable clue counter for the requested playermat
---@param showCounter boolean Whether the clickable counter should be present or not
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.clickableClues = function(showCounter, matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.clickableClues = function(showCounter, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("clickableClues", showCounter)
end
end
-- Removes all clues (to the trash for tokens and counters set to 0) for the requested playmat
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.removeClues = function(matColor)
-- Toggles the use of class textures for the requested playermat
---@param state boolean Whether the class texture should be used or not
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.useClassTexture = function(state, matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("useClassTexture", state)
end
end
-- Removes all clues (to the trash for tokens and counters set to 0) for the requested playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.removeClues = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("removeClues")
end
end
-- Reports the clue count for the requested playmat
-- Reports the clue count for the requested playermat
---@param useClickableCounters boolean Controls which type of counter is getting checked
PlaymatApi.getClueCount = function(useClickableCounters, matColor)
PlayermatApi.getClueCount = function(useClickableCounters, matColor)
local count = 0
for _, mat in pairs(getMatForColor(matColor)) do
count = count + mat.call("getClueCount", useClickableCounters)
@ -413,37 +407,36 @@ do
end
-- Updates the specified owned counter
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param type string Counter to target
---@param newValue number Value to set the counter to
---@param modifier number If newValue is not provided, the existing value will be adjusted by this modifier
PlaymatApi.updateCounter = function(matColor, type, newValue, modifier)
PlayermatApi.updateCounter = function(matColor, type, newValue, modifier)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("updateCounter", { type = type, newValue = newValue, modifier = modifier })
end
end
-- Triggers the draw function for the specified playmat
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
-- Triggers the draw function for the specified playermat
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param number number Amount of cards to draw
PlaymatApi.drawCardsWithReshuffle = function(matColor, number)
PlayermatApi.drawCardsWithReshuffle = function(matColor, number)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("drawCardsWithReshuffle", number)
end
end
-- Returns the resource counter amount
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
---@param matColor string Color of the playermat - White, Orange, Green or Red (does not support "All")
---@param type string Counter to target
PlaymatApi.getCounterValue = function(matColor, type)
PlayermatApi.getCounterValue = function(matColor, type)
for _, mat in pairs(getMatForColor(matColor)) do
return mat.call("getCounterValue", type)
end
end
-- Returns a list of mat colors that have an investigator placed
PlaymatApi.getUsedMatColors = function()
local localInvestigatorPosition = { x = -1.17, y = 1, z = -0.01 }
PlayermatApi.getUsedMatColors = function()
local usedColors = {}
for matColor, mat in pairs(getMatForColor("All")) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
@ -455,18 +448,39 @@ do
return usedColors
end
-- Returns investigator name
---@param matColor string Color of the playmat - White, Orange, Green or Red (does not support "All")
PlayermatApi.getInvestigatorName = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
local searchPos = mat.positionToWorld(localInvestigatorPosition)
local searchResult = searchLib.atPosition(searchPos, "isCardOrDeck")
if #searchResult == 1 then
return searchResult[1].getName()
end
end
return ""
end
-- Resets the specified skill tracker to "1, 1, 1, 1"
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.resetSkillTracker = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.resetSkillTracker = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("resetSkillTracker")
end
end
-- Finds all objects on the playmat and associated set aside zone and returns a table
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
-- Redraws the XML for the slot symbols based on the slotData table
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.redrawSlotSymbols = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("redrawSlotSymbols")
end
end
-- Finds all objects on the playermat and associated set aside zone and returns a table
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
---@param filter string Name of the filte function (see util/SearchLib)
PlaymatApi.searchAroundPlaymat = function(matColor, filter)
PlayermatApi.searchAroundPlayermat = function(matColor, filter)
local objList = {}
for _, mat in pairs(getMatForColor(matColor)) do
for _, obj in ipairs(mat.call("searchAroundSelf", filter)) do
@ -477,33 +491,33 @@ do
end
-- Discard a non-hidden card from the corresponding player's hand
---@param matColor string Color of the playmat - White, Orange, Green, Red or All
PlaymatApi.doDiscardOne = function(matColor)
---@param matColor string Color of the playermat - White, Orange, Green, Red or All
PlayermatApi.doDiscardOne = function(matColor)
for _, mat in pairs(getMatForColor(matColor)) do
mat.call("doDiscardOne")
end
end
-- Triggers the metadata sync for all playmats
PlaymatApi.syncAllCustomizableCards = function()
-- Triggers the metadata sync for all playermats
PlayermatApi.syncAllCustomizableCards = function()
for _, mat in pairs(getMatForColor("All")) do
mat.call("syncAllCustomizableCards")
end
end
return PlaymatApi
return PlayermatApi
end
end)
__bundle_register("util/SearchLib", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local SearchLib = {}
local filterFunctions = {
isActionToken = function(x) return x.getDescription() == "Action Token" end,
isCard = function(x) return x.type == "Card" end,
isDeck = function(x) return x.type == "Deck" end,
isCardOrDeck = function(x) return x.type == "Card" or x.type == "Deck" end,
isClue = function(x) return x.memo == "clueDoom" and x.is_face_down == false end,
isTileOrToken = function(x) return x.type == "Tile" end
isTileOrToken = function(x) return x.type == "Tile" end,
isUniversalToken = function(x) return x.getMemo() == "universalActionAbility" end,
}
-- performs the actual search and returns a filtered list of object references
@ -527,7 +541,7 @@ do
max_distance = maxDistance or 0
})
-- filtering the result
-- filter the result for matching objects
local objList = {}
for _, v in ipairs(searchResult) do
if not filter or filterFunc(v.hit_object) then
@ -544,21 +558,22 @@ do
-- searches the area on an object
SearchLib.onObject = function(obj, filter)
pos = obj.getPosition()
size = obj.getBounds().size:setAt("y", 1)
local pos = obj.getPosition()
local size = obj.getBounds().size:setAt("y", 1)
return returnSearchResult(pos, _, size, filter)
end
-- searches the specified position (a single point)
SearchLib.atPosition = function(pos, filter)
size = { 0.1, 2, 0.1 }
local size = { 0.1, 2, 0.1 }
return returnSearchResult(pos, _, size, filter)
end
-- searches below the specified position (downwards until y = 0)
SearchLib.belowPosition = function(pos, filter)
direction = { 0, -1, 0 }
maxDistance = pos.y
local size = { 0.1, 2, 0.1 }
local direction = { 0, -1, 0 }
local maxDistance = pos.y
return returnSearchResult(pos, _, size, filter, direction, maxDistance)
end
@ -568,8 +583,17 @@ end)
__bundle_register("playercards/CardsThatSealTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that seal tokens
This file is used to add sealing option to cards' context menu.
NOTE: all cards are allowed to release a single token to enable Hallow and A Watchful Peace,
and to release all sealed tokens to allow for cards that might leave play with sealed tokens on them.
Valid options (set before requiring this file):
MAX_SEALED --@type: number (maximum number of tokens allowable by the card to be sealed)
- required for all cards
- if MAX_SEALED is more than 1, then an XML label is created for the topmost token indicating the number of sealed tokens
- gives an error if user tries to seal additional tokens on the card
- example usage: "The Chthonian Stone"
> MAX_SEALED = 1
UPDATE_ON_HOVER --@type: boolean
- automatically updates the context menu options when the card is hovered
- the "Read Bag" function reads the content of the chaos bag to update the context menu
@ -580,19 +604,16 @@ KEEP_OPEN --@type: boolean
- makes the context menu stay open after selecting an option
- example usage: "Unrelenting"
SHOW_SINGLE_RELEASE --@type: boolean
SHOW_MULTI_RELEASE --@type: number (maximum amount of tokens to release at once)
- enables an entry in the context menu
- this entry allows releasing a single token
- example usage: "Holy Spear" (to keep the other tokens and just release one)
SHOW_MULTI_RELEASE --@type: number (amount of tokens to release at once)
- enables an entry in the context menu
- this entry allows releasing of multiple tokens at once
- example usage: "Nephthys" (to release 3 bless tokens at once)
- this entry allows releasing of multiple tokens at once, to the maximum number
- does not fail if there are fewer than the maximum sealed
- example usage: "Nephthys" (to release up to 3 bless tokens at once)
SHOW_MULTI_RETURN --@type: number (amount of tokens to return to pool at once)
- enables an entry in the context menu
- this entry allows returning tokens to the token pool
- fails if not enough tokens are sealed
- example usage: "Nephthys" (to return 3 bless tokens at once)
SHOW_MULTI_SEAL --@type: number (amount of tokens to seal at once)
@ -626,6 +647,7 @@ Thus it should be implemented like this:
> ["+1"] = true,
> ["Elder Sign"] = true
> }
> MAX_SEALED = 1
> require...
----------------------------------------------------------
Example 2: Holy Spear
@ -636,21 +658,35 @@ Thus it should be implemented like this:
> VALID_TOKENS = {
> ["Bless"] = true
> }
> SHOW_SINGLE_RELEASE = true
> SHOW_MULTI_SEAL = 2
> MAX_SEALED = 10
> require...
----------------------------------------------------------]]
local blessCurseManagerApi = require("chaosbag/BlessCurseManagerApi")
local chaosBagApi = require("chaosbag/ChaosBagApi")
local guidReferenceApi = require("core/GUIDReferenceApi")
local playmatApi = require("playermat/PlaymatApi")
local playermatApi = require("playermat/PlayermatApi")
local tokenArrangerApi = require("accessories/TokenArrangerApi")
local sealedTokens = {}
local ID_URL_MAP = {}
local tokensInBag = {}
-- XML background color for each token for label when stacked
local tokenColor = {
["Skull"] = "#4A0400E6",
["Cultist"] = "#173B0BE6",
["Tablet"] = "#1D2238E6",
["Elder Thing"] = "#4D2331E6",
["Auto-fail"] = "#9B0004E6",
["Bless"] = "#9D702CE6",
["Curse"] = "#633A84E6",
["Frost"] = "#404450E6",
["Elder Sign"] = "#50A8CEE6",
[""] = "#77674DE6"
}
function onSave() return JSON.encode(sealedTokens) end
function onLoad(savedData)
@ -662,13 +698,15 @@ end
-- builds the context menu
function generateContextMenu()
-- conditional single or multi release options
if SHOW_SINGLE_RELEASE then
self.addContextMenuItem("Release token", releaseOneToken)
elseif SHOW_MULTI_RELEASE then
self.addContextMenuItem("Release one token", releaseOneToken)
-- conditional release options
if MAX_SEALED > 1 then
self.addContextMenuItem("Release all tokens", releaseAllTokens)
end
if SHOW_MULTI_RELEASE then
self.addContextMenuItem("Release " .. SHOW_MULTI_RELEASE .. " token(s)", releaseMultipleTokens)
else
self.addContextMenuItem("Release token(s)", releaseAllTokens)
end
if RESOLVE_TOKEN then
@ -706,7 +744,7 @@ function generateContextMenu()
end
if allowed then
for i = 1, SHOW_MULTI_SEAL do
for i = SHOW_MULTI_SEAL, 1, -1 do
sealToken(map.name, playerColor)
end
else
@ -743,6 +781,10 @@ end
-- seals the named token on this card
function sealToken(name, playerColor)
if #sealedTokens >= MAX_SEALED then
printToColor("Cannot seal any more tokens on this card", playerColor, "Red")
return
end
if not chaosBagApi.canTouchChaosTokens() then return end
local chaosbag = chaosBagApi.findChaosBag()
for i, obj in ipairs(chaosbag.getObjects()) do
@ -759,6 +801,16 @@ function sealToken(name, playerColor)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.sealedToken(name, guid)
end
-- destroy XML on just covered token
if #sealedTokens > 1 then
local coveredToken = getObjectFromGUID(sealedTokens[#sealedTokens - 1])
if coveredToken ~= nil then
coveredToken.UI.setXml("")
else
table.remove(sealedTokens, #sealedTokens - 1)
end
end
updateStackSize()
end
})
return
@ -778,16 +830,22 @@ function releaseOneToken(playerColor)
end
end
-- release multiple tokens at once
-- release up to multiple tokens at once with no minimum
function releaseMultipleTokens(playerColor)
if SHOW_MULTI_RELEASE <= #sealedTokens then
for i = 1, SHOW_MULTI_RELEASE do
putTokenAway(table.remove(sealedTokens))
end
printToColor("Releasing " .. SHOW_MULTI_RELEASE .. " tokens", playerColor)
else
if #sealedTokens == 0 then
printToColor("Not enough tokens sealed.", playerColor)
return
end
local numRemoved = SHOW_MULTI_RELEASE
if #sealedTokens < SHOW_MULTI_RELEASE then
numRemoved = #sealedTokens
end
for i = 1, numRemoved do
putTokenAway(table.remove(sealedTokens))
end
printToColor("Releasing " .. numRemoved .. " tokens", playerColor)
end
-- releases all sealed tokens
@ -828,6 +886,7 @@ function putTokenAway(guid)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.releasedToken(name, guid)
end
updateStackSize()
end
-- returns the token to the pool (== removes it)
@ -840,6 +899,7 @@ function returnToken(guid)
if name == "Bless" or name == "Curse" then
blessCurseManagerApi.returnedToken(name, guid)
end
updateStackSize()
end
-- resolves sealed token as if it came from the chaos bag
@ -848,11 +908,47 @@ function resolveSealed()
broadcastToAll("No tokens sealed.", "Red")
return
end
local closestMatColor = playmatApi.getMatColorByPosition(self.getPosition())
local closestMatColor = playermatApi.getMatColorByPosition(self.getPosition())
local mat = guidReferenceApi.getObjectByOwnerAndType(closestMatColor, "Playermat")
local guidToBeResolved = table.remove(sealedTokens)
local resolvedToken = getObjectFromGUID(guidToBeResolved)
resolvedToken.UI.setXml("")
updateStackSize()
chaosBagApi.drawChaosToken(mat, true, _, guidToBeResolved)
end
function updateStackSize()
if MAX_SEALED == 1 then return end
if #sealedTokens == 0 then return end
-- get topmost sealed token
local topToken = getObjectFromGUID(sealedTokens[#sealedTokens])
local name = topToken.getName()
topToken.UI.setXmlTable({
{
tag = "Panel",
attributes = {
height = 380,
width = 380,
rotation = "0 0 180",
scale = "0.2 0.2 1",
position = "0 0 -12",
color = tokenColor[name] or "#77674DE6"
},
children = {
tag = "Text",
attributes = {
fontSize = "380",
font = "font_teutonic-arkham",
color = "#ffffff",
outline = "#000000",
outlineSize = "8 -8",
text = "x" .. #sealedTokens
}
}
}
})
end
end)
__bundle_register("accessories/TokenArrangerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
@ -888,6 +984,83 @@ do
return TokenArrangerApi
end
end)
__bundle_register("chaosbag/BlessCurseManagerApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local BlessCurseManagerApi = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
local function getManager()
return guidReferenceApi.getObjectByOwnerAndType("Mythos", "BlessCurseManager")
end
-- removes all taken tokens and resets the counts
BlessCurseManagerApi.removeTakenTokensAndReset = function()
local BlessCurseManager = getManager()
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Bless") end, 0.05)
Wait.time(function() BlessCurseManager.call("removeTakenTokens", "Curse") end, 0.10)
Wait.time(function() BlessCurseManager.call("doReset", "White") end, 0.15)
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.sealedToken = function(type, guid)
getManager().call("sealedToken", { type = type, guid = guid })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
---@param fromBag? boolean Whether or not token was just drawn from the chaos bag
BlessCurseManagerApi.releasedToken = function(type, guid, fromBag)
getManager().call("releasedToken", { type = type, guid = guid, fromBag = fromBag })
end
-- updates the internal count (called by cards that seal bless/curse tokens)
---@param type string Type of chaos token ("Bless" or "Curse")
---@param guid string GUID of the token
BlessCurseManagerApi.returnedToken = function(type, guid)
getManager().call("returnedToken", { type = type, guid = guid })
end
-- broadcasts the current status for bless/curse tokens
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.broadcastStatus = function(playerColor)
getManager().call("broadcastStatus", playerColor)
end
-- removes all bless / curse tokens from the chaos bag and play
---@param playerColor string Color of the player to show the broadcast to
BlessCurseManagerApi.removeAll = function(playerColor)
getManager().call("doRemove", playerColor)
end
-- adds bless / curse sealing to the hovered card
---@param playerColor string Color of the player to show the broadcast to
---@param hoveredObject tts__Object Hovered object
BlessCurseManagerApi.addBlurseSealingMenu = function(playerColor, hoveredObject)
getManager().call("addMenuOptions", { playerColor = playerColor, hoveredObject = hoveredObject })
end
-- adds bless / curse to the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.addToken = function(type)
getManager().call("addToken", type)
end
-- removes bless / curse from the chaos bag
---@param type string Type of chaos token ("Bless" or "Curse")
BlessCurseManagerApi.removeToken = function(type)
getManager().call("removeToken", type)
end
BlessCurseManagerApi.getBlessCurseInBag = function()
return getManager().call("getBlessCurseInBag", {})
end
return BlessCurseManagerApi
end
end)
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local GUIDReferenceApi = {}

View File

@ -21,7 +21,8 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"60121\",\n \"type\": \"Event\",\n \"class\": \"Guardian\",\n
\ \"cost\": 2,\n \"level\": 1,\n \"traits\": \"Spirit.\",\n \"willpowerIcons\":
2,\n \"cycle\": \"Investigator Packs\"\n}"
2,\n \"uses\": [\n {\n \"count\": 1,\n \"type\": \"Fight\",\n \"token\":
\"universalActionAbility\"\n }\n ],\n \"cycle\": \"Investigator Packs\"\n}"
GUID: 9e7f6a
Grid: true
GridProjection: false

View File

@ -21,7 +21,9 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"09053\",\n \"type\": \"Event\",\n \"class\": \"Seeker\",\n
\ \"cost\": 0,\n \"level\": 1,\n \"traits\": \"Insight.\",\n \"willpowerIcons\":
1,\n \"wildIcons\": 1,\n \"cycle\": \"The Scarlet Keys\"\n}"
1,\n \"wildIcons\": 1,\n \"uses\": [\n {\n \"count\": 1,\n \"type\":
\"Universal\",\n \"token\": \"universalActionAbility\"\n }\n ],\n \"cycle\":
\"The Scarlet Keys\"\n}"
GUID: '425841'
Grid: true
GridProjection: false

View File

@ -21,7 +21,8 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"03265\",\n \"type\": \"Event\",\n \"class\": \"Seeker\",\n
\ \"cost\": 0,\n \"level\": 0,\n \"traits\": \"Insight.\",\n \"wildIcons\": 1,\n
\ \"cycle\": \"The Path to Carcosa\"\n}"
\ \"uses\": [\n {\n \"count\": 1,\n \"type\": \"Universal\",\n \"token\":
\"universalActionAbility\"\n }\n ],\n \"cycle\": \"The Path to Carcosa\"\n}"
GUID: bbfe9b
Grid: true
GridProjection: false

View File

@ -24,14 +24,14 @@ GMNotes: "{\n \"id\": \"10015\",\n \"type\": \"Investigator\",\n \"class\": \
1,\n \"maxCount\": 1,\n \"id\": \"10015-b1\"\n },\n {\n \"count\":
1,\n \"maxCount\": 1,\n \"id\": \"10015-b2\"\n }\n ],\n \"willpowerIcons\":
3,\n \"intellectIcons\": 1,\n \"combatIcons\": 5,\n \"agilityIcons\": 3,\n \"cycle\":
\"The Feast of Hemlock Vale\",\n \"deck_requirements\": {\n \"size\": 35,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"10017\":
1\n },\n {\n \"10018\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"seeker\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"trait\": [\n \"insight\",\n \"spirit\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 2\n },\n \"limit\": 10\n }\n
\ ]\n}"
\"The Feast of Hemlock Vale\",\n \"extraToken\": \"None\",\n \"deck_requirements\":
{\n \"size\": 35,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\":
[\n {\n \"10017\": 1\n },\n {\n \"10018\": 1\n }\n
\ ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"trait\": [\n \"insight\",\n \"spirit\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 2\n },\n
\ \"limit\": 10\n }\n ]\n}"
GUID: 3764cd
Grid: true
GridProjection: false

View File

@ -20,8 +20,8 @@ CustomDeck:
Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"90048\",\n \"type\": \"Treachery\",\n \"class\": \"Neutral\",\n
\ \"traits\": \"Hardship.\",\n \"weakness\": true,\n \"cycle\": \"The Dunwich
Legacy\"\n}"
\ \"traits\": \"Hardship.\",\n \"weakness\": true,\n \"cycle\": \"On the Road
Again\"\n}"
GUID: '876557'
Grid: true
GridProjection: false

View File

@ -22,11 +22,11 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"60201\",\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n
\ \"traits\": \"Miskatonic.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
5,\n \"combatIcons\": 1,\n \"agilityIcons\": 2,\n \"cycle\": \"Investigator Packs\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"60202\": 1\n },\n {\n \"60203\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n }\n ]\n}"
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"60202\":
1\n },\n {\n \"60203\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"seeker\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n }\n ]\n}"
GUID: 1fa944
Grid: true
GridProjection: false

View File

@ -0,0 +1,250 @@
-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("playercards/CardsWithHelper", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that have helpers
This file is used to share code between cards with helpers.
It syncs the visibility of the helper with the option panel and
makes sure the card has the respective tag.
Additionally, it will call 'initiliaze()' and 'shutOff()'
in the parent file if they are present.
Instructions:
1) Define the global variables before requiring this file:
hasXML = true (whether the card has an XML display)
isHelperEnabled = false (default state of the helper, should be 'false')
2) In 'onLoad()'', call 'syncDisplayWithOptionPanel()'
----------------------------------------------------------]]
local optionPanelApi = require("core/OptionPanelApi")
-- if the respective option is enabled in onLoad(), enable the helper
function syncDisplayWithOptionPanel()
self.addTag("CardWithHelper")
local options = optionPanelApi.getOptions()
if options.enableCardHelpers then
setHelperState(true)
else
updateDisplay()
end
end
-- forces a new state
function setHelperState(newState)
isHelperEnabled = newState
updateSave()
updateDisplay()
end
-- toggles the current state
function toggleHelper()
isHelperEnabled = not isHelperEnabled
updateSave()
updateDisplay()
end
-- updates the visibility and calls events (after a small delay to allow XML being set)
function updateDisplay()
Wait.frames(actualDisplayUpdate, 5)
end
function actualDisplayUpdate()
if isHelperEnabled then
self.clearContextMenu()
self.addContextMenuItem("Disable Helper", toggleHelper)
if hasXML then self.UI.show("Helper") end
if initialize then initialize() end
else
self.clearContextMenu()
self.addContextMenuItem("Enable Helper", toggleHelper)
if hasXML then self.UI.hide("Helper") end
if shutOff then shutOff() end
end
end
end)
__bundle_register("core/OptionPanelApi", function(require, _LOADED, __bundle_register, __bundle_modules)
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
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("playercards/cards/HeavyFurs")
end)
__bundle_register("playercards/cards/HeavyFurs", function(require, _LOADED, __bundle_register, __bundle_modules)
VALID_TOKENS = {
["Skull"] = true,
["Tablet"] = true,
["Elder Thing"] = true,
["Cultist"] = true,
["Frost"] = true,
["Custom Token"] = true,
["Elder Sign"] = true,
["Bless"] = true,
["Curse"] = true
}
require("playercards/CardsWithHelper")
require("playercards/CardsThatRedrawTokens")
end)
__bundle_register("playercards/CardsThatRedrawTokens", function(require, _LOADED, __bundle_register, __bundle_modules)
--[[ Library for cards that return and redraw tokens
This file is used to add an XML button to a card, turned on via context menu.
Valid options modify the appearance of the XML button, as well as the
behavior of the return and redraw function. Set options before requiring this file.
Parameters for the return and redraw functions. Typically set VALID_TOKENS or INVALID_TOKENS, not both.
If there are no restrictions on which tokens can be redrawn (e.g. Wendy Adams), keep both empty.
* VALID_TOKENS --@type table
- keyed table which lists all tokens that can be redrawn by the card
- example usage: "False Covenant"
> VALID_TOKENS = {
> ["Curse"] = true
> }
* INVALID_TOKENS --@type table
- keyed table which lists all tokens that cannot be redrawn by the card
- example usage: "Custom Ammunition"
> INVALID_TOKENS = {
> ["Auto-fail"] = true
> }
* DRAW_SPECIFIC_TOKEN --@type string (name of token or nil)
- if set, will attempt to draw that specific token
* RETURN_TO_POOL --@type string
- allows for the name of the card to be passed onto Global for any special handling
The following parameters modify the appearence of the XML button and are not listed as part of a table.
- buttonHeight (default is 450)
- buttonWidth (default is 1400)
- buttonPosition (default is "0 -55 -22")
- buttonFontSize (default is 250)
- buttonRotation (change if button is placed on an investigator cards)
- buttonLabel (default is "Redraw Token")
- buttonIcon (to add an icon to the right)
- buttonColor (default is "#77674DE6")
----------------------------------------------------------
EXAMPLE: Claypool's Furs
This card can only redraw the Frost token, and is replaced with a random token from the bag.
As a nice reminder the XML button takes on the Frost color and icon with the text "Cancel".
> buttonValue = "Cancel"
> buttonIcon = "token-frost"
> buttonColor = "#404450E6"
> buttonFontSize = 300
> VALID_TOKENS = {
> ["Frost"] = true
> }
>
> require...
----------------------------------------------------------]]
-- intentionally global
hasXML = true
isHelperEnabled = false
function updateSave()
self.script_state = JSON.encode({ isHelperEnabled = isHelperEnabled })
end
function onLoad(savedData)
if savedData and savedData ~= "" then
local loadedData = JSON.decode(savedData)
isHelperEnabled = loadedData.isHelperEnabled
end
createHelperXML()
syncDisplayWithOptionPanel()
end
function createHelperXML()
local xmlTable = { {
tag = "Button",
attributes = {
active = "false",
id = "Helper",
height = buttonHeight or 450,
width = buttonWidth or 1400,
rotation = buttonRotation or "0 0 180",
scale = "0.1 0.1 1",
position = buttonPosition or "0 -55 -22",
padding = "50 50 50 50",
font = "font_teutonic-arkham",
fontSize = buttonFontSize or 250,
onClick = "triggerXMLTokenLabelCreation",
color = buttonColor or "#77674DE6",
textColor = "White"
},
value = buttonLabel or "Redraw Token"
} }
if buttonIcon then
xmlTable[1].attributes.iconWidth = "400"
xmlTable[1].attributes.iconAlignment = "Right"
xmlTable[1].attributes.icon = buttonIcon
end
self.UI.setXmlTable(xmlTable)
end
function triggerXMLTokenLabelCreation()
Global.call("activeRedrawEffect", {
VALID_TOKENS = VALID_TOKENS,
INVALID_TOKENS = INVALID_TOKENS,
RETURN_TO_POOL = RETURN_TO_POOL
})
end
end)
return __bundle_require("__root")

View File

@ -30,7 +30,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: ''
LuaScript: !include 'Card Heavy Furs 275450.ttslua'
LuaScriptState: ''
MeasureMovement: false
Name: Card

View File

@ -21,7 +21,7 @@ Description: Artifact from Another Life (Advanced)
DragSelectable: true
GMNotes: "{\n \"id\": \"90018\",\n \"type\": \"Asset\",\n \"slot\": \"Accessory\",\n
\ \"class\": \"Neutral\",\n \"cost\": 3,\n \"traits\": \"Item. Relic.\",\n \"willpowerIcons\":
1,\n \"combatIcons\": 1,\n \"wildIcons\": 2,\n \"cycle\": \"Standalone\"\n}"
1,\n \"combatIcons\": 1,\n \"wildIcons\": 2,\n \"cycle\": \"Bad Blood\"\n}"
GUID: bf151d
Grid: true
GridProjection: false

View File

@ -21,25 +21,29 @@ Description: ''
DragSelectable: true
GMNotes: "{\n \"id\": \"09061\",\n \"type\": \"Event\",\n \"class\": \"Rogue\",\n
\ \"cost\": 1,\n \"level\": 0,\n \"traits\": \"Gambit.\",\n \"agilityIcons\":
1,\n \"customizations\": [\n {\n \"name\": \"Reflex Response\",\n \"xp\":
1,\n \"text\": \"Add the following play condition: \u201C- You take damage
or horror.\u201D\"\n },\n {\n \"name\": \"Situational Awareness\",\n
\ \"xp\": 1,\n \"text\": \"Add the following play condition: \u201C- A
location enters play or is revealed.\u201D\"\n },\n {\n \"name\": \"Killer
Instinct\",\n \"xp\": 1,\n \"text\": \"Add the following play condition:
\u201C- An enemy engages you.\u201D\"\n },\n {\n \"name\": \"Gut Reaction\",\n
\ \"xp\": 1,\n \"text\": \"Add the following play condition: \u201C- A
treachery enters your threat area .\u201D\"\n },\n {\n \"name\": \"Muscle
Memory\",\n \"xp\": 1,\n \"text\": \"Add the following play condition:
\u201C- You play an asset.\u201D\"\n },\n {\n \"name\": \"Sharpened Talent\",\n
\ \"xp\": 2,\n \"text\": \"During the action granted by Honed Instinct,
you get +2 to each of your skills.\"\n },\n {\n \"name\": \"Impulse Control\",\n
\ \"xp\": 3,\n \"text\": \"You may include up to three copies of Honed
Instinct in your deck. Honed Instinct gets \u20131 cost.\",\n \"replaces\":
{\n \"cost\": 0\n }\n },\n {\n \"name\": \"Force of Habit\",\n
\ \"xp\": 5,\n \"text\": \"When you play Honed Instinct, you may take 2
actions instead of 1 (one at a time). Then, remove it from the game.\"\n }\n
\ ],\n \"cycle\": \"The Scarlet Keys\"\n}"
1,\n \"uses\": [\n {\n \"count\": 1,\n \"type\": \"Universal\",\n
\ \"token\": \"universalActionAbility\"\n }\n ],\n \"customizations\":
[\n {\n \"name\": \"Reflex Response\",\n \"xp\": 1,\n \"text\":
\"Add the following play condition: \u201C- You take damage or horror.\u201D\"\n
\ },\n {\n \"name\": \"Situational Awareness\",\n \"xp\": 1,\n \"text\":
\"Add the following play condition: \u201C- A location enters play or is revealed.\u201D\"\n
\ },\n {\n \"name\": \"Killer Instinct\",\n \"xp\": 1,\n \"text\":
\"Add the following play condition: \u201C- An enemy engages you.\u201D\"\n },\n
\ {\n \"name\": \"Gut Reaction\",\n \"xp\": 1,\n \"text\": \"Add
the following play condition: \u201C- A treachery enters your threat area .\u201D\"\n
\ },\n {\n \"name\": \"Muscle Memory\",\n \"xp\": 1,\n \"text\":
\"Add the following play condition: \u201C- You play an asset.\u201D\"\n },\n
\ {\n \"name\": \"Sharpened Talent\",\n \"xp\": 2,\n \"text\":
\"During the action granted by Honed Instinct, you get +2 to each of your skills.\"\n
\ },\n {\n \"name\": \"Impulse Control\",\n \"xp\": 3,\n \"text\":
\"You may include up to three copies of Honed Instinct in your deck. Honed Instinct
gets \u20131 cost.\",\n \"replaces\": {\n \"cost\": 0\n }\n },\n
\ {\n \"name\": \"Force of Habit\",\n \"xp\": 5,\n \"text\": \"When
you play Honed Instinct, you may take 2 actions instead of 1 (one at a time). Then,
remove it from the game.\",\n \"replaces\": {\n \"uses\": [\n {\n
\ \"count\": 2,\n \"type\": \"Universal\",\n \"token\":
\"universalActionAbility\"\n }\n ]\n }\n }\n ],\n \"cycle\":
\"The Scarlet Keys\"\n}"
GUID: 1cde62
Grid: true
GridProjection: false

View File

@ -30,7 +30,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: !include 'Card Jacob Morrison (3) aa38d0.ttslua'
LuaScript: ''
LuaScriptState: ''
MeasureMovement: false
Name: Card
@ -40,6 +40,7 @@ Snap: true
Sticky: true
Tags:
- Asset
- DoNotReady
- PlayerCard
Tooltip: true
Transform:

View File

@ -22,11 +22,11 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"60401\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Clairvoyant.\",\n \"willpowerIcons\": 5,\n \"intellectIcons\":
3,\n \"combatIcons\": 2,\n \"agilityIcons\": 2,\n \"cycle\": \"Investigator Packs\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"60402\": 1\n },\n {\n \"60403\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n }\n ]\n}"
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"60402\":
1\n },\n {\n \"60403\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"mystic\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n }\n ]\n}"
GUID: a2cd75
Grid: true
GridProjection: false

View File

@ -22,14 +22,14 @@ 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\",\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}"
\"The Dunwich Legacy\",\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}"
GUID: 9058d3
Grid: true
GridProjection: false
@ -71,15 +71,15 @@ States:
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 \"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"
\ \"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"
GUID: b954f6
Grid: true
GridProjection: false

View File

@ -21,8 +21,8 @@ Description: The Musician
DragSelectable: true
GMNotes: "{\n \"id\": \"02004-pb\",\n \"type\": \"Investigator\",\n \"class\":
\"Mystic\",\n \"traits\": \"Performer.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 39,\n \"randomBasicWeaknessCount\":
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"Laid to Rest\",\n
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 39,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90050\": 1,\n \"02012\": 1\n
\ },\n {\n \"90051\": 1,\n \"02013\": 1\n },\n {\n
\ \"90052\": 1\n },\n {\n \"90053\": 1\n }\n ]\n

View File

@ -21,11 +21,11 @@ Description: The Musician
DragSelectable: true
GMNotes: "{\n \"id\": \"02004-pf\",\n \"type\": \"Investigator\",\n \"class\":
\"Mystic\",\n \"traits\": \"Performer. Cursed.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90050\": 1,\n \"02012\": 1\n
\ },\n {\n \"90051\": 1,\n \"02013\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"Laid to Rest\",\n
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 30,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90050\":
1,\n \"02012\": 1\n },\n {\n \"90051\": 1,\n \"02013\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\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

View File

@ -21,12 +21,12 @@ Description: The Musician
DragSelectable: true
GMNotes: "{\n \"id\": \"02004-p\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Performer. Cursed.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 39,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90050\": 1,\n \"02012\": 1\n
\ },\n {\n \"90051\": 1,\n \"02013\": 1\n },\n {\n
\ \"90052\": 1\n },\n {\n \"90053\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"neutral\"\n
3,\n \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"Laid to Rest\",\n
\ \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\": 39,\n
\ \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"90050\":
1,\n \"02012\": 1\n },\n {\n \"90051\": 1,\n \"02013\":
1\n },\n {\n \"90052\": 1\n },\n {\n \"90053\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"neutral\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n
\ },\n {\n \"faction\": [\n \"mystic\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 3\n }\n },\n {\n \"faction\":

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"02004\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Performer.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\": 3,\n
\ \"combatIcons\": 3,\n \"agilityIcons\": 2,\n \"cycle\": \"The Dunwich Legacy\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"90050\": 1,\n \"02012\": 1\n
\ },\n {\n \"90051\": 1,\n \"02013\": 1\n }\n ]\n
\ },\n \"deck_options\": [\n {\n \"faction\": [\n \"mystic\",\n

View File

@ -21,7 +21,7 @@ Description: The Dead Speak (Advanced)
DragSelectable: true
GMNotes: "{\n \"id\": \"90050\",\n \"type\": \"Asset\",\n \"slot\": \"Hand\",\n
\ \"class\": \"Neutral\",\n \"cost\": 2,\n \"traits\": \"Item. Instrument. Relic.\",\n
\ \"willpowerIcons\": 2,\n \"wildIcons\": 2,\n \"cycle\": \"The Dunwich Legacy\"\n}"
\ \"willpowerIcons\": 2,\n \"wildIcons\": 2,\n \"cycle\": \"Laid to Rest\"\n}"
GUID: 7dfd5f
Grid: true
GridProjection: false

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"05002\",\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n
\ \"traits\": \"Detective.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\": 4,\n
\ \"combatIcons\": 4,\n \"agilityIcons\": 2,\n \"cycle\": \"The Circle Undone\",\n
\ \"deck_requirements\": {\n \"size\": 40,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 40,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"05009\": 1\n },\n {\n \"05010\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":

View File

@ -22,14 +22,15 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"10004\",\n \"type\": \"Investigator\",\n \"class\": \"Seeker\",\n
\ \"traits\": \"Miskatonic. Scholar.\",\n \"willpowerIcons\": 2,\n \"intellectIcons\":
4,\n \"combatIcons\": 2,\n \"agilityIcons\": 4,\n \"cycle\": \"The Feast of Hemlock
Vale\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"10005\": 1\n },\n {\n \"10008\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"seeker\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"trait\": [\n \"science\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 4\n }\n },\n {\n \"trait\":
[\n \"insight\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
1\n }\n }\n ]\n}"
Vale\",\n \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"10005\":
1\n },\n {\n \"10008\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"seeker\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"trait\": [\n \"science\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 4\n }\n },\n {\n \"trait\": [\n \"insight\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 1\n }\n
\ }\n ]\n}"
GUID: ce2322
Grid: true
GridProjection: false

View File

@ -22,7 +22,7 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"09008\",\n \"type\": \"Investigator\",\n \"class\": \"Rogue\",\n
\ \"traits\": \"Criminal.\",\n \"willpowerIcons\": 3,\n \"intellectIcons\": 2,\n
\ \"combatIcons\": 2,\n \"agilityIcons\": 5,\n \"cycle\": \"The Scarlet Keys\",\n
\ \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
\ \"extraToken\": \"None\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"09009\": 1\n },\n {\n \"09010\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"neutral\",\n
\ \"rogue\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
<!-- include playercards/KohakuNarukami.xml -->
<Defaults>
<Button padding="50 50 50 50"
font="font_teutonic-arkham"
fontSize="200"
iconWidth="300"
iconAlignment="Right"/>
<TableLayout position="0 188 -22"
rotation="0 0 90"
height="1800"
width="700"
scale="0.1 0.1 1"
cellSpacing="80"
cellBackgroundColor="rgba(1,1,1,0)"/>
</Defaults>
<TableLayout id="Helper"
active="false">
<Row>
<Cell>
<Button id="Bless"
icon="token-bless"/>
</Cell>
</Row>
<Row>
<Cell>
<Button id="Curse"
icon="token-curse"/>
</Cell>
</Row>
<Row>
<Cell>
<Button id="Action"
text="Remove tokens"/>
</Cell>
</Row>
<Row>
<Cell>
<Button id="ElderSign"
icon="token-eldersign"/>
</Cell>
</Row>
</TableLayout>
<!-- include playercards/KohakuNarukami.xml -->

View File

@ -22,16 +22,16 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"10012\",\n \"type\": \"Investigator\",\n \"class\": \"Mystic\",\n
\ \"traits\": \"Scholar. Blessed. Cursed.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
4,\n \"combatIcons\": 3,\n \"agilityIcons\": 1,\n \"cycle\": \"The Feast of Hemlock
Vale\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"10013\": 1\n },\n {\n \"10014\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"neutral\"\n
Vale\",\n \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"10013\":
1\n },\n {\n \"10014\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"neutral\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n \"faction\":
[\n \"mystic\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
3\n }\n },\n {\n \"trait\": [\n \"blessed\",\n \"cursed\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n
\ },\n {\n \"faction\": [\n \"mystic\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 3\n }\n },\n {\n \"trait\":
[\n \"blessed\",\n \"cursed\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 5\n }\n },\n {\n \"trait\": [\n \"occult\"\n
\ ],\n \"level\": {\n \"min\": 0,\n \"max\": 0\n }\n
\ }\n ]\n}"
\ },\n {\n \"trait\": [\n \"occult\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 0\n }\n }\n ]\n}"
GUID: 54eaa7
Grid: true
GridProjection: false
@ -40,7 +40,7 @@ HideWhenFaceDown: true
IgnoreFoW: false
LayoutGroupSortIndex: 0
Locked: false
LuaScript: ''
LuaScript: !include "Card K\u014Dhaku Narukami 54eaa7.ttslua"
LuaScriptState: ''
MeasureMovement: false
Name: Card
@ -63,4 +63,4 @@ Transform:
scaleY: 1
scaleZ: 1.15
Value: 0
XmlUI: ''
XmlUI: !include "Card K\u014Dhaku Narukami 54eaa7.xml"

View File

@ -22,12 +22,13 @@ DragSelectable: true
GMNotes: "{\n \"id\": \"04001\",\n \"type\": \"Investigator\",\n \"class\": \"Guardian\",\n
\ \"traits\": \"Veteran. Wayfarer.\",\n \"willpowerIcons\": 4,\n \"intellectIcons\":
3,\n \"combatIcons\": 4,\n \"agilityIcons\": 1,\n \"cycle\": \"The Forgotten
Age\",\n \"deck_requirements\": {\n \"size\": 30,\n \"randomBasicWeaknessCount\":
1,\n \"signatures\": [\n {\n \"04006\": 1\n },\n {\n \"04007\":
1\n }\n ]\n },\n \"deck_options\": [\n {\n \"faction\": [\n \"guardian\",\n
\ \"neutral\"\n ],\n \"level\": {\n \"min\": 0,\n \"max\":
5\n }\n },\n {\n \"faction\": [\n \"rogue\"\n ],\n \"level\":
{\n \"min\": 0,\n \"max\": 2\n }\n }\n ]\n}"
Age\",\n \"extraToken\": \"Reaction\",\n \"deck_requirements\": {\n \"size\":
30,\n \"randomBasicWeaknessCount\": 1,\n \"signatures\": [\n {\n \"04006\":
1\n },\n {\n \"04007\": 1\n }\n ]\n },\n \"deck_options\":
[\n {\n \"faction\": [\n \"guardian\",\n \"neutral\"\n ],\n
\ \"level\": {\n \"min\": 0,\n \"max\": 5\n }\n },\n {\n
\ \"faction\": [\n \"rogue\"\n ],\n \"level\": {\n \"min\":
0,\n \"max\": 2\n }\n }\n ]\n}"
GUID: '126932'
Grid: true
GridProjection: false

View File

@ -21,7 +21,9 @@ Description: The Louisiana Lion
DragSelectable: true
GMNotes: "{\n \"id\": \"01054\",\n \"alternate_ids\": [\n \"01554\"\n ],\n \"type\":
\"Asset\",\n \"slot\": \"Ally\",\n \"class\": \"Rogue\",\n \"cost\": 5,\n \"level\":
1,\n \"traits\": \"Ally. Criminal.\",\n \"intellectIcons\": 1,\n \"cycle\": \"Core\"\n}"
1,\n \"traits\": \"Ally. Criminal.\",\n \"intellectIcons\": 1,\n \"uses\": [\n
\ {\n \"count\": 1,\n \"type\": \"Universal\",\n \"token\": \"universalActionAbility\"\n
\ }\n ],\n \"cycle\": \"Core\"\n}"
GUID: 27446e
Grid: true
GridProjection: false

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