├── .editorconfig ├── .github └── workflows │ ├── pack_packs.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── F_OFL.txt │ ├── OFL.txt │ ├── alchemy-i.ttf │ ├── caslon-b.ttf │ ├── caslon-bi.ttf │ ├── caslon-i.ttf │ ├── caslon.ttf │ ├── fraunces-i.ttf │ ├── fraunces.ttf │ ├── litm-dice.otf │ ├── packard-b.ttf │ ├── packard.ttf │ ├── powell-b.ttf │ └── powell.ttf └── media │ ├── LICENSE │ ├── adventure-theme-alt-bg-top.webp │ ├── adventure-theme-bg-bottom.webp │ ├── adventure-theme-border-bottom.webp │ ├── adventure-theme-border-top.webp │ ├── background-frame.webp │ ├── background.webp │ ├── backpack-top-bg.webp │ ├── backpack.webp │ ├── bg-alt.webp │ ├── birb.webp │ ├── bottom-branch.webp │ ├── bottom-frame-branches.webp │ ├── brush.svg │ ├── burn-c.svg │ ├── burn-t.svg │ ├── burn.svg │ ├── button-bg.webp │ ├── button-border.webp │ ├── challenge-bg.webp │ ├── challenge-border.webp │ ├── character-bg.webp │ ├── checkbox-c.svg │ ├── checkbox.svg │ ├── connector.webp │ ├── dice.webp │ ├── effects.webp │ ├── feather.webp │ ├── flowers-top.webp │ ├── greatness-theme-alt-bg-top.webp │ ├── greatness-theme-bg-bottom.webp │ ├── greatness-theme-border-bottom.webp │ ├── greatness-theme-border-top.webp │ ├── green-leaf.webp │ ├── header-bg.webp │ ├── icons │ ├── LICENSE │ ├── backpack.svg │ ├── cracked-skull.svg │ └── unfurled-scroll.svg │ ├── item-divider.webp │ ├── left-div.webp │ ├── limit-bg.webp │ ├── limit-label.webp │ ├── limit-value.webp │ ├── litm_splash.webp │ ├── logo-b.webp │ ├── logo.webp │ ├── marshal-crest.webp │ ├── middle-branches.webp │ ├── necklace.webp │ ├── note.webp │ ├── origin-theme-alt-bg-top.webp │ ├── origin-theme-bg-bottom.webp │ ├── origin-theme-border-bottom.webp │ ├── origin-theme-border-top.webp │ ├── portraits │ ├── apple-picker-a.webp │ ├── apple-picker-b.webp │ ├── apple-picker-c.webp │ ├── concerned-villagers.webp │ ├── corvine-thaumaturge.webp │ ├── poisoned-wurm.webp │ ├── pond-tower.webp │ ├── red-marshal-a.webp │ ├── red-marshal-b.webp │ ├── red-marshal-c.webp │ ├── sorrowbalm.webp │ ├── wakened-sentry.webp │ ├── willow.webp │ ├── wise-one-a.webp │ ├── wise-one-b.webp │ └── wise-one-c.webp │ ├── progress-bg.webp │ ├── promotional-a.webp │ ├── promotional-b.webp │ ├── raven.webp │ ├── right-div.webp │ ├── scenes │ ├── broken-watchtower.webp │ ├── map-of-the-dales.webp │ ├── ravenhome_tavern.webp │ └── roadside-fields.webp │ ├── scroll-background.webp │ ├── scroll-border.webp │ ├── section-bg.webp │ ├── separator.webp │ ├── single-flower.webp │ ├── skull.webp │ ├── story-tag-bg.webp │ ├── tabs-bg.webp │ ├── tabs-collapse.webp │ ├── tag-divider.webp │ ├── theme-bg-top.webp │ ├── tokens │ ├── litm-demo-token-alderman.png │ ├── litm-demo-token-aleonora.png │ ├── litm-demo-token-apple-picker-a.png │ ├── litm-demo-token-apple-picker-b.png │ ├── litm-demo-token-apple-picker-c.png │ ├── litm-demo-token-corvine.png │ ├── litm-demo-token-mackross.png │ ├── litm-demo-token-pond.png │ ├── litm-demo-token-red-marshal-a.png │ ├── litm-demo-token-red-marshal-b.png │ ├── litm-demo-token-red-marshal-c.png │ ├── litm-demo-token-sorrowbalm.png │ ├── litm-demo-token-villagers.png │ ├── litm-demo-token-waken.png │ ├── litm-demo-token-willow.png │ ├── litm-demo-token-wise-one-a.png │ ├── litm-demo-token-wise-one-b.png │ ├── litm-demo-token-wise-one-c.png │ └── litm-demo-token-wurm.png │ ├── top-frame-branches.webp │ └── yellow-leaf.webp ├── biome.json ├── jsconfig.json ├── lang ├── cn.json ├── de.json ├── en.json ├── es.json └── no.json ├── litm.css ├── litm.js ├── packs ├── .gitattributes ├── .gitignore └── tinderbox-demo │ ├── 000518.ldb │ ├── 000524.log │ ├── CURRENT │ ├── LOCK │ ├── LOG │ ├── LOG.old │ └── MANIFEST-000523 ├── scripts ├── actor │ ├── challenge │ │ ├── challenge-data.js │ │ └── challenge-sheet.js │ └── character │ │ ├── character-data.js │ │ └── character-sheet.js ├── apps │ ├── dice.js │ ├── import-character.js │ ├── roll-dialog.js │ ├── roll.js │ └── story-tags.js ├── components │ ├── super-checkbox.js │ └── toggled-input.js ├── data │ └── abstract.js ├── item │ ├── backpack │ │ ├── backpack-data.js │ │ └── backpack-sheet.js │ ├── theme │ │ ├── theme-data.js │ │ └── theme-sheet.js │ └── threat │ │ ├── threat-data.js │ │ └── threat-sheet.js ├── logger.js ├── mixins │ └── sheet-mixin.js ├── system │ ├── config.js │ ├── enrichers.js │ ├── fonts.js │ ├── handlebars.js │ ├── hooks.js │ ├── keybindings.js │ ├── settings.js │ └── sockets.js └── utils.js ├── styles ├── actor │ ├── challenge.css │ └── character.css ├── apps │ ├── loot-dialog.css │ ├── roll-dialog.css │ ├── sidebar-buttons.css │ └── story-tags.css ├── chat │ └── message.css ├── components.css ├── dark.css ├── foundry.css ├── item │ ├── backpack.css │ ├── theme.css │ └── threat.css ├── popout.css └── root.css ├── system.json ├── templates ├── actor │ ├── challenge.html │ └── character.html ├── apps │ ├── loot-dialog.html │ ├── roll-dialog.html │ └── story-tags.html ├── chat │ ├── message-tooltip.html │ ├── message.html │ └── moderation.html ├── item │ ├── backpack-ro.html │ ├── backpack.html │ ├── theme-ro.html │ ├── theme.html │ └── threat.html └── partials │ ├── new-tag.html │ └── tag.html └── version.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | root = true 3 | indent_size = 2 4 | indent_style = tab 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /.github/workflows/pack_packs.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Foundry Package Registry 2 | on: 3 | workflow_dispatch: 4 | 5 | 6 | jobs: 7 | publish: 8 | name: Packman 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Node 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 'lts/*' 17 | cache: 'npm' 18 | - name: Packer 19 | id: packs 20 | uses: aMediocreDad/foundry-db-packer@next 21 | with: 22 | inputdir: packs/tinderbox-demo/_source 23 | packsdir: packs/ 24 | - name: Upload Artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: packed-packs 28 | path: packs/ -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Foundry Package Registry 2 | on: 3 | release: 4 | types: 5 | - published 6 | 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Get Version Number 14 | id: tag 15 | run: | 16 | TAG_NAME=${{ github.event.release.tag_name }} 17 | echo "version=${TAG_NAME//v/}" >> "$GITHUB_OUTPUT" 18 | - name: Push to Foundry 19 | id: foundry 20 | uses: fjogeleit/http-request-action@v1 21 | with: 22 | url: 'https://api.foundryvtt.com/_api/packages/release_version/' 23 | customHeaders: '{ "Content-Type": "application/json", "Authorization": "${{ secrets.FOUNDRY_API_KEY }}" }' 24 | data: 25 | '{ 26 | "id": "litm", 27 | "release": { 28 | "version": "${{ steps.tag.outputs.version }}", 29 | "manifest": "https://raw.githubusercontent.com/aMediocreDad/litm/${{ github.event.release.tag_name }}/system.json", 30 | "notes": "https://github.com/aMediocreDad/litm/releases/tag/${{ github.event.release.tag_name }}", 31 | "compatibility": { 32 | "minimum": "12", 33 | "verified": "12", 34 | "maximum": "12" 35 | } 36 | } 37 | }' 38 | - name: Summary 39 | run: | 40 | echo "**Version:** ${{ steps.tag.outputs.version }}" >> $GITHUB_STEP_SUMMARY 41 | echo "**Release Notes:** ${{ github.event.release.html_url }}" >> $GITHUB_STEP_SUMMARY 42 | echo "**Published:** ${{ steps.foundry.outputs.response.status }}" >> $GITHUB_STEP_SUMMARY 43 | echo "**URL:** ${{ steps.foundry.outputs.response.page }}" >> $GITHUB_STEP_SUMMARY -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fvtt 2 | diff.js 3 | foundry.js 4 | .DS_Store 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Filip Ambrosius 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This system will eventually be removed from Foundry. Please use: https://github.com/taragnor/city-of-mist 3 | 4 |
5 |

⚜️Legend in the Mist Demo

