Compare commits

...

156 Commits

Author SHA1 Message Date
Stefan Zermatten
54f055d3ef Merge branch 'bugfix-model' 2017-07-13 16:03:23 +02:00
Stefan Zermatten
2bacb296ba Fixed new effects dialog animation
makes some progress on #72
2017-07-13 16:01:43 +02:00
Stefan Zermatten
8b061f7aa9 Improved look and feel of effect editing 2017-07-13 15:53:03 +02:00
Stefan Zermatten
6a84c83644 Made passive scores show up for all skills with bonuses to passive score
closes #56
2017-07-13 13:42:04 +02:00
Stefan Zermatten
3227cd0934 Add character names to the end of their URL's
Closes #21, makes links to characters human readable
2017-07-13 13:14:04 +02:00
Stefan Zermatten
089feae26f Spell slots are now available in calculations
closes #11
2017-07-13 10:56:42 +02:00
Stefan Zermatten
99c72d1e10 Made names optional for all collections
closes #92
2017-07-13 10:39:43 +02:00
Stefan Zermatten
1e67afbe6f Made DC available in spell descriptions as a variable
closes #101
2017-07-13 10:30:16 +02:00
Stefan Zermatten
1cfec1ca45 Allowed carryMultiplier to be a decimal
closes #100
2017-07-13 09:50:20 +02:00
Stefan Zermatten
09d1ac9ba3 Merge branch 'bugfix-improved-initiative' 2017-07-11 08:53:46 +02:00
Stefan Zermatten
834b9cf384 Added passive perception to improved initiative export
closes #97
2017-07-11 08:52:47 +02:00
Stefan Zermatten
37291b347a Fixed vulnerabilities, resistances, immunities export
closes #96
2017-07-11 08:42:21 +02:00
Stefan Zermatten
efdfbeb59e Moved export to lib 2017-07-11 08:12:52 +02:00
Thaum Rystra
a165f9b253 Merge branch 'feature-improved-initiative-export'
closes #95
2017-07-10 21:02:10 +02:00
Thaum Rystra
4a6fca98bc Cleaned up export dialog, fixed copying 2017-07-10 20:59:00 +02:00
Stefan Zermatten
35464128a0 Added basic export for improved initiative 2017-07-10 16:26:38 +02:00
Stefan Zermatten
398f8a8a2a Merge branch 'bugfix-style' 2017-06-28 09:33:20 +02:00
Stefan Zermatten
812a1784b2 Improved display of home page on smaller devices 2017-06-28 09:27:24 +02:00
Stefan Zermatten
8fa9cd0148 Prevented spell titles from overflowing their item in spell lists
closes #83
2017-06-28 09:26:52 +02:00
Stefan Zermatten
0e0662cc9a Added slightly specific rule to let headings wrap
closes #90
2017-06-28 09:26:13 +02:00
Stefan Zermatten
ad4e3f5b20 Stopped inventory items being separated from their containers
hopefully fixes #91
2017-06-28 09:25:25 +02:00
Stefan Zermatten
4cd058e1fe Set max of slider before value
Prevents unnecessary capping of temp HP to 100
Fixes #89
2017-06-08 09:48:51 +02:00
Stefan Zermatten
8f30cee4d3 Hotfixed column layout with translateZ(0) hack 2017-06-07 09:46:38 +02:00
Stefan Zermatten
d7f7eb2e6a Fixed some spells in the library 2017-06-07 09:35:59 +02:00
Stefan Zermatten
a59cf1162f added return value to remove old soft removed docs 2017-05-29 11:58:19 +02:00
Stefan Zermatten
7bc80da99e Moved cron setup to startup 2017-05-29 11:35:30 +02:00
Stefan Zermatten
2ddc520bb6 Added cron job to remove old documents 2017-05-29 11:16:46 +02:00
Stefan Zermatten
d92bb0f4d4 Replace tap event with click event for mini-FABs
fixes #68, probably
2017-05-16 10:36:40 +02:00
Stefan Zermatten
abcfe57add Fixed results not being used when popping dialogs
Fixes #66
2017-05-11 11:08:00 +02:00
Stefan Zermatten
cdae75beef Merge branch 'bugfix-iamastick' 2017-05-10 16:40:05 +02:00
Stefan Zermatten
e7bcc2224c Replaced effects in-line editing with edit dialogs
Closes #41
2017-05-10 16:39:36 +02:00
Stefan Zermatten
b591c66dd5 Back button now closes dialogs
closes #9
2017-05-10 10:38:28 +02:00
Stefan Zermatten
402bc0e5ed Fixed Readme 2017-05-10 10:37:30 +02:00
Stefan Zermatten
ac772531ee Fixed SRD spells not getting a random ID when inserted
fixes #64
fixes #65
2017-05-08 11:21:30 +02:00
Stefan Zermatten
9a3edb07e4 Merge branch 'feature-spell-library' 2017-05-05 11:12:59 +02:00
Stefan Zermatten
fea45d2398 Fixed spell data source 2017-05-05 11:11:56 +02:00
Stefan Zermatten
18aa1b2040 Fixed publications of spell library 2017-05-05 11:11:43 +02:00
Stefan Zermatten
9afe38d043 removed stray log 2017-05-05 10:45:08 +02:00
Stefan Zermatten
5ddbecf97e Added spells library 2017-05-05 10:45:07 +02:00
Stefan Zermatten
7b573ca86e Fixed some dialogs getting stuck on close animation, poisoning future dialogs
closes #63
2017-05-05 10:45:05 +02:00
Stefan Zermatten
a9256fed05 Fixed some dialogs getting stuck on close animation, poisoning future dialogs
closes #63
2017-05-05 10:44:16 +02:00
Stefan Zermatten
8ec10e7a6a Added SRD spell data 2017-05-04 16:19:21 +02:00
Stefan Zermatten
5ee1f0a169 Merge branch 'feature-library' 2017-05-04 14:23:19 +02:00
Stefan Zermatten
b0652b0102 Added SRD tools
#47
2017-05-04 14:22:55 +02:00
Stefan Zermatten
47b68c777d Improved item library dialog UI 2017-05-04 14:19:47 +02:00
Stefan Zermatten
16feaaa6ca Fixed jshint breaking on es6 2017-05-04 14:19:11 +02:00
Stefan Zermatten
8a38c167c0 Removed padding from drawer 2017-05-04 09:41:09 +02:00
Stefan Zermatten
1835d968f3 Merge branch 'bugfix-smallbugs' 2017-04-25 17:16:05 +02:00
Stefan Zermatten
2a9d170647 Added spacing between values and units in the inventory
closes #44 closes #60
2017-04-25 17:12:34 +02:00
Stefan Zermatten
b4649d8c87 Fixed spacing between "weight carried" and "x lbs" 2017-04-25 16:59:54 +02:00
Stefan Zermatten
e50bcca7ce Made feature uses a text field again
Fixes #59
2017-04-25 16:23:16 +02:00
Stefan Zermatten
492b5d4bf1 Removed useless library link 2017-04-24 15:08:46 +02:00
Stefan Zermatten
6add127db4 Merge branch 'feature-library' 2017-04-24 14:57:34 +02:00
Stefan Zermatten
d2762ae4ee Added patreon and github links to drawer 2017-04-24 14:56:40 +02:00
Stefan Zermatten
7996fa4370 Finished implementing basic item library 2017-04-24 14:15:17 +02:00
Thaum Rystra
cd7702cf4f Added library page 2017-04-24 06:05:44 +02:00
Stefan Zermatten
9975a13a70 Removed useless config files 2017-04-21 15:12:34 +02:00
Stefan Zermatten
a459610c09 Added library UI for selecting a library item 2017-04-21 15:10:33 +02:00
Stefan Zermatten
7b6c59fa6c Add apple web app capable meta tag 2017-04-21 11:48:59 +02:00
Stefan Zermatten
357166063a Create License.md 2017-04-21 11:44:11 +02:00
Stefan Zermatten
2540278787 Bumped version 2017-03-09 08:40:21 +02:00
Stefan Zermatten
5ed1977082 Fixed dragging items to characters in the drawer 2017-03-09 08:40:15 +02:00
Stefan Zermatten
a750101765 Fixed app drawer appearing behind content 2017-03-09 08:39:59 +02:00
Stefan Zermatten
aa08a9b57a Fixed links not closing drawer automatically 2017-03-09 08:39:39 +02:00
Stefan Zermatten
a8960e26ec Fixed issue with health bars clamping to 100 2017-03-06 14:50:46 +02:00
Stefan Zermatten
eefb3895ad Bumped version to 1.0.0 2017-03-06 11:37:15 +02:00
Stefan Zermatten
5b1caa7e95 Merge pull request #57 from ThaumRystra/update-meteor-1.4-polymer-1.0
Update to polymer 1.0, Meteor 1.4
2017-03-06 10:36:03 +02:00
Stefan Zermatten
21fdad1054 Added resolution for webcomponentsjs 2017-02-21 09:37:32 +02:00
Stefan Zermatten
c482395426 Fixed FABs not reappearing after dialog close 2017-02-13 09:22:17 +02:00
Stefan Zermatten
cb493a9f16 Firefox now works... mostly 2017-02-13 09:21:55 +02:00
Stefan Zermatten
576c1f953b Firefox now works... barely 2017-02-10 14:01:06 +02:00
Stefan Zermatten
eb4336b3e9 Moved back to differential:vulcanize for imports 2017-01-31 15:26:05 +02:00
Stefan Zermatten
03cb32bf34 Updated color dropdown to Polymer 1 paper-swatch-picker 2017-01-31 13:36:15 +02:00
Stefan Zermatten
a869772238 Updated journal page to Polymer 1 2017-01-31 11:09:57 +02:00
Stefan Zermatten
9c61493a12 Updated persona tab to Polymer 1 2017-01-31 09:58:35 +02:00
Stefan Zermatten
7af3b8e06e Upgraded spells page to Polymer 1 2017-01-30 13:02:22 +02:00
Stefan Zermatten
fd21f4f7d7 Fixed some dialog animation edge cases 2017-01-27 15:34:59 +02:00
Stefan Zermatten
3530eefb2a Added custom tab pages animations 2017-01-27 15:34:09 +02:00
Stefan Zermatten
d710579025 Updated inventory to Polymer 1 2017-01-26 10:23:27 +02:00
Stefan Zermatten
f7a3929c05 Fixed toast 2017-01-25 08:40:30 +02:00
Stefan Zermatten
1af15eff3c Small style and event fixes 2017-01-23 15:08:19 +02:00
Stefan Zermatten
33ca60c2e6 Migrated the Feature page to Polymer 1 2017-01-20 15:45:18 +02:00
Stefan Zermatten
4261776d8c Removed SCSS, using CSS only now
SCSS wasn't being utilized enough to add much value to the build times
2017-01-20 09:06:24 +02:00
Stefan Zermatten
b44fe33b30 Finished upgrading stat tab to Polymer 1 2017-01-20 09:05:16 +02:00
Stefan Zermatten
c4a488a176 Updated useraccounts, character settings to Polymer 1 2017-01-19 15:43:48 +02:00
Stefan Zermatten
137a94f251 Began moving character sheet and related menus to Polymer 1 2017-01-18 15:04:09 +02:00
Stefan Zermatten
817020bea8 Updated hitpoint / temporary hitpoint box to Polymer 1 2017-01-18 11:19:03 +02:00
Stefan Zermatten
420de9b005 Migrated stats tab to Polymer 1.0 2017-01-17 15:01:11 +02:00
Stefan Zermatten
b8fdc27df9 Rebuilt dialog animations with cross-fade effect 2017-01-17 15:00:05 +02:00
Stefan Zermatten
bdc64dfb10 Updated the new character dialog to Polymer 1 2017-01-16 15:34:23 +02:00
Stefan Zermatten
d4bec4f5e7 Cleaned up dialogStack 2017-01-16 15:34:03 +02:00
Stefan Zermatten
041186059c Updated static pages to Polymer 1 2017-01-16 13:48:43 +02:00
Stefan Zermatten
01535a414c Updated character lists to Polymer 1 2017-01-16 13:48:18 +02:00
Stefan Zermatten
d9e180bac0 Simplified toast to just take text 2017-01-13 15:35:08 +02:00
Stefan Zermatten
235089e790 Fixed typos of the app name 2017-01-13 15:34:51 +02:00
Stefan Zermatten
c560d80c26 Updated front page to Polymer 1 2017-01-13 15:34:28 +02:00
Stefan Zermatten
a67b13f659 Updated feedback form to Polymer 1 2017-01-13 13:21:42 +02:00
Stefan Zermatten
c416adc85b Improved the dialog stack animations 2017-01-13 13:21:05 +02:00
Stefan Zermatten
dbbb3739d0 Added a new animated dialog stack
This is to replace the animated-pages hack with Blaze component built for purpose
2017-01-12 15:30:06 +02:00
Stefan Zermatten
38ea89995a Moved views out of private folder 2017-01-12 15:28:59 +02:00
Stefan Zermatten
37268495ae Got some Polymer 1.0 elements working with 1.4
Character sheets now visible, but vulcanize broke, using raw head imports instead
2016-12-22 11:15:30 +02:00
Stefan Zermatten
042b67dd77 Fixed merge conflict 2016-12-20 13:58:32 +02:00
Stefan Zermatten
07cb3859db Merge branch 'update-meteor-1.4' into update-meteor-1.4-polymer-1.0 2016-12-20 13:57:56 +02:00
Stefan Zermatten
b1fdaa726c Removed useless Kadira 2016-12-08 13:52:04 +02:00
Stefan Zermatten
a333b0bdc8 Fixed broken drawer swiping 2016-12-08 13:51:54 +02:00
Stefan Zermatten
c32680a15a Upgraded meteor 2016-12-08 13:51:35 +02:00
Stefan Zermatten
d1950b4598 Bumped changelog for webcomponentsjs update 2016-12-08 09:55:07 +02:00
Greg Williams
af20eb4f3c Updated webcomponentsjs to v0.7.23 2016-11-23 22:03:09 -08:00
Stefan Zermatten
be0283a342 Chrome 54 broke transitions again 2016-10-17 14:51:24 +02:00
Stefan Zermatten
e05561dcf9 Trying a different version of webcomponents 2016-10-03 14:27:57 +02:00
Stefan Zermatten
3c776ed018 added webcomponent dependency resolution 2016-10-03 14:19:48 +02:00
Stefan Zermatten
ebecb46935 Fixed drop-down bug, for now
Expect it to break again in Chrome 54
2016-10-03 14:05:30 +02:00
Stefan Zermatten
7af2e80ec1 Removed Polymer 0.5, started implementing Polymer 1.0 2016-10-03 13:00:27 +02:00
Stefan Zermatten
d83fe917d0 Fixed change logs version numbers 2016-09-19 10:02:25 +02:00
Stefan Zermatten
f526de88a7 Fixed invisible dropdown boxes in Chrome 52. Bumped version 2016-09-08 14:40:47 +02:00
Stefan Zermatten
55adca36d5 Added bower 2016-09-07 10:04:21 +02:00
Stefan Zermatten
be6f54e53a Added Package.json to get Scalingo working 2016-09-07 09:52:14 +02:00
Stefan Zermatten
d41c27c86d Bumped version 2016-06-03 14:49:04 +02:00
Stefan Zermatten
f4397e65ab Global update of packages 2016-06-03 14:43:11 +02:00
Stefan Zermatten
c00c69a0c7 Merge pull request #54 from ThaumRystra/fix-bug-156
closes #54
2016-05-20 09:42:25 +02:00
Robert Perce
6737983782 [#156] add "ritual" after the school in the spell detail view if the spell is, in fact, a ritual. 2016-05-13 11:40:39 -05:00
Stefan Zermatten
78441439c3 Merge pull request #52 from ThaumRystra/user-story-127
Added 'if' function for math.js to use whenever eval-ing input, closes #32
2016-05-04 07:43:21 +02:00
Robert Perce
9b72e1aea2 Added 'if' function for math.js to use whenever eval-ing input 2016-05-03 14:44:40 -05:00
Stefan Zermatten
684eca8603 Merge pull request #51 from ThaumRystra/fix-bug-163
[#163] Changed the x to a × in effectView.js
2016-05-03 11:13:32 +02:00
Stefan Zermatten
868e5f96a4 Update README.md 2016-05-03 09:03:26 +02:00
Stefan Zermatten
6fe368cde7 Updated bourbon and scss 2016-05-03 08:12:44 +02:00
Stefan Zermatten
2ffacb88e0 Included custom vulcanize package in repo 2016-05-03 07:50:12 +02:00
Stefan Zermatten
af3af0e550 Fixed missing bower dependency 2016-05-03 07:48:14 +02:00
Robert Perce
81f50d94c1 [#163] Changed the x to a × in effectView.js 2016-04-22 15:20:32 -05:00
Stefan Zermatten
49e6d51b95 Merge pull request #40 from ThaumRystra/feature-coins-inc-default
Make coins incrementable by default, closes #27
2016-03-09 14:17:23 +02:00
Stefan Zermatten
1e4e1a3b11 Merge pull request #38 from ThaumRystra/feature-base-ability-scores-10
Base ability scores default to 10, closes #36
2016-03-09 13:56:36 +02:00
Stefan Zermatten
4440c88417 Removed grouping from effects
Grouping is currently only used to differentiate effects that have the same parent, but are ambiguous as to which part of that parent they come from, like race or background. Features do not need grouped effects, because there can be no ambiguity.
2016-03-09 12:57:23 +02:00
Connor Petersen
98047ca806 Moved base-10 default to Features.js, cleaned up default Effects, and Features code to be consistent. 2016-03-07 23:21:42 -08:00
Connor Petersen
051cabc712 Cleaned up a little more 2016-03-07 22:53:27 -08:00
Connor Petersen
636fa504f1 Updated to not require caching the id or making a separate update call 2016-03-07 22:51:41 -08:00
Connor Petersen
ff9fc916f6 Added a call to adding a feature and 6 related effects to the newCharacterDialog event handler 2016-02-11 23:29:27 -08:00
Connor Petersen
2206a607b2 Added an update call to default Item adding function to update the showIncrement field to true for coins 2016-02-10 22:11:41 -08:00
Stefan Zermatten
ce224301b2 Fixed layout on chrome 49+, made character names into links 2016-02-01 09:34:22 +02:00
Stefan Zermatten
e6a9911dfc Replaced the head that was accidentally deleted 2015-11-19 12:44:11 +02:00
Stefan Zermatten
8a1871ee18 Fixed character list FAB not staying put 2015-11-19 12:43:54 +02:00
Stefan Zermatten
402f885f85 Specified thaum:vulcanize at 0.5 2015-11-18 08:52:52 +02:00
Stefan Zermatten
d07c118d47 Removed useless public items, removed Appcache, fixed vulcanize 2015-11-17 15:20:33 +02:00
Stefan Zermatten
103d44eeec Merge branch 'feature-fast-render' 2015-11-17 13:59:22 +02:00
Stefan Zermatten
33196c6771 Bumped change log 2015-11-17 13:58:50 +02:00
Stefan Zermatten
80dc862047 Added Appcache 2015-11-17 12:26:55 +02:00
Stefan Zermatten
314da14ad1 Added indexes to charId on character fields 2015-11-02 09:35:05 +02:00
Stefan Zermatten
e5dbe81ac1 Added fast render support to routes which have subscriptions
Closes #18
2015-10-13 08:22:00 +02:00
Stefan Zermatten
7e68ef64cc Merge pull request #16 from ThaumRystra/bugfix-encumberance-meter-swapping
Change character reference to be reactive based on the active, undestroyed template
2015-10-09 08:58:53 +02:00
Connor Petersen
c9d71cad52 Change character reference to be reactive based on the active, undestroyed template 2015-10-08 23:05:57 -07:00
Stefan Zermatten
d79a808c81 Bumped Version 2015-10-05 08:59:25 +02:00
Stefan Zermatten
1016c39bdf Merge branch 'fix-www-route' 2015-10-05 08:59:07 +02:00
Stefan Zermatten
5f4923e049 Added canonical URL 2015-10-05 08:57:50 +02:00
Stefan Zermatten
e83237a728 Added demeteorized to gitignore 2015-10-05 08:57:33 +02:00
Stefan Zermatten
9f323738bf Renamed custom useraccount-polymer package to be more obvious 2015-10-05 08:34:43 +02:00
Stefan Zermatten
1fc76fa50d Removed Vulcanize entirely
There is no version compatible with both Meteor 1.2 and Polymer 0.5, so screw it, clients can deal with a few ms more load time.
2015-10-05 08:34:18 +02:00
Stefan Zermatten
36d5ff0a88 Update Meteor to 1.2
This requires local copies of packages that are broken or not Polymer 0.5

Closes #15
2015-10-03 22:40:04 +02:00
257 changed files with 14643 additions and 4541 deletions

14
.codio
View File

@@ -1,14 +0,0 @@
{
// Configure your Run and Preview buttons here.
// Run button configuration
"commands": {
"Run Meteor": "cd rpg-docs \n ROOT_URL=http://period-sheriff.codio.io:3000 meteor"
},
// Preview button configuration
"preview": {
"Prieview": "http://period-sheriff-3000.codio.io",
"Ungit": "https://period-sheriff-9501.codio.io/#/repository?path=/home/codio/workspace"
}
}

View File

@@ -13,7 +13,7 @@
"disallowMixedSpacesAndTabs": "smart",
"disallowTrailingWhitespace": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowMultipleVarDecl": true,
"disallowMultipleVarDecl": false,
"disallowNewlineBeforeBlockStatements": true,
"disallowKeywordsOnNewLine": ["else"],
@@ -53,4 +53,4 @@
"disallowMultipleLineBreaks": true,
"disallowNewlineBeforeBlockStatements": true
}
}

View File

@@ -1,3 +1,4 @@
{
"undef": false
}
"undef": false,
"esversion": 6
}

674
License.md Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -1,4 +1,13 @@
RPG Docs
========
This is the repo for [DiceCloud](dicecloud.com). The currently deployed version should always be the head of the master branch.
This is the repo for [DiceCloud](dicecloud.com).
Getting started
---------------
`git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud`
`cd dicecloud`
`cd rpg-docs`
`bower install`
`meteor`

View File

