diff --git a/CHANGELOG.md b/CHANGELOG.md index a826045..022e58b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ ## Release/Patch Notes +### Version 1.2.7 - 2023-XX-XX + +* Refactor roll system +* Slight restyle of roll chat messages. +* Restyled actor sheets to be more in-line with the core book's design. You can change back to the old style in the settings for the Delta Green system. +* Weapons can now have modifiers for their rolls built-in. +* Weapons can choose a custom target for their rolls. This can be used for unnatural creature attacks that may not line up with one of the skills. +* Damage rolls can now utilize math functions like base Foundry rolls. +* Hide untrained skills toggle turned into toggleable eye icon rather than a checkbox. + ### Version 1.2.6 - 2023-09-13 * Fix [Github #82] - New packaging workflow from @jalensailin will hopefully resolve issues with the Forge by deploying the v11 compendium packs directly. @@ -8,7 +18,7 @@ * Fix - Removing more places where system still says 'Unofficial' at Arc Dream's request -### Version 1.2.4 - 2023-08-03 +### Version 1.2.4 - 2023-08-03 The system is now officially recognized by Arc Dream! @@ -55,25 +65,28 @@ The system is now officially recognized by Arc Dream! ### Version 1.0.4 - 2022-02-05 * Fix [GitHub #50] - The custom background images used on the Actor sheets was also showing up for other free-floating windows, such as when the encounter tracker is undocked. Because of the bright background, it was hard to read the text or see buttons, so have changed the CSS to try to exclude these windows from the custom background image. -* Fix [GitHub #43] - Bond Scores on Agent Pregens from u/jets_or_chasm fixed to correctly match charisma score of the agent. Also re-created the compendium using a newer version of the pregen sheet that has more +* Fix [GitHub #43] - Bond Scores on Agent Pregens from u/jets_or_chasm fixed to correctly match charisma score of the agent. Also re-created the compendium using a newer version of the pregen sheet that has more ### Version 1.0.3 - 2022-02-04 + * Other - Update compatible version to v9 (Note - this update could potentially have some effects on older Foundry versions) * Fix [GitHub #48] - The new system option to hide SAN score was not also hiding Ritual proficiency, making it trivial to back-calculate the SAN score for players that know the formula for the Ritual skill. * Fix [GitHub #50] - In Foundry v9, seems like the sections below the nav were anchored to the bottom of the window, so as the window got bigger the section would float away from the nav leaving a big gap. Attempted to fix CSS for this by setting how the grid should flex more specifically. ### Version 1.0.2 - 2021-11-04 + * Fix - Pregen Agents were getting duplicated Unarmed Strike items added on creation. * Fix - Unarmed Strike item in the compendium was using 'Melee Combat' instead of 'Unarmed Combat' as its skill. * Enhancment - Added system option for a world to make SAN target and skill rolls hidden from non-GM players. SAN tests change to blind rolls for players who are not GM. ### Version 1.0.1 - 2021-08-14 + * Fix [GitHub #31] - Default Type Skill of Art-Painting that is added automatically when making a new agent should be localizable now. * Enhancement - Added a separate Actor type and sheet for _Unnatural_ creatures and a similar sheet that for non-agent NPC characters. * Enhancement - The system will try to automatically add an 'Unarmed Attack' item to a new agent when they are created to make it more obvious how this is handled within the system. [Update - accidentally had this disabled in logic when packaging system, will re-add later...] * Enhancement - DEX x5 is now selectable as an option to roll for a weapon's skill test, which is useful for items like hand grenades that are rolled or thrown. * Enhancement - Eduard Cot (trombonecot) submitted a Catalan language pack for the system. -* Enhancement - Added list of Pregen Agents (originaly from Reddit user u/jets_or_chasm, AKA jimstorch) as a compendium pack to allow a player to jump back in quickly after a death, or to make playing for the first time easier by presenting a list of agents of 24 professions. See https://github.com/jimstorch/DGGen for more information. +* Enhancement - Added list of Pregen Agents (originaly from Reddit user u/jets_or_chasm, AKA jimstorch) as a compendium pack to allow a player to jump back in quickly after a death, or to make playing for the first time easier by presenting a list of agents of 24 professions. See for more information. * Enhancement - Added some roll tables that allow making Fall of Delta Green-style Operation Code names (e.g. 'Operation Able Archer'). These tables are based on roll tables created by Reddit user u/Travern. Note - there is also a compendium with a macro that will randomly pick a combination of the tables, then draw from those tables to make a random 2-part operation name to send to the chat. ### Version 1.0.0 - 2021-06-05 [BREAKING CHANGE: THIS VERSION FORWARD COMPATIBILE WITH FOUNDRY 0.8.6 OR HIGHER ONLY!!!] @@ -97,47 +110,50 @@ The system is now officially recognized by Arc Dream! ### Version 0.9.9 - 2021-03-27 - * Fix - [GitHub #23] - Localization mistake, had two spaces in between "Critical" and "Success" or "Failure" in chat cards. - * Fix - [GitHub #24] - The chat card that was generated from rolling a skill check by clicking on a weapon (e.g. Firearms for a pistol) was not being localized. Fixed this along with a related tooltip that wasn't being localized either. - * Fix - [GitHub #25] - The chat card for damage and lethality was not bolding those terms like it would for other skill or percentile rolls. - * Fix - [GitHub #26] - When clicking on the 'Roll Luck' button in the character sheet header, the resulting chat card was not being localized properly. - * Fix - [GitHub #27] - Using a language setting that did not have a full translation was breaking adding skills to the agent's actor sheet. Should not break quite so quickly now, but will likely need a better long term fix at some point. - * Fix - [GitHub #28] - Missing localization keys for break point tooltips added. - * Fix - Made Agent sheet default width a little wider so skills with longer names aren't getting clipped off. Also a few other alignment fixes in the CSS. +* Fix - [GitHub #23] - Localization mistake, had two spaces in between "Critical" and "Success" or "Failure" in chat cards. +* Fix - [GitHub #24] - The chat card that was generated from rolling a skill check by clicking on a weapon (e.g. Firearms for a pistol) was not being localized. Fixed this along with a related tooltip that wasn't being localized either. +* Fix - [GitHub #25] - The chat card for damage and lethality was not bolding those terms like it would for other skill or percentile rolls. +* Fix - [GitHub #26] - When clicking on the 'Roll Luck' button in the character sheet header, the resulting chat card was not being localized properly. +* Fix - [GitHub #27] - Using a language setting that did not have a full translation was breaking adding skills to the agent's actor sheet. Should not break quite so quickly now, but will likely need a better long term fix at some point. +* Fix - [GitHub #28] - Missing localization keys for break point tooltips added. +* Fix - Made Agent sheet default width a little wider so skills with longer names aren't getting clipped off. Also a few other alignment fixes in the CSS. - * Enhancement - [GitHub #28] - Spanish language translation, many thanks to CthulhuRol for providing it! - * Enhancement - Added a macro that allows for rolling the skill check on a weapon, so there can be macro buttons for both the damage and the skill check if desired. - * Sample syntax: game.deltagreen.rollItemSkillCheckMacro("Combat Dagger"); - * Enhancement - Thanks to *Uriele* - Added functionality from the Handler's Guide (page 188) for 'Inhuman' stat tests, test where the stat (e.g. CON) is 20 or higher, giving a x5 target of 100 or higher. In this event, the test auto succeeds on a roll of anything other than 100, and a roll lower than the stat value is a critical (along with regular critical logic). - * Enhancement - Added 'Ritual' skill, which is calculated and is equal to 99 - current sanity (thanks to *Uriele* for mentioning it). See page 166 under 'Ritual Activation' for the full rules. - * Enhancement - Total protection rating of all _equipped_ armor now displayed by HP. +* Enhancement - [GitHub #28] - Spanish language translation, many thanks to CthulhuRol for providing it! +* Enhancement - Added a macro that allows for rolling the skill check on a weapon, so there can be macro buttons for both the damage and the skill check if desired. + * Sample syntax: game.deltagreen.rollItemSkillCheckMacro("Combat Dagger"); +* Enhancement - Thanks to _Uriele_ - Added functionality from the Handler's Guide (page 188) for 'Inhuman' stat tests, test where the stat (e.g. CON) is 20 or higher, giving a x5 target of 100 or higher. In this event, the test auto succeeds on a roll of anything other than 100, and a roll lower than the stat value is a critical (along with regular critical logic). +* Enhancement - Added 'Ritual' skill, which is calculated and is equal to 99 - current sanity (thanks to _Uriele_ for mentioning it). See page 166 under 'Ritual Activation' for the full rules. +* Enhancement - Total protection rating of all _equipped_ armor now displayed by HP. - * Font and Background Change and Configuration Options - *NOTE THIS CAN BE CHANGED IN THE SYSTEM SETTINGS BACK TO WHAT IT WAS!* - * Enhancement - Added system setting for font choice, with a few options. For example you can set a world to use typewriter style font for an older feel. - * Enhancement - Added system setting for background image choice, with a few options. Changed default from normal Foundry parchment to a lighter, more moder looking paper. Also included option for old, used/crinkled paper for a more unsettling or hard-used look. +* Font and Background Change and Configuration Options - *NOTE THIS CAN BE CHANGED IN THE SYSTEM SETTINGS BACK TO WHAT IT WAS!* + * Enhancement - Added system setting for font choice, with a few options. For example you can set a world to use typewriter style font for an older feel. + * Enhancement - Added system setting for background image choice, with a few options. Changed default from normal Foundry parchment to a lighter, more moder looking paper. Also included option for old, used/crinkled paper for a more unsettling or hard-used look. ### Version 0.9.7 + * Enhancement - Can now click on the icon for armor/other gear to equip or unequip it. * Enhancement - Changed so that players with the 'Limited' permission on an actor now only see the C.V. tab of the actor's character sheet, instead of the entire sheet. Previously the 'Limited' permission and the 'Observer' permission gave the same result. The 'Observer' permission is unchanged and still shows a read-only view of the entire sheet. -* Enhancement - Added ability to alter formula for damage rolls by either __right clicking__ *or* __holding shift and left clicking__. -* Enhancement - Added ability to alter targets for tests by either __right clicking__ *or* __holding shift and left clicking__. Applies to the following rolls: - * Skill tests - * Attribute tests +* Enhancement - Added ability to alter formula for damage rolls by either **right clicking** _or_ **holding shift and left clicking**. +* Enhancement - Added ability to alter targets for tests by either **right clicking** _or_ **holding shift and left clicking**. Applies to the following rolls: + * Skill tests + * Attribute tests * Lethality tests * Improved CSS on actor sheet in a few places. - * New Font (was previously in style sheet, but not properly applied). - * Nav bar alignment/border consistent across tabs for a cleaner look moving between sections. - * Gear sheet alignment cleaner with certain fields now being centered. - * CV sheet now has some padding on fields so things aren't so cramped looking. + * New Font (was previously in style sheet, but not properly applied). + * Nav bar alignment/border consistent across tabs for a cleaner look moving between sections. + * Gear sheet alignment cleaner with certain fields now being centered. + * CV sheet now has some padding on fields so things aren't so cramped looking. * Fix - Thanks to @Hrunh for submitting update to localization in item sheet * Fix - typo in Alertness skill (English locale), thanks to roestrei for report ### Version 0.9.6 + * Incremented Core Compatible Version after testing against release version of Foundry 0.7.5. * Made the gear section a little less ugly by adding some nicer section dividers. * Fixed some awkward tooltip wording. ### Version 0.9.4 + * Fix - Pharmacy and Surgery skills were missing. * Fix - Accounting and Disguise base skill rating were 0%, should be 10%. * Fix - Damage on medium pistol in compendium was wrong, also fixed some base range values that were off. @@ -146,12 +162,15 @@ The system is now officially recognized by Arc Dream! * Enhancement - Added support for Dice So Nice module, since moving to custom chat message broke out of the box compatibility with it. ### Version 0.9.3 + We will never speak of 0.9.1 or 0.9.2 ever again. ### Version 0.9.1 + Added a few compendium packs with some common armor and weapon choices for Agents to equip themselves with. Note that weapon/armor statistics are not covered by the Open Game License like the other rules used in the system, they are included as an exception with permission from Arc Dream publishing. ### Version 0.9.0 + Basic system functionality complete and submitted to Foundry VTT as an official system. diff --git a/README.md b/README.md index f516e09..9f90cee 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,12 @@ Comes from a character generator created by jimstorch, who graciously posted the The roll tables added to generate operation code names comes from Reddit user /u/Travern. +## Localization + +Portugese (BR) Translation submitted by IgorAK25 +Spanish Translation submitted by CthulhuRol +Catalonian Translation submitted by trombonecot + ## Fonts (Some in use, others that I would like to use at some point) ### MARTEL FONT @@ -109,6 +115,12 @@ By Johan Holmdahl License: None given https://www.dafont.com/another-typewriter.font +### PUBLIC SANS FONT + +Designed by USWDS, Dan Williams, Pablo Impallari, Rodrigo Fuenzalida +License: Open Font License. +https://github.com/uswds/public-sans + ## Game Icons ICONS - https://game-icons.net/ @@ -152,6 +164,6 @@ https://creativecommons.org/licenses/by/3.0/ * Magic Hand icon by Lorc under CC BY 3.0 * Magic Shield icon by Lorc under CC BY 3.0 * Sly icon by Lorc under CC BY 3.0 - - -Portugese Translation submitted by IgorAK25 \ No newline at end of file +* Swap Bag icon by Lorc under CC BY 3.0 +* Person icon by Delapouite under CC BY 3.0 +* Cowled icon by Lorc under CC BY 3.0 \ No newline at end of file diff --git a/assets/font/public-sans/OFL.txt b/assets/font/public-sans/OFL.txt new file mode 100644 index 0000000..8ec2295 --- /dev/null +++ b/assets/font/public-sans/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2015 The Public Sans Project Authors (https://github.com/uswds/public-sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/font/public-sans/PublicSans-Italic-VariableFont_wght.ttf b/assets/font/public-sans/PublicSans-Italic-VariableFont_wght.ttf new file mode 100644 index 0000000..3727ffe Binary files /dev/null and b/assets/font/public-sans/PublicSans-Italic-VariableFont_wght.ttf differ diff --git a/assets/font/public-sans/PublicSans-VariableFont_wght.ttf b/assets/font/public-sans/PublicSans-VariableFont_wght.ttf new file mode 100644 index 0000000..cc76414 Binary files /dev/null and b/assets/font/public-sans/PublicSans-VariableFont_wght.ttf differ diff --git a/assets/font/public-sans/README.txt b/assets/font/public-sans/README.txt new file mode 100644 index 0000000..c02e0c1 --- /dev/null +++ b/assets/font/public-sans/README.txt @@ -0,0 +1,81 @@ +Public Sans Variable Font +========================= + +This download contains Public Sans as both variable fonts and static fonts. + +Public Sans is a variable font with this axis: + wght + +This means all the styles are contained in these files: + PublicSans-VariableFont_wght.ttf + PublicSans-Italic-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Public Sans: + static/PublicSans-Thin.ttf + static/PublicSans-ExtraLight.ttf + static/PublicSans-Light.ttf + static/PublicSans-Regular.ttf + static/PublicSans-Medium.ttf + static/PublicSans-SemiBold.ttf + static/PublicSans-Bold.ttf + static/PublicSans-ExtraBold.ttf + static/PublicSans-Black.ttf + static/PublicSans-ThinItalic.ttf + static/PublicSans-ExtraLightItalic.ttf + static/PublicSans-LightItalic.ttf + static/PublicSans-Italic.ttf + static/PublicSans-MediumItalic.ttf + static/PublicSans-SemiBoldItalic.ttf + static/PublicSans-BoldItalic.ttf + static/PublicSans-ExtraBoldItalic.ttf + static/PublicSans-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/assets/font/public-sans/static/PublicSans-Black.ttf b/assets/font/public-sans/static/PublicSans-Black.ttf new file mode 100644 index 0000000..8c9e8ec Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Black.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-BlackItalic.ttf b/assets/font/public-sans/static/PublicSans-BlackItalic.ttf new file mode 100644 index 0000000..98cde4b Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-BlackItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-Bold.ttf b/assets/font/public-sans/static/PublicSans-Bold.ttf new file mode 100644 index 0000000..7a14109 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Bold.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-BoldItalic.ttf b/assets/font/public-sans/static/PublicSans-BoldItalic.ttf new file mode 100644 index 0000000..505553b Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-BoldItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-ExtraBold.ttf b/assets/font/public-sans/static/PublicSans-ExtraBold.ttf new file mode 100644 index 0000000..148fee4 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-ExtraBold.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-ExtraBoldItalic.ttf b/assets/font/public-sans/static/PublicSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..433e2b8 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-ExtraBoldItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-ExtraLight.ttf b/assets/font/public-sans/static/PublicSans-ExtraLight.ttf new file mode 100644 index 0000000..ccad63c Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-ExtraLight.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-ExtraLightItalic.ttf b/assets/font/public-sans/static/PublicSans-ExtraLightItalic.ttf new file mode 100644 index 0000000..8c573f5 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-ExtraLightItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-Italic.ttf b/assets/font/public-sans/static/PublicSans-Italic.ttf new file mode 100644 index 0000000..7315d65 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Italic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-Light.ttf b/assets/font/public-sans/static/PublicSans-Light.ttf new file mode 100644 index 0000000..286bb3a Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Light.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-LightItalic.ttf b/assets/font/public-sans/static/PublicSans-LightItalic.ttf new file mode 100644 index 0000000..d2c621a Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-LightItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-Medium.ttf b/assets/font/public-sans/static/PublicSans-Medium.ttf new file mode 100644 index 0000000..1421f42 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Medium.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-MediumItalic.ttf b/assets/font/public-sans/static/PublicSans-MediumItalic.ttf new file mode 100644 index 0000000..1144923 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-MediumItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-Regular.ttf b/assets/font/public-sans/static/PublicSans-Regular.ttf new file mode 100644 index 0000000..7f55dba Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Regular.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-SemiBold.ttf b/assets/font/public-sans/static/PublicSans-SemiBold.ttf new file mode 100644 index 0000000..c7a1944 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-SemiBold.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-SemiBoldItalic.ttf b/assets/font/public-sans/static/PublicSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..49780a6 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-SemiBoldItalic.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-Thin.ttf b/assets/font/public-sans/static/PublicSans-Thin.ttf new file mode 100644 index 0000000..3d45ec8 Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-Thin.ttf differ diff --git a/assets/font/public-sans/static/PublicSans-ThinItalic.ttf b/assets/font/public-sans/static/PublicSans-ThinItalic.ttf new file mode 100644 index 0000000..293ad1d Binary files /dev/null and b/assets/font/public-sans/static/PublicSans-ThinItalic.ttf differ diff --git a/assets/icons/cowled.svg b/assets/icons/cowled.svg new file mode 100644 index 0000000..2e89550 --- /dev/null +++ b/assets/icons/cowled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/person-black-bg.svg b/assets/icons/person-black-bg.svg new file mode 100644 index 0000000..6715680 --- /dev/null +++ b/assets/icons/person-black-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/swap-bag-black-bg.svg b/assets/icons/swap-bag-black-bg.svg new file mode 100644 index 0000000..1d0902e --- /dev/null +++ b/assets/icons/swap-bag-black-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/CrumpledPlainPaper10.webp b/assets/img/CrumpledPlainPaper10.webp new file mode 100644 index 0000000..21028a7 Binary files /dev/null and b/assets/img/CrumpledPlainPaper10.webp differ diff --git a/assets/img/StainedPlainPaper10.webp b/assets/img/StainedPlainPaper10.webp new file mode 100644 index 0000000..d6dcd49 Binary files /dev/null and b/assets/img/StainedPlainPaper10.webp differ diff --git a/assets/img/StainedPlainPaper10a.webp b/assets/img/StainedPlainPaper10a.webp new file mode 100644 index 0000000..3420463 Binary files /dev/null and b/assets/img/StainedPlainPaper10a.webp differ diff --git a/assets/img/StainedPlainPaper10b.webp b/assets/img/StainedPlainPaper10b.webp new file mode 100644 index 0000000..7939da7 Binary files /dev/null and b/assets/img/StainedPlainPaper10b.webp differ diff --git a/assets/img/ivory-off-white-paper-texture.webp b/assets/img/ivory-off-white-paper-texture.webp new file mode 100644 index 0000000..5dc79bd Binary files /dev/null and b/assets/img/ivory-off-white-paper-texture.webp differ diff --git a/assets/img/old_paper.jpg b/assets/img/old_paper.jpg deleted file mode 100644 index fb530c6..0000000 Binary files a/assets/img/old_paper.jpg and /dev/null differ diff --git a/css/deltagreen.css b/css/deltagreen.css index ef7b53b..a81d007 100644 --- a/css/deltagreen.css +++ b/css/deltagreen.css @@ -28,23 +28,66 @@ src: url('../assets/font/special_elite/SpecialElite-Regular.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */ } +@font-face { + font-family: 'PublicSans'; + src: url('../assets/font/public-sans/PublicSans-VariableFont_wght.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */ +} + /* Global styles */ -/* -.window-app { - font-family: "TypeWriterCondensed", "atwriter", "Martel", 'Signika', sans-serif !important; - height: auto; + +/* Hacky overrides of outer Foundry stuff */ + +/* Override default foundry CSS to remove padding, so that our background image can take up the whole window */ +.deltagreen section.window-content{ + padding: 0 !important; } -*/ + +/* Override the default Foundry CSS to add the padding we removed from the outer window back in */ +.deltagreen section.window-content form.editable{ + padding: 6px 6px 6px 6px !important; +} + +/* Character Sheet Style Options */ + +form.outlaw-style, +form.cowboy-style{ + background: url("../assets/img/StainedPlainPaper10b.webp") !important; + background-size: 100% !important; +} + +form.program-style{ + background: url("../assets/img/StainedPlainPaper10a.webp") !important; + background-size: 100% !important; +} + +.cowboy-style{ + font-family: "SpecialElite", sans-serif !important; +} + +.outlaw-style, +.program-style{ + font-family: "PublicSans", sans-serif !important; +} + +.outlaw-style +.cowboy-style .tabs .item { + background: rgba(173, 151, 101, 0.3); +} + +.program-style .tabs .item { + background: rgba(138, 136, 133, 0.3); +} + /* -section.window-content{ - background: url("../assets/img/old_paper.jpg") !important; +#pause img { + content: url(../assets/img/DG_Pause_Icon.webp); } */ .old-paper-background{ - background: url("../assets/img/old_paper.jpg") !important; + background: url("../assets/img/StainedPlainPaper10.webp") !important; } .default-parchment-background{ background: url("/ui/parchment.jpg") repeat !important; @@ -70,6 +113,13 @@ section.window-content{ font-family: "SpecialElite", sans-serif !important; } +.public-sans-font{ + font-family: "PublicSans", sans-serif !important; +} + + + + .rollable:hover, .rollable:focus { color: #000; text-shadow: 0 0 10px red; @@ -84,6 +134,10 @@ section.window-content{ color:rgb(117, 0, 0); } +.bottom-margin-5px{ + margin-bottom: 5px !important; +} + .grid-1col{ display: grid; grid-column: span 1 / span 1; @@ -161,7 +215,17 @@ section.window-content{ align-items: center; text-align: center; padding: 2px; + +} + +.flex-thin-border{ border: 1px solid #999; + border-radius: 2px; +} + +.flex-mid-border{ + border: 1.5px solid rgb(0, 0, 0); + border-radius: 2px; } .flex-group-left { @@ -223,6 +287,7 @@ section.window-content{ -webkit-box-pack: start; -ms-flex-pack: start; justify-content: flex-start; + margin-bottom: 8px; } .deltagreen .sheet-header .profile-img { @@ -255,6 +320,7 @@ section.window-content{ margin: 0; } + .deltagreen .sheet-body, .deltagreen .sheet-body .tab, .deltagreen .sheet-body .tab .editor { @@ -272,6 +338,15 @@ section.window-content{ border-top: transparent !important; } +.deltagreen section.sheet-body{ + border: #000 !important; + border-width: 2px !important; + border-style: solid !important; + border-radius: 3px 3px 3px 3px; + + padding: 5px; +} + .deltagreen .tabs { height: auto; text-align: center; @@ -290,7 +365,7 @@ section.window-content{ border: gray; border-width: 1px 1px 0px 1px; border-style: solid; - background: rgba(173, 151, 101, 0.3); + /*background: rgba(173, 151, 101, 0.3);*/ } .deltagreen .tabs .item.active { @@ -301,6 +376,16 @@ section.window-content{ border-style: solid; } +.deltagreen .program-style .tabs .item.active { + background-color: #000000; + color: #ffffff; +} + +.deltagreen .cowboy-style .tabs .item.active { + background-color: rgba(156, 115, 18, 0.3); + color: #000000; +} + .deltagreen .items-list { list-style: none; /*margin: 7px 0; */ @@ -454,7 +539,7 @@ div span.bio-grid-label{ } .bio-grid-top-border{ - border-top: 1px solid #AAA; + border-top: 0px solid #AAA; padding-top: 10px; } @@ -479,7 +564,7 @@ div span.bio-grid-label{ .breaking-point-grid-3col{ display: grid; grid-template-columns: 100px 70px 65px; - border-top: 1px solid #AAA; + border-top: 0px solid #AAA; margin-bottom: 10px; border-bottom: 1px solid #AAA; align-content: center; @@ -498,7 +583,7 @@ div span.bio-grid-label{ justify-content: left; align-items: center; padding-bottom: 5px; - border-bottom: 1px solid #AAA; + border-bottom: 0px solid #AAA; } .adapted-label{ @@ -550,7 +635,7 @@ div .first-aid-received-section{ } .bonds-section-header{ - border-top: 1px solid #AAA; + /* border-top: 1px solid #AAA; */ width: 100%; display: flex; justify-content: center; @@ -583,10 +668,11 @@ textarea.wounds{ grid-template-columns: 100%; align-items: center; justify-content: center; - border: 1px solid #999; + border: 1.5px solid #000000; padding: 5px; grid-gap: 3px; text-align: center; + border-radius: 2px; } div.max-resource-value{ @@ -608,16 +694,65 @@ div.max-resource-value{ grid-gap: 5px; } -div.gear-section-header{ +.physical-stats-and-feature{ + border-radius: 2px; + border: 1.5px solid #000000; +} + +div.horizontal-bar-section-divider{ display: grid; grid-template-columns: 100%; justify-content: center; align-items: center; text-align: center; + margin-bottom: 3px; + background: #000; color: #fff; height: 30px; + + border-radius: 3px 3px 3px 3px; +} + +div.horizontal-bar-section-divider.contains-hide-button { + display: grid; + grid-template-columns: repeat(3, 1fr); + justify-content: center; + align-items: center; + text-align: center; + + margin-bottom: 3px; + + background: #000; + color: #fff; + height: 30px; + + border-radius: 3px 3px 3px 3px; +} + +.horizontal-bar-section-divider .contains-hide-button-title { + grid-column: 2; +} + +.horizontal-bar-section-divider .toggle-untrained { + grid-column: 3; + padding-right: 0.5rem; + justify-self: end; +} + +div.horizontal-bar-section-header{ + display: grid; + grid-template-columns: 100%; + justify-content: center; + align-items: center; + text-align: center; + + margin-bottom: 3px; + + background: #000; + color: #fff; + height: 50px; } .physical-description-label{ @@ -633,7 +768,6 @@ div.motivation-editor-container div.editor{ background: none !important; } - .deltagreen .tox-editor-header { background: none !important; } @@ -826,7 +960,7 @@ input.item-sheet-short-input{ .weapon-sheet-horizontal-grid-left-2col{ display: grid; - grid-template-columns: 40% 60%; + grid-template-columns: 50% 50%; align-items: center; text-align: left; padding-bottom: 10px; diff --git a/lang/ca.json b/lang/ca.json index a45cc49..bc5bee6 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -128,7 +128,6 @@ "DG.CV.NationalityPlaceholder": "Nacionalitat", "DG.CV.Education": "Educació", "DG.CV.EducationPlaceholder": "Educació o entrenament", - "DG.CV.PhysicalDescriptionLabel": "Descripció física/notes", "DG.Bonds.Bond": "Vincle", "DG.Bonds.Name": "Nom", @@ -227,6 +226,17 @@ "DG.TypeSkills.Subskills.Painting": "Pintar", + "DG.Sheet.BlockHeaders.Attributes": "ATTRIBUTES", + "DG.Sheet.BlockHeaders.Statistics": "STATISTICS", + "DG.Sheet.BlockHeaders.PhysicalDescriptionNotes": "Descripció física/notes", + "DG.Sheet.BlockHeaders.SkillsAndTraining": "SKILLS AND TRAINING", + "DG.Sheet.BlockHeaders.Injuries": "INJURIES", + "DG.Sheet.BlockHeaders.BreakingPointAndAdaptations": "BREAKING POINT AND ADAPTATIONS", + "DG.Sheet.BlockHeaders.MotivationsAndDisorders": "MOTIVATIONS AND DISDORDERS", + "DG.Sheet.BlockHeaders.PersonalInfoCV": "PERSONAL INFORMATION / CV", + "DG.Sheet.BlockHeaders.BondsRelationshipsContacts": "BONDS / RELATIONSHIPS / CONTACTS", + + "DG.UnnaturalSheet.HideUntrainedSkillsLabel": "Amagar habilitats no entrenades", "DG.UnnaturalSheet.NotesLabel": "Notes", "DG.UnnaturalSheet.ShortDescriptionFallbackText": "Descripció de la criatura o tipus de criatura", @@ -249,5 +259,9 @@ "DG.Generic.CheckDamagedBondsBoxUntilNextHomeScene": "Check a damaged Bond's box until the next Home Scene ends.", "DG.Generic.ClearAll": "Clear All", - "DG.Generic.SanDamage": "SAN Damage" + "DG.Generic.SanDamage": "SAN Damage", + + "DG.ItemWindow.Weapons.CustomSkillTarget": "Target", + "DG.ItemWindow.Weapons.SkillModifier": "Skill Modifier", + "DG.ItemWindow.Custom": "Custom" } diff --git a/lang/en.json b/lang/en.json index f0a32bf..4c57405 100644 --- a/lang/en.json +++ b/lang/en.json @@ -112,8 +112,8 @@ "DG.Mental.Helplessness": "Helplessness", "DG.Mental.AdaptedToViolence": "Adapted to Violence", "DG.Mental.AdaptedToHelplessness": "Adapted to Helplessness", - "DG.Mental.Motivations": "Motivations", - "DG.Mental.Disorders": "Disorders", + "DG.Mental.Motivations": "Motivation", + "DG.Mental.Disorders": "Disorder", "DG.Gear.Weapons": "WEAPONS", "DG.Gear.Armor": "ARMOR", @@ -134,7 +134,6 @@ "DG.CV.NationalityPlaceholder": "Nationality", "DG.CV.Education": "Education", "DG.CV.EducationPlaceholder": "Education or Training", - "DG.CV.PhysicalDescriptionLabel": "Physical Description/Notes", "DG.Bonds.Bond": "Bond", "DG.Bonds.Name": "Name", @@ -233,9 +232,20 @@ "DG.TypeSkills.Subskills.Painting": "Painting", - "DG.UnnaturalSheet.HideUntrainedSkillsLabel": "Hide Untrained Skills", + "DG.Sheet.BlockHeaders.Attributes": "ATTRIBUTES", + "DG.Sheet.BlockHeaders.Statistics": "STATISTICS", + "DG.Sheet.BlockHeaders.PhysicalDescriptionNotes": "PHYSICAL DESCRIPTION / NOTES", + "DG.Sheet.BlockHeaders.SkillsAndTraining": "SKILLS AND TRAINING", + "DG.Sheet.BlockHeaders.Injuries": "INJURIES", + "DG.Sheet.BlockHeaders.BreakingPointAndAdaptations": "BREAKING POINT AND ADAPTATIONS", + "DG.Sheet.BlockHeaders.MotivationsAndDisorders": "MOTIVATIONS AND DISDORDERS", + "DG.Sheet.BlockHeaders.PersonalInfoCV": "PERSONAL INFORMATION / CV", + "DG.Sheet.BlockHeaders.BondsRelationshipsContacts": "BONDS / RELATIONSHIPS / CONTACTS", + + "DG.UnnaturalSheet.HideUntrainedSkillsLabel": "Hide Untrained", "DG.UnnaturalSheet.NotesLabel": "Notes", "DG.UnnaturalSheet.ShortDescriptionFallbackText": "Description of creature or creature type", + "DG.UnnaturalSheet.ShowUntrainedSkillsLabel": "Show Untrained", "DG.UnnaturalSheet.Tabs.NotesTabLabel": "Skills & Notes", "DG.UnnaturalSheet.Tabs.ItemsTabLabel": "Attacks & Abilities", "DG.NPCSheet.BreakPointAbbreviation": "BP", @@ -253,7 +263,11 @@ "DG.VehicleSheet.DescriptionPlaceHolder": "Vehicle Description", "DG.Generic.CheckDamagedBondsBoxUntilNextHomeScene": "Check a damaged Bond's box until the next Home Scene ends.", - "DG.Generic.ClearAll": "Clear All", + "DG.Generic.ClearAll": "Clear Checks", - "DG.Generic.SanDamage": "SAN Damage" + "DG.Generic.SanDamage": "SAN Damage", + + "DG.ItemWindow.Weapons.CustomSkillTarget": "Target", + "DG.ItemWindow.Weapons.SkillModifier": "Skill Modifier", + "DG.ItemWindow.Custom": "Custom" } \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 7058442..04b62c5 100644 --- a/lang/es.json +++ b/lang/es.json @@ -128,7 +128,6 @@ "DG.CV.NationalityPlaceholder": "Nacionalidad", "DG.CV.Education": "Educación", "DG.CV.EducationPlaceholder": "Educación o Entrenamiento", - "DG.CV.PhysicalDescriptionLabel": "Descripción Física/Notas", "DG.Bonds.Bond": "Vínculo", "DG.Bonds.Name": "Nombre", @@ -227,6 +226,16 @@ "DG.TypeSkills.Subskills.Painting": "Cuadro", + "DG.Sheet.BlockHeaders.Attributes": "ATTRIBUTES", + "DG.Sheet.BlockHeaders.Statistics": "STATISTICS", + "DG.Sheet.BlockHeaders.PhysicalDescriptionNotes": "Descripción Física/Notas", + "DG.Sheet.BlockHeaders.SkillsAndTraining": "SKILLS AND TRAINING", + "DG.Sheet.BlockHeaders.Injuries": "INJURIES", + "DG.Sheet.BlockHeaders.BreakingPointAndAdaptations": "BREAKING POINT AND ADAPTATIONS", + "DG.Sheet.BlockHeaders.MotivationsAndDisorders": "MOTIVATIONS AND DISDORDERS", + "DG.Sheet.BlockHeaders.PersonalInfoCV": "PERSONAL INFORMATION / CV", + "DG.Sheet.BlockHeaders.BondsRelationshipsContacts": "BONDS / RELATIONSHIPS / CONTACTS", + "DG.UnnaturalSheet.HideUntrainedSkillsLabel": "Hide Untrained Skills", "DG.UnnaturalSheet.NotesLabel": "Notes", "DG.UnnaturalSheet.ShortDescriptionFallbackText": "Description of creature or creature type", @@ -260,5 +269,9 @@ "DG.Generic.CheckDamagedBondsBoxUntilNextHomeScene": "Check a damaged Bond's box until the next Home Scene ends.", "DG.Generic.ClearAll": "Clear All", - "DG.Generic.SanDamage": "SAN Damage" + "DG.Generic.SanDamage": "SAN Damage", + + "DG.ItemWindow.Weapons.CustomSkillTarget": "Target", + "DG.ItemWindow.Weapons.SkillModifier": "Skill Modifier", + "DG.ItemWindow.Custom": "Custom" } \ No newline at end of file diff --git a/lang/ja.json b/lang/ja.json index 734f44e..f97c86d 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -128,7 +128,6 @@ "DG.CV.NationalityPlaceholder": "国籍", "DG.CV.Education": "教育", "DG.CV.EducationPlaceholder": "教育", - "DG.CV.PhysicalDescriptionLabel": "身体的特徴とメモ", "DG.Bonds.Bond": "絆", "DG.Bonds.Name": "名前", @@ -227,6 +226,16 @@ "DG.TypeSkills.Subskills.Painting": "ペインティング", + "DG.Sheet.BlockHeaders.Attributes": "ATTRIBUTES", + "DG.Sheet.BlockHeaders.Statistics": "STATISTICS", + "DG.Sheet.BlockHeaders.PhysicalDescriptionNotes": "身体的特徴とメモ", + "DG.Sheet.BlockHeaders.SkillsAndTraining": "SKILLS AND TRAINING", + "DG.Sheet.BlockHeaders.Injuries": "INJURIES", + "DG.Sheet.BlockHeaders.BreakingPointAndAdaptations": "BREAKING POINT AND ADAPTATIONS", + "DG.Sheet.BlockHeaders.MotivationsAndDisorders": "MOTIVATIONS AND DISDORDERS", + "DG.Sheet.BlockHeaders.PersonalInfoCV": "PERSONAL INFORMATION / CV", + "DG.Sheet.BlockHeaders.BondsRelationshipsContacts": "BONDS / RELATIONSHIPS / CONTACTS", + "DG.UnnaturalSheet.HideUntrainedSkillsLabel": "持たないスキルを表示しない", "DG.UnnaturalSheet.NotesLabel": "ノート", "DG.UnnaturalSheet.ShortDescriptionFallbackText": "外見・種類・など", @@ -249,5 +258,9 @@ "DG.Generic.CheckDamagedBondsBoxUntilNextHomeScene": "すぎのホーム・シーヌまで、傷つけた絆のボックスにチェクを", "DG.Generic.ClearAll": "全てのチェックを消す", - "DG.Generic.SanDamage": "SAN ダメージ" + "DG.Generic.SanDamage": "SAN ダメージ", + + "DG.ItemWindow.Weapons.CustomSkillTarget": "ターゲット", + "DG.ItemWindow.Weapons.SkillModifier": "ターゲットに加えて", + "DG.ItemWindow.Custom": "カスタム" } \ No newline at end of file diff --git a/lang/pt-BR.json b/lang/pt-BR.json index 9bc9f38..efa1eee 100644 --- a/lang/pt-BR.json +++ b/lang/pt-BR.json @@ -115,7 +115,6 @@ "DG.CV.NationalityPlaceholder": "Nacionalidade", "DG.CV.Education": "Educação", "DG.CV.EducationPlaceholder": "Educação ou Treinamento", - "DG.CV.PhysicalDescriptionLabel": "Descrição Física/Notas", "DG.Bonds.Bond": "Vínculo", "DG.Bonds.Name": "Nome", "DG.Bonds.Score": "Pontuação", @@ -199,6 +198,17 @@ "DG.RollMode": "Modo de Rolagem", "DG.Bonds.Relationship": "Relacionamento", "DG.TypeSkills.Subskills.Painting": "Pintura", + + "DG.Sheet.BlockHeaders.Attributes": "ATTRIBUTES", + "DG.Sheet.BlockHeaders.Statistics": "STATISTICS", + "DG.Sheet.BlockHeaders.PhysicalDescriptionNotes": "Descrição Física/Notas", + "DG.Sheet.BlockHeaders.SkillsAndTraining": "SKILLS AND TRAINING", + "DG.Sheet.BlockHeaders.Injuries": "INJURIES", + "DG.Sheet.BlockHeaders.BreakingPointAndAdaptations": "BREAKING POINT AND ADAPTATIONS", + "DG.Sheet.BlockHeaders.MotivationsAndDisorders": "MOTIVATIONS AND DISDORDERS", + "DG.Sheet.BlockHeaders.PersonalInfoCV": "PERSONAL INFORMATION / CV", + "DG.Sheet.BlockHeaders.BondsRelationshipsContacts": "BONDS / RELATIONSHIPS / CONTACTS", + "DG.UnnaturalSheet.HideUntrainedSkillsLabel": "Esconder Habilidade não Treinadas", "DG.UnnaturalSheet.NotesLabel": "Notas", "DG.UnnaturalSheet.ShortDescriptionFallbackText": "Descrição de Criatura ou Tipo de Criatura", @@ -215,5 +225,7 @@ "DG.Generic.CheckDamagedBondsBoxUntilNextHomeScene": "Check a damaged Bond's box until the next Home Scene ends.", "DG.Generic.ClearAll": "Clear All", - "DG.Generic.SanDamage": "SAN Damage" + "DG.Generic.SanDamage": "SAN Damage", + + "DG.Generic.Custom": "Custom" } \ No newline at end of file diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js index f7a5b81..f520662 100644 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -1,10 +1,10 @@ +/* globals $ game Roll ChatMessage AudioHelper ActorSheet mergeObject Dialog TextEditor ActiveEffect ui duplicate fromUuidSync */ + import { - sendPercentileTestToChat, - sendLethalityTestToChat, - sendDamageRollToChat, - showModifyPercentileTestDialogue, - showModifyDamageRollDialogue, - sendSanityDamageToChat + DGPercentileRoll, + DGLethalityRoll, + DGDamageRoll, + DGSanityDamageRoll, } from "../roll/roll.js" /** * Extend the basic ActorSheet with some very simple modifications @@ -99,8 +99,6 @@ export class DeltaGreenActorSheet extends ActorSheet { // Iterate through items, allocating to containers // let totalWeight = 0; for (let i of sheetData.items) { - let item = i; - i.img = i.img || DEFAULT_TOKEN; // Append to armor. if (i.type === 'armor') { armor.push(i); @@ -142,21 +140,30 @@ export class DeltaGreenActorSheet extends ActorSheet { // This only exists to give a chance to activate the modifier dialogue if desired // Cannot seem to trigger the event on a right-click, so unfortunately only applies to a shift-click currently. - luckRollOnClick(event, actor, label){ + async luckRollOnClick(event){ if(event && event.which === 2){ // probably don't want rolls to trigger from a middle mouse click so just kill it here return; } - - let requestedModifyRoll = (event && event.shiftKey || event.which === 3); - let target = 50; - - if(requestedModifyRoll){ - showModifyPercentileTestDialogue(actor, label, target, false); + const rollOptions = { + rollType: "luck", + key: "luck", + actor: this.actor, } - else{ - sendPercentileTestToChat(actor, label, target, game.settings.get("core", "rollMode")); - } + + // Create a default 1d100 roll just in case. + let roll = new DGPercentileRoll("1D100", {}, rollOptions); + // Open dialog if user requests it. + if (event.shiftKey || event.which === 3) { + const dialogData = await roll.showDialog(); + if (!dialogData) return; + roll.modifier += dialogData.targetModifier; + roll.options.rollMode = dialogData.rollMode; + } + // Evaluate the roll. + await roll.evaluate({ async: true }); + // Send the roll to chat. + roll.toChat(); } activeEffectTest(sheet){ @@ -211,8 +218,9 @@ export class DeltaGreenActorSheet extends ActorSheet { }); // Rollable abilities - bind to everything with the 'Rollable' class - //html.find('.rollable').click(this._onRoll.bind(this)); - html.find('.rollable').mouseup(this._onRoll.bind(this)); + html.find('.rollable').click(this._onRoll.bind(this)); + + html.find('.toggle-untrained').click(() => this.actor.update({"system.showUntrainedSkills": !this.actor.system.showUntrainedSkills})); // Macro for toggling an item's equipped state html.find('.equipped-item').mousedown(this._onEquippedStatusChange.bind(this)); @@ -560,16 +568,17 @@ export class DeltaGreenActorSheet extends ActorSheet { }; if(type == "weapon"){ - itemData.system.skill = "firearms"; //default skill to firearms, since that will be most common - itemData.system.expense = "Standard"; + //itemData.system.skill = "firearms"; //default skill to firearms, since that will be most common + //itemData.system.expense = "Standard"; } else if(type == "armor"){ - itemData.system.armor = 3; - itemData.system.expense = "Standard"; + //itemData.system.armor = 3; + //itemData.system.expense = "Standard"; } else if(type == "bond"){ + // try to default bonds for an agent to their current CHA itemData.system.score = this.object.system.statistics.cha.value; // Can vary, but at character creation starting bond score is usually agent's charisma - itemData.img = "icons/svg/mystery-man.svg" + //itemData.img = "icons/svg/mystery-man.svg" } // create the item @@ -578,133 +587,86 @@ export class DeltaGreenActorSheet extends ActorSheet { /** * Handle clickable rolls. + * * @param {Event} event The originating click event + * @async * @private */ - _onRoll(event) { + async _onRoll(event) { event.preventDefault(); const element = event.currentTarget; - const dataset = element.dataset; + const { dataset } = element; - if(event && event.which === 2){ + if(event && event.which === 2) { // probably don't want rolls to trigger from a middle mouse click so just kill it here return; } - if (dataset.roll) { - - let key = dataset.label ? dataset.label : ''; - let label = dataset.label ? `${dataset.label}` : ''; - let targetVal = ""; - let rollType = dataset.rolltype ? dataset.rolltype : ''; - - let isDamageRoll = false; - let isLethalityRoll = false; - let isSanityDamageRoll = false; - - // if shift-click or right click, bring up roll editor dialog - let requestedModifyRoll = (event && event.shiftKey || event.which === 3); //(event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); + const item = this.actor.items.get(dataset.iid); + const rollOptions = { + rollType: dataset.rolltype, + key: dataset.key, + actor: this.actor, + item + } - // check the 'data-target="something" property to determine how to grab the target for the roll - if(rollType === "skill" || rollType === "typeskill"){ - targetVal = dataset.target; - label = game.i18n.localize(label).toUpperCase(); - } - else if(rollType === "weaponskill"){ - targetVal = dataset.target; - - // some weapons randomly can just use dexterity x5, so try to trap on that - // otherwise roll a regular skill test - if(targetVal === "dex"){ - label = game.i18n.localize("DG.Attributes.dex").toUpperCase() + "x5"; - targetVal = this.actor.system.statistics.dex.x5; - } - else if(targetVal === "int"){ - label = game.i18n.localize("DG.Attributes.int").toUpperCase() + "x5"; - targetVal = this.actor.system.statistics.int.x5; - } - else if(targetVal === "str"){ - label = game.i18n.localize("DG.Attributes.str").toUpperCase() + "x5"; - targetVal = this.actor.system.statistics.str.x5; - } - else if(targetVal === "con"){ - label = game.i18n.localize("DG.Attributes.con").toUpperCase() + "x5"; - targetVal = this.actor.system.statistics.con.x5; - } - else if(targetVal === "pow"){ - label = game.i18n.localize("DG.Attributes.pow").toUpperCase() + "x5"; - targetVal = this.actor.system.statistics.pow.x5; - } - else if(targetVal === "cha"){ - label = game.i18n.localize("DG.Attributes.cha").toUpperCase() + "x5"; - targetVal = this.actor.system.statistics.cha.x5; - } - else{ - label = game.i18n.localize("DG.Skills." + targetVal).toUpperCase(); - targetVal = this.actor.system.skills[targetVal].proficiency; - } - } - else if(dataset.target === "statistic.x5"){ - let stat = this.actor.system.statistics[key]; - targetVal = stat.x5; - label = game.i18n.localize("DG.Attributes." + key).toUpperCase() + "x5"; - } - else if(rollType === "sanity"){ - targetVal = this.actor.system.sanity.value; - label = game.i18n.localize("DG.Attributes.SAN").toUpperCase(); - } - else if(rollType === "damage"){ - // damage roll, not a skill check - isDamageRoll = true; - label = dataset.label ? dataset.label : ''; - } - else if(rollType === "lethality"){ - // a lethality roll - isLethalityRoll = true; - targetVal = dataset.target; - } - else if(rollType === 'sanity-damage'){ - isSanityDamageRoll = true; - } - - if(isDamageRoll){ - - let diceFormula = dataset.roll; - let skillType = dataset.skill ? dataset.skill : ''; - - if(this.actor.type === 'agent' && (skillType === 'unarmed_combat' || skillType === 'melee_weapons')){ + // Create a default 1d100 roll just in case. + let roll = new Roll("1d100", {}); + switch (dataset.rolltype) { + case "stat": + case "skill": + case "sanity": + case "weapon": + roll = new DGPercentileRoll("1D100", {}, rollOptions); + break; + case "lethality": + roll = new DGLethalityRoll("1D100", {}, rollOptions) + break; + case "damage": { + let diceFormula = item.system.damage; + const { skill } = item.system; + if(this.actor.type === 'agent' && (skill === 'unarmed_combat' || skill === 'melee_weapons')){ diceFormula += this.actor.system.statistics.str.meleeDamageBonusFormula; } - - if(requestedModifyRoll){ - showModifyDamageRollDialogue(this.actor, label, diceFormula); - } - else{ - sendDamageRollToChat(this.actor, label, diceFormula, game.settings.get("core", "rollMode")); - } + roll = new DGDamageRoll(diceFormula, {}, rollOptions) + break; } - else if(isSanityDamageRoll){ - - let lowRollFormula = dataset.roll; - let highRollFormula = dataset.roll2; - - sendSanityDamageToChat(this.actor, label, lowRollFormula, highRollFormula, game.settings.get("core", "rollMode")) - } - else{ - if(requestedModifyRoll){ - showModifyPercentileTestDialogue(this.actor, label, targetVal, isLethalityRoll); - } - else{ - if(isLethalityRoll) - { - sendLethalityTestToChat(this.actor, label, targetVal, game.settings.get("core", "rollMode")); - } - else{ - sendPercentileTestToChat(this.actor, label, targetVal, game.settings.get("core", "rollMode")); - } - } + case "sanity-damage":{ + const { successLoss, failedLoss } = this.actor.system.sanity; + const combinedFormula = `{${successLoss}, ${failedLoss}}`; + roll = new DGSanityDamageRoll(combinedFormula, {}, rollOptions); + break; } + default: + break; } + return this.processRoll(event, roll, rollOptions) + } + + /** + * Show a dialog for the roll and then send to chat. + * Broke this logic out from `_onRoll()` so that other files can call it, + * namely the macro logic. + * + * @param {Event} event The originating click event + * @param {Event} roll The roll to show a dialog for and then send to chat. + * @async + */ + async processRoll(event, roll) { + // Open dialog if user requests it (no dialog for Sanity Damage rolls) + if ((event.shiftKey || event.which === 3) && !(roll instanceof DGSanityDamageRoll)) { + const dialogData = await roll.showDialog(); + if (!dialogData) return; + if (dialogData.newFormula) { + roll = new DGDamageRoll(dialogData.newFormula, {}, roll.options) + } + roll.modifier += dialogData.targetModifier; + roll.options.rollMode = dialogData.rollMode; + } + // Evaluate the roll. + await roll.evaluate({ async: true }); + // Send the roll to chat. + return roll.toChat(); } _resetBreakingPoint(event){ @@ -873,7 +835,7 @@ export class DeltaGreenActorSheet extends ActorSheet { // This is from Foundry. It will get the item data from the event. const dragData = TextEditor.getDragEventData(event); // Make sure that we are dragging an item, otherwise this doesn't make sense. - if (dragData.type = "Item") { + if (dragData.type === "Item") { const item = fromUuidSync(dragData.uuid); await item.delete() } @@ -883,6 +845,6 @@ export class DeltaGreenActorSheet extends ActorSheet { activateEditor(target, editorOptions, initialContent) { editorOptions.content_css = "./systems/deltagreen/css/editor.css"; return super.activateEditor(target, editorOptions, initialContent); - }; + } } diff --git a/module/actor/actor.js b/module/actor/actor.js index 72dacd6..2efe8f9 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -73,6 +73,10 @@ export class DeltaGreenActor extends Actor { system.sanity.max = 99 - system.skills.unnatural.proficiency; + system.wp.max = system.statistics.pow.value; + + system.health.max = Math.ceil((system.statistics.con.value + system.statistics.str.value) / 2); + system.skills.ritual = { label: "Ritual", proficiency: 99 - system.sanity.value, diff --git a/module/deltagreen.js b/module/deltagreen.js index 726f509..4159a63 100644 --- a/module/deltagreen.js +++ b/module/deltagreen.js @@ -1,13 +1,15 @@ +/* globals $ Hooks game CONFIG Actors Items ActorSheet ItemSheet Handlebars Macro ChatMessage ui duplicate */ + // Import Modules import { DeltaGreenActor } from "./actor/actor.js"; import { DeltaGreenActorSheet } from "./actor/actor-sheet.js"; import { DeltaGreenItem } from "./item/item.js"; import { DeltaGreenItemSheet } from "./item/item-sheet.js"; -import { sendPercentileTestToChat, sendLethalityTestToChat, sendDamageRollToChat } from "./roll/roll.js"; +import * as DGRolls from "./roll/roll.js"; import { registerSystemSettings } from "./settings.js" import { preloadHandlebarsTemplates } from "./templates.js"; import { ParseDeltaGreenStatBlock } from "./other/stat-parser-macro.js"; -import { localizeWithFallback } from "./other/utility-functions.js"; +import DGUtils from "./other/utility-functions.js"; Hooks.once('init', async function() { @@ -29,6 +31,9 @@ Hooks.once('init', async function() { decimals: 0 }; + // Register custom dice rolls + Object.values(DGRolls).forEach((cls) => CONFIG.Dice.rolls.push(cls)) + // Register System Settings registerSystemSettings(); @@ -47,7 +52,7 @@ Hooks.once('init', async function() { // Add Handlebars helpers Handlebars.registerHelper('localizeWithFallback', function(value, fallbackValue) { - return localizeWithFallback(value, fallbackValue); + return DGUtils.localizeWithFallback(value, fallbackValue); }); Handlebars.registerHelper('concat', function() { @@ -98,7 +103,7 @@ Hooks.once('init', async function() { }); Handlebars.registerHelper('cite_ahb', function(page) { - return "See page " + str(page) + " of the Agent's Handbook."; + return `See page ${page} of the Agent's Handbook.`; }); Handlebars.registerHelper('formatLethality', function(lethality) { @@ -179,6 +184,9 @@ Hooks.once('init', async function() { if(skill === 'dex'){ label = game.i18n.localize("DG.Attributes.dex"); } + if(skill === 'DG.Skills.custom'){ + label = game.i18n.localize('DG.ItemWindow.Custom'); + } else{ label = game.i18n.localize("DG.Skills." + skill); } @@ -217,7 +225,10 @@ Hooks.once('init', async function() { // generates the character sheet. Handlebars.registerHelper('getFontFamilySystemSettingClass', function() { let setting = game.settings.get("deltagreen", "characterSheetFont"); + + let characterSheetStyle = game.settings.get("deltagreen", "characterSheetStyle"); + /* if(setting === "TypeWriterCondensed"){ return "typewriter-condensed-font"; } @@ -233,9 +244,37 @@ Hooks.once('init', async function() { else if(setting === "SpecialElite"){ return "special-elite-font"; } + else if(setting === "PublicSans"){ + return "public-sans-font"; + } else{ return "martel-font"; } + */ + + /* + if(characterSheetStyle === "cowboy"){ + return "special-elite-font"; + } + else{ + return "public-sans-font"; + } + */ + }); + + Handlebars.registerHelper('getCharacterSheetStyle', function() { + + let characterSheetStyle = game.settings.get("deltagreen", "characterSheetStyle"); + + if(characterSheetStyle === "cowboy"){ + return "cowboy-style"; + } + else if(characterSheetStyle === "outlaw"){ + return "outlaw-style"; + } + else{ + return "program-style"; + } }); @@ -282,13 +321,15 @@ Hooks.once("ready", async function() { Hooks.on("ready", ()=> { - let backgroundImageSetting = game.settings.get("deltagreen", "characterSheetBackgroundImageSetting"); + //let backgroundImageSetting = game.settings.get("deltagreen", "characterSheetBackgroundImageSetting"); + //let characterSheetStyle = game.settings.get("deltagreen", "characterSheetStyle"); - let customCss = ""; + //let customCss = ""; - let customStyle = document.createElement("style"); - customStyle.id = "dg-custom-css"; + //let customStyle = document.createElement("style"); + //customStyle.id = "dg-custom-css"; + /* if(backgroundImageSetting === "OldPaper1"){ customCss += `div.deltagreen.sheet.actor section.window-content{ background: url("systems/deltagreen/assets/img/old_paper.jpg") !important; @@ -299,15 +340,46 @@ Hooks.on("ready", ()=> { background-size: 100% !important; }`; } + */ + + /* + if(characterSheetStyle === "cowboy"){ + customCss += `div.deltagreen.sheet.actor section.window-content{ + background: url("systems/deltagreen/assets/img/old_paper.jpg") !important; + }`; + } + else if(characterSheetStyle === "program"){ + customCss += `div.deltagreen.sheet.actor section.window-content{ + background: url("systems/deltagreen/assets/img/CrumpledPlainPaper10.webp") !important; + }`; + } customStyle.innerHTML = customCss; if(customCss != ""){ - document.querySelector("head").appendChild(customStyle); + //document.querySelector("head").appendChild(customStyle); } + */ }); +Hooks.on("preCreateItem", (item) => { + console.log("preCreateItem"); + console.log(item.img); + console.log(item.type); + + if(item.img === 'icons/svg/item-bag.svg'){ + + if(item.type === 'bond'){ + item.updateSource({ img: 'systems/deltagreen/assets/icons/person-black-bg.svg'}); + } + else{ + item.updateSource({ img: 'systems/deltagreen/assets/icons/swap-bag-black-bg.svg'}); + } + } + +}); + Hooks.on("renderSidebarTab", async (app, html) => { if (app.options.id == "actors") { let button = $("") @@ -326,9 +398,9 @@ Hooks.on('createActor', async function(actor, options, userId){ // use this to trap on if this hook is firing for the same user that triggered the create // can put logic specific to a particular user session below - if (userId != game.user.id) { return; }; + if (userId !== game.user.id) return; - if(actor != null){ + if (actor !== null){ if(actor.type === 'agent'){ // update the default type skill of Art - Painting's labels to try to be localized @@ -348,7 +420,7 @@ Hooks.on('createActor', async function(actor, options, userId){ actor.AddUnarmedAttackItemIfMissing(); } else if(actor.type === 'unnatural'){ - + // Do nothing. } else if(actor.type === 'vehicle'){ actor.AddBaseVehicleItemsIfMissing(); @@ -374,14 +446,16 @@ Hooks.on('createActor', async function(actor, options, userId){ * @returns {Promise} */ async function createDeltaGreenMacro(data, slot) { + // Definitely should not be doing assignments in conditionals but if we fix this, it breaks macro creation. + // eslint-disable-next-line no-cond-assign if (data.type = "Item" && data.itemData.type !== "weapon") return; //if (!("data" in data)) return ui.notifications.warn("You can only create macro buttons for owned Items"); - const item = data.itemData.system; + const item = data.itemData; // Create the macro command let command = '// Uncomment line below to also roll skill check if desired.' - command += '\n' + `//game.deltagreen.rollItemSkillCheckMacro("${data._id}");`; - command += '\n' + `game.deltagreen.rollItemMacro("${data._id}");`; + command += '\n' + `//game.deltagreen.rollItemSkillCheckMacro("${item._id}");`; + command += '\n' + `game.deltagreen.rollItemMacro("${item._id}");`; //let macro = game.macros.entities.find(m => (m.name === data.name) && (m.command === command)); //if (!macro) { @@ -422,7 +496,7 @@ async function rollItemMacro(itemId) { if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemId}`); // Trigger the item roll - let r = await item.roll(); + const r = await item.roll(); return r; } @@ -433,7 +507,7 @@ function rollItemSkillCheckMacro(itemId) { if (speaker.token) actor = game.actors.tokens[speaker.token]; if (!actor) actor = game.actors.get(speaker.actor); - if(!actor) return ui.notifications.warn('Must have an Actor selected first.'); + if (!actor) return ui.notifications.warn('Must have an Actor selected first.'); let item = actor ? actor.items.find(i => i._id === itemId) : null; @@ -442,22 +516,12 @@ function rollItemSkillCheckMacro(itemId) { item = actor ? actor.items.find(i => i.name === itemId) : null; } - if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item '${itemName}'`); + if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item '${item.name}'`); let skillName = item.system.skill.toString(); - let skill = actor.system.skills[skillName]; - let translatedSkillLabel = ""; - - try{ - translatedSkillLabel = game.i18n.localize("DG.Skills." + skillName) - } - catch{ - translatedSkillLabel = skillName; - } - - sendPercentileTestToChat(actor, translatedSkillLabel, skill.proficiency); -} + const roll = new DGRolls.DGPercentileRoll("1D100", {}, { rollType: "weapon", key: skillName, actor, item }); + return actor.sheet.processRoll({}, roll)} function rollSkillMacro(skillName) { const speaker = ChatMessage.getSpeaker(); @@ -471,14 +535,7 @@ function rollSkillMacro(skillName) { if(!skill) return ui.notifications.warn('Bad skill name passed to macro.'); - let translatedSkillLabel = ""; - - try{ - translatedSkillLabel = game.i18n.localize("DG.Skills." + skillName) - } - catch{ - translatedSkillLabel = skillName; - } - - sendPercentileTestToChat(actor, translatedSkillLabel, skill.proficiency); + const roll = new DGRolls.DGPercentileRoll("1D100", {}, { rollType: "skill", key: skillName, actor }); + actor.sheet.processRoll({}, roll) + // sendPercentileTestToChat(actor, translatedSkillLabel, skill.proficiency); } \ No newline at end of file diff --git a/module/item/item-sheet.js b/module/item/item-sheet.js index 277eb31..9cab3bd 100644 --- a/module/item/item-sheet.js +++ b/module/item/item-sheet.js @@ -9,7 +9,7 @@ export class DeltaGreenItemSheet extends ItemSheet { return mergeObject(super.defaultOptions, { classes: ["deltagreen", "sheet", "item"], width: 520, - height: 480, + height: 600, tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "attributes" }] }); } diff --git a/module/item/item.js b/module/item/item.js index 0098f18..19cbed3 100644 --- a/module/item/item.js +++ b/module/item/item.js @@ -1,4 +1,6 @@ -import { sendPercentileTestToChat, sendLethalityTestToChat, sendDamageRollToChat } from "../roll/roll.js" +/* globals Item */ + +import { DGDamageRoll, DGLethalityRoll } from "../roll/roll.js"; /** * Extend the basic Item with some very simple modifications. @@ -24,24 +26,24 @@ export class DeltaGreenItem extends Item { */ async roll() { // Basic template rendering data - const item = this; + const { actor } = this; const actorSystemData = this.actor.system || {}; - if(item.system.isLethal){ - sendLethalityTestToChat(this.actor, item.name, item.system.lethality, game.settings.get("core", "rollMode")) - } - else{ + let roll; + if (item.system.isLethal) { + roll = new DGLethalityRoll("1D100", {}, { rollType: "lethality", actor, item }) + } else { // regular damage roll - let diceFormula = item.system.damage; let skillType = item.system.skill; - if(skillType === 'unarmed_combat' || skillType === 'melee_weapons'){ + if (skillType === 'unarmed_combat' || skillType === 'melee_weapons') { diceFormula += actorSystemData.statistics.str.meleeDamageBonusFormula; } - sendDamageRollToChat(this.actor, item.name, diceFormula); + roll = new DGDamageRoll(diceFormula, {}, { rollType: "damage", actor, item }) } + return actor.sheet.processRoll({}, roll); } } diff --git a/module/other/utility-functions.js b/module/other/utility-functions.js index 9d4d0ea..ed13a60 100644 --- a/module/other/utility-functions.js +++ b/module/other/utility-functions.js @@ -1,24 +1,40 @@ -// Attempt to localize a keyword, and gracefully fallback to something else if the key does not exist. -// This way people using incomplete translations will not as many crashes or bugs when playing against a newly updated system. -export function localizeWithFallback(key, fallback){ - try{ - let translatedValue = game.i18n.localize(key); +/* globals game */ - if(translatedValue != key){ - return translatedValue; - } - else{ - - console.warn(`Untranslated localization key '${key}'.`); - // can't have single quotes in a handlebars string literal input, so sort of hack in an escape character for them. - fallback = fallback.replace("'", "'"); - - return fallback; - } - } - catch(ex){ - console.warn(ex); - return fallback; +export default class DGUtils { + /** + * Attempt to localize a keyword, and gracefully fallback to something else if the key does not exist. + * This way people using incomplete translations will not as many crashes or bugs when playing against a newly updated system. + * + * @param {String} key - localization key + * @param {String} fallback - string to fall back to if translation does not exist for key + * @returns + */ + static localizeWithFallback(key, fallback){ + try { + let translatedValue = game.i18n.localize(key); + if (translatedValue != key) { + return translatedValue; + } else { + console.warn(`Untranslated localization key '${key}'.`); + // can't have single quotes in a handlebars string literal input, so sort of hack in an escape character for them. + fallback = fallback.replace("'", "'"); + return fallback; + } + } catch(ex) { + console.warn(ex); + return fallback; } + } + + /** + * Format a number with a leading plus. + * + * @param {Number} number + * @returns {String} - the stringified number with a leading plus if applicable + * + */ + static formatStringWithLeadingPlus(number){ + return number > 0 ? `+${number}` : number.toString() ; + } } \ No newline at end of file diff --git a/module/roll/roll.js b/module/roll/roll.js index c1dbd65..f0cb1ac 100644 --- a/module/roll/roll.js +++ b/module/roll/roll.js @@ -1,466 +1,589 @@ -import {localizeWithFallback} from "../other/utility-functions.js" +/* globals game Roll ChatMessage AudioHelper renderTemplate Dialog */ -export async function sendPercentileTestToChat(actor, skill, target, rollMode){ - //let roll = new Roll('1D100', actor.data.data).evaluate(false); - let roll = new Roll('1D100', actor.system) +import DGUtils from "../other/utility-functions.js" + +export class DGRoll extends Roll { + /** + * NOTE: This class will rarely be called on its own. It should generally be extended. Look to DGPercentileRoll as an example. + * + * Customize our roll with some useful information, passed in the `options` Object. + * + * @param {string} formula Unused - The string formula to parse (from Foundry) + * @param {Object} data Unused - The data object against which to parse attributes within the formula + * @param {Object} [options] Additional data which is preserved in the database + * @param {Number} [options.rollType] The type of roll (stat, skill, sanity, damage, etc). + * @param {String} [options.key] The key of the skill, stat, etc. to use as a basis for this roll. + * @param {DeltaGreenActor} [options.actor] The actor that this roll originates from. + * @param {DeltaGreenItem} [options.item] Optional - The item from which the roll originates. + */ + constructor(formula, data = {}, options = {}) { + super(formula, data, options); + const { rollType, key, actor, item } = options; + this.type = rollType; + this.key = key; + this.actor = actor; + this.item = item; + this.modifier = 0; + } + + /** + * Simple function that actually creates the message and sends it to chat. + * + * Broke this out so multiple functions can use it. + * + * @returns {Promise} - the created chat message. + */ + async createMessage(content, flavor, rollMode) { + let chatData = { + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + content, + flavor, + type: 5, //CHAT_MESSAGE_TYPES.ROLL, + roll: this, + rollMode + }; - await roll.evaluate({async: true}); + // play the dice rolling sound, like a regular in-chat roll + AudioHelper.play({src: "sounds/dice.wav", volume: 0.8, autoplay: true, loop: false}, true); + return ChatMessage.create(chatData); + } +} - let total = roll.total; - let isCritical = false; - let isSuccess = false; - let html = ''; - let label = ''; - let resultString = ''; - let styleOverride = ''; +export class DGPercentileRoll extends DGRoll { + /** + * Creates D100 rolls, the base die of the system. + * + * This constructor embeds the following info into the roll: + * 1. Target number that the roll needs to beat. + * 2. Localized name for the roll. + * + * Note: In order for all of our custom data to persist, our constructor must use the same parameters as its parent class. + * So, even though percentile rolls will always have a formula of "1d100" and we don't use the `data` object, + * we still have to keep them as parameters. + * + * @param {string} formula Unused - The string formula to parse (from Foundry) - Always "1d100" for percentile rolls. + * @param {Object} data Unused - The data object against which to parse attributes within the formula + * @param {Object} [options] Additional data which is preserved in the database + * @param {Number} [options.rollType] The type of roll (stat, skill, sanity, etc). + * @param {String} [options.key] The key of the skill, stat, etc. to use as a basis for this roll. + * @param {DeltaGreenActor} [options.actor] The actor that this roll originates from. + * @param {DeltaGreenItem} [options.item] Optional - The item from which the roll originates. + */ + constructor(formula = "1D100", data = {}, options) { + super("1D100", {}, options); - if(rollMode == null || rollMode === ""){ - rollMode = game.settings.get("core", "rollMode"); + const skillKeys = Object.keys(this.actor.system.skills); + const typedSkillKeys = Object.keys(this.actor.system.typedSkills); + const statKeys = Object.keys(this.actor.system.statistics); + + switch (this.type) { + case "stat": + this.target = this.actor.system.statistics[this.key].x5; + this.localizedKey = game.i18n.localize(`DG.Attributes.${this.key}`); + break; + case "skill": + // Set up a target number and localized roll based on whether the skill is regular or typed. + if (skillKeys.includes(this.key)) { + this.target = this.actor.system.skills[this.key].proficiency; + this.localizedKey = game.i18n.localize(`DG.Skills.${this.key}`); + } + if (typedSkillKeys.includes(this.key)) { + const skill = this.actor.system.typedSkills[this.key]; + this.target = skill.proficiency; + this.localizedKey = `${skill.group} (${skill.label})`; + } + break; + case "sanity": + this.target = this.actor.system.sanity.value; + this.localizedKey = game.i18n.localize("DG.Attributes.SAN"); + break; + case "weapon": + // Set up target number and localized key depending on whether the weapon uses a custom number, + // a skill, or a stat for its roll. + if (this.key === "custom") { + this.target = this.item.system.customSkillTarget; + this.localizedKey = game.i18n.localize("DG.ItemWindow.Custom"); + } else if (skillKeys.includes(this.key)) { + this.target = this.actor.system.skills[this.key].proficiency; + this.localizedKey = game.i18n.localize(`DG.Skills.${this.key}`); + } else if (statKeys.includes(this.key)) { + this.target = this.actor.system.statistics[this.key].x5; + this.localizedKey = game.i18n.localize(`DG.Attributes.${this.key}`); + } + // Add a the weapon's internal modifier. + this.modifier += this.item.system.skillModifier; + break; + case "luck": + this.target = 50; + this.localizedKey = game.i18n.localize("DG.Luck"); + break; + default: + break; + } } - // if using private san rolls, must hide any SAN role unless user is a GM - let setting = false; - - setting = game.settings.get("deltagreen", "keepSanityPrivate"); - - if(setting === true && (skill === 'SAN' || skill === 'RITUAL') && !game.user.isGM){ - rollMode = 'blindroll'; + /** + * Shows a dialog that can modify the roll. + * + * @returns {Promise} - the results of the dialog. + */ + async showDialog() { + let isSanCheck = false; + const hideSanTarget = !game.user.isGM && game.settings.get("deltagreen", "keepSanityPrivate"); + + if (this.key === 'sanity' || this.key === 'ritual') { + isSanCheck = true; + } + + let backingData = { + data:{ + label: this.localizedKey, + originalTarget: this.target, + targetModifier: 20, + isSanCheck: isSanCheck, + hideTarget: hideSanTarget + }, + }; + + let template = "systems/deltagreen/templates/dialog/modify-percentile-roll.html"; + let html = await renderTemplate(template, backingData); + return new Promise((resolve, reject) => { + new Dialog({ + content: html, + title: DGUtils.localizeWithFallback("DG.ModifySkillRollDialogue.Title", "Modify Roll"), + default: "roll", + buttons: { + roll:{ + label: DGUtils.localizeWithFallback("DG.Roll.Roll", "Roll"), + callback: html => { + try { + let targetModifier = html.find("[name='targetModifier']").val(); // this is text as a heads up + + let rollMode = html.find("[name='rollMode']").val(); + + let plusMinus = html.find("[name='plusOrMinus']").val(); + + if (targetModifier.trim() != "" && !isNaN(targetModifier)){ + targetModifier = Math.abs(parseInt(targetModifier)); + + if(plusMinus === "-"){ + targetModifier = -1 * targetModifier; + } + } + resolve({targetModifier, rollMode}) + } catch(ex) { + reject(console.log(ex)); + } + } + } + } + }).render(true); + }); } - // "Inhuman" stat being rolled, logic is different per page 188 of the Handler's Guide. - // Note - originally implemented by Uriele, but my attempt at merging conficts went poorly, so re-implementing. - // For an inhuman check, the roll succeeds except on a roll of 100 which fails AND fumbles. - // If the roll is a matching digit roll, it is a critical as normal. - // Also, if the roll is below the regular (non-x5) value of the stat, it is a critical. E.g. a CON of 25, a d100 roll of 21 would be a critical. - if(target > 99 && skillIsStatTest(skill)){ + /** + * Prepares data for a chat message and then passes that data + * to a method that actually creates a ChatMessage. + * + * Lays out and styles message based on outcome of the roll. + * + * @returns {Promise} - the created chat message. + */ + async toChat() { + let rollMode = this.options.rollMode || game.settings.get("core", "rollMode"); + + // if using private san rolls, must hide any SAN roll unless user is a GM + const privateSanSetting = game.settings.get("deltagreen", "keepSanityPrivate"); + if (privateSanSetting && (this.key === 'sanity' || this.key === 'ritual') && !game.user.isGM){ + rollMode = 'blindroll'; + } + + let label = `${game.i18n.localize("DG.Roll.Rolling")} ${this.localizedKey} ${game.i18n.localize("DG.Roll.Target")} ${this.target + this.modifier}`; + // "Inhuman" stat being rolled. See function for details. + if (this.isInhuman) { + label = `${game.i18n.localize("DG.Roll.Rolling")} ${this.localizedKey} [${game.i18n.localize("DG.Roll.Inhuman").toUpperCase()}] ${game.i18n.localize("DG.Roll.Target")} ${this.target + this.modifier}`; + } + if (this.modifier) { + label += ` (${DGUtils.formatStringWithLeadingPlus(this.modifier)}%)` + } + + let resultString = '', styleOverride = ''; + if (this.isCritical) { + resultString = `${game.i18n.localize("DG.Roll.Critical")} `; + } + + if (this.isSuccess) { + resultString += `${game.i18n.localize("DG.Roll.Success")}`; + + if (this.isCritical){ + resultString = resultString.toUpperCase() + '!'; + styleOverride="color: green"; + } + } else { + resultString += `${game.i18n.localize("DG.Roll.Failure")}`; + + if(this.isCritical){ + resultString = resultString.toUpperCase() + '!'; + styleOverride="color: red"; + } + } + + let html = ''; + html += `
` + html += `
` + html += `
${resultString}
` + html += `
` + html += `
` + html += `
` + html += `
` + html += ` ` + html += ` ${this.formula}` + html += ` ` + html += ` ` + html += ` ${this.total}` + html += ` ` + html += `
` + html += `
    ` + html += `
  1. ${this.total}
  2. ` + html += `