6 |
For Foundry Virtual Tabletop
7 |
8 | 9 |
10 | 11 | > A rustic fantasy tabletop RPG based on the acclaimed City of Mist. Spin a tale of journey and peril where choices transform gameplay. 12 | 13 | ## Welcome to Legend in the Mist 14 | 15 | This is a system for playing the game **Legend in the Mist** in Foundry Virtual Tabletop. 16 | 17 | ## Installation 18 | 19 | Thank you for wanting to try out this system! It is not under development anymore. https://github.com/taragnor/city-of-mist 20 | 21 | **To install the system in Foundry Virtual Tabletop:** 22 | 23 | 1. Find the system called "Legend in the Mist" in the systems list in the setup menu. 24 | 25 | 2. Create a new world using the "Legend in the Mist" system. 26 | 27 | \*_This system is published with express permission from **Son of Oak**_ 28 | 29 | # Special Thanks 30 | 31 | This project already has some contributors, help and good vibes! 32 | 33 | - Thanks to **@Daegony** for contributing graphics, designs, UX advice and help with rules and the game in general. 34 | 35 | - Thanks to **@CussaMitre** for contributing code and squashing bugs. 36 | 37 | - Thanks to **@Metamancer** for being a rubber duck, and giving advice on both the game and development. 38 | 39 | - Thanks to **@Altervayne** for creating the online character creator and setting up a Discord community for us. 40 | 41 | - Thanks to the _**City of Mist**_ Discord for contributing feedback, bug reports and generally being awesome about me barging in the door like I did. 42 | 43 | _If you feel like you could contribute something don't hesitate with contacting me @aMediocreDad._ 44 | -------------------------------------------------------------------------------- /assets/fonts/F_OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 The Fraunces Project Authors (https://github.com/undercasetype/Fraunces) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 The Libre Caslon Text Project Authors (https://github.com/impallari/Libre-Caslon-Text) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /assets/fonts/alchemy-i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/alchemy-i.ttf -------------------------------------------------------------------------------- /assets/fonts/caslon-b.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/caslon-b.ttf -------------------------------------------------------------------------------- /assets/fonts/caslon-bi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/caslon-bi.ttf -------------------------------------------------------------------------------- /assets/fonts/caslon-i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/caslon-i.ttf -------------------------------------------------------------------------------- /assets/fonts/caslon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/caslon.ttf -------------------------------------------------------------------------------- /assets/fonts/fraunces-i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/fraunces-i.ttf -------------------------------------------------------------------------------- /assets/fonts/fraunces.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/fraunces.ttf -------------------------------------------------------------------------------- /assets/fonts/litm-dice.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/litm-dice.otf -------------------------------------------------------------------------------- /assets/fonts/packard-b.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/packard-b.ttf -------------------------------------------------------------------------------- /assets/fonts/packard.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/packard.ttf -------------------------------------------------------------------------------- /assets/fonts/powell-b.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/powell-b.ttf -------------------------------------------------------------------------------- /assets/fonts/powell.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/fonts/powell.ttf -------------------------------------------------------------------------------- /assets/media/LICENSE: -------------------------------------------------------------------------------- 1 | ============================================== 2 | Proprietary License - Legend in the Mist 3 | ============================================== 4 | 5 | The Legend in the Mist system for Foundry Virtual Tabletop includes artwork licensed from Son of Oak Studios and @roapon. These works are packaged with and provided for use in the Legend in the Mist system under proprietary licenses. You may not use these works for any purpose other than for personal use with the Legend in the Mist system. All rights to these works are reserved by Son of Oak Studios and @roapon. 6 | -------------------------------------------------------------------------------- /assets/media/adventure-theme-alt-bg-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/adventure-theme-alt-bg-top.webp -------------------------------------------------------------------------------- /assets/media/adventure-theme-bg-bottom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/adventure-theme-bg-bottom.webp -------------------------------------------------------------------------------- /assets/media/adventure-theme-border-bottom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/adventure-theme-border-bottom.webp -------------------------------------------------------------------------------- /assets/media/adventure-theme-border-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/adventure-theme-border-top.webp -------------------------------------------------------------------------------- /assets/media/background-frame.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/background-frame.webp -------------------------------------------------------------------------------- /assets/media/background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/background.webp -------------------------------------------------------------------------------- /assets/media/backpack-top-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/backpack-top-bg.webp -------------------------------------------------------------------------------- /assets/media/backpack.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/backpack.webp -------------------------------------------------------------------------------- /assets/media/bg-alt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/bg-alt.webp -------------------------------------------------------------------------------- /assets/media/birb.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/birb.webp -------------------------------------------------------------------------------- /assets/media/bottom-branch.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/bottom-branch.webp -------------------------------------------------------------------------------- /assets/media/bottom-frame-branches.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/bottom-frame-branches.webp -------------------------------------------------------------------------------- /assets/media/burn-c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /assets/media/burn-t.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/media/burn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/media/button-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/button-bg.webp -------------------------------------------------------------------------------- /assets/media/button-border.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/button-border.webp -------------------------------------------------------------------------------- /assets/media/challenge-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/challenge-bg.webp -------------------------------------------------------------------------------- /assets/media/challenge-border.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/challenge-border.webp -------------------------------------------------------------------------------- /assets/media/character-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/character-bg.webp -------------------------------------------------------------------------------- /assets/media/checkbox-c.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /assets/media/checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /assets/media/connector.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/connector.webp -------------------------------------------------------------------------------- /assets/media/dice.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/dice.webp -------------------------------------------------------------------------------- /assets/media/effects.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/effects.webp -------------------------------------------------------------------------------- /assets/media/feather.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/feather.webp -------------------------------------------------------------------------------- /assets/media/flowers-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/flowers-top.webp -------------------------------------------------------------------------------- /assets/media/greatness-theme-alt-bg-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/greatness-theme-alt-bg-top.webp -------------------------------------------------------------------------------- /assets/media/greatness-theme-bg-bottom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/greatness-theme-bg-bottom.webp -------------------------------------------------------------------------------- /assets/media/greatness-theme-border-bottom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/greatness-theme-border-bottom.webp -------------------------------------------------------------------------------- /assets/media/greatness-theme-border-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/greatness-theme-border-top.webp -------------------------------------------------------------------------------- /assets/media/green-leaf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/green-leaf.webp -------------------------------------------------------------------------------- /assets/media/header-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/header-bg.webp -------------------------------------------------------------------------------- /assets/media/icons/LICENSE: -------------------------------------------------------------------------------- 1 | ============================================== 2 | SVG Icons from Game-Icons.net 3 | ============================================== 4 | 5 | The litm system for Foundry Virtual Tabletop includes icon artwork licensed from Game-icons.net under the Creative Commons license. These icons are packaged with and provided for use in the litm system under their respective licenses, as noted below. 6 | 7 | /backpack.svg - By Delapouite under CC BY 3.0 - https://game-icons.net/1x1/delapouite/backpack.html 8 | /unfurled-scroll.svg - By Lorc under CC BY 3.0 - https://game-icons.net/1x1/lorc/scroll-unfurled.html 9 | /cracked-skull.svg - By Lorc under CC BY 3.0 - https://game-icons.net/1x1/lorc/broken-skull.html 10 | -------------------------------------------------------------------------------- /assets/media/icons/backpack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/media/icons/cracked-skull.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/media/icons/unfurled-scroll.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/media/item-divider.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/item-divider.webp -------------------------------------------------------------------------------- /assets/media/left-div.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/left-div.webp -------------------------------------------------------------------------------- /assets/media/limit-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/limit-bg.webp -------------------------------------------------------------------------------- /assets/media/limit-label.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/limit-label.webp -------------------------------------------------------------------------------- /assets/media/limit-value.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/limit-value.webp -------------------------------------------------------------------------------- /assets/media/litm_splash.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/litm_splash.webp -------------------------------------------------------------------------------- /assets/media/logo-b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/logo-b.webp -------------------------------------------------------------------------------- /assets/media/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/logo.webp -------------------------------------------------------------------------------- /assets/media/marshal-crest.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/marshal-crest.webp -------------------------------------------------------------------------------- /assets/media/middle-branches.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/middle-branches.webp -------------------------------------------------------------------------------- /assets/media/necklace.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/necklace.webp -------------------------------------------------------------------------------- /assets/media/note.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/note.webp -------------------------------------------------------------------------------- /assets/media/origin-theme-alt-bg-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/origin-theme-alt-bg-top.webp -------------------------------------------------------------------------------- /assets/media/origin-theme-bg-bottom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/origin-theme-bg-bottom.webp -------------------------------------------------------------------------------- /assets/media/origin-theme-border-bottom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/origin-theme-border-bottom.webp -------------------------------------------------------------------------------- /assets/media/origin-theme-border-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/origin-theme-border-top.webp -------------------------------------------------------------------------------- /assets/media/portraits/apple-picker-a.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/apple-picker-a.webp -------------------------------------------------------------------------------- /assets/media/portraits/apple-picker-b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/apple-picker-b.webp -------------------------------------------------------------------------------- /assets/media/portraits/apple-picker-c.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/apple-picker-c.webp -------------------------------------------------------------------------------- /assets/media/portraits/concerned-villagers.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/concerned-villagers.webp -------------------------------------------------------------------------------- /assets/media/portraits/corvine-thaumaturge.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/corvine-thaumaturge.webp -------------------------------------------------------------------------------- /assets/media/portraits/poisoned-wurm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/poisoned-wurm.webp -------------------------------------------------------------------------------- /assets/media/portraits/pond-tower.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/pond-tower.webp -------------------------------------------------------------------------------- /assets/media/portraits/red-marshal-a.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/red-marshal-a.webp -------------------------------------------------------------------------------- /assets/media/portraits/red-marshal-b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/red-marshal-b.webp -------------------------------------------------------------------------------- /assets/media/portraits/red-marshal-c.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/red-marshal-c.webp -------------------------------------------------------------------------------- /assets/media/portraits/sorrowbalm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/sorrowbalm.webp -------------------------------------------------------------------------------- /assets/media/portraits/wakened-sentry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/wakened-sentry.webp -------------------------------------------------------------------------------- /assets/media/portraits/willow.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/willow.webp -------------------------------------------------------------------------------- /assets/media/portraits/wise-one-a.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/wise-one-a.webp -------------------------------------------------------------------------------- /assets/media/portraits/wise-one-b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/wise-one-b.webp -------------------------------------------------------------------------------- /assets/media/portraits/wise-one-c.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/portraits/wise-one-c.webp -------------------------------------------------------------------------------- /assets/media/progress-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/progress-bg.webp -------------------------------------------------------------------------------- /assets/media/promotional-a.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/promotional-a.webp -------------------------------------------------------------------------------- /assets/media/promotional-b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/promotional-b.webp -------------------------------------------------------------------------------- /assets/media/raven.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/raven.webp -------------------------------------------------------------------------------- /assets/media/right-div.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/right-div.webp -------------------------------------------------------------------------------- /assets/media/scenes/broken-watchtower.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/scenes/broken-watchtower.webp -------------------------------------------------------------------------------- /assets/media/scenes/map-of-the-dales.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/scenes/map-of-the-dales.webp -------------------------------------------------------------------------------- /assets/media/scenes/ravenhome_tavern.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/scenes/ravenhome_tavern.webp -------------------------------------------------------------------------------- /assets/media/scenes/roadside-fields.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/scenes/roadside-fields.webp -------------------------------------------------------------------------------- /assets/media/scroll-background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/scroll-background.webp -------------------------------------------------------------------------------- /assets/media/scroll-border.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/scroll-border.webp -------------------------------------------------------------------------------- /assets/media/section-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/section-bg.webp -------------------------------------------------------------------------------- /assets/media/separator.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/separator.webp -------------------------------------------------------------------------------- /assets/media/single-flower.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/single-flower.webp -------------------------------------------------------------------------------- /assets/media/skull.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/skull.webp -------------------------------------------------------------------------------- /assets/media/story-tag-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/story-tag-bg.webp -------------------------------------------------------------------------------- /assets/media/tabs-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tabs-bg.webp -------------------------------------------------------------------------------- /assets/media/tabs-collapse.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tabs-collapse.webp -------------------------------------------------------------------------------- /assets/media/tag-divider.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tag-divider.webp -------------------------------------------------------------------------------- /assets/media/theme-bg-top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/theme-bg-top.webp -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-alderman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-alderman.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-aleonora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-aleonora.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-apple-picker-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-apple-picker-a.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-apple-picker-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-apple-picker-b.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-apple-picker-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-apple-picker-c.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-corvine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-corvine.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-mackross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-mackross.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-pond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-pond.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-red-marshal-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-red-marshal-a.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-red-marshal-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-red-marshal-b.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-red-marshal-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-red-marshal-c.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-sorrowbalm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-sorrowbalm.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-villagers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-villagers.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-waken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-waken.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-willow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-willow.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-wise-one-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-wise-one-a.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-wise-one-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-wise-one-b.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-wise-one-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-wise-one-c.png -------------------------------------------------------------------------------- /assets/media/tokens/litm-demo-token-wurm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/tokens/litm-demo-token-wurm.png -------------------------------------------------------------------------------- /assets/media/top-frame-branches.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/top-frame-branches.webp -------------------------------------------------------------------------------- /assets/media/yellow-leaf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/assets/media/yellow-leaf.webp -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "formatter": { 4 | "indentWidth": 2 5 | }, 6 | "linter": { 7 | "rules": { 8 | "complexity": { 9 | "noStaticOnlyClass": "off", 10 | "noThisInStatic": "off" 11 | } 12 | }, 13 | "ignore": ["diff.js"] 14 | }, 15 | "files": { 16 | "ignore": ["packs", "fvtt", "challenge.css", "threat.css"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true 4 | }, 5 | "include": ["litm.js", "scripts", "foundry.js"] 6 | } 7 | -------------------------------------------------------------------------------- /litm.css: -------------------------------------------------------------------------------- 1 | @import "./styles/root.css"; 2 | @import "./styles/foundry.css"; 3 | @import "./styles/components.css"; 4 | @import "./styles/item/backpack.css"; 5 | @import "./styles/item/theme.css"; 6 | @import "./styles/item/threat.css"; 7 | @import "./styles/actor/challenge.css"; 8 | @import "./styles/actor/character.css"; 9 | @import "./styles/apps/loot-dialog.css"; 10 | @import "./styles/apps/roll-dialog.css"; 11 | @import "./styles/apps/sidebar-buttons.css"; 12 | @import "./styles/apps/story-tags.css"; 13 | @import "./styles/chat/message.css"; 14 | @import "./styles/popout.css"; 15 | @import "./styles/dark.css"; 16 | -------------------------------------------------------------------------------- /litm.js: -------------------------------------------------------------------------------- 1 | import { ChallengeData } from "./scripts/actor/challenge/challenge-data.js"; 2 | import { ChallengeSheet } from "./scripts/actor/challenge/challenge-sheet.js"; 3 | import { CharacterData } from "./scripts/actor/character/character-data.js"; 4 | import { CharacterSheet } from "./scripts/actor/character/character-sheet.js"; 5 | import { DENOMINATION, DoubleSix } from "./scripts/apps/dice.js"; 6 | import { importCharacter } from "./scripts/apps/import-character.js"; 7 | import { LitmRollDialog } from "./scripts/apps/roll-dialog.js"; 8 | import { LitmRoll } from "./scripts/apps/roll.js"; 9 | import { StoryTagApp } from "./scripts/apps/story-tags.js"; 10 | import { SuperCheckbox } from "./scripts/components/super-checkbox.js"; 11 | import { ToggledInput } from "./scripts/components/toggled-input.js"; 12 | import { TagData } from "./scripts/data/abstract.js"; 13 | import { BackpackData } from "./scripts/item/backpack/backpack-data.js"; 14 | import { BackpackSheet } from "./scripts/item/backpack/backpack-sheet.js"; 15 | import { ThemeData } from "./scripts/item/theme/theme-data.js"; 16 | import { ThemeSheet } from "./scripts/item/theme/theme-sheet.js"; 17 | import { ThreatData } from "./scripts/item/threat/threat-data.js"; 18 | import { ThreatSheet } from "./scripts/item/threat/threat-sheet.js"; 19 | import { info, success } from "./scripts/logger.js"; 20 | import { LitmConfig } from "./scripts/system/config.js"; 21 | import { Enrichers } from "./scripts/system/enrichers.js"; 22 | import { Fonts } from "./scripts/system/fonts.js"; 23 | import { 24 | HandlebarsHelpers, 25 | HandlebarsPartials, 26 | } from "./scripts/system/handlebars.js"; 27 | import { LitmHooks } from "./scripts/system/hooks.js"; 28 | import { KeyBindings } from "./scripts/system/keybindings.js"; 29 | import { LitmSettings } from "./scripts/system/settings.js"; 30 | import { Sockets } from "./scripts/system/sockets.js"; 31 | 32 | // Set the logo to the LitM logo 33 | $("#logo").attr("src", "systems/litm/assets/media/logo.webp"); 34 | 35 | // Register Custom Elements 36 | ToggledInput.Register(); 37 | SuperCheckbox.Register(); 38 | 39 | // Init Hook 40 | Hooks.once("init", () => { 41 | info("Initializing Legend in the Mist..."); 42 | game.litm = { 43 | data: { 44 | TagData, 45 | }, 46 | methods: { 47 | calculatePower: LitmRollDialog.calculatePower, 48 | }, 49 | importCharacter, 50 | LitmRollDialog, 51 | LitmRoll, 52 | StoryTagApp, 53 | }; 54 | 55 | info("Initializing Config..."); 56 | CONFIG.Actor.dataModels.character = CharacterData; 57 | CONFIG.Actor.dataModels.challenge = ChallengeData; 58 | CONFIG.Actor.trackableAttributes.character = 59 | CharacterData.getTrackableAttributes(); 60 | CONFIG.Dice.terms[DENOMINATION] = DoubleSix; 61 | CONFIG.Dice.rolls.push(LitmRoll); 62 | CONFIG.Item.dataModels.backpack = BackpackData; 63 | CONFIG.Item.dataModels.theme = ThemeData; 64 | CONFIG.Item.dataModels.threat = ThreatData; 65 | CONFIG.litm = new LitmConfig(); 66 | 67 | info("Registering Sheets..."); 68 | // Unregister the default sheets 69 | Actors.unregisterSheet("core", ActorSheet); 70 | Items.unregisterSheet("core", ItemSheet); 71 | // Register the new sheets 72 | Actors.registerSheet("litm", CharacterSheet, { 73 | types: ["character"], 74 | makeDefault: true, 75 | }); 76 | Actors.registerSheet("litm", ChallengeSheet, { 77 | types: ["challenge"], 78 | makeDefault: true, 79 | }); 80 | Items.registerSheet("litm", BackpackSheet, { 81 | types: ["backpack"], 82 | makeDefault: true, 83 | }); 84 | Items.registerSheet("litm", ThemeSheet, { 85 | types: ["theme"], 86 | makeDefault: true, 87 | }); 88 | Items.registerSheet("litm", ThreatSheet, { 89 | types: ["threat"], 90 | makeDefault: true, 91 | }); 92 | 93 | HandlebarsHelpers.register(); 94 | HandlebarsPartials.register(); 95 | Enrichers.register(); 96 | Fonts.register(); 97 | KeyBindings.register(); 98 | LitmSettings.register(); 99 | LitmHooks.register(); 100 | Sockets.registerListeners(); 101 | 102 | success("Successfully initialized Legend in the Mist!"); 103 | }); 104 | -------------------------------------------------------------------------------- /packs/.gitattributes: -------------------------------------------------------------------------------- 1 | /** binary 2 | -------------------------------------------------------------------------------- /packs/.gitignore: -------------------------------------------------------------------------------- 1 | /** 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /packs/tinderbox-demo/000518.ldb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/packs/tinderbox-demo/000518.ldb -------------------------------------------------------------------------------- /packs/tinderbox-demo/000524.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/packs/tinderbox-demo/000524.log -------------------------------------------------------------------------------- /packs/tinderbox-demo/CURRENT: -------------------------------------------------------------------------------- 1 | MANIFEST-000058 2 | -------------------------------------------------------------------------------- /packs/tinderbox-demo/LOCK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/packs/tinderbox-demo/LOCK -------------------------------------------------------------------------------- /packs/tinderbox-demo/LOG: -------------------------------------------------------------------------------- 1 | 2024/03/05-14:15:42.638944 16edeb000 Recovering log #55 2 | 2024/03/05-14:15:42.639544 16edeb000 Delete type=3 #53 3 | 2024/03/05-14:15:42.639633 16edeb000 Delete type=0 #55 4 | 2024/03/05-14:16:50.636614 17069b000 Level-0 table #61: started 5 | 2024/03/05-14:16:50.636655 17069b000 Level-0 table #61: 0 bytes OK 6 | 2024/03/05-14:16:50.636967 17069b000 Delete type=0 #59 7 | 2024/03/05-14:16:50.637141 17069b000 Manual compaction at level-0 from '!adventures!6Gp864ZlopbT97Zy' @ 72057594037927935 : 1 .. '!adventures!6Gp864ZlopbT97Zy' @ 0 : 0; will stop at (end) 8 | -------------------------------------------------------------------------------- /packs/tinderbox-demo/LOG.old: -------------------------------------------------------------------------------- 1 | 2024/03/05-13:48:56.780950 16f5f7000 Recovering log #51 2 | 2024/03/05-13:48:56.781457 16f5f7000 Delete type=3 #49 3 | 2024/03/05-13:48:56.781509 16f5f7000 Delete type=0 #51 4 | 2024/03/05-14:15:38.366646 17069b000 Level-0 table #56: started 5 | 2024/03/05-14:15:38.367968 17069b000 Level-0 table #56: 119474 bytes OK 6 | 2024/03/05-14:15:38.368079 17069b000 Delete type=0 #54 7 | 2024/03/05-14:15:38.368770 17069b000 Manual compaction at level-0 from '!adventures!6Gp864ZlopbT97Zy' @ 72057594037927935 : 1 .. '!adventures!6Gp864ZlopbT97Zy' @ 0 : 0; will stop at '!adventures!6Gp864ZlopbT97Zy' @ 9 : 1 8 | 2024/03/05-14:15:38.368776 17069b000 Compacting 1@0 + 1@1 files 9 | 2024/03/05-14:15:38.369815 17069b000 Generated table #57@0: 1 keys, 59945 bytes 10 | 2024/03/05-14:15:38.369825 17069b000 Compacted 1@0 + 1@1 files => 59945 bytes 11 | 2024/03/05-14:15:38.369878 17069b000 compacted to: files[ 0 1 0 0 0 0 0 ] 12 | 2024/03/05-14:15:38.369914 17069b000 Delete type=2 #56 13 | 2024/03/05-14:15:38.369975 17069b000 Delete type=2 #44 14 | 2024/03/05-14:15:38.370084 17069b000 Manual compaction at level-0 from '!adventures!6Gp864ZlopbT97Zy' @ 9 : 1 .. '!adventures!6Gp864ZlopbT97Zy' @ 0 : 0; will stop at (end) 15 | -------------------------------------------------------------------------------- /packs/tinderbox-demo/MANIFEST-000523: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aMediocreDad/litm/3a139e5310460a9296b1386d6c3e19157070a7ea/packs/tinderbox-demo/MANIFEST-000523 -------------------------------------------------------------------------------- /scripts/actor/challenge/challenge-data.js: -------------------------------------------------------------------------------- 1 | import { localize as t } from "../../utils.js"; 2 | export class ChallengeData extends foundry.abstract.TypeDataModel { 3 | static defineSchema() { 4 | const fields = foundry.data.fields; 5 | return { 6 | category: new fields.StringField({ 7 | initial: () => t("Litm.ui.name-category"), 8 | }), 9 | rating: new fields.NumberField({ 10 | required: true, 11 | initial: 1, 12 | min: 1, 13 | max: 5, 14 | }), 15 | note: new fields.HTMLField(), 16 | special: new fields.HTMLField(), 17 | limits: new fields.ArrayField( 18 | new fields.SchemaField({ 19 | name: new fields.StringField(), 20 | value: new fields.NumberField(), 21 | }), 22 | ), 23 | tags: new fields.StringField({ 24 | initial: "[tag] [status-2]", 25 | }), 26 | }; 27 | } 28 | 29 | get challenges() { 30 | return CONFIG.litm.challenge_types; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/actor/challenge/challenge-sheet.js: -------------------------------------------------------------------------------- 1 | import { SheetMixin } from "../../mixins/sheet-mixin.js"; 2 | import { confirmDelete } from "../../utils.js"; 3 | 4 | export class ChallengeSheet extends SheetMixin(ActorSheet) { 5 | static defaultOptions = foundry.utils.mergeObject(ActorSheet.defaultOptions, { 6 | classes: ["litm", "litm--challenge"], 7 | width: 320, 8 | height: 700, 9 | resizable: false, 10 | scrollY: [".litm--challenge-wrapper"], 11 | }); 12 | 13 | get template() { 14 | return "systems/litm/templates/actor/challenge.html"; 15 | } 16 | 17 | get system() { 18 | return this.actor.system; 19 | } 20 | 21 | get items() { 22 | return this.actor.items; 23 | } 24 | 25 | async getData() { 26 | const { data, rest } = super.getData(); 27 | data.system.challenges = this.system.challenges; 28 | data.system.special = await TextEditor.enrichHTML(data.system.special); 29 | data.system.note = await TextEditor.enrichHTML(data.system.note); 30 | data.system.renderedTags = await TextEditor.enrichHTML(data.system.tags); 31 | data.items = await Promise.all(this.items.map((i) => i.sheet.getData())); 32 | 33 | return { data, ...rest, isEditing: this.isEditing }; 34 | } 35 | 36 | activateListeners(html) { 37 | super.activateListeners(html); 38 | 39 | html.find("[data-click]").on("click", this.#handleClick.bind(this)); 40 | html 41 | .find("[data-dblclick]") 42 | .on("dblclick", this.#handleDblClick.bind(this)); 43 | html 44 | .find("[data-context]") 45 | .on("contextmenu", this.#handleContext.bind(this)); 46 | 47 | if (this.isEditing) html.find("[contenteditable]:has(+#tags)").focus(); 48 | } 49 | 50 | async _updateObject(event, formData) { 51 | const sanitizedFormData = this.#sanitizeTags(formData); 52 | 53 | return super._updateObject(event, sanitizedFormData); 54 | } 55 | 56 | // Prevent dropping non-threat items 57 | async _onDropItem(event, data) { 58 | const item = await Item.implementation.fromDropData(data); 59 | if (item.type !== "threat") return; 60 | 61 | if (this.items.get(item.id)) return this._onSortItem(event, item); 62 | 63 | return super._onDropItem(event, data); 64 | } 65 | 66 | #handleClick(event) { 67 | event.preventDefault(); 68 | 69 | const button = event.currentTarget; 70 | const action = button.dataset.click; 71 | 72 | switch (action) { 73 | case "add-limit": 74 | this.#addLimit(); 75 | break; 76 | case "add-threat": 77 | this.#addThreat(); 78 | break; 79 | case "increase": 80 | this.#increase(button); 81 | break; 82 | } 83 | } 84 | 85 | #handleDblClick(event) { 86 | event.preventDefault(); 87 | 88 | const button = event.currentTarget; 89 | const action = button.dataset.dblclick; 90 | 91 | switch (action) { 92 | case "edit-item": 93 | this.#openItemSheet(button); 94 | break; 95 | } 96 | } 97 | 98 | #handleContext(event) { 99 | event.preventDefault(); 100 | 101 | const button = event.currentTarget; 102 | const action = button.dataset.context; 103 | 104 | switch (action) { 105 | case "remove-limit": 106 | this.#removeLimit(button); 107 | break; 108 | case "remove-threat": 109 | this.#removeThreat(button); 110 | break; 111 | case "decrease": 112 | this.#decrease(button); 113 | break; 114 | } 115 | } 116 | 117 | #addLimit() { 118 | const limits = this.system.limits; 119 | const limit = { 120 | name: "New Limit", 121 | value: 0, 122 | }; 123 | 124 | limits.push(limit); 125 | this.actor.update({ "system.limits": limits }); 126 | } 127 | 128 | async #addThreat() { 129 | const threats = await this.actor.createEmbeddedDocuments("Item", [ 130 | { name: "New Threat", type: "threat" }, 131 | ]); 132 | threats[0].sheet.render(true); 133 | } 134 | 135 | async #removeLimit(button) { 136 | if (!(await confirmDelete("Litm.other.limit"))) return; 137 | const index = Number(button.dataset.id); 138 | const limits = this.system.limits; 139 | 140 | limits.splice(index, 1); 141 | this.actor.update({ "system.limits": limits }); 142 | } 143 | 144 | async #removeThreat(button) { 145 | if (!(await confirmDelete("TYPES.Item.threat"))) return; 146 | const item = this.items.get(button.dataset.id); 147 | item.delete(); 148 | } 149 | 150 | async #increase(target) { 151 | const attrib = target.dataset.name; 152 | const value = foundry.utils.getProperty(this.actor, attrib); 153 | 154 | return this.actor.update({ [attrib]: Math.min(value + 1, 5) }); 155 | } 156 | 157 | async #decrease(target) { 158 | const attrib = target.dataset.name; 159 | const value = foundry.utils.getProperty(this.actor, attrib); 160 | 161 | return this.actor.update({ [attrib]: Math.max(value - 1, 1) }); 162 | } 163 | 164 | #openItemSheet(button) { 165 | const item = this.items.get(button.dataset.id); 166 | item.sheet.render(true); 167 | } 168 | 169 | #sanitizeTags(formData) { 170 | if (!formData["system.tags"]) return formData; 171 | const re = CONFIG.litm.tagStringRe; 172 | const tags = formData["system.tags"].match(re); 173 | formData["system.tags"] = tags ? tags.join(" ") : ""; 174 | 175 | return formData; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /scripts/actor/character/character-data.js: -------------------------------------------------------------------------------- 1 | import { error, warn } from "../../logger.js"; 2 | 3 | export class CharacterData extends foundry.abstract.TypeDataModel { 4 | static defineSchema() { 5 | const fields = foundry.data.fields; 6 | return { 7 | note: new fields.HTMLField(), 8 | }; 9 | } 10 | 11 | static getTrackableAttributes() { 12 | return { 13 | bar: ["limit"], 14 | value: [], 15 | }; 16 | } 17 | 18 | get backpack() { 19 | const backpack = this.parent.items.find((item) => item.type === "backpack"); 20 | if (!backpack) return []; 21 | return backpack.system.contents; 22 | } 23 | 24 | get allTags() { 25 | const backpack = this.backpack; 26 | const themeTags = this.parent.items 27 | .filter((item) => item.type === "theme") 28 | .flatMap((item) => item.system.allTags); 29 | return [...backpack, ...themeTags]; 30 | } 31 | 32 | get powerTags() { 33 | return this.allTags.filter( 34 | (tag) => 35 | tag.type === "powerTag" || 36 | tag.type === "themeTag" || 37 | tag.type === "backpack", 38 | ); 39 | } 40 | 41 | get weaknessTags() { 42 | return this.parent.items 43 | .filter((item) => item.type === "theme") 44 | .flatMap((item) => item.system.weakness); 45 | } 46 | 47 | get availablePowerTags() { 48 | const backpack = this.backpack.filter( 49 | (tag) => tag.isActive && !tag.isBurnt, 50 | ); 51 | const themeTags = this.parent.items 52 | .filter((item) => item.type === "theme") 53 | .flatMap((item) => item.system.availablePowerTags); 54 | return [...backpack, ...themeTags]; 55 | } 56 | 57 | get statuses() { 58 | return this.parent.appliedEffects 59 | .filter((item) => item.getFlag("litm", "values")?.some((v) => !!v)) 60 | .map((item) => { 61 | return { 62 | ...item.flags.litm, 63 | type: "status", 64 | value: item.flags.litm.values.findLast((v) => !!v), 65 | id: item._id, 66 | name: item.name, 67 | }; 68 | }); 69 | } 70 | 71 | get storyTags() { 72 | return this.parent.appliedEffects 73 | .filter((item) => item.getFlag("litm", "values")?.every((v) => !v)) 74 | .map((item) => { 75 | return { 76 | ...item.flags.litm, 77 | type: "tag", 78 | value: item.flags.litm.values.findLast((v) => !!v), 79 | id: item._id, 80 | name: item.name, 81 | }; 82 | }); 83 | } 84 | 85 | get limit() { 86 | return { 87 | label: "Litm.other.limit", 88 | value: 89 | 6 - (this.statuses.sort((a, b) => b.value - a.value)[0]?.value || 0), 90 | max: 6, 91 | }; 92 | } 93 | 94 | async prepareDerivedData() { 95 | // Make sure only four themes are present 96 | const themes = this.parent.items.filter((item) => item.type === "theme"); 97 | if (themes.length > 4) { 98 | warn( 99 | `Too many themes found for ${this.parent.name}, attempting to resolve...`, 100 | ); 101 | const toDelete = themes.slice(4); 102 | await this.parent.deleteEmbeddedDocuments( 103 | "Item", 104 | toDelete.map((item) => item._id), 105 | ); 106 | } 107 | 108 | // Make sure only one backpack is present 109 | const backpacks = this.parent.items.filter( 110 | (item) => item.type === "backpack", 111 | ); 112 | if (backpacks.length > 1) { 113 | warn( 114 | `Too many backpacks found for ${this.parent.name}, attempting to resolve...`, 115 | ); 116 | const toDelete = backpacks.slice(1); 117 | await this.parent.deleteEmbeddedDocuments( 118 | "Item", 119 | toDelete.map((item) => item._id), 120 | ); 121 | } 122 | 123 | // Validate unique data ids 124 | // Get duplicates 125 | const duplicates = this.allTags 126 | .map((tag) => tag.id) 127 | .filter((id, index, arr) => arr.indexOf(id) !== index); 128 | if (!duplicates.length) return; 129 | warn("Duplicate tag IDs found, attempting to resolve..."); 130 | error(`Duplicate tag IDs found for: ${this.parent._id}`, duplicates); 131 | 132 | // try to fix duplicates 133 | const tags = this.allTags; 134 | for (const tag of tags) { 135 | if (duplicates.includes(tag.id)) { 136 | tag.id = foundry.utils.randomID(); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /scripts/apps/dice.js: -------------------------------------------------------------------------------- 1 | export const DENOMINATION = "x"; 2 | 3 | export class DoubleSix extends foundry.dice.terms.Die { 4 | constructor(termData) { 5 | super({ ...termData, faces: 12 }); 6 | } 7 | 8 | static DENOMINATION = "6"; 9 | 10 | get total() { 11 | const total = super.total; 12 | return Math.ceil(total / 2); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/apps/import-character.js: -------------------------------------------------------------------------------- 1 | import { localize as t } from "../utils.js"; 2 | 3 | function createTag(data, type) { 4 | return { 5 | ...(data || { name: "", isBurnt: false, isActive: false }), 6 | type, 7 | id: foundry.utils.randomID(), 8 | }; 9 | } 10 | 11 | function createStatus(data) { 12 | if (typeof data === "string") 13 | return { 14 | name: data, 15 | type: "ActiveEffect", 16 | flags: { 17 | litm: { 18 | type: "tag", 19 | values: Array(6).fill(null), 20 | value: "", 21 | isBurnt: false, 22 | }, 23 | }, 24 | }; 25 | 26 | const values = 27 | data.level?.map((level, i) => (level ? (i + 1).toString() : null)) || 28 | Array(6).fill(null); 29 | const value = values.findLast((level) => level) || ""; 30 | const type = value ? "status" : "tag"; 31 | 32 | return { 33 | name: data.name || t("Litm.other.unnamed"), 34 | type: "ActiveEffect", 35 | flags: { 36 | litm: { 37 | type, 38 | values, 39 | value, 40 | isBurnt: false, 41 | }, 42 | }, 43 | }; 44 | } 45 | 46 | export async function importCharacter(data) { 47 | if (data.compatibility && !["litm", "empty"].includes(data.compatibility)) 48 | return ui.notifications.warn("Litm.ui.warn-incompatible-data", { 49 | localize: true, 50 | }); 51 | 52 | const themeData = Object.entries(data) 53 | .filter( 54 | ([key, theme]) => 55 | key.startsWith("theme") && 56 | typeof theme === "object" && 57 | !Array.isArray(theme) && 58 | !theme.isEmpty, 59 | ) 60 | .map(([_, theme]) => ({ 61 | name: 62 | theme.content.mainTag.name || 63 | t("Litm.other.unnamed", "TYPES.Item.theme"), 64 | type: "theme", 65 | system: { 66 | themebook: theme.content.themebook, 67 | level: theme.content.level?.toLowerCase(), 68 | isActive: theme.content.mainTag.isActive, 69 | isBurnt: theme.content.mainTag.isBurnt, 70 | powerTags: Array(5) 71 | .fill() 72 | .map((_, i) => createTag(theme.content.powerTags[i], "powerTag")), 73 | weaknessTags: [ 74 | createTag( 75 | { 76 | name: theme.content.weaknessTags[0] || "", 77 | isBurnt: false, 78 | isActive: true, 79 | }, 80 | "weaknessTag", 81 | ), 82 | ], 83 | experience: theme.content.experience, 84 | decay: theme.content.decay, 85 | motivation: theme.content.bio.title?.replace(/['"“”‟]/gm, "") || "", 86 | note: theme.content.bio.body, 87 | }, 88 | })); 89 | 90 | const backpack = { 91 | name: t("TYPES.Item.backpack"), 92 | type: "backpack", 93 | system: { 94 | contents: data.backpack.map((item) => createTag(item, "backpack")), 95 | }, 96 | }; 97 | 98 | const statuses = data.statuses.map((status) => createStatus(status)); 99 | 100 | const tags = Object.values(data.miscCard?.content || {}) 101 | .flat() 102 | .map((tag) => createStatus(tag)); 103 | 104 | const actorData = { 105 | name: data.name, 106 | type: "character", 107 | system: { 108 | note: "", 109 | }, 110 | effects: [...tags, ...statuses], 111 | items: [...themeData, backpack], 112 | }; 113 | const created = await Actor.create(actorData); 114 | if (created) { 115 | const formatted = game.i18n.format("Litm.ui.info-imported-character", { 116 | name: created.name, 117 | }); 118 | ui.notifications.info(formatted); 119 | created.sheet.render(true); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /scripts/apps/roll.js: -------------------------------------------------------------------------------- 1 | import { localize as t } from "../utils.js"; 2 | 3 | export class LitmRoll extends Roll { 4 | static CHAT_TEMPLATE = "systems/litm/templates/chat/message.html"; 5 | static TOOLTIP_TEMPLATE = "systems/litm/templates/chat/message-tooltip.html"; 6 | 7 | get litm() { 8 | return this.options; 9 | } 10 | 11 | get actor() { 12 | return game.actors.get(this.litm.actorId); 13 | } 14 | 15 | get speaker() { 16 | return { alias: this.actor.name }; 17 | } 18 | 19 | get flavor() { 20 | switch (this.litm.type) { 21 | case "mitigate": 22 | return t("Litm.ui.roll-mitigate", "Litm.other.outcome"); 23 | case "tracked": 24 | return t("Litm.ui.roll-tracked", "Litm.other.outcome"); 25 | default: 26 | return t("Litm.ui.roll-quick", "Litm.other.outcome"); 27 | } 28 | } 29 | 30 | get effect() { 31 | if (this.litm.type !== "mitigate") return null; 32 | return { 33 | action: "Litm.effects.mitigate.action", 34 | description: "Litm.effects.mitigate.description", 35 | cost: "Litm.effects.mitigate.cost", 36 | }; 37 | } 38 | 39 | get power() { 40 | const { label: outcome } = this.outcome; 41 | 42 | // Quick outcomes don't need to track power 43 | if (this.litm.type === "quick") return null; 44 | if (outcome === "failure") return 0; 45 | 46 | // Minimum of 1 power 47 | let totalPower = Math.max(this.litm.totalPower, 1); 48 | 49 | // If it's not a strong success, return the total power 50 | if (outcome === "consequence") return totalPower; 51 | 52 | // Mitigate outcomes add 1 power on a strong success 53 | if (this.litm.type === "mitigate") totalPower += 1; 54 | return totalPower; 55 | } 56 | 57 | get outcome() { 58 | const { resolver } = CONFIG.litm.roll; 59 | 60 | if (typeof resolver === "function") return resolver(this); 61 | 62 | if (this.total > 9) 63 | return { label: "success", description: "Litm.ui.roll-success" }; 64 | 65 | if (this.total > 6) 66 | return { label: "consequence", description: "Litm.ui.roll-consequence" }; 67 | 68 | return { label: "failure", description: "Litm.ui.roll-failure" }; 69 | } 70 | 71 | get modifier() { 72 | return this.options.modifier || 0; 73 | } 74 | 75 | async render({ 76 | template = this.constructor.CHAT_TEMPLATE, 77 | isPrivate = false, 78 | } = {}) { 79 | if (!this._evaluated) await this.evaluate({ async: true }); 80 | 81 | const chatData = { 82 | actor: this.actor, 83 | formula: isPrivate ? "???" : this._formula.replace(/\s\+0/, ""), 84 | flavor: isPrivate ? null : this.flavor, 85 | outcome: isPrivate ? "???" : this.outcome, 86 | power: isPrivate ? "???" : this.power, 87 | result: isPrivate ? "???" : this.result, 88 | title: this.litm.title, 89 | tooltip: isPrivate ? "" : await this.getTooltip(), 90 | total: isPrivate ? "" : Math.round(this.total * 100) / 100, 91 | type: this.litm.type, 92 | effect: this.effect, 93 | modifier: isPrivate ? "???" : this.modifier, 94 | user: game.user.id, 95 | isOwner: game.user.isGM || this.actor.isOwner, 96 | hasBurnedTags: !this.litm.isBurnt && this.litm.burnedTags.length > 0, 97 | hasWeaknessTags: 98 | !this.litm.gainedExp && 99 | this.litm.weaknessTags.filter((t) => t.type === "weaknessTag").length > 100 | 0, 101 | }; 102 | 103 | return renderTemplate(template, chatData); 104 | } 105 | 106 | async getTooltip() { 107 | const parts = this.dice.map((d) => d.getTooltipData()); 108 | const data = this.getTooltipData(); 109 | return renderTemplate(LitmRoll.TOOLTIP_TEMPLATE, { data, parts }); 110 | } 111 | 112 | getTooltipData() { 113 | const { label: outcome } = this.outcome; 114 | return { 115 | mitigate: this.litm.type === "mitigate" && outcome === "success", 116 | burnedTags: this.litm.burnedTags, 117 | powerTags: this.litm.powerTags, 118 | weaknessTags: this.litm.weaknessTags, 119 | positiveStatuses: this.litm.positiveStatuses, 120 | negativeStatuses: this.litm.negativeStatuses, 121 | modifier: this.modifier, 122 | }; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /scripts/components/super-checkbox.js: -------------------------------------------------------------------------------- 1 | export class SuperCheckbox extends HTMLElement { 2 | static formAssociated = true; 3 | static observedAttributes = ["value"]; 4 | 5 | static Register() { 6 | customElements.define("litm-super-checkbox", SuperCheckbox); 7 | } 8 | 9 | #checkbox; 10 | #states = ["", "negative", "positive", "burned"]; 11 | #state = 0; 12 | #value = this.#states[this.#state]; 13 | #internals = this.attachInternals(); 14 | 15 | constructor() { 16 | super(); 17 | this.attachShadow({ mode: "open" }); 18 | this.shadowRoot.innerHTML = ` 19 | 117 | 119 | `; 120 | this.#checkbox = this.shadowRoot.querySelector("#multistate-checkbox"); 121 | this.addEventListener("click", this.#onClick.bind(this)); 122 | this.#checkbox.addEventListener("keydown", (event) => { 123 | if (event.key === "Enter" || event.key === " ") { 124 | this.#onClick(); 125 | } 126 | }); 127 | } 128 | 129 | get checked() { 130 | return this.#state > 0; 131 | } 132 | 133 | get value() { 134 | return this.#value; 135 | } 136 | 137 | set value(value) { 138 | this.#value = value; 139 | this.#internals.setFormValue(value); 140 | this.setAttribute("value", this.value); 141 | } 142 | 143 | get form() { 144 | return this.#internals.form; 145 | } 146 | 147 | get name() { 148 | return this.getAttribute("name"); 149 | } 150 | 151 | get type() { 152 | return "checkbox"; 153 | } 154 | 155 | connectedCallback() { 156 | this.#states = this.getAttribute("states")?.split(",") || this.#states; 157 | this.#state = this.#states.indexOf(this.getAttribute("value")); 158 | this.#updateState(); 159 | } 160 | 161 | attributeChangedCallback(name, oldValue, newValue) { 162 | if (oldValue === newValue) return; 163 | if (name === "value") { 164 | this.#state = this.#states.indexOf(newValue); 165 | this.#updateState(); 166 | } 167 | } 168 | 169 | #onClick() { 170 | this.#state = (this.#state + 1) % this.#states.length; 171 | this.#updateState(); 172 | this.dispatchEvent(new Event("change")); 173 | } 174 | 175 | #updateState() { 176 | this.value = this.#states[this.#state]; 177 | this.#checkbox.setAttribute("aria-checked", this.#state > 0); 178 | this.#checkbox.ariaLabel = this.#states[this.#state]; 179 | this.#checkbox.dataset.state = this.#states[this.#state]; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /scripts/components/toggled-input.js: -------------------------------------------------------------------------------- 1 | export class ToggledInput extends HTMLElement { 2 | #input = Object.assign(document.createElement("textarea"), { 3 | style: "resize: none;", 4 | }); 5 | #renderedTags = Object.assign(document.createElement("div"), { 6 | class: "litm--sro", 7 | role: "presentation", 8 | }); 9 | 10 | constructor() { 11 | super(); 12 | this.#input.value = this.textContent; 13 | this.innerHTML = ""; 14 | this.role = "textbox"; 15 | this.appendChild(this.#input); 16 | this.appendChild(this.#renderedTags); 17 | } 18 | 19 | static get observedAttributes() { 20 | return ["editing"]; 21 | } 22 | 23 | static Register() { 24 | customElements.define("litm-toggled-input", ToggledInput); 25 | } 26 | 27 | get eventListeners() { 28 | return [ 29 | { 30 | event: "click", 31 | object: this, 32 | handler: () => { 33 | this.#input.focus(); 34 | this.#input.setSelectionRange( 35 | this.#input.value.length, 36 | this.#input.value.length, 37 | ); 38 | }, 39 | }, 40 | { 41 | event: "blur", 42 | object: this.#input, 43 | handler: () => { 44 | this.toggleAttribute("editing", false); 45 | }, 46 | }, 47 | { 48 | event: "keydown", 49 | object: window, 50 | handler: (event) => { 51 | if (event.key === "Shift") { 52 | this.toggleAttribute("editing", true); 53 | } 54 | }, 55 | }, 56 | { 57 | event: "keyup", 58 | object: window, 59 | handler: (event) => { 60 | if (event.key === "Shift" && document.activeElement !== this.#input) 61 | this.toggleAttribute("editing", false); 62 | }, 63 | }, 64 | ]; 65 | } 66 | 67 | connectedCallback() { 68 | this.#input.value ??= this.textContent; 69 | this.#input.name = this.getAttribute("name"); 70 | this.removeAttribute("name"); 71 | this.#renderTags(); 72 | 73 | for (const { event, object, handler } of this.eventListeners) 74 | object.addEventListener(event, handler); 75 | } 76 | 77 | disconnectedCallback() { 78 | for (const { event, object, handler } of this.eventListeners) 79 | object.removeEventListener(event, handler); 80 | } 81 | 82 | attributeChangedCallback(name, _oldValue, newValue) { 83 | if (name === "editing") this.#renderTags(newValue !== null); 84 | } 85 | 86 | async #renderTags(editMode = false) { 87 | switch (editMode) { 88 | case true: 89 | this.#renderedTags.classList.add("litm--sro"); 90 | this.#input.classList.remove("litm--sro"); 91 | break; 92 | default: { 93 | const tags = await TextEditor.enrichHTML(this.#input.value); 94 | this.#renderedTags.innerHTML = tags; 95 | this.#input.classList.add("litm--sro"); 96 | this.#renderedTags.classList.remove("litm--sro"); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /scripts/data/abstract.js: -------------------------------------------------------------------------------- 1 | export class TagData extends foundry.abstract.DataModel { 2 | static defineSchema() { 3 | const fields = foundry.data.fields; 4 | return { 5 | id: new fields.StringField({ 6 | required: true, 7 | nullable: false, 8 | validate: (id) => foundry.data.validators.isValidId(id), 9 | initial: () => foundry.utils.randomID(), 10 | }), 11 | name: new fields.StringField({ 12 | required: true, 13 | nullable: false, 14 | }), 15 | isActive: new fields.BooleanField({ 16 | required: true, 17 | initial: false, 18 | }), 19 | isBurnt: new fields.BooleanField({ 20 | required: true, 21 | initial: false, 22 | }), 23 | type: new fields.StringField({ 24 | required: true, 25 | choices: ["weaknessTag", "powerTag", "backpack", "themeTag"], 26 | }), 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/item/backpack/backpack-data.js: -------------------------------------------------------------------------------- 1 | export class BackpackData extends foundry.abstract.TypeDataModel { 2 | static defineSchema() { 3 | const fields = foundry.data.fields; 4 | const abstract = game.litm.data; 5 | return { 6 | contents: new fields.ArrayField( 7 | new fields.EmbeddedDataField(abstract.TagData), 8 | ), 9 | }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/item/backpack/backpack-sheet.js: -------------------------------------------------------------------------------- 1 | import { SheetMixin } from "../../mixins/sheet-mixin.js"; 2 | import { confirmDelete, localize as t } from "../../utils.js"; 3 | 4 | export class BackpackSheet extends SheetMixin(ItemSheet) { 5 | /** @override */ 6 | static get defaultOptions() { 7 | return foundry.utils.mergeObject(super.defaultOptions, { 8 | classes: ["litm", "litm--backpack"], 9 | template: "systems/litm/templates/item/backpack.html", 10 | width: 400, 11 | height: 450, 12 | resizable: false, 13 | scrollY: [".taglist"], 14 | }); 15 | } 16 | 17 | get system() { 18 | return this.item.system; 19 | } 20 | 21 | /** @override */ 22 | async getData() { 23 | return { backpack: this.system.contents, name: this.item.name }; 24 | } 25 | 26 | activateListeners(html) { 27 | super.activateListeners(html); 28 | 29 | html.find("[data-click]").on("click", this.#onClick.bind(this)); 30 | html.find("[data-context]").on("contextmenu", this.#onContext.bind(this)); 31 | } 32 | 33 | /** @override - This method needs to be overriden to accommodate readonly input fields */ 34 | _getSubmitData(updateData) { 35 | if (!this.form) 36 | throw new Error( 37 | "The FormApplication subclass has no registered form element", 38 | ); 39 | const fd = new FormDataExtended(this.form, { 40 | editors: this.editors, 41 | readonly: true, 42 | disabled: true, 43 | }); 44 | let data = fd.object; 45 | if (updateData) 46 | data = foundry.utils.flattenObject( 47 | foundry.utils.mergeObject(data, updateData), 48 | ); 49 | return data; 50 | } 51 | 52 | #onClick(event) { 53 | const button = event.currentTarget; 54 | const action = button.dataset.click; 55 | 56 | switch (action) { 57 | case "add-tag": 58 | this.#addTag(); 59 | break; 60 | } 61 | } 62 | 63 | #onContext(event) { 64 | const button = event.currentTarget; 65 | const action = button.dataset.context; 66 | 67 | switch (action) { 68 | case "remove-tag": 69 | this.#removeTag(button); 70 | break; 71 | } 72 | } 73 | 74 | #addTag() { 75 | const item = { 76 | name: t("Litm.ui.name-tag"), 77 | isActive: false, 78 | isBurnt: false, 79 | type: "backpack", 80 | id: foundry.utils.randomID(), 81 | }; 82 | 83 | const contents = this.system.contents; 84 | contents.push(item); 85 | 86 | return this.item.update({ "system.contents": contents }); 87 | } 88 | 89 | async #removeTag(button) { 90 | if (!(await confirmDelete("Litm.other.tag"))) return; 91 | 92 | const index = button.dataset.id; 93 | const contents = this.system.contents; 94 | contents.splice(index, 1); 95 | 96 | return this.item.update({ "system.contents": contents }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /scripts/item/theme/theme-data.js: -------------------------------------------------------------------------------- 1 | import { localize as t, titleCase } from "../../utils.js"; 2 | 3 | export class ThemeData extends foundry.abstract.TypeDataModel { 4 | static defineSchema() { 5 | const fields = foundry.data.fields; 6 | const abstract = game.litm.data; 7 | return { 8 | themebook: new fields.StringField({ 9 | trim: true, 10 | initial: t("Litm.other.themebook"), 11 | }), 12 | level: new fields.StringField({ 13 | trim: true, 14 | initial: () => Object.keys(CONFIG.litm.theme_levels)[0], 15 | validate: (level) => 16 | Object.keys(CONFIG.litm.theme_levels).includes(level), 17 | }), 18 | isActive: new fields.BooleanField({ 19 | initial: true, 20 | }), 21 | isBurnt: new fields.BooleanField(), 22 | powerTags: new fields.ArrayField( 23 | new fields.EmbeddedDataField(abstract.TagData), 24 | { 25 | initial: () => 26 | Array(10) 27 | .fill() 28 | .map((_, i) => ({ 29 | id: foundry.utils.randomID(), 30 | name: `${i < 2 ? `${t("Litm.ui.name-power")}` : ""}`, 31 | type: "powerTag", 32 | isActive: i < 2, 33 | isBurnt: false, 34 | })), 35 | validate: (tags) => tags.length === 10, 36 | }, 37 | ), 38 | weaknessTags: new fields.ArrayField( 39 | new fields.EmbeddedDataField(abstract.TagData), 40 | { 41 | initial: () => 42 | Array(2) 43 | .fill() 44 | .map(() => ({ 45 | id: foundry.utils.randomID(), 46 | name: t("Litm.ui.name-weakness"), 47 | isActive: true, 48 | isBurnt: false, 49 | type: "weaknessTag", 50 | })), 51 | validate: (tags) => tags.length === 2, 52 | }, 53 | ), 54 | experience: new fields.NumberField({ 55 | integer: true, 56 | min: 0, 57 | initial: 0, 58 | max: 3, 59 | }), 60 | decay: new fields.NumberField({ 61 | integer: true, 62 | min: 0, 63 | initial: 0, 64 | max: 3, 65 | }), 66 | motivation: new fields.StringField({ 67 | initial: t("Litm.ui.name-motivation"), 68 | }), 69 | note: new fields.HTMLField({ 70 | initial: t("Litm.ui.name-note"), 71 | }), 72 | }; 73 | } 74 | 75 | static migrateData(source) { 76 | const numPowerTags = source.powerTags.length; 77 | if (numPowerTags < 10) { 78 | source.powerTags = [ 79 | ...source.powerTags, 80 | ...Array(10 - numPowerTags) 81 | .fill() 82 | .map(() => ({ 83 | id: foundry.utils.randomID(), 84 | name: "", 85 | type: "powerTag", 86 | isActive: false, 87 | isBurnt: false, 88 | })), 89 | ]; 90 | } 91 | if (numPowerTags > 10) { 92 | source.powerTags = source.powerTags.slice(0, 10); 93 | } 94 | 95 | const numWeaknessTags = source.weaknessTags.length; 96 | if (numWeaknessTags < 2) { 97 | source.weaknessTags = [ 98 | ...source.weaknessTags, 99 | ...Array(2 - numWeaknessTags) 100 | .fill() 101 | .map(() => ({ 102 | id: foundry.utils.randomID(), 103 | name: t("Litm.ui.name-weakness"), 104 | isActive: true, 105 | isBurnt: false, 106 | type: "weaknessTag", 107 | })), 108 | ]; 109 | } 110 | if (numWeaknessTags > 2) { 111 | source.weaknessTags = source.weaknessTags.slice(0, 2); 112 | } 113 | 114 | return super.migrateData(source); 115 | } 116 | 117 | get themeTag() { 118 | const item = { 119 | id: this.parent._id, 120 | name: titleCase(this.parent.name), 121 | isActive: this.isActive, 122 | isBurnt: this.isBurnt, 123 | type: "themeTag", 124 | }; 125 | return game.litm.data.TagData.fromSource(item); 126 | } 127 | 128 | get activatedPowerTags() { 129 | const powerTags = this.powerTags; 130 | const themeTag = this.themeTag; 131 | return [...powerTags, themeTag].filter((tag) => tag.isActive); 132 | } 133 | 134 | get availablePowerTags() { 135 | return this.activatedPowerTags.filter((tag) => !tag.isBurnt); 136 | } 137 | 138 | get powerTagRatio() { 139 | return this.availablePowerTags.length / this.activatedPowerTags.length; 140 | } 141 | 142 | get weakness() { 143 | return this.weaknessTags; 144 | } 145 | 146 | get allTags() { 147 | return [...this.weaknessTags, ...this.powerTags, this.themeTag]; 148 | } 149 | 150 | get levels() { 151 | return Object.keys(CONFIG.litm.theme_levels).reduce((acc, level) => { 152 | acc[level] = t(level, "TYPES.Item.theme"); 153 | return acc; 154 | }, {}); 155 | } 156 | 157 | get themebooks() { 158 | return CONFIG.litm.theme_levels[this.level]; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /scripts/item/theme/theme-sheet.js: -------------------------------------------------------------------------------- 1 | import { SheetMixin } from "../../mixins/sheet-mixin.js"; 2 | import { confirmDelete } from "../../utils.js"; 3 | 4 | export class ThemeSheet extends SheetMixin(ItemSheet) { 5 | static defaultOptions = foundry.utils.mergeObject(ItemSheet.defaultOptions, { 6 | classes: ["litm", "litm--theme"], 7 | width: 330, 8 | height: 700, 9 | }); 10 | 11 | get system() { 12 | return this.item.system; 13 | } 14 | 15 | get template() { 16 | return "systems/litm/templates/item/theme.html"; 17 | } 18 | 19 | getData() { 20 | const { data, ...rest } = super.getData(); 21 | 22 | data.system.weakness = this.system.weakness; 23 | data.system.levels = this.system.levels; 24 | data.system.themebooks = this.system.themebooks; 25 | 26 | const fallbackSrc = ["origin", "adventure", "greatness"].includes( 27 | data.system.level, 28 | ) 29 | ? data.system.level 30 | : "origin"; 31 | const themesrc = 32 | CONFIG.litm.theme_src[data.system.level] || 33 | `systems/litm/assets/media/${fallbackSrc}`; 34 | 35 | return { data, themesrc, ...rest }; 36 | } 37 | 38 | activateListeners(html) { 39 | super.activateListeners(html); 40 | 41 | html.find("[data-click]").click(this.#handleClicks.bind(this)); 42 | html.find("[data-context").contextmenu(this.#handleContextmenu.bind(this)); 43 | } 44 | 45 | /** @override - This method needs to be overriden to accommodate readonly input fields */ 46 | _getSubmitData(updateData) { 47 | if (!this.form) 48 | throw new Error( 49 | "The FormApplication subclass has no registered form element", 50 | ); 51 | const fd = new FormDataExtended(this.form, { 52 | editors: this.editors, 53 | readonly: true, 54 | disabled: true, 55 | }); 56 | let data = fd.object; 57 | if (updateData) 58 | data = foundry.utils.flattenObject( 59 | foundry.utils.mergeObject(data, updateData), 60 | ); 61 | return data; 62 | } 63 | 64 | #handleClicks(event) { 65 | const t = event.currentTarget; 66 | const action = t.dataset.click; 67 | const id = t.dataset.id; 68 | switch (action) { 69 | case "add-tag": 70 | this.#addTag(); 71 | break; 72 | case "remove-tag": 73 | this.#removeTag(id); 74 | break; 75 | case "increase": 76 | this.#increase(id); 77 | break; 78 | } 79 | } 80 | 81 | #handleContextmenu(event) { 82 | const t = event.currentTarget; 83 | const action = t.dataset.context; 84 | const id = t.dataset.id; 85 | switch (action) { 86 | case "decrease": 87 | this.#decrease(id); 88 | break; 89 | } 90 | } 91 | 92 | async #addTag() { 93 | throw new Error("Not implemented"); 94 | } 95 | 96 | async #removeTag(_) { 97 | if (!(await confirmDelete("Litm.other.tag"))) return; 98 | throw new Error("Not implemented"); 99 | } 100 | 101 | async #increase(field) { 102 | const attribute = foundry.utils.getProperty(this.item, field); 103 | await this.item.update({ [field]: attribute + 1 }); 104 | } 105 | 106 | async #decrease(field) { 107 | const attribute = foundry.utils.getProperty(this.item, field); 108 | await this.item.update({ [field]: attribute - 1 }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /scripts/item/threat/threat-data.js: -------------------------------------------------------------------------------- 1 | import { localize as t } from "../../utils.js"; 2 | 3 | export class ThreatData extends foundry.abstract.TypeDataModel { 4 | static defineSchema() { 5 | const fields = foundry.data.fields; 6 | return { 7 | consequences: new fields.ArrayField( 8 | new fields.StringField({ required: true, nullable: false }), 9 | { 10 | initial: () => [t("Litm.ui.name-consequence")], 11 | }, 12 | ), 13 | category: new fields.StringField(), 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/item/threat/threat-sheet.js: -------------------------------------------------------------------------------- 1 | import { SheetMixin } from "../../mixins/sheet-mixin.js"; 2 | import { confirmDelete, localize as t } from "../../utils.js"; 3 | 4 | export class ThreatSheet extends SheetMixin(ItemSheet) { 5 | isEditing = false; 6 | 7 | /** @override */ 8 | static get defaultOptions() { 9 | return foundry.utils.mergeObject(super.defaultOptions, { 10 | classes: ["litm", "litm--threat"], 11 | template: "systems/litm/templates/item/threat.html", 12 | width: 412, 13 | height: 231, 14 | resizable: true, 15 | submitOnChange: true, 16 | }); 17 | } 18 | 19 | get effects() { 20 | return this.item.effects; 21 | } 22 | 23 | get system() { 24 | return this.item.system; 25 | } 26 | 27 | /** @override */ 28 | async getData() { 29 | const { data, ...rest } = super.getData(); 30 | 31 | if (!this.isEditing) 32 | data.system.consequences = await Promise.all( 33 | data.system.consequences.map((c) => TextEditor.enrichHTML(c)), 34 | ); 35 | 36 | return { 37 | ...rest, 38 | data, 39 | isEditing: this.isEditing, 40 | }; 41 | } 42 | 43 | activateListeners(html) { 44 | super.activateListeners(html); 45 | 46 | html.find("[data-click]").on("click", this.#handleClick.bind(this)); 47 | html 48 | .find("[data-context]") 49 | .on("contextmenu", this.#handleContextMenu.bind(this)); 50 | 51 | if (this.isEditing) 52 | html.find("[contenteditable]:has(+#consequence)").focus(); 53 | } 54 | 55 | async _updateObject(event, formData) { 56 | const res = await super._updateObject(event, formData); 57 | 58 | if (!formData["system.consequences.0"]) return res; 59 | 60 | // Delete existing tags and statuses 61 | await this.item.deleteEmbeddedDocuments( 62 | "ActiveEffect", 63 | this.effects.map((e) => e._id), 64 | ); 65 | 66 | const matches = this.system.consequences.flatMap((string) => 67 | Array.from(string.matchAll(CONFIG.litm.tagStringRe)), 68 | ); 69 | 70 | // Create new tags and statuses 71 | await this.item.createEmbeddedDocuments( 72 | "ActiveEffect", 73 | matches.map(([_, tag, status]) => { 74 | const type = status !== undefined ? "status" : "tag"; 75 | return { 76 | name: tag, 77 | label: tag, 78 | flags: { 79 | litm: { 80 | type, 81 | }, 82 | }, 83 | changes: [ 84 | { 85 | key: type === "tag" ? "TAG" : "STATUS", 86 | mode: 0, 87 | value: type === "tag" ? 1 : status, 88 | }, 89 | ], 90 | }; 91 | }), 92 | ); 93 | } 94 | 95 | #handleClick(event) { 96 | const { click } = event.currentTarget.dataset; 97 | switch (click) { 98 | case "add-consequence": 99 | this.#addConsequence(); 100 | break; 101 | } 102 | } 103 | 104 | #handleContextMenu(event) { 105 | event.preventDefault(); 106 | const { context } = event.currentTarget.dataset; 107 | switch (context) { 108 | case "remove-consequence": 109 | this.#removeConsequence(event); 110 | break; 111 | } 112 | } 113 | 114 | async #addConsequence() { 115 | this.isEditing = false; 116 | await this.submit(new Event("submit")); 117 | 118 | const consequences = this.system.consequences; 119 | consequences.push(t("Litm.ui.name-consequence")); 120 | this.item.update({ "system.consequences": consequences }); 121 | } 122 | 123 | async #removeConsequence(event) { 124 | if (!(await confirmDelete("Litm.other.consequence"))) return; 125 | 126 | const { id } = event.currentTarget.dataset; 127 | this.system.consequences.splice(id, 1); 128 | 129 | this.item.update({ "system.consequences": this.system.consequences }); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /scripts/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Status - Status color 3 | * @property {string} SUCCESS - Success color 4 | * @property {string} INFO - Info color 5 | * @property {string} ERROR - Error color 6 | */ 7 | const Status = { 8 | SUCCESS: "hsl(46.61 81.44% 70%)", 9 | INFO: "hsl(225.94 10.66% 41.72%)", 10 | WARN: "hsl(45 100% 50%)", 11 | ERROR: "hsl(6.5deg 42.05% 70%)", 12 | }; 13 | 14 | /** 15 | * @param {Status} status 16 | * @returns {function} 17 | */ 18 | function log(status) { 19 | /** 20 | * @param {...string} args 21 | * @returns {void} 22 | */ 23 | return (...args) => { 24 | return console.log( 25 | `%cLegend in the Mist | %c${args.join("\n")}`, 26 | `font-weight: bold; color: ${status};`, 27 | "color: hsl(240, 100%, 98%);", 28 | ); 29 | }; 30 | } 31 | 32 | /** 33 | * @param {...string} args 34 | * @returns {void} 35 | * @example 36 | * error("This is an error message"); 37 | */ 38 | export function error(...args) { 39 | return log(Status.ERROR)(...args); 40 | } 41 | 42 | /** 43 | * @param {...string} args 44 | * @returns {void} 45 | * @example 46 | * success("This is an error message"); 47 | */ 48 | export function success(...args) { 49 | return log(Status.SUCCESS)(...args); 50 | } 51 | 52 | /** 53 | * @param {...string} args 54 | * @returns {void} 55 | * @example 56 | * info("This is an info message"); 57 | */ 58 | export function info(...args) { 59 | return log(Status.INFO)(...args); 60 | } 61 | 62 | /** 63 | * @param {...string} args 64 | * @returns {void} 65 | * @example 66 | * warn("This is a warning message"); 67 | */ 68 | export function warn(...args) { 69 | return log(Status.WARN)(...args); 70 | } 71 | -------------------------------------------------------------------------------- /scripts/mixins/sheet-mixin.js: -------------------------------------------------------------------------------- 1 | import { localize as t } from "../utils.js"; 2 | 3 | export const SheetMixin = (Base) => 4 | class extends Base { 5 | isEditing = false; 6 | #currentScale = 1; 7 | 8 | activateListeners(html) { 9 | super.activateListeners(html); 10 | 11 | html.find("[data-click]").on("click", this.#handleClick.bind(this)); 12 | 13 | html 14 | .find("[data-size-input]") 15 | .css("width", function () { 16 | return `${Math.ceil(Math.max(this.value.length * 1.5, 6))}ch`; 17 | }) 18 | .on("input", this.#sizeInput.bind(this)); 19 | 20 | html 21 | .find("[data-input") 22 | .on("input", (event) => this.#handleInput(event)) 23 | .on("blur", () => 24 | this._onSubmit(new Event("submit"), { rerender: true }), 25 | ); 26 | html 27 | .find("[data-mousedown]") 28 | .on("mousedown", this.#handleMousedown.bind(this)); 29 | html 30 | .closest(".app.window-app") 31 | .find(".litm--sheet-scale-button") 32 | .on("pointerdown", this.#scale.bind(this)); 33 | } 34 | 35 | /** @override */ 36 | async _onSubmit(event, options = {}) { 37 | this.isEditing = false; 38 | 39 | const res = await super._onSubmit(event, options); 40 | 41 | if (options.rerender) this.render(); 42 | return res; 43 | } 44 | 45 | _getHeaderButtons() { 46 | const buttons = super._getHeaderButtons(); 47 | 48 | buttons.unshift({ 49 | class: "litm--sheet-scale-button", 50 | icon: "fas fa-arrows-alt-h", 51 | tooltip: t("Resize"), 52 | onclick: () => {}, 53 | }); 54 | 55 | return buttons; 56 | } 57 | 58 | renderRollDialog() { 59 | return ui.notifications.warn("Litm.ui.warn-not-supported", { 60 | localize: true, 61 | }); 62 | } 63 | 64 | #handleInput(event) { 65 | const t = event.currentTarget; 66 | const targetId = t.dataset.input; 67 | const value = t.textContent || t.value; 68 | const target = $(t).siblings(`input#${targetId}`); 69 | target.val(value); 70 | } 71 | 72 | #handleMousedown(event) { 73 | const action = event.currentTarget.dataset.mousedown; 74 | switch (action) { 75 | case "scale": { 76 | this.#scale(event); 77 | } 78 | } 79 | } 80 | 81 | #handleClick(event) { 82 | const action = event.currentTarget.dataset.click; 83 | switch (action) { 84 | case "toggle-edit": { 85 | this.#toggleEdit(); 86 | } 87 | } 88 | } 89 | 90 | #sizeInput(event) { 91 | const input = event.currentTarget; 92 | input.style.width = `${Math.ceil(Math.max(input.value.length * 1.5, 6))}ch`; 93 | } 94 | 95 | #toggleEdit() { 96 | this.isEditing = !this.isEditing; 97 | return this.render(); 98 | } 99 | 100 | #scale(event) { 101 | event.preventDefault(); 102 | event.stopPropagation(); 103 | 104 | const eventNames = 105 | event.originalEvent.type === "pointerdown" 106 | ? ["pointermove", "pointerup"] 107 | : ["mousemove", "mouseup"]; 108 | 109 | const el = this.element; 110 | 111 | let previousX = event.screenX; 112 | let delta = 0; 113 | 114 | const clampValue = (current, delta) => { 115 | const value = current + delta / 500; 116 | return Math.max(0.3, Math.min(3, value)); 117 | }; 118 | 119 | const mousemove = (event) => { 120 | delta = event.screenX - previousX; 121 | previousX = event.screenX; 122 | this.#currentScale = clampValue(this.#currentScale, delta); 123 | 124 | el.css("transform", `scale(${this.#currentScale})`); 125 | }; 126 | 127 | const mouseup = () => { 128 | document.removeEventListener(eventNames[0], mousemove); 129 | document.removeEventListener(eventNames[1], mouseup); 130 | 131 | this.setPosition({ 132 | scale: this.#currentScale, 133 | }); 134 | }; 135 | 136 | document.addEventListener(eventNames[0], mousemove); 137 | document.addEventListener(eventNames[1], mouseup); 138 | } 139 | }; 140 | -------------------------------------------------------------------------------- /scripts/system/config.js: -------------------------------------------------------------------------------- 1 | export class LitmConfig { 2 | challenge_types = [ 3 | "attacker", 4 | "barrier-hazard", 5 | "charge", 6 | "countdown", 7 | "mystery", 8 | "pursuer", 9 | "quarry", 10 | "temptation", 11 | "watcher", 12 | ]; 13 | 14 | effects = { 15 | "Litm.effects.category-target": { 16 | attack: { 17 | description: "Litm.effects.attack.description", 18 | action: "Litm.effects.attack.action", 19 | cost: "Litm.effects.attack.cost", 20 | icon: "fas fa-swords", 21 | }, 22 | disrupt: { 23 | description: "Litm.effects.disrupt.description", 24 | action: "Litm.effects.disrupt.action", 25 | cost: "Litm.effects.disrupt.cost", 26 | icon: "fas fa-ban", 27 | }, 28 | influence: { 29 | description: "Litm.effects.influence.description", 30 | action: "Litm.effects.influence.action", 31 | cost: "Litm.effects.influence.cost", 32 | icon: "fas fa-hand-paper", 33 | }, 34 | weaken: { 35 | description: "Litm.effects.weaken.description", 36 | action: "Litm.effects.weaken.action", 37 | cost: "Litm.effects.weaken.cost", 38 | icon: "fas fa-dizzy", 39 | }, 40 | }, 41 | "Litm.effects.category-ally": { 42 | bestow: { 43 | description: "Litm.effects.bestow.description", 44 | action: "Litm.effects.bestow.action", 45 | cost: "Litm.effects.bestow.cost", 46 | icon: "fas fa-gift", 47 | }, 48 | enhance: { 49 | description: "Litm.effects.enhance.description", 50 | action: "Litm.effects.enhance.action", 51 | cost: "Litm.effects.enhance.cost", 52 | icon: "fas fa-bolt", 53 | }, 54 | create: { 55 | description: "Litm.effects.create.description", 56 | action: "Litm.effects.create.action", 57 | cost: "Litm.effects.create.cost", 58 | icon: "fas fa-tags", 59 | }, 60 | restore: { 61 | description: "Litm.effects.restore.description", 62 | action: "Litm.effects.restore.action", 63 | cost: "Litm.effects.restore.cost", 64 | icon: "fas fa-heart", 65 | }, 66 | }, 67 | "Litm.effects.category-process": { 68 | advance: { 69 | description: "Litm.effects.advance.description", 70 | action: "Litm.effects.advance.action", 71 | cost: "Litm.effects.advance.cost", 72 | icon: "fas fa-arrow-right", 73 | }, 74 | set_back: { 75 | description: "Litm.effects.set_back.description", 76 | action: "Litm.effects.set_back.action", 77 | cost: "Litm.effects.set_back.cost", 78 | icon: "fas fa-arrow-left", 79 | }, 80 | }, 81 | "Litm.effects.category-other": { 82 | discover: { 83 | description: "Litm.effects.discover.description", 84 | action: "Litm.effects.discover.action", 85 | cost: "Litm.effects.discover.cost", 86 | icon: "fas fa-search", 87 | }, 88 | extra_feat: { 89 | description: "Litm.effects.extra_feat.description", 90 | action: "Litm.effects.extra_feat.action", 91 | cost: "Litm.effects.extra_feat.cost", 92 | icon: "fas fa-plus", 93 | }, 94 | }, 95 | }; 96 | 97 | /** 98 | * You can use this to completely override the default effects. 99 | * formula: ({ totalPower }) => `${1 + Math.max(Math.abs(totalPower))}d6${totalPower < 1 ? `kl1` : "kh1"}`, 100 | * resolver: (roll) => { 101 | * if (roll.dice[0].results.every(d => d.active && d.result === 1)) return { label: "failure", description: "Litm.ui.roll-failure" }; 102 | * } 103 | * @link scripts/apps/roll-dialog.js 104 | * @link scripts/apps/roll.js 105 | */ 106 | roll = { formula: null, resolver: null }; 107 | 108 | theme_levels = { 109 | origin: [ 110 | "circumstance", 111 | "past", 112 | "devotion", 113 | "mystery", 114 | "people", 115 | "possessions", 116 | "personality", 117 | "trade-or-skill", 118 | "trait", 119 | "hedge-magic", 120 | ], 121 | adventure: [ 122 | "prodigious-skill", 123 | "duty", 124 | "relic", 125 | "uncanny-being", 126 | "thaumaturgy", 127 | ], 128 | greatness: [ 129 | "rulership", 130 | "destiny", 131 | "mastery", 132 | "monstrosity", 133 | "grand-thaumaturgy", 134 | ], 135 | }; 136 | 137 | theme_src = { 138 | origin: "systems/litm/assets/media/origin", 139 | adventure: "systems/litm/assets/media/adventure", 140 | greatness: "systems/litm/assets/media/greatness", 141 | }; 142 | 143 | tagStringRe = /(?!\b|\s)(?:\[|\{)([^\d\[\]{}]+)(?:[\s\-\:](\d+))?(?:\}|\])/gi; 144 | sceneLinkRe = /@ActivateScene\[([^\]]+)\](?:\{([^\}]+)\})?/gi; 145 | } 146 | -------------------------------------------------------------------------------- /scripts/system/enrichers.js: -------------------------------------------------------------------------------- 1 | export class Enrichers { 2 | static register() { 3 | Enrichers.#enrichSceneLinks(); 4 | // Note that this one has to go last for now 5 | Enrichers.#enrichTags(); 6 | } 7 | 8 | static #enrichSceneLinks() { 9 | const enrichSceneLinks = ([text, sceneId, flavour]) => { 10 | const id = sceneId.replace(/^Scene./, ""); 11 | 12 | const scene = game.scenes.get(id) || game.scenes.getName(id); 13 | if (!scene) return text; 14 | 15 | const link = $( 16 | `${ 21 | flavour || scene.navName 22 | }`, 23 | ); 24 | return link[0]; 25 | }; 26 | CONFIG.TextEditor.enrichers.push({ 27 | pattern: CONFIG.litm.sceneLinkRe, 28 | enricher: enrichSceneLinks, 29 | }); 30 | } 31 | 32 | static #enrichTags() { 33 | const tooltip = game.i18n.localize("Litm.ui.drag-apply"); 34 | const enrichTags = ([_text, tag, status]) => { 35 | if (tag.startsWith("-")) 36 | return $( 37 | `${tag.replace(/^-/, "")}${ 38 | status ? `:${status}` : "" 39 | }`, 40 | )[0]; 41 | if (tag && status) 42 | return $( 43 | `${tag}-${status}`, 44 | )[0]; 45 | return $( 46 | `${tag}`, 47 | )[0]; 48 | }; 49 | CONFIG.TextEditor.enrichers.push({ 50 | pattern: CONFIG.litm.tagStringRe, 51 | enricher: enrichTags, 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scripts/system/fonts.js: -------------------------------------------------------------------------------- 1 | import { info } from "../logger.js"; 2 | 3 | export class Fonts { 4 | static register() { 5 | info("Registering Fonts..."); 6 | FontConfig.loadFont("LitM Dice", { 7 | fonts: [ 8 | { 9 | name: "LitM Dice", 10 | urls: ["systems/litm/assets/fonts/litm-dice.otf"], 11 | }, 12 | ], 13 | }); 14 | FontConfig.loadFont("CaslonAntique", { 15 | editor: true, 16 | fonts: [ 17 | { 18 | name: "CaslonAntique", 19 | urls: ["systems/litm/assets/fonts/caslon.ttf"], 20 | sizeAdjust: "110%", 21 | }, 22 | { 23 | name: "CaslonAntique", 24 | urls: ["systems/litm/assets/fonts/caslon-b.ttf"], 25 | weight: "bold", 26 | sizeAdjust: "110%", 27 | }, 28 | { 29 | name: "CaslonAntique", 30 | urls: ["systems/litm/assets/fonts/caslon-i.ttf"], 31 | style: "italic", 32 | sizeAdjust: "110%", 33 | }, 34 | ], 35 | }); 36 | FontConfig.loadFont("Fraunces", { 37 | editor: true, 38 | fonts: [ 39 | { 40 | name: "Fraunces", 41 | urls: ["systems/litm/assets/fonts/fraunces.ttf"], 42 | weight: "300 800", 43 | }, 44 | { 45 | name: "Fraunces", 46 | urls: ["systems/litm/assets/fonts/fraunces-i.ttf"], 47 | style: "italic", 48 | weight: "300 800", 49 | }, 50 | ], 51 | }); 52 | FontConfig.loadFont("AlchemyItalic", { 53 | editor: true, 54 | fonts: [ 55 | { 56 | name: "AlchemyItalic", 57 | urls: ["systems/litm/assets/fonts/alchemy-i.ttf"], 58 | }, 59 | ], 60 | }); 61 | FontConfig.loadFont("PackardAntique", { 62 | editor: true, 63 | fonts: [ 64 | { 65 | name: "PackardAntique", 66 | urls: ["systems/litm/assets/fonts/packard.ttf"], 67 | }, 68 | { 69 | name: "PackardAntique", 70 | urls: ["systems/litm/assets/fonts/packard-b.ttf"], 71 | weight: "bold", 72 | }, 73 | ], 74 | }); 75 | FontConfig.loadFont("PowellAntique", { 76 | editor: true, 77 | fonts: [ 78 | { 79 | name: "PowellAntique", 80 | urls: ["systems/litm/assets/fonts/powell.ttf"], 81 | }, 82 | { 83 | name: "PowellAntique", 84 | urls: ["systems/litm/assets/fonts/powell-b.ttf"], 85 | weight: "bold", 86 | }, 87 | ], 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /scripts/system/handlebars.js: -------------------------------------------------------------------------------- 1 | import { info } from "../logger.js"; 2 | 3 | export class HandlebarsHelpers { 4 | static register() { 5 | info("Registering Handlebars Helpers..."); 6 | 7 | Handlebars.registerHelper("add", (...args) => { 8 | args.pop(); 9 | return args.reduce((acc, val) => acc + val, 0); 10 | }); 11 | 12 | Handlebars.registerHelper("includes", (array, value, path) => 13 | Array.isArray(array) 14 | ? (path && array.some((i) => i[path] === value)) || 15 | array.includes(value) 16 | : false, 17 | ); 18 | 19 | Handlebars.registerHelper( 20 | "progress-buttons", 21 | function (current, max, block) { 22 | let acc = ""; 23 | for (let i = 0; i < max; ++i) { 24 | block.data.index = i; 25 | block.data.checked = i < current; 26 | acc += block.fn(this); 27 | } 28 | return acc; 29 | }, 30 | ); 31 | 32 | Handlebars.registerHelper( 33 | "titlecase", 34 | (string) => string.charAt(0).toUpperCase() + string.slice(1), 35 | ); 36 | 37 | Handlebars.registerHelper("tagActiveString", (tag, readonly) => 38 | tag.isActive 39 | ? "Litm.tags.isActive" 40 | : readonly 41 | ? "Litm.tags.isInactive" 42 | : "Litm.tags.activate", 43 | ); 44 | } 45 | } 46 | 47 | export class HandlebarsPartials { 48 | static partials = [ 49 | "systems/litm/templates/apps/loot-dialog.html", 50 | "systems/litm/templates/apps/roll-dialog.html", 51 | "systems/litm/templates/apps/story-tags.html", 52 | "systems/litm/templates/chat/message.html", 53 | "systems/litm/templates/chat/message-tooltip.html", 54 | "systems/litm/templates/chat/moderation.html", 55 | "systems/litm/templates/item/backpack-ro.html", 56 | "systems/litm/templates/item/theme-ro.html", 57 | "systems/litm/templates/partials/new-tag.html", 58 | "systems/litm/templates/partials/tag.html", 59 | ]; 60 | 61 | static register() { 62 | info("Registering Handlebars Partials..."); 63 | loadTemplates(HandlebarsPartials.partials); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /scripts/system/keybindings.js: -------------------------------------------------------------------------------- 1 | import { localize as t } from "../utils.js"; 2 | 3 | export class KeyBindings { 4 | static register() { 5 | game.keybindings.register("litm", "openDiceRoller", { 6 | name: t("Litm.ui.dice-roller"), 7 | hint: t("Litm.ui.dice-roller-hint"), 8 | editable: [ 9 | { 10 | key: "KeyR", 11 | }, 12 | ], 13 | onDown: () => { 14 | const sheet = game.user.character?.sheet; 15 | if (!sheet) 16 | return ui.notifications.warn("Litm.ui.warn-no-character", { 17 | localize: true, 18 | }); 19 | return sheet.renderRollDialog({ toggle: true }); 20 | }, 21 | onUp: () => {}, 22 | restricted: false, 23 | precedence: CONST.KEYBINDING_PRECEDENCE.PRIORITY, 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/system/settings.js: -------------------------------------------------------------------------------- 1 | export class LitmSettings { 2 | static register() { 3 | game.settings.register("litm", "welcomed", { 4 | name: "Welcome Screen", 5 | hint: "Welcome Scene, Message, and Journal Entry has been created and displayed.", 6 | scope: "world", 7 | config: false, 8 | type: Boolean, 9 | default: false, 10 | }); 11 | 12 | game.settings.register("litm", "storytags", { 13 | name: "Story Tags", 14 | hint: "Tags that are shared between all users.", 15 | scope: "world", 16 | config: false, 17 | type: Object, 18 | default: { 19 | tags: [], 20 | actors: [], 21 | }, 22 | }); 23 | game.settings.register("litm", "show_tag_window_on_load", { 24 | name: "Litm.ui.show-tag-window-on-load", 25 | hint: "Litm.ui.show-tag-window-on-load-hint", 26 | scope: "client", 27 | config: true, 28 | type: Boolean, 29 | default: true, 30 | }); 31 | game.settings.register("litm", "skip_roll_moderation", { 32 | name: "Litm.settings.skip-roll-moderation", 33 | hint: "Litm.settings.skip-roll-moderation-hint", 34 | scope: "client", 35 | config: true, 36 | type: Boolean, 37 | default: false, 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/system/sockets.js: -------------------------------------------------------------------------------- 1 | export class Sockets { 2 | static dispatch(event, data) { 3 | if (!game.ready) 4 | return console.error( 5 | `Tried to dispatch ${event} socket event before the game was ready.`, 6 | ); 7 | 8 | const senderIsGM = game.user.isGM; 9 | const senderId = game.user.id; 10 | const id = foundry.utils.randomID(); 11 | game.socket.emit("system.litm", { 12 | id, 13 | data, 14 | event, 15 | senderIsGM, 16 | senderId, 17 | }); 18 | } 19 | 20 | static on(event, cb) { 21 | game.socket.on("system.litm", (data) => { 22 | const { event: e, senderId, ...d } = data; 23 | if (e !== event || senderId === game.userId) return; 24 | cb(d); 25 | }); 26 | } 27 | 28 | static registerListeners() { 29 | this.#registerRollUpdateListener(); 30 | this.#registerRollModerationListeners(); 31 | 32 | Hooks.once("ready", () => { 33 | if (game.user.isGM) this.#registerGMRollListeners(); 34 | }); 35 | } 36 | 37 | static #registerRollUpdateListener() { 38 | Sockets.on("updateRollDialog", (event) => { 39 | const { data } = event; 40 | const actor = game.actors.get(data.actorId); 41 | if (!actor) return console.warn(`Actor ${data.actorId} not found`); 42 | actor.sheet.updateRollDialog(data); 43 | }); 44 | } 45 | 46 | static #registerRollModerationListeners() { 47 | Sockets.on("rollDice", ({ data: { userId, data } }) => { 48 | if (userId !== game.userId) return; 49 | game.litm.LitmRollDialog.roll(data); 50 | }); 51 | 52 | Sockets.on("rejectRoll", ({ data: { actorId, name } }) => { 53 | ui.notifications.warn( 54 | game.i18n.format("Litm.ui.roll-rejected", { name }), 55 | ); 56 | const actor = game.actors.get(actorId); 57 | if (!actor) return console.warn(`Actor ${actorId} not found`); 58 | actor.sheet.renderRollDialog(); 59 | }); 60 | 61 | Sockets.on("resetRollDialog", ({ data: { actorId } }) => { 62 | const actor = game.actors.get(actorId); 63 | if (!actor) return console.warn(`Actor ${actorId} not found`); 64 | actor.sheet.resetRollDialog(); 65 | }); 66 | } 67 | 68 | static #registerGMRollListeners() { 69 | Sockets.on("skipModeration", ({ data: { name } }) => { 70 | ui.notifications.info( 71 | game.i18n.format("Litm.ui.player-skipped-moderation", { name }), 72 | ); 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /scripts/utils.js: -------------------------------------------------------------------------------- 1 | export function sleep(ms) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | 5 | export function localize(...key) { 6 | if (key.length === 1) return game.i18n.localize(key[0]); 7 | return key.map((k) => game.i18n.localize(k)).join(" "); 8 | } 9 | 10 | export function sortByName(a, b) { 11 | return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); 12 | } 13 | 14 | export function sortTags(tags) { 15 | return tags.sort(sortByName); 16 | } 17 | 18 | export function titleCase(str) { 19 | return ( 20 | str.charAt(0).toUpperCase() + 21 | str 22 | .toLowerCase() 23 | .replace(/\b\w+/g, (l) => { 24 | if (["and", "the", "of", "or", "a", "an"].includes(l)) return l; 25 | return l.charAt(0).toUpperCase() + l.substr(1); 26 | }) 27 | .slice(1) 28 | ); 29 | } 30 | 31 | export function dispatch(data) { 32 | const isGM = game.user.isGM; 33 | const user = game.user.id; 34 | return game.socket.emit("system.litm", { ...data, isGM, user }); 35 | } 36 | 37 | export async function newTagDialog(actors) { 38 | const t = localize; 39 | return Dialog.wait( 40 | { 41 | title: t("Litm.ui.add-tag"), 42 | content: await renderTemplate( 43 | "systems/litm/templates/partials/new-tag.html", 44 | { actors }, 45 | ), 46 | acceptLabel: t("Litm.ui.create"), 47 | buttons: { 48 | cancel: { 49 | label: t("Litm.ui.cancel"), 50 | }, 51 | create: { 52 | label: t("Litm.ui.create"), 53 | callback: (html) => { 54 | const form = html.find("form")[0]; 55 | const formData = new FormDataExtended(form); 56 | const expanded = foundry.utils.expandObject(formData.object); 57 | return expanded; 58 | }, 59 | }, 60 | }, 61 | default: "create", 62 | }, 63 | { 64 | classes: ["litm", "litm--new-tag"], 65 | }, 66 | ); 67 | } 68 | 69 | export async function confirmDelete(string = "Item") { 70 | const thing = game.i18n.localize(string); 71 | return Dialog.confirm({ 72 | title: game.i18n.format("Litm.ui.confirm-delete-title", { thing }), 73 | content: game.i18n.format("Litm.ui.confirm-delete-content", { thing }), 74 | defaultYes: false, 75 | options: { 76 | classes: ["litm", "litm--confirm-delete"], 77 | }, 78 | }); 79 | } 80 | 81 | export async function gmModeratedRoll(app, cb) { 82 | const id = foundry.utils.randomID(); 83 | game.litm.rolls[id] = cb; 84 | 85 | dispatch({ app, id, type: "roll" }); 86 | } 87 | -------------------------------------------------------------------------------- /styles/apps/loot-dialog.css: -------------------------------------------------------------------------------- 1 | .litm--loot-dialog { 2 | & ul { 3 | display: flex; 4 | flex-direction: column; 5 | list-style: none; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /styles/apps/roll-dialog.css: -------------------------------------------------------------------------------- 1 | .litm.litm--roll { 2 | min-width: 330px; 3 | background: url(../../assets/media/bg-alt.webp); 4 | background-size: cover; 5 | background-blend-mode: luminosity; 6 | background-color: var(--litm-color-alt-bg); 7 | 8 | & .window-header { 9 | color: var(--litm-color-primary); 10 | } 11 | 12 | & .window-content form { 13 | display: flex; 14 | flex-direction: column; 15 | overflow: hidden; 16 | overflow: clip; 17 | font-family: var(--litm-font-text); 18 | } 19 | 20 | & .title { 21 | grid-area: title; 22 | font-family: var(--litm-font-accent); 23 | font-size: var(--font-size-32); 24 | color: var(--litm-color); 25 | background: transparent; 26 | border: none; 27 | text-align: center; 28 | height: unset; 29 | transition: background-color 0.15s; 30 | 31 | &:focus { 32 | box-shadow: none; 33 | background-color: rgb(0, 0, 0, 0.1); 34 | } 35 | } 36 | 37 | & .litm--tags-summary-total { 38 | font-family: var(--litm-font-primary); 39 | font-size: var(--font-size-20); 40 | color: var(--litm-color-primary); 41 | margin: auto auto 0px; 42 | border: none; 43 | } 44 | 45 | & .litm--roll-dialog-type { 46 | display: flex; 47 | flex-direction: column; 48 | justify-content: center; 49 | align-items: center; 50 | gap: var(--litm-space-3); 51 | margin: var(--litm-space-3) var(--litm-space-4) var(--litm-space-2); 52 | 53 | & .litm--roll-dialog-type-choices { 54 | display: flex; 55 | justify-content: center; 56 | gap: var(--litm-space-2); 57 | 58 | & .checkbox { 59 | height: unset; 60 | padding: var(--litm-space-1) var(--litm-space-3); 61 | border: none; 62 | border-radius: 0px; 63 | background-color: transparent; 64 | color: var(--litm-color); 65 | font-size: var(--font-size-20); 66 | font-family: var(--litm-font-accent); 67 | transition: text-shadow 0.15s; 68 | 69 | &:hover, 70 | &:focus, 71 | &:active { 72 | box-shadow: none; 73 | text-shadow: 0 0 4px rgb(0, 0, 0, 0.5); 74 | } 75 | 76 | &:has(input:checked) { 77 | border-bottom: 2px solid var(--litm-color); 78 | border-image: url("../../assets/media/item-divider.webp") 14 / 3; 79 | } 80 | 81 | & input { 82 | position: absolute; 83 | width: 1px; 84 | height: 1px; 85 | padding: 0; 86 | margin: -1px; 87 | overflow: hidden; 88 | clip: rect(0, 0, 0, 0); 89 | white-space: nowrap; 90 | border-width: 0; 91 | } 92 | } 93 | } 94 | } 95 | 96 | & .litm--roll-dialog-modifier { 97 | display: flex; 98 | justify-content: center; 99 | margin: var(--litm-space-3) 0; 100 | 101 | & label { 102 | display: flex; 103 | align-items: center; 104 | gap: var(--litm-space-2); 105 | font-family: var(--litm-font-primary); 106 | color: var(--litm-color); 107 | font-size: var(--font-size-18); 108 | } 109 | 110 | & input[type="number"] { 111 | width: 4em; 112 | text-align: center; 113 | background: transparent; 114 | border: none; 115 | border-bottom: 2px solid var(--litm-color-muted-bg); 116 | border-image: url("../../assets/media/item-divider.webp") 14 / 3; 117 | color: var(--litm-color-primary); 118 | font-family: var(--litm-font-accent); 119 | font-size: var(--font-size-20); 120 | padding: var(--litm-space-1); 121 | 122 | &:focus { 123 | box-shadow: none; 124 | background-color: rgba(0, 0, 0, 0.1); 125 | outline: none; 126 | } 127 | 128 | /* Remove spinner buttons */ 129 | &::-webkit-inner-spin-button, 130 | &::-webkit-outer-spin-button { 131 | -webkit-appearance: none; 132 | margin: 0; 133 | } 134 | &[type="number"] { 135 | -moz-appearance: textfield; 136 | appearance: textfield; 137 | } 138 | } 139 | } 140 | 141 | & .litm--roll-dialog-tags-summary { 142 | display: flex; 143 | flex-direction: column; 144 | flex: 1; 145 | overflow: scroll; 146 | 147 | & ul { 148 | list-style: none; 149 | display: flex; 150 | flex-wrap: wrap; 151 | justify-content: center; 152 | gap: var(--litm-space-3); 153 | padding-block: var(--litm-space-2); 154 | margin-inline: var(--litm-space-4); 155 | 156 | & .litm--roll-dialog-tag { 157 | display: flex; 158 | align-items: center; 159 | gap: var(--litm-space-1); 160 | padding: 0.1em 0.5em; 161 | cursor: pointer; 162 | 163 | &:has(litm-super-checkbox[value=""]) { 164 | opacity: 0.6; 165 | } 166 | 167 | & litm-super-checkbox { 168 | font-size: var(--font-size-14); 169 | } 170 | } 171 | } 172 | 173 | & hr { 174 | border-bottom: none; 175 | border-top: 5px solid var(--litm-color-muted-bg); 176 | border-image: url(../../assets/media/item-divider.webp) 12 / 3; 177 | height: 6px; 178 | width: 80%; 179 | margin-inline: auto; 180 | } 181 | } 182 | 183 | & button[data-click="add-tag"] { 184 | margin-inline: auto; 185 | padding-inline: var(--litm-space-4); 186 | padding-bottom: var(--litm-space-2); 187 | width: fit-content; 188 | border: none; 189 | border-radius: 0px; 190 | border-top: 2px solid rgb(0, 0, 0, 0.4); 191 | border-image: url("../../assets/media/item-divider.webp") 14 / 3; 192 | border-image-outset: var(--litm-space-1); 193 | background-color: transparent; 194 | color: var(--litm-color); 195 | font-size: var(--font-size-20); 196 | font-family: var(--litm-font-accent); 197 | transition: text-shadow 0.15s; 198 | 199 | &:hover, 200 | &:focus, 201 | &:active { 202 | box-shadow: none; 203 | text-shadow: 0 0 4px rgb(0, 0, 0, 0.5); 204 | } 205 | } 206 | 207 | & .litm--roll-dialog-buttons { 208 | position: relative; 209 | grid-area: buttons; 210 | margin-block: var(--litm-space-3) var(--litm-space-4); 211 | display: flex; 212 | align-items: center; 213 | justify-content: center; 214 | gap: var(--litm-space-4); 215 | 216 | .litm--roll-dialog-skip-moderation { 217 | position: absolute; 218 | display: flex; 219 | align-items: center; 220 | gap: var(--litm-space-2); 221 | left: var(--litm-space-2); 222 | bottom: calc(-1 * 1.8rem); 223 | cursor: pointer; 224 | } 225 | 226 | & button { 227 | padding-block: var(--litm-space-1); 228 | border: none; 229 | background: none; 230 | color: var(--litm-color-primary); 231 | font-family: var(--litm-font-heading); 232 | font-weight: bold; 233 | font-size: var(--font-size-24); 234 | cursor: pointer; 235 | transition: color 0.2s; 236 | 237 | &:hover { 238 | box-shadow: none; 239 | text-shadow: 0 0 4px rgb(0, 0, 0, 0.5); 240 | } 241 | 242 | &[type="submit"] { 243 | background-image: linear-gradient( 244 | to right, 245 | transparent, 246 | var(--litm-color-muted-bg), 247 | transparent 248 | ); 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /styles/apps/sidebar-buttons.css: -------------------------------------------------------------------------------- 1 | menu.litm--sidebar-buttons-container.litm--sidebar-buttons-container { 2 | position: absolute; 3 | left: -2rem; 4 | top: 40px; 5 | cursor: pointer; 6 | pointer-events: all; 7 | padding: 0; 8 | margin: 0; 9 | list-style: none; 10 | z-index: -1; 11 | 12 | & li button { 13 | position: relative; 14 | background: url("../../assets/media/tabs-bg.webp") no-repeat center / 15 | contain; 16 | color: var(--litm-color-primary-bg); 17 | display: grid; 18 | place-content: center; 19 | width: 42px; 20 | height: 42px; 21 | border: none; 22 | transition: left 130ms ease-in-out; 23 | 24 | &:hover, 25 | &:focus, 26 | &:active { 27 | box-shadow: none; 28 | } 29 | 30 | &:hover { 31 | left: -4px; 32 | } 33 | } 34 | 35 | &.active { 36 | left: -332px; 37 | 38 | & button:hover { 39 | left: 4px; 40 | } 41 | 42 | & button:first-child i { 43 | color: transparent; 44 | background: url("../../assets/media/tabs-collapse.webp") no-repeat center 45 | / contain; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /styles/apps/story-tags.css: -------------------------------------------------------------------------------- 1 | .litm.litm--story-tags { 2 | min-width: 300px; 3 | max-width: 600px; 4 | min-height: 250px; 5 | border-radius: 0px; 6 | overflow: hidden; 7 | overflow: clip; 8 | 9 | &:not(.minimized) { 10 | outline: 2px solid var(--litm-color); 11 | outline-offset: -10px; 12 | } 13 | 14 | & .window-header { 15 | background: url(../../assets/media/origin-theme-alt-bg-top.webp) no-repeat 16 | bottom / cover; 17 | padding-block-end: var(--litm-space-4); 18 | flex-wrap: nowrap; 19 | align-items: baseline; 20 | 21 | & .window-title { 22 | display: inline; 23 | font-size: var(--font-size-14); 24 | text-transform: uppercase; 25 | } 26 | } 27 | 28 | & form { 29 | position: relative; 30 | overflow: hidden; 31 | overflow: clip; 32 | display: flex; 33 | flex-direction: column; 34 | height: 100%; 35 | } 36 | 37 | & fieldset { 38 | border: none; 39 | margin: 0px; 40 | padding: 0px; 41 | } 42 | 43 | & .window-content form section { 44 | display: flex; 45 | flex-direction: column; 46 | margin-block-start: var(--litm-space-2); 47 | overflow-y: auto; 48 | flex: 1; 49 | 50 | &:last-of-type { 51 | height: 100%; 52 | } 53 | } 54 | 55 | & hr { 56 | border: none; 57 | min-height: 16px; 58 | background: url(../../assets/media/separator.webp) no-repeat center / 59 | contain; 60 | width: 95%; 61 | } 62 | 63 | & .litm--story-tags-main, 64 | & .litm--story-tags-actor { 65 | border: none; 66 | display: flex; 67 | flex-direction: column; 68 | 69 | & .litm--story-tags-section-header { 70 | display: flex; 71 | justify-content: space-between; 72 | align-items: center; 73 | gap: var(--litm-space-1); 74 | 75 | & legend { 76 | flex: 1 0 auto; 77 | margin-inline-start: -2px; 78 | padding: 0.4em 20px 0.4em 6px; 79 | background: url(../../assets/media/section-bg.webp) no-repeat center / 80 | auto; 81 | background-position: left; 82 | background-size: cover; 83 | color: var(--litm-color-primary-bg); 84 | font-family: var(--litm-font-primary); 85 | font-size: var(--font-size-16); 86 | line-height: var(--font-size-16); 87 | 88 | & img { 89 | aspect-ratio: 1 / 1; 90 | max-width: 25px; 91 | flex: none; 92 | } 93 | } 94 | 95 | & button { 96 | border: none; 97 | background: none; 98 | color: var(--litm-color-weakness); 99 | font-size: var(--font-size-20); 100 | margin-inline-end: var(--litm-space-2); 101 | flex: none; 102 | width: auto; 103 | 104 | &:hover, 105 | &:focus, 106 | &:active { 107 | box-shadow: none; 108 | text-shadow: 0 0 4px var(--litm-color-weakness); 109 | } 110 | } 111 | } 112 | 113 | & ul { 114 | display: flex; 115 | flex-direction: column; 116 | list-style: none; 117 | padding: 0 var(--litm-space-2); 118 | gap: var(--litm-space-1); 119 | 120 | & li { 121 | display: flex; 122 | align-items: center; 123 | border: none; 124 | padding-block: var(--litm-space-1); 125 | gap: var(--litm-space-2); 126 | 127 | & .litm--tag-item-name { 128 | background: transparent; 129 | border: none; 130 | font-family: var(--litm-font-text); 131 | font-size: var(--font-size-15); 132 | color: var(--litm-color-primary); 133 | min-width: 10px; 134 | padding: 0px; 135 | } 136 | 137 | & .litm--burn-label { 138 | height: var(--font-size-18); 139 | & .litm--burn { 140 | height: var(--font-size-18); 141 | } 142 | } 143 | 144 | & .litm--active-label { 145 | font-size: var(--font-size-11); 146 | line-height: 1; 147 | color: var(--litm-color-weakness); 148 | } 149 | 150 | & .litm--tag-item-status { 151 | flex: none; 152 | margin-inline: auto 0; 153 | display: flex; 154 | align-items: center; 155 | gap: var(--litm-space-1); 156 | 157 | & [type="checkbox"] { 158 | flex: none; 159 | appearance: none; 160 | background: white; 161 | cursor: pointer; 162 | margin: 0; 163 | padding: 0; 164 | width: 0.9em; 165 | height: 0.9em; 166 | border-radius: 4px; 167 | border: 1px solid var(--litm-color); 168 | 169 | &:checked { 170 | background: var(--litm-color); 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | & .litm--story-tags-no-actors { 179 | display: grid; 180 | place-content: center; 181 | text-align: center; 182 | color: var(--litm-color); 183 | margin: var(--litm-space-3); 184 | border-radius: 8px; 185 | padding: var(--litm-space-3); 186 | 187 | & img { 188 | aspect-ratio: 1 / 1; 189 | max-width: 75px; 190 | margin-inline: auto; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /styles/chat/message.css: -------------------------------------------------------------------------------- 1 | .litm.dice-roll { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | gap: var(--litm-space-1); 7 | padding: var(--litm-space-3); 8 | background: url(/ui/parchment.jpg) repeat; 9 | background-color: var(--litm-color-accent-bg); 10 | background-blend-mode: color-burn; 11 | border: 1px solid color-mix(in hsl, var(--litm-color-accent) 70%, transparent); 12 | border-radius: 6px; 13 | transition: all 0.15s; 14 | 15 | &:hover { 16 | box-shadow: 0 0 3px var(--litm-color-primary-bg); 17 | } 18 | 19 | & .content-link { 20 | color: var(--litm-color-alt-bg); 21 | 22 | & i { 23 | color: var(--litm-color-alt-bg); 24 | } 25 | } 26 | 27 | & .dice-flavor { 28 | font-family: var(--litm-font-heading); 29 | font-size: var(--font-size-24); 30 | color: var(--litm-color-bg); 31 | text-align: center; 32 | } 33 | 34 | & .dice-effect { 35 | & p:has(em) { 36 | color: var(--litm-color-alt-bg); 37 | font-family: var(--litm-font-primary); 38 | font-size: var(--font-size-16); 39 | text-align: center; 40 | } 41 | } 42 | 43 | & .dice-result { 44 | width: 100%; 45 | margin: 0px auto; 46 | 47 | & .tooltip-part { 48 | margin-block: var(--litm-space-1); 49 | } 50 | 51 | .part-flavor { 52 | &.burned { 53 | color: color-mix( 54 | in hsl, 55 | var(--litm-color-weakness) 60%, 56 | var(--litm-color-accent) 57 | ); 58 | } 59 | 60 | &.positive { 61 | color: color-mix(in hsl, var(--litm-color-status-bg), lightgreen); 62 | } 63 | 64 | &.warning { 65 | color: color-mix(in hsl, var(--litm-color-tag-bg), yellow); 66 | } 67 | 68 | &.negative { 69 | color: var(--litm-color-limit-bg); 70 | } 71 | } 72 | } 73 | 74 | & .dice-total { 75 | background-color: var(--litm-color-alt-bg); 76 | color: var(--litm-color-primary); 77 | font-family: var(--litm-font-heading); 78 | font-size: var(--font-size-20); 79 | text-align: center; 80 | padding: var(--litm-space-1); 81 | text-shadow: 0 0 2px rgb(0, 0, 0, 0.3); 82 | 83 | &:is(h4) { 84 | display: flex; 85 | flex-direction: column; 86 | } 87 | 88 | &.success { 89 | color: var(--color-level-success); 90 | } 91 | 92 | &.failure { 93 | color: var(--color-level-error); 94 | } 95 | } 96 | 97 | & .reference { 98 | padding: 0.1em 0.5em; 99 | } 100 | 101 | & .litm--dice-roll-subtle { 102 | color: color-mix(in hsl, var(--litm-color-primary-bg) 80%, transparent); 103 | font-family: var(--litm-font-primary); 104 | font-size: var(--font-size-12); 105 | } 106 | 107 | & .dice-result button { 108 | margin-block-start: var(--litm-space-2); 109 | font-family: var(--litm-font-sc); 110 | text-transform: uppercase; 111 | font-size: var(--font-size-11); 112 | font-weight: bold; 113 | background: transparent; 114 | border: 2px solid var(--litm-color-alt-bg); 115 | color: var(--litm-color-alt-bg); 116 | scale: 0.9; 117 | width: 100%; 118 | 119 | &.litm--roll-approve-button { 120 | color: color-mix( 121 | in hsl, 122 | var(--litm-color-status-bg) 70%, 123 | var(--litm-color-accent) 124 | ); 125 | border-color: color-mix( 126 | in hsl, 127 | var(--litm-color-status-bg) 70%, 128 | var(--litm-color-accent) 129 | ); 130 | } 131 | 132 | &.litm--roll-reject-button { 133 | color: color-mix( 134 | in hsl, 135 | var(--litm-color-limit-bg) 70%, 136 | var(--litm-color-accent) 137 | ); 138 | border-color: color-mix( 139 | in hsl, 140 | var(--litm-color-limit-bg) 70%, 141 | var(--litm-color-accent) 142 | ); 143 | } 144 | 145 | &.litm--roll-burn-button { 146 | color: color-mix( 147 | in hsl, 148 | var(--litm-color-weakness) 60%, 149 | var(--litm-color-accent) 150 | ); 151 | border-color: color-mix( 152 | in hsl, 153 | var(--litm-color-weakness) 60%, 154 | var(--litm-color-accent) 155 | ); 156 | } 157 | 158 | &:hover { 159 | text-shadow: 0px 0px 2px var(--litm-color-accent); 160 | } 161 | } 162 | } 163 | 164 | /* Moderation message */ 165 | .chat-message.message { 166 | & .gmOnly { 167 | display: none; 168 | } 169 | 170 | &[data-user="gm"] { 171 | & .gmOnly { 172 | display: block; 173 | } 174 | 175 | & .playerOnly { 176 | display: none; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /styles/components.css: -------------------------------------------------------------------------------- 1 | .litm--sro { 2 | position: absolute; 3 | width: 1px; 4 | height: 1px; 5 | padding: 0; 6 | margin: -1px; 7 | overflow: hidden; 8 | clip: rect(0, 0, 0, 0); 9 | white-space: nowrap; 10 | border-width: 0; 11 | } 12 | 13 | :where(.litm--checkbox), 14 | .app.litm.litm :where(.litm--checkbox) { 15 | -webkit-appearance: none; 16 | flex: none; 17 | appearance: none; 18 | background-color: transparent; 19 | margin: 0; 20 | font: inherit; 21 | color: currentColor; 22 | height: 0.8em; 23 | width: 0.8em; 24 | border: 0.1em solid currentColor; 25 | border-radius: 0.1em; 26 | transform: translateY(0.025em); 27 | display: grid; 28 | place-content: center; 29 | rotate: 45deg; 30 | 31 | &::after { 32 | content: ""; 33 | width: 0.36em; 34 | height: 0.36em; 35 | scale: 0; 36 | transition: scale 120ms ease-in-out; 37 | box-shadow: inset 1em 1em currentColor; 38 | } 39 | 40 | &.checked::after, 41 | &:checked::after { 42 | scale: 1; 43 | } 44 | 45 | &:is([type="checkbox"]) { 46 | cursor: pointer; 47 | } 48 | 49 | &:disabled { 50 | cursor: auto; 51 | } 52 | } 53 | 54 | :where(.litm--checkbox-with-bg), 55 | .app.litm.litm :where(.litm--checkbox-with-bg) { 56 | background: url("../assets/media/checkbox.svg") no-repeat; 57 | height: 1.1em; 58 | width: 1.1em; 59 | background-size: contain; 60 | background-position: center; 61 | border: none; 62 | rotate: 0deg; 63 | position: relative; 64 | 65 | &.checked, 66 | &:checked { 67 | background-image: url("../assets/media/checkbox-c.svg"); 68 | } 69 | 70 | &::after { 71 | display: none; 72 | } 73 | 74 | &:disabled { 75 | cursor: auto; 76 | } 77 | } 78 | 79 | :where(.litm--burn), 80 | .app.litm.litm :where(.litm--burn) { 81 | -webkit-appearance: none; 82 | flex: none; 83 | appearance: none; 84 | margin: 0; 85 | font: inherit; 86 | color: currentColor; 87 | aspect-ratio: 5 / 6; 88 | height: 1.5em; 89 | width: unset; 90 | background: url("../assets/media/burn.svg") no-repeat; 91 | background-color: inherit; 92 | background-size: contain; 93 | background-position: center; 94 | 95 | &.transparent { 96 | background-image: url("../assets/media/burn-t.svg"); 97 | filter: none; 98 | } 99 | 100 | &:checked, 101 | &.checked { 102 | background-image: url("../assets/media/burn-c.svg"); 103 | } 104 | 105 | &:is([type="checkbox"]) { 106 | cursor: pointer; 107 | } 108 | 109 | &:disabled { 110 | cursor: auto; 111 | } 112 | } 113 | 114 | :where(.litm--activate) { 115 | flex: none; 116 | appearance: none; 117 | margin: 0px; 118 | padding: 0px; 119 | border: 1px solid currentColor; 120 | font: inherit; 121 | color: currentColor; 122 | background: white; 123 | rotate: 45deg; 124 | border-radius: 0.3em; 125 | display: grid; 126 | place-content: center; 127 | 128 | &::before { 129 | content: "–"; 130 | display: block; 131 | rotate: -45deg; 132 | -webkit-text-stroke-width: 0.1em; 133 | } 134 | 135 | &:checked, 136 | &.checked { 137 | background-color: currentColor; 138 | 139 | &::before { 140 | color: white; 141 | } 142 | } 143 | 144 | &:disabled { 145 | cursor: auto; 146 | } 147 | } 148 | 149 | .litm--accent-bg { 150 | background: url("/ui/parchment.jpg") repeat; 151 | background-color: var(--litm-color-accent-bg); 152 | background-blend-mode: color-burn; 153 | color: var(--litm-color-accent); 154 | } 155 | 156 | :where(.litm--tag), 157 | :where(.litm--status), 158 | :where(.litm--limit), 159 | :where(.litm--powerTag), 160 | :where(.litm--weaknessTag), 161 | :where(.litm--themeTag), 162 | :where(.litm--backpack) { 163 | padding: 0px 0.4em; 164 | border-radius: 0.2em; 165 | white-space: nowrap; 166 | -webkit-box-decoration-break: clone; 167 | box-decoration-break: clone; 168 | color: var(--litm-color-primary); 169 | font-style: italic; 170 | font-weight: 400; 171 | box-shadow: 1px 1px 1px var(--litm-color-weakness-bg); 172 | 173 | &.litm--tag { 174 | background: var(--litm-color-tag-bg); 175 | } 176 | 177 | &.litm--status { 178 | background: var(--litm-color-status-bg); 179 | } 180 | 181 | &.litm--limit, 182 | &.litm--weaknessTag { 183 | background: var(--litm-color-limit-bg); 184 | } 185 | 186 | &.litm--powerTag, 187 | &.litm--themeTag, 188 | &.litm--backpack { 189 | background: color-mix(in hsl, var(--litm-color-accent-bg) 60%, transparent); 190 | } 191 | } 192 | 193 | :where(.litm--kbd) { 194 | flex: none; 195 | padding: 0 4px; 196 | min-width: 24px; 197 | background: rgba(255, 255, 255, 0.25); 198 | border: 1px solid var(--color-border-light-2); 199 | border-radius: 5px; 200 | box-shadow: 1px 1px #444; 201 | text-align: center; 202 | } 203 | 204 | :where(.litm--red-leaf) { 205 | filter: hue-rotate(-25deg); 206 | } 207 | 208 | :where(.litm--green-leaf) { 209 | filter: hue-rotate(45deg); 210 | 211 | /* For some reason filters bug out inputs */ 212 | & + * input { 213 | filter: hue-rotate(0deg); 214 | } 215 | } 216 | 217 | /* Custom Elements */ 218 | litm-toggled-input { 219 | display: block; 220 | flex: 1; 221 | 222 | & [role="presentation"]:empty { 223 | min-width: 100px; 224 | min-height: 50px; 225 | background-color: rgb(0, 0, 0, 0.2); 226 | } 227 | } 228 | 229 | /* Custom App */ 230 | 231 | .litm.litm--new-tag { 232 | & .window-header { 233 | background: url(/ui/parchment.jpg) repeat; 234 | background-color: var(--litm-color-accent-bg); 235 | background-blend-mode: color-burn; 236 | color: var(--litm-color-accent); 237 | 238 | & h4 { 239 | display: inline; 240 | } 241 | } 242 | } 243 | 244 | .litm--import-actor { 245 | width: -webkit-fill-available; 246 | } 247 | 248 | .litm.litm--confirm-delete { 249 | outline: 2px solid var(--litm-color); 250 | outline-offset: -10px; 251 | padding: 10px; 252 | border-radius: 0px; 253 | 254 | & .window-header { 255 | background: url("../assets/media/origin-theme-alt-bg-top.webp") no-repeat 256 | bottom / cover; 257 | color: var(--litm-color-accent); 258 | flex: 0 0 32px; 259 | 260 | & h4 { 261 | display: inline; 262 | text-transform: uppercase; 263 | font-size: var(--font-size-12); 264 | } 265 | } 266 | 267 | & .window-content { 268 | padding: var(--litm-space-3); 269 | 270 | & .dialog-buttons { 271 | margin-block-start: var(--litm-space-2); 272 | display: flex; 273 | justify-content: space-between; 274 | gap: var(--litm-space-2); 275 | } 276 | 277 | & button { 278 | background: var(--litm-color-accent); 279 | color: var(--litm-color-primary); 280 | border: 2px solid transparent; 281 | border-image: url(../assets/media/button-border.webp) 13 / 4; 282 | border-image-outset: 2px; 283 | text-transform: uppercase; 284 | 285 | &.yes { 286 | color: var(--litm-color-accent); 287 | background-color: var(--litm-color-accent-bg); 288 | } 289 | } 290 | } 291 | } 292 | 293 | /* Experimental Features */ 294 | @supports (field-sizing: content) { 295 | [data-size-input] { 296 | field-sizing: content; 297 | min-inline-size: 7ch; 298 | width: unset !important; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /styles/dark.css: -------------------------------------------------------------------------------- 1 | body.theme-dark { 2 | & :not(.litm) { 3 | & .litm--tag, 4 | & .litm--status, 5 | & .litm--limit { 6 | color: var(--color-text-primary); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /styles/foundry.css: -------------------------------------------------------------------------------- 1 | /* DANGER AREA */ 2 | /* TODO: Find a better way to override styles like this */ 3 | 4 | /* Custom dice roll chat messages */ 5 | .chat-message:has(.litm.dice-roll) { 6 | background-color: var(--litm-color-weakness); 7 | background-blend-mode: color-burn; 8 | color: var(--litm-color-primary-bg); 9 | font-family: var(--litm-font-text); 10 | font-size: var(--font-size-12); 11 | 12 | &.whisper { 13 | background-color: var(--color-border-dark-primary); 14 | } 15 | 16 | &.blind { 17 | border-color: var(--color-level-warning); 18 | } 19 | 20 | &.self { 21 | border-color: var(--color-level-error); 22 | } 23 | 24 | & .message-header { 25 | color: color-mix(in hsl, var(--litm-color-accent) 70%, transparent); 26 | } 27 | } 28 | 29 | /* Custom content links */ 30 | a.content-link { 31 | border: none; 32 | border-radius: 0px; 33 | background: none; 34 | padding-inline: 4px; 35 | outline: 1px dashed; 36 | outline-offset: -1px; 37 | 38 | & i { 39 | margin-inline-end: 3px; 40 | } 41 | } 42 | 43 | #logo { 44 | object-fit: contain; 45 | object-position: top; 46 | filter: drop-shadow( 47 | 0px 0px 3px color-mix(in hsl, var(--litm-color-bg) 60%, transparent) 48 | ); 49 | margin-inline: 16px; 50 | image-rendering: -webkit-optimize-contrast; 51 | } 52 | 53 | /* Fix the pause button */ 54 | #pause.paused { 55 | animation: none; 56 | gap: 0px; 57 | 58 | img { 59 | will-change: filter; 60 | animation: pulse 3s infinite ease-in-out; 61 | opacity: 80%; 62 | object-fit: contain; 63 | width: 180px; 64 | height: 180px; 65 | left: calc(50% - 90px); 66 | rotate: 39deg; 67 | margin-block: -35px -10px; 68 | } 69 | 70 | figcaption { 71 | will-change: filter; 72 | animation: pulse 3s infinite ease-in-out; 73 | color: var(--litm-color-accent); 74 | text-shadow: 2px 2px 2px var(--litm-color); 75 | font-family: var(--litm-font-heading); 76 | font-weight: 800; 77 | letter-spacing: 2.5px; 78 | padding: 0; 79 | margin: 0; 80 | } 81 | } 82 | 83 | @media (prefers-reduced-motion) { 84 | #pause { 85 | animation: none; 86 | } 87 | } 88 | 89 | /* Reduce size of tooltips */ 90 | #tooltip, 91 | aside[role="tooltip"] { 92 | font-size: var(--font-size-14); 93 | 94 | & a.content-link { 95 | color: var(--litm-color-accent); 96 | 97 | & i { 98 | color: var(--litm-color-accent); 99 | } 100 | } 101 | } 102 | 103 | /* Draggable should have grap-cursor */ 104 | [draggable="true"] { 105 | cursor: grab; 106 | } 107 | 108 | /* Animations */ 109 | @keyframes pulse { 110 | 0% { 111 | filter: drop-shadow(0 0 0 transparent); 112 | } 113 | 114 | 50% { 115 | filter: drop-shadow(0 0 4px var(--litm-color-alt-bg)); 116 | } 117 | 118 | 100% { 119 | filter: drop-shadow(0 0 0 transparent); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /styles/item/backpack.css: -------------------------------------------------------------------------------- 1 | .litm.litm--character .litm--character-backpack, 2 | .litm.litm--backpack { 3 | isolation: isolate; 4 | background: none; 5 | padding: 0px; 6 | overflow: visible; 7 | 8 | & .window-header { 9 | position: absolute; 10 | padding-inline: 70px; 11 | width: 100%; 12 | flex-wrap: nowrap; 13 | text-shadow: 1px 2px 2px var(--litm-color); 14 | color: var(--color-text-light-highlight); 15 | } 16 | 17 | & .window-content { 18 | overflow: visible; 19 | } 20 | 21 | & .litm--backpack-wrapper { 22 | display: grid; 23 | grid-template-areas: "center"; 24 | place-items: center; 25 | 26 | & > * { 27 | grid-area: center; 28 | } 29 | } 30 | 31 | & .litm--backpack-img { 32 | border: none; 33 | rotate: -2deg; 34 | z-index: -1; 35 | pointer-events: none; 36 | } 37 | 38 | & .litm--backpack-bg-frame { 39 | pointer-events: none; 40 | z-index: 1; 41 | } 42 | 43 | & .litm--backpack-bg { 44 | background: url("../../assets/media/background.webp") 0% 0% / cover; 45 | overflow: hidden; 46 | box-shadow: none; 47 | display: flex; 48 | flex-direction: column; 49 | height: 398px; 50 | width: 260px; 51 | 52 | & .meta { 53 | background: url(../../assets/media/backpack-top-bg.webp) no-repeat bottom 54 | / cover; 55 | color: var(--litm-color-accent); 56 | font-family: var(--litm-font-primary); 57 | text-transform: uppercase; 58 | font-size: var(--font-size-12); 59 | text-align: center; 60 | padding: var(--litm-space-2) var(--litm-space-3); 61 | } 62 | 63 | & .title { 64 | color: var(--litm-color); 65 | font-size: var(--font-size-32); 66 | font-family: var(--litm-font-accent); 67 | text-align: center; 68 | background: none; 69 | border: none; 70 | 71 | &:focus, 72 | &:active { 73 | box-shadow: none; 74 | background-color: rgb(0, 0, 0, 0.2); 75 | } 76 | } 77 | 78 | & .taglist { 79 | display: flex; 80 | flex-direction: column; 81 | list-style: none; 82 | margin: var(--litm-space-1) 0px 0px; 83 | padding: var(--litm-space-2) var(--litm-space-3); 84 | height: 100%; 85 | overflow: auto; 86 | 87 | & li { 88 | display: flex; 89 | align-items: center; 90 | gap: var(--litm-space-3); 91 | color: var(--litm-color); 92 | 93 | &:nth-child(odd) { 94 | background-image: linear-gradient( 95 | to right, 96 | transparent, 97 | rgba(69, 59, 29, 0.15), 98 | transparent 99 | ); 100 | } 101 | 102 | & .tag--name { 103 | text-align: center; 104 | border: none; 105 | border-radius: 0px; 106 | padding-block: var(--litm-space-2); 107 | min-height: var(--font-size-14); 108 | height: auto; 109 | background-color: transparent; 110 | font-family: var(--litm-font-text); 111 | color: var(--litm-color-primary); 112 | width: 100%; 113 | border-top: 1.5px solid rgb(0, 0, 0, 0.2); 114 | border-image: url("../../assets/media/item-divider.webp") 14 / 2; 115 | 116 | &:focus, 117 | &:active { 118 | box-shadow: none; 119 | } 120 | 121 | &:read-only { 122 | cursor: auto; 123 | } 124 | } 125 | 126 | & label { 127 | display: flex; 128 | cursor: pointer; 129 | } 130 | } 131 | } 132 | 133 | & button[data-click="add-tag"] { 134 | margin-block: auto 0px; 135 | margin-inline: 2rem; 136 | padding-bottom: var(--litm-space-2); 137 | width: auto; 138 | border: none; 139 | border-radius: 0px; 140 | border-top: 2px solid rgb(0, 0, 0, 0.4); 141 | border-image: url("../../assets/media/item-divider.webp") 14 / 3; 142 | border-image-outset: var(--litm-space-1); 143 | background-color: transparent; 144 | color: var(--litm-color); 145 | font-size: var(--font-size-20); 146 | font-family: var(--litm-font-accent); 147 | transition: all 0.15s; 148 | 149 | &:hover, 150 | &:focus, 151 | &:active { 152 | box-shadow: none; 153 | text-shadow: 0 0 4px rgb(0, 0, 0, 0.5); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /styles/item/threat.css: -------------------------------------------------------------------------------- 1 | .litm.litm--threat { 2 | background-color: var(--litm-color-primary); 3 | background-blend-mode: luminosity; 4 | font-family: var(--litm-font-text); 5 | font-size: var(--font-size-16); 6 | outline: 2px solid var(--litm-color); 7 | outline-offset: -10px; 8 | padding: 10px; 9 | 10 | & header { 11 | color: var(--litm-color-primary); 12 | } 13 | 14 | & form { 15 | color: var(--litm-color-primary); 16 | padding: 0px var(--litm-space-2); 17 | } 18 | 19 | & h2 { 20 | text-align: center; 21 | border: none; 22 | font-family: var(--litm-font-accent); 23 | } 24 | 25 | & .litm--threat-wrapper { 26 | display: flex; 27 | flex-direction: column; 28 | gap: var(--litm-space-2); 29 | padding-inline-start: var(--litm-space-3); 30 | list-style: none; 31 | 32 | & li { 33 | display: flex; 34 | gap: var(--litm-space-1); 35 | } 36 | } 37 | 38 | & [contenteditable] { 39 | background-color: transparent; 40 | padding: var(--litm-space-1); 41 | border: none; 42 | border-radius: 4px; 43 | width: 100%; 44 | 45 | &:focus { 46 | background-color: rgb(0, 0, 0, 0.2); 47 | outline: none; 48 | } 49 | } 50 | 51 | & [contenteditable]:has(+#threat) { 52 | font-style: italic; 53 | } 54 | 55 | & [id^=consequence-] { 56 | font-weight: 500; 57 | min-width: 50px; 58 | 59 | &:empty { 60 | background-color: rgb(0, 0, 0, 0.2); 61 | } 62 | } 63 | 64 | & [contenteditable]:has(+[id^=consequence-]) { 65 | min-height: 20px; 66 | cursor: pointer; 67 | 68 | span& { 69 | cursor: text; 70 | } 71 | 72 | &:empty { 73 | background-color: rgb(0, 0, 0, 0.2); 74 | } 75 | } 76 | 77 | & button[data-click="add-consequence"] { 78 | margin-inline: 4px 3rem; 79 | padding-bottom: var(--litm-space-2); 80 | width: auto; 81 | border: none; 82 | border-radius: 0px; 83 | border-top: 1.5px solid rgb(0, 0, 0, 0.4); 84 | border-image: url(../../assets/media/tag-divider.webp) 14 / 3; 85 | background-color: transparent; 86 | color: var(--litm-color-weakness); 87 | font-size: var(--font-size-20); 88 | font-family: var(--litm-font-accent); 89 | transition: all 0.15s; 90 | text-align: left; 91 | 92 | &:hover { 93 | box-shadow: none; 94 | text-shadow: 0 0 4px rgb(0, 0, 0, 0.5); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /styles/popout.css: -------------------------------------------------------------------------------- 1 | .litm--popout { 2 | &.litm--character { 3 | width: 250px !important; 4 | height: 350px !important; 5 | position: relative; 6 | left: 270px; 7 | top: 80px; 8 | } 9 | 10 | &.litm--challenge { 11 | width: 320px !important; 12 | height: 700px !important; 13 | } 14 | 15 | &.litm--backpack { 16 | width: 400px !important; 17 | height: 450px !important; 18 | } 19 | 20 | & + .litm--roll .window-content form { 21 | overflow: auto; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /styles/root.css: -------------------------------------------------------------------------------- 1 | /* Font smooth only on macOS */ 2 | /* biome-ignore lint/correctness/noUnknownMediaFeatureName: It's used because this is specific to a certain browser/OS */ 3 | @media screen and (-webkit-min-device-pixel-ratio: 0) { 4 | html { 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | } 9 | 10 | body { 11 | --litm-color-bg: hsl(40, 85%, 87%); 12 | --litm-color: hsl(40, 27%, 24%); 13 | --litm-color-alt-bg: hsl(34, 34%, 82%); 14 | --litm-color-dark-bg: hsl(0, 0%, 45%); 15 | --litm-color-accent-bg: hsl(225.94 10.66% 41.72%); 16 | --litm-color-accent: hsl(46.61 81.44% 92.99%); 17 | --litm-color-accent-2: hsl(24deg 20.72% 43.81%); 18 | --litm-color-accent-3: hsl(17, 84%, 31%); 19 | --litm-color-primary-bg: color-mix( 20 | in hsl, 21 | var(--litm-color-accent) 75%, 22 | hsl(0 0% 100%) 23 | ); 24 | --litm-color-primary: color-mix(in hsl, var(--litm-color) 75%, hsl(0 0% 0%)); 25 | --litm-color-row-odd: rgb(0, 0, 0, 0.1); 26 | --litm-color-weakness: hsl(10 41.04% 26.41%); 27 | --litm-color-weakness-bg: hsl(6.5 42.05% 41.08% / 20%); 28 | --litm-color-muted-bg: hsl(0 0% 45.42% / 20%); 29 | --litm-color-tag-bg: hsl(45 98% 46% / 70%); 30 | --litm-color-status-bg: hsl(75.88 48.31% 42.3% / 70%); 31 | --litm-color-limit-bg: hsl(0 67.35% 63.41% / 70%); 32 | 33 | /* Fonts */ 34 | --litm-font-heading: PackardAntique, CaslonAntique, "Palatino Linotype", 35 | "Book Antiqua", Palatino, serif; 36 | --litm-font-primary: CaslonAntique, LibreCaslonText, "Palatino Linotype", 37 | "Book Antiqua", Palatino, serif; 38 | --litm-font-accent: AlchemyItalic, "Brush Script MT", "Bradley Hand ITC", 39 | "Lucida Calligraphy", "Lucida Handwriting", "Apple Chancery", cursive; 40 | --litm-font-text: Fraunces, LibreCaslonText, "Book Antiqua", Palatino, serif; 41 | --litm-font-sc: PowellAntique, PackardAntique, Palatino, serif; 42 | 43 | /* Vertical spacing */ 44 | --litm-space-1: 0.25rem; 45 | --litm-space-2: 0.5rem; 46 | --litm-space-3: 1rem; 47 | --litm-space-4: 2rem; 48 | --litm-space-5: 4rem; 49 | 50 | &.game, 51 | &.game .app { 52 | /* Foundry Overrides */ 53 | --font-size-26: 1.625rem; 54 | --font-primary: Fraunces, BespokeSans, Lato, "PackardAntique", "Open Sans", 55 | sans-serif; 56 | --color-text-light-1: var(--litm-color-accent); 57 | --color-shadow-primary: var(--litm-color-primary); 58 | --color-shadow-highlight: var(--litm-color-accent-bg); 59 | --color-border-highlight: var(--litm-color-accent-bg); 60 | --color-border-highlight-alt: var(--litm-color-accent-2); 61 | --color-text-hyperlink: var(--litm-color-accent-3); 62 | --color-level-success: rgba(26, 107, 34, 0.8); 63 | --color-level-error: rgba(105, 0, 8, 0.8); 64 | --color-level-warning: rgba(214, 150, 0, 0.8); 65 | } 66 | } 67 | 68 | .app.litm, 69 | .application.litm { 70 | background: url("../assets/media/background.webp") no-repeat left / cover; 71 | box-shadow: none; 72 | filter: drop-shadow(rgb(0, 0, 0, 0.6) 2px 4px 8px); 73 | 74 | &:where(:not(.minimized)) { 75 | padding: 0.625rem; 76 | 77 | & .window-title { 78 | display: none; 79 | } 80 | } 81 | 82 | /* Scrollbar colors */ 83 | & * { 84 | scrollbar-color: var(--litm-color-accent-2) transparent; 85 | scrollbar-width: thin; 86 | } 87 | 88 | & button { 89 | border: none; 90 | background: none; 91 | 92 | &:hover, 93 | &:focus, 94 | &:active { 95 | box-shadow: none; 96 | } 97 | } 98 | 99 | & img { 100 | max-width: 100%; 101 | height: auto; 102 | vertical-align: middle; 103 | font-style: italic; 104 | background-repeat: no-repeat; 105 | background-size: cover; 106 | shape-margin: 1rem; 107 | border: none; 108 | border-radius: 0; 109 | } 110 | 111 | & .window-header { 112 | border-bottom: none; 113 | font-family: var(--litm-font-primary); 114 | justify-content: end; 115 | flex: 0 0 1.5rem; 116 | 117 | & .document-id-link { 118 | margin-inline: 0 auto; 119 | } 120 | 121 | & .litm--sheet-scale-button { 122 | cursor: nwse-resize; 123 | 124 | & > i { 125 | rotate: 45deg; 126 | } 127 | } 128 | } 129 | 130 | & .window-content { 131 | padding: 0px; 132 | background: none; 133 | } 134 | 135 | & form { 136 | display: flex; 137 | flex-direction: column; 138 | } 139 | 140 | & .editor { 141 | min-height: 100px; 142 | height: 100%; 143 | 144 | & .tox.tox-tinymce .tox-edit-area__iframe { 145 | background-color: #fff; 146 | } 147 | 148 | blockquote { 149 | border: none; 150 | padding: 0px; 151 | font-family: var(--litm-font-accent); 152 | color: var(--litm-color-weakness); 153 | font-size: var(--font-size-14); 154 | margin: 0.8em 0px; 155 | } 156 | } 157 | } 158 | 159 | /* Animations */ 160 | @keyframes pulse { 161 | 0% { 162 | filter: drop-shadow(0 0 0 transparent); 163 | } 164 | 165 | 50% { 166 | filter: drop-shadow(0 0 4px var(--litm-color-alt-bg)); 167 | } 168 | 169 | 100% { 170 | filter: drop-shadow(0 0 0 transparent); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /system.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/foundryvtt-system-manifest.json", 3 | "id": "litm", 4 | "version": 29, 5 | "license": "LICENSE", 6 | "title": "Legend in the Mist Demo", 7 | "description": "A Rustic Fantasy Game for Foundry Virtual Tabletop.", 8 | "authors": [ 9 | { 10 | "name": "Filip Ambrosius", 11 | "discord": "aMediocreDad#2483", 12 | "email": "hello@amediocre.dev", 13 | "ko-fi": "amediocredad" 14 | } 15 | ], 16 | "esmodules": [ 17 | "litm.js" 18 | ], 19 | "styles": [ 20 | "litm.css" 21 | ], 22 | "documentTypes": { 23 | "Actor": { 24 | "character": { 25 | "htmlFields": [ 26 | "note" 27 | ] 28 | }, 29 | "challenge": { 30 | "htmlFields": [ 31 | "note", 32 | "special" 33 | ] 34 | } 35 | }, 36 | "Item": { 37 | "backpack": {}, 38 | "theme": { 39 | "htmlFields": [ 40 | "note" 41 | ] 42 | }, 43 | "threat": {} 44 | } 45 | }, 46 | "languages": [ 47 | { 48 | "lang": "cn", 49 | "name": "中文", 50 | "path": "lang/cn.json" 51 | }, 52 | { 53 | "lang": "de", 54 | "name": "Deutsch", 55 | "path": "lang/de.json" 56 | }, 57 | { 58 | "lang": "en", 59 | "name": "English", 60 | "path": "lang/en.json" 61 | }, 62 | { 63 | "lang": "es", 64 | "name": "Español", 65 | "path": "lang/es.json" 66 | }, 67 | { 68 | "lang": "no", 69 | "name": "Norsk", 70 | "path": "lang/no.json" 71 | } 72 | ], 73 | "socket": true, 74 | "compatibility": { 75 | "minimum": 12, 76 | "verified": 13, 77 | "maximum": 13 78 | }, 79 | "packs": [ 80 | { 81 | "label": "Tinderbox Demo", 82 | "type": "Adventure", 83 | "name": "tinderbox-demo", 84 | "path": "packs/tinderbox-demo", 85 | "system": "litm", 86 | "flags": {} 87 | } 88 | ], 89 | "flags": { 90 | "hotReload": { 91 | "extensions": [ 92 | "css", 93 | "html", 94 | "js", 95 | "json" 96 | ], 97 | "paths": [ 98 | "styles", 99 | "templates", 100 | "lang", 101 | "litm.css", 102 | "litm.js" 103 | ] 104 | } 105 | }, 106 | "relationships": { 107 | "recommends": [ 108 | { 109 | "id": "dice-so-nice", 110 | "manifest": "https://gitlab.com/riccisi/foundryvtt-dice-so-nice/-/raw/master/module/module.json" 111 | } 112 | ] 113 | }, 114 | "background": "systems/litm/assets/media/litm_splash.webp", 115 | "url": "https://github.com/aMediocreDad/litm", 116 | "bugs": "https://github.com/aMediocreDad/litm/issues?q=is:issue+is:open+sort:updated-desc", 117 | "manifest": "https://raw.githubusercontent.com/aMediocreDad/litm/main/system.json", 118 | "download": "https://github.com/aMediocreDad/litm/archive/refs/tags/v29.zip", 119 | "readme": "https://github.com/aMediocreDad/litm/blob/main/README.md" 120 | } 121 | -------------------------------------------------------------------------------- /templates/actor/challenge.html: -------------------------------------------------------------------------------- 1 | {{#*inline 'divider' title add}} 2 |
3 | 4 | 5 | 6 | {{#if add}} 7 | 13 | {{/if}} 14 |
15 | {{/inline}} 16 | 17 | 18 | {{#each data.system.challenges as |challenge|}} 19 | 20 | {{/each}} 21 | 22 | 24 |
25 |
26 | 28 | 30 | 31 | 33 |
35 | {{#progress-buttons data.system.rating 5}} 36 | 38 | {{/progress-buttons}} 39 |
40 | 42 |
43 | {{data.name}} 45 |
46 |
47 | 48 | 49 | {{data.name}} 51 | 52 | {{editor data.system.note target="system.note" owner=owner button=true editable=true}} 53 | {{> divider title="Litm.other.limits" add="limit"}} 54 | 71 | {{> divider title="Litm.ui.tags-statuses"}} 72 | 74 |
75 | {{#if isEditing}} 76 | {{data.system.tags}} 80 | {{else}} 81 |
82 | {{{data.system.renderedTags}}} 83 |
84 | {{/if}} 85 |
86 | {{> divider title="Litm.ui.special-features"}} 87 | {{editor data.system.special target="system.special" owner=owner button=true editable=true}} 88 | {{> divider title="Litm.ui.threats-consequences" add="threat"}} 89 | 101 |
102 |
-------------------------------------------------------------------------------- /templates/actor/character.html: -------------------------------------------------------------------------------- 1 | {{#*inline 'leaf' data}} 2 |
  • 4 | 8 |
    9 | 13 | 26 |
  • 27 | {{/inline}} 28 | 29 |
    30 | {{name}} 32 | 33 | 35 | 56 |
    57 |
    58 | {{localize 'Notes'}} 59 | 63 |
    64 | {{editor note target="system.note" owner=owner button=true editable=true}} 65 |
    66 | {{#if backpack.id}} 67 |
    70 | {{> systems/litm/templates/item/backpack-ro.html backpack=backpack}} 71 |
    72 | {{else}} 73 |
    74 | Soft illustration of a rustic backpack 77 |
    78 | {{/if}} 79 |
    80 | {{#each themes as |item|}} 81 |
    84 | {{> systems/litm/templates/item/theme-ro.html data=item.data}} 85 |
    86 | {{/each}} 87 |
    88 | 104 |
    -------------------------------------------------------------------------------- /templates/apps/loot-dialog.html: -------------------------------------------------------------------------------- 1 |
    2 |

    {{localize 'Litm.ui.item-transfer-content'}}

    3 | 12 |
    -------------------------------------------------------------------------------- /templates/apps/roll-dialog.html: -------------------------------------------------------------------------------- 1 | {{#*inline 'tag' tag }} 2 |
  • 3 | 9 |
  • 10 | {{/inline}} 11 | 12 |
    13 | 14 | 15 |
    16 |

    {{concat (localize "Litm.other.roll") " " (localize "Type")}}

    17 |
    {{radioBoxes "type" rollTypes checked=type localize=true}} 18 |
    19 |
    20 |
    21 | 27 |
    28 | 38 | {{#if isGM}} 39 |
    40 | 46 | {{/if}} 47 |
    48 |
    49 | 53 |
    54 |
    55 | {{localize "Litm.ui.total-power"}}: 56 | {{totalPower}} 57 |
    58 | 59 |
    60 | 64 | 65 |
    66 |
    -------------------------------------------------------------------------------- /templates/apps/story-tags.html: -------------------------------------------------------------------------------- 1 | {{#*inline 'tagItem' tag source editable}} 2 |
  • 6 | {{#if (eq tag.type "tag")}} 7 | 16 | {{/if}} 19 |
    20 | {{#each tag.values as |status|}} 24 | {{/each}} 25 |
    26 |
  • 27 | {{/inline}} {{#*inline 'add-button' id}} 28 | 33 | {{/inline}} 34 |
    35 |
    36 | 52 |
    53 |
    54 |
    55 | {{#each actors as |actor|}} 56 | 74 | {{else}} 75 | 80 | {{/each}} 81 |
    82 |
    -------------------------------------------------------------------------------- /templates/chat/message-tooltip.html: -------------------------------------------------------------------------------- 1 | {{#*inline "section" name value flavor flavorClass}} 2 |
    3 |
    4 | {{name}} 5 | {{#if flavor}}{{flavor}}{{/if}} 6 | {{value}} 7 |
    8 |
    9 | {{/inline}} 10 | 11 |
    12 |
    13 | {{#each parts}} 14 |
    15 |
    16 |
    17 | {{this.formula}} 18 | {{#if this.flavor}}{{this.flavor}}{{/if}} 19 | {{this.total}} 20 |
    21 |
      22 | {{#each this.rolls}} 23 |
    1. {{{this.result}}}
    2. 24 | {{/each}} 25 |
    26 |
    27 |
    28 | {{/each}} 29 | {{#each data.burnedTags as |tag|}} 30 | {{> section name=tag.name value=3 flavor=(localize 'Litm.tags.isBurnt') flavorClass="burned"}} 31 | {{/each}} 32 | {{#each data.powerTags as |tag|}} 33 | {{> section name=tag.name value=1 flavor=(ifThen (eq tag.type 'storyTag') (localize "Litm.other.tag") "") 34 | flavorClass=(ifThen (eq tag.type "storyTag") "warning" "")}} 35 | {{/each}} 36 | {{#if data.mitigate}} 37 | {{> section name=(localize 'Litm.effects.mitigate.key') value=1}} 38 | {{/if}} 39 | {{#each data.weaknessTags as |tag|}} 40 | {{> section name=tag.name value=-1 flavor=(ifThen (eq tag.type 'weaknessTag') (localize 'Litm.tags.weakness') 41 | (localize 'Litm.other.tag')) flavorClass=(ifThen (eq tag.type 'weaknessTag') "negative" "warning")}} 42 | {{/each}} 43 | {{#each data.positiveStatuses as |tag|}} 44 | {{> section name=tag.name value=tag.value flavor=(localize 'Litm.other.status') flavorClass="positive"}} 45 | {{/each}} 46 | {{#each data.negativeStatuses as |tag|}} 47 | {{> section name=tag.name value=(concat "-" tag.value) flavor=(localize 'Litm.other.status') 48 | flavorClass="positive"}} 49 | {{/each}} 50 | {{#if data.modifier}} 51 | {{> section name=(localize 'Litm.ui.modifier') value=data.modifier flavor="" flavorClass=""}} 52 | {{/if}} 53 |
    54 |
    -------------------------------------------------------------------------------- /templates/chat/message.html: -------------------------------------------------------------------------------- 1 |
    2 |
    {{flavor}}
    3 | {{#if effect}} 4 |
    5 |

    {{localize effect.description}}

    6 |

    {{localize effect.action}}

    7 |

    {{localize 'Litm.other.cost'}}: 8 | {{localize effect.cost}}

    9 |
    10 | {{else}} 11 |
    12 |

    {{{localize outcome.description}}}

    13 |
    14 | {{/if}} 15 |
    16 |
    17 | {{total}} 18 |
    19 | {{{tooltip}}} 20 |

    21 | {{#unless (eq type 'mitigate')}}{{localize outcome.label}}{{/unless}} 22 | {{#unless (eq type 'quick')}}{{power}} 23 | {{localize 'Litm.tags.power'}}{{/unless}} 24 |

    25 | {{#if (eq type 'tracked')}} 26 | {{#if (eq outcome.label 'consequence')}} 27 |

    28 | {{{localize "Litm.ui.roll-tracked-hint"}}} 29 |

    30 | {{/if}} 31 | {{#if (eq outcome.label 'success')}} 32 |

    33 | {{localize 'Litm.ui.roll-tracked-success'}} 34 |

    35 | {{/if}} 36 | {{/if}} 37 | {{#if (and isOwner hasBurnedTags)}} 38 | 39 | {{/if}} 40 | {{#if (and isOwner hasWeaknessTags)}} 41 | 42 | {{/if}} 43 |
    44 |
    -------------------------------------------------------------------------------- /templates/chat/moderation.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {{localize 'Litm.ui.moderate-roll-gm'}} 4 | {{localize 'Litm.ui.moderate-roll-player'}} 5 |
    6 |
    7 |

    {{name}} 9 | {{localize "Litm.ui.moderate-roll-gm-hint"}} 10 | {{localize (concat "Litm.ui.roll-" type)}} 11 | {{localize "Litm.other.outcome"}}.

    12 |

    13 | {{localize 'Litm.ui.moderate-roll-player-hint'}} 14 |

    15 |
    16 |
    17 | {{> systems/litm/templates/chat/message-tooltip.html data=tooltipData}} 18 |

    {{localize 'Litm.ui.total-power'}}: {{totalPower}} 19 |

    20 | 22 | 24 | 26 |
    27 |
    -------------------------------------------------------------------------------- /templates/item/backpack-ro.html: -------------------------------------------------------------------------------- 1 |
    2 | Soft illustration of a rustic backpack 4 | 6 |
    7 |
    {{localize 'Litm.tags.item-tags'}}
    8 |
    {{backpack.name}}
    9 | 14 |
    15 |
    -------------------------------------------------------------------------------- /templates/item/backpack.html: -------------------------------------------------------------------------------- 1 |
    2 | Soft illustration of a rustic backpack 4 | 6 |
    7 |
    {{localize 'Litm.tags.item-tags'}}
    8 | 9 | 14 | 15 |
    16 |
    -------------------------------------------------------------------------------- /templates/item/theme-ro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
    6 |
    7 | 8 |
    9 |
    10 | {{data.system.themebook}} 11 | {{localize data.system.level}} 12 | {{localize "TYPES.Item.theme"}} 13 |
    14 |
    15 | {{localize "Litm.tags.power"}} 16 | {{localize "Litm.other.tags"}}{{localize "Litm.tags.weakness"}} 17 | {{localize "Litm.other.tag"}} 18 |
    19 | 32 |
      33 | {{#each data.system.powerTags as |tag|}} {{> systems/litm/templates/partials/tag.html 34 | tag=tag key=@key path=(concat 'items.' ../data._id '.system.powerTags') embedded=true}} 35 | {{/each}} 36 |
    37 | 38 | {{#each data.system.weakness as |tag|}} 39 | 45 | {{/each}} 46 |
    47 | 48 |
    50 | {{#progress-buttons data.system.experience 3}} 51 | 52 | {{/progress-buttons}} 53 | 57 |
    58 | 59 |
    61 | {{#progress-buttons data.system.decay 3}} 62 | 63 | {{/progress-buttons}} 64 | 68 |
    69 |
    70 |
    71 |
    72 |
    73 | 74 |
    75 |
    76 | {{data.system.motivation}} 77 | 78 |
    79 |
    80 | {{editor data.system.note target='items.{{data._id}}.system.note' owner=owner 81 | editable=false}} 82 | 83 |
    84 |
    85 |
    86 |
    -------------------------------------------------------------------------------- /templates/item/theme.html: -------------------------------------------------------------------------------- 1 | 2 | {{#each data.system.themebooks as |themebook|}} 3 | 4 | {{/each}} 5 | 6 | 7 |
    8 |
    9 | 10 |
    11 |
    12 | 14 | 17 |
    18 |
    19 | {{localize "Litm.tags.power"}} 20 | {{localize "Litm.other.tags"}}{{localize "Litm.tags.weakness"}} 21 | {{localize "Litm.other.tag"}} 22 |
    23 |
    24 | 25 | 27 | 29 | 30 | 31 | {{title}} 32 | 33 | 39 |
    40 |
      41 | {{#each data.system.powerTags as |tag|}} 42 | {{> systems/litm/templates/partials/tag.html tag=tag key=@key path="system.powerTags"}} 43 | {{/each}} 44 |
    45 |
      46 | {{#each data.system.weakness as |tag|}} 47 |
    • 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 |
    • 57 | {{/each}} 58 |
    59 | 60 | 61 | 62 |
    63 | 64 |
    66 | {{#progress-buttons data.system.experience 3}} 67 | 68 | {{/progress-buttons}} 69 | 71 | 72 |
    73 | 74 |
    76 | {{#progress-buttons data.system.decay 3}} 77 | 78 | {{/progress-buttons}} 79 | 80 | 81 |
    82 |
    83 |
    84 |
    85 |
    86 | 87 |
    88 |
    89 | 90 | 92 | {{data.system.motivation}} 93 |
    94 |
    95 | {{editor data.system.note target="system.note" owner=owner button=true editable=true}} 96 |
    97 |
    98 |
    99 |
    -------------------------------------------------------------------------------- /templates/item/threat.html: -------------------------------------------------------------------------------- 1 | {{#*inline 'consequence' consequence key}} 2 | {{#if @root.isEditing}} 3 | {{consequence}} 6 | {{else}} 7 |
    8 | {{{consequence}}} 9 |
    10 | {{/if}} 11 | {{/inline}} 12 | 13 |
    14 |

    {{localize "Litm.other.threat"}}

    15 |
    > {{title}} 17 |
    18 | 19 | 28 |
    -------------------------------------------------------------------------------- /templates/partials/new-tag.html: -------------------------------------------------------------------------------- 1 |
    2 | 6 | 12 | 21 |
    -------------------------------------------------------------------------------- /templates/partials/tag.html: -------------------------------------------------------------------------------- 1 |
  • 3 | 4 | {{#if embedded}} 5 | 6 | 20 | {{else}} 21 | 22 | 23 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | {{/if}} 40 |
  • -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | VERSION=$(jq '.version + 1' system.json) 4 | 5 | npx -y @biomejs/biome check . --write || exit 1 6 | 7 | jq --tab --arg version "$VERSION" '.version = ($version | tonumber) | .download |= gsub("v[0-9]+"; "v" + $version)' system.json > temp.json 8 | mv temp.json system.json 9 | 10 | git commit -am "Release v$VERSION" 11 | git tag "v$VERSION" -m "v$VERSION" 12 | --------------------------------------------------------------------------------