@@ -0,0 +1,709 @@
[
{
"name": "Abacus",
"plural": "Abaci",
"description": "",
"value": 2,
"weight": 2
},
{
"name": "Acid (vial)",
"plural": "Acid (vials)",
"description": "As an action, you can splash the contents of this vial onto a creature within 5 feet of you or throw the vial up to 20 feet, shattering it on impact. In either case, make a ranged attack against a creature or object, treating the acid as an improvised weapon. On a hit, the target takes 2d6 acid damage.",
"value": 25,
"weight": 1
},
{
"name": "Alchemists fire (flask)",
"plural": "Alchemists fire (flasks)",
"description": "This sticky, adhesive fluid ignites when exposed to air. As an action, you can throw this flask up to 20 feet, shattering it on impact. Make a ranged attack against a creature or object, treating the alchemists fire as an improvised weapon. On a hit, the target takes 1d4 fire damage at the start of each of its turns. A creature can end this damage by using its action to make a DC 10 Dexterity check to extinguish the flames.",
"value": 50,
"weight": 1
},
{
"name": "Arrows (20)",
"plural": "Arrows (20)",
"description": "",
"value": 1,
"weight": 1
},
{
"name": "Blowgun needles (5)",
"plural": "Blowgun needles (5)",
"description": "",
"value": 1,
"weight": 1
},
{
"name": "Crossbow bolts (20)",
"plural": "Crossbow bolts (20)",
"description": "",
"value": 1,
"weight": 1.5
},
{
"name": "Sling bullets (20)",
"plural": "Sling bullets (20)",
"description": "",
"value": 0.04,
"weight": 1.5
},
{
"name": "Antitoxin (vial)",
"plural": "Antitoxin (vials)",
"description": "A creature that drinks this vial of liquid gains advantage on saving throws against poison for 1 hour. It confers no benefit to undead or constructs.",
"value": 50,
"weight": 0
},
{
"name": "Arcane focus Crystal",
"plural": "Arcane focus Crystals",
"description": "An arcane focus is a special item — an orb, a crystal, a rod, a specially constructed staff, a wand-like length of wood, or some similar item — designed to channel the power of arcane spells. A sorcerer, warlock, or wizard can use such an item as a spellcasting focus.",
"value": 10,
"weight": 1
},
{
"name": "Arcane focus Orb",
"plural": "Arcane focus Orbs",
"description": "An arcane focus is a special item — an orb, a crystal, a rod, a specially constructed staff, a wand-like length of wood, or some similar item — designed to channel the power of arcane spells. A sorcerer, warlock, or wizard can use such an item as a spellcasting focus.",
"value": 20,
"weight": 3
},
{
"name": "Arcane focus Rod",
"plural": "Arcane focus Rods",
"description": "An arcane focus is a special item — an orb, a crystal, a rod, a specially constructed staff, a wand-like length of wood, or some similar item — designed to channel the power of arcane spells. A sorcerer, warlock, or wizard can use such an item as a spellcasting focus.",
"value": 10,
"weight": 2
},
{
"name": "Arcane focus Staff",
"plural": "Arcane focus Staffs",
"description": "An arcane focus is a special item — an orb, a crystal, a rod, a specially constructed staff, a wand-like length of wood, or some similar item — designed to channel the power of arcane spells. A sorcerer, warlock, or wizard can use such an item as a spellcasting focus.",
"value": 5,
"weight": 4
},
{
"name": "Arcane focus Wand",
"plural": "Arcane focus Wands",
"description": "An arcane focus is a special item — an orb, a crystal, a rod, a specially constructed staff, a wand-like length of wood, or some similar item — designed to channel the power of arcane spells. A sorcerer, warlock, or wizard can use such an item as a spellcasting focus.",
"value": 10,
"weight": 1
},
{
"name": "Backpack",
"plural": "Backpacks",
"description": "",
"value": 2,
"weight": 5
},
{
"name": "Ball bearings (bag of 1,000)",
"plural": "Ball bearings (bags of 1,000)",
"description": "As an action, you can spill these tiny metal balls from their pouch to cover a level, square area that is 10 feet on a side. A creature moving across the covered area must succeed on a DC 10 Dexterity saving throw or fall Prone. A creature moving through the area at half speed doesnt need to make the save.",
"value": 1,
"weight": 2
},
{
"name": "Barrel",
"plural": "Barrels",
"description": "",
"value": 2,
"weight": 70
},
{
"name": "Basket",
"plural": "Baskets",
"description": "",
"value": 0.4,
"weight": 2
},
{
"name": "Bedroll",
"plural": "Bedrolls",
"description": "",
"value": 1,
"weight": 7
},
{
"name": "Bell",
"plural": "Bells",
"description": "",
"value": 1,
"weight": 0
},
{
"name": "Blanket",
"plural": "Blankets",
"description": "",
"value": 0.5,
"weight": 3
},
{
"name": "Block and tackle",
"plural": "Block and tackle sets",
"description": "A set of pulleys with a cable threaded through them and a hook to attach to objects, a block and tackle allows you to hoist up to four times the weight you can normally lift.",
"value": 1,
"weight": 5
},
{
"name": "Book",
"plural": "Books",
"description": "A book might contain poetry, historical accounts, information pertaining to a particular field of lore, diagrams and notes on gnomish contraptions, or just about anything else that can be represented using text or pictures. A book of spells is a spellbook.",
"value": 25,
"weight": 5
},
{
"name": "Bottle, glass",
"plural": "Bottles, glass",
"description": "",
"value": 2,
"weight": 2
},
{
"name": "Bucket",
"plural": "Buckets",
"description": "",
"value": 0.05,
"weight": 2
},
{
"name": "Caltrops (bag of 20)",
"plural": "Caltrops (bags of 20)",
"description": "As an action, you can spread a bag of caltrops to cover a square area that is 5 feet on a side. Any creature that enters the area must succeed on a DC 15 Dexterity saving throw or stop moving this turn and take 1 piercing damage. Taking this damage reduces the creatures walking speed by 10 feet until the creature regains at least 1 hit point. A creature moving through the area at half speed doesnt need to make the save.",
"value": 1,
"weight": 2
},
{
"name": "Candle",
"plural": "Candles",
"description": "For 1 hour, a candle sheds bright light in a 5-foot radius and dim light for an additional 5 feet.",
"value": 0.01,
"weight": 0
},
{
"name": "Case, crossbow bolt",
"plural": "Cases, crossbow bolt",
"description": "This wooden case can hold up to twenty crossbow bolts.",
"value": 1,
"weight": 1
},
{
"name": "Case, map or scroll",
"plural": "Cases, map or scroll",
"description": "This cylindrical leather case can hold up to ten rolled-up sheets of paper or five rolled-up sheets of parchment.",
"value": 1,
"weight": 1
},
{
"name": "Chain (10 feet)",
"plural": "Chains (10 feet)",
"description": "A chain has 10 hit points. It can be burst with a successful DC 20 Strength check.",
"value": 5,
"weight": 10
},
{
"name": "Chalk (1 piece)",
"plural": "Pieces of chalk",
"description": "",
"value": 0.01,
"weight": 0
},
{
"name": "Chest",
"plural": "Chests",
"description": "",
"value": 5,
"weight": 25
},
{
"name": "Climbers kit",
"plural": "Climbers kits",
"description": "A climbers kit includes special pitons, boot tips, gloves, and a harness. You can use the climbers kit as an action to anchor yourself; when you do, you cant fall more than 25 feet from the point where you anchored yourself, and you cant climb more than 25 feet away from that point without undoing the anchor.",
"value": 25,
"weight": 12
},
{
"name": "Clothes, common",
"plural": "Clothes, common",
"description": "",
"value": 0.5,
"weight": 3
},
{
"name": "Clothes, costume",
"plural": "Clothes, costume",
"description": "",
"value": 5,
"weight": 4
},
{
"name": "Clothes, fine",
"plural": "Clothes, fine",
"description": "",
"value": 15,
"weight": 6
},
{
"name": "Clothes, travelers",
"plural": "Clothes, travelers",
"description": "",
"value": 2,
"weight": 4
},
{
"name": "Component pouch",
"plural": "Component pouches",
"description": "A component pouch is a small, watertight leather belt pouch that has compartments to hold all the material components and other special items you need to cast your spells, except for those components that have a specific cost (as indicated in a spells description).",
"value": 25,
"weight": 2
},
{
"name": "Crowbar",
"plural": "Crowbars",
"description": "Using a crowbar grants advantage to Strength checks where the crowbars leverage can be applied.",
"value": 2,
"weight": 5
},
{
"name": "Sprig of mistletoe",
"plural": "Sprigs of mistletoe",
"description": "A druidic focus might be a sprig of mistletoe or holly, a wand or scepter made of yew or another special wood, a staff drawn whole out of a living tree, or a totem object incorporating feathers, fur, bones, and teeth from sacred animals. A druid can use such an object as a spellcasting focus.",
"value": 1,
"weight": 0
},
{
"name": "Totem",
"plural": "Totems",
"description": "A druidic focus might be a sprig of mistletoe or holly, a wand or scepter made of yew or another special wood, a staff drawn whole out of a living tree, or a totem object incorporating feathers, fur, bones, and teeth from sacred animals. A druid can use such an object as a spellcasting focus.",
"value": 1,
"weight": 0
},
{
"name": "Wooden staff",
"plural": "Wooden staffs",
"description": "A druidic focus might be a sprig of mistletoe or holly, a wand or scepter made of yew or another special wood, a staff drawn whole out of a living tree, or a totem object incorporating feathers, fur, bones, and teeth from sacred animals. A druid can use such an object as a spellcasting focus.",
"value": 5,
"weight": 4
},
{
"name": "Yew wand",
"plural": "Yew wands",
"description": "A druidic focus might be a sprig of mistletoe or holly, a wand or scepter made of yew or another special wood, a staff drawn whole out of a living tree, or a totem object incorporating feathers, fur, bones, and teeth from sacred animals. A druid can use such an object as a spellcasting focus.",
"value": 10,
"weight": 1
},
{
"name": "Fishing tackle",
"plural": "Fishing tackles",
"description": "This kit includes a wooden rod, silken line, corkwood bobbers, steel hooks, lead sinkers, velvet lures, and narrow netting.",
"value": 1,
"weight": 4
},
{
"name": "Flask",
"plural": "Flasks",
"description": "",
"value": 0.02,
"weight": 1
},
{
"name": "Tankard",
"plural": "Tankards",
"description": "",
"value": 0.02,
"weight": 1
},
{
"name": "Grappling hook",
"plural": "Grappling hooks",
"description": "",
"value": 2,
"weight": 4
},
{
"name": "Hammer",
"plural": "Hammers",
"description": "",
"value": 1,
"weight": 3
},
{
"name": "Hammer, sledge",
"plural": "Hammers, sledge",
"description": "",
"value": 2,
"weight": 10
},
{
"name": "Healers kit",
"plural": "Healers kits",
"description": "This kit is a leather pouch containing bandages, salves, and splints. The kit has ten uses. As an action, you can expend one use of the kit to stabilize a creature that has 0 hit points, without needing to make a Wisdom (Medicine) check.",
"value": 5,
"weight": 3
},
{
"name": "Holy amulet",
"plural": "Holy amulets",
"description": "A holy symbol is a representation of a god or pantheon. It might be an amulet depicting a symbol representing a deity, the same symbol carefully engraved or inlaid as an emblem on a shield, or a tiny box holding a fragment of a sacred relic. Fantasy-Historical Pantheons lists the symbols commonly associated with many gods in the multiverse. A cleric or paladin can use a holy symbol as a spellcasting focus. To use the symbol in this way, the caster must hold it in hand, wear it visibly, or bear it on a shield.",
"value": 5,
"weight": 1
},
{
"name": "Holy emblem",
"plural": "Holy emblems",
"description": "A holy symbol is a representation of a god or pantheon. It might be an amulet depicting a symbol representing a deity, the same symbol carefully engraved or inlaid as an emblem on a shield, or a tiny box holding a fragment of a sacred relic. Fantasy-Historical Pantheons lists the symbols commonly associated with many gods in the multiverse. A cleric or paladin can use a holy symbol as a spellcasting focus. To use the symbol in this way, the caster must hold it in hand, wear it visibly, or bear it on a shield.",
"value": 5,
"weight": 0
},
{
"name": "Holy reliquary",
"plural": "Holy reliquaries",
"description": "A holy symbol is a representation of a god or pantheon. It might be an amulet depicting a symbol representing a deity, the same symbol carefully engraved or inlaid as an emblem on a shield, or a tiny box holding a fragment of a sacred relic. Fantasy-Historical Pantheons lists the symbols commonly associated with many gods in the multiverse. A cleric or paladin can use a holy symbol as a spellcasting focus. To use the symbol in this way, the caster must hold it in hand, wear it visibly, or bear it on a shield.",
"value": 5,
"weight": 2
},
{
"name": "Holy water (flask)",
"plural": "Holy water (flasks)",
"description": "As an action, you can splash the contents of this flask onto a creature within 5 feet of you or throw it up to 20 feet, shattering it on impact. In either case, make a ranged attack against a target creature, treating the holy water as an improvised weapon. If the target is a fiend or undead, it takes 2d6 radiant damage. A cleric or paladin may create holy water by performing a special ritual. The ritual takes 1 hour to perform, uses 25 gp worth of powdered silver, and requires the caster to expend a 1st-level spell slot.",
"value": 25,
"weight": 1
},
{
"name": "Hourglass",
"plural": "Hourglasses",
"description": "",
"value": 25,
"weight": 1
},
{
"name": "Hunting trap",
"plural": "Hunting traps",
"description": "When you use your action to set it, this trap forms a saw-toothed steel ring that snaps shut when a creature steps on a pressure plate in the center. The trap is affixed by a heavy chain to an immobile object, such as a tree or a spike driven into the ground. A creature that steps on the plate must succeed on a DC 13 Dexterity saving throw or take 1d4 piercing damage and stop moving. Thereafter, until the creature breaks free of the trap, its movement is limited by the length of the chain (typically 3 feet long). A creature can use its action to make a DC 13 Strength check, freeing itself or another creature within its reach on a success. Each failed check deals 1 piercing damage to the trapped creature.",
"value": 5,
"weight": 25
},
{
"name": "Ink (1 ounce bottle)",
"plural": "Ink (1 ounce bottles)",
"description": "",
"value": 10,
"weight": 0
},
{
"name": "Ink pen",
"plural": "Ink pens",
"description": "",
"value": 0.02,
"weight": 0
},
{
"name": "Jug",
"plural": "Jugs",
"description": "",
"value": 0.02,
"weight": 4
},
{
"name": "Pitcher",
"plural": "Pitchers",
"description": "",
"value": 0.02,
"weight": 4
},
{
"name": "Ladder (10-foot)",
"plural": "Ladders (10-foot)",
"description": "",
"value": 0.1,
"weight": 25
},
{
"name": "Lamp",
"plural": "Lamps",
"description": "A lamp casts bright light in a 15-foot radius and dim light for an additional 30 feet. Once lit, it burns for 6 hours on a flask (1 pint) of oil.",
"value": 0.5,
"weight": 1
},
{
"name": "Lantern, bullseye",
"plural": "Lanterns, bullseye",
"description": "A bullseye lantern casts bright light in a 60-foot cone and dim light for an additional 60 feet. Once lit, it burns for 6 hours on a flask (1 pint) of oil.",
"value": 10,
"weight": 2
},
{
"name": "Lantern, hooded",
"plural": "Lanterns, hooded",
"description": "A hooded lantern casts bright light in a 30-foot radius and dim light for an additional 30 feet. Once lit, it burns for 6 hours on a flask (1 pint) of oil. As an action, you can lower the hood, reducing the light to dim light in a 5-foot radius.",
"value": 5,
"weight": 2
},
{
"name": "Lock",
"plural": "Locks",
"description": "A key is provided with the lock. Without the key, a creature proficient with thieves tools can pick this lock with a successful DC 15 Dexterity check. Your GM may decide that better locks are available for higher prices.",
"value": 10,
"weight": 1
},
{
"name": "Magnifying glass",
"plural": "Magnifying glasses",
"description": "This lens allows a closer look at small objects. It is also useful as a substitute for flint and steel when starting fires. Lighting a fire with a magnifying glass requires light as bright as sunlight to focus, tinder to ignite, and about 5 minutes for the fire to ignite. A magnifying glass grants advantage on any ability check made to appraise or inspect an item that is small or highly detailed.",
"value": 100,
"weight": 0
},
{
"name": "Manacles",
"plural": "Sets of manacles",
"description": "These metal restraints can bind a Small or Medium creature. Escaping the manacles requires a successful DC 20 Dexterity check. Breaking them requires a successful DC 20 Strength check. Each set of manacles comes with one key. Without the key, a creature proficient with thieves tools can pick the manacles lock with a successful DC 15 Dexterity check. Manacles have 15 hit points.",
"value": 2,
"weight": 6
},
{
"name": "Mess kit",
"plural": "Mess kits",
"description": "This tin box contains a cup and simple cutlery. The box clamps together, and one side can be used as a cooking pan and the other as a plate or shallow bowl.",
"value": 0.2,
"weight": 1
},
{
"name": "Mirror, steel",
"plural": "Mirrors, steel",
"description": "",
"value": 5,
"weight": 0.5
},
{
"name": "Oil (flask)",
"plural": "Oil (flasks)",
"description": "Oil usually comes in a clay flask that holds 1 pint. As an action, you can splash the oil in this flask onto a creature within 5 feet of you or throw it up to 20 feet, shattering it on impact. Make a ranged attack against a target creature or object, treating the oil as an improvised weapon. On a hit, the target is covered in oil. If the target takes any fire damage before the oil dries (after 1 minute), the target takes an additional 5 fire damage from the burning oil. You can also pour a flask of oil on the ground to cover a 5-foot-square area, provided that the surface is level. If lit, the oil burns for 2 rounds and deals 5 fire damage to any creature that enters the area or ends its turn in the area. A creature can take this damage only once per turn.",
"value": 0.1,
"weight": 1
},
{
"name": "Paper (one sheet)",
"plural": "Sheets of paper",
"description": "",
"value": 0.2,
"weight": 0
},
{
"name": "Parchment (one sheet)",
"plural": "Sheets of parchment",
"description": "",
"value": 0.1,
"weight": 0
},
{
"name": "Perfume (vial)",
"plural": "Perfume (vials)",
"description": "",
"value": 5,
"weight": 0
},
{
"name": "Pick, miners",
"plural": "Picks, miners",
"description": "",
"value": 2,
"weight": 10
},
{
"name": "Piton",
"plural": "Pitons",
"description": "",
"value": 0.05,
"weight": 0.25
},
{
"name": "Poison, basic (vial)",
"plural": "Poison, basic (vials)",
"description": "You can use the poison in this vial to coat one slashing or piercing weapon or up to three pieces of ammunition. Applying the poison takes an action. A creature hit by the poisoned weapon or ammunition must make a DC 10 Constitution saving throw or take 1d4 poison damage. Once applied, the poison retains potency for 1 minute before drying.",
"value": 100,
"weight": 0
},
{
"name": "Pole (10-foot)",
"plural": "Poles (10-foot)",
"description": "",
"value": 0.05,
"weight": 7
},
{
"name": "Pot, iron",
"plural": "Pots, iron",
"description": "",
"value": 2,
"weight": 10
},
{
"name": "Potion of healing",
"plural": "Potions of healing",
"description": "A character who drinks the magical red fluid in this vial regains 2d4 + 2 hit points. Drinking or administering a potion takes an action.",
"value": 50,
"weight": 0.5
},
{
"name": "Pouch",
"plural": "Pouches",
"description": "A cloth or leather pouch can hold up to 20 sling bullets or 50 blowgun needles, among other things. A compartmentalized pouch for holding spell components is called a component pouch (described earlier in this section).",
"value": 0.5,
"weight": 1
},
{
"name": "Quiver",
"plural": "Quivers",
"description": "A quiver can hold up to 20 arrows.",
"value": 1,
"weight": 1
},
{
"name": "Ram, portable",
"plural": "Rams, portable",
"description": "You can use a portable ram to break down doors. When doing so, you gain a +4 bonus on the Strength check. One other character can help you use the ram, giving you advantage on this check.",
"value": 4,
"weight": 35
},
{
"name": "Rations (1 day)",
"plural": "Days of rations",
"description": "Rations consist of dry foods suitable for extended travel, including jerky, dried fruit, hardtack, and nuts.",
"value": 0.5,
"weight": 2
},
{
"name": "Robes",
"plural": "Robes",
"description": "",
"value": 1,
"weight": 4
},
{
"name": "Rope, hempen (50 feet)",
"plural": "Ropes, hempen (50 feet)",
"description": "Rope, whether made of hemp or silk, has 2 hit points and can be burst with a DC 17 Strength check.",
"value": 1,
"weight": 10
},
{
"name": "Rope, silk (50 feet)",
"plural": "Ropes, silk (50 feet)",
"description": "Rope, whether made of hemp or silk, has 2 hit points and can be burst with a DC 17 Strength check.",
"value": 10,
"weight": 5
},
{
"name": "Sack",
"plural": "Sacks",
"description": "",
"value": 0.01,
"weight": 0.5
},
{
"name": "Scale, merchants",
"plural": "Scales, merchants",
"description": "A scale includes a small balance, pans, and a suitable assortment of weights up to 2 pounds. With it, you can measure the exact weight of small objects, such as raw precious metals or trade goods, to help determine their worth.",
"value": 5,
"weight": 3
},
{
"name": "Sealing wax",
"plural": "Sealing waxes",
"description": "",
"value": 0.5,
"weight": 0
},
{
"name": "Shovel",
"plural": "Shovels",
"description": "",
"value": 2,
"weight": 5
},
{
"name": "Signal whistle",
"plural": "Signal whistles",
"description": "",
"value": 0.05,
"weight": 0
},
{
"name": "Signet ring",
"plural": "Signet rings",
"description": "",
"value": 5,
"weight": 0
},
{
"name": "Soap",
"plural": "Soaps",
"description": "",
"value": 0.02,
"weight": 0
},
{
"name": "Spellbook",
"plural": "Spellbooks",
"description": "Essential for wizards, a spellbook is a leather-bound tome with 100 blank vellum pages suitable for recording spells.",
"value": 50,
"weight": 3
},
{
"name": "Spikes, iron (10)",
"plural": "Spikes, iron (10)",
"description": "",
"value": 1,
"weight": 5
},
{
"name": "Spyglass",
"plural": "Spyglasses",
"description": "Objects viewed through a spyglass are magnified to twice their size.",
"value": 1000,
"weight": 1
},
{
"name": "Tent, two-person",
"plural": "Tents, two-person",
"description": "A simple and portable canvas shelter, a tent sleeps two.",
"value": 2,
"weight": 20
},
{
"name": "Tinderbox",
"plural": "Tinderboxes",
"description": "This small container holds flint, fire steel, and tinder (usually dry cloth soaked in light oil) used to kindle a fire. Using it to light a torch — or anything else with abundant, exposed fuel — takes an action. Lighting any other fire takes 1 minute.",
"value": 0.5,
"weight": 1
},
{
"name": "Torch",
"plural": "Torches",
"description": "A torch burns for 1 hour, providing bright light in a 20-foot radius and dim light for an additional 20 feet. If you make a melee attack with a burning torch and hit, it deals 1 fire damage.",
"value": 0.01,
"weight": 1
},
{
"name": "Vial",
"plural": "Vials",
"description": "",
"value": 1,
"weight": 0
},
{
"name": "Waterskin",
"plural": "Waterskins",
"description": "",
"value": 0.2,
"weight": 5
},
{
"name": "Whetstone",
"plural": "Whetstones",
"description": "",
"value": 0.01,
"weight": 1
}
]

244
dataSources/srd/armor.json Normal file
View File

@@ -0,0 +1,244 @@
[
{
"name": "Padded Armor",
"value": 5,
"weight": 8,
"description": "Padded armor consists of quilted layers of cloth and batting.",
"effects": [
{
"operation": "base",
"value": 11,
"stat": "armor"
},
{
"operation": "disadvantage",
"stat": "stealth"
}
]
},
{
"name": "Leather Armor",
"value": 10,
"weight": 10,
"description": "The breastplate and shoulder protectors of this armor are made of leather that has been stiffened by being boiled in oil. The rest of the armor is made of softer and more flexible materials.",
"effects": [
{
"operation": "base",
"value": 11,
"stat": "armor"
}
]
},
{
"name": "Studded leather Armor",
"value": 45,
"weight": 13,
"description": "Made from tough but flexible leather, studded leather is reinforced with close-set rivets or spikes.",
"effects": [
{
"operation": "base",
"value": 12,
"stat": "armor"
}
]
},
{
"name": "Hide Armor",
"value": 10,
"weight": 12,
"description": "This crude armor consists of thick furs and pelts. It is commonly worn by barbarian tribes, evil humanoids, and other folk who lack access to the tools and materials needed to create better armor.",
"effects": [
{
"operation": "base",
"value": 12,
"stat": "armor"
},
{
"operation": "max",
"value": 2,
"stat": "dexterityArmor"
}
]
},
{
"name": "Chain shirt",
"value": 50,
"weight": 20,
"description": "Made of interlocking metal rings, a chain shirt is worn between layers of clothing or leather. This armor offers modest protection to the wearers upper body and allows the sound of the rings rubbing against one another to be muffled by outer layers.",
"effects": [
{
"operation": "base",
"value": 13,
"stat": "armor"
},
{
"operation": "max",
"value": 2,
"stat": "dexterityArmor"
}
]
},
{
"name": "Scale mail",
"value": 50,
"weight": 45,
"description": "This armor consists of a coat and leggings (and perhaps a separate skirt) of leather covered with overlapping pieces of metal, much like the scales of a fish. The suit includes gauntlets.",
"effects": [
{
"operation": "base",
"value": 14,
"stat": "armor"
},
{
"operation": "disadvantage",
"stat": "stealth"
},
{
"operation": "max",
"value": 2,
"stat": "dexterityArmor"
}
]
},
{
"name": "Breastplate",
"value": 400,
"weight": 20,
"description": "This armor consists of a fitted metal chest piece worn with supple leather. Although it leaves the legs and arms relatively unprotected, this armor provides good protection for the wearers vital organs while leaving the wearer relatively unencumbered.",
"effects": [
{
"operation": "base",
"value": 14,
"stat": "armor"
},
{
"operation": "max",
"value": 2,
"stat": "dexterityArmor"
}
]
},
{
"name": "Half plate",
"value": 750,
"weight": 40,
"description": "Half plate consists of shaped metal plates that cover most of the wearers body. It does not include leg protection beyond simple greaves that are attached with leather straps.",
"effects": [
{
"operation": "base",
"value": 15,
"stat": "armor"
},
{
"operation": "max",
"value": 2,
"stat": "dexterityArmor"
},
{
"operation": "disadvantage",
"stat": "stealth"
}
]
},
{
"name": "Ring mail",
"value": 30,
"weight": 40,
"description": "This armor is leather armor with heavy rings sewn into it. The rings help reinforce the armor against blows from swords and axes. Ring mail is inferior to chain mail, and its usually worn only by those who cant afford better armor.",
"effects": [
{
"operation": "base",
"value": 14,
"stat": "armor"
},
{
"operation": "mul",
"value": 0,
"stat": "dexterityArmor"
},
{
"operation": "disadvantage",
"stat": "stealth"
}
]
},
{
"name": "Chain mail",
"value": 75,
"weight": 55,
"description": "Made of interlocking metal rings, chain mail includes a layer of quilted fabric worn underneath the mail to prevent chafing and to cushion the impact of blows. The suit includes gauntlets.",
"effects": [
{
"operation": "base",
"value": 16,
"stat": "armor"
},
{
"operation": "mul",
"value": 0,
"stat": "dexterityArmor"
},
{
"operation": "disadvantage",
"stat": "stealth"
}
]
},
{
"name": "Splint Armor",
"value": 200,
"weight": 60,
"description": "This armor is made of narrow vertical strips of metal riveted to a backing of leather that is worn over cloth padding. Flexible chain mail protects the joints.",
"effects": [
{
"operation": "base",
"value": 17,
"stat": "armor"
},
{
"operation": "mul",
"value": 0,
"stat": "dexterityArmor"
},
{
"operation": "disadvantage",
"stat": "stealth"
}
]
},
{
"name": "Plate Armor",
"value": 1500,
"weight": 65,
"description": "Plate consists of shaped, interlocking metal plates to cover the entire body. A suit of plate includes gauntlets, heavy leather boots, a visored helmet, and thick layers of padding underneath the armor. Buckles and straps distribute the weight over the body.",
"effects": [
{
"operation": "base",
"value": 18,
"stat": "armor"
},
{
"operation": "mul",
"value": 0,
"stat": "dexterityArmor"
},
{
"operation": "disadvantage",
"stat": "stealth"
}
]
},
{
"name": "Shield",
"value": 10,
"weight": 6,
"description": "A shield is made from wood or metal and is carried in one hand. Wielding a shield increases your Armor Class by 2. You can benefit from only one shield at a time.",
"effects": [
{
"operation": "add",
"value": 2,
"stat": "armor"
}
]
}
]

