├── .editorconfig ├── .github └── workflows │ ├── get-version.js │ └── main.yml ├── LICENSE ├── README.md ├── betterrolls5e ├── css │ ├── betterrolls5e.css │ └── heart-plus.svg ├── lang │ ├── cs.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── ja.json │ ├── ko.json │ ├── kr.json │ └── pt-BR.json ├── module.json ├── scripts │ ├── betterrolls5e.js │ ├── chat-message.js │ ├── custom-roll.js │ ├── extended-prompt.js │ ├── fields.js │ ├── index.js │ ├── item-tab.js │ ├── migration.js │ ├── patching │ │ ├── index.js │ │ └── libWrapper.js │ ├── renderer.js │ ├── settings.js │ └── utils │ │ ├── collection-functions.js │ │ ├── dice-collection.js │ │ ├── index.js │ │ ├── proxy.js │ │ └── utils.js └── templates │ ├── red-ae-button.html │ ├── red-damage-button.html │ ├── red-damage-crit.html │ ├── red-damageroll.html │ ├── red-description.html │ ├── red-footer.html │ ├── red-fullroll.html │ ├── red-header.html │ ├── red-item-options.html │ ├── red-multiroll.html │ ├── red-overlay-damage-crit-only.html │ ├── red-overlay-damage.html │ ├── red-overlay-header.html │ ├── red-overlay-multiroll.html │ └── red-save-button.html └── samples ├── animate-objects.js ├── defensive-flourish-ItemMacro.js ├── defensive-flourish.js └── life-cycle-test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | insert_final_newline = true 9 | max_line_length = 120 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/workflows/get-version.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | console.log(JSON.parse(fs.readFileSync('./betterrolls5e/module.json', 'utf8')).version); -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Module CI/CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Use Node.js 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | 19 | # create a zip file with all files required by the module to add to the release 20 | - name: Zip Files 21 | working-directory: ./betterrolls5e 22 | run: zip -r ./module.zip ./* 23 | 24 | # Get the version from 'module.json' 25 | - name: Get Version 26 | shell: bash 27 | id: get-version 28 | run: echo "::set-output name=version::$(node ./.github/workflows/get-version.js)" 29 | 30 | # Generate changelog for release body 31 | - name: Changelog 32 | id: Changelog 33 | uses: scottbrenner/generate-changelog-action@master 34 | env: 35 | REPO: ${{ github.repository }} 36 | 37 | # Create a release for this specific version 38 | - name: Create Release 39 | id: create_version_release 40 | uses: ncipollo/release-action@v1 41 | with: 42 | allowUpdates: true # set this to false if you want to prevent updating existing releases 43 | name: ${{ steps.get-version.outputs.version }} 44 | body: | 45 | ${{ steps.Changelog.outputs.changelog }} 46 | draft: false 47 | prerelease: false 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | artifacts: './betterrolls5e/module.json,./betterrolls5e/module.zip' 50 | tag_name: ${{ steps.get-version.outputs.version }} 51 | 52 | # Update the 'latest' release 53 | - name: Create Release 54 | id: create_latest_release 55 | uses: ncipollo/release-action@v1 56 | if: endsWith(github.ref, 'master') 57 | with: 58 | allowUpdates: true 59 | name: Latest 60 | draft: false 61 | prerelease: false 62 | token: ${{ secrets.GITHUB_TOKEN }} 63 | artifacts: './betterrolls5e/module.json,./betterrolls5e/module.zip' 64 | tag: latest 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is no longer being maintained. For alternatives, give https://github.com/MangoFVTT/fvtt-ready-set-roll-5e a try. 2 | 3 | # Better Rolls for 5e - A FoundryVTT Module 4 | A Foundry VTT module that replaces the built in rolling system for DnD5e. It allows for quick, compounded rolls for items, ability checks saving throws, and just about any roll you might want. Though initially a fork of Hooking's [Item Sheet Buttons](https://gitlab.com/hooking/foundry-vtt---item-sheet-buttons) module, it now includes several roll templates designed for Foundry's 5e sheets to increase speed of play. Felix's Chat Damage Buttons module has also been implemented into core Better Rolls. 5 | 6 | If you are feeling generous, and would like to support my work, you can do so through this [Paypal](https://www.paypal.me/RedReignDonate) link. Thank you! 7 | 8 | ## Incompatible Modules 9 | - Mars (replaces the core roller, competing directly with Better Rolls. Its one or the other) 10 | - Better NPC Sheet 5e (very out of date) 11 | 12 | #### Partially Compatible (Special Notes) 13 | - Midi QOL: more or less works, but make sure to enable fast forward attack in the midi options. If auto hit detection is enabled in midi, you'll need to use the query roll mode in Better Rolls to have more accurate results (dual mode / triple mode will throw it off as midi does not detect edits in better rolls). 14 | - J2BA Animations: Only for attack rolls. If set to play on damage rolls it won't work. 15 | 16 | ## Installation 17 | ### Method 1 18 | - Start up Foundry and click "Install Module" in the "Add-On Modules" tab. 19 | - Search for "Better Rolls" in the pop up window. 20 | - Click "Install" and it should appear in your modules list. 21 | - Enjoy! 22 | 23 | ### Method 2 24 | - Start up Foundry and click "Install Module" in the "Add-On Modules" tab. 25 | - Paste one of the following: 26 | - Latest release: `https://raw.githubusercontent.com/RedReign/FoundryVTT-BetterRolls5e/master/betterrolls5e/module.json` 27 | - The module.json listed in any of the releases (for either an older version or an alpha version) 28 | - Click "Install" and it should appear in your modules list. 29 | - Enjoy! 30 | 31 | ## Implemented Features 32 | ### Multirolls and Roll Modes 33 | Improved roll outputs into chat for efficiency. Pretentiously dubbed "Better Rolls", these compounded rolls can include dual d20 rolls for attack rolls, ability checks, and saving throws, but also damage rolls and automatic critical damage calculation. 34 | 35 | Custom d20 roll modes includes Single, Dual, Triple, and Query Dialog rolls. Single mode will roll double in the case of advantage and disadvantage and can be edited after they're rolled into advantage or disadvantage by mousing over and clicking the [-]/[+] buttons. Rolls with advantage or disadvantage highlight the correct roll, indicating which roll is used. 36 | 37 | Details of the roll are based on the fields present in the item clicked. 38 | 39 | ![](https://i.imgur.com/DyzMi2A.png) 40 | 41 | ### Roll Editing 42 | Chat messages are condensed and are edited live. Attack is grouped together with damage. Single rolls can be updated to advantage or disadvantage, and damage can either be auto-rolled or prompted with a button. 43 | 44 | ![](https://user-images.githubusercontent.com/1286721/103615288-529fea80-4ef8-11eb-95cf-490e86084c5e.gif) 45 | 46 | ### Sheet Buttons 47 | For additional control, sheet buttons are displayed in the character sheet's item summary, allowing the sheet to quickly output whatever is needed (Attack & damage rolls combined, attack & alternate damage, just attack, just damage...) 48 | 49 | ![](https://i.imgur.com/uFvpDPw.png) 50 | ![](https://i.imgur.com/2kNCHdZ.png) 51 | 52 | ### Alt Rolls and Roll Configuration 53 | Damage rolls have an additional context field to convey what the damage comes from, or when it occurs. 54 | 55 | ![](https://i.imgur.com/L9NTE7G.png) 56 | 57 | Rolls can also be configured in the Better Rolls item tab while editing an item. Items have two roll modes: normal and alt quick rolls. Alt Quick Rolls, can be used by holding Alt when clicking the item's icon in the character sheet. 58 | 59 | ![](https://i.imgur.com/Od15JXz.png) 60 | ![](https://i.imgur.com/yPzgzEe.png) 61 | 62 | Extended support for thrown items, consumables, ammunition, and items with otherwise limited uses. 63 | 64 | ![](https://i.imgur.com/yQpSJgb.png) 65 | 66 | ### Additional Features 67 | - Need for clicking through prompts in order to get a single roll is removed, allowing for ease of use. 68 | - Per-item options for showing the item's description on a quick roll. 69 | - Per-item critical threshold. 70 | - Configurable options for changing sheet outputs and labels for both roll sets and damage type. 71 | - Localization support - now comes with full Japanese and Korean translations! 72 | 73 | ![](https://i.imgur.com/Wd0iT0E.png) 74 | ![](https://cdn.discordapp.com/attachments/513918036919713802/635495803787542559/unknown.png) 75 | 76 | ### Macro Support 77 | - Macro support! Try dragging and dropping an item, spell, or feat from your character sheet onto the macro hotbar! 78 | - Script macros are also intuitive enough to be entered manually. 79 | - Try `BetterRolls.quickRoll("Shortbow");` on a creature with an item named "Shortbow", or `BetterRolls.quickRollByName("Sharon", "Shortbow");` to fire Sharon's shortbow. 80 | - Check out the samples folder for some example macros. 81 | 82 | ![](https://i.imgur.com/fMMWz3m.gif) 83 | 84 | ## Planned Features 85 | - Additional macro support 86 | - Extended prompts to configure messages on a roll-by-roll basis 87 | - Additional hooks support and chat message flags for module cross-compatibility 88 | 89 | ## Known Issues 90 | - In versions prior to 1.1.12, there exists a bug where, if used alongside tidy5e, Actor data may increase exponentially. This has since been addressed in 1.1.12. **If you are using Foundry Virtual Tabletop 0.7.0 or higher, please update to Better Rolls 1.1.12.** 91 | 92 | ## Acknowledgements 93 | - Big thanks to Atropos for making a wonderful VTT that's worth making modules for! 94 | - CarlosFdez (Supe on discord) is the current maintainer and has done a great deal of work cleaning up the module's code and implementing new, useful features. Thanks for all your hard work! 95 | - Thanks are also due to Hooking for the initial Item Sheet Buttons module, without which this project would not exist. 96 | - Thank you, Felix#6196 for making a wonderful extension of Chat Damage Buttons reconfigured for this module. 97 | - Thank you, Brother Sharp#6921 for providing the Japanese localization for this module. 98 | - Thank you, KLO#1490 for providing the Korean localization for this module. 99 | - Thank you, Cosmo Corban#4840 for providing the Spanish localization for this module. 100 | - Thank you, Innocenti#1455 for providing the Portuguese localization for this module. 101 | - Thank you, Olirin#0350 for providing the French localization for this module. 102 | - Thank you, Acd-Jake#9087 for providing the German localization for this module. 103 | - Additional thanks go to KaKaRoTo, tposney, and Giddy of the Foundry discord for advice and assistance while developing and maintaining this module. 104 | - My gratitude extends also to all the folks of the Foundry VTT community for their endless wisdom and insight. 105 | 106 | ## License 107 | The source code is licensed under GPL-3.0. 108 | Some icons are from Game-icons.net under CC-BY 109 | -------------------------------------------------------------------------------- /betterrolls5e/css/betterrolls5e.css: -------------------------------------------------------------------------------- 1 | .dnd5e.sheet .item-buttons button { 2 | font-size: 10px; 3 | line-height: 12px; 4 | margin: 0; 5 | } 6 | 7 | .dnd5e.sheet .item-buttons span { 8 | padding: 0; 9 | } 10 | 11 | .dnd5e.chat-card .card-header img { 12 | flex: 0 0 36px; 13 | margin-right: 5px; 14 | object-fit: contain; 15 | } 16 | 17 | .dnd5e.chat-card.red-full .card-header { 18 | position: relative; 19 | } 20 | 21 | .red-dual .dice-row { 22 | position: relative; 23 | clear: both; 24 | display: flex; 25 | justify-content: space-between; 26 | } 27 | 28 | .red-dual .dice-row .dice-row-item { 29 | flex: 1; 30 | } 31 | 32 | .red-dual .tooltip.dice-row-item { 33 | padding: 0px 4px 0px 4px; 34 | } 35 | 36 | .red-dual .dice-row .failure { 37 | color: #aa0200; 38 | } 39 | 40 | .red-dual .dice-row .success { 41 | color: #257f11; 42 | } 43 | 44 | .red-full .dice-roll .dice-row .dice-row-item.ignored { 45 | opacity: .4; 46 | border: 1px solid #888888; 47 | } 48 | 49 | .dnd5e.red-full.chat-card .dice-tooltip { 50 | font-size:14px; 51 | } 52 | 53 | .red-dual .mixed { 54 | color: #11257f; 55 | } 56 | 57 | .red-dual .die-icon { 58 | background-image: url(../../../icons/svg/d20-grey.svg); 59 | width: 22px; 60 | line-height: 22px; 61 | background-size: 22px; 62 | background-repeat: no-repeat; 63 | float: left; 64 | font-weight: bold; 65 | font-size: 13px; 66 | text-align: center; 67 | opacity: 0.95; 68 | 69 | position: absolute; 70 | top: 50%; 71 | right: 5px; 72 | transform: translate(0, -50%); 73 | } 74 | 75 | .red-dual .success .die-icon { 76 | filter: sepia(0.5) hue-rotate(60deg); 77 | } 78 | 79 | .red-dual .failure .die-icon { 80 | filter: sepia(0.5) hue-rotate(-60deg); 81 | } 82 | 83 | .br5e-roll-label { 84 | text-align: center; 85 | font-size: 12px; 86 | } 87 | 88 | .br5e-consume-box { 89 | background: rgba(0, 0, 0, 0.05); 90 | border: 1px solid #999; 91 | border-radius: 4px; 92 | box-shadow: 0 0 2px #fff inset; 93 | display: flex; 94 | flex-flow: row wrap; 95 | justify-content: space-between; 96 | } 97 | 98 | .tidy5e .br5e-consume-box { 99 | flex-direction: column; 100 | } 101 | 102 | .tidy5e .br5e-consume-container { 103 | display: flex; 104 | flex-direction: row; 105 | } 106 | 107 | .br5e-consume-title { 108 | padding-left: 6px; 109 | font-size: 13px; 110 | } 111 | 112 | .red-full .br-flavor { 113 | font-style: italic; 114 | } 115 | 116 | .red-full .inline { 117 | display: inline; 118 | } 119 | 120 | .red-full .damage-type { 121 | text-align: center; 122 | font-size: 12px; 123 | } 124 | 125 | .red-full hr { 126 | margin: 8px 0; 127 | border-top: 1px dashed #b5b3a4; 128 | border-bottom: 1px dashed #f0f0e0; 129 | } 130 | 131 | .dnd5e.red-full.chat-card footer.card-footer { 132 | margin-top:2px; 133 | } 134 | 135 | .dnd5e.item form .item-betterRolls { 136 | padding: 8px 8px 0; 137 | overflow-y: auto; 138 | } 139 | 140 | .dnd5e.sheet.item .item-betterRolls input[type="text"], 141 | .dnd5e.sheet.item .item-betterRolls input[type="number"], 142 | .dnd5e.sheet.item .item-betterRolls select { 143 | border: 1px solid #7a7971; 144 | background: rgba(0, 0, 0, 0.05); 145 | } 146 | 147 | .dnd5e.sheet.item .item-betterRolls label.checkbox { 148 | display: inline-table; 149 | padding: 0px 8px; 150 | } 151 | 152 | .card-buttons .adice5e.dmg.rolled { 153 | display:inline-flex; 154 | width:100%; 155 | align-items:baseline; 156 | } 157 | 158 | .die-result-overlay-br { 159 | text-align: right!important; 160 | flex-grow: 1; 161 | position: absolute; 162 | left: 0; 163 | right: 0; 164 | top: 0; 165 | bottom: 0; 166 | padding: 0 2px; 167 | 168 | display: flex; 169 | justify-content: space-between; 170 | align-items: center; 171 | z-index: 1; 172 | } 173 | 174 | .die-result-overlay-br span { 175 | display: flex; 176 | } 177 | 178 | .die-result-overlay-br button { 179 | width: 20px; 180 | height: 20px; 181 | font-size: 10px; 182 | line-height: 1px; 183 | background-color: white; 184 | border: 1px solid #999; 185 | padding: 0; 186 | } 187 | 188 | .die-result-overlay-br button.icon { 189 | background-size: 14px; 190 | background-repeat: no-repeat; 191 | background-position: center; 192 | } 193 | 194 | .header-overlay-br { 195 | left: auto; 196 | justify-content: right; 197 | } 198 | 199 | .header-overlay-br button { 200 | width: 30px; 201 | height: 30px; 202 | font-size: 14px; 203 | } 204 | 205 | .icon.heart-plus { 206 | background-image: url("../../../modules/betterrolls5e/css/heart-plus.svg"); 207 | } 208 | 209 | .die-result-overlay-br button i { 210 | margin: 0; 211 | } 212 | 213 | .better-npc-sheet .item.expanded .item-summary .item-buttons { 214 | line-height: 22px; 215 | } 216 | 217 | .br5e-hidden { 218 | display: none; 219 | } 220 | -------------------------------------------------------------------------------- /betterrolls5e/css/heart-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /betterrolls5e/lang/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Red Reign"], 3 | 4 | "Better Rolls": "Better Rolls", 5 | "Better Rolls for 5e": "Better Rolls pro 5e", 6 | 7 | "br5e.migrating": "Aplikování Migrace Better Rolls Systému pro verzi {version}", 8 | "br5e.rollButtonsEnabled.name": "Přidat Tlačítka Hodů do Deníku", 9 | "br5e.rollButtonsEnabled.hint": "Přidá k předmětům, kouzlům a schopnostem v deníku tlačítka, která se zobrazí, když je objekt rozbalen. Není zaručena kompatibilita s modulem Item Sheet Buttons. Vyžaduje znovu otevření deníku.", 10 | "br5e.imageButtonEnabled.name": "Obrázek Předmětu Spustí Auto-hod", 11 | "br5e.imageButtonEnabled.hint": "Při kliknutí na obrázek předmětu se namísto normální zprávy v chatu vypíše zpráva Better Roll. Toto je možné obejít stisknutím klávesy Alt při kliknutí.", 12 | "br5e.quickDefaultDescriptionEnabled.name": "Popisek u Rychlého Hodu Zapnut jako Výchozí", 13 | "br5e.quickDefaultDescriptionEnabled.hint": "Pokud je zvoleno, zbraně a nářadí mají během rychlého hodu zobrazen svůj popisek. Aplikuje se pouze na nové zbraně a nářadí, jelikož ostatní typy předmětu mají zobrazení popisků již jako výchozí nastavení.", 14 | "br5e.defaultRollArt.name": "Použít Výchozí Grafiku", 15 | "br5e.defaultRollArt.hint": "Určuje výchozí grafiku u ověření vlastnosti a záchrannných hodů. Pokud ikona není k dispozici (nebo se jedná o výchozí ikonu tajemného muže), bude místo ni použita druhá možnost.", 16 | "br5e.damageContextEnabled.name": "Kontextové Štítky Zranění", 17 | "br5e.damageContextEnabled.hint": "Umožňuje pro štítky zranění vložit kontext, který bude předcházet typ zranění. Funguje pouze u Better Rolls.", 18 | "br5e.contextReplacesTitle.name": "Kontext Nahrazuje Nadpis", 19 | "br5e.contextReplacesTitle.hint": "Pokud je kontextový popisek na stejném místě jako nadpis hodu, např. \"Zranění\" nebo \"Léčení\", nadpis bude nahrazen.", 20 | "br5e.contextReplacesDamage.name": "Kontext Nahrazuje Typ Zranění", 21 | "br5e.contextReplacesDamage.hint": "Pokud je kontextový popisek na stejném místě jako popisek typu zranění, typ zranění bude nahrazen.", 22 | "br5e.rollTitlePlacement.name": "Zobrazit Nadpis Hodu", 23 | "br5e.rollTitlePlacement.hint": "Určuje, zda se má zobrazit nadpis nezraňujícího hodu, jako např. \"Útok\".", 24 | "br5e.damageTitlePlacement.name": "Zobrazit Nadpis Zranění", 25 | "br5e.damageTitlePlacement.hint": "Určuje, zda se má zobrazit nadpis hodu na zranění, jako např. \"Zranění\".", 26 | "br5e.damageContextPlacement.name": "Zobrazit Popisek Kontextu Zranění", 27 | "br5e.damageContextPlacement.hint": "Určuje, zda a kde se má zobrazit popisek kontextu hodu na zranění.", 28 | "br5e.damageRollPlacement.name": "Zobrazit Popisek Typu Zranění", 29 | "br5e.damageRollPlacement.hint": "Určuje, kam se umístí popisek typu zranění, relativně vůči hodnu na zranění.", 30 | "br5e.damageRollPlacement.choices.0": "Nezobrazovat", 31 | "br5e.damageRollPlacement.choices.1": "Nad", 32 | "br5e.damageRollPlacement.choices.2": "Pod a Uvnitř", 33 | "br5e.damageRollPlacement.choices.3": "Pod a Vně", 34 | "br5e.altSecondaryEnabled.name": "Alt-Klik pro Sekundární Hod", 35 | "br5e.altSecondaryEnabled.hint": "Při Alt-Kliknutí na obrázek předmětu se v chatu objeví sekundární Better Roll zpráva, kterou je možno nakonfigurovat pro každý předmět zvlášť. Zruší možnost použití standardního chatového výstupu.", 36 | "br5e.chatDamageButtonsEnabled.name": "Zapnout Tlačítka Zranění v Chatu", 37 | "br5e.chatDamageButtonsEnabled.hint": "Vykreslí u Better Rolls hodů na zranění tlačítka umožňující aplikovat zranění vybraným tokenům. Vyžaduje znovu načíst Foundry.", 38 | "br5e.playRollSounds.name": "Zapnout Zvuky Hodů", 39 | "br5e.playRollSounds.hint": "Zahraje zvuk hodu kostek, jakmile je zpráva Better Rolls poslána do chatu. Nespustí se, pokud je aktivován modul Maestro's Item Track.", 40 | "br5e.hideDC.name": "Skrýt SO Záchranných Hodu", 41 | "br5e.hideDC.hint": "Určuje, zda bude u Better Rolls záchranného hodu skryt Stupeň Obtížnosti.", 42 | "br5e.hideDC.string": "??", 43 | "br5e.hideDC.choices.0": "Nikdy", 44 | "br5e.hideDC.choices.1": "Pouze CP", 45 | "br5e.hideDC.choices.2": "Vždy", 46 | "br5e.damagePromptEnabled.name": "Výzva ke Zranění v Kartě Chatu", 47 | "br5e.damagePromptEnabled.hint": "Neprovést automaticky hod na zranění, namísto toho zobrazit tlačítko pro zranění pokud se jedná o hod na útok nebo záchranný hod.", 48 | "br5e.d20RollIconsEnabled.name": "Zobrazit ikonu K20 kostky pro vícenásobné hody", 49 | "br5e.d20RollIconsEnabled.hint": "Pokud je aktivováno, útoky, ověření a záchranné hody zobrazují přirozený hod kostkou", 50 | "br5e.applyActiveEffects.name": "Zobrazit Tlačítko Aktivních Efektů DAE", 51 | "br5e.applyActiveEffects.hint": "Zobrazit tlačítko pro aplikování aktivních efektů. Je vyžadován DAE modul. Pokud DAE není nainstalován, tato volba nemá žádný efekt.", 52 | 53 | "br5e.d20Mode.name": "Režim k20", 54 | "br5e.d20Mode.hint": "Určuje, jak jsou hody na útok, ověření vlastnosti a záchranné hody zobrazeny. Pokud je nastaveno na Jeden Hod Vylepšovací, po najetí kurzorem zobrazí tlačítka [-]/[+] pro výhodu a nevýhodu.", 55 | "br5e.d20Mode.choices.1": "Jeden Hod Vylepšovací (Shift Výh., Ctrl Nevýh.)", 56 | "br5e.d20Mode.choices.2": "Dvojitý Hod", 57 | "br5e.d20Mode.choices.3": "ゴゴ Trojí Ohrožení ゴゴ", 58 | "br5e.d20Mode.choices.4": "Dotázat se na (Ne)Výhodu", 59 | 60 | "br5e.critBehavior.name": "Krytické Zranění", 61 | "br5e.critBehavior.hint": "Určuje, jak je vypočteno krytické zranění.", 62 | "br5e.critBehavior.choices.0": "Žádné Extra Zranění", 63 | "br5e.critBehavior.choices.1": "Hodit Kostkou Kritického Zranění", 64 | "br5e.critBehavior.choices.2": "Hodit Základní Zranění, Maximální Kritické", 65 | "br5e.critBehavior.choices.3": "Maximální Základní & Maximílní Kritické Zranění", 66 | "br5e.critBehavior.choices.4": "Maximální Základní Zranění, Hodit Kritické Zranění", 67 | 68 | "br5e.critString.name": "Kritický Indikátor", 69 | "br5e.critString.hint": "Určuje, jak jsou krytické hody označeny. Zobrazí se jako text vpravo od hodu kritického zranění. Funguje pouze pro Better Rolls.", 70 | "br5e.critString.choices.2": "Krit", 71 | "br5e.critString.choices.3": "Krit!", 72 | "br5e.critString.choices.4": "(Krit)", 73 | "br5e.critString.choices.5": "Kritický", 74 | "br5e.critString.choices.6": "Kritický!", 75 | "br5e.critString.choices.7": "(Kritický)", 76 | "br5e.critString.choices.8": "BUMMMM!", 77 | 78 | "br5e.error.noSelectedActor": "Musíš mít vybranou postavu, aby jsi mohl použít toto makro!", 79 | "br5e.error.noKnownItemOnActor": "nevlastní předmět jménem", 80 | "br5e.error.noActorWithId": "Neznámý aktor s ID", 81 | "br5e.error.noActorWithName": "Žádný aktor takového jména není znám", 82 | "br5e.error.noItemWithId": "Není znám žádný aktorův předmět s takovým ID", 83 | "br5e.error.noChargesLeft": "Tento předmět vyčerpal všechna svá použití!", 84 | "br5e.error.autoDestroy": "Jakmile bylo spotřebováno poslední použití, {name} se rozplynul.", 85 | "br5e.error.rollEvaluation": "Vyhodnocení hodu kostkou selhalo: {msg}", 86 | "br5e.error.noDAE": "Pro aplikovaní aktivních evektů cílům je vyžadován DAE", 87 | 88 | "br5e.settings.critThreshold": "Kritická Hranice", 89 | "br5e.settings.critDamage.name": "Extra Krytické Zranění", 90 | "br5e.settings.critDamage.hint": "Extra Kritické Zranění, které se má aplikovat pokud je hozeno alespoň jedno zranění", 91 | "br5e.settings.quickRollsLabel": "Rychlé Hody", 92 | "br5e.settings.quickRollsAltLabel": "Alt Rychlé Hody", 93 | "br5e.settings.quickRollsAltSubLabel": "(Když držíš Alt během rychlého hodu)", 94 | "br5e.settings.description": "Popis", 95 | "br5e.settings.attackRoll": "Hod na Útok", 96 | "br5e.settings.attackAndSave": "Útok / Záchranný", 97 | "br5e.settings.saveDC": "SO Záchranného Hodu", 98 | "br5e.settings.baseDamage": "Základní Zranění", 99 | "br5e.settings.altDamage": "Alternativní Zranění", 100 | "br5e.settings.properties": "Vlastnosti", 101 | "br5e.settings.check": "Ověření", 102 | "br5e.settings.consume": "Spotřebovat", 103 | "br5e.settings.consumeQuantity": "Kvantita", 104 | "br5e.settings.consumeUses": "Použití", 105 | "br5e.settings.consumeResource": "Zdroj", 106 | "br5e.settings.consumeRecharge": "Nabití Akce", 107 | "br5e.settings.useTemplate": "Umístit Předlohu", 108 | "br5e.settings.label": "Popisek", 109 | "br5e.settings.context": "Kontext", 110 | "br5e.settings.otherFormula": "Vlastní Vzorec", 111 | "br5e.settings.quickFlavor": "Doplňkový text", 112 | "br5e.settings.prompt": "Rozšířená výzva", 113 | 114 | "br5e.buttons.roll": "Hod", 115 | "br5e.buttons.altRoll": "Alt. Hod", 116 | "br5e.buttons.attack": "Útok", 117 | "br5e.buttons.saveDC": "SO Záchr. Hodu", 118 | "br5e.buttons.saveAndDamage": "Hod (Záchr. & Zranění)", 119 | "br5e.buttons.attackAndDamage": "Hod (Útok & Zranění)", 120 | "br5e.buttons.damage": "Zranění", 121 | "br5e.buttons.altDamage": "Alt. Zranění", 122 | "br5e.buttons.extraDamage": "Extra Zranění", 123 | "br5e.buttons.verDamage": "Zranění Všestrannosti", 124 | "br5e.buttons.info": "Info", 125 | "br5e.buttons.itemUse": "Použít", 126 | "br5e.buttons.defaultSheetRoll": "Výchozí Hod Deníku", 127 | "br5e.buttons.applyActiveEffects": "Aplikovat Aktivní Efekty", 128 | 129 | "br5e.chat.attack": "Útok", 130 | "br5e.chat.check": "Ověření", 131 | "br5e.chat.save": "Záchranný", 132 | "br5e.chat.damage": "Zranění", 133 | "br5e.chat.healing": "Léčení", 134 | "br5e.chat.altPrefix": "Alt.", 135 | "br5e.chat.extraPrefix": "Extra", 136 | "br5e.chat.consumedBySpell": "Spotřebováno kouzlem", 137 | "br5e.chat.abrVocal": "V", 138 | "br5e.chat.abrSomatic": "P", 139 | "br5e.chat.abrMaterial": "S", 140 | "br5e.chat.other": "Jiné", 141 | "br5e.chat.advantage": "Výhoda", 142 | "br5e.chat.disadvantage": "Nevýhoda", 143 | "br5e.chat.normal": "Normální", 144 | 145 | "br5e.chatContext.repeat": "Opakovat hod", 146 | 147 | "br5e.querying.title": "Jaký typ hodu?", 148 | "br5e.querying.disadvantage": "Nevýhoda", 149 | "br5e.querying.normal": "Normální", 150 | "br5e.querying.advantage": "Výhoda", 151 | 152 | "br5e.chat.multiRollButtons.advantage.hint": "Hodit s Výhodou (Shift)", 153 | "br5e.chat.multiRollButtons.disadvantage.hint": "Hodit s Nevýhodou (Ctrl)", 154 | 155 | "br5e.chat.damageButtons": { 156 | "fullDamage.hint": "Klikni pro aplikování celého zranění vybraným tokenům.", 157 | "quarterDamage.hint": "Klikni pro aplikování čtvrtinového zranění vybraným tokenům.", 158 | "halfDamage.hint": "Klikni pro aplikování polovičního zranění vybraným tokenům.", 159 | "doubleDamage.hint": "Klikni pro aplikování dvojitého zranění vybraným tokenům.", 160 | "healing.hint": "Klikni pro aplikování celého léčení vybraným tokenům.", 161 | "crit.hint": "Klikni pro hození kritického zranění.", 162 | "tempOverwrite.title": "Přepsat Dočasné Životy", 163 | "tempOverwrite.content": "Nahradit {original} dočasných životů {new} novými dočasnými životy?", 164 | "critPrompt.title": "Použít Kritické Zranění?", 165 | "critPrompt.yes": "Ano", 166 | "critPrompt.no": "Ne" 167 | }, 168 | 169 | "Ability Roll": "Hod na Vlastnost", 170 | "What type of roll?": "Jaký typ hodu?", 171 | "Ability Check": "Ověření Vlastnosti", 172 | "Saving Throw": "Záchranný Hod", 173 | 174 | "Not Proficient": "Nezdatný", 175 | "Proficient": "Zdatný", 176 | "Jack of all Trades": "Všeuměl", 177 | "Expertise": "Kvalifikace", 178 | "Ritual": "Rituál", 179 | "Target: ": "Cíl: ", 180 | "Concentration": "Koncentrace", 181 | "AC": "OČ", 182 | "Equipped": "Vybaveno", 183 | "Stealth Disadv.": "Nevýh. Nenápadnosti" 184 | } 185 | -------------------------------------------------------------------------------- /betterrolls5e/lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Red Reign"], 3 | 4 | "Better Rolls": "Besser Würfeln", 5 | "Better Rolls for 5e": "Besser Würfeln für 5e", 6 | 7 | "br5e.rollButtonsEnabled.name": "Würfelbuttons zum Blatt hinzufügen", 8 | "br5e.rollButtonsEnabled.hint": "Fügt Buttons zu Gegenständen, Zaubern und Merkmalen auf dem Blatt hinzu, die angezeigt werden, wenn der Gegenstand erweitert wird. Möglicherweise inkompatibel mit den Buttons des Modules \"Item Sheet Buttons\". Erfordert ein erneutes Öffnen des Blattes.", 9 | "br5e.imageButtonEnabled.name": "Element-Bild automatisch würfeln lassen", 10 | "br5e.imageButtonEnabled.hint": "Beim Klicken auf das Bild eines Elements eine \"Besser Würfeln\"-Nachricht im Chat anstelle der normalen Chat-Ausgabe ausgeben. Kann durch Halten der Alt-Taste beim Klicken umgangen werden.", 11 | "br5e.quickDefaultDescriptionEnabled.name": "Schnellwürfeln-Beschreibung standardmäßig aktiviert", 12 | "br5e.quickDefaultDescriptionEnabled.hint": "Wenn diese Option aktiviert ist, werden die Beschreibungen von Waffen und Werkzeugen standardmäßig beim Schnellwürfeln angezeigt. Betrifft nur neue Waffen und Werkzeuge, da bei anderen Gegenstandsarten die Beschreibungen bereits standardmäßig aktiviert sind.", 13 | "br5e.defaultRollArt.name": "Benutze Standardbilder", 14 | "br5e.defaultRollArt.hint": "Legt fest, welche Bilder standardmäßig für Attributsproben und Rettungswürfe verwendet werden. Wenn dieses nicht verfügbar ist (oder das standardmäßige Mystery-Man-Symbol ist), wird stattdessen das andere Symbol verwendet.", 15 | "br5e.damageContextEnabled.name": "Beschriftungen von Schadenskontexten", 16 | "br5e.damageContextEnabled.hint": "Ermöglicht die Eingabe des Kontexts für Schadensbeschriftungen, die der Schadensart vorausgehen. Funktioniert nur bei besseren Rollen.", 17 | "br5e.contextReplacesTitle.name": "Kontext ersetzt Titelbeschriftung", 18 | "br5e.contextReplacesTitle.hint": "Wenn sich eine Kontextbeschriftung an der gleichen Stelle wie die Titelbeschriftung einer Schadenswurfes befindet, wie z.B. \"Schaden\" oder \"Heilung\", wird sie diese ersetzen.", 19 | "br5e.contextReplacesDamage.name": "Kontext ersetzt Schadensart", 20 | "br5e.contextReplacesDamage.hint": "Wenn sich eine Kontextbeschriftung an der gleichen Stelle wie eine Schadensart-Beschriftung befindet, wird sie diese ersetzen.", 21 | "br5e.rollTitlePlacement.name": "Wurftitel anzeigen", 22 | "br5e.rollTitlePlacement.hint": "Bestimmt, ob der Titel eines nicht beschädigten Wurfes, wie z.B. \"Angriff\", erscheint.", 23 | "br5e.damageTitlePlacement.name": "Schadenstitel anzeigen", 24 | "br5e.damageTitlePlacement.hint": "Bestimmt, ob der Titel eines Schadenswurfes, wie z.B. \"Schaden\", erscheint.", 25 | "br5e.damageRollPlacement.name": "Schadensart anzeigen", 26 | "br5e.damageRollPlacement.hint": "Bestimmt, wo die Schadensart relativ zu einem Schadenswurf platziert wird.", 27 | "br5e.damageRollPlacement.choices.0": "Nicht anzeigen", 28 | "br5e.damageRollPlacement.choices.1": "Oberhalb", 29 | "br5e.damageRollPlacement.choices.2": "Unter- & Innerhalb", 30 | "br5e.damageRollPlacement.choices.3": "Unter- & Außerhalb", 31 | "br5e.altSecondaryEnabled.name": "Alt-Klick auf das Bild für sekundären Wurf ", 32 | "br5e.altSecondaryEnabled.hint": "Wenn man mit der Alt-Taste auf das Bild eines Elements klickt, wird eine zweite \"Besser Würfeln\"-Nachricht ausgegeben, die für jedes Element separat konfiguriert werden kann. Überschreibt die Umgehungsmöglichkeit der Standard-Chatausgabe.", 33 | "br5e.chatDamageButtonsEnabled.name": "Schadenbuttons im Chat aktivieren", 34 | "br5e.chatDamageButtonsEnabled.hint": "Zeigt Buttons auf \"Besser Würfeln\"-Schadenswürfen an, die Schaden auf ausgewählte Spielfiguren anwenden. Erfordert Neuladen von Foundry.", 35 | "br5e.playRollSounds.name": "Würfelgeräusche aktivieren", 36 | "br5e.playRollSounds.hint": "Spielt einen Würfelton, wenn eine \"Besser Würfeln\"-Nachricht an den Chat gesendet wird. Wird nicht gespielt, wenn die der Item-Track von Maestro aktiviert ist.", 37 | "br5e.hideDC.name": "Verberge SGs von Rettungswürfen", 38 | "br5e.hideDC.hint": "Legt fest, ob der SG bei den Buttons für Rettungswürfe in \"Besser Würfeln\" ausgeblendet ist.", 39 | "br5e.hideDC.string": "??", 40 | "br5e.hideDC.choices.0": "Niemals", 41 | "br5e.hideDC.choices.1": "Nur bei NSCs", 42 | "br5e.hideDC.choices.2": "Immer", 43 | 44 | "br5e.d20Mode.name": "d20 Modus", 45 | "br5e.d20Mode.hint": "Legt fest, wie Angriffswürfe, Attributsproben und Rettungswürfe angezeigt werden.", 46 | "br5e.d20Mode.choices.1": "Einzelner Wurf (Shift-Vorteil, Strg-Nachteil)", 47 | "br5e.d20Mode.choices.2": "Dualer Wurf", 48 | "br5e.d20Mode.choices.3": "ゴゴ Dreifache Bedrohung ゴゴ", 49 | 50 | "br5e.critBehavior.name": "Kritischer Schaden", 51 | "br5e.critBehavior.hint": "Bestimmt, wie der kritische Schaden berechnet wird.", 52 | "br5e.critBehavior.choices.0": "Kein zusätzlicher Schaden", 53 | "br5e.critBehavior.choices.1": "Würfeln mit \"Kritischer Schaden\"-Würfel", 54 | "br5e.critBehavior.choices.2": "Basisschaden würfeln, Kritschen Schaden maximieren", 55 | "br5e.critBehavior.choices.3": "Basisschaden & kritischen Schaden maximieren", 56 | 57 | "br5e.critString.name": "Hinweis auf kritischen Treffer", 58 | "br5e.critString.hint": "Legt fest, wie kritische Treffer gekennzeichnet werden. Erscheint als Text auf der rechten Seite des kritischen Schadenswurfes. Funktioniert nur mit \"Besser Würfeln\".", 59 | "br5e.critString.choices.2": "Krit", 60 | "br5e.critString.choices.3": "Krit!", 61 | "br5e.critString.choices.4": "(Krit)", 62 | "br5e.critString.choices.5": "Kritisch", 63 | "br5e.critString.choices.6": "Kritisch!", 64 | "br5e.critString.choices.7": "(Kritisch)", 65 | "br5e.critString.choices.8": "BAMMMM!", 66 | 67 | "br5e.error.noSelectedActor": "Es muss ein Charakter ausgewählt sein, um dieses Makro zu verwenden!", 68 | "br5e.error.noKnownItemOnActor": "besitzt keinen Gegenstand namens", 69 | "br5e.error.noActorWithId": "Es ist kein Akteur mit dieser ID bekannt", 70 | "br5e.error.noActorWithName": "Kein Akteur mit diesem Namen ist bekannt", 71 | "br5e.error.noItemWithId": "Es ist kein Element mit dieser ID beim Akteur bekannt", 72 | "br5e.error.noChargesLeft": "Dieser Gegenstand hat keine Ladungen mehr!", 73 | "br5e.error.autoDestroy": "Der Gegenstand verschwindet, wenn seine letzte Ladung verbraucht ist.", 74 | 75 | "br5e.settings.critThreshold": "Kritischer Grenzwert", 76 | "br5e.settings.critDamage.name": "Zusätzlicher kritischer Schaden", 77 | "br5e.settings.critDamage.hint": "Besonders kritischer Schaden, der anzuwenden ist, wenn mindestens ein Schaden gewürfelt wird", 78 | "br5e.settings.quickRollsLabel": "Schnellwürfeln", 79 | "br5e.settings.quickRollsAltLabel": "Alternatives Schnellwürfeln", 80 | "br5e.settings.quickRollsAltSubLabel": "(Wenn die Alt-Taste während des Schnellwürfelns gedrückt wird)", 81 | "br5e.settings.description": "Beschreibung", 82 | "br5e.settings.attackRoll": "Angriffswurf", 83 | "br5e.settings.attackAndSave": "Angriff / Rettungswurf", 84 | "br5e.settings.saveDC": "Rettungswurf SG", 85 | "br5e.settings.baseDamage": "Basisschaden", 86 | "br5e.settings.altDamage": "Alternativer Schaden", 87 | "br5e.settings.properties": "Eigenschaften", 88 | "br5e.settings.check": "Probe", 89 | "br5e.settings.consumeCharge": "Ladung verbrauchen", 90 | "br5e.settings.useTemplate": "Schablone plazieren", 91 | "br5e.settings.label": "Beschriftung", 92 | "br5e.settings.context": "Kontext", 93 | "br5e.settings.otherFormula": "Andere Formel", 94 | "br5e.settings.quickFlavor": "Zusätzlicher Text", 95 | 96 | "br5e.buttons.roll": "Wurf", 97 | "br5e.buttons.altRoll": "Alt. Wurf", 98 | "br5e.buttons.attack": "Angriff", 99 | "br5e.buttons.saveDC": "Rettungswurf SG", 100 | "br5e.buttons.saveAndDamage": "Wurf (RW & Schaden)", 101 | "br5e.buttons.attackAndDamage": "Wurg (Angriff & Schaden)", 102 | "br5e.buttons.damage": "Schaden", 103 | "br5e.buttons.altDamage": "Alt. Schaden", 104 | "br5e.buttons.extraDamage": "Zusätzl. Schaden", 105 | "br5e.buttons.verDamage": "Vielseitiger Schaden", 106 | "br5e.buttons.info": "Info", 107 | "br5e.buttons.itemUse": "Benutzen", 108 | "br5e.buttons.defaultSheetRoll": "Standard Blattwurf", 109 | 110 | "br5e.chat.attack": "Angriff", 111 | "br5e.chat.check": "Probe", 112 | "br5e.chat.save": "Rettungswurf", 113 | "br5e.chat.damage": "Schaden", 114 | "br5e.chat.healing": "Heilung", 115 | "br5e.chat.altPrefix": "Alt.", 116 | "br5e.chat.extraPrefix": "Zusätzl.", 117 | "br5e.chat.consumedBySpell": "Verbraucht durch Zauber", 118 | "br5e.chat.abrVocal": "V", 119 | "br5e.chat.abrSomatic": "G", 120 | "br5e.chat.abrMaterial": "M", 121 | "br5e.chat.other": "Andere", 122 | "br5e.chat.advantage": "Vorteil", 123 | "br5e.chat.disadvantage": "Nachteil", 124 | 125 | "br5e.chat.damageButtons.fullDamage.hint": "Anklicken, um den vollen Schaden auf die ausgewählte(n) Spielfigur(en) anzuwenden.", 126 | "br5e.chat.damageButtons.halfDamage.hint": "Anklicken, um den halbierten Schaden auf die ausgewählte(n) Spielfigur(en) anzuwenden.", 127 | "br5e.chat.damageButtons.doubleDamage.hint": "Anklicken, um den doppelten Schaden auf die ausgewählte(n) Spielfigur(en) anzuwenden.", 128 | "br5e.chat.damageButtons.healing.hint": "Anklicken, um die volle Heilung auf die ausgewählte(n) Spielfigur(en) anzuwenden.", 129 | "br5e.chat.damageButtons.critPrompt.title": "Krit. Schaden anwenden?", 130 | "br5e.chat.damageButtons.critPrompt.yes": "Ja", 131 | "br5e.chat.damageButtons.critPrompt.no": "Nein", 132 | 133 | "Ability Roll": "Attributswürfe", 134 | "What type of roll?": "Welche Art des Wurfes?", 135 | "Ability Check": "Attributsprobe", 136 | "Saving Throw": "Rettungswurf", 137 | 138 | "Not Proficient": "Nich geübt", 139 | "Proficient": "Geübt", 140 | "Jack of all Trades": "Alleskönner", 141 | "Expertise": "Expertise", 142 | "Ritual": "Ritual", 143 | "Target: ": "Ziel: ", 144 | "Concentration": "Konzentration", 145 | "AC": "RK", 146 | "Equipped": "Ausgerüstet", 147 | "Stealth Disadv.": "Heimlichkeitsnachteil" 148 | } 149 | -------------------------------------------------------------------------------- /betterrolls5e/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Red Reign"], 3 | 4 | "Better Rolls": "Better Rolls", 5 | "Better Rolls for 5e": "Better Rolls for 5e", 6 | 7 | "br5e.migrating": "Applying Better Rolls System Migration for version {version}", 8 | "br5e.rollButtonsEnabled.name": "Add Extra Roll Buttons to Sheet", 9 | "br5e.rollButtonsEnabled.hint": "Adds buttons to items, spells, and features in the sheet, which display when the item is expanded. May be incompatible with the Item Sheet Buttons mod. Requires reopening the sheet.", 10 | "br5e.imageButtonEnabled.name": "Make Item Image Auto-roll", 11 | "br5e.imageButtonEnabled.hint": "When clicking on an item's image, output a Better Roll message to chat instead of the normal chat output. Can be bypassed by holding Alt when clicking.", 12 | "br5e.quickDefaultDescriptionEnabled.name": "Quick Roll Description Enabled by Default", 13 | "br5e.quickDefaultDescriptionEnabled.hint": "If enabled, weapons and tools will have their descriptions show by default on quick rolls. Only affects new weapons and tools, as other item types already have descriptions enabled by default.", 14 | "br5e.defaultRollArt.name": "Actor Roll Image Used", 15 | "br5e.defaultRollArt.hint": "Determines which art is used by default for ability checks and saving throws. If this is not available (or is the default mystery man icon), it will use the other icon instead.", 16 | "br5e.damageContextEnabled.name": "Damage Context Labels", 17 | "br5e.damageContextEnabled.hint": "Allows entering context for damage labels, which precede the damage type. Only works on Better Rolls.", 18 | "br5e.contextReplacesTitle.name": "Context Replaces Title Label", 19 | "br5e.contextReplacesTitle.hint": "If a context label is in the same place as a damage roll's title label, such as \"Damage\" or \"Healing\", it will replace it.", 20 | "br5e.contextReplacesDamage.name": "Context Replaces Damage Type", 21 | "br5e.contextReplacesDamage.hint": "If a context label is in the same place as a damage type label, it will replace it.", 22 | "br5e.rollTitlePlacement.name": "Show Roll Title Labels", 23 | "br5e.rollTitlePlacement.hint": "Determines if a non-damage roll's title, such as \"Attack\", appear.", 24 | "br5e.damageTitlePlacement.name": "Show Damage Title Labels", 25 | "br5e.damageTitlePlacement.hint": "Determines if a damage roll's title, such as \"Damage\", appears.", 26 | "br5e.damageContextPlacement.name": "Show Damage Context Labels", 27 | "br5e.damageContextPlacement.hint": "Determines if and where a damage roll's context label appears.", 28 | "br5e.damageRollPlacement.name": "Show Damage Type Labels", 29 | "br5e.damageRollPlacement.hint": "Determines where the damage type label is placed, relative to a damage roll.", 30 | "br5e.damageRollPlacement.choices.0": "Don't Show", 31 | "br5e.damageRollPlacement.choices.1": "Above", 32 | "br5e.damageRollPlacement.choices.2": "Below & Inside", 33 | "br5e.damageRollPlacement.choices.3": "Below & Outside", 34 | "br5e.altSecondaryEnabled.name": "Alt-Click Image for Secondary Roll", 35 | "br5e.altSecondaryEnabled.hint": "When Alt-clicking on an item's image, output a second Better Roll message which can be configured separately per-item. Overwrites ability to bypass with default chat output.", 36 | "br5e.playRollSounds.name": "Enable Roll Sounds", 37 | "br5e.playRollSounds.hint": "Plays a dice-rolling sound when a Better Rolls message is sent to chat. Wlll not play if Maestro's Item Track are enabled.", 38 | "br5e.hideDC.name": "Hide Save DCs", 39 | "br5e.hideDC.hint": "Determines if the DC is hidden on save DC buttons on Better Rolls.", 40 | "br5e.hideDC.string": "??", 41 | "br5e.hideDC.choices.0": "Never", 42 | "br5e.hideDC.choices.1": "NPCs Only", 43 | "br5e.hideDC.choices.2": "Always", 44 | "br5e.damagePromptEnabled.name": "Damage Button In Chat Card", 45 | "br5e.damagePromptEnabled.hint": "Do not autoroll damage, instead show a damage button if there is an attack roll or saving throw.", 46 | "br5e.d20RollIconsEnabled.name": "Show D20 die icon", 47 | "br5e.d20RollIconsEnabled.hint": "If enabled, attacks, checks, and saves show natural die roll", 48 | "br5e.applyActiveEffects.name": "Show DAE Active Effects Button", 49 | "br5e.applyActiveEffects.hint": "Show button to apply active effects. The DAE module is required. If DAE is not installed, this option will not do anything.", 50 | 51 | "br5e.d20Mode.name": "d20 Mode", 52 | "br5e.d20Mode.hint": "Determines how attack rolls, ability checks, and saving throws are shown. If set to Single Roll, also enables [-]/[+] roll overlay buttons for advantage and disadvantage.", 53 | "br5e.d20Mode.choices.1": "Single Roll Upgradeable (Shift Adv, Ctrl Disadv)", 54 | "br5e.d20Mode.choices.2": "Dual Rolls", 55 | "br5e.d20Mode.choices.3": "ゴゴ Triple Threat ゴゴ", 56 | "br5e.d20Mode.choices.4": "Query for (Dis)Advantage", 57 | 58 | "br5e.critBehavior.name": "Critical Damage Behavior", 59 | "br5e.critBehavior.hint": "Determines how critical damage is calculated. Currently, all base formula maximization is added mathematically to the critical damage segment.", 60 | "br5e.critBehavior.choices.0": "No Extra Damage", 61 | "br5e.critBehavior.choices.1": "Roll Critical Damage Dice", 62 | "br5e.critBehavior.choices.2": "Roll Base Damage, Max Critical", 63 | "br5e.critBehavior.choices.3": "Max Base & Max Critical Damage", 64 | "br5e.critBehavior.choices.4": "Max Base Damage, Roll Critical Damage", 65 | 66 | "br5e.critString.name": "Critical Indicator", 67 | "br5e.critString.hint": "Determines how criticals are labeled. Appears as text to the right of the critical damage roll. Only works on Better Rolls.", 68 | "br5e.critString.choices.2": "Crit", 69 | "br5e.critString.choices.3": "Crit!", 70 | "br5e.critString.choices.4": "(Crit)", 71 | "br5e.critString.choices.5": "Critical", 72 | "br5e.critString.choices.6": "Critical!", 73 | "br5e.critString.choices.7": "(Critical)", 74 | "br5e.critString.choices.8": "BAMMMM!", 75 | 76 | "br5e.chatDamageButtonsEnabled": { 77 | "name": "Enable Apply Damage Buttons", 78 | "hint": "Renders buttons on Better Rolls damage rolls that apply damage to selected tokens. Requires refreshing Foundry.", 79 | "choices": { 80 | "0": "Disabled", 81 | "1": "Enabled", 82 | "2": "DM Only" 83 | } 84 | }, 85 | 86 | "br5e.error.noSelectedActor": "Must have a character selected to use this macro!", 87 | "br5e.error.noKnownItemOnActor": "does not own an item named", 88 | "br5e.error.noActorWithId": "No actor is known with that ID", 89 | "br5e.error.noActorWithName": "No actor is known with that name", 90 | "br5e.error.noItemWithId": "No item is known with that ID on actor", 91 | "br5e.error.noChargesLeft": "This item has run out of charges!", 92 | "br5e.error.autoDestroy": "The {name} disappears as its last charge is consumed.", 93 | "br5e.error.rollEvaluation": "Dice roll evaluation failed: {msg}", 94 | "br5e.error.noDAE": "DAE is required to apply active effects to targets", 95 | "br5e.error.libWrapperMinVersion": "Better Rolls requires libWrapper version {version} or newer.", 96 | "br5e.error.noVersatile": "Attempt to roll versatile damage when none was set", 97 | 98 | "br5e.settings.critThreshold": "Critical Threshold", 99 | "br5e.settings.critDamage.name": "Extra Critical Damage", 100 | "br5e.settings.critDamage.hint": "Extra Critical Damage to apply when at least one damage is rolled", 101 | "br5e.settings.quickRollsLabel": "Quick Rolls", 102 | "br5e.settings.quickRollsAltLabel": "Alt Quick Rolls", 103 | "br5e.settings.quickRollsAltSubLabel": "(When holding Alt during a quick roll)", 104 | "br5e.settings.description": "Description", 105 | "br5e.settings.attackRoll": "Attack Roll", 106 | "br5e.settings.attackAndSave": "Attack / Save", 107 | "br5e.settings.saveDC": "Save DC", 108 | "br5e.settings.baseDamage": "Base Damage", 109 | "br5e.settings.altDamage": "Alternate Damage", 110 | "br5e.settings.properties": "Properties", 111 | "br5e.settings.check": "Check", 112 | "br5e.settings.consume": "Consume", 113 | "br5e.settings.consumeQuantity": "Quantity", 114 | "br5e.settings.consumeUses": "Uses", 115 | "br5e.settings.consumeResource": "Resource", 116 | "br5e.settings.consumeRecharge": "Action Charge", 117 | "br5e.settings.useTemplate": "Place Template", 118 | "br5e.settings.label": "Label", 119 | "br5e.settings.context": "Context", 120 | "br5e.settings.otherFormula": "Other Formula", 121 | "br5e.settings.quickFlavor": "Flavor", 122 | "br5e.settings.prompt": "Extended Prompt", 123 | 124 | "br5e.buttons.roll": "Roll", 125 | "br5e.buttons.altRoll": "Alt. Roll", 126 | "br5e.buttons.attack": "Attack", 127 | "br5e.buttons.saveDC": "Save DC", 128 | "br5e.buttons.saveAndDamage": "Roll (Save & Dmg)", 129 | "br5e.buttons.attackAndDamage": "Roll (Atk & Dmg)", 130 | "br5e.buttons.damage": "Damage", 131 | "br5e.buttons.altDamage": "Alt. Damage", 132 | "br5e.buttons.extraDamage": "Extra Damage", 133 | "br5e.buttons.verDamage": "Versatile Damage", 134 | "br5e.buttons.info": "Info", 135 | "br5e.buttons.itemUse": "Use", 136 | "br5e.buttons.defaultSheetRoll": "Default Sheet Roll", 137 | "br5e.buttons.applyActiveEffects": "Apply Active Effects", 138 | 139 | "br5e.chat.attack": "Attack", 140 | "br5e.chat.check": "Check", 141 | "br5e.chat.save": "Save", 142 | "br5e.chat.damage": "Damage", 143 | "br5e.chat.healing": "Healing", 144 | "br5e.chat.altPrefix": "Alt.", 145 | "br5e.chat.extraPrefix": "Extra", 146 | "br5e.chat.consumedBySpell": "Consumed by spell", 147 | "br5e.chat.abrVocal": "V", 148 | "br5e.chat.abrSomatic": "S", 149 | "br5e.chat.abrMaterial": "M", 150 | "br5e.chat.other": "Other", 151 | "br5e.chat.advantage": "Advantage", 152 | "br5e.chat.disadvantage": "Disadvantage", 153 | "br5e.chat.normal": "Normal", 154 | 155 | "br5e.chatContext.repeat": "Repeat the roll", 156 | 157 | "br5e.querying.title": "What type of roll?", 158 | "br5e.querying.disadvantage": "Disadvantage", 159 | "br5e.querying.normal": "Normal", 160 | "br5e.querying.advantage": "Advantage", 161 | 162 | "br5e.chat.multiRollButtons.advantage.hint": "Roll with Advantage (Shift)", 163 | "br5e.chat.multiRollButtons.disadvantage.hint": "Roll with Disadvantage (Ctrl)", 164 | 165 | "br5e.chat.damageButtons": { 166 | "fullDamage.hint": "Click to apply full damage to selected token(s).", 167 | "quarterDamage.hint": "Click to apply quarter damage to selected token(s).", 168 | "halfDamage.hint": "Click to apply half damage to selected token(s).", 169 | "doubleDamage.hint": "Click to apply double damage to selected token(s).", 170 | "healing.hint": "Click to apply full healing to selected token(s).", 171 | "crit.hint": "Click to roll crit damage.", 172 | "tempOverwrite.title": "Overwrite Temporary Health", 173 | "tempOverwrite.content": "Replace {original} temporary health with {new} new temporary health?", 174 | "critPrompt.title": "Use Crit Damage?", 175 | "critPrompt.yes": "Yes", 176 | "critPrompt.no": "No" 177 | }, 178 | 179 | "Ability Roll": "Ability Roll", 180 | "What type of roll?": "What type of roll?", 181 | "Ability Check": "Ability Check", 182 | "Saving Throw": "Saving Throw", 183 | 184 | "Not Proficient": "Not Proficient", 185 | "Proficient": "Proficient", 186 | "Jack of all Trades": "Jack of all Trades", 187 | "Expertise": "Expertise", 188 | "Ritual": "Ritual", 189 | "Target: ": "Target: ", 190 | "Concentration": "Concentration", 191 | "AC": "AC", 192 | "Equipped": "Equipped", 193 | "Stealth Disadv.": "Stealth Disadv." 194 | } 195 | -------------------------------------------------------------------------------- /betterrolls5e/lang/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Cosmo Corban"], 3 | 4 | "Better Rolls": "Better Rolls", 5 | "Better Rolls for 5e": "Better Rolls para 5e", 6 | 7 | "br5e.migrating": "Aplicación de la migración del sistema de tirada para la versión {versión}", 8 | "br5e.rollButtonsEnabled.name": "Añadir Botones de Tiradas a la Hoja", 9 | "br5e.rollButtonsEnabled.hint": "Añade botones a los elementos, conjuros y rasgos en la hoja de personaje, estos se muestran al expandir el elemento. Puede que no sea compatible con el mod 'Item Sheet Buttons'. Requiere reabrir la hoja.", 10 | "br5e.imageButtonEnabled.name": "Convertir Imágen en Auto-Tirada", 11 | "br5e.imageButtonEnabled.hint": "Cuando hagas clic en la imagen de algún elemento, se enviará un mensaje de 'Better Rolls' al chat en vez del mensaje predeterminado. Se puede evitar manteniendo Alt al hacer clic.", 12 | "br5e.quickDefaultDescriptionEnabled.name": "Descripción en las Tiradas Rápidas. Habilitado por Defecto", 13 | "br5e.quickDefaultDescriptionEnabled.hint": "Si se habilita, las armas y herramientas mostrarán sus descripciones por defecto en las tiradas rápidas. Sólo afecta a nuevas armas y herramientas, ya que otros tipos de elementos ya tienen descripciones habilitadas por defecto.", 14 | "br5e.defaultRollArt.name": "Imagen utilizada en tirada de Personaje", 15 | "br5e.defaultRollArt.hint": "Determina qué arte se utiliza por defecto para las pruebas de habilidad y tiradas de salvación. Si no está disponible (o es el icono por defecto), se utilizará el otro icono en su lugar.", 16 | "br5e.damageContextEnabled.name": "Etiquetas de Contexto de Daño", 17 | "br5e.damageContextEnabled.hint": "Permite ingresar contexto para las etiquetas de daño, que preceden al tipo de daño. Solo funciona en Better Rolls.", 18 | "br5e.contextReplacesTitle.name": "Contexto Reemplaza la Etiqueta de Título", 19 | "br5e.contextReplacesTitle.hint": "Si una etiqueta de contexto se encuentra en el mismo lugar que la etiqueta del título de una tirada de daño, como \"Daño \" o \"Curación \", esta la reemplazará.", 20 | "br5e.contextReplacesDamage.name": "Contexto Reemplaza el Tipo de Daño", 21 | "br5e.contextReplacesDamage.hint": "Si una etiqueta de contexto se encuentra en el mismo lugar que una etiqueta de tipo de daño, esta la reemplazará.", 22 | "br5e.rollTitlePlacement.name": "Mostrar Título de Tiradas", 23 | "br5e.rollTitlePlacement.hint": "Determina si en una tirada que no sea de daño aparecerá el título, tal como \"Ataque\".", 24 | "br5e.damageTitlePlacement.name": "Mostrar Título de Daño", 25 | "br5e.damageTitlePlacement.hint": "Determina si en una tirada de daño aparecerá el título, tal como \"Daño\".", 26 | "br5e.damageContextPlacement.name": "Mostrar etiquetas de contexto del daño", 27 | "br5e.damageContextPlacement.hint": "Determina si la etiqueta de contexto de una tirada de daño aparece y dónde.", 28 | "br5e.damageRollPlacement.name": "Mostrar Etiquetas de Tipo de Daño", 29 | "br5e.damageRollPlacement.hint": "Determina dónde se mostrará la etiqueta del tipo de daño, en relación con una tirada de daño.", 30 | "br5e.damageRollPlacement.choices.0": "No mostrar", 31 | "br5e.damageRollPlacement.choices.1": "Encima", 32 | "br5e.damageRollPlacement.choices.2": "Debajo y Adentro", 33 | "br5e.damageRollPlacement.choices.3": "Debajo y Afuera", 34 | "br5e.altSecondaryEnabled.name": "Alt-Clic en Imagen para Tirada Secundaria", 35 | "br5e.altSecondaryEnabled.hint": "Cuando hagas Alt-Clic en la imagen de un elemento, se mostrará un mensaje de chat de Better Rolls diferente al normal que puede ser configurado de manera independiente para cada elemento. Sobreescribe la posibilidad de enviar la tirada por defecto de DnD5e.", 36 | "br5e.playRollSounds.name": "Habilitar Sonido en las Tiradas", 37 | "br5e.playRollSounds.hint": "Reproduce un sonido de tirada de dados cuando se envía un mensaje de Better Rolls al chat. No se reproducirá si está habilitado el sonido de elementos del módulo \"Maestro\".", 38 | "br5e.hideDC.name": "Esconder CDs de Salvación", 39 | "br5e.hideDC.hint": "Elija si se muestra o no la Clase de Dificultad en los botones de CD de Salvación de Better Rolls.", 40 | "br5e.hideDC.string": "??", 41 | "br5e.hideDC.choices.0": "Nunca", 42 | "br5e.hideDC.choices.1": "Solo PNJs", 43 | "br5e.hideDC.choices.2": "Siempre", 44 | "br5e.damagePromptEnabled.name": "Botón de daño en la tarjeta de chat", 45 | "br5e.damagePromptEnabled.hint": "No hacer una tirada de daño automática, en su lugar mostrar un botón de daño si hay una tirada de ataque o de salvación.", 46 | "br5e.d20RollIconsEnabled.name": "Mostrar Icono de dado D20", 47 | "br5e.d20RollIconsEnabled.hint": "Si está activada, los ataques, pruebas y salvaciones muestran la tirada de un dado natural", 48 | "br5e.applyActiveEffects.name": "Mostrar Botón para efectos activos de DAE", 49 | "br5e.applyActiveEffects.hint": "Muestra el botón para aplicar los efectos activos. El módulo DAE es necesario. Si DAE no está instalado, esta opción no hará nada.", 50 | 51 | "br5e.d20Mode.name": "Modo d20", 52 | "br5e.d20Mode.hint": "Determina como se muestran las tiradas de ataque, pruebas de habilidad y tiradas de salvación.", 53 | "br5e.d20Mode.choices.1": "Tirada Individual (Shift Ventaja, Ctrl Desventaja)", 54 | "br5e.d20Mode.choices.2": "Tirada Doble", 55 | "br5e.d20Mode.choices.3": "ゴゴ Triple Amenaza ゴゴ", 56 | "br5e.d20Mode.choices.4": "Consultar para (des)ventaja", 57 | 58 | "br5e.critBehavior.name": "Daño Crítico", 59 | "br5e.critBehavior.hint": "Determina como se calcula el daño crítico.", 60 | "br5e.critBehavior.choices.0": "Sin Daño Adicional", 61 | "br5e.critBehavior.choices.1": "Tirar dado de Crítico", 62 | "br5e.critBehavior.choices.2": "Tirar Daño Base, Max. Crítico", 63 | "br5e.critBehavior.choices.3": "Max. Daño Base y Crítico", 64 | "br5e.critBehavior.choices.4": "Daño Base Máximo, Tirada de Daño Crítico", 65 | 66 | "br5e.critString.name": "Indicador de Crítico", 67 | "br5e.critString.hint": "Determina cómo se etiquetan los críticos. Aparece como texto a la derecha de la tirada de daño crítico. Solo funciona en Better Rolls.", 68 | "br5e.critString.choices.2": "Crít", 69 | "br5e.critString.choices.3": "Crít!", 70 | "br5e.critString.choices.4": "(Crít)", 71 | "br5e.critString.choices.5": "Crítico", 72 | "br5e.critString.choices.6": "Crítico!", 73 | "br5e.critString.choices.7": "(Crítico)", 74 | "br5e.critString.choices.8": "BAMMMM!", 75 | 76 | "br5e.chatDamageButtonsEnabled": { 77 | "name": "Activar los botones para aplicar daños", 78 | "hint": "Muestra botones en las tiradas de daño de Better Rolls que aplican el daño a las fichas seleccionadas. Requiere refrescar Foundry.", 79 | "choices": { 80 | "0": "Desactivado", 81 | "1": "Activado", 82 | "2": "Sólo DM" 83 | } 84 | }, 85 | 86 | "br5e.error.noSelectedActor": "¡Debe tener un personaje seleccionado para usar esta macro!", 87 | "br5e.error.noKnownItemOnActor": "no posee un elemento llamado", 88 | "br5e.error.noActorWithId": "No existe ningún personaje con esa ID", 89 | "br5e.error.noActorWithName": "No existe ningún personaje con ese nombre", 90 | "br5e.error.noItemWithId": "No existe ningún elemento con esa ID en el personaje", 91 | "br5e.error.noChargesLeft": "¡Este elemento se ha quedado sin cargas!", 92 | "br5e.error.autoDestroy": "El(La) {name} desaparece cuando se consume su última carga.", 93 | "br5e.error.rollEvaluation": "La evaluación de la tirada de dados falló: {msg}", 94 | "br5e.error.noDAE": "DAE es necesario para aplicar los efectos activos a los objetivos", 95 | "br5e.error.libWrapperMinVersion": "Better Rolls requiere libWrapper versión {version} o más reciente.", 96 | 97 | "br5e.settings.critThreshold": "Umbral para Crítico", 98 | "br5e.settings.critDamage.name": "Daño Crítico Extra", 99 | "br5e.settings.critDamage.hint": "Daño crítico extra que se aplica cuando se tira al menos un daño", 100 | "br5e.settings.quickRollsLabel": "Tiradas Rápidas", 101 | "br5e.settings.quickRollsAltLabel": "Tiradas Rápidas Alternativas", 102 | "br5e.settings.quickRollsAltSubLabel": "(Cuando se mantiene Alt en una tirada rápida)", 103 | "br5e.settings.description": "Descripción", 104 | "br5e.settings.attackRoll": "Tirada de Ataque", 105 | "br5e.settings.attackAndSave": "Ataque / Salvación", 106 | "br5e.settings.saveDC": "CD de Salvación", 107 | "br5e.settings.baseDamage": "Daño Base", 108 | "br5e.settings.altDamage": "Daño Alternativo", 109 | "br5e.settings.properties": "Propiedades", 110 | "br5e.settings.check": "Prueba", 111 | "br5e.settings.consume": "Consumir", 112 | "br5e.settings.consumeQuantity": "Cantidad", 113 | "br5e.settings.consumeUses": "Usos", 114 | "br5e.settings.consumeResource": "Recurso", 115 | "br5e.settings.consumeRecharge": "Carga por Acción", 116 | "br5e.settings.useTemplate": "Colocar Plantilla", 117 | "br5e.settings.label": "Etiqueta", 118 | "br5e.settings.context": "Contexto", 119 | "br5e.settings.otherFormula": "Otra Fórmula", 120 | "br5e.settings.quickFlavor": "Mensaje Adicional", 121 | "br5e.settings.prompt": "Pregunta Ampliada", 122 | 123 | "br5e.buttons.roll": "Tirar", 124 | "br5e.buttons.altRoll": "Tirar Alt.", 125 | "br5e.buttons.attack": "Ataque", 126 | "br5e.buttons.saveDC": "CD de Salvación:", 127 | "br5e.buttons.saveAndDamage": "Tirar (Salv. y Daño)", 128 | "br5e.buttons.attackAndDamage": "Tirar (Atq. y Daño)", 129 | "br5e.buttons.damage": "Daño", 130 | "br5e.buttons.altDamage": "Daño Alt.", 131 | "br5e.buttons.extraDamage": "Daño Extra", 132 | "br5e.buttons.verDamage": "Daño Versátil", 133 | "br5e.buttons.info": "Info", 134 | "br5e.buttons.itemUse": "Usar", 135 | "br5e.buttons.defaultSheetRoll": "Tirada por Defecto", 136 | "br5e.buttons.applyActiveEffects": "Aplicar efectos activos", 137 | 138 | "br5e.chat.attack": "Ataque", 139 | "br5e.chat.check": "Prueba", 140 | "br5e.chat.save": "Salvación", 141 | "br5e.chat.damage": "Daño", 142 | "br5e.chat.healing": "Curación", 143 | "br5e.chat.altPrefix": "Alt.", 144 | "br5e.chat.extraPrefix": "Extra", 145 | "br5e.chat.consumedBySpell": "Consumido po conjuro", 146 | "br5e.chat.abrVocal": "V", 147 | "br5e.chat.abrSomatic": "S", 148 | "br5e.chat.abrMaterial": "M", 149 | "br5e.chat.other": "Otro", 150 | "br5e.chat.advantage": "Ventaja", 151 | "br5e.chat.disadvantage": "Desventaja", 152 | "br5e.chat.normal": "Normal", 153 | 154 | "br5e.chatContext.repeat": "Repite la tirada", 155 | 156 | "br5e.querying.title": "¿Qué tipo de tirada?", 157 | "br5e.querying.disadvantage": "Desventaja", 158 | "br5e.querying.normal": "Normal", 159 | "br5e.querying.advantage": "Ventaja", 160 | 161 | "br5e.chat.multiRollButtons.advantage.hint": "Tirar con ventaja (Shift)", 162 | "br5e.chat.multiRollButtons.disadvantage.hint": "Tirar con desventaja (Ctrl)", 163 | 164 | "br5e.chat.damageButtons": { 165 | "fullDamage.hint": "Haz clic para aplicar todo el daño a la(s) ficha(s) seleccionada(s).", 166 | "quarterDamage.hint": "Haz clic para aplicar un cuarto de daño a la(s) ficha(s) seleccionada(s).", 167 | "halfDamage.hint": "Haz clic para aplicar la mitad del daño a la(s) ficha(s) seleccionada(s).", 168 | "doubleDamage.hint": "Haz clic para aplicar el doble de daño a la(s) ficha(s) seleccionada(s).", 169 | "healing.hint": "Haz clic para aplicar la curación completa a la(s) ficha(s) seleccionada(s).", 170 | "crit.hint": "Haz clic para tirar el daño crítico.", 171 | "tempOverwrite.title": "Sobrescribir Salud Temporal", 172 | "tempOverwrite.content": "¿Sustituir la salud temporal {original} por la nueva salud temporal {new}?", 173 | "critPrompt.title": "¿Usar Daño Crítico?", 174 | "critPrompt.yes": "Sí", 175 | "critPrompt.no": "No" 176 | }, 177 | 178 | "Ability Roll": "Tirada de Característica", 179 | "What type of roll?": "¿Tipo de Tirada?", 180 | "Ability Check": "Prueba de Característica", 181 | "Saving Throw": "Tirada de Salvación", 182 | 183 | "Not Proficient": "No Competente", 184 | "Proficient": "Competente", 185 | "Jack of all Trades": "Aprendiz de Mucho", 186 | "Expertise": "Pericia", 187 | "Ritual": "Ritual", 188 | "Target: ": "Objetivo: ", 189 | "Concentration": "Concentración", 190 | "AC": "CA", 191 | "Equipped": "Equipado", 192 | "Stealth Disadv.": "Desv. de Sigilo" 193 | } 194 | -------------------------------------------------------------------------------- /betterrolls5e/lang/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Olirin"], 3 | 4 | "Better Rolls": "Better Rolls", 5 | "Better Rolls for 5e": "Better Rolls pour DD5e", 6 | 7 | "br5e.rollButtonsEnabled.name": "Ajout des boutons à la feuille", 8 | "br5e.rollButtonsEnabled.hint": "Ajoute des boutons aux objets, sorts et dons dans la feuille, qui s'affichent quand l'objet est agrandis. Possible incompatibilité avec le mode Item Sheet Buttons. Requière la réouverture de la feuille.", 9 | "br5e.imageButtonEnabled.name": "Jet automatique grâce à l'image", 10 | "br5e.imageButtonEnabled.hint": "En cliquant sur l'image de l'objet, afficher le message de better roll au lieu du message normal. Peut être contourneé avec la touche Alt.", 11 | "br5e.quickDefaultDescriptionEnabled.name": "Description de Quick Roll activée par défaut", 12 | "br5e.quickDefaultDescriptionEnabled.hint": "Si activé, les armées et outils auront leur description affichée par défaut. N'affecte seulement que les armes et les outils.", 13 | "br5e.damageContextEnabled.name": "Etiquette des dégâts ", 14 | "br5e.damageContextEnabled.hint": "Autorise l'ajout d'un contexte pour l'étiquette des dégâts, avant le type des dégâts. Ne fonctionne qu’avec Better Rolls.", 15 | "br5e.contextReplacesTitle.name": "Le contexte remplace l'étiquette", 16 | "br5e.contextReplacesTitle.hint": "Si une étiquette occupe la même place que l'étiquette des dégâts, tel que \"Damage\" ou \"Healing\", Il le remplacera.", 17 | "br5e.contextReplacesDamage.name": "Contexte remplace le type de dégâts", 18 | "br5e.contextReplacesDamage.hint": "Si une étiquette occupe la même place que l'étiquette des dégâts, il la remplacera.", 19 | "br5e.rollTitlePlacement.name": "Afficher l'étiquette des jets", 20 | "br5e.rollTitlePlacement.hint": "Détermine si un jet de dès entraînant pas de dégâts, telle que \"Attack\", apparaît.", 21 | "br5e.damageTitlePlacement.name": "Montrer l'étiquette des dégâts", 22 | "br5e.damageTitlePlacement.hint": "Déterminer si le titre d'un jet de dégâts tels que \"Damage\", apparaît.", 23 | "br5e.damageRollPlacement.name": "Montrer l'étiquette du type de dégâts", 24 | "br5e.damageRollPlacement.hint": "Détermine où l'étiquette est placée par rapport au jet de dégâts.", 25 | "br5e.damageRollPlacement.choices.0": "Ne pas afficher", 26 | "br5e.damageRollPlacement.choices.1": "Au-dessus", 27 | "br5e.damageRollPlacement.choices.2": "En dessous et à l'intérieur", 28 | "br5e.damageRollPlacement.choices.3": "En dessous et à l'extérieur", 29 | "br5e.altSecondaryEnabled.name": "Alt clique sur l'image pour le jet secondaire", 30 | "br5e.altSecondaryEnabled.hint": "Avec un Alt clique sur l'image, ouvre un message secondaire qui peut être configuré séparément. contourne les réglages par défaut du chat.", 31 | "br5e.chatDamageButtonsEnabled.name": "Activer les boutons de dégâts du chat", 32 | "br5e.chatDamageButtonsEnabled.hint": "Activer les boutons de Better Rolls pour appliquer les dégâts sur le token sélectionné. Requière un rafraîchissement.", 33 | "br5e.playRollSounds.name": "Activer les sons", 34 | "br5e.playRollSounds.hint": "Activer un son de dès quand un message est envoyé au chat. Ne fonctionne pas avec le module Maestro.", 35 | 36 | "br5e.d20Mode.name": "Mode d20", 37 | "br5e.d20Mode.hint": "Détermine comment les jets d'attaque, de sauvegarde et d'habilité sont affichés.", 38 | "br5e.d20Mode.choices.1": "Jet unique (Shift avantages, Ctrl désavantage)", 39 | "br5e.d20Mode.choices.2": "Jet double", 40 | "br5e.d20Mode.choices.3": "ゴゴ Triple menace ゴゴ", 41 | 42 | "br5e.critBehavior.name": "Dégâts critiques", 43 | "br5e.critBehavior.hint": "Détermine comment les dégâts critiques sont calculés.", 44 | "br5e.critBehavior.choices.0": "Pas de dégâts supplémentaires", 45 | "br5e.critBehavior.choices.1": "Dégâts critiques normaux", 46 | "br5e.critBehavior.choices.2": "Base normale, Max critique", 47 | "br5e.critBehavior.choices.3": "Max base, Max critique", 48 | 49 | "br5e.critString.name": "Indicateurs de critique", 50 | "br5e.critString.hint": "Détermine comment les critiques sont affichées. le texte apparaît à droite du jet de critique. ne fonctionne qu'avec Better Rolls.", 51 | "br5e.critString.choices.2": "Crit", 52 | "br5e.critString.choices.3": "Crit!", 53 | "br5e.critString.choices.4": "(Crit)", 54 | "br5e.critString.choices.5": "Critique", 55 | "br5e.critString.choices.6": "Critique!", 56 | "br5e.critString.choices.7": "(critique)", 57 | "br5e.critString.choices.8": "BAMMMM!", 58 | 59 | "br5e.error.noSelectedActor": "Vous devez avoir un personnage de sélectionner pour utiliser cette macro!", 60 | "br5e.error.noKnownItemOnActor": "N'a pas d'objet nommé", 61 | "br5e.error.noActorWithId": "Aucun acteur connu avec cette ID", 62 | "br5e.error.noActorWithName": "Aucun acteur connu avec ce nom", 63 | "br5e.error.noItemWithId": "Pas d'objet connu avec cette ID sur ce personnage", 64 | "br5e.error.noChargesLeft": "Cet objet n'a plus de charges!", 65 | 66 | "br5e.settings.critThreshold": "Seuil de critique", 67 | "br5e.settings.extraDamage": "Dégâts supplémentaires", 68 | "br5e.settings.extraType": "Type supplémentaire", 69 | "br5e.settings.addExtraToBase": "Ajout supplémentaire projet de base", 70 | "br5e.settings.addExtraToAlt": "Ajout supplémentaire projet alternatif", 71 | "br5e.settings.quickRollsLabel": "Jet rapide", 72 | "br5e.settings.quickRollsAltLabel": "Jet rapide alternatif", 73 | "br5e.settings.quickRollsAltSubLabel": "(En appuyant sur Alt pendant un jet rapide)", 74 | "br5e.settings.description": "Description", 75 | "br5e.settings.attackRoll": "J'ai d'attaque", 76 | "br5e.settings.attackAndSave": "Attaque / sauvegarde", 77 | "br5e.settings.saveDC": "Difficulté de sauvegarde", 78 | "br5e.settings.baseDamage": "Dégâts de base", 79 | "br5e.settings.altDamage": "Dégâts alternatifs", 80 | "br5e.settings.properties": "Propriété", 81 | "br5e.settings.check": "Test", 82 | "br5e.settings.consumeCharge": "Utiliser une charge", 83 | "br5e.settings.useTemplate": "Placer un template", 84 | "br5e.settings.label": "Etiquettes", 85 | "br5e.settings.context": "Contexte", 86 | "br5e.settings.otherFormula": "Autre formule", 87 | "br5e.settings.quickFlavor": "Flavor", 88 | 89 | "br5e.buttons.roll": "Jet", 90 | "br5e.buttons.altRoll": "Jet Alt", 91 | "br5e.buttons.attack": "Attaque", 92 | "br5e.buttons.saveDC": "Difficultés de sauvegarde", 93 | "br5e.buttons.saveAndDamage": "Jet (sauvegarde et dégâts)", 94 | "br5e.buttons.attackAndDamage": "Jet (attaques et dégâts)", 95 | "br5e.buttons.damage": "Dégâts", 96 | "br5e.buttons.altDamage": "Dégâts Alt", 97 | "br5e.buttons.extraDamage": "Dégâts supplémentaires", 98 | "br5e.buttons.verDamage": "Dégâts versatiles", 99 | "br5e.buttons.info": "Info", 100 | "br5e.buttons.itemUse": "Utiliser", 101 | "br5e.buttons.defaultSheetRoll": "Feuille par défaut", 102 | 103 | "br5e.chat.attack": "Attaque", 104 | "br5e.chat.check": "Test", 105 | "br5e.chat.save": "Sauvegarde", 106 | "br5e.chat.damage": "Dégâts", 107 | "br5e.chat.healing": "Soins", 108 | "br5e.chat.altPrefix": "Alt.", 109 | "br5e.chat.extraPrefix": "Extra", 110 | "br5e.chat.consumedBySpell": "Utilisé par le sort", 111 | "br5e.chat.abrVocal": "V", 112 | "br5e.chat.abrSomatic": "S", 113 | "br5e.chat.abrMaterial": "M", 114 | "br5e.chat.other": "Autre", 115 | "br5e.chat.advantage": "Avantages", 116 | "br5e.chat.disadvantage": "Désavantage", 117 | 118 | "br5e.chat.damageButtons.fullDamage.hint": "Appliquer les dégâts aux tokens sélectionnés.", 119 | "br5e.chat.damageButtons.halfDamage.hint": "Appliquer la moitié des dégâts aux tokens sélectionnés.", 120 | "br5e.chat.damageButtons.doubleDamage.hint": "Appliquer le double des dégâts aux tokens sélectionnés.", 121 | "br5e.chat.damageButtons.healing.hint": "Appliquer les soins aux tokens sélectionnés.", 122 | "br5e.chat.damageButtons.critPrompt.title": "Utiliser les dégâts critiques?", 123 | "br5e.chat.damageButtons.critPrompt.yes": "Oui", 124 | "br5e.chat.damageButtons.critPrompt.no": "Non", 125 | 126 | "Ability Roll": "Jet de caractéristiques", 127 | "What type of roll?": "Quel type?", 128 | "Ability Check": "Test de caractéristiques", 129 | "Saving Throw": "Jet de sauvegarde", 130 | 131 | "Not Proficient": "Pas de maîtrise", 132 | "Proficient": "Maîtrise", 133 | "Jack of all Trades": "Touche-à-tout", 134 | "Expertise": "Expertise", 135 | "Ritual": "Rituel", 136 | "Target: ": "Cible: ", 137 | "Concentration": "Concentration", 138 | "AC": "CA", 139 | "Equipped": "Equipé", 140 | "Stealth Disadv.": "Désavantage aux jets de discrétion" 141 | } -------------------------------------------------------------------------------- /betterrolls5e/lang/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Red Reign"], 3 | 4 | "Better Rolls": "拡張ロール", 5 | "Better Rolls for 5e": "Better Rolls for 5e", 6 | 7 | "br5e.migrating": "v{version}のため、Better Rollsのシステム・マイグレーションを適用します。", 8 | "br5e.rollButtonsEnabled.name": "シートにロールボタンを追加する", 9 | "br5e.rollButtonsEnabled.hint": "アイテム、呪文、特徴などの内容を確認するとき、チャットに出力するためのボタンを追加します。この機能は同様の機能を提供するモジュールと競合する可能性があります。変更時はシートを開き直してください。", 10 | "br5e.imageButtonEnabled.name": "アイテム画像からロールする", 11 | "br5e.imageButtonEnabled.hint": "アイテム画像をクリックしたとき、標準のロールの代わりにBetter Rollによって拡張されたクイックロールを表示します。Altキーを押しながらクリックすると、標準のロールが行えます。", 12 | "br5e.quickDefaultDescriptionEnabled.name": "クイックロールに説明文を表示する", 13 | "br5e.quickDefaultDescriptionEnabled.hint": "有効にすると、武器や道具の説明がクイックロールに表示されるようになります。ほかのアイテムタイプは標準で表示されるようになっているので、この設定が影響するのは新しい武器や道具のみです。", 14 | "br5e.defaultRollArt.name": "能力値判定のアイコン", 15 | "br5e.defaultRollArt.hint": "能力値判定とセーヴィング・スローで使用する画像を指定します。これが利用できない(または標準のミステリーマンアイコンの)場合は、代わりのアイコンを使用されます。", 16 | "br5e.damageContextEnabled.name": "ダメージの補足欄", 17 | "br5e.damageContextEnabled.hint": "アイテムのダメージ設定に、補足説明を入力するフィールドを追加します。これはクイックロールでのみ動作します。", 18 | "br5e.contextReplacesTitle.name": "補足でダメージ効果を置き換える", 19 | "br5e.contextReplacesTitle.hint": "補足がダメージの効果(“ダメージ”や“回復”など)と同じ位置に表示する場合、それを補足に入力したテキストに置き換えます。", 20 | "br5e.contextReplacesDamage.name": "補足でダメージ種別を置き換える", 21 | "br5e.contextReplacesDamage.hint": "補足がダメージ種別([刺突]や[冷気]など)と同じ場所に表示する場合、それを補足に入力したテキストに置き換えます。", 22 | "br5e.rollTitlePlacement.name": "ロールの種類を表示する", 23 | "br5e.rollTitlePlacement.hint": "ダメージ“以外”のロール結果に、“攻撃”などのロールの種類を表示します。", 24 | "br5e.damageTitlePlacement.name": "ダメージ効果を表示する", 25 | "br5e.damageTitlePlacement.hint": "ダメージロールの結果に、“ダメージ”や“回復”などの効果を表示します。また、その位置も指定します。", 26 | "br5e.damageContextPlacement.name": "補足を表示する", 27 | "br5e.damageContextPlacement.hint": "ダメージロールの結果に、設定した補足テキストを表示します。また、その位置も指定します。", 28 | "br5e.damageRollPlacement.name": "ダメージ種別を表示する", 29 | "br5e.damageRollPlacement.hint": "ダメージロールの結果に、[刺突]や[冷気]などのダメージ種別を表示します。また、その位置も指定します。", 30 | "br5e.damageRollPlacement.choices.0": "表示しない", 31 | "br5e.damageRollPlacement.choices.1": "ダメージ欄の上", 32 | "br5e.damageRollPlacement.choices.2": "ダメージのすぐ下", 33 | "br5e.damageRollPlacement.choices.3": "ダメージ欄の下", 34 | "br5e.altSecondaryEnabled.name": "Alt+クリックでサブロールを出力する", 35 | "br5e.altSecondaryEnabled.hint": "Altキーを押しながらアイテム画像をクリックすると、アイテムごとに個別に設定したサブロールを出力します。これはAltキーを標準のロールに使用する機能を上書きします(「アイテム画像からロールする」を参照)。", 36 | "br5e.playRollSounds.name": "ダイスロール音の有効化", 37 | "br5e.playRollSounds.hint": "Better Rollsのメッセージがチャットに送信されると、ダイスロール音が鳴ります。MaestroのItem Trackが有効になっている場合は再生されません。", 38 | "br5e.hideDC.name": "セーヴ難易度を隠す", 39 | "br5e.hideDC.hint": "クイックロール中のセーヴボタンに、セーヴ難易度を表記するかどうかを決定します。", 40 | "br5e.hideDC.string": "〈不明〉", 41 | "br5e.hideDC.choices.0": "隠さない", 42 | "br5e.hideDC.choices.1": "NPCは隠す", 43 | "br5e.hideDC.choices.2": "常に隠す", 44 | "br5e.damagePromptEnabled.name": "チャットカードにダメージボタンを表示する", 45 | "br5e.damagePromptEnabled.hint": "ダメージロールを自動化せず、攻撃やセーヴ後にダメージロールボタンが表示されるようになります。", 46 | "br5e.d20RollIconsEnabled.name": "d20ロールの出目をアイコン表示する", 47 | "br5e.d20RollIconsEnabled.hint": "攻撃/判定/セーヴのダイスの結果を、見やすい位置に表示します。", 48 | "br5e.applyActiveEffects.name": "チャットカードにアクティブ効果ボタンを表示する", 49 | "br5e.applyActiveEffects.hint": "アクティブ効果を適用するためのボタンをチャットカードに表示します。「Dynamic Active Effect」(DAE)モジュールが必要です。DAEが未インストールの場合、このオプションは何も行いません。", 50 | 51 | "br5e.d20Mode.name": "d20モード", 52 | "br5e.d20Mode.hint": "攻撃ロール / 能力値判定 / セーヴィング・スローの表示方法を設定します。シングルロールでは、有利/不利を選択するオーバーレイボタン([-] / [+])が使用できます。", 53 | "br5e.d20Mode.choices.1": "シングルロール(Shiftで有利 / Ctrlで不利)", 54 | "br5e.d20Mode.choices.2": "デュアルロール(2個同時)", 55 | "br5e.d20Mode.choices.3": "トリプルスレット(3個同時)", 56 | "br5e.d20Mode.choices.4": "有利/不利を確認する", 57 | 58 | "br5e.critBehavior.name": "クリティカル・ダメージ", 59 | "br5e.critBehavior.hint": "クリティカル・ダメージの計算方法を指定します。", 60 | "br5e.critBehavior.choices.0": "追加ダメージなし", 61 | "br5e.critBehavior.choices.1": "追加のダメージダイスを振る(5eの標準)", 62 | "br5e.critBehavior.choices.2": "元の出目+追加の最大値", 63 | "br5e.critBehavior.choices.3": "元の最大値+追加の最大値", 64 | "br5e.critBehavior.choices.4": "元の最大値+追加の出目", 65 | 66 | "br5e.critString.name": "クリティカルの表示", 67 | "br5e.critString.hint": "クリティカル時に表示するテキストを設定します。このテキストはダメージの右側に表示されます(クイックロール時のみ)。", 68 | "br5e.critString.choices.2": "クリティカル!", 69 | "br5e.critString.choices.3": "渾身の一撃!", 70 | "br5e.critString.choices.4": "致命の痛打!", 71 | "br5e.critString.choices.5": "痛烈な一打!", 72 | "br5e.critString.choices.6": "不可避の痛撃!", 73 | "br5e.critString.choices.7": "逃れ得ぬ強打!", 74 | "br5e.critString.choices.8": "会心の一撃!", 75 | 76 | "br5e.chatDamageButtonsEnabled": { 77 | "name": "ダメージ適用ボタンの有効化", 78 | "hint": "選択したコマにダメージを適用するボタンを、ダメージロールの下に表示します。Foundryの再読み込みが必要です。", 79 | "choices": { 80 | "0": "無効", 81 | "1": "有効", 82 | "2": "DMのみ" 83 | } 84 | }, 85 | 86 | "br5e.error.noSelectedActor": "このマクロを使用するには、キャラクターを選択している必要があります!", 87 | "br5e.error.noKnownItemOnActor": "という名前のアイテムを所有していません。", 88 | "br5e.error.noActorWithId": "そのIDのアクターは存在しません。", 89 | "br5e.error.noActorWithName": "その名前のアクターは存在しません。", 90 | "br5e.error.noItemWithId": "アクター上に、その名前のIDのアイテムは存在しません。", 91 | "br5e.error.noChargesLeft": "このアイテムはチャージ切れです!", 92 | "br5e.error.autoDestroy": "最後のチャージを消費すると {name} は消滅します。", 93 | "br5e.error.rollEvaluation": "ダイスロールの評価に失敗しました:{msg}", 94 | "br5e.error.noDAE": "目標にアクティブ効果を適用するにはDAEが必要です。", 95 | "br5e.error.libWrapperMinVersion": "Better Rollsには「libWrapper」のバージョン {version} 以降が必要です。", 96 | 97 | "br5e.settings.critThreshold": "クリティカル範囲", 98 | "br5e.settings.critDamage.name": "追加クリティカルダメージ", 99 | "br5e.settings.critDamage.hint": "追加のクリティカルダメージを設定します。1ダメージ以上でないと適用されません。", 100 | "br5e.settings.quickRollsLabel": "クイックロール", 101 | "br5e.settings.quickRollsAltLabel": "サブクイックロール", 102 | "br5e.settings.quickRollsAltSubLabel": "(Altを押しながらクイックロール)", 103 | "br5e.settings.description": "効果", 104 | "br5e.settings.attackRoll": "攻撃ロール", 105 | "br5e.settings.attackAndSave": "攻撃ロール/セーヴ難易度", 106 | "br5e.settings.saveDC": "セーヴ難易度", 107 | "br5e.settings.baseDamage": "基本ダメージ", 108 | "br5e.settings.altDamage": "サブダメージ", 109 | "br5e.settings.properties": "設定", 110 | "br5e.settings.check": "判定", 111 | "br5e.settings.consume": "消費", 112 | "br5e.settings.consumeQuantity": "数量", 113 | "br5e.settings.consumeUses": "使用", 114 | "br5e.settings.consumeResource": "リソース", 115 | "br5e.settings.consumeRecharge": "アクションチャージ", 116 | "br5e.settings.useTemplate": "テンプレート配置", 117 | "br5e.settings.label": "ダメージ種別(置換)", 118 | "br5e.settings.context": "補足", 119 | "br5e.settings.otherFormula": "その他の式", 120 | "br5e.settings.quickFlavor": "フレーバー", 121 | "br5e.settings.prompt": "拡張プロンプト", 122 | 123 | "br5e.buttons.roll": "ロール", 124 | "br5e.buttons.altRoll": "サブロール", 125 | "br5e.buttons.attack": "攻撃", 126 | "br5e.buttons.saveDC": "セーヴ難易度", 127 | "br5e.buttons.saveAndDamage": "ロール(セーヴ&ダメージ)", 128 | "br5e.buttons.attackAndDamage": "ロール(攻撃&ダメージ)", 129 | "br5e.buttons.damage": "ダメージ", 130 | "br5e.buttons.altDamage": "サブダメージ", 131 | "br5e.buttons.extraDamage": "追加ダメージ", 132 | "br5e.buttons.verDamage": "両用ダメージ", 133 | "br5e.buttons.info": "詳細", 134 | "br5e.buttons.itemUse": "使用", 135 | "br5e.buttons.defaultSheetRoll": "元々のロール", 136 | "br5e.buttons.applyActiveEffects": "効果を適用", 137 | 138 | "br5e.chat.attack": "攻撃", 139 | "br5e.chat.check": "判定", 140 | "br5e.chat.save": "セーヴ", 141 | "br5e.chat.damage": "ダメージ", 142 | "br5e.chat.healing": "回復", 143 | "br5e.chat.altPrefix": "サブ", 144 | "br5e.chat.extraPrefix": "追加", 145 | "br5e.chat.consumedBySpell": "呪文により消費", 146 | "br5e.chat.abrVocal": "音声", 147 | "br5e.chat.abrSomatic": "動作", 148 | "br5e.chat.abrMaterial": "物質", 149 | "br5e.chat.other": "その他", 150 | "br5e.chat.advantage": "有利", 151 | "br5e.chat.disadvantage": "不利", 152 | "br5e.chat.normal": "通常", 153 | 154 | "br5e.chatContext.repeat": "ロールを繰り返す", 155 | 156 | "br5e.querying.title": "どの振り方にしますか?", 157 | "br5e.querying.disadvantage": "不利", 158 | "br5e.querying.normal": "通常", 159 | "br5e.querying.advantage": "有利", 160 | 161 | "br5e.chat.multiRollButtons.advantage.hint": "有利でロールする(Shift)", 162 | "br5e.chat.multiRollButtons.disadvantage.hint": "不利でロールする(Ctrl)", 163 | 164 | "br5e.chat.damageButtons": { 165 | "fullDamage.hint": "通常ダメージを適用", 166 | "quarterDamage.hint": "1/4ダメージを適用", 167 | "halfDamage.hint": "半減ダメージを適用", 168 | "doubleDamage.hint": "2倍ダメージを適用", 169 | "healing.hint": "回復を適用する", 170 | "crit.hint": "クリティカルでダメージロールを行います", 171 | "tempOverwrite.title": "一時的hpを上書きします", 172 | "tempOverwrite.content": "現在の一時的hp {original} を新しい値 {new} に置き換えますか?", 173 | "critPrompt.title": "クリティカルですか?", 174 | "critPrompt.yes": "はい", 175 | "critPrompt.no": "いいえ" 176 | }, 177 | 178 | "Ability Roll": "能力値ロール", 179 | "What type of roll?": "どの振り方にしますか?", 180 | "Ability Check": "能力値判定", 181 | "Saving Throw": "セーヴィング・スロー", 182 | 183 | "Not Proficient": "未習熟", 184 | "Proficient": "習熟", 185 | "Jack of all Trades": "なんでも屋", 186 | "Expertise": "習熟強化", 187 | "Ritual": "儀式", 188 | "Target: ": "対象:", 189 | "Concentration": "集中", 190 | "AC": "AC", 191 | "Equipped": "装備済", 192 | "Stealth Disadv.": "隠密不利" 193 | } 194 | -------------------------------------------------------------------------------- /betterrolls5e/lang/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["KLO"], 3 | 4 | "Better Rolls": "Better Rolls", 5 | "Better Rolls for 5e": "D&D 5판을 위한 개선된 굴림 모듈", 6 | 7 | "br5e.rollButtonsEnabled.name": "시트에 굴림 버튼 추가", 8 | "br5e.rollButtonsEnabled.hint": "시트 내 아이템, 주문, 기능에 버튼을 추가한다. 아이템이 열렸을 시 표시된다. 아이템 시트 버튼 모듈과 충돌할 수 있다. 시트를 다시 열어야 한다. ", 9 | "br5e.imageButtonEnabled.name": "아이템 이미지 자동 굴림", 10 | "br5e.imageButtonEnabled.hint": "아이템의 이미지를 클릭했을 때 일반적 출력 대신 개선된 굴림 메시지를 출력한다. Alt+클릭시 우회된다", 11 | "br5e.quickDefaultDescriptionEnabled.name": "기본 값으로 활성화된 빠른 굴림 설명", 12 | "br5e.quickDefaultDescriptionEnabled.hint": "이 기능이 활성화되면 무기와 도구의 설명이 빠른 굴림에 표시된다. 다른 아이템 타입에는 기본적으로 설정된 설명이 있기 때문에 새 무기와 도구에만 영향을 미친다.", 13 | "br5e.defaultRollArt.name": "기본 그림 사용", 14 | "br5e.defaultRollArt.hint": "능력 판정 및 내성 굴림에 기본적으로 사용되는 그림을 결정한다. 만약 사용할 수 없는 경우(혹은 기본 Mystery man 아이콘일 때) 다른 아이콘을 대신 사용한다.", 15 | "br5e.damageContextEnabled.name": "피해 내용 설명", 16 | "br5e.damageContextEnabled.hint": "피해 유형 앞에 피해 내용에 따른 설명을 입력할 수 있다. Better Rolls에서만 작동한다", 17 | "br5e.contextReplacesTitle.name": "이름 표시를 대체하는 설명", 18 | "br5e.contextReplacesTitle.hint": "내용 설명이 \"피해\"나 \"회복\"과 같은 피해 굴림의 이름 표시와 같은 위치에 있다면 해당 표시를 대체한다.", 19 | "br5e.contextReplacesDamage.name": "피해 유형을 대체하는 내용 설명", 20 | "br5e.contextReplacesDamage.hint": "내용 설명이 피해 유형 표시와 같은 위치에 있다면 이를 대체한다..", 21 | "br5e.rollTitlePlacement.name": "굴림 이름 표시 보기", 22 | "br5e.rollTitlePlacement.hint": "\"공격\"과 같이 피해를 주지 않는 굴림의 이름이 표시되는지의 여부를 결정한다.", 23 | "br5e.damageTitlePlacement.name": "피해 이름 표시 보기", 24 | "br5e.damageTitlePlacement.hint": "\"피해\"와 같은 피해 굴림의 이름이 표시되는지의 여부를 결정한다.", 25 | "br5e.damageRollPlacement.name": "피해 유형 표시 보기", 26 | "br5e.damageRollPlacement.hint": "피해 굴림을 기준으로 피해 유형 표시를 배치할 위치를 결정한다.", 27 | "br5e.damageRollPlacement.choices.0": "보지 않음", 28 | "br5e.damageRollPlacement.choices.1": "상부", 29 | "br5e.damageRollPlacement.choices.2": "하부 & 안쪽", 30 | "br5e.damageRollPlacement.choices.3": "하부 & 바깥쪽", 31 | "br5e.altSecondaryEnabled.name": "이미지 Alt+클릭시 보조 굴림", 32 | "br5e.altSecondaryEnabled.hint": "아이템의 이미지를 Alt+클릭하는 경우 아이템 별로 구성할 수 있는 보조적 개선된 굴림 메시지를 출력한다. 기본적 채팅 결과를 건너뛰는 기능을 덮어쓴다.", 33 | "br5e.chatDamageButtonsEnabled.name": "채팅 피해 버튼 활성화", 34 | "br5e.chatDamageButtonsEnabled.hint": "Better Rolls의 피해 굴림에서 선택한 토큰에 피해를 가하는 버튼을 활성화한다. Foundry를 새로고침해야 한다.", 35 | "br5e.playRollSounds.name": "굴림 소리 활성화", 36 | "br5e.playRollSounds.hint": "Better Rolls의 메시지가 채팅으로 전송될 때 주사위 굴림 소리를 재생한다. Maestro 모듈의 아이템 트랙이 활성화 되었을 경우 재생되지 않는다.", 37 | "br5e.hideDC.name": "내성 굴림 난이도 숨김", 38 | "br5e.hideDC.hint": "Better Rolls의 내성 굴림 버튼에 난이도를 숨기고자 할 때 선택한다.", 39 | "br5e.hideDC.string": "??", 40 | "br5e.hideDC.choices.0": "비활성화", 41 | "br5e.hideDC.choices.1": "NPC만", 42 | "br5e.hideDC.choices.2": "항상", 43 | 44 | "br5e.d20Mode.name": "d20 모드", 45 | "br5e.d20Mode.hint": "공격 굴림, 능력 판정, 내성 굴림이 어떻게 보여질지 결정한다", 46 | "br5e.d20Mode.choices.1": "단일 굴림 (Shift 유리함, Ctrl 불리함)", 47 | "br5e.d20Mode.choices.2": "2중 굴림", 48 | "br5e.d20Mode.choices.3": "3중 굴림", 49 | 50 | "br5e.critBehavior.name": "치명타 피해", 51 | "br5e.critBehavior.hint": "치명타 피해가 어떻게 계산되는 지를 결정한다.", 52 | "br5e.critBehavior.choices.0": "추가 피해 없음", 53 | "br5e.critBehavior.choices.1": "치명타 주사위 굴림", 54 | "br5e.critBehavior.choices.2": "기본 피해 굴림, 치명타 피해 최대", 55 | "br5e.critBehavior.choices.3": "기본 피해 굴림과 치명타 피해 최대", 56 | 57 | "br5e.critString.name": "치명타 표시", 58 | "br5e.critString.hint": "치명타 표시 방법을 결정한다. 치명타 피해 굴림의 우측에 텍스트로 표시된다. Better Rolls에서만 적용된다.", 59 | "br5e.critString.choices.2": "치명타", 60 | "br5e.critString.choices.3": "치명타!", 61 | "br5e.critString.choices.4": "(치명타)", 62 | "br5e.critString.choices.5": "치명적 피해", 63 | "br5e.critString.choices.6": "치명적 피해l!", 64 | "br5e.critString.choices.7": "(치명적 피해)", 65 | "br5e.critString.choices.8": "빼앰!", 66 | 67 | "br5e.error.noSelectedActor": "이 매크로를 사용하려면 캐릭터를 선택해야 합니다!", 68 | "br5e.error.noKnownItemOnActor": "지정된 아이템을 소유하고 있지 않습니다", 69 | "br5e.error.noActorWithId": "해당 ID를 가진 액터가 존재하지 않습니다", 70 | "br5e.error.noActorWithName": "그러한 이름을 가진 액터가 존재하지 않습니다", 71 | "br5e.error.noItemWithId": "액터에 대한 ID로 알려진 아이템이 없습니다", 72 | "br5e.error.noChargesLeft": "이 아이템은 차지가 다 떨어졌습니다", 73 | "br5e.error.autoDestroy": "마지막 충전량이 소비되어 항목이 사라집니다.", 74 | 75 | "br5e.settings.critThreshold": "치명타 한계점", 76 | "br5e.settings.critDamage.name": "추가 치명타 피해", 77 | "br5e.settings.critDamage.hint": "하나 이상의 피해가 굴려질 때 더해지는 추가 치명타 피해", 78 | "br5e.settings.quickRollsLabel": "빠른 굴림", 79 | "br5e.settings.quickRollsAltLabel": "대체된 빠른 굴림", 80 | "br5e.settings.quickRollsAltSubLabel": "(빠른 굴림 중 Alt 키를 누르고 있을 때)", 81 | "br5e.settings.description": "설명", 82 | "br5e.settings.attackRoll": "공격 굴림", 83 | "br5e.settings.attackAndSave": "공격 굴림 / 내성 굴림", 84 | "br5e.settings.saveDC": "내성 굴림 난이도", 85 | "br5e.settings.baseDamage": "기본 피해", 86 | "br5e.settings.altDamage": "대체 피해", 87 | "br5e.settings.properties": "특성", 88 | "br5e.settings.check": "판정", 89 | "br5e.settings.consumeCharge": "차지 소모", 90 | "br5e.settings.useTemplate": "템플릿 배치", 91 | "br5e.settings.label": "라벨", 92 | "br5e.settings.context": "설명", 93 | "br5e.settings.otherFormula": "기타 수식", 94 | "br5e.settings.quickFlavor": "플레이버", 95 | 96 | "br5e.buttons.roll": "굴림", 97 | "br5e.buttons.altRoll": "대체 굴림", 98 | "br5e.buttons.attack": "공격", 99 | "br5e.buttons.saveDC": "내성 굴림 난이도", 100 | "br5e.buttons.saveAndDamage": "굴림 (내성 & 피해)", 101 | "br5e.buttons.attackAndDamage": "굴림 (공격 & 피해)", 102 | "br5e.buttons.damage": "피해", 103 | "br5e.buttons.altDamage": "대체 피해", 104 | "br5e.buttons.extraDamage": "추가 피해", 105 | "br5e.buttons.verDamage": "다용도 피해", 106 | "br5e.buttons.info": "정보", 107 | "br5e.buttons.itemUse": "사용", 108 | "br5e.buttons.defaultSheetRoll": "기본 시트 굴림", 109 | 110 | "br5e.chat.attack": "공격", 111 | "br5e.chat.check": "판정", 112 | "br5e.chat.save": "내성 굴림", 113 | "br5e.chat.damage": "피해", 114 | "br5e.chat.healing": "회복", 115 | "br5e.chat.altPrefix": "대체", 116 | "br5e.chat.extraPrefix": "추가", 117 | "br5e.chat.consumedBySpell": "주문으로 인한 소모", 118 | "br5e.chat.abrVocal": "음성(V)", 119 | "br5e.chat.abrSomatic": "몸짓(S)", 120 | "br5e.chat.abrMaterial": "물질(M)", 121 | "br5e.chat.other": "기타", 122 | "br5e.chat.advantage": "유리함", 123 | "br5e.chat.disadvantage": "불리함", 124 | 125 | "br5e.chat.damageButtons.fullDamage.hint": "선택한 토큰에 모든 피해를 적용함.", 126 | "br5e.chat.damageButtons.halfDamage.hint": "선택한 토큰에 절반의 피해를 적용함.", 127 | "br5e.chat.damageButtons.doubleDamage.hint": "선택한 토큰에 2배의 피해를 적용함.", 128 | "br5e.chat.damageButtons.healing.hint": "선택한 토큰에 모든 회복을 적용함.", 129 | "br5e.chat.damageButtons.critPrompt.title": "치명타 피해를 사용합니까?", 130 | "br5e.chat.damageButtons.critPrompt.yes": "예", 131 | "br5e.chat.damageButtons.critPrompt.no": "아니오", 132 | 133 | "Ability Roll": "능력치 굴림", 134 | "What type of roll?": "어떤 굴림을 합니까?", 135 | "Ability Check": "능력 판정", 136 | "Saving Throw": "내성 굴림", 137 | 138 | "Not Proficient": "비숙련", 139 | "Proficient": "숙련", 140 | "Jack of all Trades": "다재다능", 141 | "Expertise": "전문화", 142 | "Ritual": "의식", 143 | "Target: ": "대상: ", 144 | "Concentration": "집중", 145 | "AC": "AC", 146 | "Equipped": "장비됨", 147 | "Stealth Disadv.": "은신 불리성." 148 | } 149 | -------------------------------------------------------------------------------- /betterrolls5e/lang/kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["KLO"], 3 | 4 | "Better Rolls": "Better Rolls", 5 | "Better Rolls for 5e": "D&D 5판을 위한 개선된 굴림 모듈", 6 | 7 | "br5e.rollButtonsEnabled.name": "시트에 굴림 버튼 추가", 8 | "br5e.rollButtonsEnabled.hint": "시트 내 아이템, 주문, 기능에 버튼을 추가한다. 아이템이 열렸을 시 표시된다. 아이템 시트 버튼 모듈과 충돌할 수 있다. 시트를 다시 열어야 한다. ", 9 | "br5e.imageButtonEnabled.name": "아이템 이미지 자동 굴림", 10 | "br5e.imageButtonEnabled.hint": "아이템의 이미지를 클릭했을 때 일반적 출력 대신 개선된 굴림 메시지를 출력한다. Alt+클릭시 우회된다", 11 | "br5e.quickDefaultDescriptionEnabled.name": "기본 값으로 활성화된 빠른 굴림 설명", 12 | "br5e.quickDefaultDescriptionEnabled.hint": "이 기능이 활성화되면 무기와 도구의 설명이 빠른 굴림에 표시된다. 다른 아이템 타입에는 기본적으로 설정된 설명이 있기 때문에 새 무기와 도구에만 영향을 미친다.", 13 | "br5e.damageContextEnabled.name": "피해 내용 설명", 14 | "br5e.damageContextEnabled.hint": "피해 유형 앞에 피해 내용에 따른 설명을 입력할 수 있다. Better Rolls에서만 작동한다", 15 | "br5e.contextReplacesTitle.name": "이름 표시를 대체하는 설명", 16 | "br5e.contextReplacesTitle.hint": "내용 설명이 \"피해\"나 \"회복\"과 같은 피해 굴림의 이름 표시와 같은 위치에 있다면 해당 표시를 대체한다.", 17 | "br5e.contextReplacesDamage.name": "피해 유형을 대체하는 내용 설명", 18 | "br5e.contextReplacesDamage.hint": "내용 설명이 피해 유형 표시와 같은 위치에 있다면 이를 대체한다..", 19 | "br5e.rollTitlePlacement.name": "굴림 이름 표시 보기", 20 | "br5e.rollTitlePlacement.hint": "\"공격\"과 같이 피해를 주지 않는 굴림의 이름이 표시되는지의 여부를 결정한다.", 21 | "br5e.damageTitlePlacement.name": "피해 이름 표시 보기", 22 | "br5e.damageTitlePlacement.hint": "\"피해\"와 같은 피해 굴림의 이름이 표시되는지의 여부를 결정한다.", 23 | "br5e.damageRollPlacement.name": "피해 유형 표시 보기", 24 | "br5e.damageRollPlacement.hint": "피해 굴림을 기준으로 피해 유형 표시를 배치할 위치를 결정한다.", 25 | "br5e.damageRollPlacement.choices.0": "보지 않음", 26 | "br5e.damageRollPlacement.choices.1": "상부", 27 | "br5e.damageRollPlacement.choices.2": "하부 & 안쪽", 28 | "br5e.damageRollPlacement.choices.3": "하부 & 바깥쪽", 29 | "br5e.altSecondaryEnabled.name": "이미지 Alt+클릭시 보조 굴림", 30 | "br5e.altSecondaryEnabled.hint": "아이템의 이미지를 Alt+클릭하는 경우 아이템 별로 구성할 수 있는 보조적 개선된 굴림 메시지를 출력한다. 기본적 채팅 결과를 건너뛰는 기능을 덮어쓴다.", 31 | "br5e.chatDamageButtonsEnabled.name": "채팅 피해 버튼 활성화", 32 | "br5e.chatDamageButtonsEnabled.hint": "Better Rolls의 피해 굴림에서 선택한 토큰에 피해를 가하는 버튼을 활성화한다. Foundry를 새로고침해야 한다.", 33 | "br5e.playRollSounds.name": "굴림 소리 활성화", 34 | "br5e.playRollSounds.hint": "Better Rolls의 메시지가 채팅으로 전송될 때 주사위 굴림 소리를 재생한다. Maestro 모듈의 아이템 트랙이 활성화 되었을 경우 재생되지 않는다.", 35 | 36 | "br5e.critBehavior.name": "치명타 피해", 37 | "br5e.critBehavior.hint": "치명타 피해가 어떻게 계산되는 지를 결정한다.", 38 | "br5e.critBehavior.choices.0": "추가 피해 없음", 39 | "br5e.critBehavior.choices.1": "치명타 주사위 굴림", 40 | "br5e.critBehavior.choices.2": "기본 피해 굴림, 치명타 피해 최대", 41 | "br5e.critBehavior.choices.3": "기본 피해 굴림과 치명타 피해 최대", 42 | 43 | "br5e.critString.name": "치명타 표시", 44 | "br5e.critString.hint": "치명타 표시 방법을 결정한다. 치명타 피해 굴림의 우측에 텍스트로 표시된다. Better Rolls에서만 적용된다.", 45 | "br5e.critString.choices.2": "치명타", 46 | "br5e.critString.choices.3": "치명타!", 47 | "br5e.critString.choices.4": "(치명타)", 48 | "br5e.critString.choices.5": "치명적 피해", 49 | "br5e.critString.choices.6": "치명적 피해l!", 50 | "br5e.critString.choices.7": "(치명적 피해)", 51 | "br5e.critString.choices.8": "빼앰!", 52 | 53 | "br5e.error.noSelectedActor": "이 매크로를 사용하려면 캐릭터를 선택해야 합니다!", 54 | "br5e.error.noKnownItemOnActor": "지정된 아이템을 소유하고 있지 않습니다", 55 | "br5e.error.noActorWithId": "해당 ID를 가진 액터가 존재하지 않습니다", 56 | "br5e.error.noActorWithName": "그러한 이름을 가진 액터가 존재하지 않습니다", 57 | "br5e.error.noItemWithId": "액터에 대한 ID로 알려진 아이템이 없습니다", 58 | "br5e.error.noChargesLeft": "이 아이템은 차지가 다 떨어졌습니다", 59 | 60 | "br5e.settings.critThreshold": "치명타 한계점", 61 | "br5e.settings.extraDamage": "추가 피해", 62 | "br5e.settings.extraType": "추가 유형", 63 | "br5e.settings.addExtraToBase": "기본 피해 굴림에 추가 피해량을 더함", 64 | "br5e.settings.addExtraToAlt": "대체 피해 굴림에 추가 피해를 더함", 65 | "br5e.settings.quickRollsLabel": "빠른 굴림", 66 | "br5e.settings.quickRollsAltLabel": "대체된 빠른 굴림", 67 | "br5e.settings.quickRollsAltSubLabel": "(빠른 굴림 중 Alt 키를 누르고 있을 때)", 68 | "br5e.settings.description": "설명", 69 | "br5e.settings.attackRoll": "공격 굴림", 70 | "br5e.settings.attackAndSave": "공격 굴림 / 내성 굴림", 71 | "br5e.settings.saveDC": "내성 굴림 난이도", 72 | "br5e.settings.baseDamage": "기본 피해", 73 | "br5e.settings.altDamage": "대체 피해", 74 | "br5e.buttons.verDamage": "다용도 피해", 75 | "br5e.settings.properties": "특성", 76 | "br5e.settings.check": "판정", 77 | "br5e.settings.consumeCharge": "차지 소모", 78 | "br5e.settings.useTemplate": "템플릿 배치", 79 | "br5e.settings.label": "라벨", 80 | "br5e.settings.context": "설명", 81 | 82 | "br5e.buttons.roll": "굴림", 83 | "br5e.buttons.altRoll": "대체 굴림", 84 | "br5e.buttons.attack": "공격", 85 | "br5e.buttons.saveDC": "내성 굴림 난이도", 86 | "br5e.buttons.saveAndDamage": "굴림 (내성 & 피해)", 87 | "br5e.buttons.attackAndDamage": "굴림 (공격 & 피해)", 88 | "br5e.buttons.damage": "피해", 89 | "br5e.buttons.altDamage": "대체 피해", 90 | "br5e.buttons.extraDamage": "추가 피해", 91 | "br5e.buttons.info": "정보", 92 | "br5e.buttons.itemUse": "사용", 93 | "br5e.buttons.defaultSheetRoll": "기본 시트 굴림", 94 | 95 | "br5e.chat.attack": "공격", 96 | "br5e.chat.check": "판정", 97 | "br5e.chat.save": "내성 굴림", 98 | "br5e.chat.damage": "피해", 99 | "br5e.chat.healing": "회복", 100 | "br5e.chat.altPrefix": "대체", 101 | "br5e.chat.extraPrefix": "추가", 102 | "br5e.chat.consumedBySpell": "주문으로 인한 소모", 103 | "br5e.chat.abrVocal": "음성(V)", 104 | "br5e.chat.abrSomatic": "몸짓(S)", 105 | "br5e.chat.abrMaterial": "물질(M)", 106 | 107 | "br5e.chat.damageButtons.fullDamage.hint": "선택한 토큰에 모든 피해를 적용함.", 108 | "br5e.chat.damageButtons.halfDamage.hint": "선택한 토큰에 절반의 피해를 적용함.", 109 | "br5e.chat.damageButtons.doubleDamage.hint": "선택한 토큰에 2배의 피해를 적용함.", 110 | "br5e.chat.damageButtons.healing.hint": "선택한 토큰에 모든 회복을 적용함.", 111 | "br5e.chat.damageButtons.critPrompt.title": "치명타 피해를 사용합니까?", 112 | "br5e.chat.damageButtons.critPrompt.yes": "예", 113 | "br5e.chat.damageButtons.critPrompt.no": "아니오", 114 | 115 | "Ability Roll": "능력치 굴림", 116 | "What type of roll?": "어떤 굴림을 합니까?", 117 | "Ability Check": "능력 판정", 118 | "Saving Throw": "내성 굴림", 119 | 120 | "Not Proficient": "비숙련", 121 | "Proficient": "숙련", 122 | "Jack of all Trades": "다재다능", 123 | "Expertise": "전문화", 124 | "Ritual": "의식", 125 | "Target: ": "대상: ", 126 | "Concentration": "집중", 127 | "AC": "AC", 128 | "Equipped": "장비함", 129 | "Stealth Disadv.": "은신 불리성." 130 | } -------------------------------------------------------------------------------- /betterrolls5e/lang/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "I18N.MAINTAINERS": ["Innocenti, Melithian"], 3 | 4 | "Better Rolls": "Rolagens Melhores", 5 | "Better Rolls for 5e": "Better Rolls for 5e", 6 | 7 | "br5e.rollButtonsEnabled.name": "Adicionar Botões de Rolagem na Ficha", 8 | "br5e.rollButtonsEnabled.hint": "Adiciona botões aos itens, magias e recursos da ficha, exibidos quando o item é expandido. Pode ser incompatível com o mod Item Sheet Buttons. Necessário reabrir a ficha.", 9 | "br5e.imageButtonEnabled.name": "Fazer Imagem do Item Rolar Automaticamente", 10 | "br5e.imageButtonEnabled.hint": "Ao clicar na imagem de um item, envie uma mensagem de Rolagens Melhores para o chat ao invés da mensagem normal. Pode ser ignorado pressionando Alt ao clicar.", 11 | "br5e.quickDefaultDescriptionEnabled.name": "Descrição em Rolagens Rápidas habilitados por padrão", 12 | "br5e.quickDefaultDescriptionEnabled.hint": "Se ativado, armas e ferramentas terão suas descrições exibidas por padrão em Rolagens Rápidas. Afeta apenas novas armas e ferramentas, pois outros tipos de itens já têm descrições ativadas por padrão.", 13 | "br5e.defaultRollArt.name": "Arte Padrão", 14 | "br5e.defaultRollArt.hint": "Determina qual arte é usada por padrão para testes de atributo e salvaguardas. Se não estiver disponível (ou for o ícone padrão do homem misterioso), ele usará o outro ícone.", 15 | "br5e.damageContextEnabled.name": "Rótulos de Contexto de Dano", 16 | "br5e.damageContextEnabled.hint": "Permite inserir contexto para etiquetas de dano, que precedem o tipo de dano. Funciona apenas com Rolagens Melhores.", 17 | "br5e.contextReplacesTitle.name": "Contexto substitui Rótulo de Título", 18 | "br5e.contextReplacesTitle.hint": "Se um rótulo de contexto estiver no mesmo local que o rótulo de título de uma jogada de dano, como \"Dano\" ou \"Cura\", ele será substituído.", 19 | "br5e.contextReplacesDamage.name": "Contexto substitui tipo de Dano", 20 | "br5e.contextReplacesDamage.hint": "Se um rótulo de contexto estiver no mesmo local que um rótulo de tipo de dano, ele será substituído.", 21 | "br5e.rollTitlePlacement.name": "Exibir rótulos de título de Rolagem", 22 | "br5e.rollTitlePlacement.hint": "Determina se o título de uma rolagem sem dano, como \"Ataque\", aparece.", 23 | "br5e.damageTitlePlacement.name": "Mostrar Rótulos de Título de Dano", 24 | "br5e.damageTitlePlacement.hint": "Determina se o título de uma rolagem de dano, como \"Dano \", aparece.", 25 | "br5e.damageContextPlacement.name": "Mostrar rótulos de contexto de Dano", 26 | "br5e.damageContextPlacement.hint": "Determina se e onde um rótulo de contexto para uma rolagem de dano aparece.", 27 | "br5e.damageRollPlacement.name": "Mostrar Rótulos de Tipo de Dano", 28 | "br5e.damageRollPlacement.hint": "Determina onde o rótulo do tipo de dano é colocado, em relação a uma rolagem de dano.", 29 | "br5e.damageRollPlacement.choices.0": "Não mostrar", 30 | "br5e.damageRollPlacement.choices.1": "Acima", 31 | "br5e.damageRollPlacement.choices.2": "Abaixo e Dentro", 32 | "br5e.damageRollPlacement.choices.3": "Abaixo e Fora", 33 | "br5e.altSecondaryEnabled.name": "Clique com a tecla Alt pressionada na imagem para Rolagem Secundária", 34 | "br5e.altSecondaryEnabled.hint": "Ao clicar com a tecla Alt pressionada na imagem de um item, emita uma segunda mensagem de Rolagens Melhores que pode ser configurada separadamente por item. Substitui a capacidade de ignorar a saída de bate-papo padrão. ", 35 | "br5e.chatDamageButtonsEnabled.name": "Habilitar Botões de Dano no Chat", 36 | "br5e.chatDamageButtonsEnabled.hint": "Renderiza botões nas jogadas de dano do Rolagens Melhores que aplicam dano aos tokens selecionados. Necessário reiniciar o Foundry.", 37 | "br5e.playRollSounds.name": "Habilitar Sons de Dados", 38 | "br5e.playRollSounds.hint": "Toca um som de dados rolando quando uma mensagem de Rolagens Melhores é enviada para o chat. Não será reproduzido se a Faixa de Item do mod Maestro estiver ativada.", 39 | "br5e.hideDC.name": "Esconder as CD de Salvaguarda", 40 | "br5e.hideDC.hint": "Determina se o CD será oculto nos botões Salvaguarda nas Rolagens Melhores.", 41 | "br5e.hideDC.string": "??", 42 | "br5e.hideDC.choices.0": "Nunca", 43 | "br5e.hideDC.choices.1": "Somente PNJs", 44 | "br5e.hideDC.choices.2": "Sempre", 45 | "br5e.damagePromptEnabled.name": "Botão de Dano no Cartão do Chat", 46 | "br5e.damagePromptEnabled.hint": "Não rolar dano automaticamente, ao invés disso mostrar um botão de dano se houve uma rolagem de ataque ou salvaguarda.", 47 | "br5e.d20RollIconsEnabled.name": "Mostrar o ícone do dado d20 para multirolagens.", 48 | "br5e.d20RollIconsEnabled.hint": "Se habilitado, ataques, testes e salvaguardas mostram a rolagem natural do dado.", 49 | 50 | "br5e.d20Mode.name": "Modo d20", 51 | "br5e.d20Mode.hint": "Determina como as jogadas de ataque, as testes de atributo e salvaguardas são mostrados. Se configurado como Jogada Simples, também habilita botões [-]/[+] nos cartões de chat das rolagens para vantagem e desvantagem.", 52 | "br5e.d20Mode.choices.1": "Jogada Simples (Shift p/ Vantagem, Ctrl p/ Desvantagem)", 53 | "br5e.d20Mode.choices.2": "Jogadas Duplas", 54 | "br5e.d20Mode.choices.3": "ゴゴ Ameaça Tripla ゴゴ", 55 | "br5e.d20Mode.choices.4": "Consultar para (Des)Vantagens", 56 | 57 | "br5e.critBehavior.name": "Dano Crítico", 58 | "br5e.critBehavior.hint": "Determina como o dano crítico é calculado.", 59 | "br5e.critBehavior.choices.0": "Sem Dano Extra", 60 | "br5e.critBehavior.choices.1": "Rolar dados de Dano Crítico", 61 | "br5e.critBehavior.choices.2": "Rolar Dano Base, Maximizar Crítico", 62 | "br5e.critBehavior.choices.3": "Maximizar Base e Dano Crítico", 63 | 64 | "br5e.critString.name": "Indicador Crítico", 65 | "br5e.critString.hint": "Determina como os críticos são rotulados. Aparece como texto à direita da rolagem de dano crítico. Funciona apenas com Rolagens Melhores.", 66 | "br5e.critString.choices.2": "Crít", 67 | "br5e.critString.choices.3": "Crít!", 68 | "br5e.critString.choices.4": "(Crít)", 69 | "br5e.critString.choices.5": "Crítico", 70 | "br5e.critString.choices.6": "Crítico!", 71 | "br5e.critString.choices.7": "(Crítico)", 72 | "br5e.critString.choices.8": "POWWWWW!", 73 | 74 | "br5e.error.noSelectedActor": "Deve haver um personagem selecionado para usar este macro!", 75 | "br5e.error.noKnownItemOnActor": "não possui um item chamado", 76 | "br5e.error.noActorWithId": "Nenhum ator é conhecido com esse ID", 77 | "br5e.error.noActorWithName": "Nenhum ator conhecido com esse nome", 78 | "br5e.error.noItemWithId": "Nenhum item é conhecido com esse ID no ator", 79 | "br5e.error.noChargesLeft": "Este item ficou sem cargas!", 80 | "br5e.error.autoDestroy": "O(A) {name} desaparecerá assim que a última carga for consumida.", 81 | "br5e.error.rollEvaluation": "Avaliação da rolagem de dado falhou: {msg}", 82 | 83 | "br5e.settings.critThreshold": "Intervalo de Crítico", 84 | "br5e.settings.critDamage.name": "Dano Crítico Extra", 85 | "br5e.settings.critDamage.hint": "Dano Crítico Extra a ser aplicado quando pelo menos um dano for rolado", 86 | "br5e.settings.quickRollsLabel": "Rolagens Rápidas", 87 | "br5e.settings.quickRollsAltLabel": "Rolagens Rápidas Alternativas", 88 | "br5e.settings.quickRollsAltSubLabel": "(Ao pressionar Alt durante uma rolagem rápida)", 89 | "br5e.settings.description": "Descrição", 90 | "br5e.settings.attackRoll": "Jogada de Ataque", 91 | "br5e.settings.attackAndSave": "Ataque / Salvaguarda", 92 | "br5e.settings.saveDC": "CD de Salvaguarda", 93 | "br5e.settings.baseDamage": "Dano Base", 94 | "br5e.settings.altDamage": "Dano Alternativo", 95 | "br5e.settings.properties": "Propriedades", 96 | "br5e.settings.check": "Teste", 97 | "br5e.settings.consume": "Consumir", 98 | "br5e.settings.consumeQuantity": "Quantidade", 99 | "br5e.settings.consumeUses": "Usos", 100 | "br5e.settings.consumeResource": "Recurso", 101 | "br5e.settings.consumeRecharge": "Carga de Ação", 102 | "br5e.settings.useTemplate": "Posicionar Modelo Medido", 103 | "br5e.settings.label": "Rótulo", 104 | "br5e.settings.context": "Contexto", 105 | "br5e.settings.otherFormula": "Outra Fórmula", 106 | "br5e.settings.quickFlavor": "Extras", 107 | "br5e.settings.prompt": "Aviso Extendido", 108 | 109 | "br5e.buttons.roll": "Rolagem", 110 | "br5e.buttons.altRoll": "Rolagem Alt.", 111 | "br5e.buttons.attack": "Ataque", 112 | "br5e.buttons.saveDC": "CD de Salvaguarda", 113 | "br5e.buttons.saveAndDamage": "Rolagem (Salvaguarda & Dano)", 114 | "br5e.buttons.attackAndDamage": "Rolagem (Ataque & Dano)", 115 | "br5e.buttons.damage": "Dano", 116 | "br5e.buttons.altDamage": "Dano Alt.", 117 | "br5e.buttons.extraDamage": "Dano Extra", 118 | "br5e.buttons.verDamage": "Dano Versátil", 119 | "br5e.buttons.info": "Info", 120 | "br5e.buttons.itemUse": "Uso", 121 | "br5e.buttons.defaultSheetRoll": "Rolagem Padrão da Ficha", 122 | 123 | "br5e.chat.attack": "Ataque", 124 | "br5e.chat.check": "Teste", 125 | "br5e.chat.save": "Salvaguarda", 126 | "br5e.chat.damage": "Dano", 127 | "br5e.chat.healing": "Cura", 128 | "br5e.chat.altPrefix": "Alt.", 129 | "br5e.chat.extraPrefix": "Extra", 130 | "br5e.chat.consumedBySpell": "Consumido pela magia", 131 | "br5e.chat.abrVocal": "V", 132 | "br5e.chat.abrSomatic": "S", 133 | "br5e.chat.abrMaterial": "M", 134 | "br5e.chat.other": "Outro", 135 | "br5e.chat.advantage": "Vantagem", 136 | "br5e.chat.disadvantage": "Desvantagem", 137 | "br5e.chat.normal": "Normal", 138 | 139 | "br5e.chatContext.repeat": "Repetir a rolagem", 140 | 141 | "br5e.querying.title": "Qual o tipo de rolagem?", 142 | "br5e.querying.disadvantage": "Desvantagem", 143 | "br5e.querying.normal": "Normal", 144 | "br5e.querying.advantage": "Vantagem", 145 | 146 | "br5e.chat.multiRollButtons.advantage.hint": "Rolar com Vantagem (Shift)", 147 | "br5e.chat.multiRollButtons.disadvantage.hint": "Rolar com Desvantagem (Ctrl)", 148 | 149 | "br5e.chat.damageButtons.fullDamage.hint": "Clique para aplicar dano total ao(s) token(s) selecionado(s).", 150 | "br5e.chat.damageButtons.halfDamage.hint": "Clique para aplicar metade do dano ao(s) token(s) selecionado(s).", 151 | "br5e.chat.damageButtons.doubleDamage.hint": "Clique para aplicar dano dobrado ao(s) token(s) selecionado(s).", 152 | "br5e.chat.damageButtons.healing.hint": "Clique para aplicar a cura completa ao(s) token(s) selecionado(s).", 153 | "br5e.chat.damageButtons.crit.hint": "Clique para rolar dano crítico.", 154 | "br5e.chat.damageButtons.critPrompt.title": "Usar Dano Crítico?", 155 | "br5e.chat.damageButtons.critPrompt.yes": "Sim", 156 | "br5e.chat.damageButtons.critPrompt.no": "Não", 157 | 158 | "Ability Roll": "Rolagem de Atributo", 159 | "What type of roll?": "Qual tipo de rolagem?", 160 | "Ability Check": "Teste de Atributo", 161 | "Saving Throw": "Salvaguarda", 162 | 163 | "Not Proficient": "Não Proficiente", 164 | "Proficient": "Proficiente", 165 | "Jack of all Trades": "Pau Para Toda Obra", 166 | "Expertise": "Especialista", 167 | "Ritual": "Ritual", 168 | "Target: ": "Alvo: ", 169 | "Concentration": "Concentração", 170 | "AC": "CA", 171 | "Equipped": "Equipado", 172 | "Stealth Disadv.": "Desv. em Furtividade" 173 | } 174 | -------------------------------------------------------------------------------- /betterrolls5e/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "betterrolls5e", 3 | "title": "Better Rolls for 5e", 4 | "description": "This module overhauls rolls for the D&D 5e system, allowing \"dual rolls\" for d20 rolls, buttons that support full output to chat, and customization for said outputs.", 5 | "author": "Red Reign#5128", 6 | "authors": [], 7 | "url": "https://github.com/RedReign/FoundryVTT-BetterRolls5e/tree/master/betterrolls5e", 8 | "flags": {}, 9 | "version": "1.7.2", 10 | "minimumCoreVersion": "9", 11 | "compatibleCoreVersion": "9", 12 | "scripts": [ 13 | "greensock/dist/gsap.min.js" 14 | ], 15 | "esmodules": [ 16 | "./scripts/index.js" 17 | ], 18 | "styles": [ 19 | "./css/betterrolls5e.css" 20 | ], 21 | "languages": [ 22 | { 23 | "lang": "en", 24 | "name": "English", 25 | "path": "lang/en.json" 26 | }, 27 | { 28 | "lang": "fr", 29 | "name": "Français (French)", 30 | "path": "lang/fr.json" 31 | }, 32 | { 33 | "lang": "es", 34 | "name": "Español (Spanish)", 35 | "path": "lang/es.json" 36 | }, 37 | { 38 | "lang": "pt-BR", 39 | "name": "Português (Brasil)", 40 | "path": "lang/pt-BR.json" 41 | }, 42 | { 43 | "lang": "de", 44 | "name": "Deutsch (German)", 45 | "path": "lang/de.json" 46 | }, 47 | { 48 | "lang": "ja", 49 | "name": "日本語 (Japanese)", 50 | "path": "lang/ja.json" 51 | }, 52 | { 53 | "lang": "ko", 54 | "name": "한국어 (Korean)", 55 | "path": "lang/ko.json" 56 | }, 57 | { 58 | "lang": "cs", 59 | "name": "Česky", 60 | "path": "lang/cs.json" 61 | } 62 | ], 63 | "packs": [], 64 | "system": [ 65 | "dnd5e" 66 | ], 67 | "dependencies": [], 68 | "socket": true, 69 | "manifest": "https://raw.githubusercontent.com/RedReign/FoundryVTT-BetterRolls5e/master/betterrolls5e/module.json", 70 | "download": "https://github.com/RedReign/FoundryVTT-BetterRolls5e/releases/download/v1.7.2/module.zip", 71 | "protected": false, 72 | "coreTranslation": false, 73 | "minimumSystemVersion": "1.5.2", 74 | "library": false 75 | } 76 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/chat-message.js: -------------------------------------------------------------------------------- 1 | import { CustomItemRoll, CustomRoll } from "./custom-roll.js"; 2 | import { migrateChatMessage } from "./migration.js"; 3 | import { BRSettings } from "./settings.js"; 4 | import { i18n, Utils } from "./utils/index.js"; 5 | 6 | /** 7 | * Class that encapsulates a better rolls card at runtime. 8 | * When a chat message enters the chat it should be binded 9 | * with BetterRollsChatCard.bind(). 10 | */ 11 | export class BetterRollsChatCard { 12 | /** Min version to enable the card on, to prevent breakage */ 13 | static min_version = "1.4"; 14 | 15 | constructor(message, html) { 16 | this.updateBinding(message, html); 17 | } 18 | 19 | get message() { 20 | return game.messages.get(this.id); 21 | } 22 | 23 | /** 24 | * Initializes data. Used in the constructor or by BetterRollsChatCard.bind(). 25 | * @param {*} message 26 | * @param {*} html 27 | * @private 28 | */ 29 | updateBinding(message, html) { 30 | // IMPLEMENTATION WARNING: DO NOT STORE html into the class properties (NO this.html AT ALL) 31 | // Foundry will sometimes call renderChatMessage() multiple times with un-bound HTML, 32 | // and we can't do anything except rely on closures to handle those events. 33 | this.id = message.id; 34 | this.roll = CustomItemRoll.fromMessage(message); 35 | this.speaker = game.actors.get(message.data.speaker.actor); 36 | message.BetterRoll = this.roll; 37 | 38 | // Hide Save DCs 39 | const actor = this.speaker; 40 | if ((!actor && !game.user.isGM) || actor?.permission != 3) { 41 | html.find(".hideSave").text(i18n("br5e.hideDC.string")); 42 | } 43 | 44 | // Setup the events for card buttons (the permanent ones, not the hover ones) 45 | this._setupCardButtons(html); 46 | 47 | // Setup hover buttons when hovered (for optimization) 48 | // Just like with html, we cannot save hoverInitialized to the object 49 | let hoverInitialized = false; 50 | html.hover(async () => { 51 | if (!hoverInitialized) { 52 | hoverInitialized = true; 53 | await this._setupOverlayButtons(html); 54 | this._onHover(html); 55 | console.log("BetterRolls5e | Hover Buttons Initialized"); 56 | } 57 | }) 58 | } 59 | 60 | /** 61 | * Inflates an existing chat message, adding runtime elements 62 | * and events to it. Does nothing if the message is not the correct type. 63 | * @param {ChatMessage} message 64 | * @param {JQuery} html 65 | */ 66 | static async bind(message, html) { 67 | const chatCard = html.find('.red-full'); 68 | if (chatCard.length === 0) { 69 | return null; 70 | } 71 | 72 | // If the card needs to be migrated, skip the binding 73 | if (await migrateChatMessage(message)) { 74 | return null; 75 | } 76 | 77 | // Check if the card already exists 78 | const existing = message.BetterRollsCardBinding; 79 | if (existing) { 80 | console.log("BetterRolls5e | Retrieved existing card"); 81 | existing.updateBinding(message, chatCard); 82 | 83 | // Pulse the card to make it look more obvious 84 | // Wait for the event queue before doing so to allow CSS calculations to work, 85 | // otherwise the border color will be incorrectly transparent 86 | window.setTimeout(() => { 87 | gsap?.from(html.get(), { 88 | "border-color": "red", 89 | "box-shadow": "0 0 6px inset #ff6400", 90 | duration: 2}); 91 | }, 0); 92 | 93 | // Scroll to bottom if the last card had updated 94 | const last = game.messages.contents[game.messages.size - 1]; 95 | if (last?.id === existing.id) { 96 | window.setTimeout(() => { ui.chat.scrollBottom(); }, 0); 97 | } 98 | 99 | return existing; 100 | } else { 101 | const newCard = new BetterRollsChatCard(message, chatCard); 102 | message.BetterRollsCardBinding = newCard; 103 | return newCard; 104 | } 105 | } 106 | 107 | /** 108 | * Adds right click menu options 109 | * @param {*} html 110 | * @param {*} options 111 | */ 112 | static addOptions(html, options) { 113 | const getBinding = (li) => game.messages.get(li.data("messageId"))?.BetterRollsCardBinding; 114 | 115 | options.push({ 116 | name: i18n("br5e.chatContext.repeat"), 117 | icon: '', 118 | condition: li => { 119 | const binding = getBinding(li); 120 | return binding && binding.roll.canRepeat(); 121 | }, 122 | callback: li => getBinding(li)?.roll.repeat({ event }) 123 | }) 124 | } 125 | 126 | /** 127 | * Internal method to setup the temporary buttons used to update advantage or disadvantage, 128 | * as well as those that that affect damage 129 | * entries, like crit rolls and damage application. 130 | * @private 131 | */ 132 | async _setupOverlayButtons(html) { 133 | // Add reroll button 134 | if (this.roll?.canRepeat()) { 135 | const templateHeader = await renderTemplate("modules/betterrolls5e/templates/red-overlay-header.html"); 136 | html.find(".card-header").append($(templateHeader)); 137 | } 138 | 139 | // Multiroll buttons (perhaps introduce a new toggle property?) 140 | if (this.roll) { 141 | const templateMulti = await renderTemplate("modules/betterrolls5e/templates/red-overlay-multiroll.html"); 142 | 143 | // Add multiroll overlay buttons to the DOM. 144 | for (const entry of this.roll.entries) { 145 | if (entry.type === "multiroll" && !entry.rollState && entry.entries?.length === 1) { 146 | const element = html.find(`.red-dual[data-id=${entry.id}] .dice-row.red-totals`); 147 | element.append($(templateMulti)); 148 | } 149 | } 150 | 151 | // Handle clicking the multi-roll overlay buttons 152 | html.find(".multiroll-overlay-br button").click(async event => { 153 | event.preventDefault(); 154 | event.stopPropagation(); 155 | const button = event.currentTarget; 156 | const id = $(button).parents(".red-dual").attr('data-id'); 157 | const action = button.dataset.action; 158 | if (action === "rollState") { 159 | const rollState = button.dataset.state; 160 | if (await this.roll.updateRollState(id, rollState)) { 161 | await this.roll.update(); 162 | } 163 | } 164 | }); 165 | } 166 | 167 | // Setup augment crit and apply damage button 168 | const templateName = (BRSettings.chatDamageButtonsEnabled) ? "red-overlay-damage" : "red-overlay-damage-crit-only"; 169 | const templateDamage = await renderTemplate(`modules/betterrolls5e/templates/${templateName}.html`); 170 | const dmgElements = html.find('.dice-total .red-base-die, .dice-total .red-extra-die').parents('.dice-row').toArray(); 171 | const customElements = html.find('[data-type=custom] .red-base-die').toArray(); 172 | 173 | // Add chat damage buttons 174 | [...dmgElements, ...customElements].forEach(element => { 175 | element = $(element); 176 | element.append($(templateDamage)); 177 | 178 | // Remove crit button if already rolled 179 | // TODO: Move this elsewhere. There's a known bug when crit settings are changed suddenly 180 | // If Crit (setting) is disabled, then re-enabled, crit buttons don't get re-added 181 | const id = element.parents('.dice-roll').attr('data-id'); 182 | const entry = this.roll?.getEntry(id); 183 | if (!this.roll?.canCrit(entry)) { 184 | element.find('.crit-button').remove(); 185 | } 186 | }); 187 | 188 | // Handle apply damage overlay button events 189 | html.find('.apply-damage-buttons button').click(async ev => { 190 | ev.preventDefault(); 191 | ev.stopPropagation(); 192 | 193 | // find out the proper dmg thats supposed to be applied 194 | const dmgElement = $(ev.target.parentNode.parentNode.parentNode.parentNode); 195 | const damageType = dmgElement.find(".dice-total").attr("data-damagetype"); 196 | let dmg = dmgElement.find('.red-base-die').text(); 197 | 198 | if (dmgElement.find('.red-extra-die').length > 0) { 199 | const critDmg = dmgElement.find('.red-extra-die').text(); 200 | const dialogPosition = { 201 | x: ev.originalEvent.screenX, 202 | y: ev.originalEvent.screenY 203 | }; 204 | 205 | dmg = await this._resolveCritDamage(Number(dmg), Number(critDmg), dialogPosition); 206 | } 207 | 208 | // getting the modifier depending on which of the buttons was pressed 209 | const modifier = $(ev.target).closest("button").attr('data-modifier'); 210 | 211 | // applying dmg to the targeted token and sending only the span that the button sits in 212 | for (const actor of Utils.getTargetActors()) { 213 | await this.applyDamage(actor, damageType, dmg, modifier) 214 | } 215 | 216 | setTimeout(() => { 217 | if (canvas.hud.token._displayState && canvas.hud.token._displayState !== 0) { 218 | canvas.hud.token.render(); 219 | } 220 | }, 50); 221 | }); 222 | 223 | // Handle crit button application event 224 | html.find('.crit-button').off().on('click', async (ev) => { 225 | ev.preventDefault(); 226 | ev.stopPropagation(); 227 | const group = $(ev.target).parents('.dice-roll').attr('data-group'); 228 | if (await this.roll.forceCrit(group)) { 229 | await this.roll.update(); 230 | } 231 | }); 232 | 233 | // Enable Hover Events (to show/hide the elements) 234 | this._onHoverEnd(html); 235 | html.hover(this._onHover.bind(this, html), this._onHoverEnd.bind(this, html)); 236 | } 237 | 238 | async applyDamage(actor, damageType, damage, modifier) { 239 | if (damageType === "temphp" && modifier < 0) { 240 | const healing = Math.abs(modifier) * damage; 241 | const actorData = actor.data.data; 242 | if (actorData.attributes.hp.temp > 0) { 243 | const overwrite = await Dialog.confirm({ 244 | title: i18n("br5e.chat.damageButtons.tempOverwrite.title"), 245 | content: i18n("br5e.chat.damageButtons.tempOverwrite.content", { 246 | original: actorData.attributes.hp.temp, 247 | new: healing 248 | }) 249 | }); 250 | 251 | if (!overwrite) return; 252 | } 253 | 254 | await actor.update({ "data.attributes.hp.temp": healing }); 255 | } else { 256 | await actor.applyDamage(damage, modifier); 257 | } 258 | } 259 | 260 | _onHover(html) { 261 | const hasPermission = this.roll.hasPermission; 262 | html.find(".die-result-overlay-br").show(); 263 | 264 | // Apply Damage / Augment Crit 265 | const controlled = canvas?.tokens.controlled.length > 0; 266 | html.find('.multiroll-overlay-br').toggle(hasPermission); 267 | html.find('.crit-button').toggle(hasPermission); 268 | html.find('.apply-damage-buttons').toggle(controlled); 269 | } 270 | 271 | _onHoverEnd(html) { 272 | html.find(".die-result-overlay-br").attr("style", "display: none;"); 273 | } 274 | 275 | /** 276 | * Displays a dialog if both dmg and critdmg have a value, otherwise just returns the first not null one. 277 | * @private 278 | */ 279 | async _resolveCritDamage(dmg, critdmg, position) { 280 | if (dmg && critdmg) { 281 | return await new Promise(async (resolve, reject) => { 282 | const options = { 283 | left: position.x, 284 | top: position.y, 285 | width: 100 286 | }; 287 | 288 | const data = { 289 | title: i18n("br5e.chat.damageButtons.critPrompt.title"), 290 | content: "", 291 | buttons: { 292 | one: { 293 | icon: '', 294 | label: i18n("br5e.chat.damageButtons.critPrompt.yes"), 295 | callback: () => { resolve(dmg + critdmg); } 296 | }, 297 | two: { 298 | icon: '', 299 | label: i18n("br5e.chat.damageButtons.critPrompt.no"), 300 | callback: () => { resolve(dmg); } 301 | } 302 | }, 303 | default: "two" 304 | } 305 | 306 | new Dialog(data, options).render(true); 307 | }); 308 | } 309 | 310 | return dmg || critdmg; 311 | } 312 | 313 | /** 314 | * Bind card button events. These are the clickable action buttons. 315 | * @private 316 | */ 317 | _setupCardButtons(html) { 318 | html.find(".card-buttons").off() 319 | html.off().click(async event => { 320 | const button = event.target.closest("button"); 321 | if (!button) return; 322 | 323 | event.preventDefault(); 324 | button.disabled = true; 325 | 326 | const action = button.dataset.action; 327 | try { 328 | await this._performAction(action, button.dataset, event); 329 | } finally { 330 | // Re-enable the button after a delay 331 | setTimeout(() => (button.disabled = false), 0); 332 | } 333 | }); 334 | } 335 | 336 | async _performAction(action, data, event={}) { 337 | if (action === "save") { 338 | const actors = Utils.getTargetActors({ required: true }); 339 | const ability = data.ability; 340 | const params = Utils.eventToAdvantage(event); 341 | for (const actor of actors) { 342 | CustomRoll.rollAttribute(actor, ability, "save", params); 343 | } 344 | } else if (action === "damage") { 345 | const group = encodeURIComponent(data.group); 346 | if (await this.roll.rollDamage(group)) { 347 | await this.roll.update(); 348 | } 349 | } else if (action === "repeat") { 350 | await this.roll.repeat({ event }); 351 | } else if (action === "apply-active-effects") { 352 | if (!window.DAE) { 353 | return ui.notifications.warn(i18n("br5e.error.noDAE")); 354 | } 355 | 356 | const roll = this.roll; 357 | let item = await roll.getItem(); 358 | if (data.ammo) { 359 | item = item.parent.items.get(item.data.data.consume.target) 360 | } 361 | 362 | let targets 363 | if (item.data.data?.target?.type === 'self' && canvas.tokens?.controlled?.length) { 364 | targets = Utils.getTargetTokens(); 365 | } else { 366 | targets = game.user.targets.size ? game.user.targets : Utils.getTargetTokens(); 367 | } 368 | 369 | window.DAE.doEffects(item, true, targets, { 370 | whisper: false, 371 | spellLevel: roll.params.slotLevel, 372 | damageTotal: roll.totalDamage, 373 | critical: roll.isCrit, 374 | itemCardId: this.id 375 | }); 376 | } 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/extended-prompt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A specialized Dialog subclass for an extended prompt for Better Rolls 3 | * @type {Dialog} 4 | */ 5 | export default class ExtendedPrompt extends Dialog { 6 | constructor(item, dialogData={}, options={}) { 7 | super(dialogData, options); 8 | this.options.classes = ["dnd5e", "dialog"]; 9 | 10 | /** 11 | * Store a reference to the Item entity being used 12 | * @type {Item5e} 13 | */ 14 | this.item = item; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/fields.js: -------------------------------------------------------------------------------- 1 | import { dnd5e, i18n, ActorUtils, ItemUtils, Utils } from "./utils/index.js"; 2 | import { BRSettings, getSettings } from "./settings.js"; 3 | import { isSave } from "./betterrolls5e.js"; 4 | 5 | /** 6 | * Roll type for advantage/disadvantage/etc 7 | * @typedef {"highest" | "lowest" | "first" | null} RollState 8 | */ 9 | 10 | /** 11 | * Provides utility functions that can be used to create model elements 12 | * for new rolls. 13 | */ 14 | export class RollFields { 15 | /** 16 | * Returns header data to be used for rendering 17 | * @param {object} options 18 | * @param {string?} options.img image to show, falls back to item image 19 | * @param {string} options.title label, falls back to item name 20 | * @param {Item?} options.item 21 | * @param {Actor?} options.actor 22 | * @param {number?} options.slotLevel 23 | * @returns {import("./renderer.js").HeaderDataProps} 24 | */ 25 | static constructHeaderData(options={}) { 26 | const { item, slotLevel } = options; 27 | const actor = options?.actor ?? item?.actor; 28 | const img = options.img ?? item?.img ?? ActorUtils.getImage(actor); 29 | let title = options.title ?? item?.name ?? actor?.name ?? ''; 30 | if (item?.data.type === "spell" && slotLevel && slotLevel != item.data.data.level) { 31 | title += ` (${dnd5e.spellLevels[slotLevel]})`; 32 | } 33 | 34 | return { type: "header", img, title }; 35 | } 36 | 37 | /** 38 | * Constructs multiroll data to be used for rendering 39 | * @param {object} options Roll options used to construct the multiroll. 40 | * @param {string | Roll} options.formula formula or roll object to use when constructing the multiroll 41 | * @param {number?} options.critThreshold optional minimum roll on the dice to cause a critical roll. 42 | * @param {number?} options.numRolls number of rolls to perform 43 | * @param {string?} options.title title to display above the roll 44 | * @param {RollState?} options.rollState highest or lowest or first or none 45 | * @param {string?} options.rollType metadata param for attack vs damage. 46 | * @param {boolean?} options.elvenAccuracy whether the actor should apply elven accuracy 47 | * @param {boolean?} options.forceCrit optional flag to force a crit result 48 | * @param {BRSettings} options.settings additional settings to override 49 | * @returns {import("./renderer.js").MultiRollDataProps} 50 | */ 51 | static constructMultiRoll(options={}) { 52 | const { critThreshold, title, rollType, elvenAccuracy } = options; 53 | if (!options.formula) { 54 | console.error("No formula given for multi-roll"); 55 | return; 56 | } 57 | 58 | // Extract info from the formula, to know if it was rolled with advantage/disadvantage 59 | // The rollstate in the options has higher priority than whatever was part of the original 60 | const parsedData = Utils.parseD20Formula(options.formula); 61 | const formula = parsedData.formula; 62 | const rollState = parsedData.rollState ?? options.rollState; 63 | 64 | const d20Mode = getSettings(options.settings).d20Mode; 65 | let numRolls = d20Mode === 4 ? 1 : (options.numRolls || d20Mode); 66 | if (!options.numRolls) { 67 | if (rollState === "first" && !options.numRolls) { 68 | numRolls = 1; 69 | } else if (rollState && numRolls == 1) { 70 | numRolls = 2; 71 | } 72 | 73 | // Apply elven accuracy 74 | if (numRolls == 2 && elvenAccuracy && rollState !== "lowest") { 75 | numRolls = 3; 76 | } 77 | } 78 | 79 | try { 80 | // Split the D20 and bonuses. We assume the first is a d20 roll always... 81 | const fullRoll = new Roll(formula); 82 | const baseRoll = new Roll(fullRoll.terms[0].formula ?? fullRoll.terms[0]); 83 | const bonusRollFormula = [...fullRoll.terms.slice(1).map(t => t.formula ?? t)].join(' ') || "0"; 84 | const bonusRoll = new Roll(bonusRollFormula).roll({async: false}); 85 | 86 | // Populate the roll entries 87 | const entries = []; 88 | for (let i = 0; i < numRolls; i++) { 89 | entries.push(Utils.processRoll(baseRoll.reroll({async: false}), critThreshold, [20], bonusRoll)); 90 | } 91 | 92 | // Mark ignored rolls if advantage/disadvantage 93 | if (rollState) { 94 | const rollTotals = entries.map(r => r.roll.total); 95 | let chosenResult = rollTotals[0]; 96 | if (rollState == "highest") { 97 | chosenResult = Math.max(...rollTotals); 98 | } else if (rollState == "lowest") { 99 | chosenResult = Math.min(...rollTotals); 100 | } 101 | 102 | // Mark the non-results as ignored 103 | entries.filter(r => r.roll.total != chosenResult).forEach(r => r.ignored = true); 104 | } 105 | 106 | return { 107 | type: "multiroll", 108 | title, 109 | critThreshold, 110 | elvenAccuracy, 111 | rollState, 112 | rollType, 113 | formula, 114 | entries, 115 | forceCrit: options.forceCrit, 116 | isCrit: options.forceCrit || entries.some(e => !e.ignored && e.isCrit), 117 | bonus: bonusRoll 118 | }; 119 | } catch (err) { 120 | ui.notifications.error(i18n("br5e.error.rollEvaluation", { msg: err.message})); 121 | throw err; // propagate the error 122 | } 123 | } 124 | 125 | /** 126 | * Constructs multiroll (attack) data to be used for data. 127 | * @param {object} options 128 | * @param {string?} options.formula optional formula to use instead of the attack formula 129 | * @param {Actor?} options.actor Actor to derive roll data from if item is not given 130 | * @param {Item?} options.item Item to derive attack formula or roll data from 131 | * @param {"weapon" | "spell" | undefined} options.itemType Type of attack. Used if item is null. 132 | * @param {number?} options.numRolls number of rolls to perform 133 | * @param {string?} options.title Alternative title to use 134 | * @param {number?} options.critThreshold override 135 | * @param {string?} options.abilityMod override for the default item abilty mod 136 | * @param {RollState} options.rollState 137 | * @param {number} options.slotLevel 138 | */ 139 | static async constructAttackRoll(options={}) { 140 | const { formula, item, rollState, slotLevel } = options; 141 | const actor = options.actor ?? item?.actor; 142 | 143 | // Get critical threshold 144 | const critThreshold = options.critThreshold ?? 145 | ItemUtils.getCritThreshold(item) ?? 146 | ActorUtils.getCritThreshold(actor, options.itemType) ?? 147 | 20; 148 | 149 | const abilityMod = options.abilityMod ?? item?.abilityMod; 150 | const elvenAccuracy = ActorUtils.testElvenAccuracy(actor, abilityMod); 151 | 152 | let title = options.title; 153 | 154 | // Get ammo bonus and add to title if title not given 155 | // Note that "null" is a valid title, so we can't override that 156 | if (typeof title === 'undefined') { 157 | title = i18n("br5e.chat.attack"); 158 | const consume = item?.data.data.consume; 159 | if ((consume?.type === 'ammo') && !!actor.items) { 160 | const ammo = actor.items.get(consume.target); 161 | title += ` [${ammo.name}]`; 162 | } 163 | } 164 | 165 | // Get Roll. Use Formula if given, otherwise get it from the item 166 | let roll = null; 167 | if (formula) { 168 | const rollData = Utils.getRollData({item, actor, abilityMod, slotLevel }); 169 | roll = new Roll(formula, rollData); 170 | } else if (item) { 171 | roll = await ItemUtils.getAttackRoll(item); 172 | } else { 173 | return null; 174 | } 175 | 176 | // Construct the multiroll 177 | return RollFields.constructMultiRoll({ 178 | ...options, 179 | formula: roll, 180 | rollState, 181 | title, 182 | critThreshold, 183 | elvenAccuracy, 184 | rollType: "attack" 185 | }); 186 | } 187 | 188 | /** 189 | * Constructs and rolls damage data for a damage entry in an item. 190 | * Can roll for an index, versatile (replaces first), or other. 191 | * @param {object} options 192 | * @param {string} options.formula optional formula to use, higher priority over the item formula 193 | * @param {Actor} options.actor 194 | * @param {Item} options.item 195 | * @param {number | "versatile" | "other"} options.damageIndex 196 | * @param {number?} options.slotLevel 197 | * @param {string?} options.context Optional damage context. Defaults to the configured damage context 198 | * @param {string?} options.damageType 199 | * @param {string?} options.title title to display. If not given defaults to damage type 200 | * @param {boolean?} options.isCrit Whether to roll crit damage 201 | * @param {number?} options.extraCritDice sets the savage property. Falls back to using the item if not given, or false otherwise. 202 | * @param {BRSettings} options.settings Override config to use for the roll 203 | * @returns {import("./renderer.js").DamageDataProps} 204 | */ 205 | static constructDamageRoll(options={}) { 206 | const { item, damageIndex, slotLevel, isCrit } = options; 207 | const actor = options?.actor ?? item?.actor; 208 | const isVersatile = damageIndex === "versatile"; 209 | const isFirst = damageIndex === 0 || isVersatile; 210 | const extraCritDice = options.extraCritDice ?? ItemUtils.getExtraCritDice(item); 211 | 212 | const settings = getSettings(options.settings); 213 | const { critBehavior } = settings; 214 | 215 | const rollData = item ? 216 | ItemUtils.getRollData(item, { slotLevel }) : 217 | actor?.getRollData(); 218 | 219 | // Bonus parts to add to the formula 220 | const parts = []; 221 | 222 | // Determine certain fields based on index 223 | let title = options.title; 224 | let context = options.context; 225 | let damageType = options.damageType; 226 | let formula = options.formula; 227 | 228 | // If no formula was given, derive from the item 229 | if (!formula && item) { 230 | const itemData = item.data.data; 231 | const flags = item.data.flags.betterRolls5e; 232 | 233 | if (damageIndex === "other") { 234 | formula = itemData.formula; 235 | title = title ?? i18n("br5e.chat.other"); 236 | context = context ?? flags.quickOther.context; 237 | } else { 238 | // If versatile, use properties from the first entry 239 | const trueIndex = isFirst ? 0 : damageIndex; 240 | 241 | formula = isVersatile ? itemData.damage.versatile : itemData.damage.parts[trueIndex][0]; 242 | damageType = damageType ?? itemData.damage.parts[trueIndex][1]; 243 | context = context ?? flags.quickDamage.context?.[trueIndex]; 244 | 245 | // Scale damage if its the first entry 246 | if (formula && isFirst) { 247 | formula = ItemUtils.scaleDamage(item, slotLevel, damageIndex, rollData) || formula; 248 | } 249 | 250 | // Add any roll bonuses but only to the first entry 251 | const isAmmo = item.data.type === "consumable" && item.data.data.consumableType === "ammo"; 252 | if (isFirst && rollData.bonuses && !isAmmo) { 253 | const actionType = `${itemData.actionType}`; 254 | const bonus = rollData.bonuses[actionType]?.damage; 255 | if (bonus && (parseInt(bonus) !== 0)) { 256 | parts.unshift(bonus); 257 | } 258 | } 259 | } 260 | } 261 | 262 | // Require a formula to continue 263 | if (!formula) { 264 | if (isVersatile) { 265 | ui.notifications.warn('br5e.error.noVersatile', { localize: true }); 266 | } 267 | return null; 268 | } 269 | 270 | // Assemble roll data and defer to the general damage construction 271 | try { 272 | const rollFormula = [formula, ...parts].join("+"); 273 | const baseRoll = new Roll(rollFormula, rollData).roll({async: false}); 274 | const total = baseRoll.total; 275 | 276 | // Roll crit damage if relevant 277 | let critRoll = null; 278 | if (damageIndex !== "other") { 279 | if (isCrit && critBehavior !== "0") { 280 | critRoll = ItemUtils.getCritRoll(baseRoll.formula, total, { settings, extraCritDice }); 281 | } 282 | } 283 | 284 | return { 285 | type: "damage", 286 | damageIndex, 287 | title: options.title ?? title, 288 | damageType, 289 | context, 290 | extraCritDice, 291 | baseRoll, 292 | critRoll 293 | }; 294 | } catch (err) { 295 | ui.notifications.error(i18n("br5e.error.rollEvaluation", { msg: err.message})); 296 | throw err; // propagate the error 297 | } 298 | } 299 | 300 | /** 301 | * Constructs and rolls damage for a crit bonus entry. 302 | * This is a simpler version of the regular damage entry. 303 | * @param {object} options 304 | * @param {string} options.formula optional formula to use, higher priority over the item formula 305 | * @param {Actor} options.actor 306 | * @param {Item} options.item 307 | * @param {number | "versatile" | "other"} options.damageIndex 308 | * @param {number?} options.slotLevel 309 | * @param {string?} options.context Optional damage context. Defaults to the configured damage context 310 | * @param {string?} options.damageType 311 | * @param {string?} options.title title to display. If not given defaults to damage type 312 | * @param {BRSettings} options.settings Override config to use for the roll 313 | * @returns {import("./renderer.js").DamageDataProps} 314 | */ 315 | static constructCritDamageRoll(options={}) { 316 | const { item, slotLevel } = options; 317 | const actor = options?.actor ?? item?.actor; 318 | const rollData = item ? 319 | ItemUtils.getRollData(item, { slotLevel }) : 320 | actor?.getRollData(); 321 | 322 | // Determine certain fields based on index 323 | let title = options.title; 324 | let context = options.context; 325 | let damageType = options.damageType; 326 | let formula = options.formula; 327 | 328 | // If no formula was given, derive from the item 329 | if (!formula && item) { 330 | formula = item.data.data.critical?.damage; 331 | } 332 | 333 | // Require a formula to continue 334 | if (!formula) { 335 | return null; 336 | } 337 | 338 | // Assemble roll data and defer to the general damage construction 339 | try { 340 | const critRoll = new Roll(formula, rollData).roll({async: false}); 341 | 342 | return { 343 | type: "crit", 344 | title: options.title ?? title, 345 | damageType, 346 | context, 347 | critRoll 348 | }; 349 | } catch (err) { 350 | ui.notifications.error(i18n("br5e.error.rollEvaluation", { msg: err.message})); 351 | throw err; // propagate the error 352 | } 353 | } 354 | 355 | /** 356 | * Creates multiple item damage rolls. This returns an array, 357 | * so when adding to a model list, add them separately or use the splat operator. 358 | * @param {object} options Remaining options that get funneled to createDamageRoll. 359 | * @param {*} options.item 360 | * @param {number[] | "all" | number} options.index one more or damage indices to roll for. 361 | * @param {boolean?} options.versatile should the first damage entry be replaced with versatile 362 | * @param {BRSettings} options.settings Override settings to use for the roll 363 | * @returns {import("./renderer.js").DamageDataProps[]} 364 | */ 365 | static constructItemDamageRange(options={}) { 366 | let index = options.index; 367 | const { formula, item } = options; 368 | 369 | // If formula is given or there is a single index, fallback to the singular function 370 | if (formula || Number.isInteger(index) || !index) { 371 | let damageIndex = Number.isInteger(index) ? Number(index) : options.damageIndex; 372 | if (index === 0 && options.versatile) { 373 | damageIndex = "versatile"; 374 | } 375 | 376 | if (!formula && damageIndex == null) { 377 | console.error("BetterRolls | Missing damage index on item range roll...invalid data"); 378 | return []; 379 | } 380 | 381 | return [RollFields.constructDamageRoll({ ...options, damageIndex })].filter(d => d); 382 | } 383 | 384 | const wasAll = index === "all"; 385 | 386 | // If all, damage indices between a sequential list from 0 to length - 1 387 | if (index === "all") { 388 | const numEntries = item.data.data.damage.parts.length; 389 | index = [...Array(numEntries).keys()]; 390 | } 391 | 392 | // If versatile, replace any "first" entry (only those) 393 | if (options.versatile && wasAll) { 394 | index = index.map(i => i === 0 ? "versatile" : i); 395 | } 396 | 397 | return index.map(i => { 398 | return RollFields.constructDamageRoll({...options, item, damageIndex: i}); 399 | }).filter(d => d); 400 | } 401 | 402 | /** 403 | * Generates the html for a save button to be inserted into a chat message. Players can click this button to perform a roll through their controlled token. 404 | * @returns {import("./renderer.js").ButtonSaveProps} 405 | */ 406 | static constructSaveButton({ item, abl = null, dc = null, context = null, settings }) { 407 | const actor = item?.actor; 408 | const saveData = ItemUtils.getSave(item); 409 | if (abl) { saveData.ability = abl; } 410 | if (dc) { saveData.dc = dc; } 411 | if (context) { saveData.context = context; } 412 | 413 | // Determine whether the DC should be hidden 414 | const hideDCSetting = getSettings(settings).hideDC; 415 | const hideDC = (hideDCSetting == "2" || (hideDCSetting == "1" && actor.data.type == "npc")); 416 | 417 | return { type: "button-save", hideDC, ...saveData }; 418 | } 419 | 420 | /** 421 | * Construct one or more model entries from a field and some metadata 422 | * @param {} field 423 | * @param {*} metadata 424 | * @param {object} settings BetterRoll settings overrides 425 | * @returns {Promise>} 426 | */ 427 | static async constructModelsFromField(field, metadata, settings={}) { 428 | let [fieldType, data] = field; 429 | data = mergeObject(metadata, data ?? {}, { recursive: false }); 430 | 431 | const { item, actor } = data; 432 | settings = getSettings(settings); 433 | 434 | switch (fieldType) { 435 | case 'header': 436 | return [RollFields.constructHeaderData(data)]; 437 | case 'attack': 438 | return [await RollFields.constructAttackRoll(data)]; 439 | case 'toolcheck': 440 | case 'tool': 441 | case 'check': 442 | return [RollFields.constructMultiRoll({ 443 | ...data, 444 | formula: data.formula ?? (await ItemUtils.getToolRoll(data.item, data.bonus)).formula, 445 | })]; 446 | case 'damage': 447 | return RollFields.constructItemDamageRange(data); 448 | case 'other': 449 | return RollFields.constructItemDamageRange({ ...data, damageIndex: "other" }); 450 | case 'ammo': 451 | if (!data.ammo) return []; 452 | 453 | // Only add ammo damage if the ammunition is a consumable with type ammo 454 | const ammo = data.ammo; 455 | if (ammo.data.type !== "consumable" || ammo.data.data.consumableType !== "ammo") { 456 | return []; 457 | } 458 | 459 | return RollFields.constructItemDamageRange({ 460 | ...data, 461 | item: ammo, 462 | index: "all", 463 | context: `${ammo.name}` 464 | }); 465 | case 'savedc': 466 | // {customAbl: null, customDC: null} 467 | return [RollFields.constructSaveButton({ settings, ...data })]; 468 | case 'ammosavedc': 469 | // {customAbl: null, customDC: null} 470 | if (!data.ammo || !isSave(data.ammo)) return []; 471 | 472 | return [RollFields.constructSaveButton({ 473 | settings, 474 | ...data, 475 | item: data.ammo, 476 | context: `${data.ammo.name}` 477 | })]; 478 | case 'custom': 479 | const { title, rolls, formula, rollState } = data; 480 | const rollData = Utils.getRollData({ item, actor }); 481 | const resolvedFormula = new Roll(formula, rollData).formula; 482 | return [RollFields.constructMultiRoll({ 483 | title, rollState, 484 | formula: resolvedFormula || "1d20", 485 | numRolls: rolls || 1, 486 | rollType: "custom" 487 | })]; 488 | case 'description': 489 | case 'desc': 490 | case 'text': 491 | const textFieldValue = data.text ?? data.content ?? item?.data.data.description.value; 492 | if (textFieldValue) { 493 | return [{ 494 | type: "description", 495 | content: TextEditor.enrichHTML(textFieldValue ?? '').trim() 496 | }]; 497 | } 498 | break; 499 | case 'flavor': 500 | const message = data?.text ?? item.data.data.chatFlavor; 501 | if (message) { 502 | return [{ 503 | type: "description", 504 | isFlavor: true, 505 | content: message 506 | }]; 507 | } 508 | break; 509 | case 'crit': 510 | return [RollFields.constructCritDamageRoll({ item, ...data })]; 511 | } 512 | 513 | return []; 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { BRSettings } from "./settings.js"; 2 | import { BetterRollsChatCard } from "./chat-message.js"; 3 | import { addItemSheetButtons, BetterRolls } from "./betterrolls5e.js"; 4 | import { ItemUtils, Utils } from "./utils/index.js"; 5 | import { addBetterRollsContent } from "./item-tab.js"; 6 | import { patchCoreFunctions } from "./patching/index.js" 7 | import { migrate } from "./migration.js"; 8 | 9 | // Attaches BetterRolls to actor sheet 10 | Hooks.on("renderActorSheet5e", (app, html, data) => { 11 | const triggeringElement = ".item .item-name h4"; 12 | const buttonContainer = ".item-properties"; 13 | 14 | // this timeout allows other modules to modify the sheet before we do 15 | setTimeout(() => { 16 | if (game.settings.get("betterrolls5e", "rollButtonsEnabled")) { 17 | addItemSheetButtons(app.object, html, data, triggeringElement, buttonContainer) 18 | } 19 | }, 0); 20 | }); 21 | 22 | // Attaches BetterRolls to item sheet 23 | Hooks.on("renderItemSheet5e", (app, html, data) => { 24 | addBetterRollsContent(app, html, data); 25 | }); 26 | 27 | Hooks.once("init", () => { 28 | BRSettings.init(); 29 | patchCoreFunctions(); 30 | 31 | // Setup template partials 32 | const prefix = "modules/betterrolls5e/templates" 33 | loadTemplates([ 34 | `${prefix}/red-damage-crit.html` 35 | ]); 36 | }); 37 | 38 | Hooks.on("ready", async () => { 39 | await migrate(); 40 | 41 | // Make a combined damage type array that includes healing 42 | const dnd5e = CONFIG.DND5E; 43 | CONFIG.betterRolls5e.combinedDamageTypes = mergeObject(duplicate(dnd5e.damageTypes), dnd5e.healingTypes); 44 | 45 | // Updates crit text from the dropdown. 46 | let critText = BRSettings.critString; 47 | if (critText.includes("br5e.critString")) { 48 | critText = i18n(critText); 49 | game.settings.set("betterrolls5e", "critString", critText); 50 | } 51 | 52 | // Set up socket 53 | game.socket.on("module.betterrolls5e", (data) => { 54 | if (data?.action === "roll-sound") { 55 | Utils.playDiceSound(); 56 | } 57 | }); 58 | 59 | // Initialize Better Rolls 60 | window.BetterRolls = BetterRolls(); 61 | Hooks.call("readyBetterRolls"); 62 | }); 63 | 64 | // Create flags for item when it's first created 65 | Hooks.on("preCreateItem", (item) => ItemUtils.ensureFlags(item)); 66 | 67 | // Modify context menu for damage rolls (they break) 68 | Hooks.on("getChatLogEntryContext", (html, options) => { 69 | let contextDamageLabels = [ 70 | game.i18n.localize("DND5E.ChatContextDamage"), 71 | game.i18n.localize("DND5E.ChatContextHealing"), 72 | game.i18n.localize("DND5E.ChatContextDoubleDamage"), 73 | game.i18n.localize("DND5E.ChatContextHalfDamage") 74 | ]; 75 | 76 | for (let i=options.length-1; i>=0; i--) { 77 | let option = options[i]; 78 | if (contextDamageLabels.includes(option.name)) { 79 | option.condition = li => canvas.tokens.controlled.length && li.find(".dice-roll").length && !li.find(".red-full").length; 80 | } 81 | } 82 | }); 83 | 84 | // Bind to any newly rendered chat cards at runtime 85 | // For whatever reason, this callback is sometimes called with unattached html elements 86 | Hooks.on("renderChatMessage", BetterRollsChatCard.bind); 87 | Hooks.on("getChatLogEntryContext", BetterRollsChatCard.addOptions); 88 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/item-tab.js: -------------------------------------------------------------------------------- 1 | import { isAttack, isSave } from "./betterrolls5e.js"; 2 | import { i18n, ItemUtils } from "./utils/index.js"; 3 | 4 | let activate = false; 5 | 6 | /** 7 | * Adds adds the Better Rolls tab to an item's sheet. Should only be called when the sheet is rendered. 8 | */ 9 | export async function addBetterRollsContent(app, protoHtml) { 10 | const item = app.object; 11 | const itemData = item.data.data; 12 | 13 | if (item.actor && item.actor.permission < 3) { return; } 14 | if (CONFIG.betterRolls5e.validItemTypes.indexOf(item.data.type) == -1) { return; } 15 | 16 | // Initialize flags. Don't commit to avoid a nested re-render 17 | ItemUtils.ensureFlags(item); 18 | 19 | let html = protoHtml; 20 | 21 | if (html[0].localName !== "div") { 22 | html = $(html[0].parentElement.parentElement); 23 | } 24 | 25 | // Create tab (for selection) 26 | const tabSelector = html.find("form nav.sheet-navigation.tabs"); 27 | const betterRollsTabString = `${i18n("Better Rolls")}`; 28 | tabSelector.append($(betterRollsTabString)); 29 | 30 | const settingsContainer = html.find(".sheet-body"); 31 | const altSecondaryEnabled = game.settings.get("betterrolls5e", "altSecondaryEnabled"); 32 | 33 | // For items with quantity (weapons, tools, consumables...) 34 | const hasQuantity = ("quantity" in itemData); 35 | // For items with "Limited Uses" configured 36 | const hasUses = !!(itemData.uses?.value || itemData.uses?.max || itemData.uses?.per); 37 | // For items with "Resource Consumption" configured 38 | const hasResource = !!(itemData.consume?.target); 39 | // For abilities with "Action Recharge" configured 40 | const hasCharge = !!(itemData.recharge?.value); 41 | 42 | // For items that have at least one way to consume something 43 | const canConsume = hasQuantity || hasUses || hasResource || hasCharge; 44 | 45 | const betterRollsTemplate = await renderTemplate("modules/betterrolls5e/templates/red-item-options.html", { 46 | DND5E: CONFIG.DND5E, 47 | item, 48 | canConsume, 49 | hasQuantity, 50 | hasUses, 51 | hasResource, 52 | hasCharge, 53 | isAttack: isAttack(item), 54 | isSave: isSave(item), 55 | flags: item.data.flags, 56 | damageTypes: CONFIG.betterRolls5e.combinedDamageTypes, 57 | altSecondaryEnabled, 58 | itemHasTemplate: item.hasAreaTarget 59 | }); 60 | 61 | settingsContainer.append(betterRollsTemplate); 62 | 63 | // Tab back to better rolls if we need (after certain events it may happen) 64 | if (activate) { 65 | app._tabs[0].activate("betterRolls5e"); 66 | app.setPosition(); 67 | activate = false; 68 | } 69 | 70 | // Add damage context input 71 | if (game.settings.get("betterrolls5e", "damageContextPlacement") !== "0") { 72 | const damageRolls = html.find(".tab.details .damage-parts .damage-part input").toArray(); 73 | // Placeholder is either "Context" or "Label" depending on system settings 74 | const placeholder = game.settings.get("betterrolls5e", "contextReplacesDamage") ? "br5e.settings.label" : "br5e.settings.context"; 75 | 76 | damageRolls.forEach((damageRoll, i) => { 77 | const contextField = $(``); 78 | 79 | damageRoll.after(contextField[0]); 80 | 81 | // Add event listener to delete context when damage is deleted 82 | $($($(damageRoll)[0].parentElement).find(`a.delete-damage`)).click(async _ => { 83 | const contextFlags = Object.values(item.data.flags.betterRolls5e.quickDamage.context); 84 | contextFlags.splice(i, 1); 85 | item.update({ 86 | [`flags.betterRolls5e.quickDamage.context`]: contextFlags, 87 | }); 88 | }); 89 | }); 90 | 91 | // Add context field for Other Formula field 92 | if (getProperty(item, "data.flags.betterRolls5e.quickOther")) { 93 | const otherRoll = html.find(`.tab.details .form-fields input[name="data.formula"]`); 94 | const otherContextField = $(``); 95 | if (otherRoll[0]) { otherRoll[0].after(otherContextField[0]); } 96 | } 97 | } 98 | 99 | // Activate the tab if anything changes in any sub-field 100 | const newSection = settingsContainer.find(".tab.item-betterRolls"); 101 | newSection.find("input[type=text]").change((evt) => activate = true); 102 | newSection.find("input[type=number]").change((evt) => activate = true); 103 | newSection.find("input[type=checkbox]").change((evt) => activate = true); 104 | newSection.find("select").change((evt) => activate = true); 105 | } 106 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/migration.js: -------------------------------------------------------------------------------- 1 | import { i18n, Utils } from "./utils/index.js"; 2 | 3 | export async function migrate() { 4 | const lastVersion = game.settings.get("betterrolls5e", "migration")?.version; 5 | let numItemsUpdated = 0; 6 | 7 | try { 8 | // Migration for the crit damage change 9 | if (isNewerVersion("1.6.12", lastVersion)) { 10 | numItemsUpdated += await iterAndUpdateItems((item) => { 11 | const updates = {}; 12 | const brFlags = item.data.flags.betterRolls5e; 13 | if (!brFlags) return; 14 | 15 | const critRange = brFlags.critRange?.value; 16 | if (critRange && !item.data.data.critical?.threshold) { 17 | updates["data.critical.threshold"] = Number(critRange) || null; 18 | } 19 | 20 | const critDamage = brFlags.critDamage?.value; 21 | if (critDamage && !item.data.data.critical?.critDamage) { 22 | updates["data.critical.damage"] = item.data.data.damage.parts[critDamage]?.[0]; 23 | } 24 | 25 | if ("critRange" in brFlags) { 26 | updates["flags.betterRolls5e.-=critRange"] = null; 27 | } 28 | 29 | if ("critDamage" in brFlags) { 30 | updates["flags.betterRolls5e.-=critDamage"] = null; 31 | } 32 | 33 | if (!foundry.utils.isObjectEmpty(updates)) { 34 | return updates; 35 | } 36 | }); 37 | } 38 | 39 | if (numItemsUpdated > 0) { 40 | ui.notifications.info(`BetterRolls migrated to version ${Utils.getVersion()}: ${numItemsUpdated} item(s) updated`); 41 | } 42 | 43 | // Update the game version, so we don't trigger another migration 44 | game.settings.set("betterrolls5e", "migration", { 45 | status: true, 46 | version: Utils.getVersion() 47 | }); 48 | } catch (err) { 49 | console.error(err); 50 | ui.notifications.error("Failed to migrate BetterRolls 5e"); 51 | } 52 | } 53 | 54 | let messageShown = false; 55 | function showMigrationMessage() { 56 | if (!messageShown) { 57 | const version = Utils.getVersion(); 58 | ui.notifications.info(i18n("br5e.migrating", { version })); 59 | messageShown = true; 60 | } 61 | } 62 | 63 | export async function iterAndUpdateItems(callback) { 64 | let numItemsUpdated = 0; 65 | 66 | // Migrate world items 67 | for (const item of game.items) { 68 | const updates = await callback(item) 69 | if (updates) { 70 | console.log(`BetterRolls5e | Migrating world ${item.name}`); 71 | await item.update(updates); 72 | numItemsUpdated += 1; 73 | } 74 | } 75 | 76 | // Migrate items of world actors 77 | for (const actor of game.actors) { 78 | const updates = []; 79 | for (const item of actor.items) { 80 | const update = await callback(item, actor); 81 | if (update) { 82 | console.log(`BetterRolls5e | Migrating ${item.name} on actor ${actor?.name}`); 83 | updates.push({ _id: item.id, ...update }); 84 | } 85 | } 86 | if (updates.length > 0) { 87 | await actor.updateEmbeddedDocuments("Item", updates); 88 | numItemsUpdated += updates.length; 89 | } 90 | } 91 | 92 | for await (const scene of game.scenes.contents) { 93 | for await (const token of scene.tokens) { 94 | const actor = token.actor; 95 | if (actor?.isToken) { 96 | const updates = []; 97 | for (const item of actor.items) { 98 | const update = await callback(item, actor); 99 | if (update) { 100 | console.log(`BetterRolls5e | Migrating ${item.name} on actor ${actor?.name}`); 101 | updates.push({ _id: item.id, ...update }); 102 | } 103 | } 104 | if (updates.length > 0) { 105 | await actor.updateEmbeddedDocuments("Item", updates); 106 | numItemsUpdated += updates.length; 107 | } 108 | } 109 | } 110 | } 111 | 112 | return numItemsUpdated; 113 | } 114 | 115 | export async function migrateChatMessage(message) { 116 | if (!game.user.isGM) return; 117 | const brFlags = message.data.flags.betterrolls5e; 118 | if (!brFlags) return false; 119 | 120 | let updated = false; 121 | const brVersion = brFlags.version ?? "1.0"; 122 | 123 | // Migrate to 1.4 (damage entries are now grouped) 124 | if (isNewerVersion("1.4.0", brVersion)) { 125 | updated = true; 126 | migrateTo_1_4(brFlags); 127 | brFlags.version = "1.4.0"; 128 | } 129 | 130 | // Migrate to 1.5 (update uuids for Foundry 0.8) 131 | if (isNewerVersion("1.5.0", brVersion)) { 132 | brFlags.version = "1.5.0"; 133 | migrateTo_1_5(brFlags); 134 | brFlags.version = "1.5.0"; 135 | } 136 | 137 | if (updated) { 138 | showMigrationMessage(); 139 | await message.update({ 140 | "flags.betterrolls5e": duplicate(brFlags) 141 | }, { diff: false }); 142 | } 143 | 144 | return updated; 145 | } 146 | 147 | function migrateTo_1_4(brFlags) { 148 | let currentId = (Math.max(...brFlags.entries.map((e) => Number(e.id))) ?? 0) + 1; 149 | let lastAttack = null; 150 | let lastJunction = null; 151 | const newEntries = []; 152 | for (const entry of brFlags.entries) { 153 | if (entry.type === "multiroll") lastAttack = entry; 154 | if (["multiroll", "button-save"].includes(entry.type)) { 155 | lastJunction = entry; 156 | } 157 | 158 | entry.id = `${entry.id}`; 159 | if (entry.group) entry.group = `${entry.group}`; 160 | if (entry.attackId) entry.attackId = `${entry.attackId}`; 161 | let lastEntry = newEntries[newEntries.length - 1]; 162 | if (["damage", "crit"].includes(entry.type)) { 163 | if (lastEntry?.type !== "damage-group") { 164 | lastEntry = { 165 | id: `${currentId++}`, 166 | type: "damage-group", 167 | attackId: lastAttack?.id, 168 | isCrit: lastAttack?.isCrit || entry?.isCrit, 169 | forceCrit: lastJunction?.forceCrit, 170 | prompt: brFlags.params.prompt[entry.group], 171 | entries: [], 172 | } 173 | 174 | newEntries.push(lastEntry); 175 | } 176 | 177 | entry.group = lastEntry.id; 178 | lastEntry.entries.push(entry); 179 | } else { 180 | newEntries.push(entry); 181 | } 182 | } 183 | } 184 | 185 | function migrateTo_1_5(brFlags) { 186 | const parts = brFlags.tokenId?.split("."); 187 | if (parts?.length !== 2) return; 188 | 189 | const [sceneId, tokenId] = parts; 190 | brFlags.tokenId = `Scene.${sceneId}.Token.${tokenId}`; 191 | } 192 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/patching/index.js: -------------------------------------------------------------------------------- 1 | import { getSettings } from "../settings.js"; 2 | import { libWrapper } from "./libWrapper.js"; 3 | 4 | import { d20Roll } from "../../../../systems/dnd5e/module/dice.js"; 5 | import { dnd5e, i18n, Utils } from "../utils/index.js"; 6 | import { CustomRoll } from "../custom-roll.js"; 7 | 8 | export function patchCoreFunctions() { 9 | if (!libWrapper.is_fallback && !libWrapper.version_at_least?.(1, 4, 0)) { 10 | Hooks.once("ready", () => { 11 | const version = "v1.4.0.0"; 12 | ui.notifications.error(i18n("br5e.error.libWrapperMinVersion", { version })); 13 | }); 14 | 15 | return; 16 | } 17 | 18 | const actorProto = "CONFIG.Actor.documentClass.prototype"; 19 | override("CONFIG.Item.documentClass.prototype.roll", itemRoll); 20 | override("CONFIG.Item.documentClass.prototype.rollAttack", itemRollAttack); 21 | override("CONFIG.Item.documentClass.prototype.rollToolCheck", itemRollToolCheck); 22 | libWrapper.register("betterrolls5e", `${actorProto}.rollSkill`, actorRollSkill, "MIXED"); 23 | libWrapper.register("betterrolls5e", `${actorProto}.rollAbilityTest`, actorRollAbilityTest, "MIXED"); 24 | libWrapper.register("betterrolls5e", `${actorProto}.rollAbilitySave`, actorRollAbilitySave, "MIXED"); 25 | } 26 | 27 | /** 28 | * A version of libwrapper OVERRIDE that tries to get the original function. 29 | * We want Better Rolls and Core 5e to be swappable between each other, 30 | * and for other modules to wrap over it. 31 | * @param {*} target 32 | * @param {*} fn A curried function that takes the original and returns a function to pass to libwrapper 33 | */ 34 | function override(target, fn) { 35 | libWrapper.register("betterrolls5e", target, fn, "OVERRIDE", {chain: true}); 36 | } 37 | 38 | /** 39 | * Override for Item5e.roll(). This is an OVERRIDE however we still want 40 | * a passthrough. We need to be lower on priority than Midi. 41 | * @param {} wrapped 42 | * @returns 43 | */ 44 | function itemRoll(defaultRoll, options) { 45 | // Handle options, same defaults as core 5e 46 | options = mergeObject({ 47 | configureDialog: true, 48 | createMessage: true, 49 | event 50 | }, options, { recursive: false }); 51 | const { rollMode, createMessage, vanilla } = options; 52 | const altKey = options.event?.altKey; 53 | const item = this; 54 | 55 | // Case - If the image button should roll a vanilla roll, UNLESS vanilla is defined and is false 56 | const { imageButtonEnabled, altSecondaryEnabled } = getSettings(); 57 | if (vanilla || (!imageButtonEnabled && vanilla !== false) || (altKey && !altSecondaryEnabled)) { 58 | return defaultRoll.bind(item)(options); 59 | } 60 | 61 | const preset = altKey ? 1 : 0; 62 | const card = window.BetterRolls.rollItem(item, { preset, event: options.event }); 63 | return card.toMessage({ rollMode, createMessage }); 64 | } 65 | 66 | /** 67 | * Override for Item5e.rollAttack(). Only kicks in if options.chatMessage is off. 68 | * It is basically an exact copy of the built in rollAttack, except that it doesn't consume ammo. 69 | * Unfortunately D&D does not allow a rollAttack() to not consume ammo. 70 | * @param {} wrapped 71 | * @returns 72 | */ 73 | async function itemRollAttack(defaultRoll, options) { 74 | // Call the default version if chatMessage is enabled 75 | if (options?.chatMessage !== false) { 76 | return defaultRoll.bind(this)(options); 77 | } 78 | 79 | const flags = this.actor.data.flags.dnd5e || {}; 80 | if ( !this.hasAttack ) { 81 | throw new Error("You may not place an Attack Roll with this Item."); 82 | } 83 | 84 | let title = `${this.name} - ${game.i18n.localize("DND5E.AttackRoll")}`; 85 | 86 | // get the parts and rollData for this item's attack 87 | const {parts, rollData} = this.getAttackToHit(); 88 | 89 | // Compose roll options 90 | const rollConfig = mergeObject({ 91 | parts: parts, 92 | actor: this.actor, 93 | data: rollData, 94 | title: title, 95 | flavor: title, 96 | dialogOptions: { 97 | width: 400, 98 | top: options.event ? options.event.clientY - 80 : null, 99 | left: window.innerWidth - 710 100 | }, 101 | messageData: { 102 | speaker: ChatMessage.getSpeaker({actor: this.actor}), 103 | "flags.dnd5e.roll": { type: "attack", itemId: this.id } 104 | } 105 | }, options); 106 | rollConfig.event = options.event; 107 | 108 | // Expanded critical hit thresholds 109 | if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) { 110 | rollConfig.critical = parseInt(flags.weaponCriticalThreshold); 111 | } else if (( this.data.type === "spell" ) && flags.spellCriticalThreshold) { 112 | rollConfig.critical = parseInt(flags.spellCriticalThreshold); 113 | } 114 | 115 | // Elven Accuracy 116 | if ( ["weapon", "spell"].includes(this.data.type) ) { 117 | if (flags.elvenAccuracy && ["dex", "int", "wis", "cha"].includes(this.abilityMod)) { 118 | rollConfig.elvenAccuracy = true; 119 | } 120 | } 121 | 122 | // Apply Halfling Lucky 123 | if ( flags.halflingLucky ) rollConfig.halflingLucky = true; 124 | 125 | // Invoke the d20 roll helper 126 | const roll = await d20Roll(rollConfig); 127 | if ( roll === false ) return null; 128 | return roll; 129 | } 130 | 131 | async function itemRollToolCheck(original, options) { 132 | if (options?.chatMessage === false || options?.vanilla) { 133 | return original.call(this, options); 134 | } 135 | 136 | const evt = options?.event ?? event; 137 | const preset = evt?.altKey ? 1 : 0; 138 | const card = window.BetterRolls.rollItem(this, { preset, ...options }); 139 | return card.toMessage(); 140 | } 141 | 142 | async function actorRollSkill(original, skillId, options) { 143 | if (options?.chatMessage === false || options?.vanilla) { 144 | return original.call(this, skillId, options); 145 | } 146 | 147 | const roll = await original.call(this, skillId, { 148 | ...options, 149 | fastForward: true, 150 | chatMessage: false, 151 | ...Utils.getRollState(options), 152 | }); 153 | 154 | return CustomRoll._fullRollActor(this, i18n(dnd5e.skills[skillId]), roll); 155 | } 156 | 157 | async function actorRollAbilityTest(original, ability, options) { 158 | if (options?.chatMessage === false || options?.vanilla) { 159 | return original.call(this, ability, options); 160 | } 161 | 162 | const roll = await original.call(this, ability, { 163 | ...options, 164 | fastForward: true, 165 | chatMessage: false, 166 | ...Utils.getRollState(options), 167 | }); 168 | 169 | const label = `${i18n(dnd5e.abilities[ability])} ${i18n("br5e.chat.check")}`; 170 | return CustomRoll._fullRollActor(this, label, roll); 171 | } 172 | 173 | async function actorRollAbilitySave(original, ability, options) { 174 | if (options?.chatMessage === false || options?.vanilla) { 175 | return original.call(this, ability, options); 176 | } 177 | 178 | const roll = await original.call(this, ability, { 179 | ...options, 180 | fastForward: true, 181 | chatMessage: false, 182 | ...Utils.getRollState(options), 183 | }); 184 | 185 | const label = `${i18n(dnd5e.abilities[ability])} ${i18n("br5e.chat.save")}`; 186 | return CustomRoll._fullRollActor(this, label, roll); 187 | } 188 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/patching/libWrapper.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | // Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro 3 | 4 | 5 | 'use strict'; 6 | 7 | // A shim for the libWrapper library 8 | export let libWrapper = undefined; 9 | 10 | Hooks.once('init', () => { 11 | // Check if the real module is already loaded - if so, use it 12 | if(globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) { 13 | libWrapper = globalThis.libWrapper; 14 | return; 15 | } 16 | 17 | // Fallback implementation 18 | libWrapper = class { 19 | static get is_fallback() { return true }; 20 | 21 | static register(module, target, fn, type="MIXED", {chain=undefined}={}) { 22 | const is_setter = target.endsWith('#set'); 23 | target = !is_setter ? target : target.slice(0, -4); 24 | const split = target.split('.'); 25 | const fn_name = split.pop(); 26 | const root_nm = split.splice(0,1)[0]; 27 | const _eval = eval; // The browser doesn't expose all global variables (e.g. 'Game') inside globalThis, but it does to an eval. We copy it to a variable to have it run in global scope. 28 | const obj = split.reduce((x,y)=>x[y], globalThis[root_nm] ?? _eval(root_nm)); 29 | 30 | let iObj = obj; 31 | let descriptor = null; 32 | while(iObj) { 33 | descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name); 34 | if(descriptor) break; 35 | iObj = Object.getPrototypeOf(iObj); 36 | } 37 | if(!descriptor) throw `libWrapper Shim: '${target}' does not exist or could not be found.`; 38 | 39 | let original = null; 40 | const wrapper = (chain ?? type != 'OVERRIDE') ? function() { return fn.call(this, original.bind(this), ...arguments); } : function() { return fn.call(this, ...arguments); }; 41 | 42 | if(!is_setter) { 43 | if(descriptor.value) { 44 | original = descriptor.value; 45 | descriptor.value = wrapper; 46 | } 47 | else { 48 | original = descriptor.get; 49 | descriptor.get = wrapper; 50 | } 51 | } 52 | else { 53 | if(!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`; 54 | original = descriptor.set; 55 | descriptor.set = wrapper; 56 | } 57 | 58 | descriptor.configurable = true; 59 | Object.defineProperty(obj, fn_name, descriptor); 60 | } 61 | } 62 | 63 | //************** USER CUSTOMIZABLE: 64 | // Whether to warn GM that the fallback is being used 65 | const WARN_FALLBACK = true; 66 | 67 | // Set up the ready hook that shows the "libWrapper not installed" warning dialog 68 | if(WARN_FALLBACK) { 69 | //************** USER CUSTOMIZABLE: 70 | // Module ID - by default attempts to auto-detect, but you might want to hardcode your module ID here to avoid potential auto-detect issues 71 | const MODULE_ID = ((import.meta?.url ?? Error().stack)?.match(/(?<=\/)modules\/.+(?=\/)/i)??[])[0]?.split('/')?.find(n => n && game.modules.has(n)); 72 | if(!MODULE_ID) { 73 | console.error("libWrapper Shim: Could not auto-detect module ID. The libWrapper fallback warning dialog will be disabled."); 74 | return; 75 | } 76 | 77 | Hooks.once('ready', () => { 78 | // Module title 79 | const MODULE_TITLE = game.modules.get(MODULE_ID).data.title; 80 | 81 | //************** USER CUSTOMIZABLE: 82 | // Title and message for the dialog shown when the real libWrapper is not installed. 83 | const FALLBACK_MESSAGE_TITLE = MODULE_TITLE; 84 | const FALLBACK_MESSAGE = ` 85 |