` + html += `
` + html += `
` + html += `
` + html += `

${this.total}

` + html += `
` + + return this.createMessage(html, label, rollMode); + } - label = `${game.i18n.localize("DG.Roll.Rolling")} ${skill} [${game.i18n.localize("DG.Roll.Inhuman").toUpperCase()}] ${game.i18n.localize("DG.Roll.Target")} ${Math.floor(target / 5)}`; + /** + * "Inhuman" stat being rolled, logic is different per page 188 of the Handler's Guide. + * Note - originally implemented by Uriele, but my attempt at merging conficts went poorly, so re-implementing. + * For an inhuman check, the roll succeeds except on a roll of 100 which fails AND fumbles. + * If the roll is a matching digit roll, it is a critical as normal. + * Also, if the roll is below the regular (non-x5) value of the stat, it is a critical. E.g. a CON of 25, a d100 roll of 21 would be a critical. + * + * @returns {Boolean} + */ + get isInhuman() { + if (this.target + this.modifier > 99 && this.type === "stat") { + return true; + } + return false; + } - if(total === 100){ - // only possible fail criteria, and also a fumble. - isSuccess = false; + /** + * Determines if a roll result is critical. + * If roll has not been evaluated, return null. + * + * @returns {null|Boolean} + */ + get isCritical() { + // If roll isn't evaluated, return null. + if (!this.total) { + return null; + } + let isCritical = false + + // 1, 100, or any matching dice are a crit, i.e. 11, 22, 33...99. + if (this.total === 1 || this.total === 100 || this.total % 11 === 0) { + // really good, or reeaaaally bad isCritical = true; } - else{ - isSuccess = true; - if(total <= (target / 5.0)){ - isCritical = true; - } - else if(skillCheckResultIsCritical(total)){ - isCritical = true; - } - else{ - isCritical = false; - } + + // If inhuman and the roll is below the regular (non-x5) value of the stat, it is a critical. + // E.g. a CON of 25, a d100 roll of 21 would be a critical. + if (this.isInhuman && this.total <= ((this.target + this.modifier) / 5)) { + isCritical = true; } + return isCritical; } - else{ - label = `${game.i18n.localize("DG.Roll.Rolling")} ${skill} ${game.i18n.localize("DG.Roll.Target")} ${target}`; - - isCritical = skillCheckResultIsCritical(total); - - if(total <= target){ - isSuccess = true; + /** + * Determines if a roll succeeded. + * If roll has not been evaluated, return null. + * + * @returns {null|Boolean} + */ + get isSuccess() { + // If roll isn't evaluated, return null. + if (!this.total) { + return null; } + // A roll of 100 always (critically) fails, even for inhuman rolls. + if (this.total === 100) return false; + return this.total <= (this.target + this.modifier); + } +} + +export class DGLethalityRoll extends DGPercentileRoll { + /** + * See constructor for DGPercentileRoll. This theoretically could be done in the parent class' + * constructor, but since Lethality rolls needs its own class for custom methods anyway, + * we will set the target and localized key here. + * + * @param {String} formula + * @param {Object} data + * @param {Object} options + */ + constructor(formula, data, options) { + super(formula, data, options); + this.target = options.item.system.lethality; + this.localizedKey = game.i18n.localize("DG.ItemWindow.Weapons.Lethality"); } - if(isCritical){ - resultString = `${game.i18n.localize("DG.Roll.Critical")} `; - } - - if(isSuccess){ - resultString += `${game.i18n.localize("DG.Roll.Success")}`; - - if(isCritical){ - resultString = resultString.toUpperCase() + '!'; - styleOverride="color: green"; - } - } - else{ - resultString += `${game.i18n.localize("DG.Roll.Failure")}`; - - if(isCritical){ - resultString = resultString.toUpperCase() + '!'; + /** + * Prepares data for a chat message and then passes that data + * to a method that actually creates a ChatMessage. + * + * Lays out and styles message based on outcome of the roll. + * + * Overrides `DGPercentileRoll.toChat()` + * + * @returns {Promise} - the created chat message. + * @override + */ + async toChat() { + const rollMode = this.options.rollMode || game.settings.get("core", "rollMode"); + let resultString = ''; + let styleOverride = ''; + if(this.total <= this.target){ + resultString = `${game.i18n.localize("DG.Roll.Lethal").toUpperCase()}`; styleOverride="color: red"; } - } - - html = `
` - html += `
` - html += `
${resultString}
` - html += `
` - html += `
` - html += `
` - html += `