4739
dataSources/srd/spells.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
// Only do if the library doesn't exist yet
id = Libraries.insert({
_id: "SRDLibraryGA3XWsd",
owner: Meteor.userId(),
name: "SRD Library",
});
_.each(items, (item) => {
item.settings = {category: }; // "adventuringGear", "armor", "weapons", "tools"
item.library = "SRDLibraryGA3XWsd"
LibraryItems.insert(item)
});
_.each(spells, (spell) => {
spell.library = "SRDLibraryGA3XWsd"
LibrarySpells.insert(spell)
});

247
dataSources/srd/tools.json Normal file
View File

@@ -0,0 +1,247 @@
[
{
"name": "Alchemists supplies",
"plural": "Alchemists supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 50,
"weight": 8
},
{
"name": "Brewers supplies",
"plural": "Brewers supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 20,
"weight": 9
},
{
"name": "Calligraphers supplies",
"plural": "Calligraphers supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 5
},
{
"name": "Carpenters tools",
"plural": "Carpenters tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 8,
"weight": 6
},
{
"name": "Cartographers tools",
"plural": "Cartographers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 15,
"weight": 6
},
{
"name": "Cobblers tools",
"plural": "Cobblers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 5,
"weight": 5
},
{
"name": "Cooks utensils",
"plural": "Cooks utensils",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 1,
"weight": 8
},
{
"name": "Glassblowers tools",
"plural": "Glassblowers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 30,
"weight": 5
},
{
"name": "Jewelers tools",
"plural": "Jewelers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 25,
"weight": 2
},
{
"name": "Leatherworkers tools",
"plural": "Leatherworkers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 5,
"weight": 5
},
{
"name": "Masons tools",
"plural": "Masons tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 8
},
{
"name": "Painters supplies",
"plural": "Painters supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 5
},
{
"name": "Potters tools",
"plural": "Potters tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 3
},
{
"name": "Smiths tools",
"plural": "Smiths tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 20,
"weight": 8
},
{
"name": "Tinkers tools",
"plural": "Tinkers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 50,
"weight": 10
},
{
"name": "Weavers tools",
"plural": "Weavers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 1,
"weight": 5
},
{
"name": "Woodcarvers tools",
"plural": "Woodcarvers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 1,
"weight": 5
},
{
"name": "Dice set",
"plural": "Dice sets",
"description": "This item encompasses a wide range of game pieces, including dice and decks of cards (for games such as Three-Dragon Ante). A few common examples appear on the Tools table, but other kinds of gaming sets exist. If you are proficient with a gaming set, you can add your proficiency bonus to ability checks you make to play a game with that set. Each type of gaming set requires a separate proficiency.",
"value": 0.1,
"weight": 0
},
{
"name": "Playing card set",
"plural": "Playing card sets",
"description": "This item encompasses a wide range of game pieces, including dice and decks of cards (for games such as Three-Dragon Ante). A few common examples appear on the Tools table, but other kinds of gaming sets exist. If you are proficient with a gaming set, you can add your proficiency bonus to ability checks you make to play a game with that set. Each type of gaming set requires a separate proficiency.",
"value": 0.5,
"weight": 0
},
{
"name": "Bagpipes",
"plural": "Bagpipes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 30,
"weight": 6
},
{
"name": "Drum",
"plural": "Drums",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 6,
"weight": 3
},
{
"name": "Dulcimer",
"plural": "Dulcimers",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 25,
"weight": 10
},
{
"name": "Flute",
"plural": "Flutes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 2,
"weight": 1
},
{
"name": "Lute",
"plural": "Lutes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 35,
"weight": 2
},
{
"name": "Lyre",
"plural": "Lyres",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 30,
"weight": 2
},
{
"name": "Horn",
"plural": "Horns",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 3,
"weight": 2
},
{
"name": "Pan flute",
"plural": "Pan flutes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 12,
"weight": 2
},
{
"name": "Shawm",
"plural": "Shawms",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 2,
"weight": 1
},
{
"name": "Viol",
"plural": "Viols",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 30,
"weight": 1
},
{
"name": "Disguise Kit",
"plural": "Disguise Kits",
"description": "This pouch of cosmetics, hair dye, and small props lets you create disguises that change your physical appearance. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to create a visual disguise.",
"value": 25,
"weight": 3
},
{
"name": "Forgery Kit",
"plural": "Forgery Kits",
"description": "This small box contains a variety of papers and parchments, pens and inks, seals and sealing wax, gold and silver leaf, and other supplies necessary to create convincing forgeries of physical documents. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to create a physical forgery of a document.",
"value": 15,
"weight": 5
},
{
"name": "Herbalism Kit",
"plural": "Herbalism Kits",
"description": "This kit contains a variety of instruments such as clippers, mortar and pestle, and pouches and vials used by herbalists to create remedies and potions. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to identify or apply herbs. Also, proficiency with this kit is required to create antitoxin and potions of healing.",
"value": 5,
"weight": 3
},
{
"name": "Navigators tools",
"plural": "Navigators tools",
"description": "This set of instruments is used for navigation at sea. Proficiency with navigators tools lets you chart a ships course and follow navigation charts. In addition, these tools allow you to add your proficiency bonus to any ability check you make to avoid getting lost at sea.",
"value": 25,
"weight": 2
},
{
"name": "Thieves tools",
"plural": "Thieves tools",
"description": "This set of tools includes a small file, a set of lock picks, a small mirror mounted on a metal handle, a set of narrow-bladed scissors, and a pair of pliers. Proficiency with these tools lets you add your proficiency bonus to any ability checks you make to disarm traps or open locks.",
"value": 25,
"weight": 1
},
{
"name": "Poisoner's Kit",
"plural": "Poisoner's Kits",
"description": "A poisoners kit includes the vials, chemicals, and other equipment necessary for the creation of poisons. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to craft or use poisons.",
"value": 50,
"weight": 2
}
]

View File

@@ -0,0 +1,481 @@
[
{
"name": "Club",
"value": 0.1,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d4 + {strengthMod}",
"details": "Light",
"damageType": "bludgeoning"
}
]
},
{
"name": "Dagger",
"value": 2,
"weight": 1,
"attacks": [
{
"attackBonus": "max(strengthMod, dexterityMod) + proficiencyBonus",
"damage": "1d4 + {max(strengthMod, dexterityMod)}",
"details": "Finesse, light, thrown (range 20/60)",
"damageType": "piercing"
}
]
},
{
"name": "Greatclub",
"value": 0.2,
"weight": 10,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"details": "Two-handed",
"damageType": "bludgeoning"
}
]
},
{
"name": "Handaxe",
"value": 5,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"details": "Light, thrown (range 20/60)",
"damageType": "slashing"
}
]
},
{
"name": "Javelin",
"value": 0.5,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"details": "Thrown (range 30/120)",
"damageType": "piercing"
}
]
},
{
"name": "Light hammer",
"value": 2,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d4 + {strengthMod}",
"details": "Light, thrown (range 20/60)",
"damageType": "bludgeoning"
}
]
},
{
"name": "Mace",
"value": 5,
"weight": 4,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"damageType": "bludgeoning"
}
]
},
{
"name": "Quarterstaff",
"value": 0.5,
"weight": 4,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"details": "Versatile (1d8)",
"damageType": "bludgeoning"
}
]
},
{
"name": "Sickle",
"value": 1,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d4 + {strengthMod}",
"details": "Light",
"damageType": "slashing"
}
]
},
{
"name": "Spear",
"value": 1,
"weight": 3,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"details": "Thrown (range 20/60), versatile (1d8)",
"damageType": "piercing"
}
]
},
{
"name": "Crossbow, light",
"value": 25,
"weight": 5,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1d8 + {dexterityMod}",
"details": "Ammunition (range 80/320), loading, two-handed",
"damageType": "piercing"
}
]
},
{
"name": "Dart",
"value": 0.05,
"weight": 0.25,
"attacks": [
{
"attackBonus": "max(strengthMod, dexterityMod) + proficiencyBonus",
"damage": "1d4 + {max(strengthMod, dexterityMod)}",
"details": "Finesse, thrown (range 20/60)",
"damageType": "piercing"
}
]
},
{
"name": "Shortbow",
"value": 25,
"weight": 2,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1d6 + {dexterityMod}",
"details": "Ammunition (range 80/320), two-handed",
"damageType": "piercing"
}
]
},
{
"name": "Sling",
"value": 0.1,
"weight": 0,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1d4 + {dexterityMod}",
"details": "Ammunition (range 30/120)",
"damageType": "bludgeoning"
}
]
},
{
"name": "Battleaxe",
"value": 10,
"weight": 4,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"details": "Versatile (1d10)",
"damageType": "slashing"
}
]
},
{
"name": "Flail",
"value": 10,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"damageType": "bludgeoning"
}
]
},
{
"name": "Glaive",
"value": 20,
"weight": 6,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d10 + {strengthMod}",
"details": "Heavy, reach, two-handed",
"damageType": "slashing"
}
]
},
{
"name": "Greataxe",
"value": 30,
"weight": 7,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d12 + {strengthMod}",
"details": "Heavy, two-handed",
"damageType": "slashing"
}
]
},
{
"name": "Greatsword",
"value": 50,
"weight": 6,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "2d6 + {strengthMod}",
"details": "Heavy, two-handed",
"damageType": "slashing"
}
]
},
{
"name": "Halberd",
"value": 20,
"weight": 6,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d10 + {strengthMod}",
"details": "Heavy, reach, two-handed",
"damageType": "slashing"
}
]
},
{
"name": "Lance",
"value": 10,
"weight": 6,
"description": "You have disadvantage when you use a lance to attack a target within 5 feet of you. Also, a lance requires two hands to wield when you arent mounted.",
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d12 + {strengthMod}",
"details": "Reach, special",
"damageType": "piercing"
}
]
},
{
"name": "Longsword",
"value": 15,
"weight": 3,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"details": "Versatile (1d10)",
"damageType": "slashing"
}
]
},
{
"name": "Maul",
"value": 10,
"weight": 10,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "2d6 + {strengthMod}",
"details": "Heavy, two-handed",
"damageType": "bludgeoning"
}
]
},
{
"name": "Morningstar",
"value": 15,
"weight": 4,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"damageType": "piercing"
}
]
},
{
"name": "Pike",
"value": 5,
"weight": 18,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d10 + {strengthMod}",
"details": "Heavy, reach, two-handed",
"damageType": "piercing"
}
]
},
{
"name": "Rapier",
"value": 25,
"weight": 2,
"attacks": [
{
"attackBonus": "max(strengthMod, dexterityMod) + proficiencyBonus",
"damage": "1d8 + {max(strengthMod, dexterityMod)}",
"details": "Finesse",
"damageType": "piercing"
}
]
},
{
"name": "Scimitar",
"value": 25,
"weight": 3,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"details": "Finesse, light",
"damageType": "slashing"
}
]
},
{
"name": "Shortsword",
"value": 10,
"weight": 2,
"attacks": [
{
"attackBonus": "max(strengthMod, dexterityMod) + proficiencyBonus",
"damage": "1d6 + {max(strengthMod, dexterityMod)}",
"details": "Finesse, light",
"damageType": "piercing"
}
]
},
{
"name": "Trident",
"value": 5,
"weight": 4,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d6 + {strengthMod}",
"details": "Thrown (range 20/60), versatile (1d8)",
"damageType": "piercing"
}
]
},
{
"name": "War pick",
"value": 5,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"damageType": "piercing"
}
]
},
{
"name": "Warhammer",
"value": 15,
"weight": 2,
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "1d8 + {strengthMod}",
"details": "Versatile (1d10)",
"damageType": "bludgeoning"
}
]
},
{
"name": "Whip",
"value": 2,
"weight": 3,
"attacks": [
{
"attackBonus": "max(strengthMod, dexterityMod) + proficiencyBonus",
"damage": "1d4 + {max(strengthMod, dexterityMod)}",
"details": "Finesse, reach",
"damageType": "slashing"
}
]
},
{
"name": "Blowgun",
"value": 10,
"weight": 1,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1 + {dexterityMod}",
"details": "Ammunition (range 25/100), loading",
"damageType": "piercing"
}
]
},
{
"name": "Crossbow, hand",
"value": 75,
"weight": 3,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1d6 + {dexterityMod}",
"details": "Ammunition (range 30/120), light, loading",
"damageType": "piercing"
}
]
},
{
"name": "Crossbow, heavy",
"value": 50,
"weight": 18,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1d10 + {dexterityMod}",
"details": "Ammunition (range 100/400), heavy, loading, two-handed",
"damageType": "piercing"
}
]
},
{
"name": "Longbow",
"value": 50,
"weight": 2,
"attacks": [
{
"attackBonus": "dexterityMod + proficiencyBonus",
"damage": "1d8 + {dexterityMod}",
"details": "Ammunition (range 150/600), heavy, two-handed",
"damageType": "piercing"
}
]
},
{
"name": "Net",
"value": 1,
"weight": 3,
"description": "A Large or smaller creature hit by a net is Restrained until it is freed. A net has no effect on creatures that are formless, or creatures that are Huge or larger. A creature can use its action to make a DC 10 Strength check, freeing itself or another creature within its reach on a success. Dealing 5 slashing damage to the net (AC 10) also frees the creature without harming it, ending the effect and destroying the net.\n\nWhen you use an action, bonus action, or reaction to attack with a net, you can make only one attack regardless of the number of attacks you can normally make.",
"attacks": [
{
"attackBonus": "strengthMod + proficiencyBonus",
"damage": "0",
"details": "Special, thrown (range 5/15)",
"damageType": "bludgeoning"
}
]
}
]

5
rpg-docs/.gitignore vendored
View File

@@ -1,6 +1,9 @@
.meteor/local
.meteor/meteorite
.demeteorized
settings.json
public/components
public/_imports.html
nohup.out
dump
node_modules
dump

View File

@@ -6,3 +6,10 @@ notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package

View File

@@ -1 +0,0 @@

View File

@@ -3,16 +3,14 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-platform
iron:router
accounts-password
accounts-ui
random
accounts-password@1.3.3
accounts-ui@1.1.9
random@1.0.10
dburles:collection-helpers
reactive-var
underscore
reactive-var@1.0.11
underscore@1.0.10
aldeed:collection2
differential:vulcanize
matb33:collection-hooks
zimme:collection-softremovable
momentjs:moment
@@ -20,12 +18,34 @@ dburles:mongo-collection-instances
percolate:migrations
ecwyne:mathjs
useraccounts:polymer
accounts-google
accounts-google@1.0.11
splendido:accounts-meld
email
fourseven:scss@2.1.1
wolves:bourbon
email@1.1.18
meteorhacks:subs-manager
meteorhacks:kadira
chuangbo:marked
reywood:iron-router-ga
meteor-base@1.0.4
mobile-experience@1.0.4
mongo@1.1.14
blaze-html-templates
session@1.1.7
jquery@1.11.10
tracker@1.1.1
logging@1.1.16
reload@1.1.11
ejson@1.0.13
spacebars
check@1.2.4
useraccounts:iron-routing
wizonesolutions:canonical
standard-minifier-js@1.2.1
shell-server@0.2.1
seba:minifiers-autoprefixer
nikogosovd:multiple-uihooks
templates:array
ecmascript@0.6.1
es5-shim@4.6.15
differential:vulcanize
reactive-dict
percolate:synced-cron
ongoworks:speakingurl

View File

@@ -1 +1 @@
METEOR@1.1.0.2
METEOR@1.4.2.6

View File

@@ -1,96 +1,130 @@
accounts-base@1.2.0
accounts-google@1.0.4
accounts-oauth@1.1.5
accounts-password@1.1.1
accounts-ui@1.1.5
accounts-ui-unstyled@1.1.7
aldeed:collection2@2.3.3
aldeed:simple-schema@1.3.3
autoupdate@1.2.1
base64@1.0.3
binary-heap@1.0.3
blaze@2.1.2
blaze-tools@1.0.3
boilerplate-generator@1.0.3
callback-hook@1.0.3
check@1.0.5
chuangbo:marked@0.3.5
coffeescript@1.0.6
dburles:collection-helpers@1.0.3
dburles:mongo-collection-instances@0.3.3
ddp@1.1.0
deps@1.0.7
differential:vulcanize@0.0.5
accounts-base@1.2.14
accounts-google@1.0.11
accounts-oauth@1.1.15
accounts-password@1.3.3
accounts-ui@1.1.9
accounts-ui-unstyled@1.1.13
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.1
aldeed:simple-schema@1.5.3
allow-deny@1.0.5
autoupdate@1.3.12
babel-compiler@6.13.0
babel-runtime@1.0.1
base64@1.0.10
binary-heap@1.0.10
blaze@2.3.0
blaze-html-templates@1.1.0
blaze-tools@1.0.10
boilerplate-generator@1.0.11
caching-compiler@1.1.9
caching-html-compiler@1.1.0
callback-hook@1.0.10
check@1.2.4
chuangbo:marked@0.3.5_1
coffeescript@1.11.1_4
dburles:collection-helpers@1.1.0
dburles:mongo-collection-instances@0.3.5
ddp@1.2.5
ddp-client@1.3.2
ddp-common@1.2.8
ddp-rate-limiter@1.0.6
ddp-server@1.3.12
deps@1.0.12
diff-sequence@1.0.7
differential:vulcanize@3.0.0
ecmascript@0.6.1
ecmascript-runtime@0.3.15
ecwyne:mathjs@0.25.0
ejson@1.0.6
email@1.0.6
fastclick@1.0.3
fourseven:scss@2.1.1
geojson-utils@1.0.3
google@1.1.5
html-tools@1.0.4
htmljs@1.0.4
http@1.1.0
id-map@1.0.3
iron:controller@1.0.7
iron:core@1.0.7
iron:dynamic-template@1.0.7
iron:layout@1.0.7
iron:location@1.0.7
iron:middleware-stack@1.0.7
iron:router@1.0.7
iron:url@1.0.7
jquery@1.11.3_2
json@1.0.3
lai:collection-extensions@0.1.3
launch-screen@1.0.2
less@1.0.14
livedata@1.0.13
localstorage@1.0.3
logging@1.0.7
matb33:collection-hooks@0.7.13
meteor@1.1.6
meteor-platform@1.2.2
meteorhacks:kadira@2.21.0
meteorhacks:meteorx@1.3.1
meteorhacks:subs-manager@1.3.0
minifiers@1.1.5
minimongo@1.0.8
mobile-status-bar@1.0.3
momentjs:moment@2.10.3
mongo@1.1.0
mongo-livedata@1.0.8
npm-bcrypt@0.7.8_2
oauth@1.1.4
oauth2@1.1.3
observe-sequence@1.0.6
ordered-dict@1.0.3
percolate:migrations@0.7.5
random@1.0.3
reactive-dict@1.1.0
reactive-var@1.0.5
reload@1.1.3
retry@1.0.3
reywood:iron-router-ga@0.6.0
routepolicy@1.0.5
service-configuration@1.0.4
session@1.1.0
sha@1.0.3
softwarerero:accounts-t9n@1.0.9
spacebars@1.0.6
spacebars-compiler@1.0.6
ejson@1.0.13
email@1.1.18
es5-shim@4.6.15
fastclick@1.0.13
geojson-utils@1.0.10
google@1.1.15
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
http@1.2.10
id-map@1.0.9
iron:controller@1.0.12
iron:core@1.0.11
iron:dynamic-template@1.0.12
iron:layout@1.0.12
iron:location@1.0.11
iron:middleware-stack@1.1.0
iron:router@1.1.1
iron:url@1.0.11
jquery@1.11.10
lai:collection-extensions@0.2.1_1
launch-screen@1.1.0
less@2.7.9
livedata@1.0.18
localstorage@1.0.12
logging@1.1.16
matb33:collection-hooks@0.8.4
mdg:validation-error@0.5.1
meteor@1.6.0
meteor-base@1.0.4
meteorhacks:subs-manager@1.6.4
minifier-css@1.2.16
minifier-js@1.2.17
minimongo@1.0.19
mobile-experience@1.0.4
mobile-status-bar@1.0.13
modules@0.7.7
modules-runtime@0.7.8
momentjs:moment@2.17.1
mongo@1.1.14
mongo-id@1.0.6
nikogosovd:multiple-uihooks@0.1.8
npm-bcrypt@0.9.2
npm-mongo@2.2.16_1
oauth@1.1.12
oauth2@1.1.11
observe-sequence@1.0.14
ongoworks:speakingurl@9.0.0
ordered-dict@1.0.9
percolate:migrations@0.9.8
percolate:synced-cron@1.3.2
promise@0.8.8
raix:eventemitter@0.1.3
random@1.0.10
rate-limit@1.0.6
reactive-dict@1.1.8
reactive-var@1.0.11
reload@1.1.11
retry@1.0.9
reywood:iron-router-ga@0.7.1
routepolicy@1.0.12
seba:minifiers-autoprefixer@1.0.1
service-configuration@1.0.11
session@1.1.7
sha@1.0.9
shell-server@0.2.1
softwarerero:accounts-t9n@1.3.7
spacebars@1.0.13
spacebars-compiler@1.1.0
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.0
srp@1.0.3
templating@1.1.1
tracker@1.0.7
ui@1.0.6
underscore@1.0.3
url@1.0.4
useraccounts:core@1.9.1
useraccounts:polymer@1.9.1
webapp@1.2.0
webapp-hashing@1.0.3
wolves:bourbon@1.0.0
zimme:collection-behaviours@1.0.4
zimme:collection-softremovable@1.0.4
splendido:accounts-meld@1.3.1
srp@1.0.10
standard-minifier-js@1.2.2
templates:array@1.0.3
templating@1.3.0
templating-compiler@1.3.0
templating-runtime@1.3.0
templating-tools@1.1.0
tracker@1.1.1
ui@1.0.12
underscore@1.0.10
url@1.0.11
useraccounts:core@1.14.2
useraccounts:iron-routing@1.14.2
useraccounts:polymer@1.14.2
webapp@1.3.12
webapp-hashing@1.0.9
wizonesolutions:canonical@0.0.5
zimme:collection-behaviours@1.1.3
zimme:collection-softremovable@1.0.5

View File