'${MODULE_TITLE}' depends on the 'libWrapper' module, which is not present.

86 |

A fallback implementation will be used, which increases the chance of compatibility issues with other modules.

87 |

'libWrapper' is a library which provides module developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other modules.

88 |

You can install it from the "Add-on Modules" tab in the Foundry VTT Setup, from the Foundry VTT package repository, or from libWrapper's Github page.

89 | `; 90 | 91 | // Settings key used for the "Don't remind me again" setting 92 | const DONT_REMIND_AGAIN_KEY = "libwrapper-dont-remind-again"; 93 | 94 | // Dialog code 95 | console.warn(`${MODULE_TITLE}: libWrapper not present, using fallback implementation.`); 96 | game.settings.register(MODULE_ID, DONT_REMIND_AGAIN_KEY, { name: '', default: false, type: Boolean, scope: 'world', config: false }); 97 | if(game.user.isGM && !game.settings.get(MODULE_ID, DONT_REMIND_AGAIN_KEY)) { 98 | new Dialog({ 99 | title: FALLBACK_MESSAGE_TITLE, 100 | content: FALLBACK_MESSAGE, buttons: { 101 | ok: { icon: '', label: 'Understood' }, 102 | dont_remind: { icon: '', label: "Don't remind me again", callback: () => game.settings.set(MODULE_ID, DONT_REMIND_AGAIN_KEY, true) } 103 | } 104 | }).render(true); 105 | } 106 | }); 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/renderer.js: -------------------------------------------------------------------------------- 1 | import { CustomItemRoll } from "./custom-roll.js"; 2 | import { BRSettings, getSettings } from "./settings.js"; 3 | import { i18n, Utils } from "./utils/index.js"; 4 | 5 | /** 6 | * Model data for rendering the header template. 7 | * Not part of the entry list 8 | * @typedef HeaderDataProps 9 | * @type {object} 10 | * @property {number} id 11 | * @property {"header"} type 12 | * @property {string} img image path to show in the box 13 | * @property {string} title header title text 14 | */ 15 | 16 | /** 17 | * Model data for rendering description or flavor text 18 | * @typedef DescriptionDataProps 19 | * @type {object} 20 | * @property {number} id 21 | * @property {"description"} type 22 | * @property {boolean} isFlavor 23 | * @property {string} content 24 | */ 25 | 26 | /** 27 | * Model data for rendering a multi roll. 28 | * @typedef MultiRollDataProps 29 | * @type {object} 30 | * @property {number} id 31 | * @property {"multiroll"} type 32 | * @property {string} title 33 | * @property {"highest" | "lowest" | "first" | null} rollState advantage/disadvantage/normal/none 34 | * @property {string?} group Damage group used to identify what damage entries would be affected by a crit 35 | * @property {number} critThreshold 36 | * @property {boolean?} elvenAccuracy whether elven accuracy applies to this attack 37 | * @property {string} rollType If its an attack or custom 38 | * @property {string} formula The full roll formula to show. This is the entries + bonus 39 | * @property {boolean?} forceCrit was the crit status forced 40 | * @property {boolean} isCrit 41 | * @property {Array<{roll: Roll}>} entries Main d20 roll. Bonuses are added to this 42 | * @property {Roll} bonus Any bonuses to add to the roll (that only get rolled once) 43 | */ 44 | 45 | /** 46 | * Model data for rendering damage information. 47 | * @typedef DamageDataProps 48 | * @type {object} 49 | * @property {number} id 50 | * @property {"damage"} type 51 | * @property {number | "versatile" | "other"} damageIndex 52 | * @property {number?} group DamageGroup id used to identify damage entries as related 53 | * @property {string} title 54 | * @property {string} damageType If its something like bludgeoning or piercing 55 | * @property {string} context 56 | * @property {number?} extraCritDice Used for things like savage 57 | * @property {Roll} baseRoll 58 | * @property {Roll?} critRoll 59 | */ 60 | 61 | /** 62 | * Model data for rendering bonus crit information. 63 | * @typedef CritDataProps 64 | * @type {object} 65 | * @property {number} id 66 | * @property {"crit"} type 67 | * @property {number?} group DamageGroup id used to identify damage entries as related 68 | * @property {string} title 69 | * @property {string} damageType If its something like bludgeoning or piercing 70 | * @property {string} context 71 | * @property {Roll?} critRoll 72 | * @property {boolean} revealed Has the crit entry been revealed 73 | */ 74 | 75 | /** 76 | * Model data for save buttons 77 | * @typedef ButtonSaveProps 78 | * @property {number} id 79 | * @property {"button-save"} type 80 | * @property {string} ability 81 | * @property {boolean} hideDC 82 | * @property {number} dc 83 | */ 84 | 85 | /** 86 | * @typedef DamageGroup 87 | * @property {number} id 88 | * @property {"damage-group"} type 89 | * @property {number} attackId id of the associated attack roll 90 | * @property {boolean} prompt 91 | * @property {boolean} isCrit 92 | * @property {boolean?} forceCrit whether crit can be unset or not 93 | * @property {DamageEntry[]} entries 94 | */ 95 | 96 | /** 97 | * Union type of all possible render model types, separatable by the type property. 98 | * @typedef { HeaderDataProps | DescriptionDataProps | MultiRollDataProps | 99 | * ButtonSaveProps | DamageGroup | DamageEntry 100 | * } RenderModelEntry 101 | */ 102 | 103 | /** 104 | * Union type of all fields that relate to damage 105 | * @typedef {DamageDataProps | CritDataProps} DamageEntry 106 | */ 107 | 108 | /** 109 | * Shortcut function to render a templates in the better rolls template folder. 110 | * @param {string} path sub path of the template in the templates folder 111 | * @param {Object} props the props to render with 112 | * @returns {Promise} rendered template 113 | */ 114 | function renderModuleTemplate(path, props) { 115 | return renderTemplate(`modules/betterrolls5e/templates/${path}`, props); 116 | } 117 | 118 | /** 119 | * A collection of functions used to build html from metadata. 120 | */ 121 | export class Renderer { 122 | /** 123 | * Renders a model by checking the type. Calls the other render functions depending on what it is 124 | * @param {RenderModel} model 125 | * @returns {Promise} 126 | */ 127 | static async renderModel(model, settings=null) { 128 | if (typeof model === "string" || !model) { 129 | return model; 130 | } 131 | 132 | switch (model.type) { 133 | case "header": 134 | return Renderer.renderHeader(model, settings); 135 | case "description": 136 | return Renderer.renderDescription(model, settings); 137 | case "multiroll": 138 | return Renderer.renderMultiRoll(model, settings); 139 | case "damage": 140 | case "crit": 141 | return Renderer.renderDamage(model, settings); 142 | case "damage-group": 143 | return Renderer.renderDamageGroup(model, settings); 144 | case "button-save": 145 | return Renderer.renderSaveButton(model, settings); 146 | case "raw": 147 | return model?.html ?? model.content?.html ?? model.content; 148 | default: 149 | console.error(`Unknown render model type ${model.type}`) 150 | } 151 | } 152 | 153 | /** 154 | * Renders the header template 155 | * @param {HeaderDataProps} properties 156 | */ 157 | static renderHeader(properties) { 158 | const { img, title, slotLevel } = properties; 159 | return renderModuleTemplate("red-header.html", { 160 | id: properties.id, 161 | item: { img: img ?? "icons/svg/mystery-man.svg", name: title }, 162 | slotLevel 163 | }); 164 | } 165 | 166 | /** 167 | * Renders the description template 168 | * @param {DescriptionDataProps} properties 169 | */ 170 | static renderDescription(properties) { 171 | return renderModuleTemplate("red-description.html", properties); 172 | } 173 | 174 | /** 175 | * Renders a multiroll, which represent most D20 rolls. 176 | * @param {MultiRollDataProps} properties 177 | * @param {BRSettings} settings 178 | */ 179 | static async renderMultiRoll(properties, settings) { 180 | const { rollTitlePlacement, d20RollIconsEnabled } = getSettings(settings); 181 | const title = rollTitlePlacement !== "0" ? properties.title : null; 182 | 183 | // Show D20 die icons if enabled 184 | let entries = properties.entries; 185 | if (d20RollIconsEnabled) { 186 | entries = entries.map(e => ({ ...e, d20Result: Utils.findD20Result(e.roll) })); 187 | } 188 | 189 | // Create roll templates 190 | const tooltips = await Promise.all(properties.entries.map(e => e.roll.getTooltip())); 191 | const bonusTooltip = await properties.bonus?.getTooltip(); 192 | 193 | // Render final result 194 | return renderModuleTemplate("red-multiroll.html", { 195 | ...properties, title, entries, tooltips, bonusTooltip 196 | }); 197 | } 198 | 199 | /** 200 | * Renders damage html data 201 | * @param {DamageDataProps | CritDataProps} properties 202 | */ 203 | static async renderDamage(properties, settings) { 204 | const { damageType, baseRoll, critRoll, context } = properties; 205 | const isVersatile = properties.damageIndex === "versatile"; 206 | if (baseRoll?.terms.length === 0 && critRoll?.terms.length === 0) return; 207 | 208 | const tooltips = (await Promise.all([ 209 | baseRoll?.getTooltip(), 210 | critRoll?.getTooltip() 211 | ])).filter(t => t); 212 | 213 | settings = getSettings(settings); 214 | const critString = settings.critString; 215 | const titlePlacement = settings.damageTitlePlacement.toString(); 216 | const damagePlacement = settings.damageRollPlacement.toString(); 217 | const contextPlacement = settings.damageContextPlacement.toString(); 218 | const replaceTitle = settings.contextReplacesTitle; 219 | const replaceDamage = settings.contextReplacesDamage; 220 | 221 | const labels = { 222 | "1": [], 223 | "2": [], 224 | "3": [] 225 | }; 226 | 227 | const dtype = CONFIG.betterRolls5e.combinedDamageTypes[damageType]; 228 | 229 | let titleString = properties.title ?? ""; 230 | if (!titleString && CONFIG.DND5E.healingTypes[damageType]) { 231 | // Show "Healing" prefix only if it's not inherently a heal action 232 | titleString = ""; 233 | } else if (!titleString && CONFIG.DND5E.damageTypes[damageType]) { 234 | // Show "Damage" prefix if it's a damage roll 235 | titleString += i18n("br5e.chat.damage"); 236 | } else if (properties.type === "crit") { 237 | // 5e crits don't have a damage type, so we mark as critical and have the user figure it out 238 | titleString += i18n("br5e.critString.choices.5"); 239 | } 240 | 241 | // Title 242 | let pushedTitle = false; 243 | if (titlePlacement !== "0" && titleString && !(replaceTitle && context && titlePlacement == contextPlacement)) { 244 | labels[titlePlacement].push(titleString); 245 | pushedTitle = true; 246 | } 247 | 248 | // Context (damage type and roll flavors) 249 | const bonusContexts = Utils.getRollFlavors(baseRoll, critRoll).filter(c => c !== context); 250 | if (context || bonusContexts.length > 0) { 251 | const contextStr = [context, bonusContexts.join("/")].filter(c=>c).join(" + "); 252 | if (contextPlacement === titlePlacement && pushedTitle) { 253 | const title = labels[contextPlacement][0]; 254 | labels[contextPlacement][0] = (title ? title + " " : "") + `(${contextStr})`; 255 | } else if (contextPlacement !== "0") { 256 | labels[contextPlacement].push(contextStr); 257 | } 258 | } 259 | 260 | // Damage type 261 | const damageStringParts = []; 262 | if (dtype) { 263 | damageStringParts.push(dtype); 264 | } 265 | if (isVersatile) { 266 | damageStringParts.push("(" + CONFIG.DND5E.weaponProperties.ver + ")"); 267 | } 268 | 269 | const damageString = damageStringParts.join(" "); 270 | if (damagePlacement !== "0" && damageString.length > 0 && !(replaceDamage && context && damagePlacement == contextPlacement)) { 271 | labels[damagePlacement].push(damageString); 272 | } 273 | 274 | for (let p in labels) { 275 | labels[p] = labels[p].join(" - "); 276 | }; 277 | 278 | return renderModuleTemplate("red-damageroll.html", { 279 | id: properties.id, 280 | group: properties.group, 281 | damageRollType: properties.type, 282 | tooltips, 283 | base: Utils.processRoll(baseRoll), 284 | crit: Utils.processRoll(critRoll), 285 | crittext: critString, 286 | damagetop: labels[1], 287 | damagemid: labels[2], 288 | damagebottom: labels[3], 289 | formula: baseRoll?.formula ?? critRoll.formula, 290 | damageType, 291 | maxRoll: baseRoll ? new Roll(baseRoll.formula).evaluate({maximize:true, async: false}).total : null, 292 | maxCrit: critRoll ? new Roll(critRoll.formula).evaluate({maximize:true, async: false}).total : null 293 | }); 294 | } 295 | 296 | /** 297 | * Renders an html save button 298 | * @param {ButtonSaveProps} properties 299 | */ 300 | static async renderSaveButton(properties) { 301 | const abilityLabel = CONFIG.DND5E.abilities[properties.ability]; 302 | return renderModuleTemplate("red-save-button.html", { 303 | id: properties.id, 304 | abilityLabel, 305 | ...properties 306 | }); 307 | } 308 | 309 | /** 310 | * Renders an html damage button 311 | * @param {DamageGroup} properties 312 | */ 313 | static async renderDamageGroup(properties, settings) { 314 | const results = []; 315 | for (const entry of properties.entries) { 316 | if (["damage", "crit"].includes(entry.type) && properties.prompt) { 317 | continue; 318 | } 319 | 320 | // Create the template, only do so if not of type crit unless crit is revealed 321 | if (entry.type !== "crit" || entry.revealed) { 322 | results.push(this.renderModel( 323 | {...entry, group: properties.id }, 324 | settings 325 | )); 326 | } 327 | } 328 | 329 | if (properties.prompt) { 330 | const { id } = properties; 331 | const button = renderModuleTemplate("red-damage-button.html", { id }); 332 | results.push(button); 333 | } 334 | 335 | const renderedResults = await Promise.all(results); 336 | return renderedResults.join(""); 337 | } 338 | 339 | /** 340 | * Renders a full card 341 | * @param {CustomItemRoll} data 342 | * @param {*} param1 343 | */ 344 | static async renderCard(data) { 345 | const templates = []; 346 | 347 | let previous = null; 348 | for (const entry of data.entries) { 349 | if (!entry) continue; 350 | 351 | // If its a new attack/damage group, add a divider 352 | const previousIsDamage = ["damage", "crit", "damage-group"].includes(previous?.type); 353 | if (previousIsDamage && ["multiroll", "button-save", "damage-group"].includes(entry.type)) { 354 | templates.push("
"); 355 | } 356 | 357 | templates.push(await Renderer.renderModel(entry)); 358 | previous = entry; 359 | } 360 | 361 | // Render apply active effects button if enabled 362 | const actor = await data.getActor(); 363 | const item = await data.getItem(); 364 | 365 | if (window.DAE && data.settings.applyActiveEffects) { 366 | const hasEffects = item?.data.effects.find(ae => !ae.data.transfer); 367 | if (hasEffects) { 368 | const button = await renderModuleTemplate("red-ae-button.html"); 369 | templates.push(button); 370 | } 371 | 372 | const hasAmmo = item?.data.data.consume?.type === "ammo" && item?.data.data.consume?.target; 373 | if (hasAmmo) { 374 | const ammo = actor.items.get(item.data.data.consume.target); 375 | const ammoHasEffects = ammo?.data.effects.find(ae => !ae.data.transfer); 376 | 377 | if (ammoHasEffects) { 378 | const button = await renderModuleTemplate("red-ae-button.html", { 379 | ammo: true, 380 | context: ammo.data.name 381 | }); 382 | templates.push(button); 383 | } 384 | } 385 | } 386 | 387 | return renderModuleTemplate("red-fullroll.html", { 388 | item, 389 | actor, 390 | tokenId: data.tokenId, 391 | isCritical: data.isCrit, 392 | templates, 393 | properties: data.properties 394 | }); 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/settings.js: -------------------------------------------------------------------------------- 1 | import { i18n, Utils } from "./utils/index.js"; 2 | 3 | const getBRSetting = (setting) => game.settings.get("betterrolls5e", setting); 4 | 5 | /** 6 | * Class type used to initialize and retrieve settings. 7 | */ 8 | class Settings { 9 | /** 10 | * Register better rolls settings. 11 | * This should only be called once, at initialization. 12 | */ 13 | init() { 14 | // Special non-config flag to handle migrations 15 | game.settings.register("betterrolls5e", "migration", { 16 | config: false, 17 | default: { status: false, version: "1.0.0" }, 18 | scope: 'world', 19 | type: Object 20 | }); 21 | 22 | game.settings.register("betterrolls5e", "d20Mode", { 23 | name: i18n("br5e.d20Mode.name"), 24 | hint: i18n("br5e.d20Mode.hint"), 25 | scope: "world", 26 | config: true, 27 | default: 1, 28 | type: Number, 29 | choices: { 30 | 1: i18n("br5e.d20Mode.choices.1"), 31 | 2: i18n("br5e.d20Mode.choices.2"), 32 | 3: i18n("br5e.d20Mode.choices.3"), 33 | 4: i18n("br5e.d20Mode.choices.4") 34 | } 35 | }); 36 | 37 | // Enables damage buttons 38 | game.settings.register("betterrolls5e", "damagePromptEnabled", { 39 | name: i18n("br5e.damagePromptEnabled.name"), 40 | hint: i18n("br5e.damagePromptEnabled.hint"), 41 | scope: "world", 42 | config: true, 43 | default: false, 44 | type: Boolean 45 | }); 46 | 47 | // Register added roll buttons 48 | game.settings.register("betterrolls5e", "rollButtonsEnabled", { 49 | name: i18n("br5e.rollButtonsEnabled.name"), 50 | hint: i18n("br5e.rollButtonsEnabled.hint"), 51 | scope: "world", 52 | config: true, 53 | default: true, 54 | type: Boolean 55 | }); 56 | 57 | // Register better roll for items 58 | game.settings.register("betterrolls5e", "imageButtonEnabled", { 59 | name: i18n("br5e.imageButtonEnabled.name"), 60 | hint: i18n("br5e.imageButtonEnabled.hint"), 61 | scope: "world", 62 | config: true, 63 | default: true, 64 | type: Boolean 65 | }); 66 | 67 | // Does Alt Click perform an Alt Roll? 68 | game.settings.register("betterrolls5e", "altSecondaryEnabled", { 69 | name: i18n("br5e.altSecondaryEnabled.name"), 70 | hint: i18n("br5e.altSecondaryEnabled.hint"), 71 | scope: "world", 72 | config: true, 73 | default: true, 74 | type: Boolean 75 | }); 76 | 77 | // Show Apply Active Effects Button 78 | game.settings.register("betterrolls5e", "applyActiveEffects", { 79 | name: i18n("br5e.applyActiveEffects.name"), 80 | hint: i18n("br5e.applyActiveEffects.hint"), 81 | scope: "world", 82 | config: true, 83 | default: true, 84 | type: Boolean 85 | }) 86 | 87 | // Register quick roll defaults for description 88 | game.settings.register("betterrolls5e", "quickDefaultDescriptionEnabled", { 89 | name: i18n("br5e.quickDefaultDescriptionEnabled.name"), 90 | hint: i18n("br5e.quickDefaultDescriptionEnabled.hint"), 91 | scope: "world", 92 | config: true, 93 | default: false, 94 | type: Boolean 95 | }); 96 | 97 | // Used to enable visually showing the natural die roll for a d20 roll. 98 | game.settings.register("betterrolls5e", "d20RollIconsEnabled", { 99 | name: i18n("br5e.d20RollIconsEnabled.name"), 100 | hint: i18n("br5e.d20RollIconsEnabled.hint"), 101 | scope: "world", 102 | config: true, 103 | default: true, 104 | type: Boolean 105 | }); 106 | 107 | // Actor Roll Image Choices 108 | game.settings.register("betterrolls5e", "defaultRollArt", { 109 | name: i18n("br5e.defaultRollArt.name"), 110 | hint: i18n("br5e.defaultRollArt.hint"), 111 | scope: "world", 112 | config: true, 113 | default: "actor", 114 | type: String, 115 | choices: { 116 | "actor": i18n("Actor"), 117 | "token": i18n("Token") 118 | } 119 | }); 120 | 121 | // Register roll label options 122 | game.settings.register("betterrolls5e", "rollTitlePlacement", { 123 | name: i18n("br5e.rollTitlePlacement.name"), 124 | hint: i18n("br5e.rollTitlePlacement.hint"), 125 | scope: "world", 126 | config: true, 127 | default: "1", 128 | type: String, 129 | choices: { 130 | "0": i18n("br5e.damageRollPlacement.choices.0"), 131 | "1": i18n("br5e.damageRollPlacement.choices.1") 132 | } 133 | }); 134 | 135 | const damagePlacementOptions = ["damageTitlePlacement", "damageContextPlacement", "damageRollPlacement"]; 136 | 137 | damagePlacementOptions.forEach(placementOption => { 138 | game.settings.register("betterrolls5e", placementOption, { 139 | name: i18n(`br5e.${placementOption}.name`), 140 | hint: i18n(`br5e.${placementOption}.hint`), 141 | scope: "world", 142 | config: true, 143 | default: "1", 144 | type: String, 145 | choices: { 146 | "0": i18n("br5e.damageRollPlacement.choices.0"), 147 | "1": i18n("br5e.damageRollPlacement.choices.1"), 148 | "2": i18n("br5e.damageRollPlacement.choices.2"), 149 | "3": i18n("br5e.damageRollPlacement.choices.3") 150 | } 151 | }); 152 | }); 153 | 154 | const contextReplacementOptions = ["contextReplacesTitle", "contextReplacesDamage"]; 155 | 156 | contextReplacementOptions.forEach(contextOption => { 157 | game.settings.register("betterrolls5e", contextOption, { 158 | name: i18n(`br5e.${contextOption}.name`), 159 | hint: i18n(`br5e.${contextOption}.hint`), 160 | scope: "world", 161 | config: true, 162 | default: false, 163 | type: Boolean 164 | }); 165 | }); 166 | 167 | game.settings.register("betterrolls5e", "critBehavior", { 168 | name: i18n("br5e.critBehavior.name"), 169 | hint: i18n("br5e.critBehavior.hint"), 170 | scope: "world", 171 | config: true, 172 | default: "1", 173 | type: String, 174 | choices: { 175 | "0": i18n("br5e.critBehavior.choices.0"), // No Extra Damage 176 | "1": i18n("br5e.critBehavior.choices.1"), // Roll Critical Damage Dice 177 | "2": i18n("br5e.critBehavior.choices.2"), // Roll Base Damage, Max Critical 178 | "3": i18n("br5e.critBehavior.choices.3"), // Max Base & Critical Damage 179 | "4": i18n("br5e.critBehavior.choices.4"), // Max Base Damage, Roll Critical Damage 180 | } 181 | }); 182 | 183 | game.settings.register("betterrolls5e", "critString", { 184 | name: i18n("br5e.critString.name"), 185 | hint: i18n("br5e.critString.hint"), 186 | scope: "world", 187 | config: true, 188 | default: "Crit", 189 | type: String 190 | }); 191 | 192 | game.settings.register("betterrolls5e", "chatDamageButtonsEnabled", { 193 | name: i18n("br5e.chatDamageButtonsEnabled.name"), 194 | hint: i18n("br5e.chatDamageButtonsEnabled.hint"), 195 | scope: "world", 196 | config: true, 197 | default: "1", 198 | type: String, 199 | choices: { 200 | "0": i18n("br5e.chatDamageButtonsEnabled.choices.0"), 201 | "1": i18n("br5e.chatDamageButtonsEnabled.choices.1"), 202 | "2": i18n("br5e.chatDamageButtonsEnabled.choices.2"), 203 | } 204 | }); 205 | 206 | game.settings.register("betterrolls5e", "playRollSounds", { 207 | name: i18n("br5e.playRollSounds.name"), 208 | hint: i18n("br5e.playRollSounds.hint"), 209 | scope: "world", 210 | config: true, 211 | default: true, 212 | type: Boolean 213 | }); 214 | 215 | game.settings.register("betterrolls5e", "hideDC", { 216 | name: i18n("br5e.hideDC.name"), 217 | hint: i18n("br5e.hideDC.hint"), 218 | scope: "world", 219 | config: true, 220 | default: "0", 221 | type: String, 222 | choices: { 223 | "0": i18n("br5e.hideDC.choices.0"), 224 | "1": i18n("br5e.hideDC.choices.1"), 225 | "2": i18n("br5e.hideDC.choices.2"), 226 | } 227 | }); 228 | } 229 | 230 | get playRollSounds() { 231 | return getBRSetting("playRollSounds"); 232 | } 233 | 234 | get damageRollPlacement() { 235 | return getBRSetting("damageRollPlacement"); 236 | } 237 | 238 | get rollTitlePlacement() { 239 | return getBRSetting("rollTitlePlacement"); 240 | } 241 | 242 | get damageTitlePlacement() { 243 | return getBRSetting("damageTitlePlacement"); 244 | } 245 | 246 | get damageContextPlacement() { 247 | return getBRSetting("damageContextPlacement") || "0"; 248 | } 249 | 250 | get contextReplacesTitle() { 251 | return getBRSetting("contextReplacesTitle"); 252 | } 253 | 254 | get contextReplacesDamage() { 255 | return getBRSetting("contextReplacesDamage"); 256 | } 257 | 258 | get critString() { 259 | return getBRSetting("critString"); 260 | } 261 | 262 | get critBehavior() { 263 | return getBRSetting("critBehavior"); 264 | } 265 | 266 | get quickDefaultDescriptionEnabled() { 267 | return getBRSetting("quickDefaultDescriptionEnabled"); 268 | } 269 | 270 | get imageButtonEnabled() { 271 | return getBRSetting("imageButtonEnabled"); 272 | } 273 | 274 | get altSecondaryEnabled() { 275 | return getBRSetting("altSecondaryEnabled"); 276 | } 277 | 278 | get applyActiveEffects() { 279 | return getBRSetting("applyActiveEffects"); 280 | } 281 | 282 | get d20Mode() { 283 | return getBRSetting("d20Mode"); 284 | } 285 | 286 | get hideDC() { 287 | return getBRSetting("hideDC"); 288 | } 289 | 290 | get chatDamageButtonsEnabled() { 291 | const setting = getBRSetting("chatDamageButtonsEnabled"); 292 | return setting === "1" || (setting === "2" && game.user.isGM); 293 | } 294 | 295 | /** 296 | * True if damage buttons should be disabled, false is auto rolling. 297 | */ 298 | get damagePromptEnabled() { 299 | return getBRSetting("damagePromptEnabled"); 300 | } 301 | 302 | /** 303 | * Whether the die icon should be shown for d20 multi rolls 304 | */ 305 | get d20RollIconsEnabled() { 306 | return getBRSetting("d20RollIconsEnabled"); 307 | } 308 | 309 | get queryAdvantageEnabled() { 310 | return this.d20Mode === 4; 311 | } 312 | } 313 | 314 | /** 315 | * Class instance that can be used to both initialize and retrieve config 316 | */ 317 | export const BRSettings = new Settings(); 318 | 319 | /** 320 | * Returns a proxy that returns the given config and falls 321 | * back to global better roll config. 322 | * @param {Settings} config 323 | * @returns {Settings} 324 | */ 325 | export const getSettings = config => { 326 | if (!config || typeof config !== "object") { 327 | return BRSettings; 328 | } 329 | 330 | if (config.__isProxy) { 331 | return config; 332 | } 333 | 334 | const proxy = new Proxy(config, { 335 | get: (target, name) => { 336 | if (name === "__isProxy") { 337 | return true; 338 | } 339 | 340 | if (Reflect.has(target, name)) { 341 | return Reflect.get(target, name); 342 | } 343 | 344 | return Reflect.get(BRSettings, name); 345 | } 346 | }); 347 | 348 | proxy.isWrapped = true; 349 | return proxy; 350 | }; 351 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/utils/collection-functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a new object containing a subset of source 3 | * using the keys in props. Equivalent to lodash's pick method. 4 | * @param {object} source 5 | * @param {string[]} props 6 | * @returns {object} subset of source 7 | */ 8 | export function pick(source, props) { 9 | const result = {}; 10 | for (const prop of props) { 11 | result[prop] = source[prop]; 12 | } 13 | return result; 14 | } 15 | 16 | export function pickBy(source, predicate) { 17 | const props = []; 18 | for (const [key, value] of Object.entries(source)) { 19 | if (predicate(value, key)) { 20 | props.push(key); 21 | } 22 | } 23 | 24 | return pick(source, props); 25 | } 26 | 27 | /** 28 | * This method is like findIndex except that it iterates 29 | * from right to left. 30 | */ 31 | export function findLastIndex(array, predicate) { 32 | const length = array == null ? 0 : array.length; 33 | if (!length) { 34 | return -1; 35 | } 36 | 37 | for (let index = length - 1; index >= 0; index--) { 38 | if (predicate(array[index], index, array)) { 39 | return index; 40 | } 41 | } 42 | 43 | return -1; 44 | } 45 | 46 | /** 47 | * This method is like find except that it iterates 48 | * from right to left. 49 | */ 50 | export function findLast(collection, predicate) { 51 | const iterable = Object(collection); 52 | const index = findLastIndex(collection, predicate); 53 | return index > -1 ? iterable[index] : undefined; 54 | } 55 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/utils/dice-collection.js: -------------------------------------------------------------------------------- 1 | import { Utils } from "./utils.js"; 2 | 3 | /** 4 | * Class used to build a growing number of dice 5 | * that will be flushed to a system like Dice So Nice. 6 | */ 7 | export class DiceCollection { 8 | /** Roll object containing all the dice */ 9 | pool = new Roll("0").roll({async: false}); 10 | 11 | /** 12 | * Creates a new DiceCollection object 13 | * @param {...Roll} initialRolls optional additional dice to start with 14 | */ 15 | constructor(...initialRolls) { 16 | if (initialRolls.length > 0) { 17 | this.push(...initialRolls); 18 | } 19 | } 20 | 21 | /** 22 | * Creates a new dice pool from a set of rolls 23 | * and immediately flushes it, returning a promise that is 24 | * true if any rolls had dice. 25 | * @param {Roll[]} rolls 26 | * @returns {Promise} 27 | */ 28 | static createAndFlush(rolls) { 29 | return new DiceCollection(...rolls).flush(); 30 | } 31 | 32 | /** 33 | * Adds one or more rolls to the dice collection, 34 | * for the purposes of 3D dice rendering. 35 | * @param {...Roll} rolls 36 | */ 37 | push(...rolls) { 38 | for (const roll of rolls.filter(r => r)) { 39 | this.pool._dice.push(...roll.dice); 40 | } 41 | } 42 | 43 | /** 44 | * Displays the collected dice to any subsystem that is interested. 45 | * Currently its just Dice So Nice (if enabled). 46 | * @returns {Promise} if there were dice in the pool 47 | */ 48 | async flush({ hasMaestroSound=false, whisperData=null }) { 49 | // Get and reset immediately (stacking flush calls shouldn't roll more dice) 50 | const pool = this.pop(); 51 | 52 | const hasDice = pool.dice.length > 0; 53 | if (game.dice3d && hasDice) { 54 | const wd = whisperData ?? Utils.getWhisperData(); 55 | await game.dice3d.showForRoll(pool, game.user, true, wd.whisper, wd.blind || false, null, wd.speaker); 56 | } 57 | 58 | const sound = Utils.getDiceSound(hasMaestroSound); 59 | if (sound && hasDice) { 60 | // Note: emited events aren't caught by the same client 61 | // the push argument didn't work for me, so using sockets instead 62 | Utils.playDiceSound(); 63 | game.socket.emit("module.betterrolls5e", { 64 | action: "roll-sound", 65 | user: game.user.id 66 | }, () => console.log("Better Rolls | Roll Sound Message Sent")); 67 | } 68 | 69 | return hasDice; 70 | } 71 | 72 | /** 73 | * Retrieves the dice pool and clears it without rolling it. 74 | * @returns {Roll} 75 | */ 76 | pop() { 77 | const pool = this.pool; 78 | this.pool = new Roll("0").roll({async: false}); 79 | return pool; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from "./collection-functions.js"; 2 | export * from "./utils.js"; 3 | export * from "./dice-collection.js"; 4 | export * from "./proxy.js"; 5 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/utils/proxy.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Creates objects (proxies) which can be used to deserialize flags efficiently. 5 | * Currently this is used so that Roll objects unwrap properly. 6 | */ 7 | export const FoundryProxy = { 8 | // A set of all created proxies. Weaksets do not prevent garbage collection, 9 | // allowing us to safely test if something is a proxy by adding it in here 10 | proxySet: new WeakSet(), 11 | 12 | /** 13 | * Creates a new proxy that turns serialized objects (like rolls) into objects. 14 | * Use the result as if it was the original object. 15 | * @param {*} data 16 | */ 17 | create(data) { 18 | const proxy = new Proxy(data, FoundryProxy); 19 | FoundryProxy.proxySet.add(proxy); 20 | return proxy; 21 | }, 22 | 23 | /** 24 | * @private 25 | */ 26 | get(target, key) { 27 | const value = target[key]; 28 | 29 | // Prevent creating the same proxy again 30 | if (FoundryProxy.proxySet.has(value)) { 31 | return value; 32 | } 33 | 34 | if (value !== null && typeof value === 'object') { 35 | if (value.class === "Roll") { 36 | // This is a serialized roll, convert to roll object 37 | return Roll.fromData(value); 38 | } else if (!{}.hasOwnProperty.call(target, key)) { 39 | // this is a getter or setter function, so no proxy-ing 40 | return value; 41 | } else { 42 | // Create a nested proxy, and save the reference 43 | const proxy = FoundryProxy.create(value); 44 | target[key] = proxy; 45 | return proxy; 46 | } 47 | } else { 48 | return value; 49 | } 50 | }, 51 | 52 | /** 53 | * @private 54 | */ 55 | set(target, key, value) { 56 | target[key] = value; 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-ae-button.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-damage-button.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-damage-crit.html: -------------------------------------------------------------------------------- 1 | {{#if crit}} 2 | {{#if base}}+ {{/if}}
{{crit.total}}
3 | {{#if crittext}}
{{crittext}}
{{/if}} 4 | {{/if}} -------------------------------------------------------------------------------- /betterrolls5e/templates/red-damageroll.html: -------------------------------------------------------------------------------- 1 |
2 | {{#if damagetop}} 3 |
{{damagetop}}
4 | {{/if}} 5 |
6 |
{{formula}}
7 |
8 | {{#each tooltips}} 9 |
{{{this}}}
10 | {{/each}} 11 |
12 |
13 |

14 | {{#if base}} 15 |
{{base.total}}
16 | {{/if}} 17 | {{> "modules/betterrolls5e/templates/red-damage-crit.html" }} 18 | {{#if damagemid}}
{{damagemid}}
{{/if}} 19 |

20 |
21 | {{#if damagebottom}}
{{damagebottom}}
{{/if}} 22 |
23 |
24 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-description.html: -------------------------------------------------------------------------------- 1 | {{#if isFlavor}} 2 |
{{content}}
3 | {{else}} 4 |
{{{content}}}
5 | {{/if}} -------------------------------------------------------------------------------- /betterrolls5e/templates/red-footer.html: -------------------------------------------------------------------------------- 1 |
2 | {{#each data.properties}} 3 | {{this}} 4 | {{/each}} 5 |
-------------------------------------------------------------------------------- /betterrolls5e/templates/red-fullroll.html: -------------------------------------------------------------------------------- 1 |
2 | {{#each templates}} 3 | {{{this}}} 4 | {{/each}} 5 | {{#if properties}} 6 |
7 | {{#each properties}} 8 | {{this}} 9 | {{/each}} 10 |
11 | {{/if}} 12 |
13 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

{{item.name}}

5 |
6 |
-------------------------------------------------------------------------------- /betterrolls5e/templates/red-item-options.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 | 6 | 20 | 21 |
22 | {{#if flags.betterRolls5e.quickDesc}} 23 | 33 | {{/if}} 34 | 35 | {{#if flags.betterRolls5e.quickAttack}} {{#if isAttack}} 36 | 46 | {{/if}}{{/if}} 47 | 48 | {{#if flags.betterRolls5e.quickSave}} {{#if isSave}} 49 | 58 | {{/if}}{{/if}} 59 | 60 | {{#if flags.betterRolls5e.quickCheck}} 61 | 71 | {{/if}} 72 | 73 | {{#if flags.betterRolls5e.quickProperties}} 74 | 84 | {{/if}} 85 | 86 | {{#if flags.betterRolls5e.quickVersatile}} 87 | 97 | {{/if}} 98 | 99 | {{#if itemHasTemplate}} 100 | {{#if flags.betterRolls5e.quickTemplate}} 101 | 111 | {{/if}} 112 | {{/if}} 113 | 114 | {{#if flags.betterRolls5e.quickOther}} 115 | 125 | {{/if}} 126 | 127 | {{#if flags.betterRolls5e.quickFlavor}} 128 | 138 | {{/if}} 139 | 140 | {{#if canConsume}} 141 |
142 | 143 | 144 | {{#if hasQuantity}} 145 | 155 | {{/if}} 156 | 157 | {{#if hasUses}} 158 | 168 | {{/if}} 169 | 170 | {{#if hasResource}} 171 | 181 | {{/if}} 182 | 183 | {{#if hasCharge}} 184 | 194 | {{/if}} 195 | 196 |
197 | {{/if}} 198 |
199 | 200 |
201 | {{#if flags.betterRolls5e.quickDamage}} 202 | {{#each flags.betterRolls5e.quickDamage.value as |part i| }} 203 | 213 | {{/each}} 214 | {{/if}} 215 |
216 | 217 | 218 | {{#if altSecondaryEnabled}} 219 |

220 | 221 |

222 | 223 | 224 | 237 | 238 |
239 | {{#if flags.betterRolls5e.quickDesc}} 240 | 250 | {{/if}} 251 | 252 | {{#if flags.betterRolls5e.quickAttack}} {{#if isAttack}} 253 | 263 | {{/if}}{{/if}} 264 | 265 | {{#if flags.betterRolls5e.quickSave}} {{#if isSave}} 266 | 276 | {{/if}}{{/if}} 277 | 278 | {{#if flags.betterRolls5e.quickCheck}} 279 | 289 | {{/if}} 290 | 291 | {{#if flags.betterRolls5e.quickProperties}} 292 | 302 | {{/if}} 303 | 304 | {{#if flags.betterRolls5e.quickVersatile}} 305 | 315 | {{/if}} 316 | 317 | {{#if itemHasTemplate}} 318 | {{#if flags.betterRolls5e.quickTemplate}} 319 | 328 | {{/if}} 329 | {{/if}} 330 | 331 | {{#if flags.betterRolls5e.quickOther}} 332 | 342 | {{/if}} 343 | 344 | {{#if flags.betterRolls5e.quickFlavor}} 345 | 355 | {{/if}} 356 | 357 | {{#if canConsume}} 358 |
359 | 360 | 361 | {{#if hasQuantity}} 362 | 372 | {{/if}} 373 | 374 | {{#if hasUses}} 375 | 385 | {{/if}} 386 | 387 | {{#if hasResource}} 388 | 398 | {{/if}} 399 | 400 | {{#if hasCharge}} 401 | 411 | {{/if}} 412 | 413 |
414 | {{/if}} 415 | 416 |
417 |
418 | {{#if flags.betterRolls5e.quickDamage}} 419 | {{#each flags.betterRolls5e.quickDamage.altValue as |part i| }} 420 | 430 | {{/each}} 431 | {{/if}} 432 |
433 | {{/if}} 434 | 435 |
436 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-multiroll.html: -------------------------------------------------------------------------------- 1 |
2 | {{#if title}} 3 |
{{title}}
4 | {{/if}} 5 |
6 |
{{formula}}
7 |
8 | {{#each tooltips}} 9 |
{{{this}}}
10 | {{/each}} 11 |
12 |
13 | {{#if bonusTooltip}} 14 |
{{{bonusTooltip}}}
15 | {{/if}} 16 |
17 |
18 | {{#each entries}} 19 |

20 | {{this.total}}{{#if this.d20Result}}{{this.d20Result}}{{/if}} 21 |

22 | {{/each}} 23 |
24 |
25 |
-------------------------------------------------------------------------------- /betterrolls5e/templates/red-overlay-damage-crit-only.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-overlay-damage.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 6 | 7 | 10 | 11 | 12 | 15 | 18 | 21 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /betterrolls5e/templates/red-overlay-header.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
-------------------------------------------------------------------------------- /betterrolls5e/templates/red-overlay-multiroll.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 8 |
-------------------------------------------------------------------------------- /betterrolls5e/templates/red-save-button.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /samples/animate-objects.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Animate Object Attacks. 3 | * Initial version stolen from somewhere but I forgot from where 4 | */ 5 | const details = { 6 | "tiny" : { "size": 1, "atk": 8, "dmg": "1d4 + 4" }, 7 | "small" : { "size": 1, "atk": 6, "dmg": "1d8 + 2" }, 8 | "medium" : { "size": 2, "atk": 5, "dmg": "2d6 + 1" }, 9 | "large" : { "size": 4, "atk": 6, "dmg": "2d10 + 2" }, 10 | "huge" : { "size": 8, "atk": 8, "dmg": "2d12 + 4" } 11 | }; 12 | 13 | // Use select token or some defaults if nothing is selected. 14 | const actorData = actor || canvas.tokens.controlled[0] || game.user.character; 15 | 16 | if (!actorData) { 17 | ui.notifications.warn("No actor selected"); 18 | } 19 | 20 | const targetId = game.user.targets.ids[0]; 21 | const targetToken = canvas.tokens.get(targetId); 22 | const targetName = targetToken?.actor.name || "Enemy"; 23 | 24 | const count = parseInt(game.brMacro?.animate_objects_count, 10) || 10; 25 | const rollState = BetterRolls.getRollState(); 26 | 27 | const content = ` 28 |