` - html += ` ${roll.formula}` - html += ` ${roll.total}` - html += `

` - html += `
    ` - html += `
  1. ${roll.total}
  2. ` - html += `
` - html += `
` - html += `
` - html += `
` - html += `

${roll.total}

` - html += `
` - - let chatData = { - speaker: ChatMessage.getSpeaker({actor: actor}), - content: html, - flavor: label, - type: 5, //CHAT_MESSAGE_TYPES.ROLL, - roll: roll, - rollMode: rollMode - }; - - // play the dice rolling sound, like a regular in-chat roll - AudioHelper.play({src: "sounds/dice.wav", volume: 0.8, autoplay: true, loop: false}, true); - - ChatMessage.create(chatData, {}); -} - -export function skillCheckResultIsCritical(rollResult){ - let isCritical = false; - - if(rollResult === 1 || rollResult === 100){ - // really good, or reeaaaally bad - isCritical = true; - } - else if(rollResult % 11 === 0){ - // any matching dice are a crit, i.e. 11, 22, 33...99. - isCritical = true; - } - - return isCritical; -} - -export async function sendLethalityTestToChat(actor, weaponName, target, rollMode){ - //let roll = new Roll('1D100', actor.data.data).evaluate(false); - - let roll = new Roll('1D100', actor.system); - - await roll.evaluate({async: true}); - - let isCritical = false; - let skillCheckTotal = roll.total; - - if(rollMode == null || rollMode === ""){ - rollMode = game.settings.get("core", "rollMode"); - } - - // page 57 of agent's handbook - // For a failed lethality check, the damage on the tens die is a 10 for a roll of '0'. E.g. '30' + '0' = 13 damage. - // Lethality rolls do not fumble or critically succeed, but the attack roll can. - // In this situation, the lethality threshold doubles (e.g. 20% would become 40%), - // and if the lethality roll fails, the damage is doubled per normal. However right now - // the attack roll and damage roll are completely separate... Perhaps in the future, use a - // button in the skill check roll to allow doubling this logic? - - // try to determine what the d100 result would be as if it was two d10's being rolled - let damageDie1 = Math.floor(skillCheckTotal / 10); - let damageDie2 = skillCheckTotal - (damageDie1 * 10); - if(damageDie2 === 0){ - // if the result is evenly divisible by 10 (e.g. 30, 40, 50...) - // then the percentile die result was actually one lower than the calculation above would show. - // For example, a '70' is actually a 60 + 10, so the damage die should be 6 + 10 = 16, not 7 + 0 = 7. - damageDie1 -= 1; - damageDie2 = 10; - } - - let damageDie1Label = damageDie1.toString(); - let damageDie2Label = damageDie2.toString(); - - if(damageDie1 === 0){ - // a roll of 00 on the tens part of the d100 counts as a ten if it becomes a damage die - // Will leave the labeling as a '0' though, to be consistent with a regular d10 roll - damageDie1 = 10; - } - - if(damageDie2 === 10){ - // keep the labelling consistent, show a '0' for a ten - damageDie2Label = '0'; - } - - let nonlethalDamage = damageDie1 + damageDie2; - - let isLethal = false; - let html = ''; - let label = `${game.i18n.localize("DG.Roll.Rolling")} ${game.i18n.localize("DG.Roll.Lethality").toUpperCase()} ${game.i18n.localize("DG.Roll.For")} ${weaponName.toUpperCase()} ${game.i18n.localize("DG.Roll.Target")} ${target}`; - let resultString = ''; - let styleOverride = ''; - - if(skillCheckTotal <= target){ - isLethal = true; - resultString = `${game.i18n.localize("DG.Roll.Lethal").toUpperCase()}`; - styleOverride="color: red"; - } - else{ - resultString = `${game.i18n.localize("DG.Roll.Failure")}`; - } - - html = `
`; - html += `
`; - html += `
${resultString}
`; - html += `
`; - html += `
`; - html += `
`; - html += `