@@ -7,13 +7,16 @@ Schemas.Action = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true,
trim: false,
},
description: {
type: String,
optional: true,
trim: false,
},
type: {

View File

@@ -7,10 +7,12 @@ Schemas.Attack = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
defaultValue: "New Attack",
optional: true,
trim: false,
},
details: {

View File

@@ -4,9 +4,11 @@ Schemas.Buff = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true,
trim: false,
},
description: {

View File

@@ -4,6 +4,7 @@ Characters = new Mongo.Collection("characters");
Schemas.Character = new SimpleSchema({
//strings
name: {type: String, defaultValue: "", trim: false, optional: true},
urlName: {type: String, defaultValue: "", trim: false, optional: true},
alignment: {type: String, defaultValue: "", trim: false, optional: true},
gender: {type: String, defaultValue: "", trim: false, optional: true},
race: {type: String, defaultValue: "", trim: false, optional: true},
@@ -184,6 +185,9 @@ Schemas.Character = new SimpleSchema({
defaultValue: "whitelist",
allowedValues: ["whitelist", "public"],
},
"settings.exportFeatures": {type: Boolean, defaultValue: true},
"settings.exportAttacks": {type: Boolean, defaultValue: true},
"settings.exportDescription": {type: Boolean, defaultValue: true},
});
Characters.attachSchema(Schemas.Character);
@@ -254,7 +258,10 @@ var attributeBase = preventLoop(function(charId, statName){
var result = (base + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
// Don't round carry multiplier
if (statName === "carryMultiplier"){
return result;
}
return Math.floor(result);
});
@@ -443,13 +450,13 @@ Characters.calculate = {
}),
};
var depreciated = function() {
//var err = new Error("this function has been depreciated");
var deprecated = function() {
//var err = new Error("this function has been deprecated");
var name = "";
if (Template.instance()){
name = Template.instance().view.name;
}
var logString = "this function has been depreciated \n";
var logString = "this function has been deprecated \n";
if (name){
logString += "View: " + name + "\n\n";
}
@@ -464,56 +471,56 @@ Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
depreciated();
deprecated();
return Characters.calculate.getField(this._id, fieldName);
},
//returns the value of a field
fieldValue : function(fieldName){
depreciated();
deprecated();
return Characters.calculate.fieldValue(this._id, fieldName);
},
attributeValue: function(attributeName){
depreciated();
deprecated();
return Characters.calculate.attributeValue(this._id, attributeName);
},
attributeBase: function(attributeName){
depreciated();
deprecated();
return Characters.calculate.attributeBase(this._id, attributeName);
},
skillMod: function(skillName){
depreciated();
deprecated();
return Characters.calculate.skillMod(this._id, skillName);
},
proficiency: function(skillName){
depreciated();
deprecated();
return Characters.calculate.proficiency(this._id, skillName);
},
passiveSkill: function(skillName){
depreciated();
deprecated();
return Characters.calculate.passiveSkill(this._id, skillName);
},
advantage: function(skillName){
depreciated();
deprecated();
return Characters.calculate.advantage(this._id, skillName);
},
abilityMod: function(attribute){
depreciated();
deprecated();
return Characters.calculate.abilityMod(this._id, attribute);
},
passiveAbility: function(attribute){
depreciated();
deprecated();
return Characters.calculate.passiveAbility(this._id, attribute);
},
xpLevel: function(){
depreciated();
deprecated();
return Characters.calculate.xpLevel(this._id);
},
level: function(){
depreciated();
deprecated();
return Characters.calculate.level(this._id);
},
experience: function(){
depreciated();
deprecated();
return Characters.calculate.experience(this._id);
},
});
@@ -534,6 +541,12 @@ if (Meteor.isServer){
Items .remove({charId: character._id});
Containers .remove({charId: character._id});
});
Characters.after.update(function(userId, doc, fieldNames, modifier, options) {
if (_.contains(fieldNames, "name")){
var urlName = getSlug(doc.name, {maintainCase: true});
Characters.update(doc._id, {$set: {urlName}});
}
});
}
Characters.allow({

View File

@@ -1,8 +1,8 @@
Classes = new Mongo.Collection("classes");
Schemas.Class = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
level: {type: Number},
createdAt: {
type: Date,

View File

@@ -8,6 +8,7 @@ Schemas.Effect = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
@@ -58,71 +59,74 @@ Schemas.Effect = new SimpleSchema({
Effects.attachSchema(Schemas.Effect);
if (Meteor.isServer) Characters.after.insert(function(userId, char) {
Effects.insert({
charId: char._id,
name: "Constitution modifier for each level",
stat: "hitPoints",
operation: "add",
calculation: "level * constitutionMod",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Proficiency bonus by level",
stat: "proficiencyBonus",
operation: "add",
calculation: "floor(level / 4 + 1.75)",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Dexterity Armor Bonus",
stat: "armor",
operation: "add",
calculation: "dexterityArmor",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Armor",
stat: "armor",
operation: "base",
value: 10,
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Carrying Capacity",
stat: "carryMultiplier",
operation: "base",
value: "1",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
});
Effects.attachBehaviour("softRemovable");
makeChild(Effects, ["enabled"]); //children of lots of things
Effects.allow(CHARACTER_SUBSCHEMA_ALLOW);
Effects.deny(CHARACTER_SUBSCHEMA_DENY);
//give characters default character effects
Characters.after.insert(function(userId, char) {
if (Meteor.isServer) {
Effects.insert({
charId: char._id,
name: "Constitution modifier for each level",
stat: "hitPoints",
operation: "add",
calculation: "level * constitutionMod",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Proficiency bonus by level",
stat: "proficiencyBonus",
operation: "add",
calculation: "floor(level / 4 + 1.75)",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Dexterity Armor Bonus",
stat: "armor",
operation: "add",
calculation: "dexterityArmor",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Armor",
stat: "armor",
operation: "base",
value: 10,
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Carrying Capacity",
stat: "carryMultiplier",
operation: "base",
value: "1",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
}
});

View File

@@ -1,8 +1,8 @@
Experiences = new Mongo.Collection("experience");
Schemas.Experience = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, defaultValue: "New Experience", trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false, defaultValue: "New Experience"},
description: {type: String, optional: true, trim: false},
value: {type: Number, defaultValue: 0},
dateAdded: {

View File

@@ -1,8 +1,8 @@
Features = new Mongo.Collection("features");
Schemas.Feature = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
uses: {type: String, optional: true, trim: false},
used: {type: Number, defaultValue: 0},
@@ -35,3 +35,81 @@ makeParent(Features, ["name", "enabled"]); //parents of effects and attacks
Features.allow(CHARACTER_SUBSCHEMA_ALLOW);
Features.deny(CHARACTER_SUBSCHEMA_DENY);
//give characters default feature of base ability scores of 10
Characters.after.insert(function(userId, char) {
if (Meteor.isServer){
var featureId = Features.insert({
name: "Base Ability Scores",
charId: char._id,
enabled: true,
alwaysEnabled: true,
});
Effects.insert({
stat: "strength",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "dexterity",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "constitution",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "intelligence",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "wisdom",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "charisma",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
}
});

View File

@@ -1,8 +1,8 @@
Notes = new Mongo.Collection("notes");
Schemas.Note = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
color: {
type: String,

View File

@@ -4,6 +4,7 @@ Schemas.Proficiency = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,

View File

@@ -1,8 +1,8 @@
SpellLists = new Mongo.Collection("spellLists");
Schemas.SpellLists = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
saveDC: {type: String, optional: true, trim: false},
attackBonus: {type: String, optional: true, trim: false},

View File

@@ -1,7 +1,7 @@
Spells = new Mongo.Collection("spells");
Schemas.Spell = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
prepared: {
type: String,
defaultValue: "prepared",
@@ -9,6 +9,7 @@ Schemas.Spell = new SimpleSchema({
},
name: {
type: String,
optional: true,
trim: false,
defaultValue: "New Spell",
},

View File

@@ -1,7 +1,7 @@
TemporaryHitPoints = new Mongo.Collection("temporaryHitPoints");
Schemas.TemporaryHitPoints = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true},
maximum: {type: Number, defaultValue: 0, min: 0, max: 500},
used: {type: Number, defaultValue: 0, min: 0, max: 500},

View File

@@ -2,8 +2,8 @@
Containers = new Mongo.Collection("containers");
Schemas.Container = new SimpleSchema({
name: {type: String, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, optional: true, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
isCarried: {type: Boolean},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},

View File

@@ -1,10 +1,10 @@
Items = new Mongo.Collection("items");
Schemas.Item = new SimpleSchema({
name: {type: String, defaultValue: "New Item", trim: false},
name: {type: String, optional: true, trim: false, defaultValue: "New Item"},
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id}, //id of owner
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
quantity: {type: Number, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
@@ -24,18 +24,18 @@ var checkMovePermission = function(itemId, parent) {
var item = Items.findOne(itemId);
if (!item)
throw new Meteor.Error("No such item",
"An item could not be found to move");
"An item could not be found to move");
//handle permissions
var permission = Meteor.call("canWriteCharacter", item.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items from this character");
"Not permitted to move items from this character");
}
if (parent.collection === "Characters"){
permission = Meteor.call("canWriteCharacter", parent.id);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items to this character");
"Not permitted to move items to this character");
}
} else {
var parentCollectionObject = global[parent.collection];
@@ -53,7 +53,7 @@ var checkMovePermission = function(itemId, parent) {
permission = Meteor.call("canWriteCharacter", parentObject.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items to this character");
"Not permitted to move items to this character");
}
}
}
@@ -228,6 +228,9 @@ Characters.after.insert(function(userId, char) {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
Items.insert({
name: "Silver piece",
@@ -241,6 +244,9 @@ Characters.after.insert(function(userId, char) {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
Items.insert({
name: "Copper piece",
@@ -254,6 +260,9 @@ Characters.after.insert(function(userId, char) {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
}
});

View File

@@ -0,0 +1,47 @@
Libraries = new Mongo.Collection("library");
Schemas.Library = new SimpleSchema({
name: {type: String},
owner: {type: String, regEx: SimpleSchema.RegEx.Id},
readers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
public: {type: Boolean, defaultValue: false},
});
Libraries.attachSchema(Schemas.Library);
Libraries.allow({
insert(userId, doc) {
return userId && doc.owner === userId;
},
update(userId, doc, fields, modifier) {
return canEdit(userId, doc);
},
remove(userId, doc) {
return canEdit(userId, doc);
},
fetch: ["owner", "writers"],
});
Libraries.deny({
// For now, only admins can manage libraries
insert(userId, doc){
var user = Meteor.users.findOne(userId);
return !user || !_.contains(user.roles, "admin");
},
update(userId, doc, fields, modifier) {
// Can't change owners
return _.contains(fields, "owner")
},
fetch: [],
});
const canEdit = function(userId, library){
if (!userId || !library) return;
return library.owner === userId || _.contains(library.writers, userId);
};
Libraries.canEdit = function(userId, libraryId){
const library = Libraries.findOne(libraryId);
return canEdit(userId, library);
};

View File

@@ -0,0 +1,43 @@
Schemas.LibraryAttacks = new SimpleSchema({
name: {
type: String,
defaultValue: "New Attack",
trim: false,
},
details: {
type: String,
optional: true,
trim: false,
},
attackBonus: {
type: String,
defaultValue: "strengthMod + proficiencyBonus",
optional: true,
trim: false,
},
damage: {
type: String,
defaultValue: "1d8 + {strengthMod}",
optional: true,
trim: false,
},
damageType: {
type: String,
allowedValues: [
"bludgeoning",
"piercing",
"slashing",
"acid",
"cold",
"fire",
"force",
"lightning",
"necrotic",
"poison",
"psychic",
"radiant",
"thunder",
],
defaultValue: "slashing",
},
});

View File

@@ -0,0 +1,40 @@
Schemas.LibraryEffects = new SimpleSchema({
name: {
type: String,
optional: true, //TODO make necessary if there is no owner
trim: false,
},
operation: {
type: String,
defaultValue: "add",
allowedValues: [
"base",
"proficiency",
"add",
"mul",
"min",
"max",
"advantage",
"disadvantage",
"passiveAdd",
"fail",
"conditional",
],
},
// Effects either have a value OR a calculation
value: {
type: Number,
decimal: true,
optional: true,
},
calculation: {
type: String,
optional: true,
trim: false,
},
//which stat the effect is applied to
stat: {
type: String,
optional: true,
},
});

View File

@@ -0,0 +1,43 @@
LibraryItems = new Mongo.Collection("libraryItems");
Schemas.LibraryItems = new SimpleSchema({
name: {type: String, defaultValue: "New Item", trim: false},
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},
quantity: {type: Number, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
requiresAttunement: {type: Boolean, defaultValue: false},
library: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
"settings.category": {
type: String,
optional: true,
allowedValues: [
"adventuringGear", "armor", "weapons", "tools",
],
},
"settings.showIncrement": {
type: Boolean,
defaultValue: false,
},
effects: {type: [Schemas.LibraryEffects], defaultValue: []},
attacks: {type: [Schemas.LibraryAttacks], defaultValue: []},
});
LibraryItems.attachSchema(Schemas.LibraryItems);
LibraryItems.allow({
insert(userId, doc) {
return Libraries.canEdit(userId, doc.library);
},
update(userId, doc, fields, modifier) {
return Libraries.canEdit(userId, doc.library);
},
remove(userId, doc) {
return Libraries.canEdit(userId, doc.library);
},
fetch: ["library"],
});

View File

@@ -0,0 +1,66 @@
LibrarySpells = new Mongo.Collection("librarySpells");
Schemas.LibrarySpells = new SimpleSchema({
name: {
type: String,
trim: false,
defaultValue: "New Spell",
},
description: {
type: String,
optional: true,
trim: false,
},
castingTime: {
type: String,
optional: true,
defaultValue: "action",
trim: false,
},
range: {
type: String,
optional: true,
trim: false,
},
duration: {
type: String,
optional: true,
trim: false,
defaultValue: "Instantaneous",
},
"components.verbal": {type: Boolean, defaultValue: false},
"components.somatic": {type: Boolean, defaultValue: false},
"components.concentration": {type: Boolean, defaultValue: false},
"components.material": {type: String, optional: true},
ritual: {
type: Boolean,
defaultValue: false,
},
level: {
type: Number,
defaultValue: 1,
},
school: {
type: String,
defaultValue: "Abjuration",
allowedValues: magicSchools,
},
library: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
effects: {type: [Schemas.LibraryEffects], defaultValue: []},
attacks: {type: [Schemas.LibraryAttacks], defaultValue: []},
});
LibrarySpells.attachSchema(Schemas.LibrarySpells);
LibrarySpells.allow({
insert(userId, doc) {
return Libraries.canEdit(userId, doc.library);
},
update(userId, doc, fields, modifier) {
return Libraries.canEdit(userId, doc.library);
},
remove(userId, doc) {
return Libraries.canEdit(userId, doc.library);
},
fetch: ["library"],
});

View File

@@ -17,8 +17,8 @@ Schemas.Report = new SimpleSchema({
},
type: {
type: String,
allowedValues: ["bug", "change", "feature", "general"],
defaultValue: "bug",
allowedValues: ["General Feedback", "Bug", "Suggested Change", "Feature Request"],
defaultValue: "General Feedback",
},
//the immediate impact of doing this action (eg. -1 rages)
severity: {

View File

@@ -24,23 +24,35 @@ Router.map(function() {
this.route("characterList", {
path: "/characterList",
waitOn: function(){
return subsManager.subscribe("characterList", Meteor.userId());
},
data: {
characters: function(){
return Characters.find({}, {fields: {_id: 1}, sort: {name: 1}});
}
return subsManager.subscribe("characterList");
},
onAfterAction: function() {
document.title = appName;
document.title = appName + " - Characters";
},
fastRender: true,
});
this.route("characterSheetNaked", {
path: "/character/:_id/",
waitOn: function(){
return [
subsManager.subscribe("singleCharacter", this.params._id),
];
},
action: function(){
var _id = this.params._id
var character = Characters.findOne(_id);
var urlName = character && character.urlName;
var path = `\/character\/${_id}\/${urlName}`;
Router.go(path,{},{replaceState:true});
},
});
this.route("characterSheet", {
path: "/character/:_id",
path: "/character/:_id/:urlName",
waitOn: function(){
return [
subsManager.subscribe("singleCharacter", this.params._id, Meteor.userId()),
subsManager.subscribe("singleCharacter", this.params._id),
];
},
data: function() {
@@ -63,6 +75,18 @@ Router.map(function() {
window.ga && window.ga("send", "pageview", "/character");
this.next();
},
fastRender: true,
});
this.route("library", {
path: "/library",
waitOn: function(){
return subsManager.subscribe("standardLibraries");
},
onAfterAction: function() {
document.title = appName + " - Library";
},
fastRender: true,
});
this.route("loading", {
@@ -91,6 +115,7 @@ Router.map(function() {
onAfterAction: function() {
document.title = appName;
},
fastRender: true,
});
this.route("/guide", {

View File

@@ -2,7 +2,9 @@
"name": "RPG Docs",
"version": "0.0.0",
"homepage": "",
"authors": ["Stefan Zermatten"],
"authors": [
"Stefan Zermatten"
],
"license": "none",
"private": true,
"ignore": [
@@ -13,13 +15,16 @@
"tests"
],
"dependencies": {
"polymer": "Polymer/polymer#~0.5.5",
"core-elements": "Polymer/core-elements#~0.5.5",
"paper-elements": "Polymer/paper-elements#~0.5.5"
"polymer": "Polymer/polymer#^1.0.0",
"iron-elements": "PolymerElements/iron-elements#^1.0.0",
"platinum-elements": "PolymerElements/platinum-elements#^1.0.1",
"neon-elements": "PolymerElements/neon-elements#^1.0.0",
"paper-elements": "PolymerElements/paper-elements#^1.0.7",
"app-elements": "PolymerElements/app-elements#^0.10.1",
"marked-element": "PolymerElements/marked-element#^1.2.0",
"paper-swatch-picker": "PolymerElements/paper-swatch-picker#~1.0.2"
},
"resolutions": {
"core-component-page": "^0.5.0",
"polymer": "^0.5.0",
"webcomponentsjs": "^0.5.0"
"webcomponentsjs": "0.7.24"
}
}
}

View File

@@ -1,14 +1,17 @@
this.GlobalUI = (function() {
function GlobalUI() {}
GlobalUI.dialog = {};
var toast;
GlobalUI.toast = function(opts) {
var toast;
toast = $("[global-toast]")[0];
toast.text = opts.text;
Session.set("global.ui.toastTemplate", opts.template);
Session.set("global.ui.toastData", opts.data);
if (!toast) toast = $("#global-toast")[0];
if (_.isObject(opts)){
toast.text = opts.text;
Session.set("global.ui.toastTemplate", opts.template);
Session.set("global.ui.toastData", opts.data);
} else {
toast.text = opts;
Session.set("global.ui.toastTemplate");
Session.set("global.ui.toastData");
}
return toast.show();
};
@@ -18,147 +21,18 @@ this.GlobalUI = (function() {
template: "undoToast",
data: {
id: id,
collection: collection
}
collection: collection,
},
});
};
GlobalUI.setDialog = function(opts) {
this.dialog = $("[global-dialog]")[0];
Session.set("global.ui.dialogHeader", opts.heading);
Session.set("global.ui.dialogData", opts.data);
Session.set("global.ui.dialogTemplate", opts.template);
Session.set("global.ui.dialogFullOnMobile", opts.fullOnMobile !== null);
};
GlobalUI.showDialog = function(opts) {
this.dialog = $("[global-dialog]")[0];
Session.set("global.ui.dialogHeader", opts.heading);
Session.set("global.ui.dialogData", opts.data);
Session.set("global.ui.dialogTemplate", opts.template);
Session.set("global.ui.dialogFullOnMobile", opts.fullOnMobile !== null);
return Tracker.afterFlush((function(_this) {
return function() {
return _this.dialog.open();
};
})(this));
};
GlobalUI.closeDialog = function() {
return this.dialog.close();
};
//To show a detail, first animate the click, with raising z-depth
//then call this function with a template and data
//the element should have a hero-id of detail-main
GlobalUI.showDetail = function(opts) {
Session.set("global.ui.detailData", opts.data);
Session.set("global.ui.detailTemplate", opts.template);
Session.set("global.ui.detailHeroId", opts.heroId);
Session.set("global.ui.detailShow", true);
};
//if setting the detail rather than showing it,
//the template should contain the following in template.rendered
//
//if (!this.alreadyRendered){
// Session.set("global.ui.detailShow", true);
// this.alreadyRendered = true;
//}
GlobalUI.setDetail = function(opts) {
Session.set("global.ui.detailData", opts.data);
Session.set("global.ui.detailTemplate", opts.template);
Session.set("global.ui.detailHeroId", opts.heroId);
if (window.history && window.history.pushState) {
history.replaceState({detail: "closed", opts: opts}, "Detail Dialog");
history.pushState({detail: "opened", opts: opts}, "Detail Dialog");
}
};
var throttleBack = _.throttle(function() {
history.back();
}, 100, {trailing: false});
GlobalUI.closeDetail = function() {
if (window.history && history.pushState && history.state.detail === "opened") {
throttleBack();
} else {
Session.set("global.ui.detailShow", false);
}
};
GlobalUI.popStateHandler = function(e) {
var state = e.originalEvent.state;
if (state) {
if (state.detail === "closed") {
Session.set("global.ui.detailShow", false);
} else if (state.detail === "opened") {
var opts = state.opts;
Session.set("global.ui.detailData", opts.data);
Session.set("global.ui.detailTemplate", opts.template);
Session.set("global.ui.detailHeroId", opts.heroId);
}
}
};
return GlobalUI;
})();
Template.layout.helpers({
globalDialogTemplate: function() {
return Session.get("global.ui.dialogTemplate");
},
globalDialogData: function() {
return Session.get("global.ui.dialogData");
},
globalDialogFullOnMobile: function() {
return Session.get("global.ui.dialogFullOnMobile");
},
globalDialogHeader: function() {
return Session.get("global.ui.dialogHeader");
},
globalDetailSelected: function() {
return Session.get("global.ui.detailShow") ? 1 : 0;
},
globalDetailTemplate: function() {
return Session.get("global.ui.detailTemplate");
},
globalDetailData: function() {
return Session.get("global.ui.detailData");
},
globalToastTemplate: function() {
return Session.get("global.ui.toastTemplate");
},
globalToastData: function() {
return Session.get("global.ui.toastData");
}
});
Template.layout.events({
"core-overlay-close-completed [global-dialog]": function(e) {
Session.set("global.ui.dialogTemplate", null);
Session.set("global.ui.dialogData", null);
return Session.set("global.ui.dialogFullOnMobile", null);
},
"core-animated-pages-transition-end [detail-pages]": function(e) {
var detailOpened = Session.get("global.ui.detailShow");
if (!detailOpened) {
Session.set("global.ui.detailData", null);
Session.set("global.ui.detailTemplate", null);
Session.set("global.ui.detailHeroId", null);
}
},
"core-animated-pages-transition-prepare": function(e) {
var detailOpened = Session.get("global.ui.detailShow");
if (detailOpened) {
//set up the transition
} else {
//undo hack
$("#mainContentSection").removeClass("fake-selected");
}
},
"tap #screenDim": function(e) {
GlobalUI.closeDetail();
}
},
});

View File

@@ -23,3 +23,15 @@ Template.registerHelper("evaluateSignedSpaced", function(charId, string) {
Template.registerHelper("evaluateString", function(charId, string) {
return evaluateString(charId, string);
});
Template.registerHelper("evaluateSpellString", function(charId, spellListId, string) {
return evaluateSpellString(charId, spellListId, string);
});
Template.registerHelper("evaluateShortString", function(charId, string) {
if (_.isString(string)){
return evaluateString(
charId, string.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0]
);
}
});

View File

@@ -1,32 +1,23 @@
openParentDialog = function(parent, charId, heroId) {
var detail;
openParentDialog = function({
parent, charId, element, returnElement, callback,
}) {
let template;
let data;
if (parent.collection === "Characters" && parent.group === "racial") {
detail = {
template: "raceDialog",
data: {charId: parent.id},
};
template = "raceDialog";
data = {charId: parent.id};
} else if (parent.collection === "Features") {
detail = {
template: "featureDialog",
data: {featureId: parent.id},
};
template = "featureDialog";
data = {featureId: parent.id};
} else if (parent.collection === "Classes") {
detail = {
template: "classDialog",
data: {classId: parent.id},
};
template = "classDialog";
data = {classId: parent.id};
} else if (parent.collection === "Items") {
detail = {
template: "itemDialog",
data: {itemId: parent.id},
};
template = "itemDialog";
data = {itemId: parent.id};
} else if (parent.collection === "Spells") {
detail = {
template: "spellDialog",
data: {spellId: parent.id},
};
template = "spellDialog";
data = {spellId: parent.id};
}
detail.heroId = heroId;
detail.charId = charId;
GlobalUI.setDetail(detail);
pushDialogStack({template, data, element, returnElement, callback});
};

View File

@@ -10,13 +10,13 @@ Template.registerHelper("valueString", function(value) {
var resultArray = [];
if (gp > 0) {
resultArray.push(gp + "gp");
resultArray.push(gp + " gp");
}
if (sp > 0) {
resultArray.push(sp + "sp");
resultArray.push(sp + " sp");
}
if (cp > 0) {
resultArray.push(cp + "cp");
resultArray.push(cp + " cp");
}
//build string with correct spacing
@@ -36,18 +36,18 @@ Template.registerHelper("longValueString", function(value) {
//sp
var gp = Math.floor(value);
if (gp > 0) {
resultArray.push(gp + "gp");
resultArray.push(gp + " gp");
}
//sp
var sp = Math.floor(10 * (value % 1));
if (sp > 0 || resultArray.length) {
resultArray.push(sp + "sp");
resultArray.push(sp + " sp");
}
//cp
var cp = 10 * ((value * 10) % 1);
cp = Math.round(cp * 1000) / 1000;
if (cp > 0 || resultArray.length) {
resultArray.push(cp + "cp");
resultArray.push(cp + " cp");
}
//build string with correct spacing

View File

@@ -0,0 +1,24 @@
/**
* Take in a map like this:
* {
* "#someId": {
* proprty1() { return someReactiveValue()}
* }
* }
* and bind the properties to the DOM on autorun.
*
* Useful for polymer components where you need to set the order of property updating
* or alter properties that don't bind well to their attributes
*/
Blaze.Template.prototype.binding = function(bindingMap){
this.onRendered(function(){
_.each(bindingMap, (propertyMap, cssPattern) => {
node = this.find(cssPattern);
_.each(propertyMap, (func, property) => {
this.autorun(() => {
node[property] = func && func.call && func.call(this, node);
});
});
});
});
};

View File

@@ -0,0 +1,195 @@
improvedInitiativeJson = function(charId, options){
options = options || {
features: true,
attacks: true,
description: true,
};
var char = Characters.findOne(charId);
if (!char) return;
var baseValue = function(attributeName){
return Characters.calculate.attributeBase(charId, attributeName);
};
var skillMod = function(skillName){
return Characters.calculate.skillMod(charId, skillName);
};
var damageMods = getDamageMods(charId);
return JSON.stringify({
"Id": char._id,
"Name": char.name,
"Source": "DiceCloud",
"Type": char.race,
"HP": {
"Value": baseValue("hitPoints"),
"Notes": getHitDiceString(charId) || "",
},
"AC": {
"Value": baseValue("armor"),
"Notes": getArmorString(charId) || "",
},
"InitiativeModifier": skillMod("initiative"),
"Speed": ["" + baseValue("speed")],
"Abilities": {
"Str": baseValue("strength"),
"Dex": baseValue("dexterity"),
"Con": baseValue("constitution"),
"Cha": baseValue("charisma"),
"Int": baseValue("intelligence"),
"Wis": baseValue("wisdom"),
},
"DamageVulnerabilities": damageMods.vulnerabilities,
"DamageResistances": damageMods.resistances,
"DamageImmunities": damageMods.immunities,
"ConditionImmunities": [],
"Saves": [
{"Name": "Str", "Modifier": skillMod("strengthSave")},
{"Name": "Dex", "Modifier": skillMod("dexteritySave")},
{"Name": "Con", "Modifier": skillMod("constitutionSave")},
{"Name": "Int", "Modifier": skillMod("intelligenceSave")},
{"Name": "Wis", "Modifier": skillMod("wisdomSave")},
{"Name": "Cha", "Modifier": skillMod("charismaSave")},
],
"Skills": getSkills(charId),
"Senses": [
"passive Perception " +
Characters.calculate.passiveSkill(charId, "perception")
],
"Languages": getLanguages(charId),
"Challenge": "",
"Traits": options.features ? getTraits(charId) : [],
"Actions": options.attacks ? getActions(charId) : [],
"Reactions": [],
"LegendaryActions": [],
"Description": options.description ? char.description : "",
"Player": Meteor.user().username,
}, null, 2);
}
var getHitDiceString = function(charId){
var d6 = Characters.calculate.attributeBase(charId, "d6HitDice");
var d8 = Characters.calculate.attributeBase(charId, "d8HitDice");
var d10 = Characters.calculate.attributeBase(charId, "d10HitDice");
var d12 = Characters.calculate.attributeBase(charId, "d12HitDice");
var con = Characters.calculate.abilityMod(charId,"constitution");
var string = "" +
(d6 ? `${d6}d6 + ` : "") +
(d8 ? `${d8}d8 + ` : "") +
(d10 ? `${d10}d10 + ` : "") +
(d12 ? `${d12}d12 + ` : "") +
con;
}
var getArmorString = function(charId){
var bases = Effects.find({
charId: charId,
stat: "armor",
operation: "base",
enabled: true,
}).map(e => ({
ame: e.name,
value: evaluateEffect(charId, e),
}));
var base = bases.length && _.max(bases, b => b.value).name || "";
var effects = Effects.find({
charId: charId,
stat: "armor",
operation: {$ne: "base"},
enabled: true,
}).map(e => e.name);
var strings = base ? [base] : [];
strings = strings.concat(effects);
return strings.join(", ");
}
var getDamageMods = function(charId){
// jscs:disable maximumLineLength
var multipliers = [
{name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")},
{name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")},
{name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")},
{name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")},
{name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")},
{name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")},
{name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")},
{name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")},
{name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")},
{name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")},
{name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")},
{name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")},
{name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")},
];
// jscs:enable maximumLineLength
multipliers = _.groupBy(multipliers, "value");
var names = o => o.name;
return {
"immunities": _.map(multipliers["0"], names),
"resistances": _.map(multipliers["0.5"], names),
"vulnerabilities": _.map(multipliers["2"], names),
};
}
var getSkills = function(charId){
var allSkills = [
{name: "acrobatics", attribute: "dexterity"},
{name: "animalHandling", attribute: "wisdom"},
{name: "arcana", attribute: "intelligence"},
{name: "athletics", attribute: "strength"},
{name: "deception", attribute: "charisma"},
{name: "history", attribute: "intelligence"},
{name: "insight", attribute: "wisdom"},
{name: "intimidation", attribute: "charisma"},
{name: "investigation", attribute: "intelligence"},
{name: "medicine", attribute: "wisdom"},
{name: "nature", attribute: "intelligence"},
{name: "perception", attribute: "wisdom"},
{name: "performance", attribute: "charisma"},
{name: "persuasion", attribute: "charisma"},
{name: "religion", attribute: "intelligence"},
{name: "sleightOfHand", attribute: "dexterity"},
{name: "stealth", attribute: "dexterity"},
{name: "survival", attribute: "wisdom"},
];
var skills = [];
_.each(allSkills, skill => {
var value = Characters.calculate.skillMod(charId, skill.name);
var mod = Characters.calculate.abilityMod(charId, skill.attribute);
if (value !== mod){
skills.push({Name: skill.name, Modifier: value});
}
});
return skills;
};
var getLanguages = function(charId){
return Proficiencies.find({
charId,
enabled: true,
type: "language",
}).map(l => l.name);
};
var getTraits = function(charId){
return Features.find(
{charId: charId},
{sort: {color: 1, name: 1}}
).map(f => ({
Name: f.name,
// evaluateShortString helper
Content: evaluateString(
charId, f.description && f.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0]
) || "",
Usage: "",
}));
}
var getActions = function(charId){
return Attacks.find(
{charId, enabled: true},
{sort: {color: 1, name: 1}}
).map(a => ({
Name: a.name,
Content: `+${evaluate(charId, a.attackBonus)} to hit, ` +
`${evaluateString(charId, a.damage)} ${a.damageType} damage, ` +
`${a.details}`,
Usage: "",
}));
}

View File

@@ -0,0 +1,28 @@
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
var lastTime = 0;
var vendors = ["ms", "moz", "webkit", "o"];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"];
window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] ||
window[vendors[x] + "CancelRequestAnimationFrame"];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};

View File

@@ -0,0 +1,87 @@
/*Column layout*/
.column-container {
column-fill: balance;
column-gap: 0px;
column-width: 304px;
padding: 4px;
transform: translateZ(0);
}
.column-container.thin-columns {
column-count: 4;
column-width: 240px;
}
.column-container > div {
padding: 4px;
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
}
/*Cards*/
.card {
background: white;
border-radius: 2px;
position: initial;
}
.card .top {
cursor: pointer;
padding: 16px;
border-radius: 2px 2px 0 0;
}
.card .top.white {
cursor: auto;
padding: 16px;
border-bottom: rgba(0,0,0,0.12) solid 1px;
}
.card .bottom {
padding: 16px;
border-radius: 0 0 2px 2px;
}
.card .bottom.list {
padding: 16px 0;
}
.card .bottom.list .paper-font-subhead {
color: rgba(0,0,0,0.54);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.010em;
padding: 12px 16px 12px 16px;
}
.card .bottom.text {
white-space: pre-wrap;
}
.card .left {
padding: 16px;
border-radius: 2px 0 0 2px;
text-align: center;
min-width: 72px;
}
.card .right {
padding: 16px;
border-radius: 0 2px 2px 0;
}
.card .left paper-icon-button {
display: block;
height: 32px;
padding: 0;
width: 32px;
}
.card .left paper-icon-button[disabled] {
color: rgba(255, 255, 255, 0.2);
}
.card img, .card iron-image {
max-width: 100%;
}

View File

@@ -1,89 +0,0 @@
@import "bourbon/bourbon";
$thickColumnWidth: 304px;
$thinColumnWidth: 240px;
//Column layout
.column-container {
@include column-fill(balance);
@include column-gap(0px);
@include column-width($thickColumnWidth);
padding: 4px;
&.thin-columns {
@include column-count(4);
@include column-width($thinColumnWidth);
}
& > div {
padding: 4px;
//stop divs breaking over multiple columns
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
}
}
//Cards
.card {
background: white;
border-radius: 2px;
.top {
cursor: pointer;
padding: 16px;
border-radius: 2px 2px 0 0;
&.white {
cursor: auto;
padding: 16px;
border-bottom: rgba(0,0,0,0.12) solid 1px;
}
paper-checkbox::shadow #ink[checked] {
color: #ffffff;
}
paper-checkbox::shadow #ink {
color: #ffffff;
}
paper-checkbox::shadow #checkbox.checked {
background-color: #ffffff;
background-color: rgba(255,255,255,0.27);
border-color: #ffffff;
border-color: rgba(255,255,255,0.27);
}
paper-checkbox::shadow #checkbox {
border-color: #ffffff;
border-color: rgba(255,255,255,0.54);
}
}
.bottom {
padding: 16px;
border-radius: 0 0 2px 2px;
&.list {
padding: 0 0 16px 0;
.subhead {
color: rgba(0,0,0,0.54);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.010em;
padding: 12px 16px 12px 16px;
}
}
&.text {
white-space: pre-wrap;
}
}
.left {
padding: 16px;
border-radius: 2px 0 0 2px;
text-align: center;
min-width: 72px;
}
.right {
padding: 16px;
border-radius: 0 2px 2px 0;
}
}
/* undo pointer cursor on detail box heading */
#globalDetail.card .top {
cursor: auto;
}

View File

@@ -70,6 +70,10 @@
background-color: #9E9E9E;
}
.blue-grey {
background-color: #607D8B;
}
.app-grey {
background-color: #424242;
}
@@ -80,4 +84,4 @@
.black {
background-color: #262626;
}
}

View File

@@ -0,0 +1,43 @@
/*
List items
*/
.item-slot {
background-color: rgb(232, 232, 232);
background-color: rgba(0, 0, 0, 0.1);
}
.item {
background: white;
cursor: pointer;
font-size: 16px;
height: 40px;
margin: 1px 0 1px 0;
padding: 0 16px 0 16px;
position: relative;
transition: box-shadow 0.3s ease, opacity 0.5s ease-in-out;
}
.item > .itemName {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item.small {
height: 32px;
}
.item.tall {
height: 56px;
}
.item.flexible {
height: auto;
padding-top: 16px;
padding-bottom: 16px;
}
.item iron-icon, .item paper-icon-button {
color: #747474;
color: rgba(0,0,0,0.54);
}

View File

@@ -1,37 +0,0 @@
/*
List items
*/
.item-slot {
background-color: rgb(232, 232, 232);
background-color: rgba(0, 0, 0, 0.1);
}
.item {
background: white;
cursor: pointer;
font-size: 16px;
height: 40px;
margin: 1px 0 1px 0;
padding: 0 16px 0 16px;
position: relative;
transition: box-shadow 0.3s ease, opacity 0.5s ease-in-out;
&.small {
height: 32px;
}
&.tall {
height: 56px;
}
&.flexible {
height: auto;
padding-top: 16px;
padding-bottom: 16px;
}
&[hero], &:active{
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.37);
z-index: 10;
}
core-icon, paper-icon-button {
color: #747474;
color: rgba(0,0,0,0.54);
}
}

View File

@@ -1,7 +1,4 @@
@import "bourbon/bourbon";
@import "colors";
//apply a natural box layout model to all elements
/*apply a natural box layout model to all elements*/
*, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
@@ -19,17 +16,7 @@ body {
background-color: #E0E0E0;
}
//fix tabs and core-toolbar having box shadow
core-toolbar {
box-shadow: none;
}
//give drawer panel a shadow always
core-header-panel[drawer] {
box-shadow: 2px 0px 5px 0px rgba(0,0,0,0.2);
}
//Paragraphs
/*Paragraphs*/
p {
margin-bottom: 8px;
}
@@ -46,14 +33,48 @@ hr {
text-align: center;
}
//FABs
.floatyButton {
position: absolute;
bottom: 24px;
right: 24px;
.avatar {
display: inline-block;
box-sizing: border-box;
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 26px;
font-color: rgba(255, 255, 255, 0.58) !important;
}
//Buttons
/*
temporary fix for https://github.com/PolymerElements/paper-item/issues/71
*/
paper-icon-item::shadow #contentIcon {
flex-shrink: 0;
}
/*FABs*/
.floatyButton {
position: fixed;
bottom: 24px;
right: 24px;
/* stop the fab from flashing during animation */
transform: scale(1) translateZ(0px);
z-index: 3;
}
paper-fab {
background-color: #d13b2e;
}
paper-fab.keyboard-focus {
background: #630c05;
}
.red-button:not([disabled]) {
background: #d23f31;
color: #fff;
margin-top: 16px;
}
/*Buttons*/
paper-button {
color: #000;
color: rgba(0,0,0,0.87);
@@ -63,7 +84,12 @@ paper-button {
text-transform: uppercase;
}
//Style shortcuts
dicecloud-selector paper-item {
white-space: nowrap;
overflow: hidden;
}
/*Style shortcuts*/
.scroll-y {
overflow-y: auto;
}

View File

@@ -1,20 +0,0 @@
.card .left paper-icon-button {
display: block;
height: 32px;
padding: 0;
width: 32px;
}
.card .left paper-icon-button[disabled] {
color: rgba(255, 255, 255, 0.2);
}
.card .left paper-icon-button /deep/ core-icon {
height: 32px;
width: 32px;
}
/*fix paper-dropdowns*/
body /deep/ core-menu {
overflow-x: hidden !important;
}

View File

@@ -0,0 +1,15 @@
td {
padding: 8px;
}
.strengthTable{
width: 100%;
}
.strengthTable td:nth-child(2){
text-align: right;
}
.summaryTable td:nth-child(3){
text-align: right;
}

View File

@@ -1,21 +0,0 @@
td {
padding: 8px;
}
.strengthTable{
width: 100%;
td{
&:nth-child(2) {
text-align: right;
}
&:nth-child(3) {
width: 250px;
}
}
}
.summaryTable {
&:nth-child(3){
text-align: right;
}
}

View File

@@ -1,68 +0,0 @@
#detailScreenFiller {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
display: flex;
justify-content: center;
align-items: center;
}
#screenDim {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: grey;
background-color: rgba(0,0,0,0.35);
}
#fadeDummy {
pointer-events: none;
}
.detailContent {
padding: 24px;
}
html /deep/ .red-button:not([disabled]) {
background: #d23f31;
color: #fff;
margin-top: 16px;
}
#globalDetail {
background-color: white;
width: 624px;
height: 500px;
border-radius: 2px;
}
#globalDetail core-toolbar {
border-radius: 2px 2px 0 0;
}
@media (max-width: 640px) {
#globalDetail core-toolbar {
border-radius: 0;
}
}
@media (max-width: 640px) {
#globalDetail {
top: 0 !important;
left: 0 !important;
width: 100%;
height: 100%;
margin: 0;
border-radius: 0;
}
}
.not-selected [hero] {
visibility: hidden;
}

View File

@@ -1,113 +1,68 @@
.display2 {
font-size: 45px;
font-weight: 400;
color: #000;
color: rgba(0,0,0,0.54);
letter-spacing: 0;
body .paper-font-display4, body .paper-font-display3, body .paper-font-title, body .paper-font-caption{
white-space: normal;
}
.white-text .display2{
.white-text {
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text .paper-font-display2{
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
.display1 {
font-size: 34px;
font-weight: 400;
color: #000;
color: rgba(0,0,0,0.54);
letter-spacing: 0;
}
.white-text .display1, .white-text.display1{
.white-text .paper-font-display1, .white-text.paper-font-display1{
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
h1, .headline {
font-size: 24px;
font-weight: 400;
margin: 0;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0;
}
.white-text h1, .white-text .headline, .white-text.headline{
.white-text h1, .white-text .paper-font-headline, .white-text.paper-font-headline{
color: #dedede;
color: rgba(255,255,255,0.87);
}
h2, .title {
font-size: 20px;
font-weight: 500;
margin: 0;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0.005em;
}
.white-text h2, .white-text .title, .white-text.title{
.white-text h2, .white-text .paper-font-title, .white-text.paper-font-title{
color: #dedede;
color: rgba(255,255,255,0.87);
}
h3, .subhead {
font-size: 16px;
font-weight: 400;
margin: 0;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0.010em;
}
.white-text h3, .white-text .subhead{
.white-text h3, .white-text .paper-font-subhead{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.body2 {
font-size: 14px;
font-weight: 500;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0.010em;
}
.white-text .body2{
.white-text .paper-font-body2{
color: #dedede;
color: rgba(255,255,255,0.87);
}
p, .body1, body {
font-size: 14px;
font-weight: 400;
margin: 0;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0.010em;
}
.white-text p, .white-text .body1{
.white-text p, .white-text .paper-font-body1{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.caption{
font-size: 12px;
font-weight: 400;
color: #000;
color: rgba(0,0,0,0.54);
letter-spacing: 0.020em;
}
.white-text .caption{
.white-text .paper-font-caption{
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
html /deep/ .white-text{
color: #fff;
}
.black54 {
color: #444;
color: #757575;
color: rgba(0,0,0,0.54);
}
.white54 {
color: #eee;
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
.black87 {
color: #212121;
color: rgba(0,0,0,0.87);
}
.white87 {
color: #dedede;
color: rgba(255,255,255,0.87);
}

View File

@@ -1,39 +1,33 @@
<template name="attackEdit">
<div layout horizontal>
<div layout vertical flex>
<div layout horizontal>
<div class="layout horizontal">
<div class="layout vertical flex">
<div class="layout horizontal wrap">
<!--attackBonus-->
<paper-input class="attackBonusInput"
label="Attack Bonus"
floatinglabel
value={{attackBonus}}
flex></paper-input>
<paper-input class="attackBonusInput flex" label="Attack Bonus" value={{attackBonus}}
style="flex-basis: 200px; margin-right: 16px;">
{{> formulaSuffix}}
</paper-input>
<!--details-->
<paper-input class="detailInput"
label="Details"
floatinglabel
value={{details}}></paper-input>
<paper-input class="detailInput" label="Details" value={{details}} style="flex-basis: 200px;">
</paper-input>
</div>
<div layout horizontal>
<div class="layout horizontal wrap">
<!--damageBonus-->
<paper-input class="damageInput"
label="Damage"
floatinglabel
value={{damage}}
flex></paper-input>
<paper-input class="damageInput flex" label="Damage" value={{damage}}
style="flex-basis: 200px; margin-right: 16px;">
{{> bracketSuffix}}
</paper-input>
<!--DamageType-->
<paper-dropdown-menu class="damageTypeDropdown" label="Damage Type">
<paper-dropdown layered class="dropdown">
<core-menu class="menu" selected={{damageType}}>
{{#each damageTypes}}
<paper-item name={{this}} class="containerMenuItem">{{this}}</paper-item>
{{/each}}
</core-menu>
</paper-dropdown>
<paper-dropdown-menu label="Damage Type" dynamic-align>
<dicecloud-selector class="dropdown-content damageTypeDropdown" selected={{damageType}}>
{{#each damageTypes}}
<paper-item name={{this}} class="containerMenuItem">{{this}}</paper-item>
{{/each}}
</dicecloud-selector>
</paper-dropdown-menu>
</div>
</div>
<!--Delete Button-->
<paper-icon-button class="deleteAttack" role="button" tabindex="0" icon="delete" aria-label="Delete"></paper-icon-button>
<paper-icon-button class="deleteAttack" icon="delete"></paper-icon-button>
</div>
</template>
</template>

View File

@@ -15,7 +15,7 @@ var damageTypes = [
];
Template.attackEdit.events({
"tap .deleteAttack": function(event, instance) {
"click .deleteAttack": function(event, instance) {
Attacks.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Attacks", "Attack");
},
@@ -31,9 +31,8 @@ Template.attackEdit.events({
var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {details: value}});
},
"core-select .damageTypeDropdown": function(event) {
"iron-select .damageTypeDropdown": function(event) {
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = detail.item.getAttribute("name");
if (value == this.damageType) return;
Attacks.update(this._id, {$set: {damageType: value}});
@@ -44,7 +43,4 @@ Template.attackEdit.helpers({
damageTypes: function() {
return damageTypes;
},
DAMAGE_DICE: function() {
return DAMAGE_DICE;
},
});

View File

@@ -3,7 +3,7 @@
{{#if attacks.count}}
<hr style="margin: 16px 0 16px 0;">
<div id="attacks">
<h2>Attacks</h2>
<div class="paper-font-title">Attacks</div>
{{#each attacks}}
{{>attackEdit}}
{{/each}}

View File

@@ -1,14 +1,14 @@
<template name="attackView">
<div class="attackView" layout horizontal>
<div class="headline" style="margin-right: 16px;" layout horizontal center>
<div class="attackView layout horizontal">
<div class="paper-font-headline layout horizontal center" style="margin-right: 16px;">
{{evaluateSigned charId attackBonus}}
</div>
<div layout vertical>
<div class="layout vertical">
<div>
{{evaluateString charId damage}}&nbsp;{{damageType}}
</div>
{{#if details}}
<div class="caption">
<div class="paper-font-caption">
{{details}}
</div>
{{/if}}

View File

@@ -2,10 +2,10 @@
{{#if attacks.count}}
<hr style="margin: 16px 0 16px 0;">
<div class="attacks">
<h2 class="spaceAfter">Attacks</h2>
<div class="spaceAfter paper-font-title">Attacks</div>
{{#each attacks}}
{{> attackView}}
{{/each}}
</div>
{{/if}}
</template>
</template>

View File

@@ -1,27 +1,24 @@
<template name="characterSettings">
{{#with character}}
<div style="height: 100px;">
<table style="width: 100%;">
<tr>
<td>Hide Spells tab</td>
<td>
<paper-toggle-button id="hideSpellcasting"
checked={{settings.hideSpellcasting}}
touch-action="pan-y">
</paper-toggle-button>
</td>
</tr>
<tr>
<td>Use variant encumbrance</td>
<td>
<paper-toggle-button id="variantEncumbrance"
checked={{settings.useVariantEncumbrance}}
touch-action="pan-y">
</paper-toggle-button>
</td>
</tr>
</table>
<div class="fit layout vertical">
<app-header-layout has-scrolling-region class="feedback flex">
<app-header fixed effects="waterfall">
<app-toolbar>
<div main-title>Character Settings</div>
</app-toolbar>
</app-header>
<div class="form flex">
<paper-toggle-button id="hideSpellcasting" checked={{settings.hideSpellcasting}}>
Hide Spells tab
</paper-toggle-button>
<paper-toggle-button id="variantEncumbrance" checked={{settings.useVariantEncumbrance}}>
Use variant encumbrance
</paper-toggle-button>
</div>
</app-header-layout>
<div class="buttons layout horizontal end-justified">
<paper-button class="doneButton"> Done </paper-button>
</div>
</div>
{{/with}}
<paper-button id="doneButton" affirmative> Done </paper-button>
</template>

View File

@@ -23,4 +23,7 @@ Template.characterSettings.events({
);
}
},
"click .doneButton": function(event, instance){
popDialogStack();
},
});

View File

@@ -1,9 +1,20 @@
<template name="deleteCharacterConfirmation">
<div>
Deleting a character cannot be undone.<br>
To continue type "{{name}}" into the box below.<br>
<paper-input id="nameInput" label="type the characters's name here" style="width: 100%;"></paper-input><br>
<paper-button id="deleteButton" style={{getStyle}} disabled={{cantDelete}}>Delete Character</paper-button>
<div class="fit layout vertical">
<app-header-layout has-scrolling-region class="feedback flex">
<app-header fixed effects="waterfall">
<app-toolbar>
<div main-title>Delete Character</div>
</app-toolbar>
</app-header>
<div class="form flex">
Deleting a character cannot be undone.<br>
To continue type "{{name}}" into the box below.<br>
<paper-input id="nameInput" label="type the characters's name here" style="width: 100%;"></paper-input><br>
<paper-button id="deleteButton" style={{getStyle}} disabled={{cantDelete}}>Delete Character</paper-button>
</div>
</app-header-layout>
<div class="buttons layout horizontal end-justified">
<paper-button class="cancelButton"> Cancel </paper-button>
</div>
</div>
<paper-button id="cancelButton" affirmative> Cancel </paper-button>
</template>
</template>

View File

@@ -18,11 +18,14 @@ Template.deleteCharacterConfirmation.events({
var canDel = instance.find("#nameInput").value === this.name;
instance.canDelete.set(canDel);
},
"tap #deleteButton": function(event, instance) {
"click #deleteButton": function(event, instance) {
if (instance.find("#nameInput").value === this.name) {
GlobalUI.closeDialog();
Router.go("/");
popDialogStack();
Router.go("/characterList");
Characters.remove(this._id);
}
}
},
"click .cancelButton": function(event, instance){
popDialogStack();
},
});

View File

@@ -1,54 +1,65 @@
<template name="shareDialog">
<div style="width: 360px;">
<div layout horizontal center>
<div>Who can view this character: </div>
<paper-dropdown-menu class="visibilityDropdown"
label="Visibility">
<paper-dropdown layered class="dropdown">
<core-menu class="menu visibilityMenu" selected={{viewPermission}}>
<paper-item name="whitelist">Only people I share with</paper-item>
<paper-item name="public">Anyone with link</paper-item>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
<div class="fit layout vertical">
<app-header-layout has-scrolling-region class="feedback flex">
<app-header fixed effects="waterfall">
<app-toolbar>
<div main-title>Share</div>
</app-toolbar>
</app-header>
<div class="form flex">
<paper-dropdown-menu label="Who can view this character">
<dicecloud-selector class="visibilityDropdown dropdown-content" selected={{viewPermission}}>
<paper-item name="whitelist">Only people I share with</paper-item>
<paper-item name="public">Anyone with link</paper-item>
</dicecloud-selector>
</paper-dropdown-menu>
<div class="layout horizontal center wrap">
<paper-input class="flex" id="userNameOrEmailInput" label="Share with username or email" floatinglabel></paper-input>
<paper-button id="shareButton"
class="red-button"
style="width: 80px; height: 37px; margin-top: 16px;"
raised
disabled={{shareButtonDisabled}}>Share</paper-button>
<paper-radio-group id="accessLevelMenu" selected="read">
<paper-radio-button name="read">View Only</paper-radio-button>
<paper-radio-button name="write">Can Edit</paper-radio-button>
</paper-radio-group>
</div>
<p style="color: red;">{{userFindError}}</p>
<div>
{{#if readers.length}}
<div class="paper-font-subhead">
Can View
</div>
{{#each id in readers}}
<div class="layout horizontal center">
{{#with id=id}}
<paper-icon-button class="deleteShare" icon="delete">
</paper-icon-button>
{{/with}}
<div class="flex">{{username id}}</div>
</div>
{{/each}}
{{/if}}
{{#if writers.length}}
<div class="paper-font-subhead">
Can Edit
</div>
{{#each id in writers}}
<div class="layout horizontal center">
{{#with id=id}}
<paper-icon-button class="deleteShare" icon="delete">
</paper-icon-button>
{{/with}}
<div class="flex">{{username id}}</div>
</div>
{{/each}}
{{/if}}
</div>
</div>
</app-header-layout>
<div class="buttons layout horizontal end-justified">
<paper-button class="doneButton"> Done </paper-button>
</div>
<div>
{{#if readers.count}}
<div style="font-weight: 500;">
Can View
</div>
{{#each readers}}
<div layout horizontal center>
<div flex>{{getUserName}}</div>
<paper-icon-button class="deleteShare" icon="delete"></paper-icon-button>
</div>
{{/each}}
{{/if}}
{{#if writers.count}}
<div style="font-weight: 500;">
Can Edit
</div>
{{#each writers}}
<div layout horizontal center>
<div flex>{{username}}</div>
<paper-icon-button class="deleteShare" icon="delete"></paper-icon-button>
</div>
{{/each}}
{{/if}}
</div>
<div layout horizontal center>
<paper-input flex id="userNameOrEmailInput" label="Username or email" floatinglabel></paper-input>
<paper-button id="shareButton"
class="red-button"
style="width: 80px; height: 37px; margin-top: 16px;"
raised
disabled={{shareButtonDisabled}}>Share</paper-button>
</div>
<p style="color: red;">{{userFindError}}</p>
<paper-radio-group id="accessLevelMenu" selected="read">
<paper-radio-button name="read" label="View Only"></paper-radio-button>
<paper-radio-button name="write" label="Can Edit"></paper-radio-button>
</paper-radio-group>
</div>
<paper-button id="doneButton" affirmative> Done </paper-button>
</template>

View File

@@ -1,5 +1,19 @@
Template.shareDialog.onCreated(function(){
this.userId = new ReactiveVar();
this.autorun(() => {
var char = Characters.findOne(Template.currentData()._id, {
fields: {readers: 1, writers: 1}
});
if (!char) return;
this.subscribe("userNames", _.union(char.readers, char.writers));
});
});
Template.shareDialog.onRendered(function(){
// Polymer not mirroring selected attribute properly
this.$("paper-listbox").each(function(){
this.selected = this.getAttribute("selected");
});
});
Template.shareDialog.helpers({
@@ -9,11 +23,16 @@ Template.shareDialog.helpers({
},
readers: function(){
var char = Characters.findOne(this._id, {fields: {readers: 1}});
return Meteor.users.find({_id: {$in: char.readers}});
return char.readers;
},
writers: function(){
var char = Characters.findOne(this._id, {fields: {writers: 1}});
return Meteor.users.find({_id: {$in: char.writers}});
//Meteor.users.find({_id: {$in: char.writers}});
return char.writers
},
username: function(id){
const user = Meteor.users.findOne(id);
return user && user.username || "user: " + id;
},
shareButtonDisabled: function(){
return !Template.instance().userId.get();
@@ -23,15 +42,11 @@ Template.shareDialog.helpers({
return "User not found";
}
},
getUserName: function() {
return this.username || "user: " + this._id;
}
});
Template.shareDialog.events({
"core-select .visibilityDropdown": function(event){
"iron-select .visibilityDropdown": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = detail.item.getAttribute("name");
var char = Characters.findOne(this._id, {fields: {settings: 1}});
if (value == char.settings.viewPermission) return;
@@ -50,7 +65,7 @@ Template.shareDialog.events({
}
});
},
"tap #shareButton": function(event, instance){
"click #shareButton": function(event, instance){
var self = this;
var permission = instance.find("#accessLevelMenu").selected;
if (!permission) throw "no permission set";
@@ -68,9 +83,12 @@ Template.shareDialog.events({
});
}
},
"tap .deleteShare": function(event, instance) {
"click .deleteShare": function(event, instance) {
Characters.update(instance.data._id, {
$pull: {writers: this._id, readers: this._id}
$pull: {writers: this.id, readers: this.id}
});
},
"click .doneButton": function(event, instance){
popDialogStack();
},
});

View File

@@ -1,28 +1,24 @@
paper-tabs, core-toolbar {
box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.2);
}
core-toolbar paper-tabs {
box-shadow: none;
}
paper-tabs[noink][nobar] paper-tab.core-selected {
color: #fff;
}
paper-tabs /deep/ #selectionBar, #onRadio {
background-color: #d50000;
}
paper-tabs ::shadow #ink {
color: #b22 !important;
}
paper-tabs.transparent-brown {
background-color: transparent;
box-shadow: none;
}
core-toolbar.medium-tall {
app-toolbar.medium-tall {
height: 108px;
}
.character-name {
padding-left: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.character-menu paper-icon-item{
cursor: pointer;
}
.character-sheet app-header {
position: relative;
z-index: 1;
}
.character-sheet iron-pages > div {
overflow-y: auto;
overflow-x: hidden;
}

View File

@@ -1,54 +1,62 @@
<template name="characterSheet">
<!--<div tool horizontal layout flex end-justified class="bottom">Title-bottom</div>-->
<core-toolbar class="medium-tall {{colorClass}}">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>
{{name}}
</div>
{{#if canEditCharacter _id}}
<div>
{{> colorDropdown}}
</div>
<paper-menu-button>
<paper-icon-button icon="more-vert" noink></paper-icon-button>
<paper-dropdown class="dropdown" halign="right">
<core-menu class="menu" style="color: black; color: rgba(0,0,0,0.87);">
<paper-item id="deleteCharacter">
<core-icon icon="delete"></core-icon>Delete
</paper-item>
<paper-item id="shareCharacter">
<core-icon icon="social:share"></core-icon>Share
</paper-item>
<paper-item id="characterSettings">
<core-icon icon="settings"></core-icon>Settings
</paper-item>
</core-menu>
</paper-dropdown>
</paper-menu-button>
{{/if}}
<div class="bottom fit" horizontal layout>
<paper-tabs flex horizontal center layout id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}">
<paper-tab name="stats">Stats</paper-tab>
<paper-tab name="features">Features</paper-tab>
<paper-tab name="inventory">Inventory</paper-tab>
<div class="fit layout vertical character-sheet">
<app-header fixed effects="waterfall">
<app-toolbar class="medium-tall {{colorClass}}">
<div top-item class="layout horizontal center">
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div class="flex character-name">
{{name}}
</div>
{{#if canEditCharacter _id}}
{{> colorDropdown}}
<paper-menu-button class="character-menu" horizontal-align="right">
<paper-icon-button icon="more-vert" class="dropdown-trigger"></paper-icon-button>
<paper-menu class="dropdown-content black87">
<paper-icon-item id="deleteCharacter">
<iron-icon icon="delete" item-icon></iron-icon>
Delete
</paper-icon-item>
<paper-icon-item id="shareCharacter">
<iron-icon icon="social:share" item-icon></iron-icon>
Share
</paper-icon-item>
<paper-icon-item id="characterSettings">
<iron-icon icon="settings" item-icon></iron-icon>
Settings
</paper-icon-item>
<paper-icon-item id="characterExport">
<iron-icon icon="content-copy" item-icon></iron-icon>
Export to Improved Initiative
</paper-icon-item>
</paper-menu>
</paper-menu-button>
{{/if}}
</div>
<div bottom-item>
<paper-tabs id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}">
<paper-tab name="stats">Stats</paper-tab>
<paper-tab name="features">Features</paper-tab>
<paper-tab name="inventory">Inventory</paper-tab>
{{#unless hideSpellcasting}}
<paper-tab name="spells">Spells</paper-tab>
{{/unless}}
<paper-tab name="persona">Persona</paper-tab>
<paper-tab name="journal">Journal</paper-tab>
</paper-tabs>
</div>
</app-toolbar>
</app-header>
<div class="flex" style="position: relative;">
<iron-pages id="tabPages" class="fit" selected={{selectedTab}}>
<div name="stats" class="tab-page fit">{{> stats}}</div>
<div name="features" class="tab-page fit">{{> features}}</div>
<div name="inventory" class="tab-page fit">{{> inventory}}</div>
{{#unless hideSpellcasting}}
<paper-tab name="spells">Spells</paper-tab>
<div name="spells" class="tab-page fit">{{> spells}}</div>
{{/unless}}
<paper-tab name="persona">Persona</paper-tab>
<paper-tab name="journal">Journal</paper-tab>
</paper-tabs>
<div name="persona" class="tab-page fit">{{> persona}}</div>
<div name="journal" class="tab-page fit">{{> journal}}</div>
</iron-pages>
</div>
</core-toolbar>
<div fit>
<core-animated-pages id="tabPages" selected={{selectedTab}} transitions="slide-from-right" fit>
<section flex name="stats">{{> stats}}</section>
<section flex name="features">{{> features}}</section>
<section flex name="inventory">{{> inventory}}</section>
{{#unless hideSpellcasting}}
<section flex name="spells">{{> spells}}</section>
{{/unless}}
<section flex name="persona">{{> persona}}</section>
<section flex name="journal">{{> journal}}</section>
</core-animated-pages>
</div>
</template>

View File

@@ -1,11 +1,162 @@
Template.characterSheet.onCreated(function() {
//default to the first tab
Session.setDefault(this.data._id + ".selectedTab", "stats");
let tabPages, tabSliders, tabFabs, tabFabMenus;
Template.characterSheet.onRendered(function() {
//default to the stats tab
Session.setDefault(this.data._id + ".selectedTab", "0");
// Keep the header's scroll target up to date with the currently selected tab
let header;
this.autorun(() => {
const tab = getTab(Template.currentData()._id);
header = header || this.find("app-header");
if (!header) return;
Tracker.afterFlush(() => {
header.scrollTarget = this.find("#tabPages .iron-selected");
header._scrollHandler && header._scrollHandler();
});
});
_.defer(() => {
// Store all the tab page components for use in animations
tabPages = _.times(6, (n) =>
this.$(`.tab-page:nth-child(${n + 1})`)
);
tabSliders = _.times(6, (n) =>
tabPages[n].find(".animation-slider")
);
tabFabs = _.times(6, (n) =>
tabPages[n].find(".floatyButton")
);
tabFabMenus = _.times(6, (n) =>
tabPages[n].find(".mini-holder")
);
})
//watch this character and make sure their encumbrance is updated
trackEncumbranceConditions(this.data._id, this);
//trackEncumbranceConditions(this.data._id, this);
});
/**
* Page change animations that suck less than neon-animated-pages
*/
const tabAnimation = ({oldTab, newTab, duration}) => {
if (newTab === oldTab) return;
duration = duration || 400;
const delay = (element, f) => {
element.on(
"transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd",
(event) => {
if (event.target == event.currentTarget){
f();
$(this).off(event);
}
}
);
}
const isForward = newTab > oldTab;
const entryAnimation = {
before: {
transform: isForward ? "translateX(100%)" : "translateX(-100%)",
},
during: {
transition: `transform ${duration}ms ease`,
transform: "",
},
after: {
transition: "",
transform: "",
},
}
const exitAnimation = {
before: {
transform: "translateZ(0)",
},
during: {
transition: `transform ${duration}ms ease`,
transform: isForward ? "translateX(-100%)" : "translateX(100%)",
},
after: {
transition: "",
transform: "",
display: "",
},
}
const oldPage = tabPages[oldTab];
const newPage = tabPages[newTab];
if (oldPage.length && newPage.length){
oldPage[0].style.setProperty("display", "initial", "important");
oldPage.css({zIndex: "1"});
newPage.css({zIndex: "0"});
_.defer(() => {
oldPage.css({
transition: `z-index ${duration}ms linear`,
zIndex: "0",
});
newPage.css({
transition: `z-index ${duration}ms linear`,
zIndex: "1",
});
});
delay(oldPage, () => oldPage.css({
display: "",
transition: "",
zIndex: "",
}));
delay(newPage, () => newPage.css({
transition: "",
zIndex: "",
}));
}
const oldSlider = tabSliders[oldTab];
if (oldSlider.length){
oldSlider.css(exitAnimation.before);
_.defer(() => oldSlider.css(exitAnimation.during));
delay(oldSlider, () => oldSlider.css(exitAnimation.after));
}
const newSlider = tabSliders[newTab];
if (newSlider.length){
newSlider.css(entryAnimation.before);
_.defer(() => newSlider.css(entryAnimation.during));
delay(newSlider, () => newSlider.css(entryAnimation.after));
}
slideDown = ({element, reverse}) => {
element.css({
transform: reverse ? "translateY(80px)" : "",
});
const fraction = duration / 4;
_.defer(() => element.css({
transition: reverse ?
`transform ${fraction}ms ease-out ${duration - fraction}ms` :
`transform ${fraction}ms ease-in`,
transform: reverse ? "" : "translateY(80px)",
}));
delay(element, () => element.css({
transition: "",
}));
}
const oldFab = tabFabs[oldTab];
const newFab = tabFabs[newTab];
if (oldFab.length && !newFab.length){
slideDown({element: oldFab});
}
if (newFab.length && !oldFab.length){
slideDown({element: newFab, reverse: true});
}
if (newFab.length && oldFab.length){
newFab.css({transform: ""});
}
const oldFabMenu = tabFabMenus[oldTab];
if (oldFabMenu.length) {
Blaze.getView(oldFabMenu[0]).templateInstance().active.set(false);
}
}
var setTab = function(charId, tab){
tabAnimation({
oldTab: +Session.get(charId + ".selectedTab"),
newTab: +tab,
});
return Session.set(charId + ".selectedTab", tab);
};
@@ -24,34 +175,39 @@ Template.characterSheet.helpers({
});
Template.characterSheet.events({
"core-animated-pages-transition-end #tabPages": function(event) {
event.stopPropagation();
},
"tap #characterSheetTabs paper-tab": function(event, instance){
setTab(this._id, event.currentTarget.getAttribute("name"));
"iron-select #characterSheetTabs": function(event, instance){
setTab(this._id, event.target.selected);
},
"color-change": function(event, instance){
console.log("character color change")
Characters.update(this._id, {$set: {color: event.color}});
},
"tap #deleteCharacter": function(event, instance){
GlobalUI.showDialog({
heading: "Delete " + this.name,
"click #deleteCharacter": function(event, instance){
pushDialogStack({
data: this,
template: "deleteCharacterConfirmation",
element: event.currentTarget.parentElement.parentElement,
});
},
"tap #shareCharacter": function(event, instance){
GlobalUI.showDialog({
heading: "Share " + this.name,
"click #shareCharacter": function(event, instance){
pushDialogStack({
data: this,
template: "shareDialog",
element: event.currentTarget.parentElement.parentElement,
});
},
"tap #characterSettings": function(event, instance){
GlobalUI.showDialog({
heading: this.name + " Settings",
"click #characterSettings": function(event, instance){
pushDialogStack({
data: this,
template: "characterSettings",
element: event.currentTarget.parentElement.parentElement,
});
},
"click #characterExport": function(event, instance){
pushDialogStack({
data: this,
template: "exportDialog",
element: event.currentTarget.parentElement.parentElement,
});
},
});

View File

@@ -1,22 +1,25 @@
html /deep/ .operationDropDown {
.effectEdit .operationDropDown {
width: 152px;
}
html /deep/ .statDropDown {
.effectEdit .statDropDown {
width: 152px;
}
html /deep/ .damageMultiplierDropDown {
.effectEdit .damageMultiplierDropDown {
width: 152px;
}
html /deep/ .effectEdit paper-input {
position: relative;
bottom: 5px;
.effectEdit .deleteEffect {
flex-shrink: 0;
}
html /deep/ .effectEdit {
height: 64px;
display: flex;
align-items: flex-end;
.effectEdit .effect-table-view {
align-self: center;
color: #757575;
color: rgba(0,0,0,0.54);
}
.effectEdit .iron-selected {
color: #ad2a1f;
}

View File

@@ -1,66 +1,64 @@
<template name="effectEdit">
<div class="effectEdit" layout horizontal center>
<paper-dropdown-menu class="statDropDown"
label="Stat">
<paper-dropdown layered
class="dropdown">
<core-menu class="menu statMenu" selected={{stat}}>
{{#each statGroups}}
<div style="font-weight: bold;
margin-top: 16px;">{{this}}</div>
{{#with effect}}
{{#baseEditDialog hideColor=true title="Effect"}}
<div class="fit layout vertical effectEdit" style="padding: 24px;">
<table class="paper-font-display1 effect-table-view">
<tr>
{{> effectView}}
</tr>
</table>
<hr class="vertMargin" style="width: 100%;">
{{#if showEffectValueInput}}
<paper-input class="effectValueInput"
label="Value"
floatinglabel
value={{effectValue}}>
</paper-input>
{{else}}
<div style="height: 62px;"></div>
{{/if}}
<div class="effectEdit layout horizontal flex">
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
{{#each statGroups}}
<div class="statGroupTitle clickable" style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
{{this}}
</div>
<iron-collapse opened={{isGroupSelected this ../stat}}>
{{#each stats}}
<paper-item name={{stat}}>{{name}}</paper-item>
<paper-item name={{stat}} class="clickable">{{name}}</paper-item>
{{/each}}
{{/each}}
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
{{#if operations}}
<paper-dropdown-menu class="operationDropDown"
label="Operation">
<paper-dropdown layered class="dropdown">
<core-menu class="menu operationMenu" selected={{operation}}>
</iron-collapse>
{{/each}}
</dicecloud-selector>
{{#if operations}}
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
{{#each operations}}
<paper-item name={{operation}}>{{name}}</paper-item>
<paper-item name={{operation}} class="clickable">{{name}}</paper-item>
{{/each}}
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
{{/if}}
{{#if effectValueTemplate}}
{{> Template.dynamic template=effectValueTemplate}}
{{else}}
<div flex></div>
{{/if}}
<paper-icon-button class="deleteEffect"
icon="delete">
</paper-icon-button>
<br>
</div>
</template>
<template name="regularEffectValue">
<paper-input class="effectValueInput"
label="Value"
floatinglabel
value={{effectValue}}
flex
style="flex-basis: 100px;">
</paper-input>
</dicecloud-selector>
{{else}} {{#if showMultiplierOperations}}
<dicecloud-selector class="multiplierMenu flex"
selected={{value}}>
<paper-item name="0.5" class="clickable">Resistance</paper-item>
<paper-item name="2" class="clickable">Vulnerability</paper-item>
<paper-item name="0" class="clickable">Immunity</paper-item>
</dicecloud-selector>
{{else}}
<div class="flex" style="height: 100%;"></div>
{{/if}} {{/if}}
</div>
</div>
{{/baseEditDialog}}
{{/with}}
</template>
<template name="multiplierEffectValue">
<paper-dropdown-menu class="damageMultiplierDropDown"
label="Damage Multiplier">
<paper-dropdown layered
class="dropdown">
<core-menu class="menu multiplierMenu"
<paper-dropdown-menu class="damageMultiplierDropDown" label="Damage Multiplier" dynamic-align>
<dicecloud-selector class="menu multiplierMenu dropdown-content"
selected={{value}}>
<paper-item name="0.5">Resistance</paper-item>
<paper-item name="2">Vulnerability</paper-item>
<paper-item name="0">Immunity</paper-item>
</core-menu>
</paper-dropdown>
</dicecloud-selector>
</paper-dropdown-menu>
<div flex></div>
</template>

View File

@@ -1,4 +1,5 @@
//TODO add dexterity armor
// jscs:disable maximumLineLength
var stats = [
{stat: "strength", name: "Strength", group: "Ability Scores"},
{stat: "dexterity", name: "Dexterity", group: "Ability Scores"},
@@ -68,9 +69,11 @@ var stats = [
{stat: "psychicMultiplier", name: "Psychic", group: "Weakness/Resistance"},
{stat: "radiantMultiplier", name: "Radiant", group: "Weakness/Resistance"},
{stat: "slashingMultiplier", name: "Slashing", group: "Weakness/Resistance"},
{stat: "thunderMultiplier", name: "Thunder", group: "Weakness/Resistance"}
{stat: "thunderMultiplier", name: "Thunder", group: "Weakness/Resistance"},
];
// jscs:enable maximumLineLength
var statsDict = _.indexBy(stats, "stat");
var statGroups = _.groupBy(stats, "group");
var statGroupNames = _.keys(statGroups);
@@ -80,7 +83,7 @@ var attributeOperations = [
{name: "Add", operation: "add"},
{name: "Multiply", operation: "mul"},
{name: "Min", operation: "min"},
{name: "Max", operation: "max"}
{name: "Max", operation: "max"},
];
var skillOperations = [
{name: "Add", operation: "add"},
@@ -91,10 +94,22 @@ var skillOperations = [
{name: "Disadvantage", operation: "disadvantage"},
{name: "Passive Bonus", operation: "passiveAdd"},
{name: "Automatically Fail", operation: "fail"},
{name: "Conditional Benefit", operation: "conditional"}
{name: "Conditional Benefit", operation: "conditional"},
];
Template.effectEdit.onRendered(function(){
_.defer(() => {
const statElement = this.find(".statMenu .iron-selected");
statElement && statElement.scrollIntoView();
const opElement = this.find(".operationMenu .iron-selected");
opElement && opElement.scrollIntoView();
});
});
Template.effectEdit.helpers({
effect: function(){
return Effects.findOne(this.id);
},
statGroups: function(){
return statGroupNames;
},
@@ -112,67 +127,129 @@ Template.effectEdit.helpers({
return attributeOperations;
}
},
effectValueTemplate: function(){
//resistance/vulnerability template
showMultiplierOperations: function(){
var stat = statsDict[this.stat];
return stat && stat.group === "Weakness/Resistance"
},
showEffectValueInput: function(){
var stat = statsDict[this.stat];
var group = stat && stat.group;
if (group === "Weakness/Resistance") return "multiplierEffectValue";
if (
group === "Weakness/Resistance"
) return false;
var op = this.operation;
if (!op) return null;
//operations that don't need templates
if (op === "advantage" || op === "disadvantage" || op === "fail") return null;
//default template
return "regularEffectValue";
}
});
Template.regularEffectValue.helpers({
if (
!op ||
op === "advantage" ||
op === "disadvantage" ||
op === "fail"
) return false;
return true;
},
effectValue: function(){
return this.calculation || this.value;
}
},
isGroupSelected: function(groupName, statName){
var stat = statsDict[statName]
return stat && (stat.group === groupName);
},
});
var setStat = function(statName, effectId){
var setter = {stat: statName};
var effect = Effects.findOne(this._id);
var group = statsDict[statName].group;
if (group === "Saving Throws" || group === "Skills"){
// Skills must have a valid skill operation
if (!_.contains(
_.map(skillOperations, ao => ao.operation),
effect.operation
)){
setter.operation = "add";
}
} else if (group !== "Weakness/Resistance"){
// Attributes must have a valid attribute operation
if (!_.contains(
_.map(attributeOperations, ao => ao.operation),
effect.operation
)){
setter.operation = "base";
}
} else {
// Weakness/Resistance must have a mul operation and value
if (effect.operation !== "mul"){
setter.operation = "mul";
}
if (!_.contains([0, 0.5, 2], effect.value)){
setter.value = 0.5;
}
}
Effects.update(effectId, {$set: setter});
};
var scrollAnimationId;
var scrollElementToView = element => {
var scrollFunction = function(){
element.scrollIntoView();
scrollAnimationId = requestAnimationFrame(scrollFunction);
};
return scrollFunction;
}
Template.effectEdit.events({
"tap .deleteEffect": function(event){
Effects.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Effects", "Effect");
"click #deleteButton": function(event, instance){
Effects.softRemoveNode(instance.data.id);
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
popDialogStack();
},
"core-select .statDropDown": function(event){
"click .statGroupTitle": function(event, instance){
var groupName = this.toString();
var firstStat = statGroups[groupName][0].stat;
setStat(firstStat, instance.data.id);
scrollAnimationId = requestAnimationFrame(scrollElementToView(event.target));
_.delay(() => cancelAnimationFrame(scrollAnimationId), 300);
},
"iron-select .statMenu": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var statName = detail.item.getAttribute("name");
if (statName == this.stat) return;
Effects.update(this._id, {$set: {stat: statName}});
setStat(statName, this._id);
},
"core-select .operationDropDown": function(event){
"iron-select .operationMenu": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var opName = detail.item.getAttribute("name");
if (opName == this.operation) return;
Effects.update(this._id, {$set: {operation: opName}});
},
"core-select .damageMultiplierDropDown": function(event){
"iron-select .multiplierMenu": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = +detail.item.getAttribute("name");
if (value == this.value) return;
Effects.update(this._id, {$set: {
value: value,
calculation: "",
operation: "mul"
operation: "mul",
}});
},
"change .effectValueInput": function(event){
"change .effectValueInput, input .effectValueInput":
_.debounce(function(event){
var value = event.currentTarget.value;
var numValue = +value;
var numValue = value === "" ? NaN : +value;
if (_.isFinite(numValue)){
if (this.value === numValue) return;
Effects.update(this._id, {$set: {value: numValue, calculation: ""}});
Effects.update(this._id, {
$set: {value: numValue},
$unset: {calculation: ""},
});
} else if (_.isString(value)){
if (this.calculation === value) return;
Effects.update(this._id, {$set: {value: "", calculation: value}});
Effects.update(this._id, {
$set: {calculation: value},
$unset: {value: ""},
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}
}
}, 400),
});

View File

@@ -1,6 +1,4 @@
<template name="effectView">
<tr>
<td>{{statName}}</td>
<td>{{operationName}}{{statValue}}</td>
</tr>
<td>{{statName}}</td>
<td>{{operationName}}{{statValue}}</td>
</template>

View File

@@ -1,4 +1,3 @@
//TODO add dexterity armor
var stats = {
"strength":{"name":"Strength"},
"dexterity":{"name":"Dexterity"},
@@ -99,7 +98,7 @@ var operations = {
base: {name: "Base Value: "},
proficiency: {name: "Proficiency"},
add: {name: "+"},
mul: {name: "x"},
mul: {name: "×"},
min: {name: "Min: "},
max: {name: "Max: "},
advantage: {name: "Advantage"},
@@ -131,8 +130,10 @@ Template.effectView.helpers({
return stats[this.stat] && stats[this.stat].name || "No Stat";
},
operationName: function(){
if (this.operation === "proficiency" ||
this.operation === "conditional") return null;
if (
this.operation === "proficiency" ||
this.operation === "conditional"
) return null;
if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance")
return null;
if (this.operation === "add" && evaluateEffect(this.charId, this) < 0)
@@ -141,9 +142,11 @@ Template.effectView.helpers({
operations[this.operation].name || "No Operation";
},
statValue: function(){
if (this.operation === "advantage" ||
this.operation === "disadvantage" ||
this.operation === "fail"){
if (
this.operation === "advantage" ||
this.operation === "disadvantage" ||
this.operation === "fail"
){
return null;
}
if (this.operation === "proficiency"){

View File

@@ -0,0 +1,3 @@
.effectsEditList .effect {
background: white;
}

View File

@@ -1,13 +1,20 @@
<!--needs to be given charId, parentId and parentCollection-->
<template name="effectsEditList">
{{#if effects.count}}
<hr class="vertMargin">
<div id="effects">
<h2>Effects</h2>
{{#each effects}}
{{>effectEdit}}
{{/each}}
{{#if effects.length}}
<div class="effectsEditList">
<div class="paper-font-title">Effects</div>
<table class="wideTable" style="width: 100%;">
{{#each effects}}
<tr class="effect" data-id={{_id}}>
{{>effectView}}
<td>
<paper-icon-button class="edit-effect" icon="create">
</paper-icon-button>
</td>
</tr>
{{/each}}
</table>
</div>
{{/if}}
<paper-button id="addEffectButton" class="red-button" raised>Add Effect</paper-button>
</template>
</template>

View File

@@ -8,17 +8,17 @@ Template.effectsEditList.helpers({
if (this.parentGroup){
selector["parent.group"] = this.parentGroup;
}
var cursor = Effects.find(selector);
return cursor;
var effects = Effects.find(selector).fetch();
return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
}
});
Template.effectsEditList.events({
"tap #addEffectButton": function(){
"tap #addEffectButton": function(event, instance){
if (!_.isBoolean(this.enabled)) {
this.enabled = true;
}
Effects.insert({
const effectId = Effects.insert({
name: this.name,
charId: this.charId,
parent: {
@@ -29,5 +29,18 @@ Template.effectsEditList.events({
operation: "add",
enabled: this.enabled,
});
pushDialogStack({
template: "effectEdit",
data: {id: effectId},
element: event.currentTarget,
returnElement: () => instance.find(`tr.effect[data-id='${effectId}']`),
});
},
"tap .edit-effect": function(event, template){
pushDialogStack({
template: "effectEdit",
data: {id: this._id},
element: event.currentTarget.parentElement.parentElement,
});
},
});

View File

@@ -1,12 +1,11 @@
<!--needs to be given charId, (parentId or stat) and type-->
<template name="effectsViewList">
{{#if effects.count}}
<hr style="margin: 16px 0 16px 0;">
{{#if effects.length}}
<div class="effects">
<h2 class="spaceAfter">Effects</h2>
<div class="spaceAfter paper-font-title">Effects</div>
<table class="wideTable">
{{#each effects}}
{{>effectView}}
<tr>{{>effectView}}</tr>
{{/each}}
</table>
</div>

View File

@@ -2,11 +2,14 @@ Template.effectsViewList.helpers({
effects: function(){
var selector = {
"parent.id": this.parentId,
"charId": this.charId
"charId": this.charId,
};
if (this.parentGroup){
selector["parent.group"] = this.parentGroup;
}
return Effects.find(selector, {fields: {parent: 0}});
let effects = Effects.find(selector, {
fields: {parent: 0},
}).fetch();
return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
}
});

View File

@@ -0,0 +1,4 @@
.exportDialog .iiexport {
overflow-y: auto;
width: 100% !important;
}

View File

@@ -0,0 +1,31 @@
<template name="exportDialog">
<div class="exportDialog fit layout vertical">
{{#with character}}
<app-header fixed effects="waterfall">
<app-toolbar>
<div main-title>Export Character to Improved Initiative</div>
</app-toolbar>
</app-header>
<div class="form flex layout vertical">
<paper-toggle-button id="exportFeatures" checked={{settings.exportFeatures}}>
Features
</paper-toggle-button>
<paper-toggle-button id="exportAttacks" checked={{settings.exportAttacks}}>
Attacks
</paper-toggle-button>
<paper-toggle-button id="exportDescription" checked={{settings.exportDescription}}>
Description
</paper-toggle-button>
<div class="paper-font-title padded">JSON</div>
<textarea class="flex iiexport">{{improvedInitiativeJson}}</textarea>
<paper-button id="copyExportButton" class="red-button" raised>
<iron-icon icon="content-copy"></iron-icon>
Copy to Clipboard
</paper-button>
</div>
<div class="buttons layout horizontal end-justified">
<paper-button class="doneButton"> Done </paper-button>
</div>
{{/with}}
</div>
</template>

View File

@@ -0,0 +1,60 @@
Template.exportDialog.helpers({
character: function(){
return Characters.findOne(this._id);
},
improvedInitiativeJson: function(){
var options = {
features: this.settings.exportFeatures,
attacks: this.settings.exportAttacks,
description: this.settings.exportDescription,
}
return improvedInitiativeJson(this._id, options);
},
});
Template.exportDialog.events({
"change #exportFeatures": function(event, template){
Characters.update(this._id, {$set: {
"settings.exportFeatures": event.target.checked,
}});
},
"change #exportAttacks": function(event, template){
Characters.update(this._id, {$set: {
"settings.exportAttacks": event.target.checked,
}});
},
"change #exportDescription": function(event, template){
Characters.update(this._id, {$set: {
"settings.exportDescription": event.target.checked,
}});
},
"click #copyExportButton": function(event, template){
var copyTextarea = template.find(".iiexport");
copyTextarea.select();
var msg;
try {
var successful = document.execCommand("copy");
var msg = successful ? "JSON copied to clipboard" : "Unable to copy JSON";
} catch (err) {
msg = "Unable to copy JSON";
} finally {
clearSelection();
GlobalUI.toast(msg);
}
},
"click .doneButton": function(event, instance){
popDialogStack();
},
});
var clearSelection = function(){
if (window.getSelection) {
if (window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) { // IE?
document.selection.empty();
}
};

View File

@@ -10,10 +10,9 @@
<template name="featureDetails">
{{#if or canEnable hasUses}}
<div layout horizontal center justified wrap>
<div class="layout horizontal center justified wrap">
{{#if canEnable}}
<div>enabled:</div>
<paper-checkbox class="sideMargin" checked={{enabled}}></paper-checkbox>
<paper-checkbox class="sideMargin" checked={{enabled}}>enabled</paper-checkbox>
{{/if}}
{{#if hasUses}}
<div class="subhead" style="margin-right: 16px">
@@ -21,7 +20,7 @@
</div>
{{/if}}
{{#if hasUses}}
<div layout horizontal>
<div class="layout horizontal">
<paper-button class="useFeature" disabled={{noUsesLeft}}>Use</paper-button>
<paper-button class="resetFeature" disabled={{usesFull}}>Reset</paper-button>
</div>
@@ -41,44 +40,36 @@
<template name="featureEdit">
<!--name-->
<paper-input id="featureNameInput" class="fullwidth" label="Name" floatinglabel value={{name}}></paper-input>
<paper-input id="featureNameInput" class="fullwidth" label="Name" value={{name}}></paper-input>
<hr class="vertMargin">
<div layout horizontal center style="height: 60px;">
<paper-dropdown-menu id="enabledDropdown" label="Enable Feature">
<paper-dropdown layered class="dropdown">
<core-menu id="enabledMenu" class="menu" selected={{enabledSelection}} on-tap="onStatMenuTap">
<paper-item name="alwaysEnabled"> Always Enabled </paper-item>
<paper-item name="enabled"> Enabled </paper-item>
<paper-item name="disabled"> Disabled </paper-item>
</core-menu>
</paper-dropdown>
<div class="layout horizontal center wrap justified">
<paper-dropdown-menu class=flex label="Enable Feature" style="flex-basis: 150px; max-width: 200px;">
<dicecloud-selector selected={{enabledSelection}} class="dropdown-content enabled-dropdown">
<paper-item name="alwaysEnabled" style="width: 150px;">
Always Enabled
</paper-item>
<paper-item name="enabled">
Enabled
</paper-item>
<paper-item name="disabled">
Disabled
</paper-item>
</dicecloud-selector>
</paper-dropdown-menu>
<div layout horizontal center class="sideMargin">
<div>Limit Uses: </div>
<paper-toggle-button id="limitUseCheck"
class="sideMargin"
checked={{usesSet}}
role="button"
aria-pressed="false"
tabindex="0"
touch-action="pan-y">
</paper-toggle-button>
</div>
<paper-toggle-button id="limitUseCheck" checked={{usesSet}} style="margin: 0 16px; height: 62px;">
Limit uses
</paper-toggle-button>
{{#if usesSet}}
<paper-input flex id="usesInput" label="Uses" floatinglabel value={{uses}}></paper-input>
<paper-input id="usesInput" class="flex" label="Uses" value={{uses}} style="flex-basis: 100px; max-width: 200px">
{{> formulaSuffix}}
</paper-input>
{{else}}
<div class="flex" style="flex-basis: 100px; max-width: 200px"></div>
{{/if}}
</div>
<hr class="vertMargin">
<!--description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="featureDescriptionInput" placeholder aria-label="Description" value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
<paper-textarea label="Description" id="featureDescriptionInput" value={{description}}></paper-textarea>
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}

View File

@@ -11,7 +11,7 @@ Template.featureDialog.events({
"tap #deleteButton": function(event, instance){
Features.softRemoveNode(instance.data.featureId);
GlobalUI.deletedToast(instance.data.featureId, "Features", "Feature");
GlobalUI.closeDetail();
popDialogStack();
},
});
@@ -31,11 +31,11 @@ Template.featureDetails.helpers({
});
Template.featureDetails.events({
"tap .useFeature": function(event){
"click .useFeature": function(event){
var featureId = this._id;
Features.update(featureId, {$inc: {used: 1}});
},
"tap .resetFeature": function(event){
"click .resetFeature": function(event){
var featureId = this._id;
Features.update(featureId, {$set: {used: 0}});
},
@@ -46,10 +46,6 @@ Template.featureDetails.events({
},
});
Template.featureEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.featureEdit.helpers({
usesSet: function(){
return _.isString(this.uses);
@@ -67,32 +63,60 @@ Template.featureEdit.helpers({
},
});
const debounce = (f) => _.debounce(f, 300);
Template.featureEdit.events({
"change #featureNameInput": function(event){
var name = Template.instance().find("#featureNameInput").value;
Features.update(this._id, {$set: {name: name}});
},
"change #featureDescriptionInput": function(event){
var description = Template.instance().find("#featureDescriptionInput").value;
Features.update(this._id, {$set: {description: description}});
},
"change #limitUseCheck": function(event){
"input #featureNameInput": debounce(function(event){
const input = event.currentTarget;
var name = input.value;
if (!name){
input.invalid = true;
input.errorMessage = "Name is required";
} else {
input.invalid = false;
Features.update(this._id, {
$set: {name: name}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}
}),
"input #featureDescriptionInput": debounce(function(event){
var description = event.currentTarget.value;
Features.update(this._id, {
$set: {description: description}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"change #limitUseCheck": debounce(function(event){
var currentUses = this.uses;
var featureId = this._id;
if (event.target.checked && !_.isString(currentUses)){
Features.update(featureId, {$set: {uses: ""}}, {removeEmptyStrings: false});
Features.update(featureId, {
$set: {uses: ""}
}, {
removeEmptyStrings: false
});
} else if (!event.target.checked && _.isString(currentUses)){
Features.update(featureId, {$unset: {uses: ""}});
Features.update(featureId, {
$unset: {uses: ""}
});
}
},
"change #usesInput, input #quantityInput": function(event){
var value = event.target.value;
}),
"input #usesInput, input #quantityInput": debounce(function(event){
var value = event.currentTarget.value;
var featureId = this._id;
Features.update(featureId, {$set: {uses: value}});
},
"core-select #enabledDropdown": function(event){
Features.update(featureId, {
$set: {uses: value}
}, {
removeEmptyStrings: false,
});
}),
"iron-select .enabled-dropdown": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = detail.item.getAttribute("name");
var setter;
if (value === "enabled"){

View File

@@ -2,14 +2,6 @@
cursor: pointer;
}
.features {
display: flex !important;
justify-content: center;
align-items: stretch;
flex-wrap: wrap;
padding: 4px;
}
.featureCardTop {
margin-bottom: 8px;
}
@@ -18,7 +10,7 @@
white-space: pre-line;
}
.resourceCards paper-shadow.healthCard {
.resourceCards paper-material.healthCard {
width: 100%;
}

View File

@@ -1,44 +1,41 @@
<template name="features">
<div fit>
<div class="scroll-y" fit>
<div class="column-container">
<!--expertiseDice-->
{{>resource name="expertiseDice" title="Expertise Dice" color="teal" char=this}}
<!--ki-->
{{>resource name="ki" title="Ki Points" color="teal" char=this}}
<!--rages-->
{{>resource name="rages" title="Rages" color="teal" char=this}}
<!--sorceryPoints-->
{{>resource name="sorceryPoints" title="Sorcery Points" color="teal" char=this}}
<!--superiorityDice-->
{{>resource name="superiorityDice" title="Superiority Dice" color="teal" char=this}}
<div class="features">
<div class="column-container animation-slider">
<!--expertiseDice-->
{{>resource name="expertiseDice" title="Expertise Dice" color="teal" char=this}}
<!--ki-->
{{>resource name="ki" title="Ki Points" color="teal" char=this}}
<!--rages-->
{{>resource name="rages" title="Rages" color="teal" char=this}}
<!--sorceryPoints-->
{{>resource name="sorceryPoints" title="Sorcery Points" color="teal" char=this}}
<!--superiorityDice-->
{{>resource name="superiorityDice" title="Superiority Dice" color="teal" char=this}}
<!--Attacks-->
<div>
<paper-shadow class="card">
<!--Attacks-->
<div>
<paper-material class="card">
<div class="top white">
Attacks
</div>
<div class="bottom list">
{{#each attacks}}
<div class="item-slot">
<div class="flexible attack item"
hero-id="main" {{detailHero}}>
<div layout horizontal>
<div class="headline"
style="margin-right: 16px;"
layout horizontal center>
<div class="flexible attack item">
<div class="layout horizontal">
<div class="paper-font-headline layout horizontal center"
style="margin-right: 16px;">
{{evaluateSigned ../_id attackBonus}}
</div>
<div flex layout vertical>
<div class="body2">
<div class="flex layout vertical">
<div class="paper-font-body2">
{{name}}
</div>
<div>
{{evaluateString ../_id damage}}&nbsp;{{damageType}}
</div>
{{#if details}}
<div class="caption">
<div>
{{details}}
</div>
{{/if}}
@@ -48,47 +45,44 @@
</div>
{{/each}}
</div>
</paper-shadow>
</div>
</paper-material>
</div>
<!--Proficiencies-->
<div>
<paper-shadow class="card">
<!--Proficiencies-->
<div>
<paper-material class="card">
<div class="white top">
Proficiencies
</div>
<div flex class="bottom list">
{{#if weaponProfs.count}}
<div class="subhead">Weapons</div>
<div class="paper-font-subhead">Weapons</div>
{{/if}}
{{#each weaponProfs}}
{{> proficiencyListItem}}
{{/each}}
{{#if armorProfs.count}}
<div class="subhead">Armor</div>
<div class="paper-font-subhead">Armor</div>
{{/if}}
{{#each armorProfs}}
{{> proficiencyListItem}}
{{/each}}
{{#if toolProfs.count}}
<div class="subhead">Tools</div>
<div class="paper-font-subhead">Tools</div>
{{/if}}
{{#each toolProfs}}
{{> proficiencyListItem}}
{{/each}}
</div>
</paper-shadow>
</div>
</paper-material>
</div>
<!--features-->
{{#each features}}
<div>
<paper-shadow class="card featureCard"
hero-id="main" {{detailHero}}>
<div class="top {{colorClass}} subhead"
layout horizontal
hero-id="toolbar" {{detailHero}}>
<div flex hero-id="title" {{detailHero}}>
<!--features-->
{{#each features}}
<div>
<paper-material class="card featureCard" data-id={{_id}}>
<div class="top {{colorClass}} paper-font-subhead layout horizontal">
<div class="flex">
{{name}}
</div>
{{#if hasUses}}
@@ -97,22 +91,22 @@
</div>
{{/if}}
{{#if canEnable}}
<core-tooltip label="Feature enabled"
position="left">
<div>
<paper-checkbox class="enabledCheckbox"
checked={{enabled}}
disabled={{#unless canEditCharacter charId}}true{{/unless}}>
</paper-checkbox>
</core-tooltip>
<paper-tooltip position="left">Feature enabled</paper-tooltip>
</div>
{{/if}}
</div>
{{#if description}}
<div flex class="bottom">
{{#markdown}}{{evaluateString charId shortDescription}}{{/markdown}}
<div class="bottom flex">
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
</div>
{{/if}}
{{#if hasUses}}
<div layout horizontal center end-justified>
<div class="layout horizontal center end-justified">
<paper-button class="useFeature"
disabled={{noUsesLeft}}>
Use
@@ -123,21 +117,16 @@
</paper-button>
</div>
{{/if}}
</paper-shadow>
</div>
{{/each}}
</div>
<div class="fab-buffer"></div>
</paper-material>
</div>
{{/each}}
</div>
{{#if canEditCharacter _id}}
<paper-fab id="addFeature"
class="floatyButton"
icon="add"
title="Add"
role="button"
tabindex="0"
aria-label="Add"
hero-id="main"></paper-fab>
icon="add">
<paper-tooltip position="left">Add Feature</paper-tooltip>
</paper-fab>
{{/if}}
</div>
</template>
@@ -145,12 +134,8 @@
<template name="resource">
{{#if characterCalculate "attributeBase" char._id name}}
<div>
<paper-shadow class="card"
hero-id="main" {{detailHero name char._id}}
layout horizontal>
<div class="left {{getColor}} display1 white-text"
hero-id="toolbar" {{detailHero name char._id}}
layout horizontal center>
<paper-material class="card layout horizontal">
<div class="left {{getColor}} paper-font-display1 white-text layout horizontal center">
<div style="margin-right: 8px;">
<paper-icon-button class="resourceUp"
icon="arrow-drop-up"
@@ -164,11 +149,10 @@
<div>{{characterCalculate "attributeValue" char._id name}}</div>
<!--<div>/{{char.attributeBase name}}</div>-->
</div>
<div class="right clickable"
flex layout horizontal center>
<div class="right clickable flex layout horizontal center">
{{title}}
</div>
</paper-shadow>
</paper-material>
</div>
{{/if}}
</template>

View File

@@ -3,11 +3,6 @@ Template.features.helpers({
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
return features;
},
shortDescription: function() {
if (_.isString(this.description)){
return this.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0];
}
},
hasUses: function(){
return this.usesValue() > 0;
},
@@ -43,54 +38,45 @@ Template.features.helpers({
});
Template.features.events({
"tap #addFeature": function(event){
"click #addFeature": function(event, instance){
var featureId = Features.insert({
name: "New Feature",
charId: this._id,
enabled: true,
alwaysEnabled: true,
});
GlobalUI.setDetail({
pushDialogStack({
template: "featureDialog",
data: {featureId: featureId, charId: this._id, startEditing: true},
heroId: featureId,
element: event.currentTarget,
returnElement: () => instance.find(`.featureCard[data-id='${featureId}']`),
});
},
"tap #addAttackButton": function(event){
var charId = this._id;
Attacks.insert({
charId: charId
}, function(error, id){
if (!error){
GlobalUI.setDetail({
template: "attackDialog",
data: {attackId: id, charId: charId},
heroId: id,
});
}
});
},
"tap .featureCard .top": function(event){
"click .featureCard .top": function(event){
var featureId = this._id;
var charId = Template.parentData()._id;
GlobalUI.setDetail({
pushDialogStack({
template: "featureDialog",
data: {featureId: featureId, charId: charId},
heroId: featureId,
element: event.currentTarget.parentElement,
});
},
"tap .attack": function(event){
openParentDialog(this.parent, this.charId, this._id);
"click .attack": function(event){
openParentDialog({
parent: this.parent,
charId: this.charId,
element: event.currentTarget,
});
},
"tap .useFeature": function(event){
"click .useFeature": function(event){
var featureId = this._id;
Features.update(featureId, {$inc: {used: 1}});
},
"tap .resetFeature": function(event){
"click .resetFeature": function(event){
var featureId = this._id;
Features.update(featureId, {$set: {used: 0}});
},
"tap .enabledCheckbox": function(event){
"click .enabledCheckbox": function(event){
event.stopPropagation();
},
"change .enabledCheckbox": function(event){
@@ -122,7 +108,7 @@ Template.resource.helpers({
});
Template.resource.events({
"tap .resourceUp": function(event){
"click .resourceUp": function(event){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
if (value < base){
@@ -131,7 +117,7 @@ Template.resource.events({
Characters.update(this.char._id, modifier, {validate: false});
}
},
"tap .resourceDown": function(event){
"click .resourceDown": function(event){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
var modifier = {$inc: {}};
@@ -139,11 +125,11 @@ Template.resource.events({
Characters.update(this.char._id, modifier, {validate: false});
}
},
"tap .right": function(event, instance) {
GlobalUI.setDetail({
"click .right": function(event, instance) {
pushDialogStack({
template: "attributeDialog",
data: {name: this.title, statName: this.name, charId: this.char._id},
heroId: this.char._id + this.name,
element: event.currentTarget.parentElement,
});
},
});

View File

@@ -0,0 +1,15 @@
.carryCapacityBar {
background-color: #7DC580;
background-color: rgba(255,255,255,0.27);
position: relative;
height: 4px;
}
.carryCapacityBar div{
height: 100%;
position: absolute;
}
.carryCapacityBar .tick {
border-right: solid 2px #E5E5E5;
border-right-color: rgba(255,255,255,0.54);
}

View File

@@ -24,7 +24,7 @@ Template.carryCapacityBar.onCreated(function() {
var self = this;
self.carriedFraction = new ReactiveVar(0);
self.autorun(function() {
self.carriedFraction.set(getFractionCarried(self.data));
self.carriedFraction.set(getFractionCarried(Template.currentData()));
});
});

View File

@@ -1,14 +0,0 @@
.carryCapacityBar {
background-color: #7DC580;
background-color: rgba(255,255,255,0.27);
position: relative;
height: 4px;
div{
height: 100%;
position: absolute;
}
.tick {
border-right: solid 2px #E5E5E5;
border-right-color: rgba(255,255,255,0.54);
}
}

View File

@@ -1,17 +1,17 @@
<template name="carryDialog">
{{#baseDialog title="Weight Carried" class=color hideEdit=true}}
<div layout horizontal center-justified end>
<div class="display2">
<div class="layout horizontal center-justified end">
<div class="paper-font-display2">
{{round carriedWeight 1}}
</div>
<div class="display1">
<div class="paper-font-display1">
lbs
</div>
</div>
<hr class="vertMargin">
{{> carryCapacityTable}}
{{/baseDialog}}
</template>
</template>

View File

@@ -11,32 +11,26 @@
<template name="containerEdit">
<paper-input id="containerNameInput"
label="Name"
floatinglabel
value={{name}}></paper-input>
<div layout horizontal around-justified wrap>
<paper-input-decorator label="Weight" floatinglabel>
<input id="weightInput" type="number" value={{weight}}>
</paper-input-decorator>
<paper-input-decorator label="Value" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
<div class="layout horizontal around-justified wrap">
<paper-input id="weightInput" label="Weight" type="number" value={{weight}}>
</paper-input>
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
</paper-input>
</div>
<hr class="vertMargin">
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="containerDescriptionInput" placeholder aria-label="Description" value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
<paper-textarea label="Description" id="containerDescriptionInput" value={{description}}>
</paper-textarea>
</template>
<template name="containerView">
<div layout horizontal wrap center justified>
<div class="layout horizontal wrap center justified">
<table class="summaryTable fullwidth">
<tr><td>Container</td><td>{{round weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{round contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="body2"><td>Total</td><td>{{round totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
<tr><td>Container</td><td>{{round weight}} lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{round contentsWeight}} lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="paper-font-body2"><td>Total</td><td>{{round totalWeight}} lbs</td><td>{{longValueString totalValue}}</td></tr>
</table>
</div>
{{#if description}}

View File

@@ -14,30 +14,44 @@ Template.containerDialog.events({
instance.data.containerId,
"Containers", "Container and contents"
);
GlobalUI.closeDetail();
popDialogStack();
},
});
Template.containerEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.containerEdit.events({
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors
"change #containerNameInput": function(event){
"input #containerNameInput": function(event){
var name = Template.instance().find("#containerNameInput").value;
Containers.update(this._id, {$set: {name: name}});
Containers.update(this._id, {
$set: {name: name}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
},
"change #weightInput, input #weightInput": function(event){
var weight = +Template.instance().find("#weightInput").value;
Containers.update(this._id, {$set: {weight: weight}});
Containers.update(this._id, {
$set: {weight: weight}
}, {
removeEmptyStrings: false,
});
},
"change #valueInput, input #valueInput": function(event){
var value = +Template.instance().find("#valueInput").value;
Containers.update(this._id, {$set: {value: value}});
Containers.update(this._id, {
$set: {value: value}
}, {
removeEmptyStrings: false,
});
},
"change #containerDescriptionInput": function(event, instance){
"input #containerDescriptionInput": function(event, instance){
var description = instance.find("#containerDescriptionInput").value;
Containers.update(this._id, {$set: {description: description}});
Containers.update(this._id, {
$set: {description: description}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
},
});

View File

@@ -1,169 +1,167 @@
<template name="inventory">
<div fit>
<div id="inventory" class="scroll-y" fit>
<div class="column-container">
<!--Net Worth-->
<div>
<paper-shadow class="card">
<div class="white top" layout horizontal center>
<div class="subhead" flex>
Net Worth
</div>
<div>
{{valueString netWorth}}
</div>
<div id="inventory" class="animation-slider">
<div class="column-container">
<!--Net Worth-->
<div>
<paper-material class="card">
<div class="white top layout horizontal center">
<div class="paper-font-subhead flex">
Net Worth
</div>
</paper-shadow>
</div>
<!--Weight Carried-->
<div>
<paper-shadow class="card"
hero-id="main" {{detailHero "weightCarried" _id}}>
<div class="top green white-text weightCarried"
hero-id="toolbar" {{detailHero "weightCarried" _id}}
layout horizontal center>
<div class="subhead" flex>
Weight Carried
</div>
<div>
{{round weightCarried}}lbs
</div>
</div>
<div class="bottom green" style="padding: 0;">
{{> carryCapacityBar}}
</div>
{{#if encumberedBuffs.count}}
<div class="bottom list">
{{#each encumberedBuffs}}
<div class="item-slot">
<div class="item buff"
hero-id="main" {{detailHero}}
layout horizontal center>
<div flex>
<core-icon icon="work"
style="margin-right: 16px">
</core-icon>
{{name}}
</div>
</div>
</div>
{{/each}}
</div>
{{/if}}
</paper-shadow>
</div>
<!--Equipment-->
<div>
<paper-shadow class="card equipmentContainer">
<div class="white top" layout horizontal center>
<div class="subhead" flex>
Equipment
</div>
<div class="caption" style="margin-right: 8px">
{{valueString equipmentValue}}
</div>
<div class="caption">
{{round equipmentWeight}}lbs
</div>
</div>
<div flex class="bottom list">
{{#if attuned.count}}
<div class="subhead">Attuned</div>
{{/if}}
{{#each attuned}}
{{>inventoryItem}}
{{/each}}
{{#if attuned.count}}
<div class="subhead">Equipment</div>
{{/if}}
{{#each equipment}}
{{>inventoryItem}}
{{/each}}
</div>
</paper-shadow>
</div>
<!--Carried Items-->
<div>
<paper-shadow class="card carriedContainer">
<div class="white top" layout horizontal center>
<div class="subhead" flex>
Carried
</div>
<div class="caption" style="margin-right: 8px">
{{valueString carriedValue}}
</div>
<div class="caption">
{{round carriedWeight}}lbs
</div>
</div>
<div flex class="bottom list">
{{#each carriedItems}}
{{>inventoryItem}}
{{/each}}
</div>
</paper-shadow>
</div>
{{#each containers}}
<div>
<paper-shadow class="card itemContainer"
hero-id="main" {{detailHero}}>
<div class="top {{colorClass}}"
hero-id="toolbar" {{detailHero}}
layout horizontal center>
<div class="subhead" flex
hero-id="title" {{detailHero}}>
{{valueString netWorth}}
</div>
</div>
</paper-material>
</div>
<!--Weight Carried-->
<div>
<paper-material class="card">
<div class="top green white-text weightCarried layout horizontal center">
<div class="paper-font-subhead flex">
Weight Carried
</div>
<div>
{{round weightCarried}} lbs
</div>
</div>
<div class="bottom green" style="padding: 0;">
{{> carryCapacityBar}}
</div>
{{#if encumberedBuffs.count}}
<div class="bottom list">
{{#each encumberedBuffs}}
<div class="item-slot">
<div class="item buff layout horizontal center">
<div class="flex">
<iron-icon icon="work"
style="margin-right: 16px">
</iron-icon>
{{name}}
</div>
<div class="caption" style="margin-right: 8px">
{{valueString totalValue}}
</div>
<div class="caption" style="margin-right: 8px">
{{round totalWeight}}lbs
</div>
<core-tooltip label="Container carried" position="left">
<paper-checkbox class="carriedCheckbox"
disabled={{#unless canEditCharacter charId}}true{{/unless}}
checked={{isCarried}}>
</paper-checkbox>
</core-tooltip>
</div>
<div class="bottom list">
{{#each items ../_id _id}}
{{>inventoryItem}}
{{/each}}
</div>
</paper-shadow>
</div>
{{/each}}
{{/each}}
</div>
{{/if}}
</paper-material>
</div>
<div class="fab-buffer"></div>
<!--Equipment-->
<div>
<paper-material class="card equipmentContainer">
<div class="white top layout horizontal center">
<div class="paper-font-subhead flex">
Equipment
</div>
<div class="paper-font-caption" style="margin-right: 8px">
{{valueString equipmentValue}}
</div>
<div class="paper-font-caption">
{{round equipmentWeight}} lbs
</div>
</div>
<div flex class="bottom list">
{{#if attuned.count}}
<div class="paper-font-subhead">Attuned</div>
{{/if}}
{{#each attuned}}
{{>inventoryItem}}
{{/each}}
{{#if attuned.count}}
<div class="paper-font-subhead">Equipment</div>
{{/if}}
{{#each equipment}}
{{>inventoryItem}}
{{/each}}
</div>
</paper-material>
</div>
<!--Carried Items-->
<div>
<paper-material class="card carriedContainer">
<div class="white top layout horizontal center">
<div class="paper-font-subhead flex">
Carried
</div>
<div class="paper-font-caption" style="margin-right: 8px">
{{valueString carriedValue}}
</div>
<div class="paper-font-caption">
{{round carriedWeight}} lbs
</div>
</div>
<div flex class="bottom list">
{{#each carriedItems}}
{{>inventoryItem}}
{{/each}}
</div>
</paper-material>
</div>
{{#each containers}}
<div>
<paper-material class="card itemContainer" data-id={{_id}}>
<div class="top {{colorClass}} layout horizontal center">
<div class="paper-font-subhead flex">
{{name}}
</div>
<div class="paper-font-caption" style="margin-right: 8px">
{{valueString totalValue}}
</div>
<div class="paper-font-caption" style="margin-right: 8px">
{{round totalWeight}} lbs
</div>
<div>
<paper-checkbox class="carriedCheckbox"
disabled={{#unless canEditCharacter charId}}true{{/unless}}
checked={{isCarried}}>
</paper-checkbox>
<paper-tooltip position="left"> Container carried</paper-tooltip>
</div>
</div>
<div class="bottom list">
{{#each items ../_id _id}}
{{>inventoryItem}}
{{/each}}
</div>
</paper-material>
</div>
{{/each}}
</div>
{{#if canEditCharacter _id}}
{{#fabMenu}}
<core-tooltip label="New container" position="left">
<paper-fab icon="work"
class="addContainer"
mini>
</paper-fab>
</core-tooltip>
<core-tooltip label="New item" position="left">
<paper-fab icon="note-add"
class="addItem"
mini>
</paper-fab>
</core-tooltip>
{{/fabMenu}}
{{/if}}
<div class="fab-buffer"></div>
</div>
{{#if canEditCharacter _id}}
{{#fabMenu}}
<div>
<paper-fab icon="work"
class="addContainer"
mini>
</paper-fab>
<paper-tooltip position="left"> New container </paper-tooltip>
</div>
<div>
<paper-fab icon="av:library-books"
class="libraryItem"
mini>
</paper-fab>
<paper-tooltip position="left"> Library item </paper-tooltip>
</div>
<div>
<paper-fab icon="note-add"
class="addItem"
mini>
</paper-fab>
<paper-tooltip position="left"> New item </paper-tooltip>
</div>
{{/fabMenu}}
{{/if}}
</template>
<template name="inventoryItem">
<div class="item-slot">
<div class="item {{hidden}} inventoryItem"
hero-id="main" {{detailHero}}
layout horizontal center
draggable={{canEditCharacter charId}}>
<div flex class="itemName">
<div class="item {{hidden}} inventoryItem layout horizontal center"
draggable={{canEditCharacter charId}}
data-id={{_id}}>
<div class="itemName flex">
{{#if ne1 quantity}}{{quantity}}&nbsp;{{/if}}{{pluralName}}
</div>
{{#if settings.showIncrement}}{{#if canEditCharacter charId}}

View File

@@ -91,9 +91,11 @@ Template.inventory.helpers({
},
equipmentWeight: function(){
var weight = 0;
Items.find({charId: this._id, enabled: true},
{fields: {weight : 1, quantity: 1}}
).forEach(function(item){
Items.find({
charId: this._id, enabled: true,
}, {
fields: {weight : 1, quantity: 1}
}).forEach(function(item){
weight += item.totalWeight();
});
return weight;
@@ -121,101 +123,125 @@ Template.inventory.helpers({
});
Template.inventory.events({
"tap .addItem": function(event){
"click .addItem": function(event, instance){
var charId = this._id;
Items.insert({
var itemId = Items.insert({
charId: charId,
parent:{
id: charId,
collection: "Characters",
},
}, function(err, itemId){
if (err) throw err;
GlobalUI.setDetail({
template: "itemDialog",
data: {itemId: itemId, charId: charId, startEditing: true},
heroId: itemId,
});
});
pushDialogStack({
template: "itemDialog",
data: {itemId: itemId, charId: charId, startEditing: true},
element: event.currentTarget,
returnElement: () => $(`[data-id='${itemId}']`).get(0),
});
},
"tap .addContainer": function(event){
"click .libraryItem": function(event, instance){
var charId = this._id;
var itemId = Items.insert({
charId: charId,
parent:{
id: charId,
collection: "Characters",
},
});
pushDialogStack({
template: "itemLibraryDialog",
element: event.currentTarget,
callback: (result) => {
if (!result) {
Items.remove(itemId);
return;
}
// Make the library item into a regular item
let item = _.omit(result, "library", "attacks", "effects");
delete item.settings.category;
// Update the item to match library item
Items.update(itemId, {$set: item});
// Copy over attacks and effects
_.each(result.attacks, (attack) => {
attack.charId = charId;
attack.parent = {id: itemId, collection: "Items"};
Attacks.insert(attack);
});
_.each(result.effects, (effect) => {
effect.charId = charId;
effect.parent = {id: itemId, collection: "Items"};
Effects.insert(effect);
});
},
returnElement: () => $(`[data-id='${itemId}']`).get(0),
})
},
"click .addContainer": function(event, instance){
var containerId = Containers.insert({
name: "New Container",
isCarried: true,
charId: this._id,
});
GlobalUI.setDetail({
pushDialogStack({
template: "containerDialog",
data: {
containerId: containerId,
charId: this.charId,
startEditing: true,
},
heroId: containerId,
element: event.currentTarget,
returnElement: instance.find(`.itemContainer[data-id='${containerId}']`),
});
},
"tap .weightCarried": function(event) {
"click .weightCarried": function(event, instance) {
var charId = this._id;
GlobalUI.setDetail({
pushDialogStack({
template: "carryDialog",
data: {charId: charId, color: "green"},
heroId: charId + "weightCarried",
element: event.currentTarget.parentElement,
});
},
"tap .buff": function(event){
"click .buff": function(event, instance){
var buffId = this._id;
var charId = Template.parentData()._id;
GlobalUI.setDetail({
pushDialogStack({
template: "buffDialog",
data: {buffId: buffId, charId: charId},
heroId: buffId,
element: event.currentTarget,
});
},
"tap .inventoryItem": function(event){
"click .inventoryItem": function(event, instance){
var itemId = this._id;
var charId = Template.parentData()._id;
GlobalUI.setDetail({
pushDialogStack({
template: "itemDialog",
data: {itemId: itemId, charId: charId},
heroId: itemId,
element: event.currentTarget,
returnElement: () => $(`[data-id='${itemId}']`).get(0),
});
},
"hold .inventoryItem": function(event, instance) {
var itemId = this._id;
var charId = Template.parentData()._id;
var containerId = this.parent.id;
GlobalUI.showDialog({
template: "moveItemDialog",
data: {
charId: charId,
itemId: itemId,
containerId: containerId,
},
heading: "Move " + this.pluralName(),
});
},
"tap .incrementButtons": function(event) {
"click .incrementButtons": function(event, instance) {
event.stopPropagation();
},
"tap .addItemQuantity": function(event) {
"click .addItemQuantity": function(event, instance) {
var itemId = this._id;
Items.update(itemId, {$set: {quantity: this.quantity + 1}});
},
"tap .subItemQuantity": function(event) {
"click .subItemQuantity": function(event, instance) {
var itemId = this._id;
Items.update(itemId, {$set: {quantity: this.quantity - 1}});
},
"tap .itemContainer .top": function(event){
GlobalUI.setDetail({
"click .itemContainer .top": function(event, instance){
pushDialogStack({
template: "containerDialog",
data: {containerId: this._id, charId: this.charId},
heroId: this._id,
element: event.currentTarget.parentElement,
});
},
"tap .carriedCheckbox": function(event){
"click .carriedCheckbox": function(event, instance){
event.stopPropagation();
},
"change .carriedCheckbox": function(event){
"change .carriedCheckbox": function(event, instance){
var carried;
if (this.isCarried) carried = false;
else carried = true;
@@ -271,7 +297,7 @@ Template.layout.events({
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
GlobalUI.showDialog({
pushDialogStack({
template: "splitStackDialog",
data: {
id: itemId,
@@ -294,12 +320,11 @@ Template.layout.events({
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
GlobalUI.showDialog({
pushDialogStack({
template: "splitStackDialog",
data: {
id: itemId,
parentCollection: "Characters",
parentId: this._id,
},
});
} else {
@@ -312,7 +337,7 @@ Template.layout.events({
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
GlobalUI.showDialog({
pushDialogStack({
template: "splitStackDialog",
data: {
id: itemId,

View File

@@ -9,11 +9,11 @@
</template>
<template name="itemDetails">
<div layout horizontal wrap center justified class="headline">
{{#if weight}}<div class="sideMargin">{{round totalWeight}}lbs</div>{{/if}}
<div class="paper-font-headline layout horizontal wrap center justified">
{{#if weight}}<div class="sideMargin">{{round totalWeight}} lbs</div>{{/if}}
{{#if value}}<div>{{valueString totalValue}}</div>{{/if}}
</div>
<div layout horizontal wrap class="caption">
<div class="paper-font-caption layout horizontal wrap">
{{#if enabled}}<div class="vertMargin" style="margin-right: 16px">Equipped</div>{{/if}}
{{#if requiresAttunement}}<div class="vertMargin">Requires Attunement</div>{{/if}}
</div>
@@ -26,74 +26,47 @@
</template>
<template name="itemEdit">
<paper-input class="fullwidth" id="itemNameInput" label="Name" floatinglabel value={{name}}></paper-input>
<div layout horizontal wrap>
<paper-input-decorator label="Quantity"
floatinglabel
style="width: 80px">
<input id="quantityInput"
type="number"
value={{quantity}}>
</paper-input-decorator>
<paper-input class="fullwidth" id="itemNameInput" label="Name" value={{name}}></paper-input>
<div class="layout horizontal center wrap">
<paper-input id="quantityInput" type="number" label="Quantity" style="width: 80px" value={{quantity}}>
</paper-input>
{{# if ne1 quantity}}
<paper-input flex id="itemPluralInput"
label="Plural Name"
floatinglabel
value={{plural}}></paper-input>
<paper-input class="flex" id="itemPluralInput" label="Plural Name" value={{plural}} style="flex-basis: 182px;">
</paper-input>
{{else}}
<div class="flex" style="flex-basis: 182px;">
</div>
{{/if}}
</div>
<div center horizontal layout>
<div class="padded">Show increment buttons</div>
<paper-checkbox id="incrementCheckbox"
checked={{settings.showIncrement}}>
<paper-checkbox id="incrementCheckbox" checked={{settings.showIncrement}}>
Show increment buttons
</paper-checkbox>
</div>
<hr class="vertMargin">
<div layout horizontal wrap justified>
<div center horizontal layout>
<div class="padded">Container</div>
{{> containerDropdown}}
</div>
<div center horizontal layout>
<div class="padded">Equipped</div>
<paper-toggle-button id="equippedInput"
checked={{enabled}}
role="button"
aria-pressed="false"
tabindex="0"
touch-action="pan-y">
</paper-toggle-button>
</div>
<div center horizontal layout>
<div class="padded">Requires Attunement</div>
<paper-checkbox id="attunementCheckbox"
checked={{requiresAttunement}}>
</paper-checkbox>
</div>
<div class="layout horizontal center wrap justified" style="margin-top: 16px;">
{{> containerDropdown}}
<paper-toggle-button id="equippedInput" checked={{enabled}}>
Equipped
</paper-toggle-button>
<paper-checkbox id="attunementCheckbox" checked={{requiresAttunement}}>
Requires Attunement
</paper-checkbox>
</div>
<hr class="vertMargin">
<div layout horizontal around-justified>
<paper-input-decorator label="Weight Each (lbs)" floatinglabel>
<input id="weightInput" type="number" value={{weight}}>
</paper-input-decorator>
<div class="layout horizontal around-justified" style="margin-top: 16px;">
<paper-input id="weightInput" type="number" value={{weight}} label="Weight Each (lbs)">
</paper-input>
<!--Value-->
<paper-input-decorator label="Value Each (GP)" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
<paper-input id="valueInput" type="number" value={{value}} label="Value Each (GP)">
</paper-input>
</div>
<hr class="vertMargin">
<!--Description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="itemDescriptionInput" placeholder aria-label="Description" value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
<paper-textarea id="itemDescriptionInput" label="Description" value={{description}}>
<div suffix>
<paper-tooltip position="left" animation-delay="0">This field accepts formulae in {curly brackets}</paper-tooltip>
<iron-icon icon="dicecloud:code-braces"></iron-icon>
</div>
</paper-textarea>
<!--Effects-->
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
<!--Attacks-->
@@ -101,13 +74,11 @@
</template>
<template name="containerDropdown">
<paper-dropdown-menu id="containerDropDown" label="Container">
<paper-dropdown layered class="dropdown">
<core-menu class="menu" selected={{parent.id}}>
{{#each containers}}
<paper-item name={{_id}} class="containerMenuItem">{{name}}</paper-item>
{{/each}}
</core-menu>
</paper-dropdown>
<paper-dropdown-menu label="Container">
<dicecloud-selector class="dropdown-content" id="containerDropDown" selected={{parent.id}}>
{{#each containers}}
<paper-item name={{_id}} class="containerMenuItem">{{name}}</paper-item>
{{/each}}
</dicecloud-selector>
</paper-dropdown-menu>
</template>

View File

@@ -27,60 +27,95 @@ Template.itemDialog.helpers({
});
Template.itemDialog.events({
"tap #editButton": function(event, instance){
"click #editButton": function(event, instance){
instance.editing.set(true);
},
"tap #doneEditingButton": function(event, instance){
"click #doneEditingButton": function(event, instance){
instance.editing.set(false);
},
"color-change": function(event, instance){
Items.update(instance.data.itemId, {$set: {color: event.color}});
},
"tap #deleteButton": function(event, instance){
"click #deleteButton": function(event, instance){
Items.softRemoveNode(instance.data.itemId);
GlobalUI.deletedToast(instance.data.itemId, "Items", "Item");
GlobalUI.closeDetail();
popDialogStack();
},
});
Template.itemEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.itemEdit.helpers({
ne1: function(num){
return num != 1;
},
});
const debounce = (f) => _.debounce(f, 200);
Template.itemEdit.events({
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors
"change #itemNameInput": function(event){
var name = Template.instance().find("#itemNameInput").value;
Items.update(this._id, {$set: {name: name}});
},
"change #itemPluralInput": function(event){
var plural = Template.instance().find("#itemPluralInput").value;
Items.update(this._id, {$set: {plural: plural}});
},
"change #quantityInput": function(event){
var quantity = +Template.instance().find("#quantityInput").value;
Items.update(this._id, {$set: {quantity: quantity}});
},
"change #weightInput": function(event){
var weight = +Template.instance().find("#weightInput").value;
Items.update(this._id, {$set: {weight: weight}});
},
"change #valueInput": function(event){
var value = +Template.instance().find("#valueInput").value;
Items.update(this._id, {$set: {value: value}});
},
"change #itemDescriptionInput": function(event){
var description = Template.instance().find("#itemDescriptionInput").value;
Items.update(this._id, {$set: {description: description}});
},
"input #itemNameInput": debounce(function(event, instance){
const input = event.currentTarget;
var name = input.value;
if (!name){
input.invalid = true;
input.errorMessage = "Name is required";
} else {
input.invalid = false;
Items.update(this._id, {
$set: {name: name}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}
}),
"input #itemPluralInput": debounce(function(event, instance){
var plural = event.currentTarget.value;
Items.update(this._id, {
$set: {plural: plural}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"input #quantityInput": debounce(function(event, instance){
var quantity = +event.currentTarget.value;
Items.update(this._id, {
$set: {quantity: quantity}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"input #weightInput": debounce(function(event, instance){
var weight = +event.currentTarget.value;
Items.update(this._id, {
$set: {weight: weight}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"input #valueInput": debounce(function(event, instance){
var value = +event.currentTarget.value;
Items.update(this._id, {
$set: {value: value}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"input #itemDescriptionInput": debounce(function(event, instance){
var description = event.currentTarget.value;
Items.update(this._id, {
$set: {description: description}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"change #equippedInput": function(event){
var equipped = Template.instance().find("#equippedInput").checked;
var equipped = event.currentTarget.checked;
if (equipped){
Meteor.call("equipItem", this._id, this.charId);
} else {
@@ -104,9 +139,8 @@ Template.containerDropdown.helpers({
});
Template.containerDropdown.events({
"core-select #containerDropDown": function(event){
"iron-select #containerDropDown": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var containerId = detail.item.getAttribute("name");
Meteor.call("moveItemToContainer", Template.currentData()._id, containerId);
}

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