Your animated objects attack ${targetName}!

29 |

Enter the details for the Animated Objects...

30 |
31 |
32 | 33 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 52 |
53 |
54 | `; 55 | 56 | const attackAction = async (html) => { 57 | let size = html.find("#size")[0].value; 58 | let count = html.find("#count")[0].value; 59 | let rollState = html.find("#roll")[0].value; 60 | const { atk, dmg } = details[size]; 61 | 62 | const rollTypes = { 63 | "highest": "Advantage", 64 | "lowest": "Disadvantage" 65 | }; 66 | 67 | let rollExpr = `1d20 + ${atk}`; 68 | const card = BetterRolls.rollItem(actorData); 69 | card.addField(["header", {img: this.data.img, title: "Animated Objects"}]); 70 | for (let i = 0; i < count; i++) { 71 | const rollTypeStr = rollState in rollTypes ? `(${rollTypes[rollState]})` : ''; 72 | const title = `Object Attacks #${i+1} (+${atk}) ${rollTypeStr}`; 73 | card.addField(["attack", { formula: rollExpr, rollState, title}]); 74 | card.addField(["damage", { formula: dmg }]); 75 | } 76 | await card.toMessage(); 77 | 78 | // Remember setting for next run 79 | game.brMacro = game.brMacro ?? {}; 80 | game.brMacro.animate_objects_count = count; 81 | }; 82 | 83 | new Dialog({ 84 | title: "Animated Object Attacks", 85 | content: content, 86 | buttons: { 87 | attack: { 88 | icon: '', 89 | label: "Attack!", 90 | callback: attackAction 91 | }, 92 | cancel: { label: "Cancel" } 93 | }, 94 | default: "attack", 95 | }).render(true); -------------------------------------------------------------------------------- /samples/defensive-flourish-ItemMacro.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Version of Defensive Flourish Macro meant to be used via the ItemMacro module. 3 | * Attach to any item with a damage roll. 4 | */ 5 | 6 | const effectImagePath = "icons/svg/combat.svg"; 7 | const actor = item?.actor; 8 | (async () => { 9 | if (!item || !actor) { 10 | return ui.notifications.error("This macro needs to be attached to an item on an actor using ItemMacro"); 11 | } 12 | 13 | const roll = BetterRolls.rollItem(item); 14 | await roll.toMessage(); 15 | 16 | if (roll.error) return; 17 | 18 | const value = roll.entriesFlattened().find(m => m.type === 'damage')?.baseRoll.total; 19 | const label = "Defensive Flourish"; 20 | const key = "data.attributes.ac.value"; 21 | 22 | const existing = actor.effects.entries.find(e => e.data.label === label); 23 | if (existing) { 24 | existing.update({ 25 | changes: [ 26 | { key, mode: 2, value, priority: 20 } 27 | ], 28 | disabled: false 29 | }); 30 | } else { 31 | actor.createEmbeddedEntity('ActiveEffect', { 32 | label, 33 | icon: effectImagePath ?? item.img, 34 | duration: { rounds: 1 }, 35 | changes: [ 36 | { key, mode: 2, value, priority: 20 } 37 | ] 38 | }); 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /samples/defensive-flourish.js: -------------------------------------------------------------------------------- 1 | const effectImagePath = "icons/svg/combat.svg"; 2 | const selected = [actor] || canvas.tokens.controlled || [game.user.character]; 3 | 4 | // Requires an item called Defensive Flourish that in turn consumes Bardic Inspiration uses 5 | (async () => { 6 | const actors = selected.filter(a => a); 7 | if (actors.length === 0) { 8 | return ui.notifications.error("No actors selected"); 9 | } 10 | 11 | let handled = false; 12 | for (const actor of actors) { 13 | const itemId = actor.items.find(i => i.name === 'Defensive Flourish')?.id; 14 | const item = actor.items.get(itemId); 15 | if (!item) continue; 16 | 17 | const roll = BetterRolls.rollItem(item); 18 | await roll.toMessage(); 19 | 20 | if (roll.error) continue; 21 | 22 | const value = roll.entriesFlattened().find(m => m.type === 'damage')?.baseRoll.total; 23 | const label = "Defensive Flourish"; 24 | const key = "data.attributes.ac.value"; 25 | 26 | const existing = actor.effects.entries.find(e => e.data.label === label); 27 | if (existing) { 28 | existing.update({ 29 | changes: [ 30 | { key, mode: 2, value, priority: 20 } 31 | ], 32 | disabled: false 33 | }); 34 | console.log(existing); 35 | } else { 36 | actor.createEmbeddedEntity('ActiveEffect', { 37 | label, 38 | icon: effectImagePath ?? item.img, 39 | duration: { rounds: 1 }, 40 | changes: [ 41 | { key, mode: 2, value, priority: 20 } 42 | ] 43 | }); 44 | } 45 | 46 | handled = true; 47 | } 48 | 49 | if (!handled) { 50 | ui.notifications.warn("No actors with a Defensive Flourish item was selected"); 51 | } 52 | })(); 53 | -------------------------------------------------------------------------------- /samples/life-cycle-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic Integration test that tests that a roll can be created, edited, updated, or what have you. 3 | * NOTE: Incomplete, was accidentally pushed. Will be updated later anyways. 4 | * As of the current release, when a roll is created, that's it. This will be used 5 | * to test that a roll can be edited using the same object for more advanced macros. 6 | */ 7 | 8 | (async () => { 9 | const settings = { 10 | damagePromptEnabled: true 11 | }; 12 | 13 | const card = BetterRolls.rollItem(item, { settings }); 14 | card.addField("header"); 15 | card.addField("flavor"); 16 | card.addField("description"); 17 | card.addField("attack"); 18 | card.addField("damage", { index: "all", versatile: true }); 19 | card.toMessage(); 20 | })(); 21 | --------------------------------------------------------------------------------