`; - html += ` d100 ${game.i18n.localize("DG.Roll.Or").toUpperCase()} d10 + d10`; - html += ` ${roll.total}`; - html += `

`; - html += `
    `; - html += `
  1. ${damageDie1Label}
  2. `; - html += `
  3. ${damageDie2Label}
  4. `; - html += `
`; - html += `
`; - html += `
`; - html += `
`; - html += `

${roll.total} (${nonlethalDamage} ${game.i18n.localize("DG.Roll.Damage")})

`; - html += `
`; - - let chatData = { - speaker: ChatMessage.getSpeaker({actor: actor}), - content: html, - flavor: label, - type: 5, //CHAT_MESSAGE_TYPES.ROLL, - roll: roll, - rollMode: rollMode - }; - - // play the dice rolling sound, like a regular in-chat roll - AudioHelper.play({src: "sounds/dice.wav", volume: 0.8, autoplay: true, loop: false}, true); - - ChatMessage.create(chatData, {}); -} - -export async function sendDamageRollToChat(actor, label, diceFormula, rollMode){ - - if(rollMode == null || rollMode === ""){ - rollMode = game.settings.get("core", "rollMode"); - } - - //diceFormula += actor.data.data.statistics.str.meleeDamageBonusFormula; - - let roll = new Roll(diceFormula, actor.system); - - await roll.evaluate({async: true}); - - try{ - label = `${game.i18n.localize("DG.Roll.Rolling")} ${game.i18n.localize("DG.Roll.Damage").toUpperCase()} ${game.i18n.localize("DG.Roll.For")} ${label.toUpperCase()}`; - } - catch{ - label = `Rolling DAMAGE for ${label.toUpperCase()}`; - } - - let chatData = { - speaker: ChatMessage.getSpeaker({actor: actor}), - content: roll.total, - flavor: label, - type: 5, //CHAT_MESSAGE_TYPES.ROLL, - roll: roll, - rollMode: rollMode - }; - - // play the dice rolling sound, like a regular in-chat roll - AudioHelper.play({src: "sounds/dice.wav", volume: 0.8, autoplay: true, loop: false}, true); - - ChatMessage.create(chatData, {}); - -} - -export async function sendSanityDamageToChat(actor, label, lowFormula, highFormula, rollMode){ - - if(rollMode == null || rollMode === ""){ - rollMode = game.settings.get("core", "rollMode"); - } - - //console.log(`Low: ${lowFormula} High: ${highFormula}`) - - // try to do both rolls as one, so that when sent to chat, Dice So Nice will roll both - let combinedRoll = new Roll('{'+lowFormula + ',' + highFormula + '}', actor.system); - - await combinedRoll.evaluate({async: true}); - - let flavor = `

Rolling ${localizeWithFallback('DG.Generic.SanDamage', 'SAN DAMAGE')} (${lowFormula}/${highFormula})

`; - - let lowResult = ""; - let highResult = ""; - console.log(combinedRoll); - - lowResult = combinedRoll.terms[0].results[0].result; - highResult = combinedRoll.terms[0].results[1].result; - - let html = `

${lowResult} / ${highResult}

`; - - let chatData = { - speaker: ChatMessage.getSpeaker({actor: actor}), - content: html, - flavor: flavor, - type: 5, //CHAT_MESSAGE_TYPES.ROLL, - roll: combinedRoll, - rollMode: rollMode - }; - - // play the dice rolling sound, like a regular in-chat roll - AudioHelper.play({src: "sounds/dice.wav", volume: 0.8, autoplay: true, loop: false}, true); - - ChatMessage.create(chatData, {}); -} - -export async function showModifyPercentileTestDialogue(actor, label, originalTarget, isLethalityTest){ - - let template = "systems/deltagreen/templates/dialog/modify-percentile-roll.html"; - - let isSanCheck = false; - let hideSanTarget = false; - - if(label === 'SAN' || label === 'RITUAL'){ - - isSanCheck = true; - - hideSanTarget = game.settings.get("deltagreen", "keepSanityPrivate"); - - if(game.user.isGM){ - hideSanTarget = false; - } - } - - let backingData = { - data:{ - label: label, - originalTarget: originalTarget, - targetModifier: 20, - isSanCheck: isSanCheck, - hideTarget: hideSanTarget - }, - }; - - let html = await renderTemplate(template, backingData); - - new Dialog({ - content: html, - title: localizeWithFallback("DG.ModifySkillRollDialogue.Title", "Modify Roll"), - default: "roll", - buttons: { - roll:{ - label: localizeWithFallback("DG.Roll.Roll", "Roll"), - - callback: html => { - try{ - let targetModifier = html.find("[name='targetModifier']").val(); // this is text as a heads up - - let rollMode = html.find("[name='targetRollMode']").val(); - - let plusMinus = html.find("[name='plusOrMinus']").val(); - - let newTarget = parseInt(originalTarget); // this should be an int, but technically the incoming value is text, so parse it just to be safe - - if(targetModifier.trim() != "" && !isNaN(targetModifier)){ - let numericTargetModifier = Math.abs(parseInt(targetModifier)); - - if(plusMinus === "-"){ - numericTargetModifier = -1 * numericTargetModifier; - } - - newTarget += numericTargetModifier; - } - - if(isLethalityTest){ - sendLethalityTestToChat(actor, label, newTarget, rollMode); - } - else{ - sendPercentileTestToChat(actor, label, newTarget, rollMode); - } - } - catch(ex){ - console.log(ex); - } - } - } - } - }).render(true); -} - -export async function showModifyDamageRollDialogue(actor, label, originalFormula){ - - let template = "systems/deltagreen/templates/dialog/modify-damage-roll.html"; - let backingData = { - data:{ - label: label, - originalFormula: originalFormula, - outerModifier: "2 * ", - innerModifier: "+ 0" - }, - }; - - let html = await renderTemplate(template, backingData); - - new Dialog({ - content: html, - title: game.i18n.localize("DG.ModifySkillRollDialogue.Title"), - default: "roll", - buttons: { - roll:{ - label: game.i18n.translations.DG.Roll.Roll, - - callback: html => { - try{ - let outerModifier = html.find("[name='outerModifier']").val(); // this is text as a heads up - let innerModifier = html.find("[name='innerModifier']").val(); // this is text as a heads up - let modifiedBaseRoll = html.find("[name='originalFormula']").val(); // this is text as a heads up - let rollMode = html.find("[name='targetRollMode']").val(); - - if(innerModifier.replace(" ", "") === "+0"){ - innerModifier = ""; - } - - let newRoll = ""; - if(outerModifier.trim() != ""){ - newRoll += outerModifier + "(" + modifiedBaseRoll + innerModifier.trim() + ")"; - } - else{ - newRoll += modifiedBaseRoll + innerModifier.trim(); - } - - sendDamageRollToChat(actor, label, newRoll, rollMode); - } - catch(ex){ - console.log(ex); - } - } - } - } - }).render(true); -} - -export function skillIsStatTest(skillName){ - try{ - if(skillName.toUpperCase() === game.i18n.localize("DG.Attributes.str").toUpperCase()){ - return true; - } - if(skillName.toUpperCase() === game.i18n.localize("DG.Attributes.con").toUpperCase()){ - return true; - } - if(skillName.toUpperCase() === game.i18n.localize("DG.Attributes.dex").toUpperCase()){ - return true; - } - if(skillName.toUpperCase() === game.i18n.localize("DG.Attributes.int").toUpperCase()){ - return true; - } - if(skillName.toUpperCase() === game.i18n.localize("DG.Attributes.pow").toUpperCase()){ - return true; - } - if(skillName.toUpperCase() === game.i18n.localize("DG.Attributes.cha").toUpperCase()){ - return true; - } else{ - return false; + resultString = `${game.i18n.localize("DG.Roll.Failure")}`; } + + const { nonLethalDamage } = this; + let label = `${game.i18n.localize("DG.Roll.Rolling")} ${game.i18n.localize("DG.Roll.Lethality").toUpperCase()} ${game.i18n.localize("DG.Roll.For")} ${this.item.name.toUpperCase()} ${game.i18n.localize("DG.Roll.Target")} ${this.target + this.modifier}`; + if (this.modifier) { + label += ` (${DGUtils.formatStringWithLeadingPlus(this.modifier)}%)` + } + + let html = ''; + html += `
`; + html += `
`; + html += `
${resultString}
`; + html += `
`; + html += `
`; + html += `
`; + html += `
`; + html += ` `; + html += ` d100`; + html += ` `; + html += ` `; + html += ` ${this.total}`; + html += ` `; + html += `
`; + html += `
    `; + html += `
  1. ${this.total}`; + html += `
`; + html += `
`; + html += `
`; + html += ` `; + html += ` 2d10 (d10 + d10)`; + html += ` `; + html += ` `; + html += ` ${nonLethalDamage.total}`; + html += ` `; + html += `
`; + html += `
    `; + html += `
  1. ${nonLethalDamage.die1}
  2. `; + html += `
  3. ${nonLethalDamage.die2}
  4. `; + html += `
`; + html += `
`; + html += `
`; + html += `
`; + html += `

${this.total} (${nonLethalDamage.total} ${game.i18n.localize("DG.Roll.Damage")})

`; + html += `
`; + + return this.createMessage(html, label, rollMode); } - catch(ex){ - return false; + + /** + * Calculates the damage for when a lethality roll fails. + * If roll has not been evaluated, return null. + * + * See full rules on page 57 of agent's handbook. + * + * Note, this getter does not actually care if the roll has failed. + * + * @returns {null|Object} - return data about the non-lethal damage. + */ + get nonLethalDamage() { + if (!this.total) { + return null; + } + + // Try to determine what the d100 result would be as if it was two d10's being rolled. + const totalString = this.total.toString(); + const digits = totalString.length; + let die1, die2; + switch (digits) { + case 1: + // If one digit in the result, one die is a 10, and the other is the result. + [die1, die2] = [10, this.total]; + break; + case 2: + // If two digits in the result, each die is the value of one of the digits. If one of those digits is 0, make it 10. + [die1, die2] = totalString.split("").map((digit) => parseInt(digit)).map((digit) => digit || 10); + break; + case 3: + // If three digits in the result (aka result === 100), each die is a 10. + [die1, die2] = [10, 10]; + break; + default: + break; + } + + const total = die1 + die2; + return { die1, die2, total } + } +} + +export class DGDamageRoll extends DGRoll { + /** + * Prepares data for a chat message and then passes that data + * to a method that actually creates a ChatMessage. + * + * @returns {Promise} - the created chat message. + * @override + */ + async toChat() { + const rollMode = this.options.rollMode || game.settings.get("core", "rollMode"); + let label = this.formula; + try{ + label = `${this.item.name}: ${game.i18n.localize("DG.Roll.Rolling")} ${game.i18n.localize("DG.Roll.Damage").toUpperCase()} ${game.i18n.localize("DG.Roll.For")} ${label.toUpperCase()}`; + } + catch{ + label = `Rolling DAMAGE for ${label.toUpperCase()}`; + } + return this.createMessage(this.total, label, rollMode); + } + + async showDialog() { + let template = "systems/deltagreen/templates/dialog/modify-damage-roll.html"; + let backingData = { + data:{ + label: this.item?.name, + originalFormula: this.formula, + outerModifier: "2 * ", + innerModifier: "+ 0" + }, + }; + + let html = await renderTemplate(template, backingData); + return new Promise((resolve, reject) => { + new Dialog({ + content: html, + title: game.i18n.localize("DG.ModifySkillRollDialogue.Title"), + default: "roll", + buttons: { + roll:{ + label: game.i18n.translations.DG.Roll.Roll, + + callback: html => { + try{ + let outerModifier = html.find("[name='outerModifier']").val(); // this is text as a heads up + let innerModifier = html.find("[name='innerModifier']").val(); // this is text as a heads up + let modifiedBaseRoll = html.find("[name='originalFormula']").val(); // this is text as a heads up + let rollMode = html.find("[name='targetRollMode']").val(); + + if(innerModifier.replace(" ", "") === "+0"){ + innerModifier = ""; + } + + let newFormula = ""; + if(outerModifier.trim() != ""){ + newFormula += outerModifier + "(" + modifiedBaseRoll + innerModifier.trim() + ")"; + } + else{ + newFormula += modifiedBaseRoll + innerModifier.trim(); + } + + resolve({newFormula, rollMode}) + } + catch(ex){ + reject(console.log(ex)); + } + } + } + } + }).render(true); + }); + } +} + +export class DGSanityDamageRoll extends DGRoll { + /** + * Prepares data for a chat message and then passes that data + * to a method that actually creates a ChatMessage. + * + * @returns {Promise} - the created chat message. + * @override + */ + async toChat() { + const rollMode = this.options.rollMode || game.settings.get("core", "rollMode"); + + const [lowDie, highDie] = this.terms[0].terms.map((formula) => { + return Roll.parse(formula)[0] || { faces: parseInt(formula), number: 1 }; + }); + + const [lowResult, highResult] = this.damageResults; + + const flavor = `Rolling ${DGUtils.localizeWithFallback('DG.Generic.SanDamage', 'SAN DAMAGE')} For ${lowDie.formula} / ${highDie.formula}`; + + let html = ''; + html += `
`; + html += `
`; + html += `
${lowDie.formula} / ${highDie.formula}
`; + html += `
`; + html += `
`; + html += `
`; + html += `
`; + html += ` `; + html += ` ${lowDie.formula}`; + html += ` `; + html += ` `; + html += ` ${lowResult}`; + html += ` `; + html += `
`; + html += `
    `; + html += `
  1. ${lowResult}`; + html += `
`; + html += `
`; + html += `
`; + html += ` `; + html += ` ${highDie.formula}`; + html += ` `; + html += ` `; + html += ` ${highResult}`; + html += ` `; + html += `
`; + html += `
    `; + html += `
  1. ${highResult}
  2. `; + html += `
`; + html += `
`; + html += `
`; + html += `
`; + html += `

${lowResult} / ${highResult}

` + html += `
`; + return this.createMessage(html, flavor, rollMode); + } + + /** + * Returns the two results for a sanity damage roll. + * + * Returns null if the roll has not been evaluated. + * + * @returns {null|Array} - Array of result numbers + */ + get damageResults() { + if (!this.total) return null; + + const [lowResult, highResult] = this.terms[0].results; + return [lowResult?.result, highResult?.result] } } \ No newline at end of file diff --git a/module/settings.js b/module/settings.js index 7bc36f5..0a97cac 100644 --- a/module/settings.js +++ b/module/settings.js @@ -1,74 +1,103 @@ -export const registerSystemSettings = function() { - game.settings.register('deltagreen', 'characterSheetFont', { - name: "World Font Choice", - hint: "Choose font style for use throughout this world.", - scope: "world", // This specifies a world-stored setting - config: true, // This specifies that the setting appears in the configuration view - type: String, - choices: { // If choices are defined, the resulting setting will be a select menu - "SpecialElite": "Special Elite (Classic Typewriters Font)", - "Martel": "Martel (Clean Modern Font)", - "Signika": "Signika (Foundry Default Font)", - "TypeWriterCondensed": "Condensed Typewriter (Modern, Small Typewriter Font)" - //"atwriter": "Another Typewriter (Alternate Old-style Typewriter Font)" - }, - default: "SpecialElite", // The default value for the setting - onChange: value => { // A callback function which triggers when the setting is changed - //console.log(value) - } - }); +export const registerSystemSettings = function () { + game.settings.register("deltagreen", "characterSheetStyle", { + name: "Character Sheet Style", + hint: "Choose how actor sheets should be styled. 'Program' is a more modern government style, where 'Cowboy/Outlaw' is an older, grittier looking typewriter style.", + scope: "world", // This specifies a world-stored setting + config: true, // This specifies that the setting appears in the configuration view + requiresReload: true, + type: String, + choices: { + // If choices are defined, the resulting setting will be a select menu + cowboy: "The Cowboys", + outlaw: "The Outlaws", + program: "The Program", + }, + default: "program", // The default value for the setting + onChange: (value) => { + // A callback function which triggers when the setting is changed + //console.log(value) + }, + }); - game.settings.register('deltagreen', 'characterSheetBackgroundImageSetting', { - name: "World Sheet Background Image", - hint: "Choose background image for use throughout this world. (Refresh page to see change.)", - scope: "world", // This specifies a world-stored setting - config: true, // This specifies that the setting appears in the configuration view - type: String, - choices: { // If choices are defined, the resulting setting will be a select menu - "OldPaper1": "Old Dirty Paper (Good with Special Elite Font)", - "IvoryPaper": "Ivory White Paper (Good with Martel Font)", - "DefaultParchment": "Default Parchment (Good with Signika Font)" - }, - default: "OldPaper1", // The default value for the setting - onChange: value => { // A callback function which triggers when the setting is changed - //console.log(value) - } - }); + game.settings.register("deltagreen", "keepSanityPrivate", { + name: "Keep Sanity Private", + hint: "Hide sanity from players on both character sheet and rolls.", + scope: "world", + config: true, + type: Boolean, + default: false, + }); - game.settings.register('deltagreen', 'keepSanityPrivate', { - name: "Keep Sanity Private", - hint: "Hide sanity from players on both character sheet and rolls.", - scope: "world", - config: true, - type: Boolean, - default: false - }); + game.settings.register("deltagreen", "skillImprovementFormula", { + name: "Default Skill Improvement Roll", + hint: " Default 1d4. There have been multiple errata on this, choose which fits best for the pace of your game.", + scope: "world", // This specifies a world-stored setting + config: true, // This specifies that the setting appears in the configuration view + type: String, + choices: { + // If choices are defined, the resulting setting will be a select menu + 1: "Flat +1", + "1d3": "+1D3%", + "1d4": "+1D4%", + "1d4-1": "+1D4-1%", + }, + default: "1d4", // The default value for the setting, per the most recent errata. + onChange: (value) => { + // A callback function which triggers when the setting is changed + //console.log(value) + }, + }); - game.settings.register('deltagreen', 'skillImprovementFormula', { - name: "Default Skill Improvement Roll", - hint: " Default 1d4. There have been multiple errata on this, choose which fits best for the pace of your game.", - scope: "world", // This specifies a world-stored setting - config: true, // This specifies that the setting appears in the configuration view - type: String, - choices: { // If choices are defined, the resulting setting will be a select menu - "1": "Flat +1", - "1d3": "+1D3%", - "1d4": "+1D4%", - "1d4-1": "+1D4-1%" - }, - default: "1d4", // The default value for the setting, per the most recent errata. - onChange: value => { // A callback function which triggers when the setting is changed - //console.log(value) - } - }); - game.settings.register('deltagreen', 'showImpossibleLandscapesContent', { - name: "Show Impossible Landscapes Content", - hint: "Show Impossible Landscapes-specific fields from character sheets.", - scope: "world", - config: true, - type: Boolean, - default: true - }); + // These two settings are obsolete now + game.settings.register("deltagreen", "showImpossibleLandscapesContent", { + name: "Show Impossible Landscapes Content", + hint: "Show Impossible Landscapes-specific fields from character sheets.", + scope: "world", + config: true, + type: Boolean, + default: true, + }); -} \ No newline at end of file + game.settings.register("deltagreen", "characterSheetFont", { + name: "World Font Choice", + hint: "Choose font style for use throughout this world.", + scope: "world", // This specifies a world-stored setting + config: false, // This specifies that the setting appears in the configuration view + type: String, + choices: { + // If choices are defined, the resulting setting will be a select menu + SpecialElite: "Special Elite (Classic Typewriters Font)", + Martel: "Martel (Clean Modern Font)", + Signika: "Signika (Foundry Default Font)", + TypeWriterCondensed: + "Condensed Typewriter (Modern, Small Typewriter Font)", + PublicSans: "Public Sans (US Government-style sans serif font)", + //"atwriter": "Another Typewriter (Alternate Old-style Typewriter Font)" + }, + default: "SpecialElite", // The default value for the setting + onChange: (value) => { + // A callback function which triggers when the setting is changed + //console.log(value) + }, + }); + + game.settings.register("deltagreen", "characterSheetBackgroundImageSetting", { + name: "World Sheet Background Image", + hint: "Choose background image for use throughout this world. (Refresh page to see change.)", + scope: "world", // This specifies a world-stored setting + config: false, // This specifies that the setting appears in the configuration view + type: String, + choices: { + // If choices are defined, the resulting setting will be a select menu + OldPaper1: "Old Dirty Paper (Good with Special Elite Font)", + IvoryPaper: "Ivory White Paper (Good with Martel Font)", + DefaultParchment: "Default Parchment (Good with Signika Font)", + }, + default: "OldPaper1", // The default value for the setting + onChange: (value) => { + // A callback function which triggers when the setting is changed + //console.log(value) + }, + }); +}; diff --git a/template.json b/template.json index 9d14d92..a5af475 100644 --- a/template.json +++ b/template.json @@ -537,10 +537,12 @@ "base" ], "skill": "", + "skillModifier": 0, + "customSkillTarget": 50, "range": "10M", "damage": "1D8", "armorPiercing": 0, - "lethality": 0, + "lethality": 10, "isLethal": false, "killRadius": "N/A", "ammo": "", diff --git a/templates/actor/actor-sheet.html b/templates/actor/actor-sheet.html index b978309..f96787e 100644 --- a/templates/actor/actor-sheet.html +++ b/templates/actor/actor-sheet.html @@ -1,10 +1,10 @@ -
+ {{!-- Sheet Header --}}
- + {{!--

DELTA GREEN

--}} {{!-- Name, Profession, Portrait --}}
@@ -49,7 +49,7 @@ {{!-- Sanity --}}
@@ -91,13 +91,17 @@
{{!-- Skills Tab --}} -
+
+
+ {{localize "DG.Sheet.BlockHeaders.SkillsAndTraining"}} +
+
{{#each actor.system.skills as |skill key|}} -
-
{{#each actor.system.typedSkills as |skill key|}} -
-